gurkencheck 0.0.9 → 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.
Files changed (334) hide show
  1. package/README.md +30 -11
  2. package/dist/config-parser.d.ts +15 -1
  3. package/dist/config-parser.js +58 -12
  4. package/dist/config-verifier.d.ts +0 -1
  5. package/dist/config-verifier.js +71 -3
  6. package/dist/diagnostics.d.ts +0 -1
  7. package/dist/diagnostics.js +0 -1
  8. package/dist/exit-codes.d.ts +0 -1
  9. package/dist/exit-codes.js +0 -1
  10. package/dist/feature-finder.d.ts +0 -1
  11. package/dist/feature-finder.js +29 -11
  12. package/dist/formatters/index.d.ts +0 -1
  13. package/dist/formatters/index.js +2 -3
  14. package/dist/formatters/json.d.ts +0 -1
  15. package/dist/formatters/json.js +0 -1
  16. package/dist/formatters/junit.d.ts +1 -13
  17. package/dist/formatters/junit.js +36 -11
  18. package/dist/formatters/sarif.d.ts +0 -1
  19. package/dist/formatters/sarif.js +25 -7
  20. package/dist/formatters/stylish.d.ts +0 -1
  21. package/dist/formatters/stylish.js +0 -1
  22. package/dist/formatters/tap.d.ts +0 -1
  23. package/dist/formatters/tap.js +37 -5
  24. package/dist/gherkin/dialects.d.ts +0 -1
  25. package/dist/gherkin/dialects.js +0 -1
  26. package/dist/gherkin/keywords.d.ts +6 -3
  27. package/dist/gherkin/keywords.js +10 -4
  28. package/dist/gherkin/parse.d.ts +0 -1
  29. package/dist/gherkin/parse.js +19 -7
  30. package/dist/gherkin/traverse.d.ts +0 -1
  31. package/dist/gherkin/traverse.js +0 -1
  32. package/dist/gherkin/variables.d.ts +12 -1
  33. package/dist/gherkin/variables.js +24 -6
  34. package/dist/index.d.ts +1 -2
  35. package/dist/index.js +1 -2
  36. package/dist/linter.d.ts +0 -1
  37. package/dist/linter.js +1 -2
  38. package/dist/logger.d.ts +0 -1
  39. package/dist/logger.js +0 -1
  40. package/dist/main.d.ts +0 -1
  41. package/dist/main.js +78 -27
  42. package/dist/presets.d.ts +2 -2
  43. package/dist/presets.js +0 -5
  44. package/dist/rules/allowed-tags.d.ts +0 -1
  45. package/dist/rules/allowed-tags.js +2 -1
  46. package/dist/rules/background-setup-only.d.ts +0 -1
  47. package/dist/rules/background-setup-only.js +0 -1
  48. package/dist/rules/file-name.d.ts +0 -1
  49. package/dist/rules/file-name.js +10 -1
  50. package/dist/rules/indentation.d.ts +0 -1
  51. package/dist/rules/indentation.js +24 -10
  52. package/dist/rules/index.d.ts +0 -1
  53. package/dist/rules/index.js +0 -1
  54. package/dist/rules/keywords-in-logical-order.d.ts +0 -1
  55. package/dist/rules/keywords-in-logical-order.js +0 -1
  56. package/dist/rules/max-scenarios-per-file.d.ts +0 -1
  57. package/dist/rules/max-scenarios-per-file.js +0 -1
  58. package/dist/rules/name-length.d.ts +0 -1
  59. package/dist/rules/name-length.js +5 -3
  60. package/dist/rules/new-line-at-eof.d.ts +0 -1
  61. package/dist/rules/new-line-at-eof.js +10 -2
  62. package/dist/rules/no-background-only-scenario.d.ts +0 -1
  63. package/dist/rules/no-background-only-scenario.js +0 -1
  64. package/dist/rules/no-dupe-feature-names.d.ts +0 -1
  65. package/dist/rules/no-dupe-feature-names.js +5 -1
  66. package/dist/rules/no-dupe-file-names.d.ts +0 -1
  67. package/dist/rules/no-dupe-file-names.js +0 -1
  68. package/dist/rules/no-dupe-scenario-names.d.ts +0 -1
  69. package/dist/rules/no-dupe-scenario-names.js +4 -1
  70. package/dist/rules/no-duplicate-tags.d.ts +0 -1
  71. package/dist/rules/no-duplicate-tags.js +0 -1
  72. package/dist/rules/no-empty-background.d.ts +0 -1
  73. package/dist/rules/no-empty-background.js +0 -1
  74. package/dist/rules/no-empty-file.d.ts +0 -1
  75. package/dist/rules/no-empty-file.js +0 -1
  76. package/dist/rules/no-examples-in-scenarios.d.ts +0 -1
  77. package/dist/rules/no-examples-in-scenarios.js +0 -1
  78. package/dist/rules/no-files-without-scenarios.d.ts +0 -1
  79. package/dist/rules/no-files-without-scenarios.js +0 -1
  80. package/dist/rules/no-homogenous-tags.d.ts +0 -1
  81. package/dist/rules/no-homogenous-tags.js +0 -1
  82. package/dist/rules/no-multiple-empty-lines.d.ts +0 -1
  83. package/dist/rules/no-multiple-empty-lines.js +2 -3
  84. package/dist/rules/no-partially-commented-tag-lines.d.ts +0 -1
  85. package/dist/rules/no-partially-commented-tag-lines.js +7 -5
  86. package/dist/rules/no-restricted-patterns.d.ts +0 -1
  87. package/dist/rules/no-restricted-patterns.js +64 -19
  88. package/dist/rules/no-restricted-tags.d.ts +0 -1
  89. package/dist/rules/no-restricted-tags.js +2 -1
  90. package/dist/rules/no-scenario-outlines-without-examples.d.ts +0 -1
  91. package/dist/rules/no-scenario-outlines-without-examples.js +0 -1
  92. package/dist/rules/no-scenarios-without-then.d.ts +0 -1
  93. package/dist/rules/no-scenarios-without-then.js +0 -1
  94. package/dist/rules/no-scenarios-without-when.d.ts +0 -1
  95. package/dist/rules/no-scenarios-without-when.js +0 -1
  96. package/dist/rules/no-superfluous-tags.d.ts +0 -1
  97. package/dist/rules/no-superfluous-tags.js +0 -1
  98. package/dist/rules/no-trailing-spaces.d.ts +0 -1
  99. package/dist/rules/no-trailing-spaces.js +13 -4
  100. package/dist/rules/no-undeclared-variables.d.ts +0 -1
  101. package/dist/rules/no-undeclared-variables.js +12 -11
  102. package/dist/rules/no-unnamed-features.d.ts +0 -1
  103. package/dist/rules/no-unnamed-features.js +0 -1
  104. package/dist/rules/no-unnamed-scenarios.d.ts +0 -1
  105. package/dist/rules/no-unnamed-scenarios.js +0 -1
  106. package/dist/rules/no-unused-variables.d.ts +0 -1
  107. package/dist/rules/no-unused-variables.js +0 -1
  108. package/dist/rules/one-space-between-tags.d.ts +0 -1
  109. package/dist/rules/one-space-between-tags.js +3 -2
  110. package/dist/rules/only-one-when.d.ts +0 -1
  111. package/dist/rules/only-one-when.js +5 -5
  112. package/dist/rules/required-tags.d.ts +0 -1
  113. package/dist/rules/required-tags.js +13 -3
  114. package/dist/rules/scenario-size.d.ts +0 -1
  115. package/dist/rules/scenario-size.js +0 -1
  116. package/dist/rules/use-and.d.ts +0 -1
  117. package/dist/rules/use-and.js +2 -2
  118. package/dist/rules.d.ts +0 -1
  119. package/dist/rules.js +49 -4
  120. package/dist/stats/collect.d.ts +0 -1
  121. package/dist/stats/collect.js +0 -1
  122. package/dist/stats/command.d.ts +0 -1
  123. package/dist/stats/command.js +24 -7
  124. package/dist/stats/format/index.d.ts +0 -1
  125. package/dist/stats/format/index.js +0 -1
  126. package/dist/stats/format/json.d.ts +0 -1
  127. package/dist/stats/format/json.js +0 -1
  128. package/dist/stats/format/markdown.d.ts +0 -1
  129. package/dist/stats/format/markdown.js +24 -6
  130. package/dist/stats/format/shared.d.ts +0 -1
  131. package/dist/stats/format/shared.js +0 -1
  132. package/dist/stats/format/text.d.ts +0 -1
  133. package/dist/stats/format/text.js +0 -1
  134. package/dist/stats/index.d.ts +0 -1
  135. package/dist/stats/index.js +0 -1
  136. package/dist/stats/normalise.d.ts +0 -1
  137. package/dist/stats/normalise.js +27 -14
  138. package/dist/stats/similar.d.ts +0 -1
  139. package/dist/stats/similar.js +0 -1
  140. package/dist/stats/types.d.ts +0 -1
  141. package/dist/stats/types.js +0 -1
  142. package/dist/suppressions.d.ts +6 -3
  143. package/dist/suppressions.js +36 -30
  144. package/dist/types.d.ts +7 -1
  145. package/dist/types.js +0 -1
  146. package/dist/util/case.d.ts +2 -2
  147. package/dist/util/case.js +17 -3
  148. package/dist/util/collections.d.ts +0 -1
  149. package/dist/util/collections.js +0 -1
  150. package/dist/util/glob.d.ts +9 -3
  151. package/dist/util/glob.js +115 -27
  152. package/dist/util/json.d.ts +0 -1
  153. package/dist/util/json.js +0 -1
  154. package/dist/util/lines.d.ts +6 -4
  155. package/dist/util/lines.js +22 -4
  156. package/dist/util/location.d.ts +8 -1
  157. package/dist/util/location.js +10 -1
  158. package/dist/util/patterns.d.ts +9 -0
  159. package/dist/util/patterns.js +21 -0
  160. package/dist/util/resolve.d.ts +6 -0
  161. package/dist/util/resolve.js +101 -0
  162. package/dist/util/stream.d.ts +0 -1
  163. package/dist/util/stream.js +3 -2
  164. package/dist/util/xml.d.ts +0 -1
  165. package/dist/util/xml.js +17 -3
  166. package/dist/version.d.ts +0 -1
  167. package/dist/version.js +0 -1
  168. package/dist/watch.d.ts +11 -1
  169. package/dist/watch.js +38 -3
  170. package/package.json +6 -2
  171. package/dist/config-parser.d.ts.map +0 -1
  172. package/dist/config-parser.js.map +0 -1
  173. package/dist/config-verifier.d.ts.map +0 -1
  174. package/dist/config-verifier.js.map +0 -1
  175. package/dist/diagnostics.d.ts.map +0 -1
  176. package/dist/diagnostics.js.map +0 -1
  177. package/dist/exit-codes.d.ts.map +0 -1
  178. package/dist/exit-codes.js.map +0 -1
  179. package/dist/feature-finder.d.ts.map +0 -1
  180. package/dist/feature-finder.js.map +0 -1
  181. package/dist/formatters/index.d.ts.map +0 -1
  182. package/dist/formatters/index.js.map +0 -1
  183. package/dist/formatters/json.d.ts.map +0 -1
  184. package/dist/formatters/json.js.map +0 -1
  185. package/dist/formatters/junit.d.ts.map +0 -1
  186. package/dist/formatters/junit.js.map +0 -1
  187. package/dist/formatters/sarif.d.ts.map +0 -1
  188. package/dist/formatters/sarif.js.map +0 -1
  189. package/dist/formatters/stylish.d.ts.map +0 -1
  190. package/dist/formatters/stylish.js.map +0 -1
  191. package/dist/formatters/tap.d.ts.map +0 -1
  192. package/dist/formatters/tap.js.map +0 -1
  193. package/dist/gherkin/dialects.d.ts.map +0 -1
  194. package/dist/gherkin/dialects.js.map +0 -1
  195. package/dist/gherkin/keywords.d.ts.map +0 -1
  196. package/dist/gherkin/keywords.js.map +0 -1
  197. package/dist/gherkin/parse.d.ts.map +0 -1
  198. package/dist/gherkin/parse.js.map +0 -1
  199. package/dist/gherkin/traverse.d.ts.map +0 -1
  200. package/dist/gherkin/traverse.js.map +0 -1
  201. package/dist/gherkin/variables.d.ts.map +0 -1
  202. package/dist/gherkin/variables.js.map +0 -1
  203. package/dist/index.d.ts.map +0 -1
  204. package/dist/index.js.map +0 -1
  205. package/dist/linter.d.ts.map +0 -1
  206. package/dist/linter.js.map +0 -1
  207. package/dist/logger.d.ts.map +0 -1
  208. package/dist/logger.js.map +0 -1
  209. package/dist/main.d.ts.map +0 -1
  210. package/dist/main.js.map +0 -1
  211. package/dist/presets.d.ts.map +0 -1
  212. package/dist/presets.js.map +0 -1
  213. package/dist/rules/allowed-tags.d.ts.map +0 -1
  214. package/dist/rules/allowed-tags.js.map +0 -1
  215. package/dist/rules/background-setup-only.d.ts.map +0 -1
  216. package/dist/rules/background-setup-only.js.map +0 -1
  217. package/dist/rules/file-name.d.ts.map +0 -1
  218. package/dist/rules/file-name.js.map +0 -1
  219. package/dist/rules/indentation.d.ts.map +0 -1
  220. package/dist/rules/indentation.js.map +0 -1
  221. package/dist/rules/index.d.ts.map +0 -1
  222. package/dist/rules/index.js.map +0 -1
  223. package/dist/rules/keywords-in-logical-order.d.ts.map +0 -1
  224. package/dist/rules/keywords-in-logical-order.js.map +0 -1
  225. package/dist/rules/max-scenarios-per-file.d.ts.map +0 -1
  226. package/dist/rules/max-scenarios-per-file.js.map +0 -1
  227. package/dist/rules/name-length.d.ts.map +0 -1
  228. package/dist/rules/name-length.js.map +0 -1
  229. package/dist/rules/new-line-at-eof.d.ts.map +0 -1
  230. package/dist/rules/new-line-at-eof.js.map +0 -1
  231. package/dist/rules/no-background-only-scenario.d.ts.map +0 -1
  232. package/dist/rules/no-background-only-scenario.js.map +0 -1
  233. package/dist/rules/no-dupe-feature-names.d.ts.map +0 -1
  234. package/dist/rules/no-dupe-feature-names.js.map +0 -1
  235. package/dist/rules/no-dupe-file-names.d.ts.map +0 -1
  236. package/dist/rules/no-dupe-file-names.js.map +0 -1
  237. package/dist/rules/no-dupe-scenario-names.d.ts.map +0 -1
  238. package/dist/rules/no-dupe-scenario-names.js.map +0 -1
  239. package/dist/rules/no-duplicate-tags.d.ts.map +0 -1
  240. package/dist/rules/no-duplicate-tags.js.map +0 -1
  241. package/dist/rules/no-empty-background.d.ts.map +0 -1
  242. package/dist/rules/no-empty-background.js.map +0 -1
  243. package/dist/rules/no-empty-file.d.ts.map +0 -1
  244. package/dist/rules/no-empty-file.js.map +0 -1
  245. package/dist/rules/no-examples-in-scenarios.d.ts.map +0 -1
  246. package/dist/rules/no-examples-in-scenarios.js.map +0 -1
  247. package/dist/rules/no-files-without-scenarios.d.ts.map +0 -1
  248. package/dist/rules/no-files-without-scenarios.js.map +0 -1
  249. package/dist/rules/no-homogenous-tags.d.ts.map +0 -1
  250. package/dist/rules/no-homogenous-tags.js.map +0 -1
  251. package/dist/rules/no-multiple-empty-lines.d.ts.map +0 -1
  252. package/dist/rules/no-multiple-empty-lines.js.map +0 -1
  253. package/dist/rules/no-partially-commented-tag-lines.d.ts.map +0 -1
  254. package/dist/rules/no-partially-commented-tag-lines.js.map +0 -1
  255. package/dist/rules/no-restricted-patterns.d.ts.map +0 -1
  256. package/dist/rules/no-restricted-patterns.js.map +0 -1
  257. package/dist/rules/no-restricted-tags.d.ts.map +0 -1
  258. package/dist/rules/no-restricted-tags.js.map +0 -1
  259. package/dist/rules/no-scenario-outlines-without-examples.d.ts.map +0 -1
  260. package/dist/rules/no-scenario-outlines-without-examples.js.map +0 -1
  261. package/dist/rules/no-scenarios-without-then.d.ts.map +0 -1
  262. package/dist/rules/no-scenarios-without-then.js.map +0 -1
  263. package/dist/rules/no-scenarios-without-when.d.ts.map +0 -1
  264. package/dist/rules/no-scenarios-without-when.js.map +0 -1
  265. package/dist/rules/no-superfluous-tags.d.ts.map +0 -1
  266. package/dist/rules/no-superfluous-tags.js.map +0 -1
  267. package/dist/rules/no-trailing-spaces.d.ts.map +0 -1
  268. package/dist/rules/no-trailing-spaces.js.map +0 -1
  269. package/dist/rules/no-undeclared-variables.d.ts.map +0 -1
  270. package/dist/rules/no-undeclared-variables.js.map +0 -1
  271. package/dist/rules/no-unnamed-features.d.ts.map +0 -1
  272. package/dist/rules/no-unnamed-features.js.map +0 -1
  273. package/dist/rules/no-unnamed-scenarios.d.ts.map +0 -1
  274. package/dist/rules/no-unnamed-scenarios.js.map +0 -1
  275. package/dist/rules/no-unused-variables.d.ts.map +0 -1
  276. package/dist/rules/no-unused-variables.js.map +0 -1
  277. package/dist/rules/one-space-between-tags.d.ts.map +0 -1
  278. package/dist/rules/one-space-between-tags.js.map +0 -1
  279. package/dist/rules/only-one-when.d.ts.map +0 -1
  280. package/dist/rules/only-one-when.js.map +0 -1
  281. package/dist/rules/required-tags.d.ts.map +0 -1
  282. package/dist/rules/required-tags.js.map +0 -1
  283. package/dist/rules/scenario-size.d.ts.map +0 -1
  284. package/dist/rules/scenario-size.js.map +0 -1
  285. package/dist/rules/use-and.d.ts.map +0 -1
  286. package/dist/rules/use-and.js.map +0 -1
  287. package/dist/rules.d.ts.map +0 -1
  288. package/dist/rules.js.map +0 -1
  289. package/dist/stats/collect.d.ts.map +0 -1
  290. package/dist/stats/collect.js.map +0 -1
  291. package/dist/stats/command.d.ts.map +0 -1
  292. package/dist/stats/command.js.map +0 -1
  293. package/dist/stats/format/index.d.ts.map +0 -1
  294. package/dist/stats/format/index.js.map +0 -1
  295. package/dist/stats/format/json.d.ts.map +0 -1
  296. package/dist/stats/format/json.js.map +0 -1
  297. package/dist/stats/format/markdown.d.ts.map +0 -1
  298. package/dist/stats/format/markdown.js.map +0 -1
  299. package/dist/stats/format/shared.d.ts.map +0 -1
  300. package/dist/stats/format/shared.js.map +0 -1
  301. package/dist/stats/format/text.d.ts.map +0 -1
  302. package/dist/stats/format/text.js.map +0 -1
  303. package/dist/stats/index.d.ts.map +0 -1
  304. package/dist/stats/index.js.map +0 -1
  305. package/dist/stats/normalise.d.ts.map +0 -1
  306. package/dist/stats/normalise.js.map +0 -1
  307. package/dist/stats/similar.d.ts.map +0 -1
  308. package/dist/stats/similar.js.map +0 -1
  309. package/dist/stats/types.d.ts.map +0 -1
  310. package/dist/stats/types.js.map +0 -1
  311. package/dist/suppressions.d.ts.map +0 -1
  312. package/dist/suppressions.js.map +0 -1
  313. package/dist/types.d.ts.map +0 -1
  314. package/dist/types.js.map +0 -1
  315. package/dist/util/case.d.ts.map +0 -1
  316. package/dist/util/case.js.map +0 -1
  317. package/dist/util/collections.d.ts.map +0 -1
  318. package/dist/util/collections.js.map +0 -1
  319. package/dist/util/glob.d.ts.map +0 -1
  320. package/dist/util/glob.js.map +0 -1
  321. package/dist/util/json.d.ts.map +0 -1
  322. package/dist/util/json.js.map +0 -1
  323. package/dist/util/lines.d.ts.map +0 -1
  324. package/dist/util/lines.js.map +0 -1
  325. package/dist/util/location.d.ts.map +0 -1
  326. package/dist/util/location.js.map +0 -1
  327. package/dist/util/stream.d.ts.map +0 -1
  328. package/dist/util/stream.js.map +0 -1
  329. package/dist/util/xml.d.ts.map +0 -1
  330. package/dist/util/xml.js.map +0 -1
  331. package/dist/version.d.ts.map +0 -1
  332. package/dist/version.js.map +0 -1
  333. package/dist/watch.d.ts.map +0 -1
  334. package/dist/watch.js.map +0 -1
