chaffjs 0.20.0 → 0.21.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 (567) hide show
  1. package/README.md +5 -4
  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 +14 -5
  8. package/dist/cli-text.js.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +4 -0
  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/config/load.d.ts +2 -0
  26. package/dist/config/load.d.ts.map +1 -1
  27. package/dist/config/load.js +1 -0
  28. package/dist/config/load.js.map +1 -1
  29. package/dist/config/option-problems.js +2 -2
  30. package/dist/config/option-problems.js.map +1 -1
  31. package/dist/config/rule-problems.js +2 -2
  32. package/dist/config/rule-problems.js.map +1 -1
  33. package/dist/custom/checked.d.ts +9 -0
  34. package/dist/custom/checked.d.ts.map +1 -0
  35. package/dist/custom/checked.js +3 -0
  36. package/dist/custom/checked.js.map +1 -0
  37. package/dist/custom/dsl-fields.d.ts +27 -0
  38. package/dist/custom/dsl-fields.d.ts.map +1 -0
  39. package/dist/custom/dsl-fields.js +86 -0
  40. package/dist/custom/dsl-fields.js.map +1 -0
  41. package/dist/custom/load.d.ts.map +1 -1
  42. package/dist/custom/load.js +2 -0
  43. package/dist/custom/load.js.map +1 -1
  44. package/dist/custom/parse.d.ts +14 -3
  45. package/dist/custom/parse.d.ts.map +1 -1
  46. package/dist/custom/parse.js +44 -40
  47. package/dist/custom/parse.js.map +1 -1
  48. package/dist/custom/problems.d.ts.map +1 -1
  49. package/dist/custom/problems.js +11 -1
  50. package/dist/custom/problems.js.map +1 -1
  51. package/dist/detectors/blank-structure.d.ts.map +1 -1
  52. package/dist/detectors/blank-structure.js +6 -4
  53. package/dist/detectors/blank-structure.js.map +1 -1
  54. package/dist/detectors/clause-shape.d.ts +22 -0
  55. package/dist/detectors/clause-shape.d.ts.map +1 -0
  56. package/dist/detectors/clause-shape.js +149 -0
  57. package/dist/detectors/clause-shape.js.map +1 -0
  58. package/dist/detectors/code-fences.d.ts +34 -0
  59. package/dist/detectors/code-fences.d.ts.map +1 -0
  60. package/dist/detectors/code-fences.js +100 -0
  61. package/dist/detectors/code-fences.js.map +1 -0
  62. package/dist/detectors/cross-definitions.d.ts +4 -0
  63. package/dist/detectors/cross-definitions.d.ts.map +1 -0
  64. package/dist/detectors/cross-definitions.js +43 -0
  65. package/dist/detectors/cross-definitions.js.map +1 -0
  66. package/dist/detectors/cross-facts.d.ts +4 -0
  67. package/dist/detectors/cross-facts.d.ts.map +1 -0
  68. package/dist/detectors/cross-facts.js +53 -0
  69. package/dist/detectors/cross-facts.js.map +1 -0
  70. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts +3 -0
  71. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts.map +1 -0
  72. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js +3 -0
  73. package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js.map +1 -0
  74. package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts +3 -0
  75. package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts.map +1 -0
  76. package/dist/detectors/cross-registry/cross-doc-fact-conflict.js +3 -0
  77. package/dist/detectors/cross-registry/cross-doc-fact-conflict.js.map +1 -0
  78. package/dist/detectors/custom.d.ts.map +1 -1
  79. package/dist/detectors/custom.js +4 -1
  80. package/dist/detectors/custom.js.map +1 -1
  81. package/dist/detectors/defined-term-form.d.ts +25 -0
  82. package/dist/detectors/defined-term-form.d.ts.map +1 -0
  83. package/dist/detectors/defined-term-form.js +170 -0
  84. package/dist/detectors/defined-term-form.js.map +1 -0
  85. package/dist/detectors/email-letter.d.ts +46 -0
  86. package/dist/detectors/email-letter.d.ts.map +1 -0
  87. package/dist/detectors/email-letter.js +234 -0
  88. package/dist/detectors/email-letter.js.map +1 -0
  89. package/dist/detectors/impossible-date.d.ts +20 -0
  90. package/dist/detectors/impossible-date.d.ts.map +1 -0
  91. package/dist/detectors/impossible-date.js +105 -0
  92. package/dist/detectors/impossible-date.js.map +1 -0
  93. package/dist/detectors/impossible-time.d.ts +18 -0
  94. package/dist/detectors/impossible-time.d.ts.map +1 -0
  95. package/dist/detectors/impossible-time.js +116 -0
  96. package/dist/detectors/impossible-time.js.map +1 -0
  97. package/dist/detectors/link-shape.d.ts +11 -0
  98. package/dist/detectors/link-shape.d.ts.map +1 -0
  99. package/dist/detectors/link-shape.js +56 -0
  100. package/dist/detectors/link-shape.js.map +1 -0
  101. package/dist/detectors/mark-shape.d.ts +53 -0
  102. package/dist/detectors/mark-shape.d.ts.map +1 -0
  103. package/dist/detectors/mark-shape.js +160 -0
  104. package/dist/detectors/mark-shape.js.map +1 -0
  105. package/dist/detectors/registry/adverb-polarity.d.ts +3 -0
  106. package/dist/detectors/registry/adverb-polarity.d.ts.map +1 -0
  107. package/dist/detectors/registry/adverb-polarity.js +3 -0
  108. package/dist/detectors/registry/adverb-polarity.js.map +1 -0
  109. package/dist/detectors/registry/attachment-missing.d.ts +3 -0
  110. package/dist/detectors/registry/attachment-missing.d.ts.map +1 -0
  111. package/dist/detectors/registry/attachment-missing.js +3 -0
  112. package/dist/detectors/registry/attachment-missing.js.map +1 -0
  113. package/dist/detectors/registry/bare-url-mix.d.ts +3 -0
  114. package/dist/detectors/registry/bare-url-mix.d.ts.map +1 -0
  115. package/dist/detectors/registry/bare-url-mix.js +3 -0
  116. package/dist/detectors/registry/bare-url-mix.js.map +1 -0
  117. package/dist/detectors/registry/caps-shouting.d.ts +3 -0
  118. package/dist/detectors/registry/caps-shouting.d.ts.map +1 -0
  119. package/dist/detectors/registry/caps-shouting.js +3 -0
  120. package/dist/detectors/registry/caps-shouting.js.map +1 -0
  121. package/dist/detectors/registry/code-fence-language.d.ts +3 -0
  122. package/dist/detectors/registry/code-fence-language.d.ts.map +1 -0
  123. package/dist/detectors/registry/code-fence-language.js +3 -0
  124. package/dist/detectors/registry/code-fence-language.js.map +1 -0
  125. package/dist/detectors/registry/defined-name-repeated.d.ts +3 -0
  126. package/dist/detectors/registry/defined-name-repeated.d.ts.map +1 -0
  127. package/dist/detectors/registry/defined-name-repeated.js +3 -0
  128. package/dist/detectors/registry/defined-name-repeated.js.map +1 -0
  129. package/dist/detectors/registry/defined-term-form.d.ts +3 -0
  130. package/dist/detectors/registry/defined-term-form.d.ts.map +1 -0
  131. package/dist/detectors/registry/defined-term-form.js +3 -0
  132. package/dist/detectors/registry/defined-term-form.js.map +1 -0
  133. package/dist/detectors/registry/email-frame.d.ts +3 -0
  134. package/dist/detectors/registry/email-frame.d.ts.map +1 -0
  135. package/dist/detectors/registry/email-frame.js +3 -0
  136. package/dist/detectors/registry/email-frame.js.map +1 -0
  137. package/dist/detectors/registry/email-subject.d.ts +3 -0
  138. package/dist/detectors/registry/email-subject.d.ts.map +1 -0
  139. package/dist/detectors/registry/email-subject.js +3 -0
  140. package/dist/detectors/registry/email-subject.js.map +1 -0
  141. package/dist/detectors/registry/ending-run.d.ts +3 -0
  142. package/dist/detectors/registry/ending-run.d.ts.map +1 -0
  143. package/dist/detectors/registry/ending-run.js +3 -0
  144. package/dist/detectors/registry/ending-run.js.map +1 -0
  145. package/dist/detectors/registry/heading-command.d.ts +3 -0
  146. package/dist/detectors/registry/heading-command.d.ts.map +1 -0
  147. package/dist/detectors/registry/heading-command.js +3 -0
  148. package/dist/detectors/registry/heading-command.js.map +1 -0
  149. package/dist/detectors/registry/hyphen-dash.d.ts +3 -0
  150. package/dist/detectors/registry/hyphen-dash.d.ts.map +1 -0
  151. package/dist/detectors/registry/hyphen-dash.js +3 -0
  152. package/dist/detectors/registry/hyphen-dash.js.map +1 -0
  153. package/dist/detectors/registry/image-file-name-alt.d.ts +3 -0
  154. package/dist/detectors/registry/image-file-name-alt.d.ts.map +1 -0
  155. package/dist/detectors/registry/image-file-name-alt.js +3 -0
  156. package/dist/detectors/registry/image-file-name-alt.js.map +1 -0
  157. package/dist/detectors/registry/impossible-date.d.ts +3 -0
  158. package/dist/detectors/registry/impossible-date.d.ts.map +1 -0
  159. package/dist/detectors/registry/impossible-date.js +3 -0
  160. package/dist/detectors/registry/impossible-date.js.map +1 -0
  161. package/dist/detectors/registry/impossible-time.d.ts +3 -0
  162. package/dist/detectors/registry/impossible-time.d.ts.map +1 -0
  163. package/dist/detectors/registry/impossible-time.js +3 -0
  164. package/dist/detectors/registry/impossible-time.js.map +1 -0
  165. package/dist/detectors/registry/latin-abbreviation.d.ts +3 -0
  166. package/dist/detectors/registry/latin-abbreviation.d.ts.map +1 -0
  167. package/dist/detectors/registry/latin-abbreviation.js +3 -0
  168. package/dist/detectors/registry/latin-abbreviation.js.map +1 -0
  169. package/dist/detectors/registry/link-text-url-mismatch.d.ts +3 -0
  170. package/dist/detectors/registry/link-text-url-mismatch.d.ts.map +1 -0
  171. package/dist/detectors/registry/link-text-url-mismatch.js +3 -0
  172. package/dist/detectors/registry/link-text-url-mismatch.js.map +1 -0
  173. package/dist/detectors/registry/mark-consistency.d.ts +3 -0
  174. package/dist/detectors/registry/mark-consistency.d.ts.map +1 -0
  175. package/dist/detectors/registry/mark-consistency.js +3 -0
  176. package/dist/detectors/registry/mark-consistency.js.map +1 -0
  177. package/dist/detectors/registry/origin-particle.d.ts +3 -0
  178. package/dist/detectors/registry/origin-particle.d.ts.map +1 -0
  179. package/dist/detectors/registry/origin-particle.js +3 -0
  180. package/dist/detectors/registry/origin-particle.js.map +1 -0
  181. package/dist/detectors/registry/request-without-deadline.d.ts +3 -0
  182. package/dist/detectors/registry/request-without-deadline.d.ts.map +1 -0
  183. package/dist/detectors/registry/request-without-deadline.js +3 -0
  184. package/dist/detectors/registry/request-without-deadline.js.map +1 -0
  185. package/dist/detectors/registry/sentence-spacing.d.ts +3 -0
  186. package/dist/detectors/registry/sentence-spacing.d.ts.map +1 -0
  187. package/dist/detectors/registry/sentence-spacing.js +3 -0
  188. package/dist/detectors/registry/sentence-spacing.js.map +1 -0
  189. package/dist/detectors/registry/shell-prompt.d.ts +3 -0
  190. package/dist/detectors/registry/shell-prompt.d.ts.map +1 -0
  191. package/dist/detectors/registry/shell-prompt.js +3 -0
  192. package/dist/detectors/registry/shell-prompt.js.map +1 -0
  193. package/dist/detectors/registry/step-statement.d.ts +3 -0
  194. package/dist/detectors/registry/step-statement.d.ts.map +1 -0
  195. package/dist/detectors/registry/step-statement.js +3 -0
  196. package/dist/detectors/registry/step-statement.js.map +1 -0
  197. package/dist/detectors/registry/unpaired-parallel.d.ts +3 -0
  198. package/dist/detectors/registry/unpaired-parallel.d.ts.map +1 -0
  199. package/dist/detectors/registry/unpaired-parallel.js +3 -0
  200. package/dist/detectors/registry/unpaired-parallel.js.map +1 -0
  201. package/dist/detectors/request-owner.d.ts +23 -0
  202. package/dist/detectors/request-owner.d.ts.map +1 -0
  203. package/dist/detectors/request-owner.js +110 -0
  204. package/dist/detectors/request-owner.js.map +1 -0
  205. package/dist/detectors/technical-docs.d.ts +32 -0
  206. package/dist/detectors/technical-docs.d.ts.map +1 -0
  207. package/dist/detectors/technical-docs.js +199 -0
  208. package/dist/detectors/technical-docs.js.map +1 -0
  209. package/dist/extension/pack-read.d.ts +21 -0
  210. package/dist/extension/pack-read.d.ts.map +1 -0
  211. package/dist/extension/pack-read.js +117 -0
  212. package/dist/extension/pack-read.js.map +1 -0
  213. package/dist/extension/plugin-load.d.ts.map +1 -1
  214. package/dist/extension/plugin-load.js +44 -10
  215. package/dist/extension/plugin-load.js.map +1 -1
  216. package/dist/extension/plugin-parse.d.ts.map +1 -1
  217. package/dist/extension/plugin-parse.js +10 -3
  218. package/dist/extension/plugin-parse.js.map +1 -1
  219. package/dist/facts/address-values.d.ts +5 -0
  220. package/dist/facts/address-values.d.ts.map +1 -0
  221. package/dist/facts/address-values.js +64 -0
  222. package/dist/facts/address-values.js.map +1 -0
  223. package/dist/facts/cross-facts.d.ts +20 -0
  224. package/dist/facts/cross-facts.d.ts.map +1 -0
  225. package/dist/facts/cross-facts.js +41 -0
  226. package/dist/facts/cross-facts.js.map +1 -0
  227. package/dist/fix-plan/chosen-depth.d.ts +10 -0
  228. package/dist/fix-plan/chosen-depth.d.ts.map +1 -0
  229. package/dist/fix-plan/chosen-depth.js +24 -0
  230. package/dist/fix-plan/chosen-depth.js.map +1 -0
  231. package/dist/fix-plan/mode.d.ts +13 -4
  232. package/dist/fix-plan/mode.d.ts.map +1 -1
  233. package/dist/fix-plan/mode.js +38 -7
  234. package/dist/fix-plan/mode.js.map +1 -1
  235. package/dist/fix-plan/plan.d.ts +20 -0
  236. package/dist/fix-plan/plan.d.ts.map +1 -1
  237. package/dist/fix-plan/plan.js +23 -5
  238. package/dist/fix-plan/plan.js.map +1 -1
  239. package/dist/fix-plan/render.d.ts.map +1 -1
  240. package/dist/fix-plan/render.js +24 -0
  241. package/dist/fix-plan/render.js.map +1 -1
  242. package/dist/fix-plan/text.d.ts +8 -0
  243. package/dist/fix-plan/text.d.ts.map +1 -1
  244. package/dist/fix-plan/text.js +36 -10
  245. package/dist/fix-plan/text.js.map +1 -1
  246. package/dist/genre-load.d.ts +1 -1
  247. package/dist/genre-load.d.ts.map +1 -1
  248. package/dist/genre-load.js +7 -4
  249. package/dist/genre-load.js.map +1 -1
  250. package/dist/genre-parse.d.ts +6 -0
  251. package/dist/genre-parse.d.ts.map +1 -1
  252. package/dist/genre-parse.js +18 -0
  253. package/dist/genre-parse.js.map +1 -1
  254. package/dist/init.js +10 -10
  255. package/dist/init.js.map +1 -1
  256. package/dist/known-genres.d.ts +8 -0
  257. package/dist/known-genres.d.ts.map +1 -0
  258. package/dist/known-genres.js +17 -0
  259. package/dist/known-genres.js.map +1 -0
  260. package/dist/link-shape.d.ts +20 -0
  261. package/dist/link-shape.d.ts.map +1 -0
  262. package/dist/link-shape.js +58 -0
  263. package/dist/link-shape.js.map +1 -0
  264. package/dist/reasons.d.ts.map +1 -1
  265. package/dist/reasons.js +15 -4
  266. package/dist/reasons.js.map +1 -1
  267. package/dist/render/explain.d.ts.map +1 -1
  268. package/dist/render/explain.js +11 -2
  269. package/dist/render/explain.js.map +1 -1
  270. package/dist/render/friendly.js +1 -1
  271. package/dist/render/friendly.js.map +1 -1
  272. package/dist/render/rules-json.d.ts.map +1 -1
  273. package/dist/render/rules-json.js +12 -3
  274. package/dist/render/rules-json.js.map +1 -1
  275. package/dist/render/rules-table.js +6 -6
  276. package/dist/render/rules-table.js.map +1 -1
  277. package/dist/render/semantic.js +2 -2
  278. package/dist/render/semantic.js.map +1 -1
  279. package/dist/render/suppressions.js +2 -2
  280. package/dist/render/suppressions.js.map +1 -1
  281. package/dist/rewrite-depth.d.ts +22 -0
  282. package/dist/rewrite-depth.d.ts.map +1 -0
  283. package/dist/rewrite-depth.js +42 -0
  284. package/dist/rewrite-depth.js.map +1 -0
  285. package/dist/rule-fields.d.ts +23 -0
  286. package/dist/rule-fields.d.ts.map +1 -0
  287. package/dist/rule-fields.js +64 -0
  288. package/dist/rule-fields.js.map +1 -0
  289. package/dist/rule-guide.d.ts +3 -0
  290. package/dist/rule-guide.d.ts.map +1 -1
  291. package/dist/rule-guide.js +6 -0
  292. package/dist/rule-guide.js.map +1 -1
  293. package/dist/rule-load.d.ts.map +1 -1
  294. package/dist/rule-load.js +13 -5
  295. package/dist/rule-load.js.map +1 -1
  296. package/dist/rule-offs.d.ts +14 -0
  297. package/dist/rule-offs.d.ts.map +1 -0
  298. package/dist/rule-offs.js +45 -0
  299. package/dist/rule-offs.js.map +1 -0
  300. package/dist/structure/build.d.ts.map +1 -1
  301. package/dist/structure/build.js +14 -1
  302. package/dist/structure/build.js.map +1 -1
  303. package/dist/structure/cross-definitions.d.ts +23 -0
  304. package/dist/structure/cross-definitions.d.ts.map +1 -0
  305. package/dist/structure/cross-definitions.js +34 -0
  306. package/dist/structure/cross-definitions.js.map +1 -0
  307. package/genres.yaml +14 -313
  308. package/package.json +3 -3
  309. package/rules/absolute-exception.yaml +3 -1
  310. package/rules/abstract-length.yaml +7 -1
  311. package/rules/acronym-expansion-conflict.yaml +2 -1
  312. package/rules/adverb-overuse.yaml +7 -1
  313. package/rules/adverb-without-negation.yaml +60 -0
  314. package/rules/adversative-ga-repeat.yaml +2 -1
  315. package/rules/agentless-passive.yaml +10 -1
  316. package/rules/agreement-slip.yaml +4 -1
  317. package/rules/ai-generated-composite.yaml +6 -1
  318. package/rules/ai-structure.yaml +8 -2
  319. package/rules/ai-tell.yaml +7 -1
  320. package/rules/all-caps-shouting.yaml +61 -0
  321. package/rules/announced-count-mismatch.yaml +3 -1
  322. package/rules/announcing-opener.yaml +7 -1
  323. package/rules/article-sound.yaml +6 -2
  324. package/rules/assistant-residue.yaml +4 -1
  325. package/rules/attachment-not-attached.yaml +112 -0
  326. package/rules/baito-keigo.yaml +64 -0
  327. package/rules/bare-url-mix.yaml +86 -0
  328. package/rules/bold-density.yaml +1 -0
  329. package/rules/bold-label-list.yaml +9 -1
  330. package/rules/bracket-nesting.yaml +2 -1
  331. package/rules/bracket-width-consistency.yaml +64 -0
  332. package/rules/broken-link.yaml +1 -1
  333. package/rules/chat-citation-residue.yaml +2 -1
  334. package/rules/citation-reference-mismatch.yaml +7 -1
  335. package/rules/citation-style-mix.yaml +7 -1
  336. package/rules/clause-chain.yaml +6 -1
  337. package/rules/closing-cliche.yaml +7 -0
  338. package/rules/code-fence-language-mix.yaml +176 -0
  339. package/rules/colloquial-opener.yaml +62 -0
  340. package/rules/colon-lead-in.yaml +7 -1
  341. package/rules/concrete-evidence-density.yaml +6 -0
  342. package/rules/contraction-consistency.yaml +4 -1
  343. package/rules/contrast-framing.yaml +7 -1
  344. package/rules/cross-doc-duplicate-definition.yaml +70 -0
  345. package/rules/cross-doc-fact-conflict.yaml +64 -0
  346. package/rules/cushion-phrase-density.yaml +8 -1
  347. package/rules/dangling-figure-reference.yaml +3 -1
  348. package/rules/dangling-reference.yaml +3 -1
  349. package/rules/date-format-consistency.yaml +3 -2
  350. package/rules/date-order.yaml +3 -1
  351. package/rules/date-range-reversed.yaml +3 -1
  352. package/rules/date-weekday-mismatch.yaml +3 -1
  353. package/rules/date-without-year.yaml +4 -3
  354. package/rules/defined-name-repeated.yaml +97 -0
  355. package/rules/defined-term-form.yaml +113 -0
  356. package/rules/demonstrative-opener-run.yaml +2 -1
  357. package/rules/double-keigo.yaml +6 -1
  358. package/rules/double-negative.yaml +5 -1
  359. package/rules/doubled-nado.yaml +2 -1
  360. package/rules/doubled-punctuation.yaml +1 -1
  361. package/rules/doubled-word.yaml +4 -1
  362. package/rules/duplicate-definition.yaml +3 -1
  363. package/rules/duplicate-heading.yaml +1 -1
  364. package/rules/duration-mismatch.yaml +2 -1
  365. package/rules/elapsed-years-mismatch.yaml +4 -1
  366. package/rules/email-greeting-closing.yaml +122 -0
  367. package/rules/email-subject-length.yaml +112 -0
  368. package/rules/emoji-density.yaml +3 -1
  369. package/rules/emoji-heading.yaml +7 -1
  370. package/rules/empty-intensifier.yaml +4 -0
  371. package/rules/empty-list-item.yaml +2 -1
  372. package/rules/empty-section.yaml +1 -1
  373. package/rules/empty-table-cell.yaml +2 -1
  374. package/rules/excessive-hedging.yaml +7 -1
  375. package/rules/exclamation-width-consistency.yaml +64 -0
  376. package/rules/expletive-construction.yaml +7 -1
  377. package/rules/fact-conflict.yaml +4 -1
  378. package/rules/figure-reference-order.yaml +7 -1
  379. package/rules/fullwidth-alnum-consistency.yaml +2 -2
  380. package/rules/hankaku-kana.yaml +1 -1
  381. package/rules/heading-command-missing.yaml +109 -0
  382. package/rules/heading-echo.yaml +6 -1
  383. package/rules/heading-level-skip.yaml +3 -3
  384. package/rules/heading-numbering-mix.yaml +4 -3
  385. package/rules/hiragana-fukushi.yaml +4 -1
  386. package/rules/homophone-slip.yaml +5 -1
  387. package/rules/humble-for-others.yaml +63 -0
  388. package/rules/hyphen-as-dash.yaml +59 -0
  389. package/rules/idiom-misuse.yaml +4 -1
  390. package/rules/image-file-name-alt.yaml +83 -0
  391. package/rules/impossible-date.yaml +79 -0
  392. package/rules/impossible-time.yaml +76 -0
  393. package/rules/internal-jargon.yaml +1 -1
  394. package/rules/invisible-character.yaml +1 -1
  395. package/rules/katakana-density.yaml +2 -1
  396. package/rules/katakana-long-vowel.yaml +1 -1
  397. package/rules/known-misspelling.yaml +2 -1
  398. package/rules/kutoten-consistency.yaml +1 -1
  399. package/rules/latin-abbreviation-form.yaml +58 -0
  400. package/rules/latin-spacing.yaml +6 -2
  401. package/rules/link-text-url-mismatch.yaml +72 -0
  402. package/rules/list-item-form-mix.yaml +7 -1
  403. package/rules/max-kanji-continuous.yaml +10 -2
  404. package/rules/max-paragraph-length.yaml +4 -1
  405. package/rules/max-sentence-length.yaml +29 -21
  406. package/rules/max-ten.yaml +4 -1
  407. package/rules/misconversion.yaml +2 -1
  408. package/rules/modal-conflict.yaml +2 -1
  409. package/rules/name-variant.yaml +4 -3
  410. package/rules/ngram-repetition.yaml +5 -0
  411. package/rules/no-doubled-joshi.yaml +5 -1
  412. package/rules/no-em-dash.yaml +4 -1
  413. package/rules/no-lead.yaml +6 -1
  414. package/rules/no-mixed-desumasu.yaml +5 -2
  415. package/rules/no-nakaguro-parallel.yaml +3 -0
  416. package/rules/nominalization.yaml +5 -1
  417. package/rules/number-style-consistency.yaml +6 -1
  418. package/rules/number-without-unit.yaml +2 -1
  419. package/rules/numbering-gap.yaml +1 -1
  420. package/rules/one-sentence-paragraph-run.yaml +5 -1
  421. package/rules/orthographic-variant.yaml +4 -1
  422. package/rules/outside-announced-list.yaml +2 -1
  423. package/rules/oxford-comma-consistency.yaml +3 -1
  424. package/rules/padded-intro.yaml +7 -0
  425. package/rules/paragraph-length-variance.yaml +7 -1
  426. package/rules/paragraph-restatement.yaml +6 -1
  427. package/rules/percent-sum-mismatch.yaml +3 -1
  428. package/rules/person-consistency.yaml +4 -1
  429. package/rules/preamble-length.yaml +6 -0
  430. package/rules/preferred-term.yaml +1 -1
  431. package/rules/proper-noun-density.yaml +8 -1
  432. package/rules/quote-style-consistency.yaml +66 -0
  433. package/rules/ra-nuki.yaml +4 -1
  434. package/rules/redundant-expression.yaml +4 -1
  435. package/rules/reference-title-mismatch.yaml +4 -3
  436. package/rules/reference-topic-missing.yaml +2 -1
  437. package/rules/relative-date-mismatch.yaml +2 -1
  438. package/rules/repeated-conjunction.yaml +4 -1
  439. package/rules/repeated-sentence-ending.yaml +62 -0
  440. package/rules/repeated-sentence-head.yaml +4 -0
  441. package/rules/request-without-deadline.yaml +92 -0
  442. package/rules/required-sections.yaml +1 -1
  443. package/rules/requirement-modal.yaml +1 -1
  444. package/rules/requirement-smell.yaml +12 -4
  445. package/rules/rule-of-three.yaml +8 -1
  446. package/rules/sasete-itadaku.yaml +7 -1
  447. package/rules/section-length-uniformity.yaml +7 -1
  448. package/rules/sentence-initial-conjunction-run.yaml +5 -1
  449. package/rules/sentence-initial-so.yaml +6 -1
  450. package/rules/sentence-rhythm.yaml +8 -1
  451. package/rules/sentence-spacing-consistency.yaml +63 -0
  452. package/rules/shell-prompt-mix.yaml +128 -0
  453. package/rules/space-before-punctuation.yaml +1 -1
  454. package/rules/spelling-consistency.yaml +3 -1
  455. package/rules/step-statement-mix.yaml +76 -0
  456. package/rules/stock-transition.yaml +8 -1
  457. package/rules/stray-space.yaml +2 -2
  458. package/rules/summary-fact-mismatch.yaml +4 -1
  459. package/rules/table-header-variant.yaml +2 -1
  460. package/rules/taigen-dome-in-prose.yaml +6 -0
  461. package/rules/tari-unpaired.yaml +58 -0
  462. package/rules/teki-overuse.yaml +2 -1
  463. package/rules/title-case-consistency.yaml +1 -1
  464. package/rules/title-length.yaml +7 -1
  465. package/rules/topic-predicate-distance.yaml +6 -1
  466. package/rules/total-mismatch.yaml +3 -1
  467. package/rules/translationese-density.yaml +4 -1
  468. package/rules/unbalanced-bracket.yaml +3 -3
  469. package/rules/undefined-acronym.yaml +8 -1
  470. package/rules/unfilled-placeholder.yaml +1 -1
  471. package/rules/unit-mismatch.yaml +2 -1
  472. package/rules/unknown-word.yaml +4 -1
  473. package/rules/unqualified-superlative.yaml +6 -1
  474. package/rules/unused-definition.yaml +4 -3
  475. package/rules/url-run-on.yaml +1 -1
  476. package/rules/use-before-definition.yaml +4 -3
  477. package/rules/vague-figure-reference.yaml +3 -1
  478. package/rules/vague-link-text.yaml +2 -1
  479. package/rules/vague-word-density.yaml +2 -1
  480. package/rules/very-adjective.yaml +5 -1
  481. package/rules/weasel-word.yaml +6 -1
  482. package/rules/wordy-phrase.yaml +6 -1
  483. package/rules/yori-as-from.yaml +61 -0
  484. package/src/api.ts +13 -1
  485. package/src/cli-text.ts +18 -7
  486. package/src/cli.ts +3 -0
  487. package/src/command-help.ts +26 -0
  488. package/src/commands/eval.ts +2 -2
  489. package/src/commands/fix-plan.ts +13 -1
  490. package/src/commands/help.ts +8 -0
  491. package/src/config/load.ts +3 -0
  492. package/src/config/option-problems.ts +2 -2
  493. package/src/config/rule-problems.ts +2 -2
  494. package/src/custom/checked.ts +8 -0
  495. package/src/custom/dsl-fields.ts +102 -0
  496. package/src/custom/load.ts +2 -0
  497. package/src/custom/parse.ts +72 -50
  498. package/src/custom/problems.ts +18 -2
  499. package/src/detectors/blank-structure.ts +12 -4
  500. package/src/detectors/clause-shape.ts +199 -0
  501. package/src/detectors/code-fences.ts +113 -0
  502. package/src/detectors/cross-definitions.ts +49 -0
  503. package/src/detectors/cross-facts.ts +60 -0
  504. package/src/detectors/cross-registry/cross-doc-duplicate-definition.ts +4 -0
  505. package/src/detectors/cross-registry/cross-doc-fact-conflict.ts +4 -0
  506. package/src/detectors/custom.ts +10 -2
  507. package/src/detectors/defined-term-form.ts +200 -0
  508. package/src/detectors/email-letter.ts +262 -0
  509. package/src/detectors/impossible-date.ts +141 -0
  510. package/src/detectors/impossible-time.ts +151 -0
  511. package/src/detectors/link-shape.ts +70 -0
  512. package/src/detectors/mark-shape.ts +202 -0
  513. package/src/detectors/registry/adverb-polarity.ts +4 -0
  514. package/src/detectors/registry/attachment-missing.ts +4 -0
  515. package/src/detectors/registry/bare-url-mix.ts +4 -0
  516. package/src/detectors/registry/caps-shouting.ts +4 -0
  517. package/src/detectors/registry/code-fence-language.ts +4 -0
  518. package/src/detectors/registry/defined-name-repeated.ts +4 -0
  519. package/src/detectors/registry/defined-term-form.ts +4 -0
  520. package/src/detectors/registry/email-frame.ts +4 -0
  521. package/src/detectors/registry/email-subject.ts +4 -0
  522. package/src/detectors/registry/ending-run.ts +4 -0
  523. package/src/detectors/registry/heading-command.ts +4 -0
  524. package/src/detectors/registry/hyphen-dash.ts +4 -0
  525. package/src/detectors/registry/image-file-name-alt.ts +4 -0
  526. package/src/detectors/registry/impossible-date.ts +4 -0
  527. package/src/detectors/registry/impossible-time.ts +4 -0
  528. package/src/detectors/registry/latin-abbreviation.ts +4 -0
  529. package/src/detectors/registry/link-text-url-mismatch.ts +4 -0
  530. package/src/detectors/registry/mark-consistency.ts +4 -0
  531. package/src/detectors/registry/origin-particle.ts +4 -0
  532. package/src/detectors/registry/request-without-deadline.ts +4 -0
  533. package/src/detectors/registry/sentence-spacing.ts +4 -0
  534. package/src/detectors/registry/shell-prompt.ts +4 -0
  535. package/src/detectors/registry/step-statement.ts +4 -0
  536. package/src/detectors/registry/unpaired-parallel.ts +4 -0
  537. package/src/detectors/request-owner.ts +141 -0
  538. package/src/detectors/technical-docs.ts +230 -0
  539. package/src/extension/pack-read.ts +128 -0
  540. package/src/extension/plugin-load.ts +49 -9
  541. package/src/extension/plugin-parse.ts +10 -3
  542. package/src/facts/address-values.ts +74 -0
  543. package/src/facts/cross-facts.ts +54 -0
  544. package/src/fix-plan/chosen-depth.ts +28 -0
  545. package/src/fix-plan/mode.ts +57 -11
  546. package/src/fix-plan/plan.ts +37 -5
  547. package/src/fix-plan/render.ts +24 -0
  548. package/src/fix-plan/text.ts +47 -10
  549. package/src/genre-load.ts +7 -4
  550. package/src/genre-parse.ts +19 -0
  551. package/src/init.ts +10 -10
  552. package/src/known-genres.ts +19 -0
  553. package/src/link-shape.ts +62 -0
  554. package/src/reasons.ts +16 -4
  555. package/src/render/explain.ts +14 -2
  556. package/src/render/friendly.ts +1 -1
  557. package/src/render/rules-json.ts +14 -3
  558. package/src/render/rules-table.ts +6 -6
  559. package/src/render/semantic.ts +2 -2
  560. package/src/render/suppressions.ts +2 -2
  561. package/src/rewrite-depth.ts +60 -0
  562. package/src/rule-fields.ts +95 -0
  563. package/src/rule-guide.ts +9 -0
  564. package/src/rule-load.ts +10 -4
  565. package/src/rule-offs.ts +50 -0
  566. package/src/structure/build.ts +21 -1
  567. package/src/structure/cross-definitions.ts +60 -0
