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.
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 +2 -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
@@ -18,4 +18,3 @@ export { DEFAULT_STATS_FORMAT, DEFAULT_TOP, STATS_FORMATTERS, getStatsFormatter,
18
18
  export type { StatsFormatOptions, StatsFormatter } from './format/index.ts';
19
19
  export { runStats, statsUsage } from './command.ts';
20
20
  export type * from './types.ts';
21
- //# sourceMappingURL=index.d.ts.map
@@ -14,4 +14,3 @@ export { DEFAULT_SIMILARITY, boundedEditDistance, groupSimilar } from './similar
14
14
  export { NUMBER_MARKER, PLACEHOLDER_MARKER, STRING_MARKER, countWords, normaliseStepText, } from './normalise.js';
15
15
  export { DEFAULT_STATS_FORMAT, DEFAULT_TOP, STATS_FORMATTERS, getStatsFormatter, toJson as statisticsToJson, toMarkdown, toText, } from './format/index.js';
16
16
  export { runStats, statsUsage } from './command.js';
17
- //# sourceMappingURL=index.js.map
@@ -35,4 +35,3 @@ export declare const NUMBER_MARKER = "0";
35
35
  export declare function normaliseStepText(text: string): string;
36
36
  /** How many words a step is made of, counted before normalisation. */
37
37
  export declare function countWords(text: string): number;
38
- //# sourceMappingURL=normalise.d.ts.map
@@ -26,16 +26,34 @@ export const STRING_MARKER = '""';
26
26
  /** Stands in for a number, whatever its value. */
27
27
  export const NUMBER_MARKER = '0';
28
28
  const PLACEHOLDER = /<[^<>]*>/gu;
29
- const QUOTED = /"[^"]*"/gu;
29
+ /** A double quoted string, which may hold quotes escaped with a backslash. */
30
+ const QUOTED = /"(?:[^"\\]|\\.)*"/gu;
30
31
  /**
31
- * A whole number or decimal standing on its own. The word boundaries keep
32
- * `1st` and `v2` intact, where the digits are part of a name rather than a
33
- * value a step definition would capture.
32
+ * A whole number or decimal standing on its own, with its minus sign. The
33
+ * word boundaries keep `1st` and `v2` intact, where the digits are part of a
34
+ * name rather than a value a step definition would capture.
34
35
  */
35
- const NUMBER = /\b\d+(?:[.,]\d+)*\b/gu;
36
+ const NUMBER = /(?:(?<![\p{L}\p{N}_])-)?\b\d+(?:[.,]\d+)*\b/gu;
36
37
  const WHITESPACE = /\s+/gu;
37
- /** A full stop or exclamation mark at the end, which changes nothing. */
38
- const TRAILING_PUNCTUATION = /[\s.!]*[.!]$/u;
38
+ /**
39
+ * The text without a full stop or exclamation mark at the end, which changes
40
+ * nothing, and whatever spaces and further stops sit in front of it: `I own
41
+ * it .` and `I own it` are one step. A text of nothing but punctuation is
42
+ * left as it is rather than made empty.
43
+ *
44
+ * A loop rather than a regular expression: `/[\s.!]*[.!]$/` is tried from
45
+ * every position in a long run of ` .`, which takes seconds on one step.
46
+ */
47
+ function withoutTrailingPunctuation(text) {
48
+ if (!text.endsWith('.') && !text.endsWith('!')) {
49
+ return text;
50
+ }
51
+ let end = text.length;
52
+ while (end > 0 && /[\s.!]/u.test(text[end - 1])) {
53
+ end--;
54
+ }
55
+ return end === 0 ? text : text.slice(0, end);
56
+ }
39
57
  /**
40
58
  * The form of a step text used to decide whether two steps are the same one.
41
59
  *
@@ -44,22 +62,17 @@ const TRAILING_PUNCTUATION = /[\s.!]*[.!]$/u;
44
62
  * half the sentence. Cucumber's own expressions quote with `"` anyway.
45
63
  */
