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,113 @@
1
+ // Fenced code blocks (``` or ~~~) and inline code spans of a Markdown source. Pure. A fence's language is the first word
2
+ // of its info string (```bash, ```{r}); a fence inside a blockquote is someone else's and is not read.
3
+ import { parse } from "../markdown-read.ts";
4
+ import { spanOf } from "../markdown-node.ts";
5
+ import { eachPreOrder } from "../tree-walk.ts";
6
+
7
+ export type CodeFence = {
8
+ /** Where the opening fence line starts. */
9
+ readonly start: number;
10
+ /** The first word of the info string, lower-cased; "" when the fence names none. */
11
+ readonly language: string;
12
+ /** The lines between the fences. */
13
+ readonly lines: readonly string[];
14
+ /** Where the block ends: the end of the closing fence line, or of the document. */
15
+ readonly end: number;
16
+ };
17
+
18
+ export type CodeSpan = { readonly start: number; readonly text: string };
19
+
20
+ const MIN_FENCE = 3;
21
+
22
+ type Opening = { readonly mark: string; readonly length: number };
23
+
24
+ /** The fence a code block's first line opens, with its info string; undefined for an indented block, which has none. */
25
+ export const fenceOf = (line: string): { readonly fence: Opening; readonly info: string } | undefined => {
26
+ const mark = line.charAt(0);
27
+ if (mark !== "`" && mark !== "~") return undefined;
28
+ const length = [...line].findIndex((char) => char !== mark);
29
+ const run = length === -1 ? line.length : length;
30
+ if (run < MIN_FENCE) return undefined;
31
+ const info = line.slice(run).trim();
32
+ // A backtick fence's info string has no backtick: ```js``` on one line is an inline span.
33
+ return mark === "`" && info.includes("`") ? undefined : { fence: { mark, length: run }, info };
34
+ };
35
+
36
+ /** The language an info string names: its first word, without braces or a leading dot ({r}, {.python}). */
37
+ export const languageOf = (info: string): string => {
38
+ const word = info.split(/\s/u)[0] ?? "";
39
+ const opened = word.startsWith("{") ? word.slice(1) : word;
40
+ const bare = opened.startsWith(".") ? opened.slice(1) : opened;
41
+ const cut = [...bare].findIndex((char) => char === "{" || char === "}" || char === ",");
42
+ return (cut === -1 ? bare : bare.slice(0, cut)).toLowerCase();
43
+ };
44
+
45
+ /** How many backticks run from `at`. */
46
+ const runAt = (text: string, at: number): number => {
47
+ const state = { end: at };
48
+ while (text.charAt(state.end) === "`") state.end += 1;
49
+ return state.end - at;
50
+ };
51
+
52
+ /** The fences of the last source read: the three rules that read fences parse a document once. */
53
+ const lastRead: { source: string | undefined; fences: readonly CodeFence[] } = { source: undefined, fences: [] };
54
+
55
+ const isQuoted = (source: string, offset: number): boolean =>
56
+ source
57
+ .slice(source.lastIndexOf("\n", offset - 1) + 1, offset)
58
+ .trimStart()
59
+ .startsWith(">");
60
+
61
+ /**
62
+ * Every fenced code block, in order, as the Markdown parser reads it: a fence in a list item is one, an indented code
63
+ * block (four spaces, no fence) is not, and an unclosed fence runs to the end. A fence inside a quotation is left out.
64
+ */
65
+ export const codeFences = (source: string): readonly CodeFence[] => {
66
+ if (lastRead.source === source) return lastRead.fences;
67
+ const fences: CodeFence[] = [];
68
+ eachPreOrder(parse(source), (node) => {
69
+ const span = node.type === "code" ? spanOf(node) : undefined;
70
+ if (span === undefined || isQuoted(source, span.start)) return;
71
+ const lineEnd = source.indexOf("\n", span.start);
72
+ // A fenced block starts at its fence; an indented block starts at its indentation, which is no fence.
73
+ const opening = fenceOf(source.slice(span.start, lineEnd === -1 ? source.length : lineEnd).trimEnd());
74
+ if (opening === undefined) return;
75
+ fences.push({ start: span.start, language: languageOf(opening.info), lines: (node.value ?? "").split("\n"), end: span.end });
76
+ });
77
+ lastRead.source = source;
78
+ lastRead.fences = fences;
79
+ return fences;
80
+ };
81
+
82
+ /** Inline code spans in a stretch of text: a run of backticks closed by a run of the same length. */
83
+ export const codeSpans = (text: string, offset = 0): CodeSpan[] => {
84
+ const spans: CodeSpan[] = [];
85
+ const state = { at: 0 };
86
+ while (state.at < text.length) {
87
+ const open = text.indexOf("`", state.at);
88
+ if (open === -1) break;
89
+ const length = runAt(text, open);
90
+ const fence = "`".repeat(length);
91
+ const close = findRun(text, fence, open + length);
92
+ if (close === -1) {
93
+ state.at = open + length;
94
+ } else {
95
+ spans.push({ start: offset + open, text: text.slice(open + length, close).trim() });
96
+ state.at = close + length;
97
+ }
98
+ }
99
+ return spans;
100
+ };
101
+
102
+ /** The next run of exactly this many backticks from `from`, or -1. */
103
+ const findRun = (text: string, run: string, from: number): number => {
104
+ const state = { at: from };
105
+ while (state.at < text.length) {
106
+ const found = text.indexOf(run, state.at);
107
+ if (found === -1) return -1;
108
+ if (text.charAt(found + run.length) !== "`" && text.charAt(found - 1) !== "`") return found;
109
+ state.at = found + run.length;
110
+ while (text.charAt(state.at) === "`") state.at += 1;
111
+ }
112
+ return -1;
113
+ };
@@ -0,0 +1,49 @@
1
+ import type { CrossDetector, DocumentFinding, ProseDocument, StructureNode } from "../plugin.ts";
2
+ import { inDocumentOrder } from "../structure/issues.ts";
3
+ import { definitionsOutOfStep, type DefinitionOutOfStep, type WrittenDefinition } from "../structure/cross-definitions.ts";
4
+ import { quoteAt } from "./structure-tree.ts";
5
+
6
+ // cross-doc-duplicate-definition reads definitions as duplicate-definition does (the tree's definition nodes), and compares
7
+ // what each file says after the term: 「X」とは、…をいう / "X" means ….
8
+
9
+ /**
10
+ * A definition that stands alone: not one held within an article or section (scope local), and not the inline kind
11
+ * (以下「X」という, ("X")), whose meaning is written before the term rather than after it.
12
+ */
13
+ const standsAlone = (node: StructureNode): boolean =>
14
+ node.kind === "definition" && node.attrs["scope"] !== "local" && node.attrs["placement"] !== "inline" && node.attrs["term"] !== undefined;
15
+
16
+ /** The language's words of a definition (をいう, means): the definition-statement word list. */
17
+ const statementWordsOf = (doc: ProseDocument): string[] => (doc.lexicons["definition-statement"] ?? []).map((entry) => entry.pattern);
18
+
19
+ /**
20
+ * What the sentence says after the term, up to its end. Empty when the sentence has none of the words of a definition:
21
+ * 「『AI が書いた』とは言いません」 names the term without defining it.
22
+ */
23
+ const bodyAfter = (doc: ProseDocument, node: StructureNode, statementWords: readonly string[]): string => {
24
+ const sentence = doc.sentences.find((candidate) => candidate.span.start <= node.span.start && node.span.start < candidate.span.end);
25
+ if (sentence === undefined || node.span.end > sentence.span.end) return "";
26
+ const text = doc.source.slice(sentence.span.start, sentence.span.end);
27
+ return statementWords.some((word) => text.includes(word)) ? doc.source.slice(node.span.end, sentence.span.end) : "";
28
+ };
29
+
30
+ const definitionsOf = (doc: ProseDocument, tree: StructureNode): WrittenDefinition[] => {
31
+ const statementWords = statementWordsOf(doc);
32
+ return inDocumentOrder(tree)
33
+ .filter(standsAlone)
34
+ .map((node) => ({ term: String(node.attrs["term"]), body: bodyAfter(doc, node, statementWords), offset: node.span.start }));
35
+ };
36
+
37
+ const findingOf = (byPath: ReadonlyMap<string, ProseDocument>, odd: DefinitionOutOfStep): DocumentFinding[] => {
38
+ const doc = byPath.get(odd.path);
39
+ if (doc === undefined) return [];
40
+ const values = { term: odd.definition.term, count: odd.files, example: odd.usual.path, offset: odd.definition.offset };
41
+ return [{ path: odd.path, finding: { rule: "", severity: "warning", line: 0, column: 0, quote: quoteAt(doc.source, odd.definition.offset), values } }];
42
+ };
43
+
44
+ /** A term defined in other words than in most files of the run that define it: reported in the files of the fewer wording. */
45
+ export const crossDocDuplicateDefinition: CrossDetector = (docs) => {
46
+ const files = docs.flatMap((doc) => (doc.structure === undefined ? [] : [{ path: doc.path, definitions: definitionsOf(doc, doc.structure) }]));
47
+ const byPath = new Map(docs.map((doc) => [doc.path, doc]));
48
+ return definitionsOutOfStep(files).flatMap((odd) => findingOf(byPath, odd));
49
+ };
@@ -0,0 +1,60 @@
1
+ import type { CrossDetector, DocumentFinding, ProseDocument, Span, StructureNode } from "../plugin.ts";
2
+ import { nameSpans } from "../compare/proper-nouns.ts";
3
+ import { overlapsAny, spanIndex } from "../compare/spans.ts";
4
+ import { factValues } from "../facts/fact-values.ts";
5
+ import { labelledFacts, type Fact } from "../facts/labelled-facts.ts";
6
+ import { tableFacts } from "../facts/table-facts.ts";
7
+ import { emailSpans, versionSpans } from "../facts/address-values.ts";
8
+ import { scopedFacts, type ScopedFact } from "../facts/fact-scope.ts";
9
+ import { factsOutOfStep, writtenValue, type FactOutOfStep } from "../facts/cross-facts.ts";
10
+ import { factWordsOf } from "./fact-consistency.ts";
11
+ import { quoteAt } from "./structure-tree.ts";
12
+
13
+ // cross-doc-fact-conflict reads facts as fact-conflict does (fact-consistency.ts), with two more kinds of value that a docs
14
+ // site repeats from page to page: an email address and a version of three parts or more (2.4.1, v3).
15
+
16
+ /** The proper nouns, and the addresses and versions, read as values named by what they are. A name inside an address is the address. */
17
+ const namedValues = (doc: ProseDocument): Span[] => {
18
+ const extra = [...emailSpans(doc.source), ...versionSpans(doc.source)];
19
+ const taken = spanIndex(extra);
20
+ return [...nameSpans(doc).filter((span) => !overlapsAny(taken, span)), ...extra];
21
+ };
22
+
23
+ const summaryHeadings = (doc: ProseDocument): string[] => (doc.lexicons["summary-heading"] ?? []).map((entry) => entry.pattern);
24
+
25
+ /** A colon right before the value, past emphasis marks: "Fee: $12", 「料金:1,200円」, "**Fee:** $12". */
26
+ const COLON_BEFORE = /[::][\s*_]*$/u;
27
+
28
+ /**
29
+ * A fact written as an entry: "Item: value", or a table cell. A fact in a sentence (「指摘は 3 件です」) is about what that
30
+ * page is saying, and the same words on another page are often about something else; within one file, fact-conflict reads them.
31
+ */
32
+ const isEntry = (source: string, fact: Fact): boolean => COLON_BEFORE.test(source.slice(source.lastIndexOf("\n", fact.value.start - 1) + 1, fact.value.start));
33
+
34
+ const factsOf = (doc: ProseDocument, tree: StructureNode): ScopedFact[] => {
35
+ const values = factValues(tree, doc.source, namedValues(doc));
36
+ const entries = labelledFacts(doc.source, values, factWordsOf(doc)).filter((fact) => isEntry(doc.source, fact));
37
+ return scopedFacts([...entries, ...tableFacts(doc.source, values)], tree, doc.source, summaryHeadings(doc));
38
+ };
39
+
40
+ const findingOf = (byPath: ReadonlyMap<string, ProseDocument>, odd: FactOutOfStep): DocumentFinding[] => {
41
+ const doc = byPath.get(odd.path);
42
+ const usualDoc = byPath.get(odd.usual.path);
43
+ if (doc === undefined || usualDoc === undefined) return [];
44
+ const values = {
45
+ label: odd.fact.label,
46
+ value: writtenValue(doc.source, odd.fact.value),
47
+ other: writtenValue(usualDoc.source, odd.usual.value.value),
48
+ count: odd.files,
49
+ example: odd.usual.path,
50
+ offset: odd.fact.value.start,
51
+ };
52
+ return [{ path: odd.path, finding: { rule: "", severity: "warning", line: 0, column: 0, quote: quoteAt(doc.source, odd.fact.value.start), values } }];
53
+ };
54
+
55
+ /** A labelled fact given one value in some files of the run and another in the rest: reported in the files of the fewer value. */
56
+ export const crossDocFactConflict: CrossDetector = (docs) => {
57
+ const files = docs.flatMap((doc) => (doc.structure === undefined ? [] : [{ path: doc.path, facts: factsOf(doc, doc.structure) }]));
58
+ const byPath = new Map(docs.map((doc) => [doc.path, doc]));
59
+ return factsOutOfStep(files).flatMap((odd) => findingOf(byPath, odd));
60
+ };
@@ -0,0 +1,4 @@
1
+ import type { CrossDetector } from "../../plugin.ts";
2
+ import { crossDocDuplicateDefinition } from "../cross-definitions.ts";
3
+
4
+ export const detector: CrossDetector = crossDocDuplicateDefinition;
@@ -0,0 +1,4 @@
1
+ import type { CrossDetector } from "../../plugin.ts";
2
+ import { crossDocFactConflict } from "../cross-facts.ts";
3
+
4
+ export const detector: CrossDetector = crossDocFactConflict;
@@ -0,0 +1,90 @@
1
+ import type { Detector, Finding, ProseDocument } from "../plugin.ts";
2
+ import { escapeRegExp } from "../orthography.ts";
3
+ import { formMinority } from "./form-minority.ts";
4
+ import { quoteAround } from "./quote-around.ts";
5
+
6
+ // 一つの文書で、同じ通貨の金額を二通りに書いた所(1,000円 と ¥1,000、$20 と 20 dollars)。どちらが正しいかは決めず、少ないほうを指す。
7
+ // 通貨の書き方、それを金額のどちら側に書くか、どの通貨か(group)は語彙表 currency-notation、桁の語は amount-multiplier が言う。
8
+
9
+ export type CurrencyForm = { readonly pattern: string; readonly position: "before" | "after"; readonly currency: string };
10
+
11
+ /** 金額 1 つ。form は書き方(側と記号)、currency はその通貨。 */
12
+ export type WrittenAmount = { readonly offset: number; readonly written: string; readonly form: string; readonly currency: string };
13
+
14
+ /** 少ないほうの書き方の金額と、多いほうの書き方の例、その通貨の金額の数。 */
15
+ export type CurrencyMinority = { readonly odd: WrittenAmount; readonly usual: string; readonly count: number; readonly of: number };
16
+
17
+ const AMOUNT = "[0-90-9]+(?:[,,][0-90-9]{3})*(?:[..][0-90-9]+)?";
18
+
19
+ const alternation = (words: readonly string[]): string => words.map(escapeRegExp).join("|");
20
+
21
+ /** 記号が英字で始まる・終わるなら、語の中(USDT、dollarsign)を読まないよう英字の切れ目を求める。 */
22
+ const latinEdge = (pattern: string, side: "start" | "end"): string => {
23
+ const edge = side === "start" ? pattern.charAt(0) : pattern.charAt(pattern.length - 1);
24
+ if (!/[A-Za-z]/u.test(edge)) return "";
25
+ return side === "start" ? "(?<![A-Za-z])" : "(?![A-Za-z])";
26
+ };
27
+
28
+ const amountPattern = (form: CurrencyForm, multipliers: readonly string[]): RegExp => {
29
+ const mark = escapeRegExp(form.pattern);
30
+ const multiplier = multipliers.length === 0 ? "" : `(?:\\s?(?:${alternation(multipliers)}))?`;
31
+ return form.position === "before"
32
+ ? new RegExp(`${latinEdge(form.pattern, "start")}${mark}\\s?${AMOUNT}${multiplier}(?![A-Za-z0-90-9])`, "gu")
33
+ : new RegExp(`(?<![\\w.,,0-9])${AMOUNT}${multiplier}\\s?${mark}${latinEdge(form.pattern, "end")}`, "gu");
34
+ };
35
+
36
+ const overlaps = (left: WrittenAmount, right: WrittenAmount): boolean =>
37
+ left.offset < right.offset + right.written.length && right.offset < left.offset + left.written.length;
38
+
39
+ /** 文字列の中の金額と、その書き方。重なる所に二つの書き方が当たれば、記号の長いほう(US$ と $ なら US$、米ドル と ドル なら 米ドル)。 */
40
+ export const amountsIn = (text: string, forms: readonly CurrencyForm[], multipliers: readonly string[]): WrittenAmount[] =>
41
+ forms
42
+ .toSorted((left, right) => right.pattern.length - left.pattern.length)
43
+ .flatMap((form) =>
44
+ [...text.matchAll(amountPattern(form, multipliers))].map((match) => ({
45
+ offset: match.index,
46
+ written: match[0],
47
+ form: `${form.position}:${form.pattern}`,
48
+ currency: form.currency,
49
+ })),
50
+ )
51
+ .reduce<WrittenAmount[]>((kept, amount) => (kept.some((other) => overlaps(other, amount)) ? kept : [...kept, amount]), [])
52
+ .toSorted((left, right) => left.offset - right.offset);
53
+
54
+ const minorityOf = (ofCurrency: readonly WrittenAmount[], limit: number): CurrencyMinority[] => {
55
+ const minority = formMinority(ofCurrency, limit);
56
+ if (minority === undefined) return [];
57
+ return minority.odd.map((amount) => ({ odd: amount, usual: minority.usual.written, count: minority.odd.length, of: ofCurrency.length }));
58
+ };
59
+
60
+ /**
61
+ * 通貨ごとに、多いほうと違う書き方の金額。少ないほうが limit パーセントを超えれば、使い分けている文書と読んで言わない。
62
+ * いちばん多い書き方が同じ数で並べば、どちらにも立たない。
63
+ */
64
+ export const currencyMinorities = (amounts: readonly WrittenAmount[], limit: number): CurrencyMinority[] =>
65
+ [...new Set(amounts.map((amount) => amount.currency))]
66
+ .flatMap((currency) =>
67
+ minorityOf(
68
+ amounts.filter((amount) => amount.currency === currency),
69
+ limit,
70
+ ),
71
+ )
72
+ .toSorted((left, right) => left.odd.offset - right.odd.offset);
73
+
74
+ const formsOf = (doc: ProseDocument): CurrencyForm[] =>
75
+ (doc.lexicons["currency-notation"] ?? []).flatMap((entry) =>
76
+ entry.position === undefined || entry.group === undefined ? [] : [{ pattern: entry.pattern, position: entry.position, currency: entry.group }],
77
+ );
78
+
79
+ export const currencyNotation: Detector = (doc, options): Finding[] => {
80
+ const text = doc.prose ?? doc.source;
81
+ const multipliers = (doc.lexicons["amount-multiplier"] ?? []).map((entry) => entry.pattern);
82
+ return currencyMinorities(amountsIn(text, formsOf(doc), multipliers), options.limit).map(({ odd, usual, count, of }) => ({
83
+ rule: "",
84
+ severity: "info",
85
+ line: 0,
86
+ column: 0,
87
+ quote: quoteAround(text, odd.offset, odd.offset + odd.written.length),
88
+ values: { written: odd.written.trim(), usual: usual.trim(), count, of, offset: odd.offset },
89
+ }));
90
+ };
@@ -1,4 +1,4 @@
1
- import type { Detector, Finding, Sentence, Span } from "../plugin.ts";
1
+ import type { Detector, DetectorOptions, Finding, Sentence, Span } from "../plugin.ts";
2
2
  import { occurrencesOutside } from "../orthography.ts";