@@ -0,0 +1,202 @@
1
+ import type { Detector, Finding, Lexicon, ProseDocument, Sentence } from "../plugin.ts";
2
+ import { escapeRegExp } from "../orthography.ts";
3
+ import { minorityReport } from "../spacing-minority.ts";
4
+ import { isWithinAny, quotedSpans } from "../quoted-span.ts";
5
+
6
+ /** The letters a mark must touch to count as part of the document's text, by language. */
7
+ const SCRIPT: Readonly<Record<string, RegExp>> = {
8
+ ja: /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}ー々]/u,
9
+ en: /\p{Script=Latin}/u,
10
+ };
11
+
12
+ /** One way of writing a mark: its kind, which of the two ways it is (side), and how the other way writes it. */
13
+ type Form = { readonly kind: string; readonly side: boolean; readonly other: string };
14
+
15
+ /**
16
+ * The two ways of writing each kind of mark, from the lexicon: pattern is one way, instead_of the other, group the kind.
17
+ * Two patterns may share one instead_of (“ and ” both against "), so the other way of a shared mark is both patterns.
18
+ */
19
+ export const formsOf = (lexicon: Lexicon): ReadonlyMap<string, Form> => {
20
+ const forms = new Map<string, Form>();
21
+ lexicon.forEach((entry) => {
22
+ const kind = entry.group ?? entry.pattern;
23
+ const shared = entry.instead_of ?? "";
24
+ forms.set(entry.pattern, { kind, side: true, other: shared });
25
+ const known = forms.get(shared);
26
+ forms.set(shared, { kind, side: false, other: `${known?.side === false ? known.other : ""}${entry.pattern}` });
27
+ });
28
+ return forms;
29
+ };
30
+
31
+ export type Mark = { readonly sentence: Sentence; readonly char: string; readonly form: Form; readonly offset: number; readonly spaced: boolean };
32
+
33
+ /** Punctuation that may stand between a closing or opening mark and the word it belongs to ("draft." and “draft”.). */
34
+ const BETWEEN = /[.,;:!?。、]/u;
35
+
36
+ /** The letter beside a mark on one side (step -1 or 1), past one punctuation mark. */
37
+ const besideLetter = (text: string, at: number, step: number, script: RegExp): boolean => {
38
+ const next = text.charAt(at + step);
39
+ return script.test(next) || (BETWEEN.test(next) && script.test(text.charAt(at + step * 2)));
40
+ };
41
+
42
+ const touchesScript = (text: string, at: number, script: RegExp): boolean => besideLetter(text, at, -1, script) || besideLetter(text, at, 1, script);
43
+
44
+ /** The marks in a sentence that touch a letter of the document's script, outside 「」『』 quotations. spaced is the lexicon's pattern side. */
45
+ export const marksIn = (sentence: Sentence, forms: ReadonlyMap<string, Form>, script: RegExp): Mark[] => {
46
+ const quoted = quotedSpans(sentence.text);
47
+ return sentence.text.split("").flatMap((char, at) => {
48
+ const form = forms.get(char);
49
+ if (form === undefined || !touchesScript(sentence.text, at, script)) return [];
50
+ if (isWithinAny(quoted, { start: at, end: at })) return [];
51
+ return [{ sentence, char, form, offset: sentence.span.start + at, spaced: form.side }];
52
+ });
53
+ };
54
+
55
+ /** Something written one of two ways (spaced is which), at a place in a sentence. */
56
+ type Sided = { readonly sentence: Sentence; readonly offset: number; readonly spaced: boolean };
57
+
58
+ const sidedFinding = (item: Sided, values: Finding["values"], variant?: string): Finding => ({
59
+ rule: "",
60
+ severity: "info",
61
+ line: 0,
62
+ column: 0,
63
+ quote: item.sentence.text.trim(),
64
+ ...(variant === undefined ? {} : { variant }),
65
+ values: { ...values, offset: item.offset },
66
+ });
67
+
68
+ /** The less common way, one finding each, or one finding for a document written both ways (spacing-minority.ts). */
69
+ const minorityFindings = <T extends Sided>(items: readonly T[], limit: number, valuesOf: (item: T) => Finding["values"]): Finding[] => {
70
+ const report = minorityReport(items, limit);
71
+ if (report === undefined) return [];
72
+ if (report.mode === "mixed") {
73
+ const { first, odd, spaced, touching } = report;
74
+ return [sidedFinding(first, { ...valuesOf(first), spaced, touching, count: odd, of: items.length, limit }, "mixed")];
75
+ }
76
+ return report.odd.map((item) => sidedFinding(item, { ...valuesOf(item), count: report.odd.length, of: items.length, limit }));
77
+ };
78
+
79
+ /** Sentences written in the document's own language; a quoted sentence in another language follows its own marks. */
80
+ const ownSentences = (doc: ProseDocument): readonly Sentence[] => doc.sentences.filter((sentence) => sentence.embeddedLanguage === undefined);
81
+
82
+ /**
83
+ * A mark written two ways in one document (( and (, “ and "), the less common way reported. The two ways come from the
84
+ * lexicon; neither is called right. Only marks next to a letter of the document's script count, so a bracket in code-like
85
+ * text (f(x)) or an English aside in a Japanese document does not.
86
+ */
87
+ export const markConsistency: Detector = (doc, options): Finding[] => {
88
+ const script = SCRIPT[doc.language];
89
+ if (script === undefined) return [];
90
+ const forms = formsOf(options.lexicon ?? []);
91
+ const marks = ownSentences(doc).flatMap((sentence) => marksIn(sentence, forms, script));
92
+ const kinds = [...new Set(marks.map((mark) => mark.form.kind))];
93
+ return kinds.flatMap((kind) =>
94
+ minorityFindings(
95
+ marks.filter((mark) => mark.form.kind === kind),
96
+ options.limit,
97
+ (mark) => ({ mark: mark.char, usual: mark.form.other }),
98
+ ),
99
+ );
100
+ };
101
+
102
+ /**
103
+ * A sentence that surely ends: a lower-case word of two letters or more, then the mark. A number ("11.1."), an abbreviation
104
+ * ("Req.", "e.g.") or a capital ("U.S.") may be a split the adapter made inside a sentence, where one space is usual.
105
+ */
106
+ const SENTENCE_END = /(?<![\p{L}\d.])\p{Ll}{2,}[.!?]["'”’)\]]?$/u;
107
+ /** The next sentence starts with a capital, past an opening quote: not a citation ("[ISO99]") or an aside in brackets. */
108
+ const SENTENCE_START = /^["'“‘]?\p{Lu}/u;
109
+ const ONE_SPACE = " ";
110
+ const TWO_SPACES = " ";
111
+
112
+ /** The space between two sentences on one line, when the first ends with a period, question or exclamation mark. spaced is two spaces. */
113
+ export const sentenceGaps = (doc: ProseDocument): Sided[] =>
114
+ ownSentences(doc).flatMap((sentence, at, sentences) => {
115
+ const next = sentences[at + 1];
116
+ if (next === undefined || !SENTENCE_END.test(sentence.text) || !SENTENCE_START.test(next.text)) return [];
117
+ const gap = doc.source.slice(sentence.span.end, next.span.start);
118
+ if (gap !== ONE_SPACE && gap !== TWO_SPACES) return [];
119
+ return [{ sentence: next, spaced: gap === TWO_SPACES, offset: sentence.span.end }];
120
+ });
121
+
122
+ /** One space between sentences and two, mixed in one document: the less common is reported. */
123
+ export const sentenceSpacing: Detector = (doc, options): Finding[] =>
124
+ minorityFindings(sentenceGaps(doc), options.limit, (gap) => ({
125
+ spaces: gap.spaced ? TWO_SPACES.length : ONE_SPACE.length,
126
+ usual: gap.spaced ? ONE_SPACE.length : TWO_SPACES.length,
127
+ }));
128
+
129
+ type Hit = { readonly sentence: Sentence; readonly matched: string; readonly offset: number };
130
+
131
+ const hitFinding = (hit: Hit, count: number, limit: number): Finding => ({
132
+ rule: "",
133
+ severity: "info",
134
+ line: 0,
135
+ column: 0,
136
+ quote: hit.sentence.text.trim(),
137
+ values: { matched: hit.matched, count, limit, offset: hit.offset },
138
+ });
139
+
140
+ const hitsOf = (sentences: readonly Sentence[], pattern: RegExp): Hit[] =>
141
+ sentences.flatMap((sentence) =>
142
+ [...sentence.text.matchAll(pattern)].map((match) => ({ sentence, matched: match[0].trim(), offset: sentence.span.start + match.index })),
143
+ );
144
+
145
+ const reportFrom = (hits: readonly Hit[], limit: number): Finding[] => (hits.length < limit ? [] : hits.map((hit) => hitFinding(hit, hits.length, limit)));
146
+
147
+ /** A spaced hyphen or a double hyphen between words, standing for a dash ("late - very late", "late--very late"). */
148
+ const HYPHEN_DASH = /(?<=[\p{L},;)]) -{1,2} (?=[\p{L}(])|(?<=\p{L})--(?=\p{L})/gu;
149
+
150
+ export const hyphenDash: Detector = (doc, options): Finding[] => reportFrom(hitsOf(ownSentences(doc), HYPHEN_DASH), options.limit);
151
+
152
+ /** Every way of writing an abbreviation with some of its periods dropped but not all (e.g. → e.g, eg.). */
153
+ export const partialForms = (abbreviation: string): string[] => {
154
+ const dots = [...abbreviation].flatMap((char, at) => (char === "." ? [at] : []));
155
+ const subsets = Array.from({ length: 2 ** dots.length }, (_unused, mask) => dots.filter((_dot, bit) => (mask >> bit) % 2 === 1));
156
+ return subsets
157
+ .filter((dropped) => dropped.length > 0 && dropped.length < dots.length)
158
+ .map((dropped) => [...abbreviation].filter((_char, at) => !dropped.includes(at)).join(""));
159
+ };
160
+
161
+ /** One partial form as a pattern: its first letter in either case, not inside a word, not followed by a period or letter. */
162
+ const partialPattern = (form: string): string =>
163
+ `(?<![\\p{L}.])[${form.charAt(0).toLowerCase()}${form.charAt(0).toUpperCase()}]${escapeRegExp(form.slice(1))}(?![\\p{L}.])`;
164
+
165
+ /** Latin abbreviations from the lexicon (e.g., i.e.) written with only some of their periods (e.g, eg.). */
166
+ export const latinAbbreviation: Detector = (doc, options): Finding[] => {
167
+ const forms = (options.lexicon ?? []).flatMap((entry) => partialForms(entry.pattern));
168
+ if (forms.length === 0) return [];
169
+ const pattern = new RegExp(forms.map(partialPattern).join("|"), "gu");
170
+ return reportFrom(hitsOf(ownSentences(doc), pattern), options.limit);
171
+ };
172
+
173
+ /** How many all-capital words in a row read as shouting rather than a run of acronyms. */
174
+ const SHOUT_WORDS = 3;
175
+ const CAPS_RUN = new RegExp(`\\b[A-Z][A-Z']*(?:,? [A-Z][A-Z']*){${String(SHOUT_WORDS - 1)},}\\b`, "gu");
176
+ /** One-letter words (I, A) may sit in a run, but a run of them alone ("A B C") is a list of labels. */
177
+ const MIN_LONG_WORDS = 2;
178
+
179
+ const LATIN_WORD = /[A-Za-z][A-Za-z']*/gu;
180
+
181
+ /** The words a source writes at least once not wholly in capitals (do, Do), in lower case: words, not acronyms. */
182
+ export const lowerCaseWords = (source: string): ReadonlySet<string> =>
183
+ new Set((source.match(LATIN_WORD) ?? []).filter((word) => word !== word.toUpperCase()).map((word) => word.toLowerCase()));
184
+
185
+ /**
186
+ * Whether more than half the words of a capital run are also written elsewhere in lower case. Words the lexicon lists as
187
+ * written in capitals by convention (MUST NOT in a specification) count like acronyms.
188
+ */
189
+ export const isShouting = (run: string, known: ReadonlySet<string>, conventional: ReadonlySet<string>): boolean => {
190
+ const words = run.split(/,? /u);
191
+ const ordinary = words.filter((word) => !conventional.has(word) && known.has(word.toLowerCase()));
192
+ const long = words.filter((word) => word.length > 1);
193
+ return long.length >= MIN_LONG_WORDS && ordinary.length * 2 > words.length;
194
+ };
195
+
196
+ /** Runs of three or more all-capital words that are ordinary words, not acronyms ("DO NOT DELETE THIS"). */
197
+ export const capsShouting: Detector = (doc, options): Finding[] => {
198
+ const known = lowerCaseWords(doc.prose ?? doc.source);
199
+ const conventional = new Set((options.lexicon ?? []).map((entry) => entry.pattern));
200
+ const hits = hitsOf(ownSentences(doc), CAPS_RUN).filter((hit) => isShouting(hit.matched, known, conventional));
201
+ return reportFrom(hits, options.limit);
202
+ };
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { adverbPolarity } from "../clause-shape.ts";
3
+
4
+ export const detector: Detector = adverbPolarity;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { attachmentMissing } from "../email-letter.ts";
3
+
4
+ export const detector: Detector = attachmentMissing;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { bareUrlMix } from "../link-shape.ts";
3
+
4
+ export const detector: Detector = bareUrlMix;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { capsShouting } from "../mark-shape.ts";
3
+
4
+ export const detector: Detector = capsShouting;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { codeFenceLanguage } from "../technical-docs.ts";
3
+
4
+ export const detector: Detector = codeFenceLanguage;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { definedNameRepeated } from "../defined-term-form.ts";
3
+
4
+ export const detector: Detector = definedNameRepeated;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { definedTermForm } from "../defined-term-form.ts";
3
+
4
+ export const detector: Detector = definedTermForm;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { emailFrame } from "../email-letter.ts";
3
+
4
+ export const detector: Detector = emailFrame;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { emailSubject } from "../email-letter.ts";
3
+
4
+ export const detector: Detector = emailSubject;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { endingRun } from "../clause-shape.ts";
3
+
4
+ export const detector: Detector = endingRun;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { headingCommand } from "../technical-docs.ts";
3
+
4
+ export const detector: Detector = headingCommand;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { hyphenDash } from "../mark-shape.ts";
3
+
4
+ export const detector: Detector = hyphenDash;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { imageFileNameAlt } from "../link-shape.ts";
3
+
4
+ export const detector: Detector = imageFileNameAlt;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { impossibleDate } from "../impossible-date.ts";
3
+
4
+ export const detector: Detector = impossibleDate;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { impossibleTime } from "../impossible-time.ts";
3
+
4
+ export const detector: Detector = impossibleTime;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { latinAbbreviation } from "../mark-shape.ts";
3
+
4
+ export const detector: Detector = latinAbbreviation;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { linkTextUrlMismatch } from "../link-shape.ts";
3
+
4
+ export const detector: Detector = linkTextUrlMismatch;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { markConsistency } from "../mark-shape.ts";
3
+
4
+ export const detector: Detector = markConsistency;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { originParticle } from "../clause-shape.ts";
3
+
4
+ export const detector: Detector = originParticle;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { requestWithoutDeadline } from "../request-owner.ts";
3
+
4
+ export const detector: Detector = requestWithoutDeadline;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { sentenceSpacing } from "../mark-shape.ts";
3
+
4
+ export const detector: Detector = sentenceSpacing;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { shellPrompt } from "../technical-docs.ts";
3
+
4
+ export const detector: Detector = shellPrompt;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { stepStatement } from "../technical-docs.ts";
3
+
4
+ export const detector: Detector = stepStatement;
@@ -0,0 +1,4 @@
1
+ import type { Detector } from "../../plugin.ts";
2
+ import { unpairedParallel } from "../clause-shape.ts";
3
+
4
+ export const detector: Detector = unpairedParallel;
@@ -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
+ }));