Skip to content

Commit

Permalink
Add name-length rule (#1)
Browse files Browse the repository at this point in the history
* add name-length rule

* add name-length rule
  • Loading branch information
cjmarkham authored Aug 22, 2024
1 parent 72691a1 commit b6d1145
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 27 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ An ESM linter for Gherkin, inspired by [Gherkin Lint](https://github.com/gherkin
```typescript
import GherkinLinter from 'gherkin-linter'

const error = await GherkinLinter(config, ruleConfig)
const errors = await GherkinLinter(config, ruleConfig)
```

`config` is general configuration for the linter and supports the following values:
Expand All @@ -30,10 +30,10 @@ Each rule **must** export a default function. These functions are passed a few a
| Argument | Type | Description |
|------------|-----------------|-----------------------------------|
| ruleConfig | RuleConfig | The configuration for rules |
| fileName | string | The name of the file being linted |
| document | GherkinDocument | The Gherkin Document |
| fileName | string | The name of the file being linted |

Each rule **must** return an array of LintError.
Each rule **must** return an array of LintError or an empty array if no errors.

# Testing

Expand Down
35 changes: 14 additions & 21 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,23 @@ export interface Config {
directory: string
}

interface IndentationRule {
feature: number
background: number
scenario: number
examples: number
example: number
given: number
when: number
then: number
and: number
but: number
}

export enum FileNameFormat {
camel,
kebab,
pascal,
snake,
title,
interface NumericalKeywordValue {
feature?: number
background?: number
scenario?: number
step?: number
examples?: number
example?: number
given?: number
when?: number
then?: number
and?: number
but?: number
}

export interface RuleConfig {
allowedTags?: Array<string>
indentation?: IndentationRule
fileName?: FileNameFormat
indentation?: NumericalKeywordValue
maxScenariosPerFile?: number
nameLength?: NumericalKeywordValue
}
2 changes: 1 addition & 1 deletion src/rules/indentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default (ruleConfig: RuleConfig, document: GherkinDocument): Array<LintEr
child.scenario.steps.forEach((step) => {
const name = step.keyword.trim()
const lowerName = name.toLowerCase()
const expectedIndent = ruleConfig?.indentation[lowerName] || defaultConfig.indentation[lowerName]
const expectedIndent = ruleConfig?.indentation?.[lowerName] || defaultConfig.indentation[lowerName]
if (step.location.column - 1 !== expectedIndent) {
errors.push(
newLintError(
Expand Down
2 changes: 2 additions & 0 deletions src/rules/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import allowedTags from './allowed-tags'
import indentation from './indentation'
import maxScenariosPerFile from './max-scenarios-per-file'
import nameLength from './name-length'

export default {
allowedTags,
indentation,
maxScenariosPerFile,
nameLength,
}
61 changes: 61 additions & 0 deletions src/rules/name-length.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { GherkinDocument } from '@cucumber/messages'
import { RuleConfig } from '../config'
import { LintError, newLintError } from '../error'

const defaultConfig: RuleConfig = {
nameLength: {
feature: 70,
scenario: 70,
step: 70,
},
}

const ruleName = 'name-length'

export default (ruleConfig: RuleConfig, document: GherkinDocument): Array<LintError> => {
if (!document || (document && !document.feature)) {
return
}

const errors: Array<LintError> = []

const expectedFeatureNameLength = ruleConfig?.nameLength?.feature || defaultConfig.nameLength.feature
const expectedScenarioNameLength = ruleConfig?.nameLength?.scenario || defaultConfig.nameLength.scenario
const expectedStepNameLength = ruleConfig?.nameLength?.step || defaultConfig.nameLength.step

if (document.feature.name.length > expectedFeatureNameLength) {
errors.push(
newLintError(
ruleName,
`"Feature" name is too long. Got ${document.feature.name.length}, want ${expectedFeatureNameLength}`,
document.feature.location,
),
)
}

document.feature.children.forEach((child) => {
if (child.scenario.name.length > expectedScenarioNameLength) {
errors.push(
newLintError(
ruleName,
`"Scenario" name is too long. Got ${child.scenario.name.length}, want ${expectedScenarioNameLength}`,
child.scenario.location,
),
)
}

child.scenario.steps.forEach((step) => {
if (step.text.length > expectedStepNameLength) {
errors.push(
newLintError(
ruleName,
`"${step.keyword}" name is too long. Got ${step.text.length}, want ${expectedStepNameLength}`,
step.location,
),
)
}
})
})

return errors
}
16 changes: 14 additions & 2 deletions tests/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Scenario, Step } from '@cucumber/messages'

export const scenario = {
id: '5b68c77b-5e45-4611-97cd-93e9b137f8af',
location: {
Expand All @@ -7,7 +9,17 @@ export const scenario = {
tags: [],
name: 'Scenario 1',
description: '',
steps: [],
steps: [
{
keyword: 'Given',
text: 'Step 1',
location: {
line: 1,
column: 5,
},
id: 'f90a0095-55b5-486c-8c85-1db35916fd95',
},
] as Array<Step>,
examples: [],
keyword: '',
}
} as Scenario
116 changes: 116 additions & 0 deletions tests/name-length.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { expect } from 'chai'
import { GherkinDocument } from '@cucumber/messages'

import allowedTags from '../src/rules/allowed-tags'
import { scenario } from './fixtures'
import nameLength from '../src/rules/name-length'

describe('Name Length', () => {
it('should not return an error if the feature name is the correct length', async () => {
const document: GherkinDocument = {
feature: {
name: 'Name Length',
description: '',
language: '',
keyword: '',
location: {
line: 0,
column: 0,
},
tags: [],
children: [{ scenario }],
},
comments: [],
}

const errors = nameLength({}, document)
expect(errors.length).to.eq(0)
})

it('should return an error if the feature name is an incorrect length', async () => {
const document: GherkinDocument = {
feature: {
name: 'Name Length',
description: '',
language: '',
keyword: '',
location: {
line: 0,
column: 0,
},
tags: [],
children: [{ scenario }],
},
comments: [],
}

const errors = nameLength(
{
nameLength: {
feature: 10,
},
},
document,
)
expect(errors.length).to.eq(1)
expect(errors[0].message).to.eq('"Feature" name is too long. Got 11, want 10')
})

it('should return an error if the scenario name is an incorrect length', async () => {
const document: GherkinDocument = {
feature: {
name: 'Name Length',
description: '',
language: '',
keyword: '',
location: {
line: 0,
column: 0,
},
tags: [],
children: [{ scenario }],
},
comments: [],
}

const errors = nameLength(
{
nameLength: {
scenario: 1,
},
},
document,
)
expect(errors.length).to.eq(1)
expect(errors[0].message).to.eq('"Scenario" name is too long. Got 10, want 1')
})

it('should return an error if the step name is an incorrect length', async () => {
const document: GherkinDocument = {
feature: {
name: 'Name Length',
description: '',
language: '',
keyword: '',
location: {
line: 0,
column: 0,
},
tags: [],
children: [{ scenario }],
},
comments: [],
}

const errors = nameLength(
{
nameLength: {
step: 2,
},
},
document,
)
expect(errors.length).to.eq(1)
expect(errors[0].message).to.eq('"Given" name is too long. Got 6, want 2')
})
})

0 comments on commit b6d1145

Please sign in to comment.