chaffjs 0.20.0 → 0.22.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 (762) hide show
  1. package/README.md +7 -5
  2. package/dist/api.d.ts +16 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js.map +1 -1
  5. package/dist/cli-text.d.ts +2 -0
  6. package/dist/cli-text.d.ts.map +1 -1
  7. package/dist/cli-text.js +18 -5
  8. package/dist/cli-text.js.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +8 -11
  11. package/dist/cli.js.map +1 -1
  12. package/dist/command-help.d.ts +7 -0
  13. package/dist/command-help.d.ts.map +1 -0
  14. package/dist/command-help.js +18 -0
  15. package/dist/command-help.js.map +1 -0
  16. package/dist/commands/eval.js +2 -2
  17. package/dist/commands/eval.js.map +1 -1
  18. package/dist/commands/fix-plan.d.ts.map +1 -1
  19. package/dist/commands/fix-plan.js +11 -1
  20. package/dist/commands/fix-plan.js.map +1 -1
  21. package/dist/commands/help.d.ts +4 -0
  22. package/dist/commands/help.d.ts.map +1 -0
  23. package/dist/commands/help.js +7 -0
  24. package/dist/commands/help.js.map +1 -0
  25. package/dist/commands/init-command.d.ts +4 -0
  26. package/dist/commands/init-command.d.ts.map +1 -0
  27. package/dist/commands/init-command.js +15 -0
  28. package/dist/commands/init-command.js.map +1 -0
  29. package/dist/commands/plugin-init.d.ts +20 -0
  30. package/dist/commands/plugin-init.d.ts.map +1 -0
  31. package/dist/commands/plugin-init.js +129 -0
  32. package/dist/commands/plugin-init.js.map +1 -0
  33. package/dist/commands/plugin-test.d.ts +11 -0
  34. package/dist/commands/plugin-test.d.ts.map +1 -0
  35. package/dist/commands/plugin-test.js +81 -0
  36. package/dist/commands/plugin-test.js.map +1 -0
  37. package/dist/config/load.d.ts +2 -0
  38. package/dist/config/load.d.ts.map +1 -1
  39. package/dist/config/load.js +1 -0
  40. package/dist/config/load.js.map +1 -1
  41. package/dist/config/option-problems.js +2 -2
  42. package/dist/config/option-problems.js.map +1 -1
  43. package/dist/config/rule-problems.js +2 -2
  44. package/dist/config/rule-problems.js.map +1 -1
  45. package/dist/custom/checked.d.ts +9 -0
  46. package/dist/custom/checked.d.ts.map +1 -0
  47. package/dist/custom/checked.js +3 -0
  48. package/dist/custom/checked.js.map +1 -0
  49. package/dist/custom/dsl-fields.d.ts +27 -0
  50. package/dist/custom/dsl-fields.d.ts.map +1 -0
  51. package/dist/custom/dsl-fields.js +86 -0
  52. package/dist/custom/dsl-fields.js.map +1 -0
  53. package/dist/custom/load.d.ts.map +1 -1
  54. package/dist/custom/load.js +2 -0
  55. package/dist/custom/load.js.map +1 -1
  56. package/dist/custom/parse.d.ts +14 -3
  57. package/dist/custom/parse.d.ts.map +1 -1
  58. package/dist/custom/parse.js +44 -40
  59. package/dist/custom/parse.js.map +1 -1
  60. package/dist/custom/problems.d.ts.map +1 -1
  61. package/dist/custom/problems.js +10 -1
  62. package/dist/custom/problems.js.map +1 -1
  63. package/dist/detectors/amount-for-count.d.ts +21 -0
  64. package/dist/detectors/amount-for-count.d.ts.map +1 -0
  65. package/dist/detectors/amount-for-count.js +55 -0
  66. package/dist/detectors/amount-for-count.js.map +1 -0
  67. package/dist/detectors/blank-structure.d.ts.map +1 -1
  68. package/dist/detectors/blank-structure.js +6 -4
  69. package/dist/detectors/blank-structure.js.map +1 -1
  70. package/dist/detectors/clause-shape.d.ts +22 -0
  71. package/dist/detectors/clause-shape.d.ts.map +1 -0
  72. package/dist/detectors/clause-shape.js +149 -0
  73. package/dist/detectors/clause-shape.js.map +1 -0
  74. package/dist/detectors/code-fences.d.ts +34 -0
  75. package/dist/detectors/code-fences.d.ts.map +1 -0
  76. package/dist/detectors/code-fences.js +100 -0
  77. package/dist/detectors/code-fences.js.map +1 -0
  78. package/dist/detectors/cross-definitions.d.ts +4 -0
  79. package/dist/detectors/cross-definitions.d.ts.map +1 -0
  80. package/dist/detectors/cross-definitions.js +43 -0
  81. package/dist/detectors/cross-definitions.js.map +1 -0
  82. package/dist/detectors/cross-facts.d.ts +4 -0
  83. package/dist/detectors/cross-facts.d.ts.map +1 -0
  84. package/dist/detectors/cross-facts.js +53 -0
  85. package/dist/detectors/cross-facts.js.map +1 -0
  86. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts +3 -0
  87. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts.map +1 -0
  88. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js +3 -0
  89. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js.map +1 -0
  90. package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts +3 -0
  91. package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts.map +1 -0
  92. package/dist/detectors/cross-registry/cross-doc-fact-conflict.js +3 -0
  93. package/dist/detectors/cross-registry/cross-doc-fact-conflict.js.map +1 -0
  94. package/dist/detectors/currency-notation.d.ts +29 -0
  95. package/dist/detectors/currency-notation.d.ts.map +1 -0
  96. package/dist/detectors/currency-notation.js +58 -0
  97. package/dist/detectors/currency-notation.js.map +1 -0
  98. package/dist/detectors/custom.d.ts.map +1 -1
  99. package/dist/detectors/custom.js +4 -1
  100. package/dist/detectors/custom.js.map +1 -1
  101. package/dist/detectors/dangling-opener.d.ts +14 -0
  102. package/dist/detectors/dangling-opener.d.ts.map +1 -0
  103. package/dist/detectors/dangling-opener.js +55 -0
  104. package/dist/detectors/dangling-opener.js.map +1 -0
  105. package/dist/detectors/defined-term-form.d.ts +25 -0
  106. package/dist/detectors/defined-term-form.d.ts.map +1 -0
  107. package/dist/detectors/defined-term-form.js +170 -0
  108. package/dist/detectors/defined-term-form.js.map +1 -0
  109. package/dist/detectors/email-letter.d.ts +46 -0
  110. package/dist/detectors/email-letter.d.ts.map +1 -0
  111. package/dist/detectors/email-letter.js +234 -0
  112. package/dist/detectors/email-letter.js.map +1 -0
  113. package/dist/detectors/footnote-mismatch.d.ts +11 -0
  114. package/dist/detectors/footnote-mismatch.d.ts.map +1 -0
  115. package/dist/detectors/footnote-mismatch.js +55 -0
  116. package/dist/detectors/footnote-mismatch.js.map +1 -0
  117. package/dist/detectors/form-minority.d.ts +13 -0
  118. package/dist/detectors/form-minority.d.ts.map +1 -0
  119. package/dist/detectors/form-minority.js +19 -0
  120. package/dist/detectors/form-minority.js.map +1 -0
  121. package/dist/detectors/heading-end-mark.d.ts +10 -0
  122. package/dist/detectors/heading-end-mark.d.ts.map +1 -0
  123. package/dist/detectors/heading-end-mark.js +59 -0
  124. package/dist/detectors/heading-end-mark.js.map +1 -0
  125. package/dist/detectors/impossible-date.d.ts +20 -0
  126. package/dist/detectors/impossible-date.d.ts.map +1 -0
  127. package/dist/detectors/impossible-date.js +105 -0
  128. package/dist/detectors/impossible-date.js.map +1 -0
  129. package/dist/detectors/impossible-time.d.ts +18 -0
  130. package/dist/detectors/impossible-time.d.ts.map +1 -0
  131. package/dist/detectors/impossible-time.js +116 -0
  132. package/dist/detectors/impossible-time.js.map +1 -0
  133. package/dist/detectors/link-shape.d.ts +11 -0
  134. package/dist/detectors/link-shape.d.ts.map +1 -0
  135. package/dist/detectors/link-shape.js +56 -0
  136. package/dist/detectors/link-shape.js.map +1 -0
  137. package/dist/detectors/list-marker.d.ts +7 -0
  138. package/dist/detectors/list-marker.d.ts.map +1 -0
  139. package/dist/detectors/list-marker.js +50 -0
  140. package/dist/detectors/list-marker.js.map +1 -0
  141. package/dist/detectors/mark-shape.d.ts +53 -0
  142. package/dist/detectors/mark-shape.d.ts.map +1 -0
  143. package/dist/detectors/mark-shape.js +160 -0
  144. package/dist/detectors/mark-shape.js.map +1 -0
  145. package/dist/detectors/markdown-slip.d.ts +19 -0
  146. package/dist/detectors/markdown-slip.d.ts.map +1 -0
  147. package/dist/detectors/markdown-slip.js +122 -0
  148. package/dist/detectors/markdown-slip.js.map +1 -0
  149. package/dist/detectors/numeral-opener.d.ts +10 -0
  150. package/dist/detectors/numeral-opener.d.ts.map +1 -0
  151. package/dist/detectors/numeral-opener.js +65 -0
  152. package/dist/detectors/numeral-opener.js.map +1 -0
  153. package/dist/detectors/ordinal-suffix.d.ts +12 -0
  154. package/dist/detectors/ordinal-suffix.d.ts.map +1 -0
  155. package/dist/detectors/ordinal-suffix.js +33 -0
  156. package/dist/detectors/ordinal-suffix.js.map +1 -0
  157. package/dist/detectors/range-notation.d.ts +15 -0
  158. package/dist/detectors/range-notation.d.ts.map +1 -0
  159. package/dist/detectors/range-notation.js +58 -0
  160. package/dist/detectors/range-notation.js.map +1 -0
  161. package/dist/detectors/registry/adverb-polarity.d.ts +3 -0
  162. package/dist/detectors/registry/adverb-polarity.d.ts.map +1 -0
  163. package/dist/detectors/registry/adverb-polarity.js +3 -0
  164. package/dist/detectors/registry/adverb-polarity.js.map +1 -0
  165. package/dist/detectors/registry/amount-for-count.d.ts +3 -0
  166. package/dist/detectors/registry/amount-for-count.d.ts.map +1 -0
  167. package/dist/detectors/registry/amount-for-count.js +3 -0
  168. package/dist/detectors/registry/amount-for-count.js.map +1 -0
  169. package/dist/detectors/registry/attachment-missing.d.ts +3 -0
  170. package/dist/detectors/registry/attachment-missing.d.ts.map +1 -0
  171. package/dist/detectors/registry/attachment-missing.js +3 -0
  172. package/dist/detectors/registry/attachment-missing.js.map +1 -0
  173. package/dist/detectors/registry/bare-url-mix.d.ts +3 -0
  174. package/dist/detectors/registry/bare-url-mix.d.ts.map +1 -0
  175. package/dist/detectors/registry/bare-url-mix.js +3 -0
  176. package/dist/detectors/registry/bare-url-mix.js.map +1 -0
  177. package/dist/detectors/registry/caps-shouting.d.ts +3 -0
  178. package/dist/detectors/registry/caps-shouting.d.ts.map +1 -0
  179. package/dist/detectors/registry/caps-shouting.js +3 -0
  180. package/dist/detectors/registry/caps-shouting.js.map +1 -0
  181. package/dist/detectors/registry/code-fence-language.d.ts +3 -0
  182. package/dist/detectors/registry/code-fence-language.d.ts.map +1 -0
  183. package/dist/detectors/registry/code-fence-language.js +3 -0
  184. package/dist/detectors/registry/code-fence-language.js.map +1 -0
  185. package/dist/detectors/registry/currency-notation.d.ts +3 -0
  186. package/dist/detectors/registry/currency-notation.d.ts.map +1 -0
  187. package/dist/detectors/registry/currency-notation.js +3 -0
  188. package/dist/detectors/registry/currency-notation.js.map +1 -0
  189. package/dist/detectors/registry/dangling-opener.d.ts +3 -0
  190. package/dist/detectors/registry/dangling-opener.d.ts.map +1 -0
  191. package/dist/detectors/registry/dangling-opener.js +3 -0
  192. package/dist/detectors/registry/dangling-opener.js.map +1 -0
  193. package/dist/detectors/registry/defined-name-repeated.d.ts +3 -0
  194. package/dist/detectors/registry/defined-name-repeated.d.ts.map +1 -0
  195. package/dist/detectors/registry/defined-name-repeated.js +3 -0
  196. package/dist/detectors/registry/defined-name-repeated.js.map +1 -0
  197. package/dist/detectors/registry/defined-term-form.d.ts +3 -0
  198. package/dist/detectors/registry/defined-term-form.d.ts.map +1 -0
  199. package/dist/detectors/registry/defined-term-form.js +3 -0
  200. package/dist/detectors/registry/defined-term-form.js.map +1 -0
  201. package/dist/detectors/registry/email-frame.d.ts +3 -0
  202. package/dist/detectors/registry/email-frame.d.ts.map +1 -0
  203. package/dist/detectors/registry/email-frame.js +3 -0
  204. package/dist/detectors/registry/email-frame.js.map +1 -0
  205. package/dist/detectors/registry/email-subject.d.ts +3 -0
  206. package/dist/detectors/registry/email-subject.d.ts.map +1 -0
  207. package/dist/detectors/registry/email-subject.js +3 -0
  208. package/dist/detectors/registry/email-subject.js.map +1 -0
  209. package/dist/detectors/registry/ending-run.d.ts +3 -0
  210. package/dist/detectors/registry/ending-run.d.ts.map +1 -0
  211. package/dist/detectors/registry/ending-run.js +3 -0
  212. package/dist/detectors/registry/ending-run.js.map +1 -0
  213. package/dist/detectors/registry/footnote-mismatch.d.ts +3 -0
  214. package/dist/detectors/registry/footnote-mismatch.d.ts.map +1 -0
  215. package/dist/detectors/registry/footnote-mismatch.js +3 -0
  216. package/dist/detectors/registry/footnote-mismatch.js.map +1 -0
  217. package/dist/detectors/registry/heading-command.d.ts +3 -0
  218. package/dist/detectors/registry/heading-command.d.ts.map +1 -0
  219. package/dist/detectors/registry/heading-command.js +3 -0
  220. package/dist/detectors/registry/heading-command.js.map +1 -0
  221. package/dist/detectors/registry/heading-end-punctuation.d.ts +3 -0
  222. package/dist/detectors/registry/heading-end-punctuation.d.ts.map +1 -0
  223. package/dist/detectors/registry/heading-end-punctuation.js +3 -0
  224. package/dist/detectors/registry/heading-end-punctuation.js.map +1 -0
  225. package/dist/detectors/registry/hyphen-dash.d.ts +3 -0
  226. package/dist/detectors/registry/hyphen-dash.d.ts.map +1 -0
  227. package/dist/detectors/registry/hyphen-dash.js +3 -0
  228. package/dist/detectors/registry/hyphen-dash.js.map +1 -0
  229. package/dist/detectors/registry/image-file-name-alt.d.ts +3 -0
  230. package/dist/detectors/registry/image-file-name-alt.d.ts.map +1 -0
  231. package/dist/detectors/registry/image-file-name-alt.js +3 -0
  232. package/dist/detectors/registry/image-file-name-alt.js.map +1 -0
  233. package/dist/detectors/registry/impossible-date.d.ts +3 -0
  234. package/dist/detectors/registry/impossible-date.d.ts.map +1 -0
  235. package/dist/detectors/registry/impossible-date.js +3 -0
  236. package/dist/detectors/registry/impossible-date.js.map +1 -0
  237. package/dist/detectors/registry/impossible-time.d.ts +3 -0
  238. package/dist/detectors/registry/impossible-time.d.ts.map +1 -0
  239. package/dist/detectors/registry/impossible-time.js +3 -0
  240. package/dist/detectors/registry/impossible-time.js.map +1 -0
  241. package/dist/detectors/registry/latin-abbreviation.d.ts +3 -0
  242. package/dist/detectors/registry/latin-abbreviation.d.ts.map +1 -0
  243. package/dist/detectors/registry/latin-abbreviation.js +3 -0
  244. package/dist/detectors/registry/latin-abbreviation.js.map +1 -0
  245. package/dist/detectors/registry/link-text-url-mismatch.d.ts +3 -0
  246. package/dist/detectors/registry/link-text-url-mismatch.d.ts.map +1 -0
  247. package/dist/detectors/registry/link-text-url-mismatch.js +3 -0
  248. package/dist/detectors/registry/link-text-url-mismatch.js.map +1 -0
  249. package/dist/detectors/registry/list-marker.d.ts +3 -0
  250. package/dist/detectors/registry/list-marker.d.ts.map +1 -0
  251. package/dist/detectors/registry/list-marker.js +3 -0
  252. package/dist/detectors/registry/list-marker.js.map +1 -0
  253. package/dist/detectors/registry/mark-consistency.d.ts +3 -0
  254. package/dist/detectors/registry/mark-consistency.d.ts.map +1 -0
  255. package/dist/detectors/registry/mark-consistency.js +3 -0
  256. package/dist/detectors/registry/mark-consistency.js.map +1 -0
  257. package/dist/detectors/registry/numeral-opener.d.ts +3 -0
  258. package/dist/detectors/registry/numeral-opener.d.ts.map +1 -0
  259. package/dist/detectors/registry/numeral-opener.js +3 -0
  260. package/dist/detectors/registry/numeral-opener.js.map +1 -0
  261. package/dist/detectors/registry/ordinal-suffix.d.ts +3 -0
  262. package/dist/detectors/registry/ordinal-suffix.d.ts.map +1 -0
  263. package/dist/detectors/registry/ordinal-suffix.js +3 -0
  264. package/dist/detectors/registry/ordinal-suffix.js.map +1 -0
  265. package/dist/detectors/registry/origin-particle.d.ts +3 -0
  266. package/dist/detectors/registry/origin-particle.d.ts.map +1 -0
  267. package/dist/detectors/registry/origin-particle.js +3 -0
  268. package/dist/detectors/registry/origin-particle.js.map +1 -0
  269. package/dist/detectors/registry/range-notation.d.ts +3 -0
  270. package/dist/detectors/registry/range-notation.d.ts.map +1 -0
  271. package/dist/detectors/registry/range-notation.js +3 -0
  272. package/dist/detectors/registry/range-notation.js.map +1 -0
  273. package/dist/detectors/registry/relative-pronoun-mix.d.ts +3 -0
  274. package/dist/detectors/registry/relative-pronoun-mix.d.ts.map +1 -0
  275. package/dist/detectors/registry/relative-pronoun-mix.js +3 -0
  276. package/dist/detectors/registry/relative-pronoun-mix.js.map +1 -0
  277. package/dist/detectors/registry/request-without-deadline.d.ts +3 -0
  278. package/dist/detectors/registry/request-without-deadline.d.ts.map +1 -0
  279. package/dist/detectors/registry/request-without-deadline.js +3 -0
  280. package/dist/detectors/registry/request-without-deadline.js.map +1 -0
  281. package/dist/detectors/registry/sentence-spacing.d.ts +3 -0
  282. package/dist/detectors/registry/sentence-spacing.d.ts.map +1 -0
  283. package/dist/detectors/registry/sentence-spacing.js +3 -0
  284. package/dist/detectors/registry/sentence-spacing.js.map +1 -0
  285. package/dist/detectors/registry/share-over-hundred.d.ts +3 -0
  286. package/dist/detectors/registry/share-over-hundred.d.ts.map +1 -0
  287. package/dist/detectors/registry/share-over-hundred.js +3 -0
  288. package/dist/detectors/registry/share-over-hundred.js.map +1 -0
  289. package/dist/detectors/registry/shell-prompt.d.ts +3 -0
  290. package/dist/detectors/registry/shell-prompt.d.ts.map +1 -0
  291. package/dist/detectors/registry/shell-prompt.js +3 -0
  292. package/dist/detectors/registry/shell-prompt.js.map +1 -0
  293. package/dist/detectors/registry/step-reference.d.ts +3 -0
  294. package/dist/detectors/registry/step-reference.d.ts.map +1 -0
  295. package/dist/detectors/registry/step-reference.js +3 -0
  296. package/dist/detectors/registry/step-reference.js.map +1 -0
  297. package/dist/detectors/registry/step-statement.d.ts +3 -0
  298. package/dist/detectors/registry/step-statement.d.ts.map +1 -0
  299. package/dist/detectors/registry/step-statement.js +3 -0
  300. package/dist/detectors/registry/step-statement.js.map +1 -0
  301. package/dist/detectors/registry/table-row-overflow.d.ts +3 -0
  302. package/dist/detectors/registry/table-row-overflow.d.ts.map +1 -0
  303. package/dist/detectors/registry/table-row-overflow.js +3 -0
  304. package/dist/detectors/registry/table-row-overflow.js.map +1 -0
  305. package/dist/detectors/registry/toc-heading.d.ts +3 -0
  306. package/dist/detectors/registry/toc-heading.d.ts.map +1 -0
  307. package/dist/detectors/registry/toc-heading.js +3 -0
  308. package/dist/detectors/registry/toc-heading.js.map +1 -0
  309. package/dist/detectors/registry/unclosed-code-fence.d.ts +3 -0
  310. package/dist/detectors/registry/unclosed-code-fence.d.ts.map +1 -0
  311. package/dist/detectors/registry/unclosed-code-fence.js +3 -0
  312. package/dist/detectors/registry/unclosed-code-fence.js.map +1 -0
  313. package/dist/detectors/registry/unpaired-parallel.d.ts +3 -0
  314. package/dist/detectors/registry/unpaired-parallel.d.ts.map +1 -0
  315. package/dist/detectors/registry/unpaired-parallel.js +3 -0
  316. package/dist/detectors/registry/unpaired-parallel.js.map +1 -0
  317. package/dist/detectors/registry/unrendered-emphasis.d.ts +3 -0
  318. package/dist/detectors/registry/unrendered-emphasis.d.ts.map +1 -0
  319. package/dist/detectors/registry/unrendered-emphasis.js +3 -0
  320. package/dist/detectors/registry/unrendered-emphasis.js.map +1 -0
  321. package/dist/detectors/relative-pronoun-mix.d.ts +19 -0
  322. package/dist/detectors/relative-pronoun-mix.d.ts.map +1 -0
  323. package/dist/detectors/relative-pronoun-mix.js +60 -0
  324. package/dist/detectors/relative-pronoun-mix.js.map +1 -0
  325. package/dist/detectors/request-owner.d.ts +23 -0
  326. package/dist/detectors/request-owner.d.ts.map +1 -0
  327. package/dist/detectors/request-owner.js +110 -0
  328. package/dist/detectors/request-owner.js.map +1 -0
  329. package/dist/detectors/share-over-hundred.d.ts +20 -0
  330. package/dist/detectors/share-over-hundred.d.ts.map +1 -0
  331. package/dist/detectors/share-over-hundred.js +55 -0
  332. package/dist/detectors/share-over-hundred.js.map +1 -0
  333. package/dist/detectors/step-reference.d.ts +20 -0
  334. package/dist/detectors/step-reference.d.ts.map +1 -0
  335. package/dist/detectors/step-reference.js +57 -0
  336. package/dist/detectors/step-reference.js.map +1 -0
  337. package/dist/detectors/technical-docs.d.ts +32 -0
  338. package/dist/detectors/technical-docs.d.ts.map +1 -0
  339. package/dist/detectors/technical-docs.js +199 -0
  340. package/dist/detectors/technical-docs.js.map +1 -0
  341. package/dist/detectors/toc-heading.d.ts +10 -0
  342. package/dist/detectors/toc-heading.d.ts.map +1 -0
  343. package/dist/detectors/toc-heading.js +99 -0
  344. package/dist/detectors/toc-heading.js.map +1 -0
  345. package/dist/extension/pack-read.d.ts +21 -0
  346. package/dist/extension/pack-read.d.ts.map +1 -0
  347. package/dist/extension/pack-read.js +117 -0
  348. package/dist/extension/pack-read.js.map +1 -0
  349. package/dist/extension/plugin-load.d.ts.map +1 -1
  350. package/dist/extension/plugin-load.js +44 -10
  351. package/dist/extension/plugin-load.js.map +1 -1
  352. package/dist/extension/plugin-parse.d.ts.map +1 -1
  353. package/dist/extension/plugin-parse.js +10 -3
  354. package/dist/extension/plugin-parse.js.map +1 -1
  355. package/dist/facts/address-values.d.ts +5 -0
  356. package/dist/facts/address-values.d.ts.map +1 -0
  357. package/dist/facts/address-values.js +64 -0
  358. package/dist/facts/address-values.js.map +1 -0
  359. package/dist/facts/cross-facts.d.ts +20 -0
  360. package/dist/facts/cross-facts.d.ts.map +1 -0
  361. package/dist/facts/cross-facts.js +41 -0
  362. package/dist/facts/cross-facts.js.map +1 -0
  363. package/dist/fix-plan/chosen-depth.d.ts +10 -0
  364. package/dist/fix-plan/chosen-depth.d.ts.map +1 -0
  365. package/dist/fix-plan/chosen-depth.js +24 -0
  366. package/dist/fix-plan/chosen-depth.js.map +1 -0
  367. package/dist/fix-plan/mode.d.ts +13 -4
  368. package/dist/fix-plan/mode.d.ts.map +1 -1
  369. package/dist/fix-plan/mode.js +38 -7
  370. package/dist/fix-plan/mode.js.map +1 -1
  371. package/dist/fix-plan/plan.d.ts +20 -0
  372. package/dist/fix-plan/plan.d.ts.map +1 -1
  373. package/dist/fix-plan/plan.js +23 -5
  374. package/dist/fix-plan/plan.js.map +1 -1
  375. package/dist/fix-plan/render.d.ts.map +1 -1
  376. package/dist/fix-plan/render.js +24 -0
  377. package/dist/fix-plan/render.js.map +1 -1
  378. package/dist/fix-plan/text.d.ts +8 -0
  379. package/dist/fix-plan/text.d.ts.map +1 -1
  380. package/dist/fix-plan/text.js +36 -10
  381. package/dist/fix-plan/text.js.map +1 -1
  382. package/dist/genre-load.d.ts +1 -1
  383. package/dist/genre-load.d.ts.map +1 -1
  384. package/dist/genre-load.js +7 -4
  385. package/dist/genre-load.js.map +1 -1
  386. package/dist/genre-parse.d.ts +6 -0
  387. package/dist/genre-parse.d.ts.map +1 -1
  388. package/dist/genre-parse.js +18 -0
  389. package/dist/genre-parse.js.map +1 -1
  390. package/dist/init.js +10 -10
  391. package/dist/init.js.map +1 -1
  392. package/dist/known-genres.d.ts +8 -0
  393. package/dist/known-genres.d.ts.map +1 -0
  394. package/dist/known-genres.js +17 -0
  395. package/dist/known-genres.js.map +1 -0
  396. package/dist/link-shape.d.ts +20 -0
  397. package/dist/link-shape.d.ts.map +1 -0
  398. package/dist/link-shape.js +58 -0
  399. package/dist/link-shape.js.map +1 -0
  400. package/dist/list-marks.d.ts +21 -0
  401. package/dist/list-marks.d.ts.map +1 -0
  402. package/dist/list-marks.js +82 -0
  403. package/dist/list-marks.js.map +1 -0
  404. package/dist/reasons.d.ts.map +1 -1
  405. package/dist/reasons.js +15 -4
  406. package/dist/reasons.js.map +1 -1
  407. package/dist/render/explain.d.ts.map +1 -1
  408. package/dist/render/explain.js +11 -2
  409. package/dist/render/explain.js.map +1 -1
  410. package/dist/render/friendly.js +1 -1
  411. package/dist/render/friendly.js.map +1 -1
  412. package/dist/render/rules-json.d.ts.map +1 -1
  413. package/dist/render/rules-json.js +12 -3
  414. package/dist/render/rules-json.js.map +1 -1
  415. package/dist/render/rules-table.js +6 -6
  416. package/dist/render/rules-table.js.map +1 -1
  417. package/dist/render/semantic.js +2 -2
  418. package/dist/render/semantic.js.map +1 -1
  419. package/dist/render/suppressions.js +2 -2
  420. package/dist/render/suppressions.js.map +1 -1
  421. package/dist/rewrite-depth.d.ts +22 -0
  422. package/dist/rewrite-depth.d.ts.map +1 -0
  423. package/dist/rewrite-depth.js +42 -0
  424. package/dist/rewrite-depth.js.map +1 -0
  425. package/dist/rule-fields.d.ts +28 -0
  426. package/dist/rule-fields.d.ts.map +1 -0
  427. package/dist/rule-fields.js +85 -0
  428. package/dist/rule-fields.js.map +1 -0
  429. package/dist/rule-guide.d.ts +3 -0
  430. package/dist/rule-guide.d.ts.map +1 -1
  431. package/dist/rule-guide.js +6 -0
  432. package/dist/rule-guide.js.map +1 -1
  433. package/dist/rule-load.d.ts.map +1 -1
  434. package/dist/rule-load.js +13 -5
  435. package/dist/rule-load.js.map +1 -1
  436. package/dist/rule-offs.d.ts +14 -0
  437. package/dist/rule-offs.d.ts.map +1 -0
  438. package/dist/rule-offs.js +45 -0
  439. package/dist/rule-offs.js.map +1 -0
  440. package/dist/structure/build.d.ts.map +1 -1
  441. package/dist/structure/build.js +14 -1
  442. package/dist/structure/build.js.map +1 -1
  443. package/dist/structure/cross-definitions.d.ts +23 -0
  444. package/dist/structure/cross-definitions.d.ts.map +1 -0
  445. package/dist/structure/cross-definitions.js +34 -0
  446. package/dist/structure/cross-definitions.js.map +1 -0
  447. package/genres.yaml +14 -313
  448. package/package.json +3 -3
  449. package/rules/absolute-exception.yaml +3 -1
  450. package/rules/abstract-length.yaml +7 -1
  451. package/rules/acronym-expansion-conflict.yaml +2 -1
  452. package/rules/adverb-overuse.yaml +7 -1
  453. package/rules/adverb-without-negation.yaml +60 -0
  454. package/rules/adversative-ga-repeat.yaml +2 -1
  455. package/rules/agentless-passive.yaml +10 -1
  456. package/rules/agreement-slip.yaml +4 -1
  457. package/rules/ai-generated-composite.yaml +6 -1
  458. package/rules/ai-structure.yaml +8 -2
  459. package/rules/ai-tell.yaml +7 -1
  460. package/rules/all-caps-shouting.yaml +61 -0
  461. package/rules/announced-count-mismatch.yaml +3 -1
  462. package/rules/announcing-opener.yaml +7 -1
  463. package/rules/article-sound.yaml +6 -2
  464. package/rules/assistant-residue.yaml +4 -1
  465. package/rules/attachment-not-attached.yaml +112 -0
  466. package/rules/baito-keigo.yaml +64 -0
  467. package/rules/bare-url-mix.yaml +86 -0
  468. package/rules/bold-density.yaml +1 -0
  469. package/rules/bold-label-list.yaml +9 -1
  470. package/rules/bracket-nesting.yaml +2 -1
  471. package/rules/bracket-width-consistency.yaml +64 -0
  472. package/rules/broken-link.yaml +1 -1
  473. package/rules/chat-citation-residue.yaml +2 -1
  474. package/rules/citation-reference-mismatch.yaml +7 -1
  475. package/rules/citation-style-mix.yaml +7 -1
  476. package/rules/clause-chain.yaml +6 -1
  477. package/rules/cliche.yaml +67 -0
  478. package/rules/closing-cliche.yaml +7 -0
  479. package/rules/code-fence-language-mix.yaml +176 -0
  480. package/rules/colloquial-opener.yaml +62 -0
  481. package/rules/colon-lead-in.yaml +7 -1
  482. package/rules/concrete-evidence-density.yaml +6 -0
  483. package/rules/contraction-consistency.yaml +4 -1
  484. package/rules/contrast-framing.yaml +7 -1
  485. package/rules/cross-doc-duplicate-definition.yaml +70 -0
  486. package/rules/cross-doc-fact-conflict.yaml +64 -0
  487. package/rules/currency-notation-consistency.yaml +77 -0
  488. package/rules/cushion-phrase-density.yaml +8 -1
  489. package/rules/dangling-figure-reference.yaml +3 -1
  490. package/rules/dangling-participle.yaml +58 -0
  491. package/rules/dangling-reference.yaml +3 -1
  492. package/rules/date-format-consistency.yaml +3 -2
  493. package/rules/date-order.yaml +3 -1
  494. package/rules/date-range-reversed.yaml +3 -1
  495. package/rules/date-weekday-mismatch.yaml +3 -1
  496. package/rules/date-without-year.yaml +4 -3
  497. package/rules/defined-name-repeated.yaml +97 -0
  498. package/rules/defined-term-form.yaml +113 -0
  499. package/rules/demonstrative-opener-run.yaml +2 -1
  500. package/rules/double-keigo.yaml +6 -1
  501. package/rules/double-negative.yaml +5 -1
  502. package/rules/doubled-nado.yaml +2 -1
  503. package/rules/doubled-punctuation.yaml +1 -1
  504. package/rules/doubled-word.yaml +4 -1
  505. package/rules/duplicate-definition.yaml +3 -1
  506. package/rules/duplicate-heading.yaml +1 -1
  507. package/rules/duration-mismatch.yaml +2 -1
  508. package/rules/elapsed-years-mismatch.yaml +4 -1
  509. package/rules/email-greeting-closing.yaml +122 -0
  510. package/rules/email-subject-length.yaml +112 -0
  511. package/rules/emoji-density.yaml +3 -1
  512. package/rules/emoji-heading.yaml +7 -1
  513. package/rules/empty-intensifier.yaml +4 -0
  514. package/rules/empty-list-item.yaml +2 -1
  515. package/rules/empty-section.yaml +1 -1
  516. package/rules/empty-table-cell.yaml +2 -1
  517. package/rules/excessive-hedging.yaml +7 -1
  518. package/rules/exclamation-width-consistency.yaml +64 -0
  519. package/rules/expletive-construction.yaml +7 -1
  520. package/rules/fact-conflict.yaml +4 -1
  521. package/rules/figure-reference-order.yaml +7 -1
  522. package/rules/footnote-mismatch.yaml +91 -0
  523. package/rules/fullwidth-alnum-consistency.yaml +2 -2
  524. package/rules/hankaku-kana.yaml +1 -1
  525. package/rules/heading-command-missing.yaml +109 -0
  526. package/rules/heading-echo.yaml +6 -1
  527. package/rules/heading-end-punctuation-mix.yaml +158 -0
  528. package/rules/heading-level-skip.yaml +3 -3
  529. package/rules/heading-numbering-mix.yaml +4 -3
  530. package/rules/hiragana-fukushi.yaml +4 -1
  531. package/rules/homophone-slip.yaml +5 -1
  532. package/rules/humble-for-others.yaml +63 -0
  533. package/rules/hyphen-as-dash.yaml +59 -0
  534. package/rules/idiom-misuse.yaml +4 -1
  535. package/rules/image-file-name-alt.yaml +83 -0
  536. package/rules/impossible-date.yaml +79 -0
  537. package/rules/impossible-time.yaml +76 -0
  538. package/rules/internal-jargon.yaml +1 -1
  539. package/rules/invisible-character.yaml +1 -1
  540. package/rules/katakana-density.yaml +2 -1
  541. package/rules/katakana-long-vowel.yaml +1 -1
  542. package/rules/known-misspelling.yaml +2 -1
  543. package/rules/kutoten-consistency.yaml +1 -1
  544. package/rules/latin-abbreviation-form.yaml +58 -0
  545. package/rules/latin-spacing.yaml +6 -2
  546. package/rules/less-for-fewer.yaml +59 -0
  547. package/rules/link-text-url-mismatch.yaml +72 -0
  548. package/rules/list-item-form-mix.yaml +7 -1
  549. package/rules/list-marker-mix.yaml +111 -0
  550. package/rules/max-kanji-continuous.yaml +10 -2
  551. package/rules/max-paragraph-length.yaml +4 -1
  552. package/rules/max-sentence-length.yaml +29 -21
  553. package/rules/max-ten.yaml +4 -1
  554. package/rules/misconversion.yaml +2 -1
  555. package/rules/modal-conflict.yaml +2 -1
  556. package/rules/name-variant.yaml +4 -3
  557. package/rules/ngram-repetition.yaml +5 -0
  558. package/rules/no-doubled-joshi.yaml +5 -1
  559. package/rules/no-em-dash.yaml +4 -1
  560. package/rules/no-lead.yaml +6 -1
  561. package/rules/no-mixed-desumasu.yaml +5 -2
  562. package/rules/no-nakaguro-parallel.yaml +3 -0
  563. package/rules/nominalization.yaml +5 -1
  564. package/rules/nonstandard-word.yaml +80 -0
  565. package/rules/number-style-consistency.yaml +6 -1
  566. package/rules/number-without-unit.yaml +2 -1
  567. package/rules/numbering-gap.yaml +1 -1
  568. package/rules/numeral-sentence-start.yaml +56 -0
  569. package/rules/one-sentence-paragraph-run.yaml +5 -1
  570. package/rules/ordinal-suffix-mismatch.yaml +65 -0
  571. package/rules/orthographic-variant.yaml +4 -1
  572. package/rules/outside-announced-list.yaml +2 -1
  573. package/rules/oxford-comma-consistency.yaml +3 -1
  574. package/rules/padded-intro.yaml +7 -0
  575. package/rules/paragraph-length-variance.yaml +7 -1
  576. package/rules/paragraph-restatement.yaml +6 -1
  577. package/rules/percent-sum-mismatch.yaml +3 -1
  578. package/rules/person-consistency.yaml +4 -1
  579. package/rules/preamble-length.yaml +6 -0
  580. package/rules/preferred-term.yaml +1 -1
  581. package/rules/proper-noun-density.yaml +8 -1
  582. package/rules/quote-style-consistency.yaml +66 -0
  583. package/rules/ra-nuki.yaml +4 -1
  584. package/rules/range-notation-consistency.yaml +73 -0
  585. package/rules/redundant-expression.yaml +4 -1
  586. package/rules/reference-title-mismatch.yaml +4 -3
  587. package/rules/reference-topic-missing.yaml +2 -1
  588. package/rules/relative-date-mismatch.yaml +2 -1
  589. package/rules/repeated-conjunction.yaml +4 -1
  590. package/rules/repeated-sentence-ending.yaml +62 -0
  591. package/rules/repeated-sentence-head.yaml +4 -0
  592. package/rules/request-without-deadline.yaml +92 -0
  593. package/rules/required-sections.yaml +1 -1
  594. package/rules/requirement-modal.yaml +1 -1
  595. package/rules/requirement-smell.yaml +12 -4
  596. package/rules/rule-of-three.yaml +8 -1
  597. package/rules/sasete-itadaku.yaml +7 -1
  598. package/rules/section-length-uniformity.yaml +7 -1
  599. package/rules/sentence-initial-conjunction-run.yaml +5 -1
  600. package/rules/sentence-initial-so.yaml +6 -1
  601. package/rules/sentence-rhythm.yaml +8 -1
  602. package/rules/sentence-spacing-consistency.yaml +63 -0
  603. package/rules/share-over-hundred.yaml +65 -0
  604. package/rules/shell-prompt-mix.yaml +128 -0
  605. package/rules/space-before-punctuation.yaml +1 -1
  606. package/rules/spelling-consistency.yaml +3 -1
  607. package/rules/step-reference-missing.yaml +85 -0
  608. package/rules/step-statement-mix.yaml +76 -0
  609. package/rules/stock-transition.yaml +8 -1
  610. package/rules/stray-space.yaml +2 -2
  611. package/rules/summary-fact-mismatch.yaml +4 -1
  612. package/rules/table-header-variant.yaml +2 -1
  613. package/rules/table-row-overflow.yaml +90 -0
  614. package/rules/taigen-dome-in-prose.yaml +6 -0
  615. package/rules/tari-unpaired.yaml +58 -0
  616. package/rules/teki-overuse.yaml +2 -1
  617. package/rules/title-case-consistency.yaml +1 -1
  618. package/rules/title-length.yaml +7 -1
  619. package/rules/toc-heading-mismatch.yaml +136 -0
  620. package/rules/topic-predicate-distance.yaml +6 -1
  621. package/rules/total-mismatch.yaml +3 -1
  622. package/rules/translationese-density.yaml +4 -1
  623. package/rules/unbalanced-bracket.yaml +3 -3
  624. package/rules/unclosed-code-fence.yaml +107 -0
  625. package/rules/uncomparable-graded.yaml +65 -0
  626. package/rules/undefined-acronym.yaml +8 -1
  627. package/rules/unfilled-placeholder.yaml +1 -1
  628. package/rules/unit-mismatch.yaml +2 -1
  629. package/rules/unknown-word.yaml +4 -1
  630. package/rules/unqualified-superlative.yaml +6 -1
  631. package/rules/unrendered-emphasis.yaml +73 -0
  632. package/rules/unused-definition.yaml +4 -3
  633. package/rules/url-run-on.yaml +1 -1
  634. package/rules/use-before-definition.yaml +4 -3
  635. package/rules/vague-figure-reference.yaml +3 -1
  636. package/rules/vague-link-text.yaml +2 -1
  637. package/rules/vague-word-density.yaml +2 -1
  638. package/rules/very-adjective.yaml +5 -1
  639. package/rules/weasel-word.yaml +6 -1
  640. package/rules/which-that-consistency.yaml +64 -0
  641. package/rules/wordy-phrase.yaml +6 -1
  642. package/rules/yori-as-from.yaml +61 -0
  643. package/skills/chaff/SKILL.md +7 -7
  644. package/src/api.ts +13 -1
  645. package/src/cli-text.ts +22 -7
  646. package/src/cli.ts +8 -9
  647. package/src/command-help.ts +26 -0
  648. package/src/commands/eval.ts +2 -2
  649. package/src/commands/fix-plan.ts +13 -1
  650. package/src/commands/help.ts +8 -0
  651. package/src/commands/init-command.ts +13 -0
  652. package/src/commands/plugin-init.ts +150 -0
  653. package/src/commands/plugin-test.ts +111 -0
  654. package/src/config/load.ts +3 -0
  655. package/src/config/option-problems.ts +2 -2
  656. package/src/config/rule-problems.ts +2 -2
  657. package/src/custom/checked.ts +8 -0
  658. package/src/custom/dsl-fields.ts +102 -0
  659. package/src/custom/load.ts +2 -0
  660. package/src/custom/parse.ts +72 -50
  661. package/src/custom/problems.ts +16 -2
  662. package/src/detectors/amount-for-count.ts +90 -0
  663. package/src/detectors/blank-structure.ts +12 -4
  664. package/src/detectors/clause-shape.ts +199 -0
  665. package/src/detectors/code-fences.ts +113 -0
  666. package/src/detectors/cross-definitions.ts +49 -0
  667. package/src/detectors/cross-facts.ts +60 -0
  668. package/src/detectors/cross-registry/cross-doc-duplicate-definition.ts +4 -0
  669. package/src/detectors/cross-registry/cross-doc-fact-conflict.ts +4 -0
  670. package/src/detectors/currency-notation.ts +90 -0
  671. package/src/detectors/custom.ts +10 -2
  672. package/src/detectors/dangling-opener.ts +74 -0
  673. package/src/detectors/defined-term-form.ts +200 -0
  674. package/src/detectors/email-letter.ts +262 -0
  675. package/src/detectors/footnote-mismatch.ts +72 -0
  676. package/src/detectors/form-minority.ts +21 -0
  677. package/src/detectors/heading-end-mark.ts +82 -0
  678. package/src/detectors/impossible-date.ts +141 -0
  679. package/src/detectors/impossible-time.ts +151 -0
  680. package/src/detectors/link-shape.ts +70 -0
  681. package/src/detectors/list-marker.ts +60 -0
  682. package/src/detectors/mark-shape.ts +202 -0
  683. package/src/detectors/markdown-slip.ts +140 -0
  684. package/src/detectors/numeral-opener.ts +72 -0
  685. package/src/detectors/ordinal-suffix.ts +40 -0
  686. package/src/detectors/range-notation.ts +76 -0
  687. package/src/detectors/registry/adverb-polarity.ts +4 -0
  688. package/src/detectors/registry/amount-for-count.ts +4 -0
  689. package/src/detectors/registry/attachment-missing.ts +4 -0
  690. package/src/detectors/registry/bare-url-mix.ts +4 -0
  691. package/src/detectors/registry/caps-shouting.ts +4 -0
  692. package/src/detectors/registry/code-fence-language.ts +4 -0
  693. package/src/detectors/registry/currency-notation.ts +4 -0
  694. package/src/detectors/registry/dangling-opener.ts +4 -0
  695. package/src/detectors/registry/defined-name-repeated.ts +4 -0
  696. package/src/detectors/registry/defined-term-form.ts +4 -0
  697. package/src/detectors/registry/email-frame.ts +4 -0
  698. package/src/detectors/registry/email-subject.ts +4 -0
  699. package/src/detectors/registry/ending-run.ts +4 -0
  700. package/src/detectors/registry/footnote-mismatch.ts +4 -0
  701. package/src/detectors/registry/heading-command.ts +4 -0
  702. package/src/detectors/registry/heading-end-punctuation.ts +4 -0
  703. package/src/detectors/registry/hyphen-dash.ts +4 -0
  704. package/src/detectors/registry/image-file-name-alt.ts +4 -0
  705. package/src/detectors/registry/impossible-date.ts +4 -0
  706. package/src/detectors/registry/impossible-time.ts +4 -0
  707. package/src/detectors/registry/latin-abbreviation.ts +4 -0
  708. package/src/detectors/registry/link-text-url-mismatch.ts +4 -0
  709. package/src/detectors/registry/list-marker.ts +4 -0
  710. package/src/detectors/registry/mark-consistency.ts +4 -0
  711. package/src/detectors/registry/numeral-opener.ts +4 -0
  712. package/src/detectors/registry/ordinal-suffix.ts +4 -0
  713. package/src/detectors/registry/origin-particle.ts +4 -0
  714. package/src/detectors/registry/range-notation.ts +4 -0
  715. package/src/detectors/registry/relative-pronoun-mix.ts +4 -0
  716. package/src/detectors/registry/request-without-deadline.ts +4 -0
  717. package/src/detectors/registry/sentence-spacing.ts +4 -0
  718. package/src/detectors/registry/share-over-hundred.ts +4 -0
  719. package/src/detectors/registry/shell-prompt.ts +4 -0
  720. package/src/detectors/registry/step-reference.ts +4 -0
  721. package/src/detectors/registry/step-statement.ts +4 -0
  722. package/src/detectors/registry/table-row-overflow.ts +4 -0
  723. package/src/detectors/registry/toc-heading.ts +4 -0
  724. package/src/detectors/registry/unclosed-code-fence.ts +4 -0
  725. package/src/detectors/registry/unpaired-parallel.ts +4 -0
  726. package/src/detectors/registry/unrendered-emphasis.ts +4 -0
  727. package/src/detectors/relative-pronoun-mix.ts +79 -0
  728. package/src/detectors/request-owner.ts +141 -0
  729. package/src/detectors/share-over-hundred.ts +75 -0
  730. package/src/detectors/step-reference.ts +83 -0
  731. package/src/detectors/technical-docs.ts +230 -0
  732. package/src/detectors/toc-heading.ts +125 -0
  733. package/src/extension/pack-read.ts +128 -0
  734. package/src/extension/plugin-load.ts +49 -9
  735. package/src/extension/plugin-parse.ts +10 -3
  736. package/src/facts/address-values.ts +74 -0
  737. package/src/facts/cross-facts.ts +54 -0
  738. package/src/fix-plan/chosen-depth.ts +28 -0
  739. package/src/fix-plan/mode.ts +57 -11
  740. package/src/fix-plan/plan.ts +37 -5
  741. package/src/fix-plan/render.ts +24 -0
  742. package/src/fix-plan/text.ts +47 -10
  743. package/src/genre-load.ts +7 -4
  744. package/src/genre-parse.ts +19 -0
  745. package/src/init.ts +10 -10
  746. package/src/known-genres.ts +19 -0
  747. package/src/link-shape.ts +62 -0
  748. package/src/list-marks.ts +106 -0
  749. package/src/reasons.ts +16 -4
  750. package/src/render/explain.ts +14 -2
  751. package/src/render/friendly.ts +1 -1
  752. package/src/render/rules-json.ts +14 -3
  753. package/src/render/rules-table.ts +6 -6
  754. package/src/render/semantic.ts +2 -2
  755. package/src/render/suppressions.ts +2 -2
  756. package/src/rewrite-depth.ts +60 -0
  757. package/src/rule-fields.ts +123 -0
  758. package/src/rule-guide.ts +9 -0
  759. package/src/rule-load.ts +10 -4
  760. package/src/rule-offs.ts +50 -0
  761. package/src/structure/build.ts +21 -1
  762. package/src/structure/cross-definitions.ts +60 -0