@@ -8,24 +8,41 @@
8
8
  * See https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
9
9
  */
10
10
  import path from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
12
+ import { ALWAYS_ON_RULES } from '../gherkin/parse.js';
13
+ import { BUILT_IN_RULES } from '../rules/index.js';
11
14
  import { version } from '../version.js';
12
15
  const SCHEMA = 'https://json.schemastore.org/sarif-2.1.0.json';
13
16
  const SARIF_VERSION = '2.1.0';
14
17
  const DOCUMENTATION = 'https://sebs.github.io/gurkencheck/';
18
+ /** The rules with a page on the documentation site. */
19
+ const DOCUMENTED_RULES = new Set([
20
+ ...BUILT_IN_RULES.map((rule) => rule.name),
21
+ ...ALWAYS_ON_RULES,
22
+ ]);
15
23
  /** SARIF's levels, which happen to line up with ours. */
16
24
  const LEVEL = { error: 'error', warning: 'warning' };
17
25
  function levelOf(error) {
18
26
  return LEVEL[error.severity ?? 'error'];
19
27
  }
20
28
  /**
21
- * Paths relative to the working directory, with forward slashes.
29
+ * Paths relative to the working directory, with forward slashes, as a URI.
22
30
  *
23
31
  * Code scanning matches a result to a file in the repository by this URI, so
24
32
  * an absolute path from whichever machine ran the linter would match nothing.
33
+ * It is a URI rather than a path, so each segment is percent-encoded: a `#`
34
+ * in a file name would otherwise start a fragment, and `%20` read as a space.
35
+ *
36
+ * A file outside the working directory is not in the repository as code
37
+ * scanning sees it, so a relative `../` path would match nothing either; it
38
+ * gets an absolute `file:` URI, which at least names the file exactly.
25
39
  */