3
3
  import { tokenRuns } from "../custom/token-pattern.ts";
4
4
  import { joinedView, type JoinedView } from "../joined-view.ts";
@@ -43,13 +43,21 @@ const withoutBreaks = (source: string, start: number, end: number, breaks: reado
43
43
  .toReversed()
44
44
  .reduce((text, span) => `${text.slice(0, span.start - start)}${text.slice(span.end - start)}`, source.slice(start, end));
45
45
 
46
+ /** A words rule's own words, or its word list's in the document's language: each entry's pattern, and its rewrite as the spelling to use. */
47
+ const pairsOf = (
48
+ spec: { readonly words: readonly { readonly avoid: string; readonly use: string }[] },
49
+ options: DetectorOptions,
50
+ ): readonly { avoid: string; use: string }[] =>
51
+ spec.words.length > 0 ? spec.words : (options.lexicon ?? []).map((entry) => ({ avoid: entry.pattern, use: entry.rewrite ?? "" }));
52
+
46
53
  /** words: each spelling to avoid, outside the spelling to use (「ユーザ」 inside 「ユーザー」 is not reported). */
47
54
  export const customWords: Detector = (doc, options): Finding[] => {
48
55
  const spec = options.custom;
49
56
  if (spec?.type !== "words") return [];
57
+ const pairs = pairsOf(spec, options).filter((pair) => pair.avoid !== "" && pair.avoid !== pair.use);
50
58
  return doc.sentences.flatMap((sentence) => {
51
59
  const view = readAsWritten(sentence);
52
- return spec.words.flatMap((pair) =>
60
+ return pairs.flatMap((pair) =>
53
61
  occurrencesOutside(view.text, pair.avoid, pair.use).map((start) => findingOf(hitAt(sentence, view, start, start + pair.avoid.length, pair.use))),
54
62
  );
55
63
  });
@@ -0,0 +1,74 @@
1
+ import type { Detector, Finding, Lexicon, Token } from "../plugin.ts";
2
+
3
+ /**
4
+ * 分詞で始めた句の後ろの主語が、形だけの it か there の文(Having reviewed the code, it is ready.)。
5
+ * 分詞の動作をしたのは書き手か読み手で、it ではない。読むのはこの形だけに絞り、主語が名詞の文は読まない。
6
+ * 読むのは -ing の分詞だけ。決まった言い方として読まれる分詞(Given, Based on, Generally speaking)は語彙表 dangling-participle の exempt が言う。
7
+ */
8
+
9
+ export type DanglingOpener = { readonly opener: string; readonly subject: Token; readonly offset: number };
10
+
11
+ /** 分詞の句として読む語の数の上限。長い句は読点の位置が当てにならない。 */
12
+ const MAX_OPENER_TOKENS = 12;
13
+ const BE = "be";
14
+ const HAVE = "have";
15
+ /** be の後ろで読点が来たら、it is … は挿入句(Testing the integration, it is worth noting, requires …)。 */
16
+ const PARENTHETICAL_REACH = 4;
17
+
18
+ const lower = (token: Token | undefined): string => token?.surface.toLowerCase() ?? "";
19
+
20
+ /** -ing の分詞だけ。過去分詞の句(Configured with SSO, it is ready.)は it そのものを言うことが多い。 */
21
+ const isPresentParticiple = (token: Token | undefined): boolean => token?.pos === "VERB" && token.features?.["VerbForm"] === "Ger";
22
+
23
+ const lemmaOf = (token: Token | undefined): string => token?.lemma?.toLowerCase() ?? "";
24
+
25
+ /** 文の頭が決まった言い方か。語彙表の語は空白で区切って、頭の語と順に比べる。 */
26
+ const opensWithExempt = (tokens: readonly Token[], exempt: readonly string[]): boolean =>
27
+ exempt.some((phrase) => phrase.split(/\s+/u).every((word, at) => lower(tokens[at]) === word));
28
+
29
+ /** 形だけの主語の後ろの be(it is / there are)、助動詞と be(it can be)、have と been(it has been)。 */
30
+ const isBeAfter = (tokens: readonly Token[], at: number): boolean => {
31
+ const verb = tokens[at];
32
+ if (lemmaOf(verb) === BE) return true;
33
+ return (verb?.pos === "AUX" || lemmaOf(verb) === HAVE) && lemmaOf(tokens[at + 1]) === BE;
34
+ };
35
+
36
+ const isParenthetical = (tokens: readonly Token[], at: number): boolean => tokens.slice(at, at + PARENTHETICAL_REACH).some((token) => token.surface === ",");
37
+
38
+ export const danglingOpenerIn = (
39
+ source: string,
40
+ tokens: readonly Token[],
41
+ subjects: ReadonlySet<string>,
42
+ exempt: readonly string[],
43
+ ): DanglingOpener | undefined => {
44
+ const [first] = tokens;
45
+ if (first === undefined || !isPresentParticiple(first) || opensWithExempt(tokens, exempt)) return undefined;
46
+ const comma = tokens.findIndex((token, at) => at > 0 && token.surface === ",");
47
+ const subject = tokens[comma + 1];
48
+ if (comma === -1 || comma > MAX_OPENER_TOKENS || subject === undefined || !subjects.has(lower(subject)) || !isBeAfter(tokens, comma + 2)) return undefined;
49
+ if (isParenthetical(tokens, comma + 3)) return undefined;
50
+ const last = tokens[comma - 1] ?? first;
51
+ return { opener: source.slice(first.span.start, last.span.end), subject, offset: first.span.start };
52
+ };
53
+
54
+ const patternsIn = (lexicon: Lexicon, group: string): string[] => lexicon.filter((entry) => entry.group === group).map((entry) => entry.pattern.toLowerCase());
55
+
56
+ export const danglingOpener: Detector = (doc): Finding[] => {
57
+ const words = doc.lexicons["dangling-participle"] ?? [];
58
+ const subjects = new Set(patternsIn(words, "subject"));
59
+ const exempt = patternsIn(words, "exempt");
60
+ return doc.sentences.flatMap((sentence) => {
61
+ const found = sentence.embeddedLanguage === undefined ? danglingOpenerIn(doc.source, sentence.tokens ?? [], subjects, exempt) : undefined;
62
+ if (found === undefined) return [];
63
+ return [
64
+ {
65
+ rule: "",
66
+ severity: "info",
67
+ line: 0,
68
+ column: 0,
69
+ quote: sentence.text.trim(),
70
+ values: { opener: found.opener, subject: found.subject.surface, offset: found.offset },
71
+ },
72
+ ];
73
+ });
74
+ };
@@ -0,0 +1,200 @@
1
+ // A defined term used later in another form, and a long name used again after its short name was defined. Pure. The
2
+ // definitions come from the document's structure tree (以下「甲」という, ("Seller")); which words may stand in a long
3
+ // name is read from the tokens' parts of speech.
4
+ import { definedTerms, usesOf, type BodyText, type DefinedTerm } from "../structure/definition-use.ts";
5
+ import { escapeRegExp } from "../orthography.ts";
6
+ import { quoteAt } from "./structure-tree.ts";
7
+ import type { Detector, Finding, ProseDocument, Sentence, Span, Token } from "../plugin.ts";
8
+
9
+ const bodyOf = (doc: ProseDocument): BodyText[] => doc.sentences.map((sentence) => ({ start: sentence.span.start, text: sentence.text }));
10
+
11
+ const finding = (doc: ProseDocument, rule: string, offset: number, variant: string, values: Readonly<Record<string, string | number>>): Finding => ({
12
+ rule,
13
+ severity: "info",
14
+ line: 0,
15
+ column: 0,
16
+ quote: quoteAt(doc.source, offset),
17
+ values: { ...values, offset },
18
+ variant,
19
+ });
20
+
21
+ const OPENING_QUOTES = new Set(["「", "『", '"', "“"]);
22
+ const CLOSING_QUOTES = new Set(["」", "』", '"', "”"]);
23
+ /** A quoted term right before these is defined again (「X」とは, "X" means), which duplicate-definition reads. */
24
+ const DEFINING_AFTER = /^(?:とは|という|\s+(?:means|shall mean|refers to|has the meaning))/u;
25
+ /** The ending a use may carry past the term: a plural or a possessive (Services, Service's). */
26
+ const WORD_ENDING = /^(?:e?s|['’]s?)/u;
27
+
28
+ /** The first definition of each term, with the spans of all its definitions. */
29
+ const firstDefinitions = (terms: readonly DefinedTerm[]): { readonly term: DefinedTerm; readonly spans: readonly Span[] }[] => {
30
+ const groups = new Map<string, { term: DefinedTerm; spans: Span[] }>();
31
+ terms.forEach((term) => {
32
+ const group = groups.get(term.term);
33
+ if (group === undefined) groups.set(term.term, { term, spans: [term.span] });
34
+ else group.spans.push(term.span);
35
+ });
36
+ return [...groups.values()];
37
+ };
38
+
39
+ /** A use of the term set in quotes again after its definition: 「本サービス」 where 本サービス was defined. */
40
+ export const quotedUses = (source: string, term: string, uses: readonly number[], definedAt: number): number[] =>
41
+ uses.filter((offset) => {
42
+ const after = offset + term.length + (WORD_ENDING.exec(source.slice(offset + term.length, offset + term.length + 2))?.[0].length ?? 0);
43
+ return (
44
+ offset > definedAt &&
45
+ OPENING_QUOTES.has(source.charAt(offset - 1)) &&
46
+ CLOSING_QUOTES.has(source.charAt(after)) &&
47
+ !DEFINING_AFTER.test(source.slice(after + 1, after + 20))
48
+ );
49
+ });
50
+
51
+ const UPPER_START = /^\p{Lu}/u;
52
+
53
+ /**
54
+ * The uses of a capitalised term written in lower case ("services" where "Services" is defined), when they are the fewer
55
+ * and at most limit: a document that mostly writes it in lower case uses the word in its ordinary sense on purpose.
56
+ */
57
+ export const lowerCaseUses = (source: string, term: string, uses: readonly number[], definedAt: number, limit: number): number[] => {
58
+ if (!UPPER_START.test(term) || term.toLowerCase() === term) return [];
59
+ const later = uses.filter((offset) => offset > definedAt);
60
+ const lower = later.filter((offset) => source.charAt(offset) === term.charAt(0).toLowerCase() && !isSentenceStart(source, offset));
61
+ const capital = later.filter((offset) => source.charAt(offset) === term.charAt(0));
62
+ return lower.length > 0 && lower.length < capital.length && lower.length <= limit ? lower : [];
63
+ };
64
+
65
+ /** How far back a sentence's start is looked for: past a few spaces to the mark before them. */
66
+ const LOOK_BACK = 40;
67
+ const SENTENCE_END = new Set([".", "!", "?", ":"]);
68
+
69
+ /** A sentence's first word is capitalised whatever it is, so only a lower-case use inside a sentence counts. */
70
+ const isSentenceStart = (source: string, offset: number): boolean => {
71
+ const before = source.slice(Math.max(0, offset - LOOK_BACK), offset);
72
+ const text = before.trimEnd();
73
+ return (offset < LOOK_BACK && text === "") || before.slice(text.length).includes("\n") || SENTENCE_END.has(text.at(-1) ?? "");
74
+ };
75
+
76
+ /** Where the sentence holding the definition ends: a use inside it ("Customer" means the party named "Customer") belongs to the definition. */
77
+ const definingSentenceEnd = (doc: ProseDocument, definition: Span): number =>
78
+ doc.sentences.find((sentence) => sentence.span.start <= definition.start && definition.start < sentence.span.end)?.span.end ?? definition.end;
79
+
80
+ const VERBAL = new Set(["VERB", "AUX"]);
81
+
82
+ /** A word used as a verb ("you input"), not the defined thing ("the Input"): a different word that shares the spelling. */
83
+ const isVerbAt = (doc: ProseDocument, offset: number): boolean => {
84
+ const tokens = doc.sentences.find((candidate) => candidate.span.start <= offset && offset < candidate.span.end)?.tokens ?? [];
85
+ const at = tokens.findIndex((candidate) => candidate.span.start <= offset && offset < candidate.span.end);
86
+ const token = tokens[at];
87
+ if (token === undefined) return false;
88
+ // After a subject pronoun the word is its verb (you input), whatever the tagger says; after a possessive (your input) it is a noun.
89
+ const before = tokens[at - 1];
90
+ const afterSubject = before?.pos === "PRON" && before.features?.["Poss"] !== "Yes";
91
+ return VERBAL.has(token.pos) || afterSubject;
92
+ };
93
+
94
+ export const definedTermForm: Detector = (doc, options): Finding[] => {
95
+ if (doc.structure === undefined) return [];
96
+ const texts = bodyOf(doc);
97
+ return firstDefinitions(definedTerms(doc.structure)).flatMap(({ term, spans }) => {
98
+ const uses = usesOf(term.term, texts, spans, "loose");
99
+ const definingEnd = definingSentenceEnd(doc, term.span);
100
+ const quoted = quotedUses(doc.source, term.term, uses, definingEnd).map((offset) =>
101
+ finding(doc, "defined-term-form", offset, "quoted", { term: term.term, line: term.line }),
102
+ );
103
+ const nounUses = uses.filter((offset) => !isVerbAt(doc, offset));
104
+ const lower = lowerCaseUses(doc.source, term.term, nounUses, definingEnd, options.limit).map((offset) =>
105
+ finding(doc, "defined-term-form", offset, "case", { term: term.term, line: term.line }),
106
+ );
107
+ return [...quoted, ...lower];
108
+ });
109
+ };
110
+
111
+ /** The words a long name is made of: nouns and names (株式会社 みなと 製作所, Acme Corporation), and the marks inside one. */
112
+ const NAME_PARTS = new Set(["NOUN", "PROPN", "NUM"]);
113
+ const NAME_MARKS = new Set(["・", "&", "&", ".", ","]);
114
+ /** A long name shorter than this (法律, Customer) is an ordinary word, used in its own sense again. */
115
+ const MIN_NAME_CHARS = 4;
116
+ /** In English a long name has two words or more (Acme Corporation); one capitalised word is the term's own kind of word. */
117
+ const MIN_NAME_WORDS = 2;
118
+ const LATIN = /\p{Script=Latin}/u;
119
+
120
+ const isNamePart = (token: Token): boolean => NAME_PARTS.has(token.pos) || NAME_MARKS.has(token.surface);
121
+
122
+ /** The long name written right before an inline definition: the run of name words that ends at its opening bracket. */
123
+ export const nameBefore = (sentence: Sentence, bracket: number): string | undefined => {
124
+ const tokens = (sentence.tokens ?? []).filter((token) => token.span.end <= bracket);
125
+ const firstOfRun = tokens.findLastIndex((token) => !isNamePart(token)) + 1;
126
+ if (isModifier(tokens[firstOfRun - 1])) return undefined;
127
+ const run = tokens.slice(firstOfRun).filter((token, index, all) => index > 0 || !NAME_MARKS.has(token.surface) || all.length === 1);
128
+ const first = run[0];
129
+ const end = run.at(-1);
130
+ if (first === undefined || end === undefined || bracket - end.span.end > 1) return undefined;
131
+ const name = withoutTrailingMarks(sentence.text.slice(first.span.start - sentence.span.start, end.span.end - sentence.span.start));
132
+ const words = name.split(/\s+/u).length;
133
+ return [...name].length < MIN_NAME_CHARS || (LATIN.test(name) && words < MIN_NAME_WORDS) ? undefined : name;
134
+ };
135
+
136
+ /**
137
+ * A word that narrows the name before it: an article, an adjective, a verb, or の (妊娠中の女性従業員, the Beta Preview, 本利用規約).
138
+ * The words then name a kind of thing the definition picks out, not one party, and are used in their own sense again.
139
+ */
140
+ const MODIFIERS = new Set(["DET", "ADJ", "VERB", "AUX"]);
141
+ const isModifier = (token: Token | undefined): boolean => token !== undefined && (MODIFIERS.has(token.pos) || token.surface === "の");
142
+
143
+ const TRAILING_MARKS = new Set([",", ".", " "]);
144
+
145
+ const withoutTrailingMarks = (text: string): string => {
146
+ const chars = [...text];
147
+ return chars.slice(0, chars.findLastIndex((char) => !TRAILING_MARKS.has(char)) + 1).join("");
148
+ };
149
+
150
+ /** Where the inline definition's bracket opens: (以下「甲」という。) or ("Seller"). */
151
+ const bracketBefore = (source: string, definition: Span): number => {
152
+ const before = source.slice(Math.max(0, definition.start - 2), definition.start + 1);
153
+ const at = Math.max(before.lastIndexOf("("), before.lastIndexOf("("));
154
+ return at === -1 ? -1 : Math.max(0, definition.start - 2) + at;
155
+ };
156
+
157
+ export type RepeatedName = { readonly offset: number; readonly name: string; readonly term: string; readonly line: number };
158
+
159
+ /** A line that holds the short name too (甲 株式会社みなと) or little else than the long name: a signature block names both. */
160
+ const isNameLine = (source: string, offset: number, name: string, term: string): boolean => {
161
+ const start = source.lastIndexOf("\n", offset - 1) + 1;
162
+ const end = source.indexOf("\n", offset);
163
+ const line = source.slice(start, end === -1 ? source.length : end).trim();
164
+ const SLACK = 12;
165
+ return line.includes(term) || [...line].length <= [...name].length + SLACK;
166
+ };
167
+
168
+ /** A name inside a longer word (東京大学 in 東京大学大学院, Acme in Acmeware) is part of another name. */
169
+ const WORD_CHAR = /[\p{Script=Han}\p{Script=Katakana}\p{Script=Latin}\p{N}ー]/u;
170
+ const standsAlone = (source: string, offset: number, name: string): boolean =>
171
+ !WORD_CHAR.test(source.charAt(offset - 1)) && !WORD_CHAR.test(source.charAt(offset + name.length));
172
+
173
+ /**
174
+ * Each use of a long name after the definition gave it a short name, outside a signature line. A name that is itself a
175
+ * defined term (during each Subscription Term ("Permitted Use")) is a clause's last words, not the thing being named.
176
+ */
177
+ export const repeatedNames = (doc: ProseDocument, terms: readonly DefinedTerm[]): RepeatedName[] => {
178
+ const defined = new Set(terms.map((term) => term.term));
179
+ return terms
180
+ .filter((term) => term.inline)
181
+ .flatMap((term) => {
182
+ const bracket = bracketBefore(doc.source, term.span);
183
+ const sentence = doc.sentences.find((candidate) => candidate.span.start <= bracket && bracket < candidate.span.end);
184
+ const name = sentence === undefined || bracket === -1 ? undefined : nameBefore(sentence, bracket);
185
+ if (name === undefined || name === term.term || term.term.includes(name) || defined.has(name)) return [];
186
+ const pattern = new RegExp(escapeRegExp(name), "gu");
187
+ return doc.sentences
188
+ .filter((later) => later.span.start > term.span.end)
189
+ .flatMap((later) => [...later.text.matchAll(pattern)].map((match) => later.span.start + match.index))
190
+ .filter((offset) => standsAlone(doc.source, offset, name) && !isNameLine(doc.source, offset, name, term.term))
191
+ .map((offset) => ({ offset, name, term: term.term, line: term.line }));
192
+ });
193
+ };
194
+
195
+ export const definedNameRepeated: Detector = (doc): Finding[] =>
196
+ doc.structure === undefined
197
+ ? []
198
+ : repeatedNames(doc, definedTerms(doc.structure)).map((repeated) =>
199
+ finding(doc, "defined-name-repeated", repeated.offset, "name", { name: repeated.name, term: repeated.term, line: repeated.line }),
200
+ );