gurkencheck 0.0.10 → 0.1.0
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 +30 -11
- package/dist/config-parser.d.ts +15 -1
- package/dist/config-parser.js +58 -12
- package/dist/config-verifier.d.ts +0 -1
- package/dist/config-verifier.js +71 -3
- package/dist/diagnostics.d.ts +0 -1
- package/dist/diagnostics.js +0 -1
- package/dist/exit-codes.d.ts +0 -1
- package/dist/exit-codes.js +0 -1
- package/dist/feature-finder.d.ts +0 -1
- package/dist/feature-finder.js +29 -11
- package/dist/formatters/index.d.ts +0 -1
- package/dist/formatters/index.js +2 -3
- package/dist/formatters/json.d.ts +0 -1
- package/dist/formatters/json.js +0 -1
- package/dist/formatters/junit.d.ts +1 -13
- package/dist/formatters/junit.js +36 -11
- package/dist/formatters/sarif.d.ts +0 -1
- package/dist/formatters/sarif.js +25 -7
- package/dist/formatters/stylish.d.ts +0 -1
- package/dist/formatters/stylish.js +0 -1
- package/dist/formatters/tap.d.ts +0 -1
- package/dist/formatters/tap.js +37 -5
- package/dist/gherkin/dialects.d.ts +0 -1
- package/dist/gherkin/dialects.js +0 -1
- package/dist/gherkin/keywords.d.ts +6 -3
- package/dist/gherkin/keywords.js +10 -4
- package/dist/gherkin/parse.d.ts +0 -1
- package/dist/gherkin/parse.js +19 -7
- package/dist/gherkin/traverse.d.ts +0 -1
- package/dist/gherkin/traverse.js +0 -1
- package/dist/gherkin/variables.d.ts +12 -1
- package/dist/gherkin/variables.js +24 -6
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/linter.d.ts +0 -1
- package/dist/linter.js +1 -2
- package/dist/logger.d.ts +0 -1
- package/dist/logger.js +0 -1
- package/dist/main.d.ts +0 -1
- package/dist/main.js +78 -27
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +0 -5
- package/dist/rules/allowed-tags.d.ts +0 -1
- package/dist/rules/allowed-tags.js +2 -1
- package/dist/rules/background-setup-only.d.ts +0 -1
- package/dist/rules/background-setup-only.js +0 -1
- package/dist/rules/file-name.d.ts +0 -1
- package/dist/rules/file-name.js +10 -1
- package/dist/rules/indentation.d.ts +0 -1
- package/dist/rules/indentation.js +24 -10
- package/dist/rules/index.d.ts +0 -1
- package/dist/rules/index.js +0 -1
- package/dist/rules/keywords-in-logical-order.d.ts +0 -1
- package/dist/rules/keywords-in-logical-order.js +0 -1
- package/dist/rules/max-scenarios-per-file.d.ts +0 -1
- package/dist/rules/max-scenarios-per-file.js +0 -1
- package/dist/rules/name-length.d.ts +0 -1
- package/dist/rules/name-length.js +5 -3
- package/dist/rules/new-line-at-eof.d.ts +0 -1
- package/dist/rules/new-line-at-eof.js +10 -2
- package/dist/rules/no-background-only-scenario.d.ts +0 -1
- package/dist/rules/no-background-only-scenario.js +0 -1
- package/dist/rules/no-dupe-feature-names.d.ts +0 -1
- package/dist/rules/no-dupe-feature-names.js +5 -1
- package/dist/rules/no-dupe-file-names.d.ts +0 -1
- package/dist/rules/no-dupe-file-names.js +0 -1
- package/dist/rules/no-dupe-scenario-names.d.ts +0 -1
- package/dist/rules/no-dupe-scenario-names.js +4 -1
- package/dist/rules/no-duplicate-tags.d.ts +0 -1
- package/dist/rules/no-duplicate-tags.js +0 -1
- package/dist/rules/no-empty-background.d.ts +0 -1
- package/dist/rules/no-empty-background.js +0 -1
- package/dist/rules/no-empty-file.d.ts +0 -1
- package/dist/rules/no-empty-file.js +0 -1
- package/dist/rules/no-examples-in-scenarios.d.ts +0 -1
- package/dist/rules/no-examples-in-scenarios.js +0 -1
- package/dist/rules/no-files-without-scenarios.d.ts +0 -1
- package/dist/rules/no-files-without-scenarios.js +0 -1
- package/dist/rules/no-homogenous-tags.d.ts +0 -1
- package/dist/rules/no-homogenous-tags.js +0 -1
- package/dist/rules/no-multiple-empty-lines.d.ts +0 -1
- package/dist/rules/no-multiple-empty-lines.js +2 -3
- package/dist/rules/no-partially-commented-tag-lines.d.ts +0 -1
- package/dist/rules/no-partially-commented-tag-lines.js +7 -5
- package/dist/rules/no-restricted-patterns.d.ts +0 -1
- package/dist/rules/no-restricted-patterns.js +64 -19
- package/dist/rules/no-restricted-tags.d.ts +0 -1
- package/dist/rules/no-restricted-tags.js +2 -1
- package/dist/rules/no-scenario-outlines-without-examples.d.ts +0 -1
- package/dist/rules/no-scenario-outlines-without-examples.js +0 -1
- package/dist/rules/no-scenarios-without-then.d.ts +0 -1
- package/dist/rules/no-scenarios-without-then.js +0 -1
- package/dist/rules/no-scenarios-without-when.d.ts +0 -1
- package/dist/rules/no-scenarios-without-when.js +0 -1
- package/dist/rules/no-superfluous-tags.d.ts +0 -1
- package/dist/rules/no-superfluous-tags.js +0 -1
- package/dist/rules/no-trailing-spaces.d.ts +0 -1
- package/dist/rules/no-trailing-spaces.js +13 -4
- package/dist/rules/no-undeclared-variables.d.ts +0 -1
- package/dist/rules/no-undeclared-variables.js +12 -11
- package/dist/rules/no-unnamed-features.d.ts +0 -1
- package/dist/rules/no-unnamed-features.js +0 -1
- package/dist/rules/no-unnamed-scenarios.d.ts +0 -1
- package/dist/rules/no-unnamed-scenarios.js +0 -1
- package/dist/rules/no-unused-variables.d.ts +0 -1
- package/dist/rules/no-unused-variables.js +0 -1
- package/dist/rules/one-space-between-tags.d.ts +0 -1
- package/dist/rules/one-space-between-tags.js +3 -2
- package/dist/rules/only-one-when.d.ts +0 -1
- package/dist/rules/only-one-when.js +5 -5
- package/dist/rules/required-tags.d.ts +0 -1
- package/dist/rules/required-tags.js +13 -3
- package/dist/rules/scenario-size.d.ts +0 -1
- package/dist/rules/scenario-size.js +0 -1
- package/dist/rules/use-and.d.ts +0 -1
- package/dist/rules/use-and.js +2 -2
- package/dist/rules.d.ts +0 -1
- package/dist/rules.js +49 -4
- package/dist/stats/collect.d.ts +0 -1
- package/dist/stats/collect.js +0 -1
- package/dist/stats/command.d.ts +0 -1
- package/dist/stats/command.js +24 -7
- package/dist/stats/format/index.d.ts +0 -1
- package/dist/stats/format/index.js +0 -1
- package/dist/stats/format/json.d.ts +0 -1
- package/dist/stats/format/json.js +0 -1
- package/dist/stats/format/markdown.d.ts +0 -1
- package/dist/stats/format/markdown.js +24 -6
- package/dist/stats/format/shared.d.ts +0 -1
- package/dist/stats/format/shared.js +0 -1
- package/dist/stats/format/text.d.ts +0 -1
- package/dist/stats/format/text.js +0 -1
- package/dist/stats/index.d.ts +0 -1
- package/dist/stats/index.js +0 -1
- package/dist/stats/normalise.d.ts +0 -1
- package/dist/stats/normalise.js +27 -14
- package/dist/stats/similar.d.ts +0 -1
- package/dist/stats/similar.js +0 -1
- package/dist/stats/types.d.ts +0 -1
- package/dist/stats/types.js +0 -1
- package/dist/suppressions.d.ts +6 -3
- package/dist/suppressions.js +36 -30
- package/dist/types.d.ts +7 -1
- package/dist/types.js +0 -1
- package/dist/util/case.d.ts +2 -2
- package/dist/util/case.js +17 -3
- package/dist/util/collections.d.ts +0 -1
- package/dist/util/collections.js +0 -1
- package/dist/util/glob.d.ts +9 -3
- package/dist/util/glob.js +115 -27
- package/dist/util/json.d.ts +0 -1
- package/dist/util/json.js +0 -1
- package/dist/util/lines.d.ts +6 -4
- package/dist/util/lines.js +22 -4
- package/dist/util/location.d.ts +8 -1
- package/dist/util/location.js +10 -1
- package/dist/util/patterns.d.ts +9 -0
- package/dist/util/patterns.js +21 -0
- package/dist/util/resolve.d.ts +6 -0
- package/dist/util/resolve.js +101 -0
- package/dist/util/stream.d.ts +0 -1
- package/dist/util/stream.js +3 -2
- package/dist/util/xml.d.ts +0 -1
- package/dist/util/xml.js +17 -3
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/dist/watch.d.ts +11 -1
- package/dist/watch.js +38 -3
- package/package.json +2 -2
- package/dist/config-parser.d.ts.map +0 -1
- package/dist/config-parser.js.map +0 -1
- package/dist/config-verifier.d.ts.map +0 -1
- package/dist/config-verifier.js.map +0 -1
- package/dist/diagnostics.d.ts.map +0 -1
- package/dist/diagnostics.js.map +0 -1
- package/dist/exit-codes.d.ts.map +0 -1
- package/dist/exit-codes.js.map +0 -1
- package/dist/feature-finder.d.ts.map +0 -1
- package/dist/feature-finder.js.map +0 -1
- package/dist/formatters/index.d.ts.map +0 -1
- package/dist/formatters/index.js.map +0 -1
- package/dist/formatters/json.d.ts.map +0 -1
- package/dist/formatters/json.js.map +0 -1
- package/dist/formatters/junit.d.ts.map +0 -1
- package/dist/formatters/junit.js.map +0 -1
- package/dist/formatters/sarif.d.ts.map +0 -1
- package/dist/formatters/sarif.js.map +0 -1
- package/dist/formatters/stylish.d.ts.map +0 -1
- package/dist/formatters/stylish.js.map +0 -1
- package/dist/formatters/tap.d.ts.map +0 -1
- package/dist/formatters/tap.js.map +0 -1
- package/dist/gherkin/dialects.d.ts.map +0 -1
- package/dist/gherkin/dialects.js.map +0 -1
- package/dist/gherkin/keywords.d.ts.map +0 -1
- package/dist/gherkin/keywords.js.map +0 -1
- package/dist/gherkin/parse.d.ts.map +0 -1
- package/dist/gherkin/parse.js.map +0 -1
- package/dist/gherkin/traverse.d.ts.map +0 -1
- package/dist/gherkin/traverse.js.map +0 -1
- package/dist/gherkin/variables.d.ts.map +0 -1
- package/dist/gherkin/variables.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/linter.d.ts.map +0 -1
- package/dist/linter.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/presets.d.ts.map +0 -1
- package/dist/presets.js.map +0 -1
- package/dist/rules/allowed-tags.d.ts.map +0 -1
- package/dist/rules/allowed-tags.js.map +0 -1
- package/dist/rules/background-setup-only.d.ts.map +0 -1
- package/dist/rules/background-setup-only.js.map +0 -1
- package/dist/rules/file-name.d.ts.map +0 -1
- package/dist/rules/file-name.js.map +0 -1
- package/dist/rules/indentation.d.ts.map +0 -1
- package/dist/rules/indentation.js.map +0 -1
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/keywords-in-logical-order.d.ts.map +0 -1
- package/dist/rules/keywords-in-logical-order.js.map +0 -1
- package/dist/rules/max-scenarios-per-file.d.ts.map +0 -1
- package/dist/rules/max-scenarios-per-file.js.map +0 -1
- package/dist/rules/name-length.d.ts.map +0 -1
- package/dist/rules/name-length.js.map +0 -1
- package/dist/rules/new-line-at-eof.d.ts.map +0 -1
- package/dist/rules/new-line-at-eof.js.map +0 -1
- package/dist/rules/no-background-only-scenario.d.ts.map +0 -1
- package/dist/rules/no-background-only-scenario.js.map +0 -1
- package/dist/rules/no-dupe-feature-names.d.ts.map +0 -1
- package/dist/rules/no-dupe-feature-names.js.map +0 -1
- package/dist/rules/no-dupe-file-names.d.ts.map +0 -1
- package/dist/rules/no-dupe-file-names.js.map +0 -1
- package/dist/rules/no-dupe-scenario-names.d.ts.map +0 -1
- package/dist/rules/no-dupe-scenario-names.js.map +0 -1
- package/dist/rules/no-duplicate-tags.d.ts.map +0 -1
- package/dist/rules/no-duplicate-tags.js.map +0 -1
- package/dist/rules/no-empty-background.d.ts.map +0 -1
- package/dist/rules/no-empty-background.js.map +0 -1
- package/dist/rules/no-empty-file.d.ts.map +0 -1
- package/dist/rules/no-empty-file.js.map +0 -1
- package/dist/rules/no-examples-in-scenarios.d.ts.map +0 -1
- package/dist/rules/no-examples-in-scenarios.js.map +0 -1
- package/dist/rules/no-files-without-scenarios.d.ts.map +0 -1
- package/dist/rules/no-files-without-scenarios.js.map +0 -1
- package/dist/rules/no-homogenous-tags.d.ts.map +0 -1
- package/dist/rules/no-homogenous-tags.js.map +0 -1
- package/dist/rules/no-multiple-empty-lines.d.ts.map +0 -1
- package/dist/rules/no-multiple-empty-lines.js.map +0 -1
- package/dist/rules/no-partially-commented-tag-lines.d.ts.map +0 -1
- package/dist/rules/no-partially-commented-tag-lines.js.map +0 -1
- package/dist/rules/no-restricted-patterns.d.ts.map +0 -1
- package/dist/rules/no-restricted-patterns.js.map +0 -1
- package/dist/rules/no-restricted-tags.d.ts.map +0 -1
- package/dist/rules/no-restricted-tags.js.map +0 -1
- package/dist/rules/no-scenario-outlines-without-examples.d.ts.map +0 -1
- package/dist/rules/no-scenario-outlines-without-examples.js.map +0 -1
- package/dist/rules/no-scenarios-without-then.d.ts.map +0 -1
- package/dist/rules/no-scenarios-without-then.js.map +0 -1
- package/dist/rules/no-scenarios-without-when.d.ts.map +0 -1
- package/dist/rules/no-scenarios-without-when.js.map +0 -1
- package/dist/rules/no-superfluous-tags.d.ts.map +0 -1
- package/dist/rules/no-superfluous-tags.js.map +0 -1
- package/dist/rules/no-trailing-spaces.d.ts.map +0 -1
- package/dist/rules/no-trailing-spaces.js.map +0 -1
- package/dist/rules/no-undeclared-variables.d.ts.map +0 -1
- package/dist/rules/no-undeclared-variables.js.map +0 -1
- package/dist/rules/no-unnamed-features.d.ts.map +0 -1
- package/dist/rules/no-unnamed-features.js.map +0 -1
- package/dist/rules/no-unnamed-scenarios.d.ts.map +0 -1
- package/dist/rules/no-unnamed-scenarios.js.map +0 -1
- package/dist/rules/no-unused-variables.d.ts.map +0 -1
- package/dist/rules/no-unused-variables.js.map +0 -1
- package/dist/rules/one-space-between-tags.d.ts.map +0 -1
- package/dist/rules/one-space-between-tags.js.map +0 -1
- package/dist/rules/only-one-when.d.ts.map +0 -1
- package/dist/rules/only-one-when.js.map +0 -1
- package/dist/rules/required-tags.d.ts.map +0 -1
- package/dist/rules/required-tags.js.map +0 -1
- package/dist/rules/scenario-size.d.ts.map +0 -1
- package/dist/rules/scenario-size.js.map +0 -1
- package/dist/rules/use-and.d.ts.map +0 -1
- package/dist/rules/use-and.js.map +0 -1
- package/dist/rules.d.ts.map +0 -1
- package/dist/rules.js.map +0 -1
- package/dist/stats/collect.d.ts.map +0 -1
- package/dist/stats/collect.js.map +0 -1
- package/dist/stats/command.d.ts.map +0 -1
- package/dist/stats/command.js.map +0 -1
- package/dist/stats/format/index.d.ts.map +0 -1
- package/dist/stats/format/index.js.map +0 -1
- package/dist/stats/format/json.d.ts.map +0 -1
- package/dist/stats/format/json.js.map +0 -1
- package/dist/stats/format/markdown.d.ts.map +0 -1
- package/dist/stats/format/markdown.js.map +0 -1
- package/dist/stats/format/shared.d.ts.map +0 -1
- package/dist/stats/format/shared.js.map +0 -1
- package/dist/stats/format/text.d.ts.map +0 -1
- package/dist/stats/format/text.js.map +0 -1
- package/dist/stats/index.d.ts.map +0 -1
- package/dist/stats/index.js.map +0 -1
- package/dist/stats/normalise.d.ts.map +0 -1
- package/dist/stats/normalise.js.map +0 -1
- package/dist/stats/similar.d.ts.map +0 -1
- package/dist/stats/similar.js.map +0 -1
- package/dist/stats/types.d.ts.map +0 -1
- package/dist/stats/types.js.map +0 -1
- package/dist/suppressions.d.ts.map +0 -1
- package/dist/suppressions.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/util/case.d.ts.map +0 -1
- package/dist/util/case.js.map +0 -1
- package/dist/util/collections.d.ts.map +0 -1
- package/dist/util/collections.js.map +0 -1
- package/dist/util/glob.d.ts.map +0 -1
- package/dist/util/glob.js.map +0 -1
- package/dist/util/json.d.ts.map +0 -1
- package/dist/util/json.js.map +0 -1
- package/dist/util/lines.d.ts.map +0 -1
- package/dist/util/lines.js.map +0 -1
- package/dist/util/location.d.ts.map +0 -1
- package/dist/util/location.js.map +0 -1
- package/dist/util/stream.d.ts.map +0 -1
- package/dist/util/stream.js.map +0 -1
- package/dist/util/xml.d.ts.map +0 -1
- package/dist/util/xml.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/dist/watch.d.ts.map +0 -1
- package/dist/watch.js.map +0 -1
package/README.md
CHANGED
|
@@ -70,7 +70,8 @@ gurkencheck [options] <feature-files>
|
|
|
70
70
|
or the path to a formatter of your own
|
|
71
71
|
(default: stylish)
|
|
72
72
|
-c, --config <path> configuration file (default: .gurkencheckrc)
|
|
73
|
-
-i, --ignore <globs> comma separated globs to skip, overriding .gurkencheckignore
|
|
73
|
+
-i, --ignore <globs> comma separated globs to skip, overriding .gurkencheckignore;
|
|
74
|
+
may be given more than once
|
|
74
75
|
-r, --rulesdir <dir> directory of custom rules; may be given more than once
|
|
75
76
|
-l, --language <code> dialect for files with no "# language:" header
|
|
76
77
|
-w, --watch keep running, checking again whenever a file changes
|
|
@@ -153,7 +154,9 @@ Statistics are for reading, not for failing a build — that is what the rules a
|
|
|
153
154
|
gurkencheck stats [options] <feature-files>
|
|
154
155
|
|
|
155
156
|
-f, --format <format> output format: text, json, md (default: text)
|
|
156
|
-
-
|
|
157
|
+
-c, --config <path> configuration file to take the language from
|
|
158
|
+
(default: .gurkencheckrc)
|
|
159
|
+
-i, --ignore <globs> comma separated globs to skip; may be given more than once
|
|
157
160
|
-l, --language <code> dialect for files with no "# language:" header
|
|
158
161
|
--top <n> how many entries each list shows (default: 10)
|
|
159
162
|
-h, --help show this message
|
|
@@ -244,7 +247,8 @@ created after it started is picked up too. `node_modules` and `.git` are skipped
|
|
|
244
247
|
```
|
|
245
248
|
|
|
246
249
|
Paths in the log are relative to the directory gurkencheck ran in, which is what code
|
|
247
|
-
scanning needs to match a finding to a file in the repository
|
|
250
|
+
scanning needs to match a finding to a file in the repository, so run it from the repository
|
|
251
|
+
root. A file outside that directory is named by an absolute `file:` URI instead.
|
|
248
252
|
|
|
249
253
|
## Switching a rule off for one place
|
|
250
254
|
|
|
@@ -264,7 +268,7 @@ Write a comment in the feature file:
|
|
|
264
268
|
| Directive | What it covers |
|
|
265
269
|
|---|---|
|
|
266
270
|
| `gurkencheck-disable-next-line` | The line directly below the comment |
|
|
267
|
-
| `gurkencheck-disable` | From the comment to the end of the file, or to the next `gurkencheck-enable` |
|
|
271
|
+
| `gurkencheck-disable` | From the comment to the end of the file, or to the next `gurkencheck-enable`. Written above the `Feature`, it also covers findings about the whole file, such as `file-name` |
|
|
268
272
|
| `gurkencheck-enable` | Resumes the rules a `gurkencheck-disable` switched off |
|
|
269
273
|
| `gurkencheck-disable-file` | The whole file, wherever the comment appears |
|
|
270
274
|
|
|
@@ -299,7 +303,7 @@ extends, and later entries in a list win over earlier ones.
|
|
|
299
303
|
{
|
|
300
304
|
"extends": "gurkencheck:recommended",
|
|
301
305
|
"indentation": ["on", {"Step": 4}],
|
|
302
|
-
"no-
|
|
306
|
+
"no-dupe-scenario-names": ["on", "in-feature"]
|
|
303
307
|
}
|
|
304
308
|
```
|
|
305
309
|
|
|
@@ -314,9 +318,18 @@ extends, and later entries in a list win over earlier ones.
|
|
|
314
318
|
Put one glob per line in a `.gurkencheckignore` file, or pass `--ignore` on the command
|
|
315
319
|
line. Without either, `node_modules` is skipped and everything else is checked.
|
|
316
320
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
Patterns are read the way `.gitignore` reads them:
|
|
322
|
+
|
|
323
|
+
- A pattern that matches a directory skips everything below it, so `build` is enough and
|
|
324
|
+
you do not have to write `build/**`.
|
|
325
|
+
- A pattern with no slash in it, such as `build` or `*.wip.feature`, matches at any depth.
|
|
326
|
+
One with a slash, such as `features/legacy`, is anchored to the directory gurkencheck
|
|
327
|
+
runs in.
|
|
328
|
+
- A trailing slash, as in `tmp/`, matches only a directory.
|
|
329
|
+
- A leading `!` brings back something an earlier pattern skipped. The last pattern to match
|
|
330
|
+
decides, and nothing inside a skipped directory can be brought back.
|
|
331
|
+
|
|
332
|
+
Blank lines and lines starting with `#` are ignored.
|
|
320
333
|
|
|
321
334
|
## Custom formatters
|
|
322
335
|
|
|
@@ -370,6 +383,9 @@ npx gurkencheck --rulesdir ./rules
|
|
|
370
383
|
|
|
371
384
|
A custom rule may reuse a built-in rule's name to replace it.
|
|
372
385
|
|
|
386
|
+
Every module directly inside the directory has to be a rule; TypeScript declaration files
|
|
387
|
+
(`.d.ts`) are skipped. Code the rules share belongs in a subdirectory, which is not read.
|
|
388
|
+
|
|
373
389
|
`run` may also be `async` and return a promise, for a rule that has to wait for
|
|
374
390
|
something — reading a file, or asking an issue tracker whether a tag refers to a real
|
|
375
391
|
ticket. Files are checked one after another, so rules see a predictable order.
|
|
@@ -380,7 +396,7 @@ ticket. Files are checked one after another, so rules see a predictable order.
|
|
|
380
396
|
import {findFeatureFiles, lint, loadRules, readConfiguration} from 'gurkencheck';
|
|
381
397
|
|
|
382
398
|
const rules = await loadRules();
|
|
383
|
-
const config = readConfiguration('.gurkencheckrc', rules);
|
|
399
|
+
const config = await readConfiguration('.gurkencheckrc', rules);
|
|
384
400
|
|
|
385
401
|
if (config.ok) {
|
|
386
402
|
const {files} = findFeatureFiles(['features']);
|
|
@@ -392,8 +408,11 @@ Each error carries `message`, `rule`, `line` and, where the rule knows one, `col
|
|
|
392
408
|
both 1-based, so an editor can underline exactly the right text. Errors about a whole file
|
|
393
409
|
or a whole line, such as a missing new line at the end of the file, have no `column`.
|
|
394
410
|
|
|
395
|
-
Nothing in the library
|
|
396
|
-
line's job.
|
|
411
|
+
Nothing in the library exits the process, and the parts that lint, read configuration or
|
|
412
|
+
find files never write to the console; that is the command line's job. The exceptions are
|
|
413
|
+
the ones whose job is printing: each formatter's `printResults`, the functions in
|
|
414
|
+
`FORMATTERS`, and `runStats`, which runs the `stats` command and writes its report to
|
|
415
|
+
stdout. To get the text instead, use `toJson`, `toSarif`, or `getStatsFormatter`.
|
|
397
416
|
|
|
398
417
|
## Developing
|
|
399
418
|
|
package/dist/config-parser.d.ts
CHANGED
|
@@ -7,15 +7,29 @@ export type ConfigurationResult = {
|
|
|
7
7
|
configuration: Configuration;
|
|
8
8
|
source: string;
|
|
9
9
|
language?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Every file the configuration was read from - the file itself and
|
|
12
|
+
* each one it extends, as absolute paths. What a watch has to keep an
|
|
13
|
+
* eye on to know the configuration changed.
|
|
14
|
+
*/
|
|
15
|
+
files?: string[];
|
|
10
16
|
} | {
|
|
11
17
|
ok: false;
|
|
12
18
|
message: string;
|
|
13
19
|
details: string[];
|
|
14
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* The `language` a configuration file sets, following what it extends, or
|
|
23
|
+
* `undefined` when there is no file, it sets none, or it cannot be read.
|
|
24
|
+
*
|
|
25
|
+
* For commands that read feature files without linting them, such as
|
|
26
|
+
* `stats`: they need the dialect, but should not refuse to run over a rule
|
|
27
|
+
* setting they never use.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readConfiguredLanguage(configPath?: string): Promise<string | undefined>;
|
|
15
30
|
/**
|
|
16
31
|
* Reads the configuration file at `configPath`, or the default file in the
|
|
17
32
|
* working directory when no path is given. With neither, the recommended
|
|
18
33
|
* rules are used.
|
|
19
34
|
*/
|
|
20
35
|
export declare function readConfiguration(configPath: string | undefined, rules: RuleRegistry): Promise<ConfigurationResult>;
|
|
21
|
-
//# sourceMappingURL=config-parser.d.ts.map
|
package/dist/config-parser.js
CHANGED
|
@@ -10,12 +10,12 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
10
10
|
* Reading and validating the configuration file.
|
|
11
11
|
*/
|
|
12
12
|
import fs from 'node:fs';
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
13
|
import path from 'node:path';
|
|
15
14
|
import { pathToFileURL } from 'node:url';
|
|
16
15
|
import { verifyConfiguration } from './config-verifier.js';
|
|
17
16
|
import { PRESETS, RECOMMENDED } from './presets.js';
|
|
18
17
|
import { stripJsonComments } from './util/json.js';
|
|
18
|
+
import { resolvePackage } from './util/resolve.js';
|
|
19
19
|
/** The configuration file looked for when none is given on the command line. */
|
|
20
20
|
export const DEFAULT_CONFIG_FILE_NAME = '.gurkencheckrc';
|
|
21
21
|
/** The key naming the configurations to build on top of. */
|
|
@@ -30,7 +30,8 @@ function isConfigurationObject(value) {
|
|
|
30
30
|
function parseFile(filePath) {
|
|
31
31
|
let parsed;
|
|
32
32
|
try {
|
|
33
|
-
|
|
33
|
+
// JSON.parse refuses a byte order mark, which some editors write.
|
|
34
|
+
parsed = JSON.parse(stripJsonComments(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/u, '')));
|
|
34
35
|
}
|
|
35
36
|
catch (thrown) {
|
|
36
37
|
throw new ConfigurationError(`Could not parse "${filePath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`);
|
|
@@ -80,7 +81,7 @@ async function loadExtended(specifier, fromFile) {
|
|
|
80
81
|
}
|
|
81
82
|
let resolved;
|
|
82
83
|
try {
|
|
83
|
-
resolved =
|
|
84
|
+
resolved = resolvePackage(specifier, from);
|
|
84
85
|
}
|
|
85
86
|
catch {
|
|
86
87
|
throw new ConfigurationError(`Could not resolve the package "${specifier}", extended from "${fromFile}". Is it installed?`);
|
|
@@ -105,21 +106,60 @@ async function loadExtended(specifier, fromFile) {
|
|
|
105
106
|
* Flattens a configuration and everything it extends into one object. What a
|
|
106
107
|
* file says wins over what it extends, and later entries in an `extends` list
|
|
107
108
|
* win over earlier ones.
|
|
109
|
+
*
|
|
110
|
+
* `ancestors` holds the chain of files that led here, not every file seen so
|
|
111
|
+
* far: two files extending the same base is sharing, not a cycle. `files`
|
|
112
|
+
* collects every file read along the way.
|
|
108
113
|
*/
|
|
109
|
-
async function flatten(configuration, source,
|
|
110
|
-
if (
|
|
114
|
+
async function flatten(configuration, source, ancestors, files) {
|
|
115
|
+
if (ancestors.has(source)) {
|
|
111
116
|
throw new ConfigurationError(`"${source}" ends up extending itself.`);
|
|
112
117
|
}
|
|
113
|
-
|
|
118
|
+
ancestors.add(source);
|
|
119
|
+
if (!Object.hasOwn(PRESETS, source)) {
|
|
120
|
+
files.add(path.resolve(source));
|
|
121
|
+
}
|
|
114
122
|
let merged = {};
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
123
|
+
try {
|
|
124
|
+
for (const specifier of extendsList(configuration, source)) {
|
|
125
|
+
const extended = await loadExtended(specifier, source);
|
|
126
|
+
merged = {
|
|
127
|
+
...merged,
|
|
128
|
+
...(await flatten(extended.configuration, extended.source, ancestors, files)),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
ancestors.delete(source);
|
|
118
134
|
}
|
|
119
135
|
const own = { ...configuration };
|
|
120
136
|
delete own[EXTENDS];
|
|
121
137
|
return { ...merged, ...own };
|
|
122
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* The `language` a configuration file sets, following what it extends, or
|
|
141
|
+
* `undefined` when there is no file, it sets none, or it cannot be read.
|
|
142
|
+
*
|
|
143
|
+
* For commands that read feature files without linting them, such as
|
|
144
|
+
* `stats`: they need the dialect, but should not refuse to run over a rule
|
|
145
|
+
* setting they never use.
|
|
146
|
+
*/
|
|
147
|
+
export async function readConfiguredLanguage(configPath = DEFAULT_CONFIG_FILE_NAME) {
|
|
148
|
+
if (!fs.existsSync(configPath)) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const flattened = (await flatten(parseFile(configPath), configPath, new Set(), new Set()));
|
|
153
|
+
const language = flattened[LANGUAGE];
|
|
154
|
+
return typeof language === 'string' ? language : undefined;
|
|
155
|
+
}
|
|
156
|
+
catch (thrown) {
|
|
157
|
+
if (thrown instanceof ConfigurationError) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
throw thrown;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
123
163
|
/**
|
|
124
164
|
* Reads the configuration file at `configPath`, or the default file in the
|
|
125
165
|
* working directory when no path is given. With neither, the recommended
|
|
@@ -145,8 +185,9 @@ export async function readConfiguration(configPath, rules) {
|
|
|
145
185
|
configPath = DEFAULT_CONFIG_FILE_NAME;
|
|
146
186
|
}
|
|
147
187
|
let flattened;
|
|
188
|
+
const files = new Set();
|
|
148
189
|
try {
|
|
149
|
-
flattened = await flatten(parseFile(configPath), configPath, new Set());
|
|
190
|
+
flattened = await flatten(parseFile(configPath), configPath, new Set(), files);
|
|
150
191
|
}
|
|
151
192
|
catch (thrown) {
|
|
152
193
|
if (thrown instanceof ConfigurationError) {
|
|
@@ -172,6 +213,11 @@ export async function readConfiguration(configPath, rules) {
|
|
|
172
213
|
if (errors.length > 0) {
|
|
173
214
|
return { ok: false, message: 'Error(s) in configuration file:', details: errors };
|
|
174
215
|
}
|
|
175
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
ok: true,
|
|
218
|
+
configuration,
|
|
219
|
+
source: configPath,
|
|
220
|
+
files: [...files],
|
|
221
|
+
...(language === undefined ? {} : { language }),
|
|
222
|
+
};
|
|
176
223
|
}
|
|
177
|
-
//# sourceMappingURL=config-parser.js.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { Configuration, RuleRegistry } from './types.ts';
|
|
2
2
|
/** Returns a list of problems; an empty list means the file is usable. */
|
|
3
3
|
export declare function verifyConfiguration(configuration: Configuration, rules: RuleRegistry): string[];
|
|
4
|
-
//# sourceMappingURL=config-verifier.d.ts.map
|
package/dist/config-verifier.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* a rule name is reported once and clearly rather than silently ignored.
|
|
4
4
|
*/
|
|
5
5
|
import { ALWAYS_ON_RULES } from './gherkin/parse.js';
|
|
6
|
+
import { BUILT_IN_RULES } from './rules/index.js';
|
|
6
7
|
const STATES = ['on', 'warn', 'off'];
|
|
7
8
|
function describeAllowed(availableConfigs) {
|
|
8
9
|
if (Array.isArray(availableConfigs)) {
|
|
@@ -15,9 +16,69 @@ function describeAllowed(availableConfigs) {
|
|
|
15
16
|
}
|
|
16
17
|
return '';
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
/** What a value should be, going by the default it would replace. */
|
|
20
|
+
function describeType(fallback) {
|
|
21
|
+
if (Array.isArray(fallback))
|
|
22
|
+
return 'a list of strings';
|
|
23
|
+
if (typeof fallback === 'number')
|
|
24
|
+
return 'a number';
|
|
25
|
+
if (typeof fallback === 'boolean')
|
|
26
|
+
return 'true or false';
|
|
27
|
+
if (typeof fallback === 'string')
|
|
28
|
+
return 'a string';
|
|
29
|
+
return 'an object';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Checks each setting against the type of the default it replaces, going
|
|
33
|
+
* into nested settings. A value of the wrong type is not a matter of taste:
|
|
34
|
+
* the rule would crash on it, or quietly do something else - a string where
|
|
35
|
+
* a list belongs is read one character at a time.
|
|
36
|
+
*/
|
|
37
|
+
function verifyTypes(prefix, defaults, settings, path, errors) {
|
|
38
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
39
|
+
if (!Object.hasOwn(defaults, key)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const fallback = defaults[key];
|
|
43
|
+
const name = `${path}${key}`;
|
|
44
|
+
let fits;
|
|
45
|
+
if (Array.isArray(fallback)) {
|
|
46
|
+
fits = Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
47
|
+
}
|
|
48
|
+
else if (typeof fallback === 'number') {
|
|
49
|
+
fits = typeof value === 'number' && Number.isFinite(value);
|
|
50
|
+
}
|
|
51
|
+
else if (typeof fallback === 'object' && fallback !== null) {
|
|
52
|
+
fits = typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
53
|
+
if (fits) {
|
|
54
|
+
for (const nested of Object.keys(value)) {
|
|
55
|
+
if (!Object.hasOwn(fallback, nested)) {
|
|
56
|
+
errors.push(`${prefix}"${name}" has no setting called "${nested}". Available settings: ${describeAllowed(fallback)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
verifyTypes(prefix, fallback, value, `${name}.`, errors);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
fits = typeof value === typeof fallback;
|
|
65
|
+
}
|
|
66
|
+
if (!fits) {
|
|
67
|
+
errors.push(`${prefix}"${name}" should be ${describeType(fallback)}, not ${JSON.stringify(value)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function verifySettings(rule, settings, errors) {
|
|
72
|
+
const ruleName = rule.name;
|
|
73
|
+
const availableConfigs = rule.availableConfigs;
|
|
19
74
|
const prefix = `Invalid rule configuration for "${ruleName}" - `;
|
|
20
75
|
if (availableConfigs === undefined) {
|
|
76
|
+
// A custom rule may read settings it never declared; a built-in one
|
|
77
|
+
// declares every setting it has, so settings for one with none are a
|
|
78
|
+
// mistake.
|
|
79
|
+
if (BUILT_IN_RULES.includes(rule)) {
|
|
80
|
+
errors.push(`${prefix}the rule has no settings, so the config should be "on", "warn" or "off"`);
|
|
81
|
+
}
|
|
21
82
|
return;
|
|
22
83
|
}
|
|
23
84
|
if (Array.isArray(availableConfigs)) {
|
|
@@ -37,6 +98,14 @@ function verifySettings(ruleName, availableConfigs, settings, errors) {
|
|
|
37
98
|
errors.push(`${prefix}the rule has no setting called "${key}". Available settings: ${describeAllowed(availableConfigs)}`);
|
|
38
99
|
}
|
|
39
100
|
}
|
|
101
|
+
const before = errors.length;
|
|
102
|
+
verifyTypes(prefix, availableConfigs, settings, '', errors);
|
|
103
|
+
// The rule's own checks assume the types are right, so they run only then.
|
|
104
|
+
if (errors.length === before && rule.verifySettings !== undefined) {
|
|
105
|
+
for (const problem of rule.verifySettings(settings)) {
|
|
106
|
+
errors.push(`${prefix}${problem}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
40
109
|
}
|
|
41
110
|
/** Returns a list of problems; an empty list means the file is usable. */
|
|
42
111
|
export function verifyConfiguration(configuration, rules) {
|
|
@@ -74,8 +143,7 @@ export function verifyConfiguration(configuration, rules) {
|
|
|
74
143
|
errors.push(`${prefix}the config should have exactly 2 parts: a state and the rule's settings`);
|
|
75
144
|
continue;
|
|
76
145
|
}
|
|
77
|
-
verifySettings(
|
|
146
|
+
verifySettings(rule, ruleConfig[1], errors);
|
|
78
147
|
}
|
|
79
148
|
return errors;
|
|
80
149
|
}
|
|
81
|
-
//# sourceMappingURL=config-verifier.js.map
|
package/dist/diagnostics.d.ts
CHANGED
package/dist/diagnostics.js
CHANGED
package/dist/exit-codes.d.ts
CHANGED
package/dist/exit-codes.js
CHANGED
package/dist/feature-finder.d.ts
CHANGED
package/dist/feature-finder.js
CHANGED
|
@@ -5,33 +5,51 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { uniq } from './util/collections.js';
|
|
8
|
-
import { globRoot, globStream, globSync } from './util/glob.js';
|
|
8
|
+
import { escapeGlob, globRoot, globStream, globSync, literalPath } from './util/glob.js';
|
|
9
9
|
/** The ignore file looked for when `--ignore` is not given. */
|
|
10
10
|
export const DEFAULT_IGNORE_FILE_NAME = '.gurkencheckignore';
|
|
11
11
|
/** Never worth linting, and expensive to walk into. */
|
|
12
|
-
export const DEFAULT_IGNORED_PATTERNS = ['node_modules
|
|
12
|
+
export const DEFAULT_IGNORED_PATTERNS = ['node_modules'];
|
|
13
|
+
/** The argument with forward slashes, whatever the platform writes. */
|
|
14
|
+
function toSlashes(pattern) {
|
|
15
|
+
return pattern.split(path.sep).join('/');
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Expands one command line argument into a glob that only matches feature
|
|
15
19
|
* files, or `undefined` when it does not name anything usable.
|
|
20
|
+
*
|
|
21
|
+
* Something that exists is taken as a name rather than as a glob, and
|
|
22
|
+
* escaped, so a file or directory whose name holds glob characters - `[wip]`,
|
|
23
|
+
* `{x}` - is still found, and a directory called `x.feature` is searched. A
|
|
24
|
+
* name without glob syntax that does not exist is reported rather than
|
|
25
|
+
* searched for: matching nothing, it would end in a clean run.
|
|
16
26
|
*/
|
|
17
27
|
function toFeatureGlob(pattern) {
|
|
18
28
|
if (pattern === '.') {
|
|
19
29
|
return '**/*.feature';
|
|
20
30
|
}
|
|
31
|
+
let stats;
|
|
32
|
+
try {
|
|
33
|
+
stats = fs.statSync(pattern);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
stats = undefined;
|
|
37
|
+
}
|
|
38
|
+
if (stats?.isDirectory() === true) {
|
|
39
|
+
return `${escapeGlob(toSlashes(pattern)).replace(/\/+$/, '')}/**/*.feature`;
|
|
40
|
+
}
|
|
41
|
+
if (stats?.isFile() === true) {
|
|
42
|
+
return pattern.endsWith('.feature') ? escapeGlob(toSlashes(pattern)) : undefined;
|
|
43
|
+
}
|
|
44
|
+
if (literalPath(toSlashes(pattern)) !== undefined) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
21
47
|
if (/\/\*\*$/.test(pattern)) {
|
|
22
48
|
return `${pattern}/*.feature`;
|
|
23
49
|
}
|
|
24
50
|
if (pattern.endsWith('.feature')) {
|
|
25
51
|
return pattern;
|
|
26
52
|
}
|
|
27
|
-
try {
|
|
28
|
-
if (fs.statSync(pattern).isDirectory()) {
|
|
29
|
-
return path.join(pattern, '**/*.feature').split(path.sep).join('/');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// Reported by the caller as an invalid pattern.
|
|
34
|
-
}
|
|
35
53
|
return undefined;
|
|
36
54
|
}
|
|
37
55
|
/** Reads the ignore file, one glob pattern per line, ignoring blank lines. */
|
|
@@ -51,6 +69,7 @@ export function readIgnorePatterns(ignoreArgument, ignoreFileName = DEFAULT_IGNO
|
|
|
51
69
|
return [...DEFAULT_IGNORED_PATTERNS];
|
|
52
70
|
}
|
|
53
71
|
return contents
|
|
72
|
+
.replace(/^\uFEFF/u, '')
|
|
54
73
|
.split(/\r\n|\r|\n/)
|
|
55
74
|
.map((line) => line.trim())
|
|
56
75
|
.filter((line) => line !== '' && !line.startsWith('#'));
|
|
@@ -145,4 +164,3 @@ export function featureRoots(patterns) {
|
|
|
145
164
|
}
|
|
146
165
|
return kept;
|
|
147
166
|
}
|
|
148
|
-
//# sourceMappingURL=feature-finder.js.map
|
|
@@ -60,4 +60,3 @@ export declare function getFormatter(format: string | undefined): Formatter | un
|
|
|
60
60
|
* `printResults`, or be the function itself.
|
|
61
61
|
*/
|
|
62
62
|
export declare function loadFormatter(format: string | undefined, cwd?: string): Promise<Formatter>;
|
|
63
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/formatters/index.js
CHANGED
|
@@ -9,7 +9,6 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
/**
|
|
10
10
|
* Output formats.
|
|
11
11
|
*/
|
|
12
|
-
import { createRequire } from 'node:module';
|
|
13
12
|
import path from 'node:path';
|
|
14
13
|
import { pathToFileURL } from 'node:url';
|
|
15
14
|
import { printResults as json } from './json.js';
|
|
@@ -17,6 +16,7 @@ import { printResults as junit } from './junit.js';
|
|
|
17
16
|
import { printResults as sarif } from './sarif.js';
|
|
18
17
|
import { printResults as stylish, startRun as streamStylish } from './stylish.js';
|
|
19
18
|
import { printResults as tap, startRun as streamTap } from './tap.js';
|
|
19
|
+
import { resolvePackage } from '../util/resolve.js';
|
|
20
20
|
/** The formats accepted by `--format` without having to be loaded. */
|
|
21
21
|
export const FORMATTERS = {
|
|
22
22
|
stylish,
|
|
@@ -80,7 +80,7 @@ async function loadFormatterModule(specifier, cwd) {
|
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
82
|
try {
|
|
83
|
-
resolved =
|
|
83
|
+
resolved = resolvePackage(specifier, from);
|
|
84
84
|
}
|
|
85
85
|
catch {
|
|
86
86
|
throw new Error(`Unsupported format "${specifier}". Use one of ${Object.keys(FORMATTERS).join(', ')}, ` +
|
|
@@ -113,4 +113,3 @@ export async function loadFormatter(format, cwd = process.cwd()) {
|
|
|
113
113
|
}
|
|
114
114
|
return formatter;
|
|
115
115
|
}
|
|
116
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -28,4 +28,3 @@ export interface JsonResult {
|
|
|
28
28
|
export declare function toJson(results: readonly FileResult[]): JsonResult[];
|
|
29
29
|
/** Writes the results as a single line of JSON. */
|
|
30
30
|
export declare function printResults(results: readonly FileResult[]): void;
|
|
31
|
-
//# sourceMappingURL=json.d.ts.map
|
package/dist/formatters/json.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JUnit XML, the report format CI servers such as Jenkins and TeamCity read.
|
|
3
|
-
*
|
|
4
|
-
* Each file becomes a test suite and each finding a test case, so a build
|
|
5
|
-
* shows one failing test per problem with its message and position, rather
|
|
6
|
-
* than one opaque failure per file. A file with nothing to report still
|
|
7
|
-
* contributes a passing test case, so the report says what was checked.
|
|
8
|
-
*
|
|
9
|
-
* A warning does not fail the run, so it is reported as a passing test case
|
|
10
|
-
* carrying its message, rather than as a failure the build would trip over.
|
|
11
|
-
*/
|
|
12
1
|
import type { FileResult } from '../types.ts';
|
|
13
2
|
/** Renders the results as a JUnit report. */
|
|
14
|
-
export declare function format(results: readonly FileResult[]): string;
|
|
3
|
+
export declare function format(results: readonly FileResult[], cwd?: string): string;
|
|
15
4
|
/** Writes the JUnit report to stdout. */
|
|
16
5
|
export declare function printResults(results: readonly FileResult[]): void;
|
|
17
|
-
//# sourceMappingURL=junit.d.ts.map
|
package/dist/formatters/junit.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JUnit XML, the report format CI servers such as Jenkins and TeamCity read.
|
|
3
|
+
*
|
|
4
|
+
* Each file becomes a test suite and each finding a test case, so a build
|
|
5
|
+
* shows one failing test per problem with its message and position, rather
|
|
6
|
+
* than one opaque failure per file. A file with nothing to report still
|
|
7
|
+
* contributes a passing test case, so the report says what was checked.
|
|
8
|
+
*
|
|
9
|
+
* A warning does not fail the run, so it is reported as a passing test case
|
|
10
|
+
* carrying its message, rather than as a failure the build would trip over.
|
|
11
|
+
*/
|
|
12
|
+
import path from 'node:path';
|
|
1
13
|
import { attributes, escapeXml, indent, XML_DECLARATION } from '../util/xml.js';
|
|
2
14
|
function severityOf(error) {
|
|
3
15
|
return error.severity ?? 'error';
|
|
@@ -10,32 +22,46 @@ function classNameFor(filePath) {
|
|
|
10
22
|
.filter((part) => part !== '' && part !== '.')
|
|
11
23
|
.join('.');
|
|
12
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The path to show for a file: relative to the working directory, as a CI
|
|
27
|
+
* server shows the repository, so suites read `features/Login.feature` and
|
|
28
|
+
* group as `features.Login` rather than by where the build machine keeps its
|
|
29
|
+
* checkout. A file outside the working directory keeps its absolute path.
|
|
30
|
+
*/
|
|
31
|
+
function displayPath(filePath, cwd) {
|
|
32
|
+
const relative = path.relative(cwd, filePath);
|
|
33
|
+
if (relative === '' || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
34
|
+
return filePath;
|
|
35
|
+
}
|
|
36
|
+
return relative.split(path.sep).join('/');
|
|
37
|
+
}
|
|
13
38
|
function positionOf(error) {
|
|
14
39
|
return error.column === undefined ? `${error.line}` : `${error.line}:${error.column}`;
|
|
15
40
|
}
|
|
16
|
-
function testCase(
|
|
41
|
+
function testCase(filePath, error) {
|
|
17
42
|
const open = `<testcase${attributes({
|
|
18
43
|
name: `${error.rule} (${positionOf(error)})`,
|
|
19
|
-
classname: classNameFor(
|
|
44
|
+
classname: classNameFor(filePath),
|
|
20
45
|
})}>`;
|
|
21
|
-
const detail = escapeXml(`${
|
|
46
|
+
const detail = escapeXml(`${filePath}:${positionOf(error)} (${error.rule}) ${error.message}`);
|
|
22
47
|
const body = severityOf(error) === 'warning'
|
|
23
48
|
? `<system-out>${detail}</system-out>`
|
|
24
49
|
: `<failure${attributes({ message: error.message, type: error.rule })}>${detail}</failure>`;
|
|
25
50
|
return `${open}\n${indent(body, 1)}\n</testcase>`;
|
|
26
51
|
}
|
|
27
|
-
function testSuite(result) {
|
|
52
|
+
function testSuite(result, cwd) {
|
|
53
|
+
const filePath = displayPath(result.filePath, cwd);
|
|
28
54
|
const failures = result.errors.filter((error) => severityOf(error) === 'error').length;
|
|
29
55
|
const cases = result.errors.length > 0
|
|
30
|
-
? result.errors.map((error) => testCase(
|
|
56
|
+
? result.errors.map((error) => testCase(filePath, error))
|
|
31
57
|
: [
|
|
32
58
|
`<testcase${attributes({
|
|
33
|
-
name:
|
|
34
|
-
classname: classNameFor(
|
|
59
|
+
name: filePath,
|
|
60
|
+
classname: classNameFor(filePath),
|
|
35
61
|
})}/>`,
|
|
36
62
|
];
|
|
37
63
|
const open = `<testsuite${attributes({
|
|
38
|
-
name:
|
|
64
|
+
name: filePath,
|
|
39
65
|
tests: Math.max(result.errors.length, 1),
|
|
40
66
|
failures,
|
|
41
67
|
errors: 0,
|
|
@@ -44,12 +70,12 @@ function testSuite(result) {
|
|
|
44
70
|
return `${open}\n${indent(cases.join('\n'), 1)}\n</testsuite>`;
|
|
45
71
|
}
|
|
46
72
|
/** Renders the results as a JUnit report. */
|
|
47
|
-
export function format(results) {
|
|
73
|
+
export function format(results, cwd = process.cwd()) {
|
|
48
74
|
const tests = results.reduce((total, result) => total + Math.max(result.errors.length, 1), 0);
|
|
49
75
|
const failures = results.reduce((total, result) => total + result.errors.filter((error) => severityOf(error) === 'error').length, 0);
|
|
50
76
|
const body = [
|
|
51
77
|
`<testsuites${attributes({ name: 'gurkencheck', tests, failures, errors: 0 })}>`,
|
|
52
|
-
...results.map((result) => indent(testSuite(result), 1)),
|
|
78
|
+
...results.map((result) => indent(testSuite(result, cwd), 1)),
|
|
53
79
|
'</testsuites>',
|
|
54
80
|
].join('\n');
|
|
55
81
|
return `${XML_DECLARATION}\n${body}`;
|
|
@@ -58,4 +84,3 @@ export function format(results) {
|
|
|
58
84
|
export function printResults(results) {
|
|
59
85
|
console.log(format(results));
|
|
60
86
|
}
|
|
61
|
-
//# sourceMappingURL=junit.js.map
|
|
@@ -3,4 +3,3 @@ import type { FileResult } from '../types.ts';
|
|
|
3
3
|
export declare function toSarif(results: readonly FileResult[], cwd?: string): unknown;
|
|
4
4
|
/** Writes the SARIF log to stdout. */
|
|
5
5
|
export declare function printResults(results: readonly FileResult[]): void;
|
|
6
|
-
//# sourceMappingURL=sarif.d.ts.map
|