26
40
  function uriFor(filePath, cwd) {
27
41
  const relative = path.relative(cwd, filePath);
28
- return relative.split(path.sep).join('/');
42
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
43
+ return pathToFileURL(path.resolve(cwd, filePath)).href;
44
+ }
45
+ return relative.split(path.sep).map(encodeURIComponent).join('/');
29
46
  }
30
47
  function toResult(result, error, cwd) {
31
48
  const physicalLocation = {
@@ -63,12 +80,14 @@ export function toSarif(results, cwd = process.cwd()) {
63
80
  name: 'gurkencheck',
64
81
  informationUri: DOCUMENTATION,
65
82
  version: version(),
66
- rules: ruleIds.map((id) => ({
67
- id,
68
- helpUri: `${DOCUMENTATION}rules/${id}.html`,
69
- })),
83
+ // Only the rules documented on the site get a link: one to a
84
+ // custom rule's page, or to unexpected-error's, would be a 404.
85
+ rules: ruleIds.map((id) => DOCUMENTED_RULES.has(id) ? { id, helpUri: `${DOCUMENTATION}rules/${id}.html` } : { id }),
70
86
  },
71
87
  },
88
+ // Columns count characters, as the Gherkin parser does, not the
89
+ // UTF-16 units some tools assume.
90
+ columnKind: 'unicodeCodePoints',
72
91
  results: findings,
73
92
  },
