@ohlori/gherkin-lint 4.3.0 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,299 +1,44 @@
1
1
  # Gherkin lint
2
- [![Run NodeJS Tests](https://github.com/gherkin-lint/gherkin-lint/actions/workflows/npm_test.yml/badge.svg)](https://github.com/gherkin-lint/gherkin-lint/actions/workflows/npm_test.yml)
3
- [![Coverage Status](https://coveralls.io/repos/github/gherkin-lint/gherkin-lint/badge.svg?branch=master)](https://coveralls.io/github/gherkin-lint/gherkin-lint?branch=master)
4
- [![npm](https://img.shields.io/npm/v/gherkin-lint.svg?maxAge=2592000)](https://www.npmjs.com/package/gherkin-lint)
5
2
 
6
- Uses [Gherkin](https://github.com/cucumber/gherkin-javascript) to parse feature files and runs linting against the default rules, and the optional rules you specified in your `.gherkin-lintrc` file.
3
+ An unofficial [fork](https://github.com/ohlori/gherkin-lint) of [gherkin-lint](https://github.com/gherkin-lint/gherkin-lint), published as [`@ohlori/gherkin-lint`](https://www.npmjs.com/package/@ohlori/gherkin-lint). Based on upstream `4.2.4`.
7
4
 
8
- ## Installation
9
- ```
10
- npm install gherkin-lint
11
- ```
12
-
13
- ## Demo
14
- To see the output for all the errors that the linter can detect run:
15
- ```
16
- git clone https://github.com/gherkin-lint/gherkin-lint.git
17
- npm run demo
18
- ```
19
- Or check this:
20
- ![console](https://i.imgur.com/Qfp1FQR.png)
21
-
22
-
23
- ## Available rules
5
+ Current fork version: **4.3.2**. It is not affiliated with or endorsed by the original authors.
24
6
 
7
+ ## Changes
25
8
 
26
- | Name | Functionality |
27
- |---------------------------------------------|------------------------------------------------------------------------------------------|
28
- | `no-tags-on-backgrounds` * | Disallows tags on Background |
29
- | `one-feature-per-file` * | Disallows multiple Feature definitions in the same file |
30
- | `up-to-one-background-per-file` * | Disallows multiple Background definition in the same file |
31
- | `no-multiline-steps` * | Disallows mutiline Steps ||   | |
32
- | [`allowed-tags`](#allowed-tags) | Just the listed tags are allowed |
33
- | [`file-name`](#file-name) | Restrict feature file names to a commmon style |
34
- | [`indentation`](#indentation) | Allows the user to specify indentation rules |
35
- | [`max-scenarios-per-file`](#max-scenarios-per-file)| Allows the user to specify the max number of scenarios per feature file |
36
- | [`name-length`](#name-length) | Allows restricting length of Feature/Scenario/Step names |
37
- | [`new-line-at-eof`](#new-line-at-eof) | Disallows/enforces new line at EOF |
38
- | `no-background-only-scenario` | Disallows background when there is just one scenario |
39
- | `no-dupe-feature-names` | Disallows duplicate Feature names |
40
- | [`no-dupe-scenario-names`](#no-dupe-scenario-names)| Disallows duplicate Scenario names |
41
- | `no-duplicate-tags` | Disallows duplicate tags on the same Feature or Scenario |
42
- | `no-empty-background` | Disallows features with backgrounds without steps |
43
- | `no-empty-file` | Disallows empty feature files |
44
- | `no-examples-in-scenarios` | Disallow the use of "Examples" in Scenarios, only allowed in Scenario Outlines |
45
- | `no-files-without-scenarios` | Disallows files with no scenarios |
46
- | `no-homogenous-tags` | Disallows tags present on every Scenario in a Feature, rather than on the Feature itself |
47
- | `no-multiple-empty-lines` | Disallows multiple empty lines |
48
- | `no-partially-commented-tag-lines` | Disallows partially commented tag lines |
49
- | [`no-restricted-patterns`](#no-restricted-patterns) | A list of patterns to disallow globally, or specifically in features, backgrounds, scenarios, or scenario outlines |
50
- | [`no-restricted-tags`](#no-restricted-tags) | Disallow use of particular @tags |
51
- | `no-scenario-outlines-without-examples` | Disallows scenario outlines without examples |
52
- | `no-superfluous-tags` | Disallows tags present on a Feature and a Scenario in that Feature |
53
- | `no-trailing-spaces` | Disallows trailing spaces |
54
- | `no-unnamed-features` | Disallows empty Feature name |
55
- | `no-unnamed-scenarios` | Disallows empty Scenario name |
56
- | `no-unused-variables` | Disallows unused variables in scenario outlines |
57
- | `one-space-between-tags` | Tags on the same line must be separated by a single space |
58
- | [`required-tags`](#required-tags) | Require tags/patterns of tags on Scenarios |
59
- | [`scenario-size`](#scenario-size) | Allows restricting the maximum number of steps in a scenario, scenario outline and background |
60
- | `use-and` | Disallows repeated step names requiring use of And instead |
61
- | `keywords-in-logical-order` | Requires that Given, When and Then appear in logical sequence |
62
- | `only-one-when` | Requires that there is at most one When step for each scenario |
63
-
64
- \* These rules cannot be turned off because they detect undocumented cucumber functionality that causes the [gherkin](https://github.com/cucumber/gherkin-javascript) parser to crash.
65
-
66
- ## Rule Configuration
67
- The not-configurable rules are turned on by default and cannot be turned off. Configurable rules can be customized using a [file](#configuration-file).
68
-
69
- The configurable rules are off by default. To turn them on, you will need to create a json file, where you specify the name of each rule and its desired state (which can be "on" or "off"). Eg:
70
- ```
71
- {
72
- "no-unnamed-features": "on"
73
- }
74
- ```
75
- will turn on the `no-unnamed-features` rule.
9
+ - Supports modern Gherkin `Rule` blocks, including Rule-scoped `Background`s.
10
+ - Scopes `no-dupe-scenario-names` by `Rule`: the same scenario name under different Rules is allowed; duplicates under the same Rule (or both at Feature level) are still flagged.
11
+ - Allows indentation-only blank lines inside DocStrings (`no-trailing-spaces`).
12
+ - Runs the stylish formatter from source and refreshes production dependencies.
76
13
 
77
- ### allowed-tags
14
+ ### `no-dupe-scenario-names` + Rules example
78
15
 
79
- `allowed-tags` should be configured with the list of allowed tags and patterns:
16
+ ```gherkin
17
+ Rule: Users Tab
18
+ Scenario: [Pagination] Next page
19
+ Then users on the next page are shown
80
20
 
21
+ Rule: Companies Tab
22
+ Scenario: [Pagination] Next page
23
+ Then companies on the next page are shown
81
24
  ```
82
- {
83
- "allowed-tags": ["on", {"tags": ["@watch", "@wip"], "patterns": ["^@todo$"]}]
84
- }
85
- ```
86
-
87
- Any tag not included in this list won't be allowed.
88
25
 
89
- ### file-name
26
+ Those two scenarios share a name but live under different Rules, so the rule does not report a duplicate.
90
27
 
91
- `file-name` is configured with a style to enforce. The default is `PascalCase`:
28
+ Configure per-feature duplicate checks like this:
92
29
 
93
30
  ```json
94
- {
95
- "file-name": ["on", {"style": "PascalCase"}]
96
- }
97
- ```
98
-
99
- The list of supported styles is:
100
-
101
- - `PascalCase` - first letter of each word capitalized (no spaces) e.g. "MyFancyFeature.feature"
102
- - `Title Case` - first letter of each word capitalized (with spaces) e.g. "My Fancy Feature.feature"
103
- - `camelCase` - first letter of each word capitalized, except first e.g. "myFancyFeature.feature"
104
- - `kebab-case` - all lowercase, hyphen-delimited e.g. "my-fancy-feature.feature"
105
- - `snake_case` - all lowercase, underscore-delimited e.g. "my_fancy_feature.feature"
106
-
107
- ### no-restricted-patterns
108
-
109
- `no-restricted-patterns` is a list of exact or partial patterns whose matches are dissallowed in feature name and description, and in background, scenario and scenario outline name, description and steps.
110
- All patterns are treated as case insensitive.
111
- The rule can be configured like this:
112
- ```
113
- {
114
- "no-restricted-patterns": ["on", {
115
- "Global": [
116
- "^globally restricted pattern"
117
- ],
118
- "Feature": [
119
- "poor description",
120
- "validate",
121
- "verify"
122
- ],
123
- "Background": [
124
- "show last response",
125
- "a debugging step"
126
- ],
127
- "Scenario": [
128
- "show last response",
129
- "a debugging step"
130
- ]
131
- }]
132
- }
133
- ```
134
-
135
- Notes:
136
- - Step keywords `Given`, `When`, `Then` and `And` should not be included in the patterns.
137
- - Description violations always get reported in the Feature/Scenario/etc definition line. This is due to the parsed gherkin tree not having information about which line the description appears.
138
-
139
- ### indentation
140
-
141
- `indentation` can be configured in a more granular level and uses following rules by default:
142
- - Expected indentation for Feature, Background, Scenario, Examples heading: 0 spaces
143
- - Expected indentation for Steps and each example: 2 spaces
144
-
145
- You can override the defaults for `indentation` like this:
146
- ```
147
- {
148
- "indentation" : [
149
- "on", {
150
- "Feature": 0,
151
- "Background": 0,
152
- "Scenario": 0,
153
- "Step": 2,
154
- "Examples": 0,
155
- "example": 2,
156
- "given": 2,
157
- "when": 2,
158
- "then": 2,
159
- "and": 2,
160
- "but": 2,
161
- "feature tag": 0,
162
- "scenario tag": 0
163
- }
164
- ]
165
- }
166
- ```
167
- There is no need to override all the defaults, as is done above, instead they can be overriden only where required. `Step` will be used as a fallback if the keyword of the step, eg. 'given', is not specified. If `feature tag` is not set then `Feature` is used as a fallback, and if `scenario tag` is not set then `Scenario` is used as a fallback.
168
-
169
- This feature is able to handle all localizations of the gherkin steps.
170
-
171
-
172
- ### max-scenarios-per-file
173
- The `max-scenarios-per-file` supports some configuration options:
174
-
175
- - `maxScenarios` (number) the maximum scenarios per file after which the rule fails - defaults to `10`
176
- - `countOutlineExamples` (boolean) whether to count every example row for a Scenario Outline, as opposed to just 1 for the whole block - defaults to `true`
177
-
178
- The configuration looks like this (showing the defaults):
179
- ```
180
- {
181
- "max-scenarios-per-file": ["on", {"maxScenarios": 10, "countOutlineExamples": true}]
182
- }
183
- ```
184
-
185
-
186
- ### name-length
187
-
188
- `name-length` can be configured separately for Feature, Scenario and Step names.
189
- The default is 70 characters for each of these:
190
-
191
- ```
192
- {
193
- "name-length" : ["on", { "Feature": 70, "Scenario": 70, "Step": 70 }]
194
- }
195
- ```
196
-
197
-
198
- ### new-line-at-eof
199
-
200
- `new-line-at-eof` can be configured to enforce or disallow new lines at EOF.
201
- - To enforce new lines at EOF:
202
- ```
203
- {
204
- "new-line-at-eof": ["on", "yes"]
205
- }
206
- ```
207
- - To disallow new lines at EOF:
208
- ```
209
- {
210
- "new-line-at-eof": ["on", "no"]
211
- }
212
- ```
213
-
214
-
215
- ### no-dupe-scenario-names
216
-
217
- `no-dupe-scenario-names` can be configured to search for duplicates in each individual feature or amongst all feature files.
218
- To enable searching for duplicates in each individual feature (same scenario name in different features won't raise an error) you need to configure the rule like this:
219
-
220
- ```
221
31
  {
222
32
  "no-dupe-scenario-names": ["on", "in-feature"]
223
33
  }
224
34
  ```
225
35
 
226
- The default case is testing against all the features (same scenario name in different features will raise an error). To get that behavor use the following configuration:
227
-
228
- ```
229
- {
230
- "no-dupe-scenario-names": "on"
231
- }
232
- ```
233
-
234
- or
36
+ ## Install
235
37
 
38
+ ```sh
39
+ npm install @ohlori/gherkin-lint
236
40
  ```
237
- {
238
- "no-dupe-scenario-names": ["on", "anywhere"]
239
- }
240
- ```
241
-
242
41
 
243
- ### no-restricted-tags
244
- `no-restricted-tags` should be configured with the list of restricted tags and patterns:
245
- ```
246
- {
247
- "no-restricted-tags": ["on", {"tags": ["@watch", "@wip"], "patterns": ["^@todo$"]}]
248
- }
249
- ```
250
-
251
-
252
- ### required-tags
253
-
254
- `required-tags` supports some configuration options:
255
-
256
- - `tags` (array) the array of tag patterns that must match at least one tag - defaults to `[]`
257
- - `ignoreUntagged` (boolean) whether to ignore scenarios that have no tag - defaults to `true`
258
-
259
- ```
260
- {
261
- "required-tags": ["on", {"tags": ["^@issue:[1-9]\\d*$"], "ignoreUntagged": false}]
262
- }
263
- ```
264
-
265
-
266
- ### scenario-size
267
-
268
- `scenario-size` lets you specify a maximum step length for scenarios and backgrounds. The `Scenario` configuration applies to both scenarios and scenario outlines:
269
- ```
270
- {
271
- "scenario-size": ["on", { "steps-length": { "Background": 15, "Scenario": 15 }}]
272
- }
273
- ```
274
-
275
- ## Configuration File
276
- The default name for the configuration file is `.gherkin-lintrc` and it's expected to be in your working directory.
277
-
278
- The file contents must be valid JSON, though it does allow comments.
279
-
280
- If you are using a file with a different name or a file in a different folder, you will need to specify the `-c` or `--config` option and pass in the relative path to your configuration file. Eg: `gherkin-lint -c path/to/configuration/file.extention`
281
-
282
- You can find an example configuration file, that turns on all of the rules in the root of this repo (.gherkin-lintrc).
283
-
284
- ## Ignoring Feature Files
285
- There are 2 ways you can specify files that the linter should ignore:
286
- 1. Add a `.gherkin-lintignore` file in your working directory and specify one glob pattern per file line
287
- 1. Use the command line option`-i` or `--ignore`, pass in a comma separated list of glob patterns. If specified, the command line option will override the `.gherkin-lintignore` file.
288
-
289
-
290
- ## Custom rules
291
- You can specify one more more custom rules directories by using the `-r` or `--rulesdir` command line option. Rules in the given directories will be available additionally to the default rules.
292
-
293
- Example:
294
- ```
295
- gherkin-lint --rulesdir "/path/to/my/rulesdir" --rulesdir "from/cwd/rulesdir"
296
- ```
42
+ See the [original README](https://github.com/gherkin-lint/gherkin-lint#readme) for CLI usage and the full rule set.
297
43
 
298
- Paths can either be absolute or relative to the current working directory.
299
- Have a look at the `src/rules/` directory for examples; The `no-empty-file` rule is a good example to start with.
44
+ License: [ISC](https://github.com/ohlori/gherkin-lint/blob/cucumber-rule-support/LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohlori/gherkin-lint",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "description": "A Gherkin linter/validator written in javascript",
5
5
  "author": "Vasiliki Siakka",
6
6
  "contributors": [
@@ -7,21 +7,28 @@ const availableConfigs = [
7
7
 
8
8
  let scenarios = [];
9
9
 
10
+ function scenarioKey(scenario, ruleNode) {
11
+ const ruleName = ruleNode ? ruleNode.name : '';
12
+ return `${ruleName}\0${scenario.name}`;
13
+ }
14
+
10
15
  function run(feature, file, configuration) {
11
16
  if (!feature) {
12
17
  return [];
13
18
  }
14
19
 
15
20
  let errors = [];
16
- if(configuration === 'in-feature') {
21
+ if (configuration === 'in-feature') {
17
22
  scenarios = [];
18
23
  }
19
24
 
20
- gherkinUtils.getScenarios(feature).forEach(scenario => {
21
- if (scenario.name in scenarios) {
22
- const dupes = getFileLinePairsAsStr(scenarios[scenario.name].locations);
23
-
24
- scenarios[scenario.name].locations.push({
25
+ gherkinUtils.getScenariosWithRule(feature).forEach(({scenario, rule: ruleNode}) => {
26
+ const key = scenarioKey(scenario, ruleNode);
27
+
28
+ if (key in scenarios) {
29
+ const dupes = getFileLinePairsAsStr(scenarios[key].locations);
30
+
31
+ scenarios[key].locations.push({
25
32
  file: file.relativePath,
26
33
  line: scenario.location.line
27
34
  });
@@ -31,7 +38,7 @@ function run(feature, file, configuration) {
31
38
  rule : rule,
32
39
  line : scenario.location.line});
33
40
  } else {
34
- scenarios[scenario.name] = {
41
+ scenarios[key] = {
35
42
  locations: [
36
43
  {
37
44
  file: file.relativePath,
@@ -41,7 +48,7 @@ function run(feature, file, configuration) {
41
48
  };
42
49
  }
43
50
  });
44
-
51
+
45
52
  return errors;
46
53
  }
47
54
 
@@ -1,15 +1,47 @@
1
1
  const rule = 'no-trailing-spaces';
2
+ const {getStepContainers} = require('./utils/gherkin.js');
2
3
 
3
- function run(unused, file) {
4
+ function getDocStringStartLines(feature) {
5
+ if (!feature) {
6
+ return new Set();
7
+ }
8
+
9
+ return new Set(getStepContainers(feature).reduce((lines, container) => {
10
+ (container.steps || []).forEach(step => {
11
+ if (step.docString) {
12
+ lines.push(step.docString.location.line);
13
+ }
14
+ });
15
+ return lines;
16
+ }, []));
17
+ }
18
+
19
+ function run(feature, file) {
4
20
  let errors = [];
5
21
  let lineNo = 1;
22
+ let docStringDelimiter;
23
+ const docStringStartLines = getDocStringStartLines(feature);
24
+
6
25
  file.lines.forEach(line => {
7
- if (/[\t ]+$/.test(line)) {
26
+ const trimmedLine = line.trim();
27
+ const isClosingDelimiter = docStringDelimiter &&
28
+ trimmedLine.startsWith(docStringDelimiter);
29
+ const isIndentedBlankDocStringLine = docStringDelimiter &&
30
+ !isClosingDelimiter && /^[\t ]+$/.test(line);
31
+
32
+ if (!isIndentedBlankDocStringLine && /[\t ]+$/.test(line)) {
8
33
  errors.push({message: 'Trailing spaces are not allowed',
9
34
  rule : rule,
10
35
  line : lineNo});
11
36
  }
12
37
 
38
+ if (isClosingDelimiter) {
39
+ docStringDelimiter = undefined;
40
+ } else if (docStringStartLines.has(lineNo)) {
41
+ const openingDelimiter = line.trimStart().match(/^("""|```)/);
42
+ docStringDelimiter = openingDelimiter && openingDelimiter[1];
43
+ }
44
+
13
45
  lineNo++;
14
46
  });
15
47
 
@@ -60,6 +60,26 @@ function getScenarios(feature) {
60
60
  .map(child => child.scenario);
61
61
  }
62
62
 
63
+ function getScenariosWithRule(feature) {
64
+ const result = [];
65
+
66
+ (feature.children || []).forEach(child => {
67
+ if (child.scenario) {
68
+ result.push({scenario: child.scenario, rule: null});
69
+ }
70
+
71
+ if (child.rule) {
72
+ (child.rule.children || []).forEach(ruleChild => {
73
+ if (ruleChild.scenario) {
74
+ result.push({scenario: ruleChild.scenario, rule: child.rule});
75
+ }
76
+ });
77
+ }
78
+ });
79
+
80
+ return result;
81
+ }
82
+
63
83
  function getBackgrounds(feature) {
64
84
  return getAllChildren(feature)
65
85
  .filter(child => child.background)
@@ -85,6 +105,7 @@ module.exports = {
85
105
  getAllChildren: getAllChildren,
86
106
  getRules: getRules,
87
107
  getScenarios: getScenarios,
108
+ getScenariosWithRule: getScenariosWithRule,
88
109
  getBackgrounds: getBackgrounds,
89
110
  getStepContainers: getStepContainers,
90
111
  getTaggableNodes: getTaggableNodes,