@@ -0,0 +1,141 @@
1
+ // A request for an action (「資料をご確認ください」 "Please send the report.") with no deadline and no person or role,
2
+ // in its own sentence or the next. The words come from the language's lexicons; the code only combines them.
3
+ import type { Detector, Finding, Lexicon, ProseDocument, Sentence, Span, StructureNode } from "../plugin.ts";
4
+ import { proseText } from "../measure.ts";
5
+ import { comparableWords } from "./lexicon-match.ts";
6
+ import { escapeRegExp } from "../orthography.ts";
7
+ import { inDocumentOrder } from "../structure/issues.ts";
8
+
9
+ /** The lexicons the rule reads, by name. A language without them checks nothing. */
10
+ export const REQUEST_LEXICONS = {
11
+ ending: "request-ending",
12
+ action: "request-action",
13
+ condition: "request-condition",
14
+ deadline: "request-deadline",
15
+ owner: "request-owner",
16
+ pointer: "request-pointer",
17
+ } as const;
18
+
19
+ type Words = Readonly<Record<keyof typeof REQUEST_LEXICONS, readonly string[]>>;
20
+
21
+ /** A clock time (14:00, 14時, 2pm) says when as much as a date does; a duration (3時間) does not. */
22
+ const CLOCK = /\d{1,2}\s*(?:[::]\d{2}|時(?!間)|\s?[ap]\.?m\.?(?![a-z]))/iu;
23
+
24
+ const patternsOf = (lexicon: Lexicon | undefined): string[] => (lexicon ?? []).map((entry) => comparableWords(entry.pattern));
25
+
26
+ const wordsOf = (doc: ProseDocument): Words => ({
27
+ ending: patternsOf(doc.lexicons[REQUEST_LEXICONS.ending]),
28
+ action: patternsOf(doc.lexicons[REQUEST_LEXICONS.action]),
29
+ condition: patternsOf(doc.lexicons[REQUEST_LEXICONS.condition]),
30
+ deadline: patternsOf(doc.lexicons[REQUEST_LEXICONS.deadline]),
31
+ owner: patternsOf(doc.lexicons[REQUEST_LEXICONS.owner]),
32
+ pointer: patternsOf(doc.lexicons[REQUEST_LEXICONS.pointer]),
33
+ });
34
+
35
+ /** How far apart, in characters, the action word and the request ending may be: the action must be what is requested. */
36
+ const ACTION_REACH = 12;
37
+
38
+ /** A Latin word matches whole words only ("by" is not in "maybe"); other scripts have no spaces to bound a word. */
39
+ const matcherOf = (pattern: string): RegExp =>
40
+ /^[\p{Script=Latin}\d]/u.test(pattern)
41
+ ? new RegExp(`(?<![\\p{L}\\d])${escapeRegExp(pattern)}(?![\\p{L}\\d])`, "gu")
42
+ : new RegExp(escapeRegExp(pattern), "gu");
43
+
44
+ type Place = { readonly start: number; readonly end: number };
45
+
46
+ /** Where the lexicon's words occur in the text (after the comparable form: lower case, straight apostrophes). */
47
+ const placesOf = (text: string, patterns: readonly string[]): Place[] => {
48
+ const comparable = comparableWords(text);
49
+ return patterns
50
+ .filter((pattern) => pattern !== "")
51
+ .flatMap((pattern) => [...comparable.matchAll(matcherOf(pattern))].map((match) => ({ start: match.index, end: match.index + match[0].length })));
52
+ };
53
+
54
+ export const containsAny = (text: string, patterns: readonly string[]): boolean => placesOf(text, patterns).length > 0;
55
+
56
+ const gapBetween = (left: Place, right: Place): number => Math.max(left.start - right.end, right.start - left.end, 0);
57
+
58
+ /**
59
+ * A request for an action: an action word next to a request ending (「確認してください」「please send」), not a condition
60
+ * (「ご不明な点があれば…ください」 is an offer), and not a pointer to where to read more (「詳細は資料をご確認ください」).
61
+ * The action has to be close to the ending, or a noun like 検討会 or 履修登録 earlier in the sentence would make one.
62
+ */
63
+ export const isRequest = (text: string, words: Words): boolean => {
64
+ const endings = placesOf(text, words.ending);
65
+ const asked = placesOf(text, words.action).some((action) => endings.some((ending) => gapBetween(action, ending) <= ACTION_REACH));
66
+ return asked && !containsAny(text, words.condition) && !containsAny(text, words.pointer);
67
+ };
68
+
69
+ const NAMED = new Set(["PROPN"]);
70
+
71
+ /** A proper noun in the request itself (「Ito, please share」) names who; one in the next sentence may be a product or a place. */
72
+ const namesSomeone = (sentence: Sentence): boolean => (sentence.tokens ?? []).some((token) => NAMED.has(token.pos));
73
+
74
+ const datedWithin = (dates: readonly Span[], span: Span): boolean => dates.some((date) => date.start >= span.start && date.start < span.end);
75
+
76
+ type Bounds = { readonly starts: ReadonlySet<number>; readonly ends: ReadonlySet<number> };
77
+
78
+ /** Where the sentence's words start and end in its comparable text, found by walking the token surfaces in order. */
79
+ const wordBounds = (comparable: string, sentence: Sentence): Bounds | undefined => {
80
+ const tokens = sentence.tokens;
81
+ if (tokens === undefined || tokens.length === 0) return undefined;
82
+ const starts = new Set<number>();
83
+ const ends = new Set<number>();
84
+ tokens.reduce((from, token) => {
85
+ const surface = comparableWords(token.surface);
86
+ const at = surface === "" ? -1 : comparable.indexOf(surface, from);
87
+ if (at === -1) return from;
88
+ starts.add(at);
89
+ ends.add(at + surface.length);
90
+ return at + surface.length;
91
+ }, 0);
92
+ return { starts, ends };
93
+ };
94
+
95
+ /**
96
+ * Whether the sentence holds one of the words as whole words. With parts of speech a word must start and end on a word
97
+ * boundary, so 様 is not found in 同様, さん in たくさん, or まで in いままで; without them, as text.
98
+ */
99
+ const holdsWord = (sentence: Sentence, patterns: readonly string[]): boolean => {
100
+ const comparable = comparableWords(proseText(sentence));
101
+ const bounds = wordBounds(comparable, sentence);
102
+ return placesOf(comparable, patterns).some((place) => bounds === undefined || (bounds.starts.has(place.start) && bounds.ends.has(place.end)));
103
+ };
104
+
105
+ /** Whether a sentence says when (a date, a clock time, a deadline word) or who (a role; a name in the request itself). */
106
+ const saysWhenOrWho = (sentence: Sentence, words: Words, dates: readonly Span[], isRequestItself: boolean): boolean =>
107
+ datedWithin(dates, sentence.span) ||
108
+ CLOCK.test(proseText(sentence)) ||
109
+ holdsWord(sentence, words.deadline) ||
110
+ holdsWord(sentence, words.owner) ||
111
+ (isRequestItself && namesSomeone(sentence));
112
+
113
+ /** A paragraph break or a new list item between two sentences: the second does not continue the first. */
114
+ const BREAK = /\n[^\S\n]*\n|\n[^\S\n]*(?:[-*+]|\d+[.)])[^\S\n]/u;
115
+
116
+ const continues = (source: string, sentence: Sentence, next: Sentence | undefined): next is Sentence =>
117
+ next !== undefined && !BREAK.test(source.slice(sentence.span.end, next.span.start));
118
+
119
+ const dateSpans = (tree: StructureNode | undefined): Span[] =>
120
+ tree === undefined ? [] : inDocumentOrder(tree).flatMap((node) => (node.kind === "date" ? [node.span] : []));
121
+
122
+ /** The request sentences with neither a deadline nor a person in them or in the sentence after, in the same paragraph or item. */
123
+ export const ownerlessRequests = (doc: ProseDocument): Sentence[] => {
124
+ const words = wordsOf(doc);
125
+ const dates = dateSpans(doc.structure);
126
+ return doc.sentences.filter((sentence, index) => {
127
+ if (!isRequest(proseText(sentence), words) || saysWhenOrWho(sentence, words, dates, true)) return false;
128
+ const next = doc.sentences[index + 1];
129
+ return !continues(doc.source, sentence, next) || !saysWhenOrWho(next, words, dates, false);
130
+ });
131
+ };
132
+
133
+ export const requestWithoutDeadline: Detector = (doc: ProseDocument): Finding[] =>
134
+ ownerlessRequests(doc).map((sentence) => ({
135
+ rule: "request-without-deadline",
136
+ severity: "info",
137
+ line: 0,
138
+ column: 0,
139
+ quote: proseText(sentence),
140
+ values: { offset: sentence.span.start },
141
+ }));
@@ -0,0 +1,75 @@
1
+ import type { Detector, Finding, ProseDocument } from "../plugin.ts";
2
+ import { escapeRegExp } from "../orthography.ts";
3
+ import { quoteAround } from "./quote-around.ts";
4
+
5
+ // 集まりの一部の割合なのに 100% を超える百分率(回答者の120%、150% of respondents)。一部は全体を超えない。
6
+ // 集まりの語は語彙表 share-whole、百分率とのあいだの語とその側は share-whole-link、百分率の単位は percent-unit が言う。
7
+
8
+ export type ShareWords = {
9
+ readonly wholes: readonly string[];
10
+ readonly links: readonly { readonly pattern: string; readonly position: "before" | "after" }[];
11
+ readonly units: readonly string[];
12
+ };
13
+
14
+ /** 100% を超える一部の割合 1 つ。written は百分率、whole は何の一部か。 */
15
+ export type ShareOverHundred = { readonly offset: number; readonly end: number; readonly written: string; readonly whole: string };
16
+
17
+ const WHOLE_PERCENT = 100;
18
+ /** 集まりの語が、より長い語の頭でないこと(users_2025、回答者数)。漢字やカタカナが続けば別の語(回答者数は人数で、割合の元ではない)。 */
19
+ const NOT_INSIDE_WORD = "(?![A-Za-z0-9_\\p{Script=Han}\\p{Script=Katakana}])";
20
+ const FULLWIDTH_OFFSET = 0xfee0;
21
+
22
+ const halfWidth = (text: string): string => text.replace(/[0-9.]/gu, (char) => String.fromCharCode(char.charCodeAt(0) - FULLWIDTH_OFFSET));
23
+
24
+ const alternation = (words: readonly string[]): string =>
25
+ words
26
+ .toSorted((left, right) => right.length - left.length)
27
+ .map(escapeRegExp)
28
+ .join("|");
29
+
30
+ /** 集まりの語の前後の英字だけを語の切れ目に見る。日本語は語を続けて書く(アンケートでは回答者の、120%の回答者が)。 */
31
+ const wholeNear = (text: string, start: number, end: number, words: ShareWords): string | undefined => {
32
+ const wholes = alternation(words.wholes);
33
+ return words.links
34
+ .map((link) => {
35
+ const near =
36
+ link.position === "after"
37
+ ? new RegExp(`^\\s*${escapeRegExp(link.pattern)}\\s*(?<whole>${wholes})${NOT_INSIDE_WORD}`, "u").exec(text.slice(end))
38
+ : new RegExp(`(?<![A-Za-z])(?<whole>${wholes})${escapeRegExp(link.pattern)}\\s*$`, "u").exec(text.slice(0, start));
39
+ return near?.groups?.["whole"];
40
+ })
41
+ .find((whole) => whole !== undefined);
42
+ };
43
+
44
+ /** 文字列の中の、集まりの一部の割合なのに 100% を超える百分率。 */
45
+ export const sharesOverHundred = (text: string, words: ShareWords): ShareOverHundred[] => {
46
+ if (words.wholes.length === 0 || words.units.length === 0) return [];
47
+ const percent = new RegExp(`(?<![\\d.,,0-9])(?<value>[0-90-9]+(?:[,,][0-90-9]{3})*(?:[..][0-90-9]+)?)\\s?(?:${alternation(words.units)})`, "gu");
48
+ return [...text.matchAll(percent)].flatMap((match) => {
49
+ const value = Number(halfWidth(match.groups?.["value"] ?? "").replace(/[,,]/gu, ""));
50
+ if (value <= WHOLE_PERCENT) return [];
51
+ const end = match.index + match[0].length;
52
+ const whole = wholeNear(text, match.index, end, words);
53
+ return whole === undefined ? [] : [{ offset: match.index, end, written: match[0], whole }];
54
+ });
55
+ };
56
+
57
+ const positioned = (doc: ProseDocument): ShareWords["links"] =>
58
+ (doc.lexicons["share-whole-link"] ?? []).flatMap((entry) => (entry.position === undefined ? [] : [{ pattern: entry.pattern, position: entry.position }]));
59
+
60
+ export const shareOverHundred: Detector = (doc): Finding[] => {
61
+ const text = doc.prose ?? doc.source;
62
+ const words: ShareWords = {
63
+ wholes: (doc.lexicons["share-whole"] ?? []).map((entry) => entry.pattern),
64
+ links: positioned(doc),
65
+ units: (doc.lexicons["percent-unit"] ?? []).map((entry) => entry.pattern),
66
+ };
67
+ return sharesOverHundred(text, words).map((found) => ({
68
+ rule: "",
69
+ severity: "warning",
70
+ line: 0,
71
+ column: 0,
72
+ quote: quoteAround(text, found.offset, found.end),
73
+ values: { written: found.written, whole: found.whole, offset: found.offset },
74
+ }));
75
+ };
@@ -0,0 +1,83 @@
1
+ import type { Detector, Finding, ProseDocument } from "../plugin.ts";
2
+ import { escapeRegExp } from "../orthography.ts";
3
+ import { quoteAround } from "./quote-around.ts";
4
+
5
+ // 本文が指す手順の番号(「手順5を参照」「see step 5」)が、文書のどの手順にも無い所。
6
+ // 手順は、手順の語(語彙表 step-word)と番号で始まる見出しと、番号付きの箇条書きの項目。手順が一つも無い文書では言わない。
7
+ // 番号のすぐ後ろの語(語彙表 figure-elsewhere の of・in)や前の文書の名前が、ほかの文書の手順だと言えば数えない。
8
+
9
+ /** 無い手順を指す参照 1 つ。label は書いたままの参照、last は文書の手順の最後の番号。 */
10
+ export type MissingStep = { readonly offset: number; readonly label: string; readonly last: number };
11
+
12
+ /**
13
+ * 手順を読むのに要るもの。source は見出しの印(#)を残した文書、prose はコードを覆った本文(位置は source と同じ)。
14
+ * elsewhere は参照がほかの文書の手順を指すか(参照の始まりと終わりの位置で)。
15
+ */
16
+ export type StepText = { readonly source: string; readonly prose: string; readonly elsewhere: (start: number, end: number) => boolean };
17
+
18
+ const FULLWIDTH_OFFSET = 0xfee0;
19
+ const LIST_ITEM = /^[ \t]*(?<number>\d{1,3})[.)][ \t]/u;
20
+ const HEADING = /^#{1,6}[ \t]+/u;
21
+
22
+ const halfWidth = (text: string): string => text.replace(/[0-9]/gu, (char) => String.fromCharCode(char.charCodeAt(0) - FULLWIDTH_OFFSET));
23
+
24
+ type Mention = { readonly offset: number; readonly label: string; readonly number: number; readonly heading: boolean };
25
+
26
+ const mentionPattern = (words: readonly string[]): RegExp =>
27
+ new RegExp(`(?<![A-Za-z])(?:${words.map(escapeRegExp).join("|")})[ \\t\\u3000]?(?<number>[0-90-9]{1,3})(?![0-90-9A-Za-z])`, "gu");
28
+
29
+ const isHeadingAt = (source: string, offset: number): boolean => HEADING.test(source.slice(source.lastIndexOf("\n", offset - 1) + 1, offset));
30
+
31
+ /** 本文の参照(コードを覆った prose から)と、見出しの手順(prose は見出しも覆うので source から)。 */
32
+ const mentionsOf = ({ source, prose }: StepText, words: readonly string[]): Mention[] => {
33
+ const toMention = (match: RegExpExecArray | RegExpMatchArray, heading: boolean): Mention => ({
34
+ offset: match.index ?? 0,
35
+ label: match[0],
36
+ number: Number(halfWidth(match.groups?.["number"] ?? "")),
37
+ heading,
38
+ });
39
+ const headings = [...source.matchAll(mentionPattern(words))].filter((match) => isHeadingAt(source, match.index)).map((match) => toMention(match, true));
40
+ const references = [...prose.matchAll(mentionPattern(words))].filter((match) => !isHeadingAt(source, match.index)).map((match) => toMention(match, false));
41
+ return [...headings, ...references];
42
+ };
43
+
44
+ const listNumbers = (prose: string): number[] =>
45
+ prose.split("\n").flatMap((line) => {
46
+ const number = LIST_ITEM.exec(line)?.groups?.["number"];
47
+ return number === undefined ? [] : [Number(number)];
48
+ });
49
+
50
+ /** 文書のどの手順にも無い番号を指す参照。手順の最後の番号を超えるものだけ(箇条書きが二つあれば、番号はどちらかにある)。 */
51
+ export const missingSteps = (text: StepText, words: readonly string[]): MissingStep[] => {
52
+ if (words.length === 0) return [];
53
+ const mentions = mentionsOf(text, words);
54
+ const steps = [...mentions.filter((mention) => mention.heading).map((mention) => mention.number), ...listNumbers(text.prose)];
55
+ if (steps.length === 0) return [];
56
+ const last = Math.max(...steps);
57
+ return mentions
58
+ .filter((mention) => !mention.heading && mention.number > last && !text.elsewhere(mention.offset, mention.offset + mention.label.length))
59
+ .map((mention) => ({ offset: mention.offset, label: mention.label, last }));
60
+ };
61
+
62
+ /** 番号のすぐ後ろが figure-elsewhere の語か、すぐ前がこの文書でない文書の名前なら、ほかの文書の手順。 */
63
+ const elsewhereOf =
64
+ (doc: ProseDocument, text: string) =>
65
+ (start: number, end: number): boolean => {
66
+ const after = text.slice(end).trimStart();
67
+ const placed = (doc.lexicons["figure-elsewhere"] ?? []).some((entry) => after.startsWith(`${entry.pattern} `) && /^\s/u.test(text.slice(end)));
68
+ const named = doc.namedDocument?.(text, start);
69
+ return placed || (named !== undefined && !named.self);
70
+ };
71
+
72
+ export const stepReference: Detector = (doc): Finding[] => {
73
+ const prose = doc.prose ?? doc.source;
74
+ const words = (doc.lexicons["step-word"] ?? []).map((entry) => entry.pattern);
75
+ return missingSteps({ source: doc.source, prose, elsewhere: elsewhereOf(doc, prose) }, words).map((missing) => ({
76
+ rule: "",
77
+ severity: "warning",
78
+ line: 0,
79
+ column: 0,
80
+ quote: quoteAround(prose, missing.offset, missing.offset + missing.label.length),
81
+ values: { label: missing.label, last: missing.last, offset: missing.offset },
82
+ }));
83
+ };
@@ -0,0 +1,230 @@
1
+ // Checks a README or a manual needs: code fences tagged with a language or not, shell commands written with and without
2
+ // a prompt, a heading that names a command its section's code never shows, and a step of a numbered procedure written
3
+ // as a statement among instructions. Pure. Shell languages and prompt marks come from the shell-fence-language and
4
+ // shell-prompt lexicons.
5
+ import { codeFences, codeSpans, type CodeFence } from "./code-fences.ts";
6
+ import { quoteAt } from "./structure-tree.ts";
7
+ import type { Detector, Finding, ProseDocument, Span, Token } from "../plugin.ts";
8
+
9
+ const patternsOf = (doc: ProseDocument, id: string): string[] => (doc.lexicons[id] ?? []).map((entry) => entry.pattern);
10
+
11
+ /** The members of the less used of two kinds: at most limit of them, and fewer than the other kind. */
12
+ export const minorityOf = <T>(first: readonly T[], second: readonly T[], limit: number): readonly T[] => {
13
+ const [fewer, more] = first.length <= second.length ? [first, second] : [second, first];
14
+ return fewer.length > 0 && fewer.length < more.length && fewer.length <= limit ? fewer : [];
15
+ };
16
+
17
+ const finding = (doc: ProseDocument, rule: string, offset: number, values: Readonly<Record<string, string | number>> = {}): Finding => ({
18
+ rule,
19
+ severity: "info",
20
+ line: 0,
21
+ column: 0,
22
+ quote: quoteAt(doc.source, offset),
23
+ values: { ...values, offset },
24
+ });
25
+
26
+ /** Fences without a language, when they are the fewer kind: a document that mostly leaves the language out is not reported. */
27
+ export const untaggedFewer = (fences: readonly CodeFence[], limit: number): readonly CodeFence[] => {
28
+ const untagged = fences.filter((fence) => fence.language === "");
29
+ const tagged = fences.filter((fence) => fence.language !== "");
30
+ return untagged.length > 0 && untagged.length < tagged.length && untagged.length <= limit ? untagged : [];
31
+ };
32
+
33
+ export const codeFenceLanguage: Detector = (doc, options): Finding[] => {
34
+ const fences = codeFences(doc.source);
35
+ const tagged = fences.filter((fence) => fence.language !== "").length;
36
+ return untaggedFewer(fences, options.limit).map((fence) =>
37
+ finding(doc, "code-fence-language-mix", fence.start, { tagged, untagged: fences.length - tagged }),
38
+ );
39
+ };
40
+
41
+ export type PromptStyle = "prompt" | "bare" | undefined;
42
+
43
+ /**
44
+ * Whether a shell block writes its commands after a prompt ($ npm install) or bare (npm install). A block that mixes
45
+ * prompted and unprompted lines shows a command with its output, and is neither. Comment lines (#) are skipped.
46
+ */
47
+ export const promptStyleOf = (fence: CodeFence, prompts: readonly string[]): PromptStyle => {
48
+ const lines = fence.lines.map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
49
+ if (lines.length === 0) return undefined;
50
+ const prompted = lines.filter((line) => prompts.some((prompt) => line.startsWith(`${prompt} `))).length;
51
+ if (prompted === lines.length) return "prompt";
52
+ return prompted === 0 ? "bare" : undefined;
53
+ };
54
+
55
+ export const shellPrompt: Detector = (doc, options): Finding[] => {
56
+ const languages = new Set(patternsOf(doc, "shell-fence-language").map((language) => language.toLowerCase()));
57
+ const prompts = patternsOf(doc, "shell-prompt");
58
+ const shells = codeFences(doc.source).filter((fence) => languages.has(fence.language));
59
+ const prompted = shells.filter((fence) => promptStyleOf(fence, prompts) === "prompt");
60
+ const bare = shells.filter((fence) => promptStyleOf(fence, prompts) === "bare");
61
+ const minority = minorityOf(prompted, bare, options.limit);
62
+ const style = minority === prompted ? "prompt" : "bare";
63
+ return minority.map((fence) => finding(doc, "shell-prompt-mix", fence.start, { majority: style === "prompt" ? bare.length : prompted.length }));
64
+ };
65
+
66
+ /**
67
+ * A code span that names a command or an option: a flag (--force, -f) or a command with its arguments (npm install). A
68
+ * single plain word (net, init) is as often a package, an endpoint or a type as a command, and is left out.
69
+ */
70
+ const COMMAND_WORD = /^[a-z][a-z0-9_-]*$/u;
71
+ const FLAG = /^--?[A-Za-z][\w-]*$/u;
72
+
73
+ export const isCommandName = (text: string): boolean => {
74
+ const words = text.trim().split(/\s+/u);
75
+ const first = words[0] ?? "";
76
+ return words.length === 1 ? FLAG.test(first) : COMMAND_WORD.test(first);
77
+ };
78
+
79
+ /** The heading without its links' text: [`net`](/pkg/net/) names the page it links to, not a command shown below. */
80
+ export const withoutLinks = (heading: string): string => {
81
+ const pieces: string[] = [];
82
+ const state = { at: 0 };
83
+ while (state.at < heading.length) {
84
+ const open = heading.indexOf("[", state.at);
85
+ const close = open === -1 ? -1 : heading.indexOf("]", open);
86
+ if (close === -1) break;
87
+ pieces.push(heading.slice(state.at, open), " ");
88
+ state.at = close + 1 + targetLength(heading, close + 1);
89
+ }
90
+ pieces.push(heading.slice(state.at));
91
+ return pieces.join("");
92
+ };
93
+
94
+ /** The length of a link's target right after its text: (url), [label] or [] (a reference link); 0 when there is none. */
95
+ const targetLength = (heading: string, at: number): number => {
96
+ const pair: Readonly<Record<string, string>> = { "(": ")", "[": "]" };
97
+ const closer = pair[heading.charAt(at)];
98
+ const end = closer === undefined ? -1 : heading.indexOf(closer, at);
99
+ return end === -1 ? 0 : end - at + 1;
100
+ };
101
+
102
+ /** The commands and options a heading names: its code spans that read as one, and bare flags (--force). */
103
+ export const commandsIn = (heading: string): string[] => {
104
+ const spans = codeSpans(withoutLinks(heading)).map((span) => span.text);
105
+ const flags = heading
106
+ .replace(/`[^`]*`/gu, " ")
107
+ .split(/\s+/u)
108
+ .filter((word) => FLAG.test(word) && word.startsWith("--"));
109
+ return [...new Set([...spans.filter(isCommandName), ...flags])];
110
+ };
111
+
112
+ const collapse = (text: string): string => text.replace(/\s+/gu, " ");
113
+ const OPTION_CHAR = /[A-Za-z0-9_-]/u;
114
+
115
+ /** Whether the code holds the command as a whole word: --force is not --force-with-lease, init is not initial. */
116
+ export const showsCommand = (code: string, command: string): boolean => {
117
+ const text = collapse(code);
118
+ const wanted = collapse(command.trim());
119
+ const state = { at: text.indexOf(wanted) };
120
+ while (state.at !== -1) {
121
+ const before = text.charAt(state.at - 1);
122
+ const after = text.charAt(state.at + wanted.length);
123
+ if (!OPTION_CHAR.test(before) && !OPTION_CHAR.test(after)) return true;
124
+ state.at = text.indexOf(wanted, state.at + 1);
125
+ }
126
+ return false;
127
+ };
128
+
129
+ type Section = { readonly heading: string; readonly start: number; readonly body: Span };
130
+
131
+ const sectionsOf = (doc: ProseDocument): Section[] => {
132
+ const headings = doc.markup?.headings ?? [];
133
+ return headings.map((heading, index) => {
134
+ const next = headings.slice(index + 1).find((later) => later.depth <= heading.depth);
135
+ return { heading: doc.source.slice(heading.start, heading.end), start: heading.start, body: { start: heading.end, end: next?.start ?? doc.source.length } };
136
+ });
137
+ };
138
+
139
+ /** The code a section shows: its fenced blocks and its inline code spans. Empty when it shows none. */
140
+ const codeOf = (doc: ProseDocument, fences: readonly CodeFence[], body: Span): string => {
141
+ const inFences = fences.filter((fence) => fence.start >= body.start && fence.start < body.end);
142
+ const outside = inFences.reduceRight(
143
+ (text, fence) => `${text.slice(0, fence.start - body.start)}${text.slice(Math.min(fence.end, body.end) - body.start)}`,
144
+ doc.source.slice(body.start, body.end),
145
+ );
146
+ return [...inFences.flatMap((fence) => fence.lines), ...codeSpans(outside).map((span) => span.text)].join("\n");
147
+ };
148
+
149
+ export const headingCommand: Detector = (doc): Finding[] => {
150
+ const fences = codeFences(doc.source);
151
+ return sectionsOf(doc).flatMap((section) => {
152
+ const commands = commandsIn(section.heading);
153
+ if (commands.length === 0) return [];
154
+ const code = codeOf(doc, fences, section.body);
155
+ if (code.trim() === "") return [];
156
+ return commands.filter((command) => !showsCommand(code, command)).map((command) => finding(doc, "heading-command-missing", section.start, { command }));
157
+ });
158
+ };
159
+
160
+ const ORDERED_ITEM = /^\s*\d{1,3}[.)]\s/u;
161
+ /** What opens a statement: an article or a pronoun (The installer asks, You click, It opens). */
162
+ const SUBJECT_OPENER = new Set(["DET", "PRON"]);
163
+ /** What a subject opens with when it is a name or a noun phrase (Docker builds, Each user gets). */
164
+ const SUBJECT_HEAD = new Set(["DET", "PRON", "PROPN", "NOUN", "ADJ"]);
165
+ /** What may stand between the opener and the verb: the rest of the subject. */
166
+ const SUBJECT_PART = new Set(["DET", "PRON", "PROPN", "NOUN", "ADJ", "ADP"]);
167
+ /** What opens an instruction. The tagger reads a sentence-initial imperative as a noun, an adjective or a name (Click, Open, Restart). */
168
+ const INSTRUCTION_OPENER = new Set(["VERB", "NOUN", "ADJ", "PROPN"]);
169
+ const PREDICATE = new Set(["VERB", "AUX"]);
170
+ const SKIPPED = new Set(["PUNCT", "NUM", "SYM", "X", "SPACE"]);
171
+ /** Steps shorter than this are labels (1. Setup), not instructions or statements. */
172
+ const MIN_STEP_TOKENS = 3;
173
+ /** A list needs this many steps to have a form to keep. */
174
+ const MIN_STEPS = 3;
175
+
176
+ /** A verb in a finite, inflected form (starts, built): it has a subject before it. A base form (Save) may be an imperative's object. */
177
+ const isInflected = (token: Token): boolean =>
178
+ token.lemma !== undefined && token.lemma !== "" && token.surface.toLowerCase() !== token.lemma && token.features?.["VerbForm"] !== "Part";
179
+
180
+ /**
181
+ * A step written as a statement: a subject, then its verb. The subject opens with an article or a pronoun (The installer
182
+ * asks, You click), or it is a name or a noun whose verb is inflected (Docker builds). A noun before a base-form verb is
183
+ * not enough: the tagger reads Click and Press there as nouns (Click Save).
184
+ */
185
+ export const isStatement = (tokens: readonly Token[]): boolean => {
186
+ const words = tokens.filter((token) => !SKIPPED.has(token.pos));
187
+ const [head, ...rest] = words;
188
+ if (head === undefined || words.length < MIN_STEP_TOKENS || !SUBJECT_HEAD.has(head.pos)) return false;
189
+ const verbAt = rest.findIndex((token) => PREDICATE.has(token.pos));
190
+ const verb = rest[verbAt];
191
+ if (verb === undefined || !rest.slice(0, verbAt).every((token) => SUBJECT_PART.has(token.pos))) return false;
192
+ // Punctuation before the verb is no subject: a label (Guests: the users who …) or an opening phrase (On the left bar, select …).
193
+ const interrupted = tokens.some((token) => token.pos === "PUNCT" && token.span.start > head.span.start && token.span.start < verb.span.start);
194
+ return !interrupted && (SUBJECT_OPENER.has(head.pos) || isInflected(verb));
195
+ };
196
+
197
+ type Step = { readonly start: number; readonly kind: "statement" | "instruction" | "other" };
198
+
199
+ /** A step that opens with its verb: an instruction (Run the installer). */
200
+ export const isInstruction = (tokens: readonly Token[]): boolean => INSTRUCTION_OPENER.has(tokens.find((token) => !SKIPPED.has(token.pos))?.pos ?? "");
201
+
202
+ const kindOf = (tokens: readonly Token[]): Step["kind"] => {
203
+ if (isStatement(tokens)) return "statement";
204
+ return isInstruction(tokens) ? "instruction" : "other";
205
+ };
206
+
207
+ /** The steps of each numbered list: each item's first sentence, read as a statement or not. */
208
+ const stepLists = (doc: ProseDocument): Step[][] =>
209
+ doc.lists
210
+ .filter((list) => ORDERED_ITEM.test(doc.source.slice(list.span.start, list.span.end)))
211
+ .map((list) =>
212
+ list.itemSpans.flatMap((item): Step[] => {
213
+ const sentence = doc.sentences.find((candidate) => candidate.span.start >= item.start && candidate.span.start < item.end);
214
+ // Only the sentence on the item's own line: a note paragraph under it (**NOTE:** …) is not the step.
215
+ const onItemLine = sentence !== undefined && !doc.source.slice(item.start, sentence.span.start).includes("\n");
216
+ return sentence?.tokens === undefined || !onItemLine ? [] : [{ start: sentence.span.start, kind: kindOf(sentence.tokens) }];
217
+ }),
218
+ )
219
+ .filter((steps) => steps.length >= MIN_STEPS);
220
+
221
+ export const stepStatement: Detector = (doc, options): Finding[] =>
222
+ stepLists(doc).flatMap((steps) => {
223
+ const statements = steps.filter((step) => step.kind === "statement");
224
+ const instructions = steps.filter((step) => step.kind === "instruction");
225
+ // A procedure is a list most of whose steps are instructions; a numbered list of terms and definitions is not one.
226
+ const isProcedure = instructions.length * 2 > steps.length;
227
+ return isProcedure && statements.length > 0 && statements.length < instructions.length && statements.length <= options.limit
228
+ ? statements.map((step) => finding(doc, "step-statement-mix", step.start, { count: instructions.length }))
229
+ : [];
230
+ });
@@ -0,0 +1,125 @@
1
+ import type { Detector, Finding } from "../plugin.ts";
2
+ import { headingWords } from "../structure-shape/heading-words.ts";
3
+ import { quoteAround } from "./quote-around.ts";
4
+
5
+ // 目次の項目が、文書のどの見出しとも合わない所。目次の見出しは語彙表 toc-heading が言う。
6
+ // 項目と見出しは、空白・記号・大文字小文字を無視して比べ、頭の番号(1.2、2-1、第3章)を外しても比べる。
7
+
8
+ /** 見出しの無い目次の項目 1 つ。entry は項目の言葉(リンクなら字の部分)。 */
9
+ export type StrayTocEntry = { readonly offset: number; readonly entry: string };
10
+
11
+ type Line = { readonly text: string; readonly offset: number };
12
+ type Heading = { readonly title: string; readonly offset: number };
13
+
14
+ const HEADING = /^[ \t]{0,3}#{1,6}[ \t]/u;
15
+ const FENCE = /^[ \t]*(?:```|~~~)/u;
16
+ const LIST_ITEM = /^[ \t]*(?:[-*+]|\d{1,3}[.)])[ \t]/u;
17
+ const LINK_TEXT = /^\[(?<text>[^\]]*)\]\([^)]*\)/u;
18
+ const ATTRIBUTES = /\{#[^}]*\}/gu;
19
+ const DIGIT = /[\d0-9]/u;
20
+ const LEADER_DOTS = new Set([".", "…", "・"]);
21
+ const MIN_LEADER = 2;
22
+ /** 目次の項目のうち見出しに合わないものがこれより多ければ、目次は別の書き方の見出し(本文に無い章立て)と読んで言わない。 */
23
+ const MAX_STRAY_SHARE = 0.5;
24
+
25
+ const linesOf = (source: string): Line[] => [...source.matchAll(/^.*$/gmu)].map((match) => ({ text: match[0], offset: match.index }));
26
+
27
+ /** コードの囲みの外の行。 */
28
+ const outsideFences = (lines: readonly Line[]): Line[] => {
29
+ const fence = { open: false };
30
+ return lines.filter((line) => {
31
+ if (FENCE.test(line.text)) {
32
+ fence.open = !fence.open;
33
+ return false;
34
+ }
35
+ return !fence.open;
36
+ });
37
+ };
38
+
39
+ /** 比べるための形。全角を半角にそろえ(4 と 4)、空白・記号を消し、小文字にする。 */
40
+ const comparable = (text: string): string =>
41
+ text
42
+ .replace(ATTRIBUTES, "")
43
+ .normalize("NFKC")
44
+ .toLowerCase()
45
+ .replace(/[\s\p{P}\p{S}]/gu, "");
46
+
47
+ /** 頭の番号(1.2、2-1、第3章)と飾りを外した、比べるための形。 */
48
+ const withoutNumber = (text: string): string => comparable(headingWords(text));
49
+
50
+ /** text の終わりから、isTail に当たる字を外したもの。 */
51
+ const trimTail = (text: string, isTail: (char: string) => boolean): string => {
52
+ const chars = [...text];
53
+ return chars.slice(0, chars.findLastIndex((char) => !isTail(char)) + 1).join("");
54
+ };
55
+
56
+ /** 見出しの行の題(頭と終わりの # を外す)。見出しでなければ undefined。 */
57
+ const headingTitle = (line: string): string | undefined => {
58
+ if (!HEADING.test(line)) return undefined;
59
+ const withoutMarks = line.trim().replace(/^#+/u, "").trim();
60
+ return trimTail(withoutMarks, (char) => char === "#").trim();
61
+ };
62
+
63
+ /** 終わりのページ番号(「背景 ........ 3」の 3 と点線)。点線の無い数(Step 3)は題のうち。 */
64
+ const withoutPageNumber = (text: string): string => {
65
+ const before = trimTail(text, (char) => DIGIT.test(char)).trimEnd();
66
+ if (before === text.trimEnd()) return text;
67
+ const title = trimTail(before, (char) => LEADER_DOTS.has(char));
68
+ return before.length - title.length >= MIN_LEADER ? title.trim() : text;
69
+ };
70
+
71
+ /** 項目の言葉と、項目の行の中でそれが始まる所。リンクなら字の部分(番号の後ろのリンク「1. [概要](#…)」も)。 */
72
+ const entryOf = (item: string): { readonly text: string; readonly start: number } => {
73
+ const bracket = item.indexOf("[");
74
+ const link = bracket === -1 ? undefined : LINK_TEXT.exec(item.slice(bracket))?.groups?.["text"];
75
+ if (link !== undefined) return { text: withoutPageNumber(link), start: bracket + 1 };
76
+ return { text: withoutPageNumber(item.trim()), start: item.length - item.trimStart().length };
77
+ };
78
+
79
+ const matchesHeading = (entry: string, headings: readonly Heading[]): boolean =>
80
+ headings.some(
81
+ (heading) => comparable(heading.title) === comparable(entry) || (withoutNumber(entry) !== "" && withoutNumber(heading.title) === withoutNumber(entry)),
82
+ );
83
+
84
+ /** 目次の見出しの下(次の見出しまで)の箇条書きの項目。 */
85
+ const entriesUnder = (lines: readonly Line[], tocAt: number): Line[] => {
86
+ const after = lines.filter((line) => line.offset > tocAt);
87
+ const end = after.findIndex((line) => HEADING.test(line.text));
88
+ return (end === -1 ? after : after.slice(0, end)).flatMap((line) => {
89
+ const marker = LIST_ITEM.exec(line.text)?.[0];
90
+ if (marker === undefined) return [];
91
+ const entry = entryOf(line.text.slice(marker.length));
92
+ return [{ text: entry.text, offset: line.offset + marker.length + entry.start }];
93
+ });
94
+ };
95
+
96
+ /** 文書の目次の項目のうち、どの見出しとも合わないもの。 */
97
+ export const strayTocEntries = (source: string, tocWords: readonly string[]): StrayTocEntry[] => {
98
+ const lines = outsideFences(linesOf(source));
99
+ const headings = lines.flatMap((line) => {
100
+ const title = headingTitle(line.text);
101
+ return title === undefined ? [] : [{ title, offset: line.offset }];
102
+ });
103
+ const tocNames = new Set(tocWords.map(comparable));
104
+ return headings
105
+ .filter((heading) => tocNames.has(comparable(heading.title)))
106
+ .flatMap((toc) => {
107
+ const entries = entriesUnder(lines, toc.offset).filter((entry) => entry.text !== "");
108
+ const others = headings.filter((heading) => heading !== toc);
109
+ const stray = entries.filter((entry) => !matchesHeading(entry.text, others));
110
+ return stray.length > entries.length * MAX_STRAY_SHARE ? [] : stray.map((entry) => ({ offset: entry.offset, entry: entry.text }));
111
+ });
112
+ };
113
+
114
+ export const tocHeading: Detector = (doc): Finding[] =>
115
+ strayTocEntries(
116
+ doc.source,
117
+ (doc.lexicons["toc-heading"] ?? []).map((entry) => entry.pattern),
118
+ ).map((stray) => ({
119
+ rule: "",
120
+ severity: "warning",
121
+ line: 0,
122
+ column: 0,
123
+ quote: quoteAround(doc.source, stray.offset, stray.offset + stray.entry.length),
124
+ values: { entry: stray.entry, offset: stray.offset },
125
+ }));