gurkencheck 0.0.4

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.
Files changed (259) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +327 -0
  3. package/dist/config-parser.d.ts +21 -0
  4. package/dist/config-parser.d.ts.map +1 -0
  5. package/dist/config-parser.js +171 -0
  6. package/dist/config-parser.js.map +1 -0
  7. package/dist/config-verifier.d.ts +4 -0
  8. package/dist/config-verifier.d.ts.map +1 -0
  9. package/dist/config-verifier.js +81 -0
  10. package/dist/config-verifier.js.map +1 -0
  11. package/dist/feature-finder.d.ts +18 -0
  12. package/dist/feature-finder.d.ts.map +1 -0
  13. package/dist/feature-finder.js +70 -0
  14. package/dist/feature-finder.js.map +1 -0
  15. package/dist/formatters/index.d.ts +23 -0
  16. package/dist/formatters/index.d.ts.map +1 -0
  17. package/dist/formatters/index.js +79 -0
  18. package/dist/formatters/index.js.map +1 -0
  19. package/dist/formatters/json.d.ts +31 -0
  20. package/dist/formatters/json.d.ts.map +1 -0
  21. package/dist/formatters/json.js +31 -0
  22. package/dist/formatters/json.js.map +1 -0
  23. package/dist/formatters/junit.d.ts +17 -0
  24. package/dist/formatters/junit.d.ts.map +1 -0
  25. package/dist/formatters/junit.js +61 -0
  26. package/dist/formatters/junit.js.map +1 -0
  27. package/dist/formatters/sarif.d.ts +6 -0
  28. package/dist/formatters/sarif.d.ts.map +1 -0
  29. package/dist/formatters/sarif.js +81 -0
  30. package/dist/formatters/sarif.js.map +1 -0
  31. package/dist/formatters/stylish.d.ts +9 -0
  32. package/dist/formatters/stylish.d.ts.map +1 -0
  33. package/dist/formatters/stylish.js +66 -0
  34. package/dist/formatters/stylish.js.map +1 -0
  35. package/dist/formatters/tap.d.ts +14 -0
  36. package/dist/formatters/tap.d.ts.map +1 -0
  37. package/dist/formatters/tap.js +45 -0
  38. package/dist/formatters/tap.js.map +1 -0
  39. package/dist/gherkin/dialects.d.ts +31 -0
  40. package/dist/gherkin/dialects.d.ts.map +1 -0
  41. package/dist/gherkin/dialects.js +40 -0
  42. package/dist/gherkin/dialects.js.map +1 -0
  43. package/dist/gherkin/keywords.d.ts +21 -0
  44. package/dist/gherkin/keywords.d.ts.map +1 -0
  45. package/dist/gherkin/keywords.js +53 -0
  46. package/dist/gherkin/keywords.js.map +1 -0
  47. package/dist/gherkin/parse.d.ts +32 -0
  48. package/dist/gherkin/parse.d.ts.map +1 -0
  49. package/dist/gherkin/parse.js +236 -0
  50. package/dist/gherkin/parse.js.map +1 -0
  51. package/dist/gherkin/traverse.d.ts +45 -0
  52. package/dist/gherkin/traverse.d.ts.map +1 -0
  53. package/dist/gherkin/traverse.js +89 -0
  54. package/dist/gherkin/traverse.js.map +1 -0
  55. package/dist/gherkin/variables.d.ts +26 -0
  56. package/dist/gherkin/variables.d.ts.map +1 -0
  57. package/dist/gherkin/variables.js +51 -0
  58. package/dist/gherkin/variables.js.map +1 -0
  59. package/dist/index.d.ts +39 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +31 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/linter.d.ts +23 -0
  64. package/dist/linter.d.ts.map +1 -0
  65. package/dist/linter.js +57 -0
  66. package/dist/linter.js.map +1 -0
  67. package/dist/logger.d.ts +16 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +28 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/main.d.ts +3 -0
  72. package/dist/main.d.ts.map +1 -0
  73. package/dist/main.js +144 -0
  74. package/dist/main.js.map +1 -0
  75. package/dist/presets.d.ts +19 -0
  76. package/dist/presets.d.ts.map +1 -0
  77. package/dist/presets.js +24 -0
  78. package/dist/presets.js.map +1 -0
  79. package/dist/rules/allowed-tags.d.ts +4 -0
  80. package/dist/rules/allowed-tags.d.ts.map +1 -0
  81. package/dist/rules/allowed-tags.js +34 -0
  82. package/dist/rules/allowed-tags.js.map +1 -0
  83. package/dist/rules/file-name.d.ts +4 -0
  84. package/dist/rules/file-name.d.ts.map +1 -0
  85. package/dist/rules/file-name.js +41 -0
  86. package/dist/rules/file-name.js.map +1 -0
  87. package/dist/rules/indentation.d.ts +4 -0
  88. package/dist/rules/indentation.d.ts.map +1 -0
  89. package/dist/rules/indentation.js +153 -0
  90. package/dist/rules/indentation.js.map +1 -0
  91. package/dist/rules/index.d.ts +13 -0
  92. package/dist/rules/index.d.ts.map +1 -0
  93. package/dist/rules/index.js +70 -0
  94. package/dist/rules/index.js.map +1 -0
  95. package/dist/rules/keywords-in-logical-order.d.ts +4 -0
  96. package/dist/rules/keywords-in-logical-order.d.ts.map +1 -0
  97. package/dist/rules/keywords-in-logical-order.js +37 -0
  98. package/dist/rules/keywords-in-logical-order.js.map +1 -0
  99. package/dist/rules/max-scenarios-per-file.d.ts +4 -0
  100. package/dist/rules/max-scenarios-per-file.d.ts.map +1 -0
  101. package/dist/rules/max-scenarios-per-file.js +42 -0
  102. package/dist/rules/max-scenarios-per-file.js.map +1 -0
  103. package/dist/rules/name-length.d.ts +4 -0
  104. package/dist/rules/name-length.d.ts.map +1 -0
  105. package/dist/rules/name-length.js +50 -0
  106. package/dist/rules/name-length.js.map +1 -0
  107. package/dist/rules/new-line-at-eof.d.ts +4 -0
  108. package/dist/rules/new-line-at-eof.d.ts.map +1 -0
  109. package/dist/rules/new-line-at-eof.js +30 -0
  110. package/dist/rules/new-line-at-eof.js.map +1 -0
  111. package/dist/rules/no-background-only-scenario.d.ts +4 -0
  112. package/dist/rules/no-background-only-scenario.d.ts.map +1 -0
  113. package/dist/rules/no-background-only-scenario.js +43 -0
  114. package/dist/rules/no-background-only-scenario.js.map +1 -0
  115. package/dist/rules/no-dupe-feature-names.d.ts +4 -0
  116. package/dist/rules/no-dupe-feature-names.d.ts.map +1 -0
  117. package/dist/rules/no-dupe-feature-names.js +25 -0
  118. package/dist/rules/no-dupe-feature-names.js.map +1 -0
  119. package/dist/rules/no-dupe-file-names.d.ts +4 -0
  120. package/dist/rules/no-dupe-file-names.d.ts.map +1 -0
  121. package/dist/rules/no-dupe-file-names.js +28 -0
  122. package/dist/rules/no-dupe-file-names.js.map +1 -0
  123. package/dist/rules/no-dupe-scenario-names.d.ts +4 -0
  124. package/dist/rules/no-dupe-scenario-names.d.ts.map +1 -0
  125. package/dist/rules/no-dupe-scenario-names.js +41 -0
  126. package/dist/rules/no-dupe-scenario-names.js.map +1 -0
  127. package/dist/rules/no-duplicate-tags.d.ts +4 -0
  128. package/dist/rules/no-duplicate-tags.d.ts.map +1 -0
  129. package/dist/rules/no-duplicate-tags.js +38 -0
  130. package/dist/rules/no-duplicate-tags.js.map +1 -0
  131. package/dist/rules/no-empty-background.d.ts +4 -0
  132. package/dist/rules/no-empty-background.d.ts.map +1 -0
  133. package/dist/rules/no-empty-background.js +24 -0
  134. package/dist/rules/no-empty-background.js.map +1 -0
  135. package/dist/rules/no-empty-file.d.ts +4 -0
  136. package/dist/rules/no-empty-file.d.ts.map +1 -0
  137. package/dist/rules/no-empty-file.js +12 -0
  138. package/dist/rules/no-empty-file.js.map +1 -0
  139. package/dist/rules/no-examples-in-scenarios.d.ts +4 -0
  140. package/dist/rules/no-examples-in-scenarios.d.ts.map +1 -0
  141. package/dist/rules/no-examples-in-scenarios.js +25 -0
  142. package/dist/rules/no-examples-in-scenarios.js.map +1 -0
  143. package/dist/rules/no-files-without-scenarios.d.ts +4 -0
  144. package/dist/rules/no-files-without-scenarios.d.ts.map +1 -0
  145. package/dist/rules/no-files-without-scenarios.js +18 -0
  146. package/dist/rules/no-files-without-scenarios.js.map +1 -0
  147. package/dist/rules/no-homogenous-tags.d.ts +4 -0
  148. package/dist/rules/no-homogenous-tags.d.ts.map +1 -0
  149. package/dist/rules/no-homogenous-tags.js +54 -0
  150. package/dist/rules/no-homogenous-tags.js.map +1 -0
  151. package/dist/rules/no-multiple-empty-lines.d.ts +4 -0
  152. package/dist/rules/no-multiple-empty-lines.d.ts.map +1 -0
  153. package/dist/rules/no-multiple-empty-lines.js +44 -0
  154. package/dist/rules/no-multiple-empty-lines.js.map +1 -0
  155. package/dist/rules/no-partially-commented-tag-lines.d.ts +4 -0
  156. package/dist/rules/no-partially-commented-tag-lines.d.ts.map +1 -0
  157. package/dist/rules/no-partially-commented-tag-lines.js +44 -0
  158. package/dist/rules/no-partially-commented-tag-lines.js.map +1 -0
  159. package/dist/rules/no-restricted-patterns.d.ts +4 -0
  160. package/dist/rules/no-restricted-patterns.d.ts.map +1 -0
  161. package/dist/rules/no-restricted-patterns.js +101 -0
  162. package/dist/rules/no-restricted-patterns.js.map +1 -0
  163. package/dist/rules/no-restricted-tags.d.ts +4 -0
  164. package/dist/rules/no-restricted-tags.d.ts.map +1 -0
  165. package/dist/rules/no-restricted-tags.js +35 -0
  166. package/dist/rules/no-restricted-tags.js.map +1 -0
  167. package/dist/rules/no-scenario-outlines-without-examples.d.ts +4 -0
  168. package/dist/rules/no-scenario-outlines-without-examples.d.ts.map +1 -0
  169. package/dist/rules/no-scenario-outlines-without-examples.js +29 -0
  170. package/dist/rules/no-scenario-outlines-without-examples.js.map +1 -0
  171. package/dist/rules/no-superfluous-tags.d.ts +4 -0
  172. package/dist/rules/no-superfluous-tags.d.ts.map +1 -0
  173. package/dist/rules/no-superfluous-tags.js +40 -0
  174. package/dist/rules/no-superfluous-tags.js.map +1 -0
  175. package/dist/rules/no-trailing-spaces.d.ts +4 -0
  176. package/dist/rules/no-trailing-spaces.d.ts.map +1 -0
  177. package/dist/rules/no-trailing-spaces.js +24 -0
  178. package/dist/rules/no-trailing-spaces.js.map +1 -0
  179. package/dist/rules/no-undeclared-variables.d.ts +4 -0
  180. package/dist/rules/no-undeclared-variables.d.ts.map +1 -0
  181. package/dist/rules/no-undeclared-variables.js +35 -0
  182. package/dist/rules/no-undeclared-variables.js.map +1 -0
  183. package/dist/rules/no-unnamed-features.d.ts +4 -0
  184. package/dist/rules/no-unnamed-features.d.ts.map +1 -0
  185. package/dist/rules/no-unnamed-features.js +19 -0
  186. package/dist/rules/no-unnamed-features.js.map +1 -0
  187. package/dist/rules/no-unnamed-scenarios.d.ts +4 -0
  188. package/dist/rules/no-unnamed-scenarios.d.ts.map +1 -0
  189. package/dist/rules/no-unnamed-scenarios.js +24 -0
  190. package/dist/rules/no-unnamed-scenarios.js.map +1 -0
  191. package/dist/rules/no-unused-variables.d.ts +4 -0
  192. package/dist/rules/no-unused-variables.d.ts.map +1 -0
  193. package/dist/rules/no-unused-variables.js +36 -0
  194. package/dist/rules/no-unused-variables.js.map +1 -0
  195. package/dist/rules/one-space-between-tags.d.ts +4 -0
  196. package/dist/rules/one-space-between-tags.d.ts.map +1 -0
  197. package/dist/rules/one-space-between-tags.js +36 -0
  198. package/dist/rules/one-space-between-tags.js.map +1 -0
  199. package/dist/rules/only-one-when.d.ts +4 -0
  200. package/dist/rules/only-one-when.d.ts.map +1 -0
  201. package/dist/rules/only-one-when.js +59 -0
  202. package/dist/rules/only-one-when.js.map +1 -0
  203. package/dist/rules/required-tags.d.ts +4 -0
  204. package/dist/rules/required-tags.d.ts.map +1 -0
  205. package/dist/rules/required-tags.js +36 -0
  206. package/dist/rules/required-tags.js.map +1 -0
  207. package/dist/rules/scenario-size.d.ts +4 -0
  208. package/dist/rules/scenario-size.d.ts.map +1 -0
  209. package/dist/rules/scenario-size.js +37 -0
  210. package/dist/rules/scenario-size.js.map +1 -0
  211. package/dist/rules/use-and.d.ts +4 -0
  212. package/dist/rules/use-and.d.ts.map +1 -0
  213. package/dist/rules/use-and.js +33 -0
  214. package/dist/rules/use-and.js.map +1 -0
  215. package/dist/rules.d.ts +24 -0
  216. package/dist/rules.d.ts.map +1 -0
  217. package/dist/rules.js +109 -0
  218. package/dist/rules.js.map +1 -0
  219. package/dist/suppressions.d.ts +33 -0
  220. package/dist/suppressions.d.ts.map +1 -0
  221. package/dist/suppressions.js +84 -0
  222. package/dist/suppressions.js.map +1 -0
  223. package/dist/types.d.ts +81 -0
  224. package/dist/types.d.ts.map +1 -0
  225. package/dist/types.js +2 -0
  226. package/dist/types.js.map +1 -0
  227. package/dist/util/case.d.ts +21 -0
  228. package/dist/util/case.d.ts.map +1 -0
  229. package/dist/util/case.js +42 -0
  230. package/dist/util/case.js.map +1 -0
  231. package/dist/util/collections.d.ts +30 -0
  232. package/dist/util/collections.d.ts.map +1 -0
  233. package/dist/util/collections.js +87 -0
  234. package/dist/util/collections.js.map +1 -0
  235. package/dist/util/glob.d.ts +20 -0
  236. package/dist/util/glob.d.ts.map +1 -0
  237. package/dist/util/glob.js +203 -0
  238. package/dist/util/glob.js.map +1 -0
  239. package/dist/util/json.d.ts +14 -0
  240. package/dist/util/json.d.ts.map +1 -0
  241. package/dist/util/json.js +63 -0
  242. package/dist/util/json.js.map +1 -0
  243. package/dist/util/lines.d.ts +24 -0
  244. package/dist/util/lines.d.ts.map +1 -0
  245. package/dist/util/lines.js +45 -0
  246. package/dist/util/lines.js.map +1 -0
  247. package/dist/util/location.d.ts +15 -0
  248. package/dist/util/location.d.ts.map +1 -0
  249. package/dist/util/location.js +11 -0
  250. package/dist/util/location.js.map +1 -0
  251. package/dist/util/xml.d.ts +16 -0
  252. package/dist/util/xml.d.ts.map +1 -0
  253. package/dist/util/xml.js +36 -0
  254. package/dist/util/xml.js.map +1 -0
  255. package/dist/version.d.ts +3 -0
  256. package/dist/version.d.ts.map +1 -0
  257. package/dist/version.js +24 -0
  258. package/dist/version.js.map +1 -0
  259. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2016, Vasiliki Siakka
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,327 @@
1
+ # gurkencheck
2
+
3
+ [![CI](https://github.com/sebs/gurkencheck/actions/workflows/ci.yml/badge.svg)](https://github.com/sebs/gurkencheck/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/gurkencheck.svg)](https://www.npmjs.com/package/gurkencheck)
5
+
6
+ A linter for Gherkin feature files. It reads your `.feature` files and tells you where
7
+ they drift from the conventions your team has agreed on.
8
+
9
+ **[Documentation](https://sebs.github.io/gurkencheck/)** — every rule has its own
10
+ page with an example that passes and one that fails.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install --save-dev gurkencheck
16
+ ```
17
+
18
+ Requires Node.js 22 or newer.
19
+
20
+ ## Get started
21
+
22
+ Just run it:
23
+
24
+ ```sh
25
+ npx gurkencheck
26
+ ```
27
+
28
+ With no configuration file, gurkencheck uses its **recommended** rules: the ones that catch
29
+ a mistake rather than express a preference — an empty file, a scenario with no name, a
30
+ variable that will never be substituted. Nothing in that set depends on how you lay a file
31
+ out, so it should be quiet on a codebase that has never been linted.
32
+
33
+ When you want something different, create a `.gurkencheckrc` and list the rules you want. A
34
+ configuration file replaces the recommended set rather than adding to it, so every rule is
35
+ off until you switch it on:
36
+
37
+ ```jsonc
38
+ {
39
+ "no-unnamed-features": "on",
40
+ "no-unnamed-scenarios": "on",
41
+ "no-trailing-spaces": "on",
42
+ "indentation": ["on", {"Feature": 0, "Scenario": 2, "Step": 4}]
43
+ }
44
+ ```
45
+
46
+ With no paths given it searches the current directory for `.feature` files.
47
+
48
+ | Exit code | Meaning |
49
+ |---|---|
50
+ | `0` | Nothing worse than a warning |
51
+ | `1` | At least one rule set to `"on"` was broken |
52
+ | `2` | The linter could not run: bad arguments, or an invalid config |
53
+
54
+ Findings go to **stdout**, so `gurkencheck > report.json` and `gurkencheck | less` work.
55
+ Anything that stops the linter running — a bad option, an invalid config — goes to stderr.
56
+
57
+ A rule is set to `"on"`, `"warn"` or `"off"`. `"warn"` reports exactly the same findings but
58
+ does not fail the run — for a rule the team is working towards rather than enforcing:
59
+
60
+ ```json
61
+ {"no-unnamed-scenarios": "on", "use-and": "warn"}
62
+ ```
63
+
64
+ ## Command line
65
+
66
+ ```
67
+ gurkencheck [options] <feature-files>
68
+
69
+ -f, --format <format> output format: stylish, json, junit, sarif, tap,
70
+ or the path to a formatter of your own
71
+ (default: stylish)
72
+ -c, --config <path> configuration file (default: .gurkencheckrc)
73
+ -i, --ignore <globs> comma separated globs to skip, overriding .gurkencheckignore
74
+ -r, --rulesdir <dir> directory of custom rules; may be given more than once
75
+ -l, --language <code> dialect for files with no "# language:" header
76
+ -h, --help show this message
77
+ -v, --version show the version number
78
+ ```
79
+
80
+ ## Rules
81
+
82
+ There are 33 rules you switch on, covering naming, tags, indentation, structure and size,
83
+ plus four that are always on because Gherkin itself refuses to read a file that breaks
84
+ them. Each one is documented at
85
+ **[sebs.github.io/gurkencheck](https://sebs.github.io/gurkencheck/)** with a
86
+ good and a bad example.
87
+
88
+ The configuration file is JSON and may contain comments. A rule is either a state:
89
+
90
+ ```json
91
+ {"no-empty-file": "on"}
92
+ ```
93
+
94
+ or a state plus that rule's own settings:
95
+
96
+ ```json
97
+ {"name-length": ["on", {"Feature": 70, "Scenario": 70, "Step": 70}]}
98
+ ```
99
+
100
+ Mistyped rule names and settings are reported before any file is read.
101
+
102
+ ## Reporting to GitHub code scanning
103
+
104
+ `--format sarif` writes a SARIF 2.1.0 log, which GitHub reads directly:
105
+
106
+ ```yaml
107
+ - run: npx gurkencheck --format sarif > gurkencheck.sarif
108
+ continue-on-error: true
109
+
110
+ - uses: github/codeql-action/upload-sarif@v3
111
+ with:
112
+ sarif_file: gurkencheck.sarif
113
+ ```
114
+
115
+ Paths in the log are relative to the directory gurkencheck ran in, which is what code
116
+ scanning needs to match a finding to a file in the repository.
117
+
118
+ ## Switching a rule off for one place
119
+
120
+ Write a comment in the feature file:
121
+
122
+ ```gherkin
123
+ # gurkencheck-disable-next-line name-length
124
+ Scenario: A name that is long for a good reason and stays that way
125
+
126
+ # gurkencheck-disable use-and, name-length
127
+ ... everything below here skips those two rules ...
128
+ # gurkencheck-enable use-and
129
+
130
+ # gurkencheck-disable-file no-trailing-spaces
131
+ ```
132
+
133
+ | Directive | What it covers |
134
+ |---|---|
135
+ | `gurkencheck-disable-next-line` | The line directly below the comment |
136
+ | `gurkencheck-disable` | From the comment to the end of the file, or to the next `gurkencheck-enable` |
137
+ | `gurkencheck-enable` | Resumes the rules a `gurkencheck-disable` switched off |
138
+ | `gurkencheck-disable-file` | The whole file, wherever the comment appears |
139
+
140
+ Name the rules you mean, separated by commas or spaces; a directive naming no rules covers
141
+ all of them. Comments inside a doc string are text and are left alone. The four always-on
142
+ rules cannot be switched off this way — a file that breaks one of them cannot be read at
143
+ all, so hiding the message would leave nothing in its place.
144
+
145
+ ## Feature files in another language
146
+
147
+ Gherkin is translated into dozens of languages. A file says which one it is written in with
148
+ a header on its first line:
149
+
150
+ ```gherkin
151
+ # language: fr
152
+ Fonctionnalité: Se déconnecter
153
+
154
+ Scénario: Se déconnecter
155
+ Quand Ulrick se déconnecte
156
+ ```
157
+
158
+ If every file in your project is written in the same language, set it once instead — with
159
+ `--language fr` or a `language` key in your configuration file. A header in a file always
160
+ wins over that setting, so a project can be mostly one language with exceptions.
161
+
162
+ ## Sharing a configuration
163
+
164
+ Build on top of another configuration with `extends`. What your file says wins over what it
165
+ extends, and later entries in a list win over earlier ones.
166
+
167
+ ```jsonc
168
+ {
169
+ "extends": "gurkencheck:recommended",
170
+ "indentation": ["on", {"Step": 4}],
171
+ "no-trailing-spaces": "off"
172
+ }
173
+ ```
174
+
175
+ | Entry | What it means |
176
+ |---|---|
177
+ | `gurkencheck:recommended` | The built-in recommended rules |
178
+ | `./team/.gurkencheckrc` | Another file, resolved from the file doing the extending |
179
+ | `@acme/gurkencheck-config` | An installed package exporting a configuration, as JSON or as a module |
180
+
181
+ ## Skipping files
182
+
183
+ Put one glob per line in a `.gurkencheckignore` file, or pass `--ignore` on the command
184
+ line. Without either, `node_modules` is skipped and everything else is checked.
185
+
186
+ A pattern that matches a directory skips everything below it, as in `.gitignore` and
187
+ `.eslintignore`, so `build` is enough and you do not have to write `build/**`. Blank lines
188
+ and lines starting with `#` are ignored.
189
+
190
+ ## Custom formatters
191
+
192
+ Pass a path or a package name to `--format`. The module exports a function taking the
193
+ results; it may print the output itself, or return a string and let gurkencheck print it.
194
+
195
+ ```js
196
+ // count.mjs
197
+ export default function count(results) {
198
+ const findings = results.reduce((total, file) => total + file.errors.length, 0);
199
+ return `${findings} findings in ${results.length} files`;
200
+ }
201
+ ```
202
+
203
+ ```sh
204
+ npx gurkencheck --format ./count.mjs
205
+ ```
206
+
207
+ Each result is `{filePath, errors}`, and each error is
208
+ `{message, rule, line, column, severity}`. A default export, a `printResults` export, or a
209
+ module that is itself the function all work.
210
+
211
+ ## Custom rules
212
+
213
+ Point `--rulesdir` at a directory of your own modules. Each exports an object with a
214
+ `name` and a `run` function, and is called once per file. CommonJS, ES modules and
215
+ TypeScript all work.
216
+
217
+ ```js
218
+ // rules/no-lorem.js
219
+ const name = 'no-lorem';
220
+
221
+ export default {
222
+ name,
223
+ run(feature, file) {
224
+ if (feature === undefined) return [];
225
+ return feature.children
226
+ .filter((child) => child.scenario?.name.includes('lorem'))
227
+ .map((child) => ({
228
+ message: 'Placeholder text left in a scenario name',
229
+ rule: name,
230
+ line: child.scenario.location.line,
231
+ }));
232
+ },
233
+ };
234
+ ```
235
+
236
+ ```sh
237
+ npx gurkencheck --rulesdir ./rules
238
+ ```
239
+
240
+ A custom rule may reuse a built-in rule's name to replace it.
241
+
242
+ `run` may also be `async` and return a promise, for a rule that has to wait for
243
+ something — reading a file, or asking an issue tracker whether a tag refers to a real
244
+ ticket. Files are checked one after another, so rules see a predictable order.
245
+
246
+ ## Using it as a library
247
+
248
+ ```ts
249
+ import {findFeatureFiles, lint, loadRules, readConfiguration} from 'gurkencheck';
250
+
251
+ const rules = await loadRules();
252
+ const config = readConfiguration('.gurkencheckrc', rules);
253
+
254
+ if (config.ok) {
255
+ const {files} = findFeatureFiles(['features']);
256
+ const results = await lint(files, config.configuration, rules);
257
+ }
258
+ ```
259
+
260
+ Each error carries `message`, `rule`, `line` and, where the rule knows one, `column` —
261
+ both 1-based, so an editor can underline exactly the right text. Errors about a whole file
262
+ or a whole line, such as a missing new line at the end of the file, have no `column`.
263
+
264
+ Nothing in the library writes to the console or exits the process; that is the command
265
+ line's job.
266
+
267
+ ## Developing
268
+
269
+ The project is TypeScript with two runtime dependencies:
270
+ [`@cucumber/gherkin`](https://github.com/cucumber/gherkin) and its own `@cucumber/messages`.
271
+ Everything else — argument parsing, globbing, JSON-with-comments, XML output, the
272
+ collection helpers — is plain TypeScript in `src/util`. Tests run on node's built-in test
273
+ runner, straight off the sources using node's type stripping, so there is no build step
274
+ to run before testing.
275
+
276
+ ```sh
277
+ npm test # node --test over test/**/*.test.ts
278
+ npm run coverage # the same, with node's coverage reporter
279
+ npm run typecheck # tsc over src, test, site and scripts
280
+ npm run build # compile src into dist
281
+ npm run docs # regenerate the documentation site into docs/
282
+ npm run demo # lint the deliberately broken files in examples/
283
+ ```
284
+
285
+ ### Releasing
286
+
287
+ Versions are bumped and tagged locally; everything after that is a workflow.
288
+
289
+ ```sh
290
+ npm version patch # bumps package.json, commits, and tags v0.0.4
291
+ git push --follow-tags
292
+ ```
293
+
294
+ Pushing the tag runs two workflows:
295
+
296
+ | Workflow | Trigger | What it does |
297
+ |---|---|---|
298
+ | **Release** | the tag | Checks the tag matches `package.json`, runs the tests, builds, and creates the GitHub release with the npm tarball attached |
299
+ | **Pages** | the tag | Builds the documentation site from that version and deploys it |
300
+
301
+ Publishing to npm is deliberately a separate step, so a release can be looked at before it
302
+ goes out. Run the **Publish** workflow by hand and give it the version — it checks out that
303
+ tag, re-runs the tests, and publishes with provenance. There is no `NPM_TOKEN` in the
304
+ repository: npm Trusted Publishing mints a short-lived credential for the run.
305
+
306
+ The site is not committed. `docs/` is generated during the Pages run, so the published site
307
+ always matches the version it was built from. Run the Pages workflow by hand with a version
308
+ to re-publish or to roll the site back; leave the version empty to publish from the branch
309
+ you run it on, which is useful for previewing a docs change.
310
+
311
+ ### Adding a rule
312
+
313
+ 1. Add `src/rules/<name>.ts`, exporting a default object with `name` and `run`.
314
+ 2. Run `npm run generate:rules` to add it to the registry.
315
+ 3. Add `test/rules/<name>/<name>.test.ts` and its fixtures.
316
+ 4. Add an entry to `site/content.ts` and run `npm run docs`.
317
+
318
+ Steps 2 and 4 are enforced by tests, so a rule cannot ship undocumented or unregistered.
319
+
320
+ ## History
321
+
322
+ gurkencheck is a fork of [gherkin-lint](https://github.com/gherkin-lint/gherkin-lint),
323
+ rewritten in TypeScript. See [MIGRATING.md](MIGRATING.md) if you are coming from it.
324
+
325
+ ## Licence
326
+
327
+ ISC. See [LICENSE](LICENSE).
@@ -0,0 +1,21 @@
1
+ import type { Configuration, RuleRegistry } from './types.ts';
2
+ /** The configuration file looked for when none is given on the command line. */
3
+ export declare const DEFAULT_CONFIG_FILE_NAME = ".gurkencheckrc";
4
+ /** Either a usable configuration, or the reason there isn't one. */
5
+ export type ConfigurationResult = {
6
+ ok: true;
7
+ configuration: Configuration;
8
+ source: string;
9
+ language?: string;
10
+ } | {
11
+ ok: false;
12
+ message: string;
13
+ details: string[];
14
+ };
15
+ /**
16
+ * Reads the configuration file at `configPath`, or the default file in the
17
+ * working directory when no path is given. With neither, the recommended
18
+ * rules are used.
19
+ */
20
+ export declare function readConfiguration(configPath: string | undefined, rules: RuleRegistry): Promise<ConfigurationResult>;
21
+ //# sourceMappingURL=config-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-parser.d.ts","sourceRoot":"","sources":["../src/config-parser.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,YAAY,CAAC;AAG5D,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAQzD,oEAAoE;AACpE,MAAM,MAAM,mBAAmB,GAC3B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,GAC3E;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC;AA6HpD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,mBAAmB,CAAC,CAqD9B"}
@@ -0,0 +1,171 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ /**
10
+ * Reading and validating the configuration file.
11
+ */
12
+ import fs from 'node:fs';
13
+ import { createRequire } from 'node:module';
14
+ import path from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { verifyConfiguration } from './config-verifier.js';
17
+ import { PRESETS, RECOMMENDED } from './presets.js';
18
+ import { stripJsonComments } from './util/json.js';
19
+ /** The configuration file looked for when none is given on the command line. */
20
+ export const DEFAULT_CONFIG_FILE_NAME = '.gurkencheckrc';
21
+ /** The key naming the configurations to build on top of. */
22
+ const EXTENDS = 'extends';
23
+ /** The key setting the dialect for files with no `# language:` header. */
24
+ const LANGUAGE = 'language';
25
+ class ConfigurationError extends Error {
26
+ }
27
+ function isConfigurationObject(value) {
28
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
29
+ }
30
+ function parseFile(filePath) {
31
+ let parsed;
32
+ try {
33
+ parsed = JSON.parse(stripJsonComments(fs.readFileSync(filePath, 'utf8')));
34
+ }
35
+ catch (thrown) {
36
+ throw new ConfigurationError(`Could not parse "${filePath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`);
37
+ }
38
+ if (!isConfigurationObject(parsed)) {
39
+ throw new ConfigurationError(`"${filePath}" must hold a JSON object mapping rule names to their settings.`);
40
+ }
41
+ return parsed;
42
+ }
43
+ /** The `extends` entries of a configuration, as a list. */
44
+ function extendsList(configuration, source) {
45
+ const value = configuration[EXTENDS];
46
+ if (value === undefined) {
47
+ return [];
48
+ }
49
+ if (typeof value === 'string') {
50
+ return [value];
51
+ }
52
+ if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) {
53
+ return value;
54
+ }
55
+ throw new ConfigurationError(`"${EXTENDS}" in "${source}" must be a name or a list of names.`);
56
+ }
57
+ /**
58
+ * Loads what one `extends` entry refers to: a built-in preset, another
59
+ * configuration file, or a package.
60
+ *
61
+ * Packages and relative paths are resolved from the file doing the extending,
62
+ * so a shared configuration installed in a project is found where you would
63
+ * expect and a relative path means what it looks like.
64
+ */
65
+ async function loadExtended(specifier, fromFile) {
66
+ const preset = PRESETS[specifier];
67
+ if (preset !== undefined) {
68
+ return { configuration: preset, source: specifier };
69
+ }
70
+ if (specifier.startsWith('gurkencheck:')) {
71
+ throw new ConfigurationError(`There is no built-in configuration called "${specifier}". Available: ${Object.keys(PRESETS).join(', ')}`);
72
+ }
73
+ const from = path.resolve(fromFile);
74
+ if (specifier.startsWith('.') || path.isAbsolute(specifier)) {
75
+ const resolved = path.resolve(path.dirname(from), specifier);
76
+ if (!fs.existsSync(resolved)) {
77
+ throw new ConfigurationError(`Could not find "${specifier}", extended from "${fromFile}".`);
78
+ }
79
+ return { configuration: parseFile(resolved), source: resolved };
80
+ }
81
+ let resolved;
82
+ try {
83
+ resolved = createRequire(pathToFileURL(from)).resolve(specifier);
84
+ }
85
+ catch {
86
+ throw new ConfigurationError(`Could not resolve the package "${specifier}", extended from "${fromFile}". Is it installed?`);
87
+ }
88
+ if (resolved.endsWith('.json')) {
89
+ return { configuration: parseFile(resolved), source: resolved };
90
+ }
91
+ const module = (await import(__rewriteRelativeImportExtension(pathToFileURL(resolved).href)));
92
+ const exported = module['default'] ?? module;
93
+ if (!isConfigurationObject(exported)) {
94
+ throw new ConfigurationError(`"${specifier}" does not export a configuration object.`);
95
+ }
96
+ return { configuration: exported, source: resolved };
97
+ }
98
+ /**
99
+ * Flattens a configuration and everything it extends into one object. What a
100
+ * file says wins over what it extends, and later entries in an `extends` list
101
+ * win over earlier ones.
102
+ */
103
+ async function flatten(configuration, source, seen) {
104
+ if (seen.has(source)) {
105
+ throw new ConfigurationError(`"${source}" ends up extending itself.`);
106
+ }
107
+ seen.add(source);
108
+ let merged = {};
109
+ for (const specifier of extendsList(configuration, source)) {
110
+ const extended = await loadExtended(specifier, source);
111
+ merged = { ...merged, ...(await flatten(extended.configuration, extended.source, seen)) };
112
+ }
113
+ const own = { ...configuration };
114
+ delete own[EXTENDS];
115
+ return { ...merged, ...own };
116
+ }
117
+ /**
118
+ * Reads the configuration file at `configPath`, or the default file in the
119
+ * working directory when no path is given. With neither, the recommended
120
+ * rules are used.
121
+ */
122
+ export async function readConfiguration(configPath, rules) {
123
+ if (configPath !== undefined) {
124
+ if (!fs.existsSync(configPath)) {
125
+ return {
126
+ ok: false,
127
+ message: `Could not find specified config file "${configPath}"`,
128
+ details: [],
129
+ };
130
+ }
131
+ }
132
+ else {
133
+ if (!fs.existsSync(DEFAULT_CONFIG_FILE_NAME)) {
134
+ // Nothing to read, so fall back to the recommended rules rather than
135
+ // refusing to run. Writing a configuration file is then a way to change
136
+ // the defaults, not a hurdle before the first run.
137
+ return { ok: true, configuration: RECOMMENDED, source: 'the recommended preset' };
138
+ }
139
+ configPath = DEFAULT_CONFIG_FILE_NAME;
140
+ }
141
+ let flattened;
142
+ try {
143
+ flattened = await flatten(parseFile(configPath), configPath, new Set());
144
+ }
145
+ catch (thrown) {
146
+ if (thrown instanceof ConfigurationError) {
147
+ return {
148
+ ok: false,
149
+ message: `Could not read config file "${configPath}"`,
150
+ details: [thrown.message],
151
+ };
152
+ }
153
+ throw thrown;
154
+ }
155
+ // "language" is a setting rather than a rule, so it is taken out before the
156
+ // rest is checked against the rule list.
157
+ const { [LANGUAGE]: language, ...configuration } = flattened;
158
+ if (language !== undefined && typeof language !== 'string') {
159
+ return {
160
+ ok: false,
161
+ message: `Could not read config file "${configPath}"`,
162
+ details: [`"${LANGUAGE}" must be a language code, such as "fr".`],
163
+ };
164
+ }
165
+ const errors = verifyConfiguration(configuration, rules);
166
+ if (errors.length > 0) {
167
+ return { ok: false, message: 'Error(s) in configuration file:', details: errors };
168
+ }
169
+ return { ok: true, configuration, source: configPath, ...(language === undefined ? {} : { language }) };
170
+ }
171
+ //# sourceMappingURL=config-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-parser.js","sourceRoot":"","sources":["../src/config-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Configuration, RuleRegistry } from './types.ts';
2
+ /** Returns a list of problems; an empty list means the file is usable. */
3
+ export declare function verifyConfiguration(configuration: Configuration, rules: RuleRegistry): string[];
4
+ //# sourceMappingURL=config-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-verifier.d.ts","sourceRoot":"","sources":["../src/config-verifier.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,YAAY,CAAC;AAmD5D,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE,CA6C/F"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Checking a configuration file before anything is linted, so that a typo in
3
+ * a rule name is reported once and clearly rather than silently ignored.
4
+ */
5
+ import { ALWAYS_ON_RULES } from './gherkin/parse.js';
6
+ const STATES = ['on', 'warn', 'off'];
7
+ function describeAllowed(availableConfigs) {
8
+ if (Array.isArray(availableConfigs)) {
9
+ return availableConfigs.map((value) => `"${String(value)}"`).join(', ');
10
+ }
11
+ if (typeof availableConfigs === 'object' && availableConfigs !== null) {
12
+ return Object.keys(availableConfigs)
13
+ .map((key) => `"${key}"`)
14
+ .join(', ');
15
+ }
16
+ return '';
17
+ }
18
+ function verifySettings(ruleName, availableConfigs, settings, errors) {
19
+ const prefix = `Invalid rule configuration for "${ruleName}" - `;
20
+ if (availableConfigs === undefined) {
21
+ return;
22
+ }
23
+ if (Array.isArray(availableConfigs)) {
24
+ // The rule takes one value out of a fixed list, e.g. "yes" or "no".
25
+ if (!availableConfigs.includes(settings)) {
26
+ errors.push(`${prefix}"${String(settings)}" is not one of the allowed values: ${describeAllowed(availableConfigs)}`);
27
+ }
28
+ return;
29
+ }
30
+ if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
31
+ errors.push(`${prefix}the settings should be an object with one or more of: ${describeAllowed(availableConfigs)}`);
32
+ return;
33
+ }
34
+ const allowedKeys = new Set(Object.keys(availableConfigs));
35
+ for (const key of Object.keys(settings)) {
36
+ if (!allowedKeys.has(key)) {
37
+ errors.push(`${prefix}the rule has no setting called "${key}". Available settings: ${describeAllowed(availableConfigs)}`);
38
+ }
39
+ }
40
+ }
41
+ /** Returns a list of problems; an empty list means the file is usable. */
42
+ export function verifyConfiguration(configuration, rules) {
43
+ const errors = [];
44
+ for (const [ruleName, ruleConfig] of Object.entries(configuration)) {
45
+ const rule = rules.get(ruleName);
46
+ const prefix = `Invalid rule configuration for "${ruleName}" - `;
47
+ if (rule === undefined) {
48
+ // The rules the parser enforces are documented alongside the rest, so
49
+ // people reasonably list them. Naming one is harmless; asking for it to
50
+ // be off is the only thing worth saying something about.
51
+ if (ALWAYS_ON_RULES.includes(ruleName)) {
52
+ const state = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
53
+ if (state === 'off') {
54
+ errors.push(`${prefix}this rule is always on. A file breaking it cannot be parsed at all, so there is nothing to switch off.`);
55
+ }
56
+ else if (!STATES.includes(state)) {
57
+ errors.push(`${prefix}the config should be "on", "warn" or "off"`);
58
+ }
59
+ continue;
60
+ }
61
+ errors.push(`Rule "${ruleName}" does not exist`);
62
+ continue;
63
+ }
64
+ if (!Array.isArray(ruleConfig)) {
65
+ if (!STATES.includes(ruleConfig)) {
66
+ errors.push(`${prefix}the config should be "on", "warn" or "off"`);
67
+ }
68
+ continue;
69
+ }
70
+ if (!STATES.includes(ruleConfig[0])) {
71
+ errors.push(`${prefix}the first part of the config should be "on", "warn" or "off"`);
72
+ }
73
+ if (ruleConfig.length !== 2) {
74
+ errors.push(`${prefix}the config should have exactly 2 parts: a state and the rule's settings`);
75
+ continue;
76
+ }
77
+ verifySettings(ruleName, rule.availableConfigs, ruleConfig[1], errors);
78
+ }
79
+ return errors;
80
+ }
81
+ //# sourceMappingURL=config-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-verifier.js","sourceRoot":"","sources":["../src/config-verifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /** The ignore file looked for when `--ignore` is not given. */
2
+ export declare const DEFAULT_IGNORE_FILE_NAME = ".gurkencheckignore";
3
+ /** Never worth linting, and expensive to walk into. */
4
+ export declare const DEFAULT_IGNORED_PATTERNS: string[];
5
+ export interface FeatureSearch {
6
+ /** Matching feature files, relative to the working directory. */
7
+ files: string[];
8
+ /** Arguments that were neither a feature file, a directory nor a glob. */
9
+ invalidPatterns: string[];
10
+ }
11
+ /** Reads the ignore file, one glob pattern per line, ignoring blank lines. */
12
+ export declare function readIgnorePatterns(ignoreArgument: readonly string[] | undefined, ignoreFileName?: string): string[];
13
+ /**
14
+ * Finds the feature files named by the given patterns. With no patterns, the
15
+ * working directory is searched recursively.
16
+ */
17
+ export declare function findFeatureFiles(patterns: readonly string[], ignoreArgument?: readonly string[]): FeatureSearch;
18
+ //# sourceMappingURL=feature-finder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-finder.d.ts","sourceRoot":"","sources":["../src/feature-finder.ts"],"names":[],"mappings":"AASA,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAE7D,uDAAuD;AACvD,eAAO,MAAM,wBAAwB,UAAsB,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,0EAA0E;IAC1E,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AA0BD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAC7C,cAAc,GAAE,MAAiC,GAChD,MAAM,EAAE,CAYV;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GACjC,aAAa,CAiBf"}