46
64
  export function normaliseStepText(text) {
47
- return text
65
+ const normalised = text
48
66
  .replace(PLACEHOLDER, PLACEHOLDER_MARKER)
49
67
  .replace(QUOTED, STRING_MARKER)
50
68
  .replace(NUMBER, NUMBER_MARKER)
51
69
  .toLowerCase()
52
70
  .replace(WHITESPACE, ' ')
53
- .trim()
54
- // Whatever sat in front of the full stop goes with it: `I own it .` and
55
- // `I own it` are one step, and a step text ending in a space is not one
56
- // anybody would recognise in the report.
57
- .replace(TRAILING_PUNCTUATION, '')
58
71
  .trim();
72
+ return withoutTrailingPunctuation(normalised);
59
73
  }
60
74
  /** How many words a step is made of, counted before normalisation. */
61
75
  export function countWords(text) {
62
76
  const trimmed = text.trim();
63
77
  return trimmed === '' ? 0 : trimmed.split(WHITESPACE).length;
64
78
  }
65
- //# sourceMappingURL=normalise.js.map
@@ -58,4 +58,3 @@ export declare function boundedEditDistance(left: string, right: string, limit:
58
58
  export declare function groupSimilar<T extends {
59
59
  text: string;
60
60
  }>(entries: readonly T[], options?: SimilarityOptions): T[][];
61
- //# sourceMappingURL=similar.d.ts.map
@@ -170,4 +170,3 @@ export function groupSimilar(entries, options = DEFAULT_SIMILARITY) {
170
170
  .filter((group) => group.length > 1)
171
171
  .map((group) => group.sort((a, b) => a.index - b.index).map((member) => member.entry));
172
172
  }
173
- //# sourceMappingURL=similar.js.map
@@ -156,4 +156,3 @@ export interface Statistics {
156
156
  /** Dialects in use, most common first. A single stray entry is a bug. */
157
157
  languages: LanguageEntry[];
158
158
  }
159
- //# sourceMappingURL=types.d.ts.map
@@ -7,4 +7,3 @@
7
7
  * report stays short enough to read.
8
8
  */
9
9
  export {};
10
- //# sourceMappingURL=types.js.map
@@ -21,6 +21,7 @@
21
21
  * breaks one of them cannot be read at all, so hiding the message would leave
22
22
  * nothing but silence.
23
23
  */
24
+ import type { Feature } from '@cucumber/messages';
24
25
  import type { RuleError } from './types.ts';
25
26
  export interface Suppressions {
26
27
  /** True when a directive in the file covers this error. */
@@ -28,6 +29,8 @@ export interface Suppressions {
28
29
  /** True when the file carries no directives at all. */
29
30
  readonly isEmpty: boolean;
30
31
  }
31
- /** Reads the directives out of a feature file's lines. */
32
- export declare function readSuppressions(lines: readonly string[]): Suppressions;
33
- //# sourceMappingURL=suppressions.d.ts.map
32
+ /**
33
+ * Reads the directives out of a feature file's lines. Given the parsed
34
+ * document, it is used to tell where the doc strings are.
35
+ */
36
+ export declare function readSuppressions(lines: readonly string[], feature?: Feature): Suppressions;
@@ -1,5 +1,10 @@
1
1
  import { markDocStrings } from './util/lines.js';
2
- const DIRECTIVE = /^\s*#\s*gurkencheck-(disable-next-line|disable-file|disable|enable)\b(.*)$/;
2
+ /**
3
+ * A directive, then the rules it names. Whitespace or the end of the line has
4
+ * to follow the directive's name: `\b` would also match before a hyphen, and
5
+ * read `disable-nextline` as a `disable` of every rule named after it.
6
+ */
7
+ const DIRECTIVE = /^\s*#\s*gurkencheck-(disable-next-line|disable-file|disable|enable)(?=\s|$)(.*)$/;
3
8
  /** Stands in for "every rule" when a directive names none. */
4
9
  const ALL_RULES = '*';
5
10
  function parseRuleNames(rest) {
@@ -9,16 +14,25 @@ function parseRuleNames(rest) {
9
14
  .filter((name) => name !== '');
10
15
  return names.length > 0 ? names : [ALL_RULES];
11
16
  }
12
- /** Reads the directives out of a feature file's lines. */
13
- export function readSuppressions(lines) {
14
- const inDocString = markDocStrings(lines);
17
+ /**
18
+ * Reads the directives out of a feature file's lines. Given the parsed
19
+ * document, it is used to tell where the doc strings are.
20
+ */
21
+ export function readSuppressions(lines, feature) {
22
+ const inDocString = markDocStrings(lines, feature);
15
23
  const wholeFile = new Set();
16
24
  /** Line number -> the rules switched off on that line only. */
17
25
  const nextLine = new Map();
18
- const ranges = [];
19
- /** Rule -> the line its still-open `disable` started on. */
20
- const open = new Map();
26
+ const switches = [];
21
27
  let found = false;
28
+ /**
29
+ * The first line that is neither blank nor a comment. A finding about the
30
+ * whole file belongs to no line, so it is taken to sit here: a `disable`
31
+ * written in the comments at the top of the file covers it, one further
32
+ * down does not.
33
+ */
34
+ const firstContent = lines.findIndex((text) => text.trim() !== '' && !text.trim().startsWith('#'));
35
+ const wholeFileLine = firstContent === -1 ? lines.length + 1 : firstContent + 1;
22
36
  lines.forEach((text, index) => {
23
37
  if (inDocString[index] === true) {
24
38
  return;
@@ -40,31 +54,11 @@ export function readSuppressions(lines) {
40
54
  onLine.add(name);
41
55
  nextLine.set(line + 1, onLine);
42
56
  }
43
- else if (kind === 'disable') {
44
- if (!open.has(name)) {
45
- open.set(name, line);
46
- }
47
- }
48
57
  else {
49
- // enable
50
- const from = open.get(name);
51
- if (from !== undefined) {
52
- ranges.push({ rule: name, from, to: line });
53
- open.delete(name);
54
- }
55
- if (name === ALL_RULES) {
56
- // `enable` with no names closes everything that is open.
57
- for (const [openRule, openFrom] of open) {
58
- ranges.push({ rule: openRule, from: openFrom, to: line });
59
- }
60
- open.clear();
61
- }
58
+ switches.push({ line, off: kind === 'disable', rule: name });
62
59
  }
63
60
  }
64
61
  });
65
- for (const [rule, from] of open) {
66
- ranges.push({ rule, from, to: Infinity });
67
- }
68
62
  const covers = (rule, candidate) => candidate === ALL_RULES || candidate === rule;
69
63
  return {
70
64
  isEmpty: !found,
@@ -77,8 +71,20 @@ export function readSuppressions(lines) {
77
71
  if (covers(error.rule, candidate))
78
72
  return true;
79
73
  }
80
- return ranges.some((range) => covers(error.rule, range.rule) && error.line >= range.from && error.line <= range.to);
74
+ // Whatever switched this rule on or off last, above the line, decides.
75
+ // So `enable use-and` after a bare `disable` switches use-and back on
76
+ // and leaves every other rule off, and `enable` with no names switches
77
+ // everything back on.
78
+ const line = error.line === 0 ? wholeFileLine : error.line;
79
+ let off = false;
80
+ for (const change of switches) {
81
+ if (change.line > line)
82
+ break;
83
+ if (covers(error.rule, change.rule)) {
84
+ off = change.off;
85
+ }
86
+ }
87
+ return off;
81
88
  },
82
89
  };
83
90
  }
84
- //# sourceMappingURL=suppressions.js.map
package/dist/types.d.ts CHANGED
@@ -89,6 +89,13 @@ export interface LintRule {
89
89
  * An array lists the allowed values; an object lists the allowed keys.
90
90
  */
91
91
  readonly availableConfigs?: unknown;
92
+ /**
93
+ * Checks what the configuration gives the rule beyond the types of its
94
+ * settings, which are checked against `availableConfigs` already: that a
95
+ * pattern is a regular expression, or a value one of a fixed set. Returns
96
+ * what is wrong, an empty list when nothing is.
97
+ */
98
+ verifySettings?(settings: Record<string, unknown>): string[];
92
99
  /**
93
100
  * Returns the violations found in this file. A rule that needs to wait for
94
101
  * something - reading a file, asking a service - may return a promise.
@@ -122,4 +129,3 @@ export interface LintRule {
122
129
  }
123
130
  /** All rules available to a run, keyed by name. */
124
131
  export type RuleRegistry = ReadonlyMap<string, LintRule>;
125
- //# sourceMappingURL=types.d.ts.map
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map
@@ -5,7 +5,8 @@
5
5
  /**
6
6
  * Splits an identifier into words, breaking on separators, on lower-to-upper
7
7
  * transitions and between a run of capitals and a following capitalised word
8
- * (so `XMLHttpRequest` becomes `XML`, `Http`, `Request`).
8
+ * (so `XMLHttpRequest` becomes `XML`, `Http`, `Request`). Letters outside
9
+ * ASCII are letters too: `Über` is one word, not `Ber`.
9
10
  */
10
11
  export declare function words(value: string): string[];
11
12
  /** `my fancy feature` -> `My Fancy Feature` */
@@ -18,4 +19,3 @@ export declare function camelCase(value: string): string;
18
19
  export declare function kebabCase(value: string): string;
19
20
  /** `my fancy feature` -> `my_fancy_feature` */
20
21
  export declare function snakeCase(value: string): string;
21
- //# sourceMappingURL=case.d.ts.map
package/dist/util/case.js CHANGED
@@ -2,13 +2,28 @@
2
2
  * Word splitting and case conversion, as used by the `file-name` rule.
3
3
  * Replaces lodash's `startCase`/`camelCase`/`kebabCase`/`snakeCase`.
4
4
  */
5
+ /** Capital letters, in any script. */
6
+ const UPPER = String.raw `\p{Lu}\p{Lt}`;
7
+ /**
8
+ * Small letters, and letters with no case at all - Japanese, Chinese, Arabic
9
+ * - which carry on a word the way small letters do.
10
+ */
11
+ const LOWER = String.raw `\p{Ll}\p{Lm}\p{Lo}`;
12
+ const DIGIT = String.raw `\p{Nd}`;
13
+ const WORD = new RegExp([
14
+ `[${UPPER}]{2,}(?=[${UPPER}][${LOWER}]+[${DIGIT}]*|(?![${UPPER}${LOWER}${DIGIT}]))`,
15
+ `[${UPPER}]?[${LOWER}]+[${DIGIT}]*`,
16
+ `[${UPPER}]+[${DIGIT}]*`,
17
+ `[${DIGIT}]+`,
18
+ ].join('|'), 'gu');
5
19
  /**
6
20
  * Splits an identifier into words, breaking on separators, on lower-to-upper
7
21
  * transitions and between a run of capitals and a following capitalised word
8
- * (so `XMLHttpRequest` becomes `XML`, `Http`, `Request`).
22
+ * (so `XMLHttpRequest` becomes `XML`, `Http`, `Request`). Letters outside
23
+ * ASCII are letters too: `Über` is one word, not `Ber`.
9
24
  */
10
25
  export function words(value) {
11
- return value.match(/[A-Z]{2,}(?=[A-Z][a-z]+\d*|\b)|[A-Z]?[a-z]+\d*|[A-Z]+\d*|\d+/g) ?? [];
26
+ return value.match(WORD) ?? [];
12
27
  }
13
28
  function upperFirst(word) {
14
29
  return word.charAt(0).toUpperCase() + word.slice(1);
@@ -39,4 +54,3 @@ export function snakeCase(value) {
39
54
  .map((word) => word.toLowerCase())
40
55
  .join('_');
41
56
  }
42
- //# sourceMappingURL=case.js.map
@@ -27,4 +27,3 @@ export declare function intersectionBy<T>(values: readonly T[], other: readonly
27
27
  * their overrides back into the shared default config object.
28
28
  */
29
29
  export declare function mergeDefaults<T>(defaults: T, override: unknown): T;
30
- //# sourceMappingURL=collections.d.ts.map
@@ -84,4 +84,3 @@ export function mergeDefaults(defaults, override) {
84
84
  }
85
85
  return merged;
86
86
  }
87
- //# sourceMappingURL=collections.js.map
@@ -3,6 +3,13 @@
3
3
  * slash-separated paths.
4
4
  */
5
5
  export declare function globToRegExp(pattern: string): RegExp;
6
+ /** Escapes every character glob syntax would read, so the text matches only itself. */
7
+ export declare function escapeGlob(literal: string): string;
8
+ /**
9
+ * The path a pattern names when it holds no glob syntax, with its escapes
10
+ * removed, or `undefined` when it is a real pattern.
11
+ */
12
+ export declare function literalPath(pattern: string): string | undefined;
6
13
  /**
7
14
  * The directory a pattern's search starts from - the leading run of literal
8
15
  * segments, resolved against `cwd`. What you would watch to be told about
@@ -13,8 +20,8 @@ export interface GlobOptions {
13
20
  /** Directory that relative patterns and returned paths are relative to. */
14
21
  cwd?: string;
15
22
  /**
16
- * Patterns whose matches are dropped from the result. A pattern matching a
17
- * directory drops everything below it, as in .gitignore and .eslintignore.
23
+ * Patterns whose matches are dropped from the result, read as .gitignore
24
+ * reads them. A pattern matching a directory drops everything below it.
18
25
  */
19
26
  ignore?: readonly string[];
20
27
  }
@@ -35,4 +42,3 @@ export declare function globSync(pattern: string, options?: GlobOptions): string
35
42
  * to sort: `byPathOrder` is what makes the two agree.
36
43
  */
37
44
  export declare function globStream(pattern: string, options?: GlobOptions): AsyncGenerator<string>;
38
- //# sourceMappingURL=glob.d.ts.map
package/dist/util/glob.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
- const REGEXP_SPECIAL_CHARACTERS = new Set('.+^$()|\\'.split(''));
13
+ const REGEXP_SPECIAL_CHARACTERS = new Set('.+^$()|\\[]{}*?'.split(''));
14
14
  /**
15
15
  * Translates a glob pattern into an anchored regular expression matching
16
16
  * slash-separated paths.
@@ -61,8 +61,15 @@ export function globToRegExp(pattern) {
61
61
  }
62
62
  }
63
63
  else if (character === '{') {
64
- braceDepth++;
65
- source += '(?:';
64
+ // A brace with nothing closing it is taken literally, as an unclosed
65
+ // `[` is, rather than leaving a group open in the regular expression.
66
+ if (findClosingBrace(pattern, index) === -1) {
67
+ source += '\\{';
68
+ }
69
+ else {
70
+ braceDepth++;
71
+ source += '(?:';
72
+ }
66
73
  }
67
74
  else if (character === '}' && braceDepth > 0) {
68
75
  braceDepth--;
@@ -84,6 +91,23 @@ export function globToRegExp(pattern) {
84
91
  function escapeLiteral(character) {
85
92
  return REGEXP_SPECIAL_CHARACTERS.has(character) ? `\\${character}` : character;
86
93
  }
94
+ /** Where the brace opened just before `from` closes, or -1 when it never does. */
95
+ function findClosingBrace(pattern, from) {
96
+ let depth = 1;
97
+ for (let index = from; index < pattern.length; index++) {
98
+ const character = pattern[index];
99
+ if (character === '\\') {
100
+ index++;
101
+ }
102
+ else if (character === '{') {
103
+ depth++;
104
+ }
105
+ else if (character === '}' && --depth === 0) {
106
+ return index;
107
+ }
108
+ }
109
+ return -1;
110
+ }
87
111
  function findClosingBracket(pattern, from) {
88
112
  for (let index = from + 1; index < pattern.length; index++) {
89
113
  if (pattern[index] === ']')
@@ -91,23 +115,45 @@ function findClosingBracket(pattern, from) {
91
115
  }
92
116
  return -1;
93
117
  }
94
- /** True when the segment contains glob syntax rather than a literal name. */
95
- function isDynamic(segment) {
96
- return /[*?[\]{}]/.test(segment);
118
+ /** Escapes every character glob syntax would read, so the text matches only itself. */
119
+ export function escapeGlob(literal) {
120
+ return literal.replace(/[*?[\]{}\\]/g, '\\$&');
121
+ }
122
+ /**
123
+ * The path a pattern names when it holds no glob syntax, with its escapes
124
+ * removed, or `undefined` when it is a real pattern.
125
+ */
126
+ export function literalPath(pattern) {
127
+ let literal = '';
128
+ for (let index = 0; index < pattern.length; index++) {
129
+ const character = pattern[index];
130
+ if (character === '\\' && index + 1 < pattern.length) {
131
+ literal += pattern[++index];
132
+ }
133
+ else if ('*?[]{}'.includes(character)) {
134
+ return undefined;
135
+ }
136
+ else {
137
+ literal += character;
138
+ }
139
+ }
140
+ return literal;
97
141
  }
98
142
  /**
99
143
  * The leading run of literal segments, used as the directory to start walking
100
- * from so that `a/b/**` does not scan the whole tree.
144
+ * from so that `a/b/**` does not scan the whole tree, with its escapes
145
+ * removed - and what is left of the pattern after it.
101
146
  */
102
- function staticPrefix(pattern) {
147
+ function splitPattern(pattern) {
103
148
  const segments = pattern.split('/');
104
149
  const literal = [];
105
150
  for (const segment of segments.slice(0, -1)) {
106
- if (isDynamic(segment))
151
+ const name = literalPath(segment);
152
+ if (name === undefined)
107
153
  break;
108
- literal.push(segment);
154
+ literal.push(name);
109
155
  }
110
- return literal.join('/');
156
+ return { prefix: literal.join('/'), rest: segments.slice(literal.length).join('/') };
111
157
  }
112
158
  /**
113
159
  * The directory a pattern's search starts from - the leading run of literal
@@ -115,7 +161,7 @@ function staticPrefix(pattern) {
115
161
  * anything the pattern could match.
116
162
  */
117
163
  export function globRoot(pattern, cwd = process.cwd()) {
118
- return path.resolve(cwd, staticPrefix(pattern.split(path.sep).join('/')));
164
+ return path.resolve(cwd, splitPattern(pattern.split(path.sep).join('/')).prefix);
119
165
  }
120
166
  function isHidden(name) {
121
167
  return name.startsWith('.');
@@ -197,22 +243,48 @@ async function* walkStream(root) {
197
243
  }
198
244
  }
199
245
  /**
200
- * True when the path, or any directory leading to it, matches one of the
201
- * patterns.
246
+ * Reads an ignore pattern as .gitignore does. A pattern with no slash, other
247
+ * than a trailing one, matches a name at any depth; one with a slash is
248
+ * anchored to the working directory, whether or not it starts with `./` or
249
+ * `/`; a trailing slash means directories only; and a leading `!` brings back
250
+ * what an earlier pattern left out.
251
+ */
252
+ function toIgnoreRule(pattern) {
253
+ let text = pattern.split(path.sep).join('/');
254
+ const negated = text.startsWith('!');
255
+ if (negated) {
256
+ text = text.slice(1);
257
+ }
258
+ const directoryOnly = text.endsWith('/');
259
+ text = text.replace(/\/+$/u, '').replace(/^(?:\.\/)+/u, '');
260
+ const anchored = text.includes('/');
261
+ text = text.replace(/^\/+/u, '');
262
+ return { matches: globToRegExp(anchored ? text : `**/${text}`), negated, directoryOnly };
263
+ }
264
+ /**
265
+ * True when the path, or any directory leading to it, is ignored.
202
266
  *
203
267
  * Testing the parent directories is what makes an entry like `build` or `o*e`
204
268
  * skip the whole directory rather than only a file of that exact name. Without
205
269
  * it an ignore file has to spell out `build/**` everywhere, which is not how
206
- * .gitignore or .eslintignore behave.
270
+ * .gitignore or .eslintignore behave. As there, the last pattern to match a
271
+ * path decides, and nothing inside an ignored directory can be brought back.
207
272
  */
208
- function isIgnored(relativePath, ignores) {
209
- if (ignores.length === 0) {
273
+ function isIgnored(relativePath, rules) {
274
+ if (rules.length === 0) {
210
275
  return false;
211
276
  }
212
277
  const segments = relativePath.split('/');
213
278
  for (let depth = 1; depth <= segments.length; depth++) {
214
279
  const prefix = segments.slice(0, depth).join('/');
215
- if (ignores.some((ignore) => ignore.test(prefix))) {
280
+ const isDirectory = depth < segments.length;
281
+ let ignored = false;
282
+ for (const rule of rules) {
283
+ if ((isDirectory || !rule.directoryOnly) && rule.matches.test(prefix)) {
284
+ ignored = !rule.negated;
285
+ }
286
+ }
287
+ if (ignored) {
216
288
  return true;
217
289
  }
218
290
  }
@@ -221,18 +293,35 @@ function isIgnored(relativePath, ignores) {
221
293
  function newSearch(pattern, options) {
222
294
  const cwd = options.cwd ?? process.cwd();
223
295
  const normalisedPattern = pattern.split(path.sep).join('/');
224
- const matches = globToRegExp(normalisedPattern);
225
- const ignores = (options.ignore ?? []).map((ignorePattern) => globToRegExp(ignorePattern.split(path.sep).join('/')));
226
- // An absolute pattern is matched against absolute paths: comparing it with
227
- // a path relative to cwd can never match, and the search would come back
296
+ const ignores = (options.ignore ?? []).map(toIgnoreRule);
297
+ // Matched against absolute paths, with the literal start of the pattern
298
+ // resolved first. Comparing the pattern as written with a path relative to
299
+ // cwd goes wrong whenever the two spell the same place differently -
300
+ // `./features/a.feature` against `features/a.feature`, or `../**` against
301
+ // a file below cwd, which has no `../` at all - and the search comes back
228
302
  // empty rather than wrong, which reads as "nothing to report".
229
- const absolutePattern = path.isAbsolute(normalisedPattern);
303
+ //
304
+ // A pattern holding no glob syntax names one path, which is looked at
305
+ // directly rather than searched for, so a file named on purpose is found
306
+ // even where a search would pass it by: a dotfile, or a symlink.
307
+ const literal = literalPath(normalisedPattern);
308
+ let searchRoot;
309
+ let matches;
310
+ if (literal !== undefined) {
311
+ searchRoot = path.resolve(cwd, literal);
312
+ matches = globToRegExp(escapeGlob(searchRoot.split(path.sep).join('/')));
313
+ }
314
+ else {
315
+ const { prefix, rest } = splitPattern(normalisedPattern);
316
+ searchRoot = path.resolve(cwd, prefix);
317
+ const root = searchRoot.split(path.sep).join('/').replace(/\/$/, '');
318
+ matches = globToRegExp(`${escapeGlob(root)}/${rest}`);
319
+ }
230
320
  return {
231
- searchRoot: path.resolve(cwd, staticPrefix(normalisedPattern)),
321
+ searchRoot,
232
322
  match(absolutePath) {
233
323
  const relativePath = path.relative(cwd, absolutePath).split(path.sep).join('/');
234
- const candidate = absolutePattern ? absolutePath.split(path.sep).join('/') : relativePath;
235
- if (!matches.test(candidate))
324
+ if (!matches.test(absolutePath.split(path.sep).join('/')))
236
325
  return undefined;
237
326
  if (isIgnored(relativePath, ignores))
238
327
  return undefined;
@@ -304,4 +393,3 @@ function statOrUndefined(target) {
304
393
  return undefined;
305
394
  }
306
395
  }
307
- //# sourceMappingURL=glob.js.map
@@ -11,4 +11,3 @@
11
11
  export declare function stripJsonComments(json: string): string;
12
12
  /** Parses JSON that may contain comments. */
13
13
  export declare function parseJsonWithComments(json: string): unknown;
14
- //# sourceMappingURL=json.d.ts.map
package/dist/util/json.js CHANGED
@@ -60,4 +60,3 @@ export function stripJsonComments(json) {
60
60
  export function parseJsonWithComments(json) {
61
61
  return JSON.parse(stripJsonComments(json));
62
62
  }
63
- //# sourceMappingURL=json.js.map
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Helpers for looking at a feature file as plain text.
3
3
  */
4
+ import type { Feature } from '@cucumber/messages';
4
5
  import type { FeatureFile } from '../types.ts';
5
6
  /**
6
7
  * The lines a person would see in their editor.
@@ -17,8 +18,9 @@ export declare function contentLines(file: FeatureFile): string[];
17
18
  * Text inside a doc string is data belonging to the step, not part of the
18
19
  * layout of the file, so rules about formatting have to leave it alone.
19
20
  *
20
- * This reads the raw lines rather than the parsed document so that it still
21
- * works on a file the parser rejected.
21
+ * Given the parsed document, the doc strings are taken from it, which is
22
+ * exact. Without one - a file the parser rejected - the raw lines are read
23
+ * instead, and a line opening with a delimiter is taken to open a doc
24
+ * string, which a description line starting with `"""` also does.
22
25
  */
23
- export declare function markDocStrings(lines: readonly string[]): boolean[];
24
- //# sourceMappingURL=lines.d.ts.map
26
+ export declare function markDocStrings(lines: readonly string[], feature?: Feature): boolean[];
@@ -1,3 +1,4 @@
1
+ import { stepContainersOf } from '../gherkin/traverse.js';
1
2
  /**
2
3
  * The lines a person would see in their editor.
3
4
  *
@@ -18,11 +19,29 @@ const DOC_STRING_DELIMITERS = ['"""', '```'];
18
19
  * Text inside a doc string is data belonging to the step, not part of the
19
20
  * layout of the file, so rules about formatting have to leave it alone.
20
21
  *
21
- * This reads the raw lines rather than the parsed document so that it still
22
- * works on a file the parser rejected.
22
+ * Given the parsed document, the doc strings are taken from it, which is
23
+ * exact. Without one - a file the parser rejected - the raw lines are read
24
+ * instead, and a line opening with a delimiter is taken to open a doc
25
+ * string, which a description line starting with `"""` also does.
23
26
  */
24
- export function markDocStrings(lines) {
27
+ export function markDocStrings(lines, feature) {
25
28
  const inside = new Array(lines.length).fill(false);
29
+ if (feature !== undefined) {
30
+ for (const { node } of stepContainersOf(feature)) {
31
+ for (const step of node.steps) {
32
+ if (step.docString === undefined)
33
+ continue;
34
+ const { content, location } = step.docString;
35
+ const contentLines = content === '' ? 0 : content.split('\n').length;
36
+ // The opening delimiter, the content, and the closing delimiter.
37
+ const last = location.line + contentLines + 1;
38
+ for (let line = location.line; line <= last && line <= lines.length; line++) {
39
+ inside[line - 1] = true;
40
+ }
41
+ }
42
+ }
43
+ return inside;
44
+ }
26
45
  let openDelimiter;
27
46
  lines.forEach((line, index) => {
28
47
  const trimmed = line.trim();
@@ -42,4 +61,3 @@ export function markDocStrings(lines) {
42
61
  });
43
62
  return inside;
44
63
  }
45
- //# sourceMappingURL=lines.js.map