74
93
  ],
@@ -78,4 +97,3 @@ export function toSarif(results, cwd = process.cwd()) {
78
97
  export function printResults(results) {
79
98
  console.log(JSON.stringify(toSarif(results), null, 2));
80
99
  }
81
- //# sourceMappingURL=sarif.js.map
@@ -9,4 +9,3 @@ export declare function startRun(): FormatterRun;
9
9
  * stops the linter running is written to stderr instead.
10
10
  */
11
11
  export declare function printResults(results: readonly FileResult[]): void;
12
- //# sourceMappingURL=stylish.d.ts.map
@@ -94,4 +94,3 @@ export function printResults(results) {
94
94
  console.log('\n');
95
95
  }
96
96
  }
97
- //# sourceMappingURL=stylish.js.map
@@ -25,4 +25,3 @@ export declare function startRun(): FormatterRun;
25
25
  export declare function format(results: readonly FileResult[]): string;
26
26
  /** Writes the TAP stream to stdout. */
27
27
  export declare function printResults(results: readonly FileResult[]): void;
28
- //# sourceMappingURL=tap.d.ts.map
@@ -1,9 +1,42 @@
1
1
  /**
2
- * Quotes a scalar for YAML using single quotes, in which the only character
3
- * needing attention is the quote itself.
2
+ * Characters a single-quoted YAML scalar cannot hold as they are: anything
3
+ * YAML does not count as printable, and line breaks, which it would fold.
4
+ */
5
+ const NEEDS_ESCAPING = /[^\t\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u;
6
+ /**
7
+ * Quotes a scalar for YAML. Single quotes, in which the only character needing
8
+ * attention is the quote itself, unless the value holds something they cannot
9
+ * carry; then double quotes, which can escape anything.
4
10
  */
5
11
  function quote(value) {
6
- return `'${value.split("'").join("''")}'`;
12
+ if (!NEEDS_ESCAPING.test(value)) {
13
+ return `'${value.split("'").join("''")}'`;
14
+ }
15
+ let escaped = '';
16
+ for (const character of value) {
17
+ if (character === '"' || character === '\\') {
18
+ escaped += `\\${character}`;
19
+ }
20
+ else if (NEEDS_ESCAPING.test(character)) {
21
+ const code = character.codePointAt(0);
22
+ escaped +=
23
+ code <= 0xff
24
+ ? `\\x${code.toString(16).padStart(2, '0')}`
25
+ : `\\u${code.toString(16).padStart(4, '0')}`;
26
+ }
27
+ else {
28
+ escaped += character;
29
+ }
30
+ }
31
+ return `"${escaped}"`;
32
+ }
33
+ /**
34
+ * Escapes a test point's description. An unescaped `#` starts a directive, so
35
+ * a failing file whose path holds `# TODO` or `# SKIP` would be read as one
36
+ * that is allowed to fail, and the run would pass.
37
+ */
38
+ function description(text) {
39
+ return text.replace(/[\\#]/g, '\\$&');
7
40
  }
8
41
  function severityOf(error) {
9
42
  return error.severity ?? 'error';
@@ -41,7 +74,7 @@ export function startRun() {
41
74
  file(result) {
42
75
  count++;
43
76
  const status = fails(result) ? 'not ok' : 'ok';
44
- return [`${status} ${count} - ${result.filePath}`, ...diagnostics(result), ''].join('\n');
77
+ return [`${status} ${count} - ${description(result.filePath)}`, ...diagnostics(result), ''].join('\n');
45
78
  },
46
79
  end: () => `1..${count}\n`,
47
80
  };
@@ -57,4 +90,3 @@ export function format(results) {
57
90
  export function printResults(results) {
58
91
  console.log(format(results));
59
92
  }
60
- //# sourceMappingURL=tap.js.map
@@ -28,4 +28,3 @@ export declare function isKnownLanguage(language: string): boolean;
28
28
  * entirely in one language set it once instead of in every file.
29
29
  */
30
30
  export declare function detectLanguage(lines: readonly string[], fallback?: string): string;
31
- //# sourceMappingURL=dialects.d.ts.map
@@ -37,4 +37,3 @@ export function detectLanguage(lines, fallback = DEFAULT_LANGUAGE) {
37
37
  }
38
38
  return isKnownLanguage(fallback) ? fallback : DEFAULT_LANGUAGE;
39
39
  }
40
- //# sourceMappingURL=dialects.js.map
@@ -8,10 +8,14 @@ import type { Background, Examples, Feature, Rule, Scenario, Step } from '@cucum
8
8
  /** Any node that carries a Gherkin keyword. */
9
9
  export type KeywordNode = Feature | Rule | Background | Scenario | Examples | Step;
10
10
  /** The keyword categories used across the rules. */
11
- export type NeutralKeyword = 'feature' | 'rule' | 'background' | 'scenario' | 'scenariooutline' | 'examples' | 'given' | 'when' | 'then' | 'and' | 'but' | '';
11
+ export type NeutralKeyword = 'feature' | 'rule' | 'background' | 'scenario' | 'scenariooutline' | 'examples' | 'given' | 'when' | 'then' | 'and' | 'but' | 'star' | '';
12
12
  /**
13
13
  * Maps a node's localised keyword (`Szenario`) onto the language-neutral name
14
14
  * (`scenario`). Returns an empty string for keywords the dialect does not know.
15
+ *
16
+ * `*` is `star` of its own. Every dialect lists it under all five step
17
+ * keywords, so looking it up would say `given` - but like `And` it carries on
18
+ * from the step before it, which is how Cucumber reads it too.
15
19
  */
16
20
  export declare function getNeutralKeyword(node: Pick<KeywordNode, 'keyword'>, language: string | undefined): NeutralKeyword;
17
21
  /**
@@ -24,8 +28,7 @@ export declare function getNeutralKeyword(node: Pick<KeywordNode, 'keyword'>, la
24
28
  * the empty string, the same as a keyword the dialect does not know.
25
29
  */
26
30
  export declare function resolvedStepKeywords(steps: readonly Pick<Step, 'keyword'>[], language: string | undefined): NeutralKeyword[];
27
- /** True when the keyword is a Given/When/Then/And/But step keyword. */
31
+ /** True when the keyword is a Given/When/Then/And/But/`*` step keyword. */
28
32
  export declare function isStepKeyword(keyword: NeutralKeyword): boolean;
29
33
  /** The display name for a node, as used in error messages. */
30
34
  export declare function getNodeType(node: Pick<KeywordNode, 'keyword'>, language: string | undefined): string;
31
- //# sourceMappingURL=keywords.d.ts.map
@@ -12,12 +12,19 @@ const KEYWORD_FIELDS = [
12
12
  'and',
13
13
  'but',
14
14
  ];
15
- const STEP_KEYWORDS = new Set(['given', 'when', 'then', 'and', 'but']);
15
+ const STEP_KEYWORDS = new Set(['given', 'when', 'then', 'and', 'but', 'star']);
16
16
  /**
17
17
  * Maps a node's localised keyword (`Szenario`) onto the language-neutral name
18
18
  * (`scenario`). Returns an empty string for keywords the dialect does not know.
19
+ *
20
+ * `*` is `star` of its own. Every dialect lists it under all five step
21
+ * keywords, so looking it up would say `given` - but like `And` it carries on
22
+ * from the step before it, which is how Cucumber reads it too.
19
23
  */
20
24
  export function getNeutralKeyword(node, language) {
25
+ if (node.keyword.trim() === '*') {
26
+ return 'star';
27
+ }
21
28
  const dialect = getDialect(language);
22
29
  for (const field of KEYWORD_FIELDS) {
23
30
  if (dialect[field].includes(node.keyword)) {
@@ -39,14 +46,14 @@ export function resolvedStepKeywords(steps, language) {
39
46
  let carried = '';
40
47
  return steps.map((step) => {
41
48
  const keyword = getNeutralKeyword(step, language);
42
- if (keyword === 'and' || keyword === 'but') {
49
+ if (keyword === 'and' || keyword === 'but' || keyword === 'star') {
43
50
  return carried;
44
51
  }
45
52
  carried = keyword;
46
53
  return keyword;
47
54
  });
48
55
  }
49
- /** True when the keyword is a Given/When/Then/And/But step keyword. */
56
+ /** True when the keyword is a Given/When/Then/And/But/`*` step keyword. */
50
57
  export function isStepKeyword(keyword) {
51
58
  return STEP_KEYWORDS.has(keyword);
52
59
  }
@@ -70,4 +77,3 @@ export function getNodeType(node, language) {
70
77
  return isStepKeyword(keyword) ? 'Step' : '';
71
78
  }
72
79
  }
73
- //# sourceMappingURL=keywords.js.map
@@ -70,4 +70,3 @@ export interface ReadOptions {
70
70
  * is what the rules looking across files need.
71
71
  */
72
72
  export declare function readAndParseFiles(files: Sequence<string>, options?: ReadOptions): AsyncGenerator<ParseResult>;
73
- //# sourceMappingURL=parse.d.ts.map
@@ -81,16 +81,26 @@ function startsWithKeyword(text, keywords) {
81
81
  return keywords.some((keyword) => text.startsWith(`${keyword.trim()}:`));
82
82
  }
83
83
  /**
84
- * True when a Background is already open above `line`.
84
+ * True when a Background is already open above `line`, in the same Feature
85
+ * or Rule.
85
86
  *
86
87
  * The parser rejects a second Background and a Background written below a
87
88
  * Scenario in exactly the same way, but they are different mistakes with
88
- * different fixes, so the lines above are checked to tell them apart.
89
+ * different fixes, so the lines above are checked to tell them apart. A Rule
90
+ * may have a Background of its own beside the Feature's, so the search stops
91
+ * at the Rule the line belongs to.
89
92
  */
90
93
  function hasEarlierBackground(lines, line, dialect) {
91
- return lines
92
- .slice(0, Math.max(0, line - 1))
93
- .some((text) => startsWithKeyword(text.trim(), dialect.background));
94
+ for (let index = line - 2; index >= 0; index--) {
95
+ const text = lines[index].trim();
96
+ if (startsWithKeyword(text, dialect.background)) {
97
+ return true;
98
+ }
99
+ if (startsWithKeyword(text, dialect.rule)) {
100
+ return false;
101
+ }
102
+ }
103
+ return false;
94
104
  }
95
105
  /** The step keywords of a dialect, without the `*` wildcard, for messages. */
96
106
  function stepKeywordsFor(dialect) {
@@ -191,7 +201,10 @@ export function toLines(source) {
191
201
  * language.
192
202
  */
193
203
  export function parseFeature(relativePath, source, defaultLanguage = DEFAULT_LANGUAGE) {
194
- const lines = toLines(source);
204
+ // A byte order mark, as some Windows editors write, is not text: left in,
205
+ // it is one more character at the start of line 1, and every column on
206
+ // that line is one out.
207
+ const lines = toLines(source.replace(/^\uFEFF/u, ''));
195
208
  const file = { relativePath, lines };
196
209
  const dialect = getDialect(detectLanguage(lines, defaultLanguage));
197
210
  const errors = [];
@@ -298,4 +311,3 @@ export const DEFAULT_READ_AHEAD = 16;
298
311
  export function readAndParseFiles(files, options = {}) {
299
312
  return mapWithWindow(files, options.readAhead ?? DEFAULT_READ_AHEAD, (file) => readAndParseFile(file, options.language));
300
313
  }
301
- //# sourceMappingURL=parse.js.map
@@ -50,4 +50,3 @@ export interface TaggedNode {
50
50
  * with the ones it inherits does not have to walk the tree itself.
51
51
  */
52
52
  export declare function taggedNodesOf(feature: Feature): Generator<TaggedNode>;
53
- //# sourceMappingURL=traverse.d.ts.map
@@ -109,4 +109,3 @@ export function* taggedNodesOf(feature) {
109
109
  }
110
110
  }
111
111
  }
112
- //# sourceMappingURL=traverse.js.map
@@ -14,13 +14,24 @@ export interface Position {
14
14
  }
15
15
  /** Variable name -> every place it appears. */
16
16
  export type Sightings = Map<string, Position[]>;
17
+ /** The columns one Examples table declares. */
18
+ export interface ExamplesTable {
19
+ /** The line of the `Examples:` keyword. */
20
+ line: number;
21
+ columns: Set<string>;
22
+ }
17
23
  /** What a Scenario Outline declares, and what it actually uses. */
18
24
  export interface ScenarioVariables {
19
25
  /** Column names, against the Examples header cells declaring them. */
20
26
  declared: Sightings;
27
+ /**
28
+ * Each Examples table with a header, on its own. Cucumber runs the rows of
29
+ * each table separately, so a variable has to be declared in every one of
30
+ * them, not merely in one.
31
+ */
32
+ tables: ExamplesTable[];
21
33
  /** Variable names, against every node using them. */
22
34
  used: Sightings;
23
35
  }
24
36
  /** Gathers the variables a Scenario Outline declares and the ones it uses. */
25
37
  export declare function variablesOf(scenario: Scenario): ScenarioVariables;
26
- //# sourceMappingURL=variables.d.ts.map
@@ -1,6 +1,13 @@
1
1
  import { at } from '../util/location.js';
2
- /** `<name>` placeholders, as used in Scenario Outlines. */
3
- const VARIABLE = /<([^>]*)>/gu;
2
+ /**
3
+ * `<name>` placeholders, as used in Scenario Outlines.
4
+ *
5
+ * Cucumber substitutes any column name written in angle brackets, so what
6
+ * counts as one here is only what could be a column name: not starting or
7
+ * ending with a space or `/`, and holding no quotes or `=`. Without that,
8
+ * `x < 5 and y > 3`, `<div class="x">` and `<br/>` would read as variables.
9
+ */
10
+ const VARIABLE = /<([^<>\s"'=/](?:[^<>\n"'=]*[^<>\s"'=/])?)>/gu;
4
11
  function note(variable, position, into) {
5
12
  const seen = into.get(variable);
6
13
  if (seen === undefined) {
@@ -25,12 +32,19 @@ function collect(text, position, into) {
25
32
  export function variablesOf(scenario) {
26
33
  const declared = new Map();
27
34
  const used = new Map();
35
+ const tables = [];
28
36
  for (const examples of scenario.examples) {
29
- for (const cell of examples.tableHeader?.cells ?? []) {
37
+ if (examples.tableHeader === undefined) {
38
+ continue;
39
+ }
40
+ const columns = new Set();
41
+ for (const cell of examples.tableHeader.cells) {
30
42
  if (cell.value !== '') {
31
43
  note(cell.value, at(cell.location), declared);
44
+ columns.add(cell.value);
32
45
  }
33
46
  }
47
+ tables.push({ line: examples.location.line, columns });
34
48
  }
35
49
  collect(scenario.name, at(scenario.location), used);
36
50
  for (const step of scenario.steps) {
@@ -42,10 +56,14 @@ export function variablesOf(scenario) {
42
56
  collect(cell.value, at(cell.location), used);
43
57
  }
44
58
  }
59
+ // A doc string's content starts on the line below its opening delimiter,
60
+ // and each line of it is reported where it is.
45
61
  if (step.docString !== undefined) {
46
- collect(step.docString.content, at(step.location), used);
62
+ const { content, location } = step.docString;
63
+ content.split('\n').forEach((line, index) => {
64
+ collect(line, { line: location.line + 1 + index, column: location.column }, used);
65
+ });
47
66
  }
48
67
  }
49
- return { declared, used };
68
+ return { declared, tables, used };
50
69
  }
51
- //# sourceMappingURL=variables.js.map
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * import {findFeatureFiles, lint, loadRules, readConfiguration} from 'gurkencheck';
6
6
  *
7
7
  * const rules = await loadRules();
8
- * const config = readConfiguration('.gurkencheckrc', rules);
8
+ * const config = await readConfiguration('.gurkencheckrc', rules);
9
9
  * if (config.ok) {
10
10
  * const {files} = findFeatureFiles(['features']);
11
11
  * const results = await lint(files, config.configuration, rules);
@@ -54,4 +54,3 @@ export type { StatsFormatOptions, StatsFormatter } from './stats/format/index.ts
54
54
  export { runStats, statsUsage } from './stats/command.ts';
55
55
  export type { Distribution, Inventory, KeywordMix, LanguageEntry, ScenarioRef, ScenarioStats, SimilarGroup, Statistics, StepEntry, StepStats, TagEntry, TagStats, UnreadableFile, } from './stats/types.ts';
56
56
  export type { Configuration, FeatureFile, FileResult, LintRule, RuleConfig, RuleError, RuleRegistry, RuleState, RunContext, RunFinding, Severity, } from './types.ts';
57
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * import {findFeatureFiles, lint, loadRules, readConfiguration} from 'gurkencheck';
6
6
  *
7
7
  * const rules = await loadRules();
8
- * const config = readConfiguration('.gurkencheckrc', rules);
8
+ * const config = await readConfiguration('.gurkencheckrc', rules);
9
9
  * if (config.ok) {
10
10
  * const {files} = findFeatureFiles(['features']);
11
11
  * const results = await lint(files, config.configuration, rules);
@@ -38,4 +38,3 @@ export { DEFAULT_SIMILARITY, boundedEditDistance, groupSimilar } from './stats/s
38
38
  export { NUMBER_MARKER, PLACEHOLDER_MARKER, STRING_MARKER, countWords, normaliseStepText, } from './stats/normalise.js';
39
39
  export { DEFAULT_STATS_FORMAT, DEFAULT_TOP, STATS_FORMATTERS, getStatsFormatter, toJson as statisticsToJson, toMarkdown, toText, } from './stats/format/index.js';
40
40
  export { runStats, statsUsage } from './stats/command.js';
41
- //# sourceMappingURL=index.js.map
package/dist/linter.d.ts CHANGED
@@ -48,4 +48,3 @@ export declare function countBySeverity(results: readonly FileResult[]): {
48
48
  errors: number;
49
49
  warnings: number;
50
50
  };
51
- //# sourceMappingURL=linter.d.ts.map
package/dist/linter.js CHANGED
@@ -54,7 +54,7 @@ export async function* lintStream(files, configuration, rules, options = {}) {
54
54
  let suppressions = EMPTY_SUPPRESSIONS;
55
55
  if (errors.length === 0) {
56
56
  errors = await runEnabledRules(result.feature, result.file, configuration, rules, run);
57
- suppressions = readSuppressions(result.file.lines);
57
+ suppressions = readSuppressions(result.file.lines, result.feature);
58
58
  if (!suppressions.isEmpty) {
59
59
  errors = errors.filter((error) => !suppressions.isSuppressed(error));
60
60
  }
@@ -121,4 +121,3 @@ export function countBySeverity(results) {
121
121
  }
122
122
  return { errors, warnings };
123
123
  }
124
- //# sourceMappingURL=linter.js.map
package/dist/logger.d.ts CHANGED
@@ -15,4 +15,3 @@ export declare const style: {
15
15
  /** Red for something that fails the run, yellow for something that does not. */
16
16
  severity: (severity: string, text: string) => string;
17
17
  };
18
- //# sourceMappingURL=logger.d.ts.map
package/dist/logger.js CHANGED
@@ -29,4 +29,3 @@ export const style = {
29
29
  /** Red for something that fails the run, yellow for something that does not. */
30
30
  severity: (severity, text) => paint(severity === 'warning' ? `${ESC}33m` : `${ESC}31m`, text),
31
31
  };
32
- //# sourceMappingURL=logger.js.map
package/dist/main.d.ts CHANGED
@@ -8,4 +8,3 @@ import type { Diagnostics } from './diagnostics.ts';
8
8
  * library leaves the console alone until you ask for it.
9
9
  */
10
10
  export declare function run(argv: readonly string[], diagnostics?: Diagnostics): Promise<number>;
11
- //# sourceMappingURL=main.d.ts.map
package/dist/main.js CHANGED
@@ -29,7 +29,8 @@ function usage() {
29
29
  ' or the path to a formatter of your own',
30
30
  ` (default: ${DEFAULT_FORMAT})`,
31
31
  ` -c, --config <path> configuration file (default: ${DEFAULT_CONFIG_FILE_NAME})`,
32
- ` -i, --ignore <globs> comma separated globs to skip, overriding ${DEFAULT_IGNORE_FILE_NAME}`,
32
+ ` -i, --ignore <globs> comma separated globs to skip, overriding ${DEFAULT_IGNORE_FILE_NAME};`,
33
+ ' may be given more than once',
33
34
  ' -r, --rulesdir <dir> directory of custom rules; may be given more than once',
34
35
  ' -l, --language <code> dialect for files with no "# language:" header',
35
36
  ' -w, --watch keep running, checking again whenever a file changes',
@@ -65,7 +66,7 @@ export async function run(argv, diagnostics = SILENT) {
65
66
  options: {
66
67
  format: { type: 'string', short: 'f' },
67
68
  config: { type: 'string', short: 'c' },
68
- ignore: { type: 'string', short: 'i' },
69
+ ignore: { type: 'string', short: 'i', multiple: true },
69
70
  rulesdir: { type: 'string', short: 'r', multiple: true },
70
71
  language: { type: 'string', short: 'l' },
71
72
  watch: { type: 'boolean', short: 'w' },
@@ -104,16 +105,34 @@ export async function run(argv, diagnostics = SILENT) {
104
105
  });
105
106
  return EXIT_USAGE;
106
107
  }
107
- const configuration = await readConfiguration(values.config, rules);
108
- if (!configuration.ok) {
109
- diagnostics.report({
110
- level: 'error',
111
- message: configuration.message,
112
- details: configuration.details,
113
- });
108
+ /**
109
+ * The configuration and the dialect it asks for, or `undefined` once what
110
+ * is wrong with them has been reported. Read again on every pass of a
111
+ * watch, since a change to the configuration is one of the things it
112
+ * checks again for.
113
+ */
114
+ const readSettings = async () => {
115
+ const read = await readConfiguration(values.config, rules);
116
+ if (!read.ok) {
117
+ diagnostics.report({ level: 'error', message: read.message, details: read.details });
118
+ return undefined;
119
+ }
120
+ const language = values.language ?? read.language;
121
+ if (language !== undefined && !isKnownLanguage(language)) {
122
+ diagnostics.report({
123
+ level: 'error',
124
+ message: `Unknown language "${language}". Use a Gherkin language code, such as "fr".`,
125
+ });
126
+ return undefined;
127
+ }
128
+ return { configuration: read.configuration, language, files: read.files };
129
+ };
130
+ let settings = await readSettings();
131
+ if (settings === undefined) {
114
132
  return EXIT_USAGE;
115
133
  }
116
- const ignore = values.ignore?.split(',').map((pattern) => pattern.trim());
134
+ // Given more than once, the lists add up rather than the last one winning.
135
+ const ignore = values.ignore?.flatMap((list) => list.split(',').map((pattern) => pattern.trim()));
117
136
  // Files are handed over as the walk finds them, so reading and checking
118
137
  // start on the first one rather than after the last. Only the patterns are
119
138
  // checked here; the walk itself happens once per pass.
@@ -149,22 +168,32 @@ export async function run(argv, diagnostics = SILENT) {
149
168
  });
150
169
  return EXIT_USAGE;
151
170
  }
152
- const language = values.language ?? configuration.language;
153
- if (language !== undefined && !isKnownLanguage(language)) {
154
- diagnostics.report({
155
- level: 'error',
156
- message: `Unknown language "${language}". Use a Gherkin language code, such as "fr".`,
157
- });
158
- return EXIT_USAGE;
159
- }
160
171
  // Discovery is a stream, and a stream can only be read once - so a run that
161
172
  // may happen more than once is given a fresh one each time.
162
173
  const discover = () => findFeatureFileStream(positionals, ignore).files;
174
+ let passes = 0;
163
175
  const checkOnce = async () => {
176
+ if (passes++ > 0) {
177
+ settings = await readSettings();
178
+ if (settings === undefined) {
179
+ return EXIT_USAGE;
180
+ }
181
+ }
182
+ const { configuration, language } = settings;
164
183
  if (streaming !== undefined) {
165
- return await runStreaming(streaming, discover(), configuration.configuration, rules, language, diagnostics);
184
+ return await runStreaming(streaming, discover(), configuration, rules, language, diagnostics);
185
+ }
186
+ let results;
187
+ try {
188
+ results = await lint(discover(), configuration, rules, { language });
189
+ }
190
+ catch (thrown) {
191
+ diagnostics.report({
192
+ level: 'error',
193
+ message: `The run failed: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
194
+ });
195
+ return EXIT_USAGE;
166
196
  }
167
- const results = await lint(discover(), configuration.configuration, rules, { language });
168
197
  // A formatter may print the output itself or hand it back as a string.
169
198
  let output;
170
199
  try {
@@ -186,7 +215,19 @@ export async function run(argv, diagnostics = SILENT) {
186
215
  // Watching never fails: a run that found something is the normal state of
187
216
  // affairs while you are fixing it, and the exit code is only read once,
188
217
  // when you stop.
189
- return await watch(featureRoots(positionals), values.config ?? DEFAULT_CONFIG_FILE_NAME, { diagnostics, ignore: DEFAULT_IGNORED_NAMES }, checkOnce);
218
+ const configPath = values.config ?? DEFAULT_CONFIG_FILE_NAME;
219
+ return await watch(featureRoots(positionals), configPath, {
220
+ diagnostics,
221
+ ignore: DEFAULT_IGNORED_NAMES,
222
+ // The configuration is watched even when it does not exist yet, so
223
+ // writing one is noticed; the ignore file only when --ignore is not
224
+ // there to overrule it.
225
+ files: () => [
226
+ configPath,
227
+ ...(settings?.files ?? []),
228
+ ...(ignore === undefined ? [DEFAULT_IGNORE_FILE_NAME] : []),
229
+ ],
230
+ }, checkOnce);
190
231
  }
191
232
  return await checkOnce();
192
233
  }
@@ -202,20 +243,31 @@ async function runStreaming(streaming, files, configuration, rules, language, di
202
243
  process.stdout.write(text);
203
244
  }
204
245
  };
246
+ // Linting and writing take turns in one loop, so what failed is recorded
247
+ // rather than guessed at: a message blaming the formatter for a rule's
248
+ // mistake sends you looking in the wrong place.
249
+ let formatting = false;
250
+ const format = (step) => {
251
+ formatting = true;
252
+ const written = step();
253
+ formatting = false;
254
+ return written;
255
+ };
205
256
  let failed = false;
206
257
  try {
207
- const run = streaming();
208
- write(run.start?.());
258
+ const run = format(() => streaming());
259
+ write(format(() => run.start?.()));
209
260
  for await (const result of lintStream(files, configuration, rules, { language })) {
210
261
  failed ||= result.errors.some((error) => (error.severity ?? 'error') === 'error');
211
- write(run.file(result));
262
+ write(format(() => run.file(result)));
212
263
  }
213
- write(run.end?.());
264
+ write(format(() => run.end?.()));
214
265
  }
215
266
  catch (thrown) {
267
+ const reason = thrown instanceof Error ? thrown.message : String(thrown);
216
268
  diagnostics.report({
217
269
  level: 'error',
218
- message: `The formatter failed: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
270
+ message: formatting ? `The formatter failed: ${reason}` : `The run failed: ${reason}`,
219
271
  });
220
272
  return EXIT_USAGE;
221
273
  }
@@ -268,4 +320,3 @@ async function main(argv) {
268
320
  if (isMainModule()) {
269
321
  process.exitCode = await main(process.argv.slice(2));
270
322
  }
271
- //# sourceMappingURL=main.js.map
package/dist/presets.d.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  * quiet on a codebase that has never been linted.
9
9
  *
10
10
  * Rules about style - indentation, name length, which tags go where, whether
11
- * repeated keywords become And - are deliberately left out. They are worth
11
+ * repeated keywords become And, and whitespace such as trailing spaces or a
12
+ * new line at the end of the file - are deliberately left out. They are worth
12
13
  * switching on, but that is a decision for a team to make rather than a
13
14
  * default to inherit.
14
15
  */
@@ -16,4 +17,3 @@ import type { Configuration } from './types.ts';
16
17
  export declare const RECOMMENDED: Configuration;
17
18
  /** The presets that can be named in a configuration file. */
18
19
  export declare const PRESETS: Record<string, Configuration>;
19
- //# sourceMappingURL=presets.d.ts.map