chaffjs 0.18.0 → 0.19.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 (682) hide show
  1. package/README.md +52 -56
  2. package/bin/chaff.js +17 -2
  3. package/dist/acronym-expansions.d.ts +26 -0
  4. package/dist/acronym-expansions.d.ts.map +1 -0
  5. package/dist/acronym-expansions.js +88 -0
  6. package/dist/acronym-expansions.js.map +1 -0
  7. package/dist/announced-count.d.ts.map +1 -1
  8. package/dist/announced-count.js +44 -10
  9. package/dist/announced-count.js.map +1 -1
  10. package/dist/announced-list.d.ts +30 -0
  11. package/dist/announced-list.d.ts.map +1 -0
  12. package/dist/announced-list.js +95 -0
  13. package/dist/announced-list.js.map +1 -0
  14. package/dist/api.d.ts +168 -0
  15. package/dist/api.d.ts.map +1 -0
  16. package/dist/api.js +10 -0
  17. package/dist/api.js.map +1 -0
  18. package/dist/cli-text.d.ts +0 -1
  19. package/dist/cli-text.d.ts.map +1 -1
  20. package/dist/cli-text.js +6 -14
  21. package/dist/cli-text.js.map +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +58 -62
  24. package/dist/cli.js.map +1 -1
  25. package/dist/closing-paragraph.d.ts +7 -0
  26. package/dist/closing-paragraph.d.ts.map +1 -0
  27. package/dist/closing-paragraph.js +22 -0
  28. package/dist/closing-paragraph.js.map +1 -0
  29. package/dist/commands/compare.d.ts +8 -1
  30. package/dist/commands/compare.d.ts.map +1 -1
  31. package/dist/commands/compare.js +9 -17
  32. package/dist/commands/compare.js.map +1 -1
  33. package/dist/commands/document-commands.d.ts +9 -0
  34. package/dist/commands/document-commands.d.ts.map +1 -0
  35. package/dist/commands/document-commands.js +17 -0
  36. package/dist/commands/document-commands.js.map +1 -0
  37. package/dist/commands/eval.d.ts.map +1 -1
  38. package/dist/commands/eval.js +3 -2
  39. package/dist/commands/eval.js.map +1 -1
  40. package/dist/commands/facts.d.ts +8 -0
  41. package/dist/commands/facts.d.ts.map +1 -0
  42. package/dist/commands/facts.js +30 -0
  43. package/dist/commands/facts.js.map +1 -0
  44. package/dist/commands/feedback.d.ts +10 -0
  45. package/dist/commands/feedback.d.ts.map +1 -1
  46. package/dist/commands/feedback.js +25 -1
  47. package/dist/commands/feedback.js.map +1 -1
  48. package/dist/commands/fix-plan.d.ts +13 -0
  49. package/dist/commands/fix-plan.d.ts.map +1 -0
  50. package/dist/commands/fix-plan.js +44 -0
  51. package/dist/commands/fix-plan.js.map +1 -0
  52. package/dist/commands/outline.d.ts +8 -0
  53. package/dist/commands/outline.d.ts.map +1 -0
  54. package/dist/commands/outline.js +43 -0
  55. package/dist/commands/outline.js.map +1 -0
  56. package/dist/commands/read-document.d.ts +14 -0
  57. package/dist/commands/read-document.d.ts.map +1 -0
  58. package/dist/commands/read-document.js +21 -0
  59. package/dist/commands/read-document.js.map +1 -0
  60. package/dist/commands/suppressions.d.ts +7 -0
  61. package/dist/commands/suppressions.d.ts.map +1 -0
  62. package/dist/commands/suppressions.js +9 -0
  63. package/dist/commands/suppressions.js.map +1 -0
  64. package/dist/commands/test.js +1 -1
  65. package/dist/commands/test.js.map +1 -1
  66. package/dist/compare/clock-time.d.ts.map +1 -1
  67. package/dist/compare/clock-time.js +5 -2
  68. package/dist/compare/clock-time.js.map +1 -1
  69. package/dist/compare/distinct.d.ts +9 -0
  70. package/dist/compare/distinct.d.ts.map +1 -0
  71. package/dist/compare/distinct.js +12 -0
  72. package/dist/compare/distinct.js.map +1 -0
  73. package/dist/compare/extract.d.ts.map +1 -1
  74. package/dist/compare/extract.js +7 -5
  75. package/dist/compare/extract.js.map +1 -1
  76. package/dist/compare/fact-key.d.ts +17 -0
  77. package/dist/compare/fact-key.d.ts.map +1 -0
  78. package/dist/compare/fact-key.js +25 -0
  79. package/dist/compare/fact-key.js.map +1 -0
  80. package/dist/compare/fact-text.d.ts +5 -0
  81. package/dist/compare/fact-text.d.ts.map +1 -1
  82. package/dist/compare/fact-text.js +16 -2
  83. package/dist/compare/fact-text.js.map +1 -1
  84. package/dist/compare/facts-render.d.ts +17 -0
  85. package/dist/compare/facts-render.d.ts.map +1 -0
  86. package/dist/compare/facts-render.js +43 -0
  87. package/dist/compare/facts-render.js.map +1 -0
  88. package/dist/compare/facts-text.d.ts +15 -0
  89. package/dist/compare/facts-text.d.ts.map +1 -0
  90. package/dist/compare/facts-text.js +27 -0
  91. package/dist/compare/facts-text.js.map +1 -0
  92. package/dist/compare/match.d.ts +1 -0
  93. package/dist/compare/match.d.ts.map +1 -1
  94. package/dist/compare/match.js +1 -1
  95. package/dist/compare/match.js.map +1 -1
  96. package/dist/compare/outcome.d.ts +4 -1
  97. package/dist/compare/outcome.d.ts.map +1 -1
  98. package/dist/compare/outcome.js +10 -4
  99. package/dist/compare/outcome.js.map +1 -1
  100. package/dist/compare/proper-nouns.d.ts +3 -3
  101. package/dist/compare/proper-nouns.d.ts.map +1 -1
  102. package/dist/compare/proper-nouns.js +2 -3
  103. package/dist/compare/proper-nouns.js.map +1 -1
  104. package/dist/compare/render.d.ts +4 -0
  105. package/dist/compare/render.d.ts.map +1 -1
  106. package/dist/compare/render.js +2 -2
  107. package/dist/compare/render.js.map +1 -1
  108. package/dist/compare/text-atoms.d.ts +5 -2
  109. package/dist/compare/text-atoms.d.ts.map +1 -1
  110. package/dist/compare/text-atoms.js +7 -5
  111. package/dist/compare/text-atoms.js.map +1 -1
  112. package/dist/compare/text.d.ts.map +1 -1
  113. package/dist/compare/text.js +2 -2
  114. package/dist/compare/text.js.map +1 -1
  115. package/dist/config/load.d.ts +8 -0
  116. package/dist/config/load.d.ts.map +1 -1
  117. package/dist/config/load.js +1 -0
  118. package/dist/config/load.js.map +1 -1
  119. package/dist/config/option-problems.d.ts +3 -2
  120. package/dist/config/option-problems.d.ts.map +1 -1
  121. package/dist/config/option-problems.js +4 -3
  122. package/dist/config/option-problems.js.map +1 -1
  123. package/dist/config/style.d.ts +5 -0
  124. package/dist/config/style.d.ts.map +1 -1
  125. package/dist/config/style.js +13 -1
  126. package/dist/config/style.js.map +1 -1
  127. package/dist/custom/load.d.ts +6 -3
  128. package/dist/custom/load.d.ts.map +1 -1
  129. package/dist/custom/load.js +10 -2
  130. package/dist/custom/load.js.map +1 -1
  131. package/dist/custom/module-path.d.ts +11 -0
  132. package/dist/custom/module-path.d.ts.map +1 -0
  133. package/dist/custom/module-path.js +15 -0
  134. package/dist/custom/module-path.js.map +1 -0
  135. package/dist/custom/parse.d.ts +17 -3
  136. package/dist/custom/parse.d.ts.map +1 -1
  137. package/dist/custom/parse.js +57 -14
  138. package/dist/custom/parse.js.map +1 -1
  139. package/dist/custom/problems.d.ts +4 -1
  140. package/dist/custom/problems.d.ts.map +1 -1
  141. package/dist/custom/problems.js +56 -39
  142. package/dist/custom/problems.js.map +1 -1
  143. package/dist/detect-language.js +1 -1
  144. package/dist/detect.d.ts.map +1 -1
  145. package/dist/detect.js +9 -3
  146. package/dist/detect.js.map +1 -1
  147. package/dist/detectors/ai-phrasing.d.ts +4 -1
  148. package/dist/detectors/ai-phrasing.d.ts.map +1 -1
  149. package/dist/detectors/ai-phrasing.js +6 -2
  150. package/dist/detectors/ai-phrasing.js.map +1 -1
  151. package/dist/detectors/bold-label.d.ts +10 -0
  152. package/dist/detectors/bold-label.d.ts.map +1 -0
  153. package/dist/detectors/bold-label.js +44 -0
  154. package/dist/detectors/bold-label.js.map +1 -0
  155. package/dist/detectors/dangling-figure.d.ts +5 -2
  156. package/dist/detectors/dangling-figure.d.ts.map +1 -1
  157. package/dist/detectors/dangling-figure.js +5 -3
  158. package/dist/detectors/dangling-figure.js.map +1 -1
  159. package/dist/detectors/definition-use.d.ts +8 -0
  160. package/dist/detectors/definition-use.d.ts.map +1 -0
  161. package/dist/detectors/definition-use.js +32 -0
  162. package/dist/detectors/definition-use.js.map +1 -0
  163. package/dist/detectors/doubled-punctuation.d.ts.map +1 -1
  164. package/dist/detectors/doubled-punctuation.js +4 -2
  165. package/dist/detectors/doubled-punctuation.js.map +1 -1
  166. package/dist/detectors/duplicate-heading.d.ts +14 -0
  167. package/dist/detectors/duplicate-heading.d.ts.map +1 -0
  168. package/dist/detectors/duplicate-heading.js +39 -0
  169. package/dist/detectors/duplicate-heading.js.map +1 -0
  170. package/dist/detectors/empty-section.d.ts +8 -0
  171. package/dist/detectors/empty-section.d.ts.map +1 -0
  172. package/dist/detectors/empty-section.js +16 -0
  173. package/dist/detectors/empty-section.js.map +1 -0
  174. package/dist/detectors/fullwidth-alnum.d.ts +24 -0
  175. package/dist/detectors/fullwidth-alnum.d.ts.map +1 -0
  176. package/dist/detectors/fullwidth-alnum.js +88 -0
  177. package/dist/detectors/fullwidth-alnum.js.map +1 -0
  178. package/dist/detectors/hankaku-kana.d.ts +9 -0
  179. package/dist/detectors/hankaku-kana.d.ts.map +1 -0
  180. package/dist/detectors/hankaku-kana.js +27 -0
  181. package/dist/detectors/hankaku-kana.js.map +1 -0
  182. package/dist/detectors/heading-code-words.js +2 -2
  183. package/dist/detectors/heading-code-words.js.map +1 -1
  184. package/dist/detectors/index.d.ts.map +1 -1
  185. package/dist/detectors/index.js +36 -0
  186. package/dist/detectors/index.js.map +1 -1
  187. package/dist/detectors/invisible-character.d.ts +20 -0
  188. package/dist/detectors/invisible-character.d.ts.map +1 -0
  189. package/dist/detectors/invisible-character.js +143 -0
  190. package/dist/detectors/invisible-character.js.map +1 -0
  191. package/dist/detectors/kutoten-consistency.js +2 -2
  192. package/dist/detectors/kutoten-consistency.js.map +1 -1
  193. package/dist/detectors/lexicon-match.d.ts +10 -1
  194. package/dist/detectors/lexicon-match.d.ts.map +1 -1
  195. package/dist/detectors/lexicon-match.js +26 -1
  196. package/dist/detectors/lexicon-match.js.map +1 -1
  197. package/dist/detectors/lexicon.d.ts +6 -1
  198. package/dist/detectors/lexicon.d.ts.map +1 -1
  199. package/dist/detectors/lexicon.js +31 -13
  200. package/dist/detectors/lexicon.js.map +1 -1
  201. package/dist/detectors/list-sentence.d.ts +3 -1
  202. package/dist/detectors/list-sentence.d.ts.map +1 -1
  203. package/dist/detectors/list-sentence.js +26 -1
  204. package/dist/detectors/list-sentence.js.map +1 -1
  205. package/dist/detectors/long-vowel.d.ts.map +1 -1
  206. package/dist/detectors/long-vowel.js +18 -4
  207. package/dist/detectors/long-vowel.js.map +1 -1
  208. package/dist/detectors/markup-finding.d.ts +3 -1
  209. package/dist/detectors/markup-finding.d.ts.map +1 -1
  210. package/dist/detectors/markup-finding.js +10 -0
  211. package/dist/detectors/markup-finding.js.map +1 -1
  212. package/dist/detectors/modal-conflict.d.ts +4 -0
  213. package/dist/detectors/modal-conflict.d.ts.map +1 -0
  214. package/dist/detectors/modal-conflict.js +22 -0
  215. package/dist/detectors/modal-conflict.js.map +1 -0
  216. package/dist/detectors/name-variant.d.ts +4 -0
  217. package/dist/detectors/name-variant.d.ts.map +1 -0
  218. package/dist/detectors/name-variant.js +13 -0
  219. package/dist/detectors/name-variant.js.map +1 -0
  220. package/dist/detectors/nominalization.d.ts +11 -0
  221. package/dist/detectors/nominalization.d.ts.map +1 -0
  222. package/dist/detectors/nominalization.js +56 -0
  223. package/dist/detectors/nominalization.js.map +1 -0
  224. package/dist/detectors/orthography.d.ts.map +1 -1
  225. package/dist/detectors/orthography.js +59 -28
  226. package/dist/detectors/orthography.js.map +1 -1
  227. package/dist/detectors/outside-announced-list.d.ts +4 -0
  228. package/dist/detectors/outside-announced-list.d.ts.map +1 -0
  229. package/dist/detectors/outside-announced-list.js +24 -0
  230. package/dist/detectors/outside-announced-list.js.map +1 -0
  231. package/dist/detectors/oxford-comma.d.ts.map +1 -1
  232. package/dist/detectors/oxford-comma.js +1 -0
  233. package/dist/detectors/oxford-comma.js.map +1 -1
  234. package/dist/detectors/phrase-match.d.ts +4 -1
  235. package/dist/detectors/phrase-match.d.ts.map +1 -1
  236. package/dist/detectors/phrase-match.js +26 -7
  237. package/dist/detectors/phrase-match.js.map +1 -1
  238. package/dist/detectors/ra-nuki.d.ts +9 -0
  239. package/dist/detectors/ra-nuki.d.ts.map +1 -0
  240. package/dist/detectors/ra-nuki.js +30 -0
  241. package/dist/detectors/ra-nuki.js.map +1 -0
  242. package/dist/detectors/requirement-modal.d.ts +5 -0
  243. package/dist/detectors/requirement-modal.d.ts.map +1 -0
  244. package/dist/detectors/requirement-modal.js +54 -0
  245. package/dist/detectors/requirement-modal.js.map +1 -0
  246. package/dist/detectors/requirement-smell.d.ts +4 -0
  247. package/dist/detectors/requirement-smell.d.ts.map +1 -0
  248. package/dist/detectors/requirement-smell.js +41 -0
  249. package/dist/detectors/requirement-smell.js.map +1 -0
  250. package/dist/detectors/space-before-punctuation.d.ts +8 -0
  251. package/dist/detectors/space-before-punctuation.d.ts.map +1 -0
  252. package/dist/detectors/space-before-punctuation.js +24 -0
  253. package/dist/detectors/space-before-punctuation.js.map +1 -0
  254. package/dist/detectors/spelling-variety.d.ts +23 -0
  255. package/dist/detectors/spelling-variety.d.ts.map +1 -0
  256. package/dist/detectors/spelling-variety.js +59 -0
  257. package/dist/detectors/spelling-variety.js.map +1 -0
  258. package/dist/detectors/structure-tree.d.ts.map +1 -1
  259. package/dist/detectors/structure-tree.js +3 -1
  260. package/dist/detectors/structure-tree.js.map +1 -1
  261. package/dist/detectors/structure.d.ts.map +1 -1
  262. package/dist/detectors/structure.js +16 -2
  263. package/dist/detectors/structure.js.map +1 -1
  264. package/dist/detectors/superlative-comparison.d.ts +20 -0
  265. package/dist/detectors/superlative-comparison.d.ts.map +1 -0
  266. package/dist/detectors/superlative-comparison.js +32 -0
  267. package/dist/detectors/superlative-comparison.js.map +1 -0
  268. package/dist/detectors/url-run-on.d.ts.map +1 -1
  269. package/dist/detectors/url-run-on.js +6 -1
  270. package/dist/detectors/url-run-on.js.map +1 -1
  271. package/dist/detectors/vague-figure-pointer.d.ts +10 -0
  272. package/dist/detectors/vague-figure-pointer.d.ts.map +1 -0
  273. package/dist/detectors/vague-figure-pointer.js +46 -0
  274. package/dist/detectors/vague-figure-pointer.js.map +1 -0
  275. package/dist/detectors/word-list.d.ts +2 -0
  276. package/dist/detectors/word-list.d.ts.map +1 -1
  277. package/dist/detectors/word-list.js +4 -1
  278. package/dist/detectors/word-list.js.map +1 -1
  279. package/dist/document.d.ts +9 -3
  280. package/dist/document.d.ts.map +1 -1
  281. package/dist/document.js +55 -32
  282. package/dist/document.js.map +1 -1
  283. package/dist/extension/document-view.d.ts +7 -0
  284. package/dist/extension/document-view.d.ts.map +1 -0
  285. package/dist/extension/document-view.js +91 -0
  286. package/dist/extension/document-view.js.map +1 -0
  287. package/dist/extension/failure-text.d.ts +5 -0
  288. package/dist/extension/failure-text.d.ts.map +1 -0
  289. package/dist/extension/failure-text.js +33 -0
  290. package/dist/extension/failure-text.js.map +1 -0
  291. package/dist/extension/import-default.d.ts +7 -0
  292. package/dist/extension/import-default.d.ts.map +1 -0
  293. package/dist/extension/import-default.js +21 -0
  294. package/dist/extension/import-default.js.map +1 -0
  295. package/dist/extension/load.d.ts +28 -0
  296. package/dist/extension/load.d.ts.map +1 -0
  297. package/dist/extension/load.js +51 -0
  298. package/dist/extension/load.js.map +1 -0
  299. package/dist/extension/module-detector.d.ts +21 -0
  300. package/dist/extension/module-detector.d.ts.map +1 -0
  301. package/dist/extension/module-detector.js +49 -0
  302. package/dist/extension/module-detector.js.map +1 -0
  303. package/dist/extension/plugin-load.d.ts +9 -0
  304. package/dist/extension/plugin-load.d.ts.map +1 -0
  305. package/dist/extension/plugin-load.js +68 -0
  306. package/dist/extension/plugin-load.js.map +1 -0
  307. package/dist/extension/plugin-name.d.ts +8 -0
  308. package/dist/extension/plugin-name.d.ts.map +1 -0
  309. package/dist/extension/plugin-name.js +22 -0
  310. package/dist/extension/plugin-name.js.map +1 -0
  311. package/dist/extension/plugin-parse.d.ts +41 -0
  312. package/dist/extension/plugin-parse.d.ts.map +1 -0
  313. package/dist/extension/plugin-parse.js +156 -0
  314. package/dist/extension/plugin-parse.js.map +1 -0
  315. package/dist/extension/problem-text.d.ts +5 -0
  316. package/dist/extension/problem-text.d.ts.map +1 -0
  317. package/dist/extension/problem-text.js +74 -0
  318. package/dist/extension/problem-text.js.map +1 -0
  319. package/dist/extension/real-path.d.ts +3 -0
  320. package/dist/extension/real-path.d.ts.map +1 -0
  321. package/dist/extension/real-path.js +8 -0
  322. package/dist/extension/real-path.js.map +1 -0
  323. package/dist/extension/returned-findings.d.ts +17 -0
  324. package/dist/extension/returned-findings.d.ts.map +1 -0
  325. package/dist/extension/returned-findings.js +48 -0
  326. package/dist/extension/returned-findings.js.map +1 -0
  327. package/dist/extension/rule-export.d.ts +13 -0
  328. package/dist/extension/rule-export.d.ts.map +1 -0
  329. package/dist/extension/rule-export.js +16 -0
  330. package/dist/extension/rule-export.js.map +1 -0
  331. package/dist/feedback/draft.d.ts +2 -0
  332. package/dist/feedback/draft.d.ts.map +1 -1
  333. package/dist/feedback/draft.js +3 -0
  334. package/dist/feedback/draft.js.map +1 -1
  335. package/dist/figure-references.d.ts +11 -2
  336. package/dist/figure-references.d.ts.map +1 -1
  337. package/dist/figure-references.js +50 -13
  338. package/dist/figure-references.js.map +1 -1
  339. package/dist/finding-order.d.ts +9 -0
  340. package/dist/finding-order.d.ts.map +1 -0
  341. package/dist/finding-order.js +3 -0
  342. package/dist/finding-order.js.map +1 -0
  343. package/dist/fix-plan/mode.d.ts +21 -0
  344. package/dist/fix-plan/mode.d.ts.map +1 -0
  345. package/dist/fix-plan/mode.js +16 -0
  346. package/dist/fix-plan/mode.js.map +1 -0
  347. package/dist/fix-plan/plan.d.ts +77 -0
  348. package/dist/fix-plan/plan.d.ts.map +1 -0
  349. package/dist/fix-plan/plan.js +100 -0
  350. package/dist/fix-plan/plan.js.map +1 -0
  351. package/dist/fix-plan/render.d.ts +6 -0
  352. package/dist/fix-plan/render.d.ts.map +1 -0
  353. package/dist/fix-plan/render.js +101 -0
  354. package/dist/fix-plan/render.js.map +1 -0
  355. package/dist/fix-plan/text.d.ts +35 -0
  356. package/dist/fix-plan/text.d.ts.map +1 -0
  357. package/dist/fix-plan/text.js +95 -0
  358. package/dist/fix-plan/text.js.map +1 -0
  359. package/dist/language-sample.d.ts +2 -0
  360. package/dist/language-sample.d.ts.map +1 -0
  361. package/dist/language-sample.js +41 -0
  362. package/dist/language-sample.js.map +1 -0
  363. package/dist/link-line.d.ts +8 -0
  364. package/dist/link-line.d.ts.map +1 -0
  365. package/dist/link-line.js +12 -0
  366. package/dist/link-line.js.map +1 -0
  367. package/dist/long-vowel.d.ts +9 -6
  368. package/dist/long-vowel.d.ts.map +1 -1
  369. package/dist/long-vowel.js +15 -8
  370. package/dist/long-vowel.js.map +1 -1
  371. package/dist/markup.d.ts.map +1 -1
  372. package/dist/markup.js +4 -2
  373. package/dist/markup.js.map +1 -1
  374. package/dist/name-variants.d.ts +25 -0
  375. package/dist/name-variants.d.ts.map +1 -0
  376. package/dist/name-variants.js +181 -0
  377. package/dist/name-variants.js.map +1 -0
  378. package/dist/node-version.d.ts +7 -0
  379. package/dist/node-version.d.ts.map +1 -0
  380. package/dist/node-version.js +19 -0
  381. package/dist/node-version.js.map +1 -0
  382. package/dist/not-run.d.ts +14 -0
  383. package/dist/not-run.d.ts.map +1 -0
  384. package/dist/not-run.js +12 -0
  385. package/dist/not-run.js.map +1 -0
  386. package/dist/orthography.d.ts +8 -1
  387. package/dist/orthography.d.ts.map +1 -1
  388. package/dist/orthography.js +33 -12
  389. package/dist/orthography.js.map +1 -1
  390. package/dist/outline/render.d.ts +18 -0
  391. package/dist/outline/render.d.ts.map +1 -0
  392. package/dist/outline/render.js +60 -0
  393. package/dist/outline/render.js.map +1 -0
  394. package/dist/outline/shape.d.ts +26 -0
  395. package/dist/outline/shape.d.ts.map +1 -0
  396. package/dist/outline/shape.js +37 -0
  397. package/dist/outline/shape.js.map +1 -0
  398. package/dist/outline/text.d.ts +21 -0
  399. package/dist/outline/text.d.ts.map +1 -0
  400. package/dist/outline/text.js +30 -0
  401. package/dist/outline/text.js.map +1 -0
  402. package/dist/plugin.d.ts +24 -2
  403. package/dist/plugin.d.ts.map +1 -1
  404. package/dist/reasons.d.ts +1 -0
  405. package/dist/reasons.d.ts.map +1 -1
  406. package/dist/reasons.js +2 -0
  407. package/dist/reasons.js.map +1 -1
  408. package/dist/render/compact.d.ts +3 -1
  409. package/dist/render/compact.d.ts.map +1 -1
  410. package/dist/render/compact.js +8 -2
  411. package/dist/render/compact.js.map +1 -1
  412. package/dist/render/explain.d.ts +3 -1
  413. package/dist/render/explain.d.ts.map +1 -1
  414. package/dist/render/explain.js +32 -15
  415. package/dist/render/explain.js.map +1 -1
  416. package/dist/render/rules-json.d.ts.map +1 -1
  417. package/dist/render/rules-json.js +16 -12
  418. package/dist/render/rules-json.js.map +1 -1
  419. package/dist/render/suppressions.d.ts +3 -0
  420. package/dist/render/suppressions.d.ts.map +1 -1
  421. package/dist/render/suppressions.js +20 -1
  422. package/dist/render/suppressions.js.map +1 -1
  423. package/dist/rule-guide.d.ts +19 -0
  424. package/dist/rule-guide.d.ts.map +1 -1
  425. package/dist/rule-guide.js +14 -0
  426. package/dist/rule-guide.js.map +1 -1
  427. package/dist/run-semantic.d.ts.map +1 -1
  428. package/dist/run-semantic.js +2 -1
  429. package/dist/run-semantic.js.map +1 -1
  430. package/dist/run.d.ts +5 -1
  431. package/dist/run.d.ts.map +1 -1
  432. package/dist/run.js +31 -14
  433. package/dist/run.js.map +1 -1
  434. package/dist/setting-problems.d.ts.map +1 -1
  435. package/dist/setting-problems.js +3 -1
  436. package/dist/setting-problems.js.map +1 -1
  437. package/dist/spacing-minority.d.ts +16 -0
  438. package/dist/spacing-minority.d.ts.map +1 -0
  439. package/dist/spacing-minority.js +23 -0
  440. package/dist/spacing-minority.js.map +1 -0
  441. package/dist/stet-block.d.ts +5 -0
  442. package/dist/stet-block.d.ts.map +1 -0
  443. package/dist/stet-block.js +59 -0
  444. package/dist/stet-block.js.map +1 -0
  445. package/dist/stet.d.ts +4 -0
  446. package/dist/stet.d.ts.map +1 -1
  447. package/dist/stet.js +31 -11
  448. package/dist/stet.js.map +1 -1
  449. package/dist/structure/date-range.d.ts +10 -0
  450. package/dist/structure/date-range.d.ts.map +1 -1
  451. package/dist/structure/date-range.js +33 -1
  452. package/dist/structure/date-range.js.map +1 -1
  453. package/dist/structure/definition-use.d.ts +32 -0
  454. package/dist/structure/definition-use.d.ts.map +1 -0
  455. package/dist/structure/definition-use.js +102 -0
  456. package/dist/structure/definition-use.js.map +1 -0
  457. package/dist/structure/lazy-tree.d.ts +8 -0
  458. package/dist/structure/lazy-tree.d.ts.map +1 -0
  459. package/dist/structure/lazy-tree.js +14 -0
  460. package/dist/structure/lazy-tree.js.map +1 -0
  461. package/dist/structure/modal-conflict.d.ts +28 -0
  462. package/dist/structure/modal-conflict.d.ts.map +1 -0
  463. package/dist/structure/modal-conflict.js +74 -0
  464. package/dist/structure/modal-conflict.js.map +1 -0
  465. package/dist/structure/numbered-clauses.d.ts +4 -0
  466. package/dist/structure/numbered-clauses.d.ts.map +1 -0
  467. package/dist/structure/numbered-clauses.js +9 -0
  468. package/dist/structure/numbered-clauses.js.map +1 -0
  469. package/dist/structure/universal.d.ts.map +1 -1
  470. package/dist/structure/universal.js +8 -2
  471. package/dist/structure/universal.js.map +1 -1
  472. package/dist/style-parse.d.ts +6 -0
  473. package/dist/style-parse.d.ts.map +1 -1
  474. package/dist/style-parse.js +32 -4
  475. package/dist/style-parse.js.map +1 -1
  476. package/dist/subheading-line.d.ts +19 -2
  477. package/dist/subheading-line.d.ts.map +1 -1
  478. package/dist/subheading-line.js +39 -11
  479. package/dist/subheading-line.js.map +1 -1
  480. package/dist/tag-coverage.d.ts +8 -0
  481. package/dist/tag-coverage.d.ts.map +1 -0
  482. package/dist/tag-coverage.js +7 -0
  483. package/dist/tag-coverage.js.map +1 -0
  484. package/genres.yaml +42 -0
  485. package/package.json +7 -3
  486. package/rules/acronym-expansion-conflict.yaml +82 -0
  487. package/rules/adverb-overuse.yaml +2 -2
  488. package/rules/agentless-passive.yaml +28 -0
  489. package/rules/ai-tell.yaml +34 -0
  490. package/rules/announced-count-mismatch.yaml +2 -2
  491. package/rules/announcing-opener.yaml +31 -0
  492. package/rules/assistant-residue.yaml +30 -0
  493. package/rules/bold-density.yaml +28 -0
  494. package/rules/bold-label-list.yaml +81 -0
  495. package/rules/closing-cliche.yaml +27 -2
  496. package/rules/colon-lead-in.yaml +28 -4
  497. package/rules/contrast-framing.yaml +33 -2
  498. package/rules/cushion-phrase-density.yaml +32 -5
  499. package/rules/dangling-reference.yaml +1 -0
  500. package/rules/date-order.yaml +1 -0
  501. package/rules/date-range-reversed.yaml +2 -2
  502. package/rules/double-negative.yaml +51 -0
  503. package/rules/doubled-punctuation.yaml +2 -2
  504. package/rules/doubled-word.yaml +2 -2
  505. package/rules/duplicate-heading.yaml +78 -0
  506. package/rules/emoji-density.yaml +5 -4
  507. package/rules/empty-intensifier.yaml +25 -0
  508. package/rules/empty-section.yaml +74 -0
  509. package/rules/excessive-hedging.yaml +32 -4
  510. package/rules/fullwidth-alnum-consistency.yaml +53 -0
  511. package/rules/hankaku-kana.yaml +41 -0
  512. package/rules/image-alt-text.yaml +1 -1
  513. package/rules/invisible-character.yaml +63 -0
  514. package/rules/katakana-long-vowel.yaml +10 -9
  515. package/rules/latin-spacing.yaml +12 -5
  516. package/rules/max-kanji-continuous.yaml +1 -0
  517. package/rules/max-paragraph-length.yaml +1 -0
  518. package/rules/max-sentence-length.yaml +26 -0
  519. package/rules/modal-conflict.yaml +106 -0
  520. package/rules/name-variant.yaml +88 -0
  521. package/rules/no-doubled-joshi.yaml +1 -0
  522. package/rules/no-em-dash.yaml +26 -2
  523. package/rules/no-mixed-desumasu.yaml +1 -0
  524. package/rules/nominalization.yaml +89 -0
  525. package/rules/outside-announced-list.yaml +80 -0
  526. package/rules/padded-intro.yaml +25 -0
  527. package/rules/paragraph-length-variance.yaml +26 -2
  528. package/rules/preferred-term.yaml +1 -0
  529. package/rules/proper-noun-density.yaml +14 -5
  530. package/rules/ra-nuki.yaml +43 -0
  531. package/rules/requirement-modal.yaml +94 -0
  532. package/rules/requirement-smell.yaml +70 -0
  533. package/rules/rule-of-three.yaml +46 -0
  534. package/rules/section-length-uniformity.yaml +26 -2
  535. package/rules/sentence-rhythm.yaml +26 -0
  536. package/rules/space-before-punctuation.yaml +41 -0
  537. package/rules/spelling-consistency.yaml +47 -0
  538. package/rules/stock-transition.yaml +33 -2
  539. package/rules/taigen-dome-in-prose.yaml +14 -0
  540. package/rules/total-mismatch.yaml +1 -0
  541. package/rules/undefined-acronym.yaml +1 -0
  542. package/rules/unqualified-superlative.yaml +5 -4
  543. package/rules/unused-definition.yaml +75 -0
  544. package/rules/use-before-definition.yaml +77 -0
  545. package/rules/vague-figure-reference.yaml +66 -0
  546. package/skills/chaff/SKILL.md +108 -8
  547. package/src/acronym-expansions.ts +106 -0
  548. package/src/announced-count.ts +53 -11
  549. package/src/announced-list.ts +120 -0
  550. package/src/api.ts +154 -0
  551. package/src/cli-text.ts +6 -11
  552. package/src/cli.ts +64 -63
  553. package/src/closing-paragraph.ts +24 -0
  554. package/src/commands/compare.ts +11 -18
  555. package/src/commands/document-commands.ts +19 -0
  556. package/src/commands/eval.ts +3 -2
  557. package/src/commands/facts.ts +33 -0
  558. package/src/commands/feedback.ts +33 -1
  559. package/src/commands/fix-plan.ts +53 -0
  560. package/src/commands/outline.ts +49 -0
  561. package/src/commands/read-document.ts +24 -0
  562. package/src/commands/suppressions.ts +19 -0
  563. package/src/commands/test.ts +1 -1
  564. package/src/compare/clock-time.ts +5 -2
  565. package/src/compare/distinct.ts +13 -0
  566. package/src/compare/extract.ts +7 -5
  567. package/src/compare/fact-key.ts +31 -0
  568. package/src/compare/fact-text.ts +21 -2
  569. package/src/compare/facts-render.ts +63 -0
  570. package/src/compare/facts-text.ts +41 -0
  571. package/src/compare/match.ts +1 -1
  572. package/src/compare/outcome.ts +14 -4
  573. package/src/compare/proper-nouns.ts +4 -4
  574. package/src/compare/render.ts +2 -2
  575. package/src/compare/text-atoms.ts +7 -6
  576. package/src/compare/text.ts +3 -2
  577. package/src/config/load.ts +9 -0
  578. package/src/config/option-problems.ts +6 -4
  579. package/src/config/style.ts +14 -1
  580. package/src/custom/load.ts +11 -3
  581. package/src/custom/module-path.ts +23 -0
  582. package/src/custom/parse.ts +67 -18
  583. package/src/custom/problems.ts +63 -41
  584. package/src/detect-language.ts +1 -1
  585. package/src/detect.ts +10 -3
  586. package/src/detectors/ai-phrasing.ts +6 -2
  587. package/src/detectors/bold-label.ts +55 -0
  588. package/src/detectors/dangling-figure.ts +8 -4
  589. package/src/detectors/definition-use.ts +41 -0
  590. package/src/detectors/doubled-punctuation.ts +5 -2
  591. package/src/detectors/duplicate-heading.ts +47 -0
  592. package/src/detectors/empty-section.ts +21 -0
  593. package/src/detectors/fullwidth-alnum.ts +118 -0
  594. package/src/detectors/hankaku-kana.ts +38 -0
  595. package/src/detectors/heading-code-words.ts +2 -2
  596. package/src/detectors/index.ts +36 -0
  597. package/src/detectors/invisible-character.ts +190 -0
  598. package/src/detectors/kutoten-consistency.ts +2 -2
  599. package/src/detectors/lexicon-match.ts +33 -1
  600. package/src/detectors/lexicon.ts +40 -13
  601. package/src/detectors/list-sentence.ts +45 -4
  602. package/src/detectors/long-vowel.ts +20 -4
  603. package/src/detectors/markup-finding.ts +16 -1
  604. package/src/detectors/modal-conflict.ts +26 -0
  605. package/src/detectors/name-variant.ts +15 -0
  606. package/src/detectors/nominalization.ts +73 -0
  607. package/src/detectors/orthography.ts +77 -27
  608. package/src/detectors/outside-announced-list.ts +25 -0
  609. package/src/detectors/oxford-comma.ts +1 -0
  610. package/src/detectors/phrase-match.ts +27 -7
  611. package/src/detectors/ra-nuki.ts +38 -0
  612. package/src/detectors/requirement-modal.ts +67 -0
  613. package/src/detectors/requirement-smell.ts +52 -0
  614. package/src/detectors/space-before-punctuation.ts +32 -0
  615. package/src/detectors/spelling-variety.ts +81 -0
  616. package/src/detectors/structure-tree.ts +3 -1
  617. package/src/detectors/structure.ts +17 -3
  618. package/src/detectors/superlative-comparison.ts +47 -0
  619. package/src/detectors/url-run-on.ts +7 -1
  620. package/src/detectors/vague-figure-pointer.ts +58 -0
  621. package/src/detectors/word-list.ts +6 -1
  622. package/src/document.ts +88 -41
  623. package/src/extension/document-view.ts +109 -0
  624. package/src/extension/failure-text.ts +46 -0
  625. package/src/extension/import-default.ts +13 -0
  626. package/src/extension/load.ts +97 -0
  627. package/src/extension/module-detector.ts +61 -0
  628. package/src/extension/plugin-load.ts +82 -0
  629. package/src/extension/plugin-name.ts +26 -0
  630. package/src/extension/plugin-parse.ts +229 -0
  631. package/src/extension/problem-text.ts +103 -0
  632. package/src/extension/real-path.ts +10 -0
  633. package/src/extension/returned-findings.ts +62 -0
  634. package/src/extension/rule-export.ts +23 -0
  635. package/src/feedback/draft.ts +6 -0
  636. package/src/figure-references.ts +73 -12
  637. package/src/finding-order.ts +3 -0
  638. package/src/fix-plan/mode.ts +34 -0
  639. package/src/fix-plan/plan.ts +181 -0
  640. package/src/fix-plan/render.ts +128 -0
  641. package/src/fix-plan/text.ts +133 -0
  642. package/src/language-sample.ts +48 -0
  643. package/src/link-line.ts +13 -0
  644. package/src/long-vowel.ts +18 -8
  645. package/src/markup.ts +4 -2
  646. package/src/name-variants.ts +217 -0
  647. package/src/node-version.ts +22 -0
  648. package/src/not-run.ts +16 -0
  649. package/src/orthography.ts +40 -11
  650. package/src/outline/render.ts +85 -0
  651. package/src/outline/shape.ts +72 -0
  652. package/src/outline/text.ts +50 -0
  653. package/src/plugin.ts +21 -4
  654. package/src/reasons.ts +3 -0
  655. package/src/render/compact.ts +13 -3
  656. package/src/render/explain.ts +47 -20
  657. package/src/render/rules-json.ts +18 -12
  658. package/src/render/suppressions.ts +30 -2
  659. package/src/rule-guide.ts +38 -0
  660. package/src/run-semantic.ts +2 -1
  661. package/src/run.ts +37 -17
  662. package/src/setting-problems.ts +3 -1
  663. package/src/spacing-minority.ts +27 -0
  664. package/src/stet-block.ts +60 -0
  665. package/src/stet.ts +41 -15
  666. package/src/structure/date-range.ts +49 -1
  667. package/src/structure/definition-use.ts +130 -0
  668. package/src/structure/lazy-tree.ts +15 -0
  669. package/src/structure/modal-conflict.ts +86 -0
  670. package/src/structure/numbered-clauses.ts +11 -0
  671. package/src/structure/universal.ts +10 -2
  672. package/src/style-parse.ts +38 -3
  673. package/src/subheading-line.ts +51 -13
  674. package/src/tag-coverage.ts +13 -0
  675. package/styles/ieice.yaml +5 -4
  676. package/styles/jis-z8301-2011.yaml +5 -2
  677. package/styles/jis-z8301-2019.yaml +31 -0
  678. package/styles/koyobun.yaml +35 -0
  679. package/dist/legal-address.d.ts +0 -2
  680. package/dist/legal-address.d.ts.map +0 -1
  681. package/dist/legal-address.js +0 -16
  682. package/dist/legal-address.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { RuleDefinition } from "../plugin.ts";
1
+ import type { Finding, RuleDefinition } from "../plugin.ts";
2
2
  import type { RunResult } from "../run.ts";
3
3
  import { messageOf } from "./text.ts";
4
4
  import { counted } from "./plural.ts";
@@ -12,14 +12,24 @@ const TALLY: Texts<{ readonly findings: (n: number) => string; readonly notRun:
12
12
  en: { findings: (n) => counted(n, "finding"), notRun: (n) => `${counted(n, "rule")} not run`, separator: ", " },
13
13
  };
14
14
 
15
+ const POSITION_MIN_WIDTH = 8;
16
+ const SEVERITY_WIDTH = 8;
17
+
18
+ const positionOf = (finding: Finding): string => `${String(finding.line)}:${String(finding.column)}`;
19
+
20
+ /** 「行:桁」の列の幅。一番長い位置の後ろにも空白を 1 つ残す。grep や awk が重さの語と区切れるように。 */
21
+ export const positionWidthOf = (findings: readonly Finding[]): number =>
22
+ findings.reduce((width, finding) => Math.max(width, positionOf(finding).length + 1), POSITION_MIN_WIDTH);
23
+
15
24
  /** エンジニア向け。1 件 2 行。 */
16
25
  export const renderCompact = (header: string, result: RunResult, rules: readonly RuleDefinition[], language: string): string => {
17
26
  const byId = new Map(rules.map((rule) => [rule.id, rule]));
27
+ const width = positionWidthOf(result.findings);
18
28
  const lines = result.findings.flatMap((finding) => {
19
29
  const rule = byId.get(finding.rule);
20
30
  if (rule === undefined) return [];
21
- const at = `${finding.line}:${finding.column}`.padEnd(8);
22
- return [` ${at}${finding.severity.padEnd(8)}${messageOf(rule, finding, language)}`, ` ${" ".repeat(8)}${finding.rule}`];
31
+ const message = ` ${positionOf(finding).padEnd(width)}${finding.severity.padEnd(SEVERITY_WIDTH)}${messageOf(rule, finding, language)}`;
32
+ return [message, ` ${" ".repeat(width + SEVERITY_WIDTH)}${finding.rule}`];
23
33
  });
24
34
  const text = TALLY[uiLanguageOf(language)];
25
35
  const tail = [text.findings(result.findings.length), result.skipped.length > 0 ? text.notRun(result.skipped.length) : undefined].filter(
@@ -1,7 +1,7 @@
1
1
  import { definedLevels, resolve, severityAt } from "../levels.ts";
2
- import { readableText } from "./text.ts";
2
+ import { fill, localized, readableText } from "./text.ts";
3
3
  import { SEVERITY_NAME } from "./severity-name.ts";
4
- import { uiLanguageOf, type Texts, type UiLanguage } from "../ui.ts";
4
+ import { uiLanguageOf, type Texts } from "../ui.ts";
5
5
  import type { Level, RuleDefinition } from "../plugin.ts";
6
6
  import { optionLines } from "./options.ts";
7
7
  import type { OptionLayer } from "../rule-options.ts";
@@ -13,10 +13,12 @@ const TEXT: Texts<{
13
13
  readonly experimental: string;
14
14
  readonly howToFix: string;
15
15
  readonly example: string;
16
- readonly definedIn: string;
17
- readonly values: (unit: string) => string;
16
+ readonly definedIn: (plugin: string | undefined) => string;
17
+ readonly values: string;
18
+ readonly times: string;
18
19
  readonly severities: string;
19
20
  readonly now: (level: string) => string;
21
+ readonly nowNumber: (limit: string) => string;
20
22
  readonly from: (source: string) => string;
21
23
  readonly change: (id: string) => string;
22
24
  }> = {
@@ -27,10 +29,13 @@ const TEXT: Texts<{
27
29
  experimental: "このルールはまだ試験中で、既定では動きません(--experimental で動きます)。",
28
30
  howToFix: "直しかた",
29
31
  example: "例",
30
- definedIn: "このルールはチームが chaff.yaml の custom_rules で決めたものです。",
31
- values: (unit) => `設定できる値(単位: ${unit}):`,
32
+ definedIn: (plugin) =>
33
+ plugin === undefined ? "このルールはチームが chaff.yaml の custom_rules で決めたものです。" : `このルールはプラグイン ${plugin} のものです。`,
34
+ values: "設定できる値:",
35
+ times: "回",
32
36
  severities: "設定できる値(数える上限は無く、指摘の重さが変わります):",
33
37
  now: (level) => `いまは ${level} です。`,
38
+ nowNumber: (limit) => `いまは段階ではなく数です: ${limit}。`,
34
39
  from: (source) => `(${source} で決めています)`,
35
40
  change: (id) => `変える: npx chaff relax ${id} --why "理由"`,
36
41
  },
@@ -41,23 +46,38 @@ const TEXT: Texts<{
41
46
  experimental: "This rule is still experimental and does not run by default (--experimental runs it).",
42
47
  howToFix: "How to fix",
43
48
  example: "Example",
44
- definedIn: "The team defined this rule under custom_rules in chaff.yaml.",
45
- values: (unit) => `Levels (unit: ${unit}):`,
49
+ definedIn: (plugin) =>
50
+ plugin === undefined ? "The team defined this rule under custom_rules in chaff.yaml." : `This rule comes from the plugin ${plugin}.`,
51
+ values: "Levels:",
52
+ times: "times",
46
53
  severities: "Levels (there is no limit to count to; a level sets how a finding is marked):",
47
54
  now: (level) => `Now: ${level}.`,
55
+ nowNumber: (limit) => `Now: ${limit}, set as a number rather than a level.`,
48
56
  from: (source) => ` (set by ${source})`,
49
57
  change: (id) => `Change it: npx chaff relax ${id} --why "reason"`,
50
58
  },
51
59
  };
52
60
 
61
+ /**
62
+ * A limit in the rule's words for what it counts ("up to 8 emoji per 1000 words"), as the rule reference shows it.
63
+ * A rule that says nothing (a team's custom rule) counts matches, so its limit is a number of times.
64
+ */
65
+ const limitText = (rule: RuleDefinition, limit: number, language: string): string => {
66
+ const meaning = localized(rule.guide?.levelMeaning ?? {}, language);
67
+ return meaning === "" ? `${String(limit)} ${TEXT[uiLanguageOf(language)].times}` : fill(meaning, { limit });
68
+ };
69
+
53
70
  /** What a level holds: the limit a detector counts to, or the severity a rule with nothing to count gives. */
54
- const valueAt = (rule: RuleDefinition, level: Exclude<Level, "off">, genre: string | undefined, ui: UiLanguage): string =>
55
- rule.level_sets === "severity" ? SEVERITY_NAME[ui][severityAt(rule, level, genre)] : String(resolve(rule, level, genre).limit);
71
+ const valueAt = (rule: RuleDefinition, level: Exclude<Level, "off">, genre: string | undefined, language: string): string =>
72
+ rule.level_sets === "severity"
73
+ ? SEVERITY_NAME[uiLanguageOf(language)][severityAt(rule, level, genre)]
74
+ : limitText(rule, resolve(rule, level, genre).limit, language);
56
75
 
57
- const levelLine = (rule: RuleDefinition, level: Level, current: Level, genre: string | undefined, ui: UiLanguage): string => {
76
+ /** current is undefined when a number, not a level, sets the limit: no level is marked. */
77
+ const levelLine = (rule: RuleDefinition, level: Level, current: Level | undefined, genre: string | undefined, language: string): string => {
58
78
  const mark = level === current ? "→" : " ";
59
79
  const name = level.padEnd(9);
60
- return level === "off" ? ` ${mark} ${name}${TEXT[ui].off}` : ` ${mark} ${name}${valueAt(rule, level, genre, ui)}`;
80
+ return level === "off" ? ` ${mark} ${name}${TEXT[uiLanguageOf(language)].off}` : ` ${mark} ${name}${valueAt(rule, level, genre, language)}`;
61
81
  };
62
82
 
63
83
  /** ジャンルで数字が変わる rule は、それを言わないと「設定したのに効かない」に見える。 */
@@ -72,17 +92,24 @@ const exampleLines = (rule: RuleDefinition, language: string, text: (typeof TEXT
72
92
  const examples = rule.guide?.examples ?? {};
73
93
  const example = examples[language] ?? examples["en"] ?? Object.values(examples)[0];
74
94
  if (rule.custom === undefined || example === undefined) return [];
75
- return ["", ` ${text.example}: ${example.before}`, ` → ${example.after}`, "", ` ${text.definedIn}`];
95
+ return ["", ` ${text.example}: ${example.before}`, ` → ${example.after}`, "", ` ${text.definedIn(rule.plugin)}`];
76
96
  };
77
97
 
78
98
  /** Where the rule's settings come from: the option layers, strongest first, and the source of its level when a style set it. */
79
- export type ExplainSettings = { readonly optionLayers?: readonly OptionLayer[]; readonly levelFrom?: string | undefined };
99
+ /** limit: a number chaff.yaml or its style set for the rule (rules: { id: 80 }, a style's limits), which a level does not show. */
100
+ export type ExplainSettings = {
101
+ readonly optionLayers?: readonly OptionLayer[];
102
+ readonly levelFrom?: string | undefined;
103
+ readonly limit?: number | undefined;
104
+ };
80
105
 
81
106
  /** rule の意図と根拠を読む。指摘に納得できないときの入口。 */
82
- export const renderExplain = (rule: RuleDefinition, current: Level, language: string, unit: string, genre?: string, settings: ExplainSettings = {}): string => {
107
+ export const renderExplain = (rule: RuleDefinition, current: Level, language: string, genre?: string, settings: ExplainSettings = {}): string => {
83
108
  const { optionLayers = [], levelFrom } = settings;
84
- const ui = uiLanguageOf(language);
85
- const text = TEXT[ui];
109
+ // A rule with nothing to count has no number to set; chaff.yaml's number on it is reported as a problem and ignored.
110
+ const limit = rule.level_sets === "severity" ? undefined : settings.limit;
111
+ const marked = limit === undefined ? current : undefined;
112
+ const text = TEXT[uiLanguageOf(language)];
86
113
  const experimental = rule.status === "experimental" ? [` ${text.experimental}`] : [];
87
114
  return [
88
115
  "",
@@ -93,12 +120,12 @@ export const renderExplain = (rule: RuleDefinition, current: Level, language: st
93
120
  ` ${text.howToFix}: ${readableText(rule, rule.how_to_fix, language)}`,
94
121
  ...exampleLines(rule, language, text),
95
122
  "",
96
- ` ${rule.level_sets === "severity" ? text.severities : text.values(unit)}`,
97
- ...definedLevels(rule).map((level) => levelLine(rule, level, current, genre, ui)),
123
+ ` ${rule.level_sets === "severity" ? text.severities : text.values}`,
124
+ ...definedLevels(rule).map((level) => levelLine(rule, level, marked, genre, language)),
98
125
  ...genreNote(rule, genre, text),
99
126
  ...optionLines(rule, optionLayers, language),
100
127
  "",
101
- ` ${text.now(current)}${levelFrom === undefined ? "" : text.from(levelFrom)}`,
128
+ ` ${limit === undefined ? text.now(current) : text.nowNumber(limitText(rule, limit, language))}${levelFrom === undefined ? "" : text.from(levelFrom)}`,
102
129
  ...experimental,
103
130
  "",
104
131
  ` ${text.change(rule.id)}`,
@@ -8,9 +8,10 @@ import { presetLevelsOf, type PresetLevels } from "../genre-parse.ts";
8
8
  import { RULE_GROUPS, groupTextOf } from "../rule-guide.ts";
9
9
  import { standingIn } from "../rule-genres.ts";
10
10
  import { optionsJson } from "./options.ts";
11
- import { styleLevelSource } from "../config/style.ts";
11
+ import { limitsFor, styleLevelSource } from "../config/style.ts";
12
12
  import { loadStyles } from "../style-load.ts";
13
13
  import type { OptionLayer } from "../rule-options.ts";
14
+ import { CUSTOM_TYPES } from "../custom/parse.ts";
14
15
 
15
16
  const TEXT: Texts<{
16
17
  readonly offBySetting: string;
@@ -59,13 +60,15 @@ const TEXT: Texts<{
59
60
  },
60
61
  };
61
62
 
62
- const now = (rule: RuleDefinition, config: Config, genre: string, text: (typeof TEXT)["ja"], preset: PresetLevels): Record<string, unknown> => {
63
+ type Limits = Readonly<Record<string, number>>;
64
+
65
+ const now = (rule: RuleDefinition, config: Config, limits: Limits, genre: string, text: (typeof TEXT)["ja"], preset: PresetLevels): Record<string, unknown> => {
63
66
  // A run leaves out a rule whose use_for does not cover the genre before it reads any level.
64
67
  if (standingIn(rule, genre, {}).kind === "unsuited") return { level: "off", why_off: text.offUnsuited(genre) };
65
68
  const explicit = config.rules[rule.id] ?? preset[rule.id];
66
69
  if (explicit === "off" && config.rules[rule.id] === undefined) return { level: "off", why_off: text.offByGenre(genre) };
67
70
  if (explicit === "off") return { level: "off", why_off: text.offBySetting };
68
- const limit = rule.level_sets === "severity" ? undefined : config.limits[rule.id];
71
+ const limit = rule.level_sets === "severity" ? undefined : limits[rule.id];
69
72
  if (limit !== undefined) return { level: "normal", limit, set_as: "number" };
70
73
  if (explicit !== undefined) return { level: explicit, ...effectAt(rule, explicit, genre) };
71
74
  if (rule.status === "experimental" && !config.experimental) {
@@ -76,23 +79,23 @@ const now = (rule: RuleDefinition, config: Config, genre: string, text: (typeof
76
79
 
77
80
  /** The level a rule runs at now, and why it is off when it is: what `chaff rules` shows in its table. */
78
81
  export const nowFor = (rule: RuleDefinition, config: Config, language: string, genre: string): Record<string, unknown> =>
79
- now(rule, config, genre, TEXT[uiLanguageOf(language)], presetLevels(genre));
82
+ now(rule, config, limitsFor(config, language), genre, TEXT[uiLanguageOf(language)], presetLevels(genre));
80
83
 
81
84
  /** What a level does to a rule: the limit it counts to, or, with nothing to count, the severity of its findings. */
82
85
  const effectAt = (rule: RuleDefinition, level: Exclude<Level, "off">, genre: string): Record<string, unknown> =>
83
86
  rule.level_sets === "severity" ? { severity: severityAt(rule, level, genre) } : { limit: resolve(rule, level, genre).limit };
84
87
 
85
- const yourSetting = (rule: RuleDefinition, config: Config): Record<string, unknown> | null => {
88
+ const yourSetting = (rule: RuleDefinition, config: Config, limits: Limits): Record<string, unknown> | null => {
86
89
  const level = config.rules[rule.id];
87
90
  if (level === undefined) return null;
88
91
  const from = styleLevelSource(config, rule.id) ?? config.path;
89
- const limit = rule.level_sets === "severity" ? undefined : config.limits[rule.id];
92
+ const limit = rule.level_sets === "severity" ? undefined : limits[rule.id];
90
93
  return limit === undefined ? { level, from } : { level, limit, from };
91
94
  };
92
95
 
93
96
  /** The house style chaff.yaml names: what it decides and the guideline it follows, so an AI can say where a setting came from. */
94
97
  const styleOf = (config: Config): Record<string, unknown> | null => {
95
- const style = loadStyles().find((entry) => entry.id === config.applied?.style);
98
+ const style = [...loadStyles(), ...(config.extensions?.styles ?? [])].find((entry) => entry.id === config.applied?.style);
96
99
  return style === undefined ? null : { id: style.id, name: style.name, summary: style.summary, source: style.source };
97
100
  };
98
101
 
@@ -153,11 +156,11 @@ const groupsOf = (): Record<string, unknown>[] =>
153
156
 
154
157
  /**
155
158
  * What the local-rules release adds. Named now so an AI reading the JSON knows they are coming and does not invent them.
156
- * Filled in when they ship: the style presets under style:, the custom_rules types, and each rule's options.
159
+ * Filled in as they ship: the style presets under style:, each rule's options. The custom_rules types have shipped.
157
160
  */
158
161
  const COMING = {
159
162
  style_presets: { status: "coming", presets: [] },
160
- custom_rule_types: { status: "coming", planned: ["words", "pattern", "tokens", "module"] },
163
+ custom_rule_types: { status: "available", types: CUSTOM_TYPES },
161
164
  rule_options: { status: "coming", note: "Each rule's options (with their types and allowed values) will be listed under options." },
162
165
  };
163
166
 
@@ -171,6 +174,7 @@ export const rulesJson = (
171
174
  ): string => {
172
175
  const text = TEXT[uiLanguageOf(language)];
173
176
  const preset = presetLevels(genre);
177
+ const limits = limitsFor(config, language);
174
178
  return JSON.stringify(
175
179
  {
176
180
  // 2: rules gained group, summary, example, not_flagged, level_meaning, languages, requires and genres,
@@ -194,10 +198,12 @@ export const rulesJson = (
194
198
  level_sets: rule.level_sets,
195
199
  ...levelsOf(rule, genre),
196
200
  levels_you_can_set: definedLevels(rule),
197
- your_setting: yourSetting(rule, config),
198
- now: now(rule, config, genre, text, preset),
201
+ your_setting: yourSetting(rule, config, limits),
202
+ now: now(rule, config, limits, genre, text, preset),
199
203
  ...(rule.options === undefined ? {} : { options: optionsJson(rule, optionLayers) }),
200
- ...(rule.custom === undefined ? {} : { defined_in: "chaff.yaml custom_rules", custom: rule.custom }),
204
+ ...(rule.custom === undefined
205
+ ? {}
206
+ : { defined_in: rule.plugin === undefined ? "chaff.yaml custom_rules" : `plugin ${rule.plugin}`, custom: rule.custom }),
201
207
  ...guideOf(rule),
202
208
  })),
203
209
  how_to_write_settings_from_a_style_note: text.fromStyleNote,
@@ -1,4 +1,5 @@
1
1
  import type { Suppressed, Suppression } from "../stet.ts";
2
+ import type { NotRun } from "../not-run.ts";
2
3
  import type { Texts, UiLanguage } from "../ui.ts";
3
4
  import { counted } from "./plural.ts";
4
5
 
@@ -11,6 +12,8 @@ const TEXT: Texts<{
11
12
  readonly none: string;
12
13
  readonly total: (n: number) => string;
13
14
  readonly reasonless: (n: number) => string;
15
+ readonly notRun: string;
16
+ readonly runExperimental: string;
14
17
  }> = {
15
18
  ja: {
16
19
  nudge: " ← 設定の見直しを検討してください",
@@ -21,6 +24,8 @@ const TEXT: Texts<{
21
24
  none: "\n 抑制されている指摘はありません。\n",
22
25
  total: (n) => `抑制されている指摘: ${n} 件`,
23
26
  reasonless: (n) => `理由が書かれていない抑制: ${n} 件`,
27
+ notRun: "stet はあるが、今回動いていない rule(数えていません):",
28
+ runExperimental: "試験中の rule は --experimental を付けると数えます。",
24
29
  },
25
30
  en: {
26
31
  nudge: " <- consider changing the setting instead",
@@ -31,10 +36,18 @@ const TEXT: Texts<{
31
36
  none: "\n No findings are silenced.\n",
32
37
  total: (n) => `Silenced findings: ${n}`,
33
38
  reasonless: (n) => `Silenced without a reason: ${n}`,
39
+ notRun: "Rules with a stet that did not run in this check (not counted):",
40
+ runExperimental: "Experimental rules are counted with --experimental.",
34
41
  },
35
42
  };
36
43
 
37
- export type PerFile = { readonly path: string; readonly suppressed: readonly Suppressed[]; readonly reasonless: readonly Suppression[] };
44
+ export type PerFile = {
45
+ readonly path: string;
46
+ readonly suppressed: readonly Suppressed[];
47
+ readonly reasonless: readonly Suppression[];
48
+ /** Rules a stet in the file names that did not run, so their silenced findings could not be counted. */
49
+ readonly notRun: readonly NotRun[];
50
+ };
38
51
 
39
52
  /** これを超えたら、その rule は (b) ではなく (c) にすべきというサイン。 */
40
53
  const NUDGE_AT = 5;
@@ -72,6 +85,19 @@ const groupLines = (group: Group, ui: UiLanguage): string[] => {
72
85
  ];
73
86
  };
74
87
 
88
+ /** The rules whose stets were not counted because the rule did not run, one line each, with why and where. */
89
+ const notRunLines = (files: readonly PerFile[], ui: UiLanguage): string[] => {
90
+ const text = TEXT[ui];
91
+ const rows = files.flatMap((file) => file.notRun.map((entry) => ({ ...entry, path: file.path })));
92
+ if (rows.length === 0) return [];
93
+ const rules = [...new Set(rows.map((row) => row.rule))];
94
+ const lines = rules.map((rule) => {
95
+ const mine = rows.filter((row) => row.rule === rule);
96
+ return ` ${rule.padEnd(26)}${mine[0]?.why ?? ""} ${[...new Set(mine.map((row) => row.path))].join(", ")}`;
97
+ });
98
+ return ["", ` ${text.notRun}`, ...lines, ...(rows.some((row) => row.needsExperimental) ? [` ${text.runExperimental}`] : [])];
99
+ };
100
+
75
101
  /**
76
102
  * 抑制が溜まったことに気づけないと、規範と現実の乖離が静かに進む。
77
103
  * 同じ rule を何度も黙らせているなら、それは (b) ではなく (c) のサイン。
@@ -81,13 +107,15 @@ export const renderSuppressions = (files: readonly PerFile[], ui: UiLanguage = "
81
107
  const text = TEXT[ui];
82
108
  const groups = groupByRule(files);
83
109
  const total = groups.reduce((sum, group) => sum + group.count, 0);
84
- if (total === 0) return text.none;
110
+ const notRun = notRunLines(files, ui);
111
+ if (total === 0) return notRun.length === 0 ? text.none : [text.none.trimEnd(), ...notRun, ""].join("\n");
85
112
  const reasonless = files.flatMap((file) => file.reasonless.map(() => file.path));
86
113
  return [
87
114
  "",
88
115
  ` ${text.total(total)}`,
89
116
  ...groups.flatMap((group) => groupLines(group, ui)),
90
117
  ...(reasonless.length > 0 ? ["", ` ${text.reasonless(reasonless.length)}`, ` ${[...new Set(reasonless)].join(", ")}`] : []),
118
+ ...notRun,
91
119
  "",
92
120
  ].join("\n");
93
121
  };
package/src/rule-guide.ts CHANGED
@@ -80,6 +80,20 @@ export type ExampleOutcome = {
80
80
  readonly after: readonly ExampleFinding[];
81
81
  };
82
82
 
83
+ /** One spot written the way the rule flags it, and the same spot rewritten without adding a fact. */
84
+ export type RewritePair = { readonly before: string; readonly after: string };
85
+
86
+ /**
87
+ * How to rewrite a spot the rule flags, for an AI or a person doing the fix (chaff fix-plan prints it).
88
+ * direction: what to do; keep: what must not change; avoid: the mistakes a rewriter makes here.
89
+ */
90
+ export type RuleRewrite = {
91
+ readonly direction: string;
92
+ readonly pairs: readonly RewritePair[];
93
+ readonly keep: readonly string[];
94
+ readonly avoid: readonly string[];
95
+ };
96
+
83
97
  /** The plain-language part of a rule file: what the reference tells a reader, apart from the texts chaff prints. */
84
98
  export type RuleGuide = {
85
99
  readonly group: RuleGroup | undefined;
@@ -91,6 +105,10 @@ export type RuleGuide = {
91
105
  readonly notFlagged: Localized;
92
106
  /** What a level's number means, with {limit} for the number ("一文 {limit} 字まで"). None when every level is the same. */
93
107
  readonly levelMeaning: Localized;
108
+ /** The bibliography entries the rule rests on: the anchors of site/src/content/guide/{ja,en}/bibliography.md. */
109
+ readonly sources: readonly string[];
110
+ /** By language, optional per rule. */
111
+ readonly rewrite: Readonly<Record<string, RuleRewrite>>;
94
112
  };
95
113
 
96
114
  const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -121,6 +139,24 @@ const examplesOf = (value: unknown): Readonly<Record<string, RuleExample>> =>
121
139
  )
122
140
  : {};
123
141
 
142
+ const textsOf = (value: unknown): string[] => (Array.isArray(value) ? value.filter(isText) : []);
143
+
144
+ const pairOf = (value: unknown): RewritePair[] =>
145
+ isRecord(value) && isText(value["before"]) && isText(value["after"]) ? [{ before: value["before"], after: value["after"] }] : [];
146
+
147
+ /** A block that is written reads field by field; what is missing reads as empty, and the test on rule files names it. */
148
+ const rewriteOf = (value: Readonly<Record<string, unknown>>): RuleRewrite => ({
149
+ direction: isText(value["direction"]) ? value["direction"] : "",
150
+ pairs: Array.isArray(value["pairs"]) ? value["pairs"].flatMap(pairOf) : [],
151
+ keep: textsOf(value["keep"]),
152
+ avoid: textsOf(value["avoid"]),
153
+ });
154
+
155
+ const rewritesOf = (value: unknown): Readonly<Record<string, RuleRewrite>> =>
156
+ isRecord(value)
157
+ ? Object.fromEntries(Object.entries(value).flatMap(([language, entry]) => (isRecord(entry) ? [[language, rewriteOf(entry)] as const] : [])))
158
+ : {};
159
+
124
160
  /** A field that is missing or malformed reads as empty; the test on rule files names what a rule lacks. */
125
161
  export const ruleGuideOf = (raw: Readonly<Record<string, unknown>>): RuleGuide => ({
126
162
  group: RULE_GROUPS.find((group) => group === raw["group"]),
@@ -128,6 +164,8 @@ export const ruleGuideOf = (raw: Readonly<Record<string, unknown>>): RuleGuide =
128
164
  examples: examplesOf(raw["example"]),
129
165
  notFlagged: localizedOf(raw["not_flagged"]),
130
166
  levelMeaning: localizedOf(raw["level_meaning"]),
167
+ sources: textsOf(raw["sources"]),
168
+ rewrite: rewritesOf(raw["rewrite"]),
131
169
  });
132
170
 
133
171
  /** The rules in each group, in the order the reference lists them. A rule with no group is in none, which the test on rule files reports. */
@@ -6,6 +6,7 @@ import { localized } from "./render/text.ts";
6
6
  import type { AdapterNeeds, Finding, Level, ProseDocument, RuleDefinition, Severity } from "./plugin.ts";
7
7
  import { lineStarts, placeOf } from "./position.ts";
8
8
  import { wantsTags } from "./run.ts";
9
+ import { byPosition } from "./finding-order.ts";
9
10
 
10
11
  export type SemanticResult = {
11
12
  readonly findings: readonly Finding[];
@@ -137,7 +138,7 @@ export const runSemantic = async (
137
138
  ),
138
139
  );
139
140
  return {
140
- findings: answers.filter((finding) => finding !== undefined).toSorted((left, right) => left.line - right.line),
141
+ findings: answers.filter((finding) => finding !== undefined).toSorted(byPosition),
141
142
  skipped,
142
143
  asked: live.reduce((sum, job) => sum + job.candidates.length, 0),
143
144
  sentencesSeen: doc.sentences.length,
package/src/run.ts CHANGED
@@ -13,9 +13,18 @@ import { uiLanguageOf } from "./ui.ts";
13
13
  import { presetLevels } from "./genre-load.ts";
14
14
  import { bodySectionOf } from "./body-section.ts";
15
15
  import { optionValues, settleOptions, type OptionLayer } from "./rule-options.ts";
16
+ import { byPosition } from "./finding-order.ts";
16
17
  import { PatternTimeout } from "./custom/bounded-match.ts";
17
-
18
- export type Skipped = { readonly rule: string; readonly why: string };
18
+ import { PluginRuleFailure } from "./extension/module-detector.ts";
19
+ import { failureReason } from "./extension/failure-text.ts";
20
+ import { tagCoverage } from "./tag-coverage.ts";
21
+
22
+ export type Skipped = {
23
+ readonly rule: string;
24
+ readonly why: string;
25
+ /** Off only because it is experimental: --experimental (or chaff.yaml's experimental) would run it. */
26
+ readonly offUntilExperimental?: true;
27
+ };
19
28
 
20
29
  export type RunResult = {
21
30
  readonly findings: readonly Finding[];
@@ -40,9 +49,10 @@ const levelFor = (rule: RuleDefinition, settings: Settings, experimental: boolea
40
49
  };
41
50
 
42
51
  /** Why a rule at off did not run: chaff.yaml turned it off, the genre's preset did, or it is experimental. */
43
- const offReason = (rule: RuleDefinition, settings: Settings, preset: Settings, genre: string, reasons: Reasons): string => {
44
- if (settings[rule.id] !== undefined) return reasons.turnedOff;
45
- return preset[rule.id] === undefined ? reasons.experimental : reasons.presetOff(genre);
52
+ const offSkip = (rule: RuleDefinition, settings: Settings, preset: Settings, genre: string, reasons: Reasons): Skipped => {
53
+ if (settings[rule.id] !== undefined) return { rule: rule.id, why: reasons.turnedOff };
54
+ if (preset[rule.id] !== undefined) return { rule: rule.id, why: reasons.presetOff(genre) };
55
+ return { rule: rule.id, why: reasons.experimental, offUntilExperimental: true };
46
56
  };
47
57
 
48
58
  const reasonsFor = (doc: ProseDocument): Reasons => REASONS[uiLanguageOf(doc.language)];
@@ -102,9 +112,13 @@ const has = (capabilities: ProseDocument["capabilities"], need: string): boolean
102
112
  *
103
113
  * `capabilities.pos: true` と言いながら token を返さないアダプタでも、rule は
104
114
  * `sentence.tokens ?? []` を見るので**例外にならず、指摘 0 件で終わる**。
105
- * 0 件は「問題なし」と見分けがつかない。
115
+ * 0 件は「問題なし」と見分けがつかない。一部の文にだけ token が無いとき(解析器が読めなかった段落)も、その段落の 0 件が同じく保証に見える。
106
116
  */
107
- const hasTokens = (doc: ProseDocument): boolean => doc.sentences.length === 0 || doc.sentences.some((sentence) => sentence.tokens !== undefined);
117
+ const tagReason = (doc: ProseDocument): string | undefined => {
118
+ const coverage = tagCoverage(doc.sentences);
119
+ if (coverage === "all") return undefined;
120
+ return coverage === "none" ? reasonsFor(doc).noTags : reasonsFor(doc).unreadTags;
121
+ };
108
122
 
109
123
  /** 要求を満たさない rule は動かせない。満たさないまま動かすと「指摘 0 件」が保証に見える。 */
110
124
  const unmet = (rule: RuleDefinition, doc: ProseDocument): string | undefined => {
@@ -119,7 +133,7 @@ const unmet = (rule: RuleDefinition, doc: ProseDocument): string | undefined =>
119
133
  * 先に聞くと、止めている rule まで「アダプタが品詞を返さなかった」と、違う理由で出る。
120
134
  */
121
135
  const untagged = (rule: RuleDefinition, doc: ProseDocument): string | undefined =>
122
- rule.requires.some((need) => need === "pos" || need === "lemma") && !hasTokens(doc) ? reasonsFor(doc).noTags : undefined;
136
+ rule.requires.some((need) => need === "pos" || need === "lemma") ? tagReason(doc) : undefined;
123
137
 
124
138
  const forGenre = (rules: readonly RuleDefinition[], genre: string): RuleDefinition[] =>
125
139
  rules.filter((rule) => rule.use_for.some((target) => genre.startsWith(target)));
@@ -141,16 +155,21 @@ export const neededBy = (rules: readonly RuleDefinition[], settings: Settings, e
141
155
  /** The token features the rules read (RuleDefinition.token_features), each once. */
142
156
  export const tokenFeaturesOf = (rules: readonly RuleDefinition[]): string[] => [...new Set(rules.flatMap((rule) => rule.token_features ?? []))];
143
157
 
144
- /** A detector's findings, or how long it ran before a team's pattern was stopped (bounded-match.ts). Other errors are chaff's bugs and propagate. */
158
+ /**
159
+ * A detector's findings, or why the rule did not run: a team's pattern stopped at its time limit (bounded-match.ts), or a
160
+ * plugin's detector that threw or returned something else than findings (module-detector.ts). Other errors are chaff's
161
+ * bugs and propagate.
162
+ */
145
163
  const runDetector = (
146
164
  detector: Detector,
147
165
  doc: ProseDocument,
148
166
  options: DetectorOptions,
149
- ): { readonly findings: readonly Finding[] } | { readonly timedOut: number } => {
167
+ ): { readonly findings: readonly Finding[] } | { readonly notRun: string } => {
150
168
  try {
151
169
  return { findings: detector(doc, options) };
152
170
  } catch (error) {
153
- if (error instanceof PatternTimeout) return { timedOut: error.budget_ms };
171
+ if (error instanceof PatternTimeout) return { notRun: reasonsFor(doc).patternTimeout(error.budget_ms) };
172
+ if (error instanceof PluginRuleFailure) return { notRun: failureReason(error.origin, error.failure, uiLanguageOf(doc.language)) };
154
173
  throw error;
155
174
  }
156
175
  };
@@ -210,6 +229,8 @@ export type RunContext = {
210
229
  readonly limits?: Limits;
211
230
  /** Where rule options come from, strongest first (chaff.yaml). An option no layer sets is at its default. */
212
231
  readonly optionLayers?: readonly OptionLayer[];
232
+ /** The detectors loaded from the code chaff.yaml names, by rule id. They come before chaff's own table. */
233
+ readonly detectors?: Readonly<Record<string, Detector>>;
213
234
  };
214
235
 
215
236
  export const runRules = (
@@ -222,7 +243,7 @@ export const runRules = (
222
243
  ): RunResult => runRulesWith(doc, rules, { settings, experimental, genre, limits });
223
244
 
224
245
  export const runRulesWith = (doc: ProseDocument, rules: readonly RuleDefinition[], context: RunContext): RunResult => {
225
- const { settings, experimental, genre, limits = {}, optionLayers = [] } = context;
246
+ const { settings, experimental, genre, limits = {}, optionLayers = [], detectors = {} } = context;
226
247
  const preset = presetLevels(genre);
227
248
  const starts = lineStarts(doc.source);
228
249
  const applicable = forGenre(rules, genre);
@@ -239,8 +260,7 @@ export const runRulesWith = (doc: ProseDocument, rules: readonly RuleDefinition[
239
260
  const blocked = unmet(rule, doc);
240
261
  if (blocked !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: blocked }] };
241
262
  const level = levelFor(rule, settings, experimental, preset);
242
- if (level === "off")
243
- return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: offReason(rule, settings, preset, genre, reasonsFor(doc)) }] };
263
+ if (level === "off") return { findings: acc.findings, skipped: [...acc.skipped, offSkip(rule, settings, preset, genre, reasonsFor(doc))] };
244
264
  const noTags = untagged(rule, doc);
245
265
  if (noTags !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: noTags }] };
246
266
  // 木は capability ではなく、adapter が structure を持つかで決まる。持たない言語で動かすと「参照先が無い」が 0 件に見える。
@@ -251,7 +271,7 @@ export const runRulesWith = (doc: ProseDocument, rules: readonly RuleDefinition[
251
271
  if (noDocumentNeed !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: noDocumentNeed }] };
252
272
  // 複合シグナルは二段目で扱う。一段目では「検出器が無い」と言わせない。
253
273
  if (rule.from.length > 0) return acc;
254
- const detector = DETECTORS[rule.how_to_find];
274
+ const detector = detectors[rule.id] ?? DETECTORS[rule.how_to_find];
255
275
  if (detector === undefined)
256
276
  return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: reasonsFor(doc).noDetector(rule.how_to_find) }] };
257
277
  // 語彙表を要求する rule で、その言語に語彙表が無ければ動かせない。黙って通さない。
@@ -268,7 +288,7 @@ export const runRulesWith = (doc: ProseDocument, rules: readonly RuleDefinition[
268
288
  ...(rule.custom === undefined ? {} : { custom: rule.custom }),
269
289
  };
270
290
  const ran = runDetector(detector, doc, options);
271
- if ("timedOut" in ran) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: reasonsFor(doc).patternTimeout(ran.timedOut) }] };
291
+ if ("notRun" in ran) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: ran.notRun }] };
272
292
  const found = ran.findings.map((finding) => place(starts, { ...finding, rule: rule.id, severity: severityAt(rule, level, genre) }));
273
293
  return { findings: [...acc.findings, ...found], skipped: acc.skipped };
274
294
  },
@@ -280,5 +300,5 @@ export const runRulesWith = (doc: ProseDocument, rules: readonly RuleDefinition[
280
300
  compositeOf(rule, outcome.findings, limitFor(rule, levelFor(rule, settings, experimental, preset), genre, limits), starts, doc.language),
281
301
  );
282
302
  const all = [...outcome.findings, ...composites];
283
- return { ...outcome, findings: all.toSorted((left, right) => left.line - right.line), forcedExperimental: forced, presetExperimental: presetOn };
303
+ return { ...outcome, findings: all.toSorted(byPosition), forcedExperimental: forced, presetExperimental: presetOn };
284
304
  };
@@ -4,6 +4,7 @@ import type { UiLanguage } from "./ui.ts";
4
4
  import { styleProblems } from "./config/style.ts";
5
5
  import { loadStyles } from "./style-load.ts";
6
6
  import { customRuleProblems } from "./custom/problems.ts";
7
+ import { extensionProblems } from "./extension/problem-text.ts";
7
8
  import { GENRES } from "./genre.ts";
8
9
  import { unknownGenres, writtenGenres } from "./genre-check.ts";
9
10
  import { loadProfiles } from "./profile/load.ts";
@@ -25,6 +26,7 @@ const profileProblems = (command: string, config: Config, text: CliText): string
25
26
  export const settingProblems = (command: string, genreFlag: string | undefined, config: Config, text: CliText, ui: UiLanguage = "en"): string[] => [
26
27
  ...genreProblems(command, genreFlag, config, text),
27
28
  ...profileProblems(command, config, text),
28
- ...styleProblems(config, loadStyles(), ui),
29
+ ...styleProblems(config, [...loadStyles(), ...(config.extensions?.styles ?? [])], ui),
29
30
  ...customRuleProblems(config, ui),
31
+ ...extensionProblems(config, ui),
30
32
  ];
@@ -0,0 +1,27 @@
1
+ import { minorityStyle } from "./orthography.ts";
2
+
3
+ /**
4
+ * 少ないほうがこの割合を超えると、書き損じではなく、書き方が二通りある文書と読む。1 箇所ずつ「違う」と指すと、
5
+ * 半々に近い文書でどちらかの側に立つことになる。そのときは 1 件にまとめて、両方の数を言う。
6
+ */
7
+ const MIXED_SHARE = 0.2;
8
+
9
+ /** 割合が大きくても、これより少なければ 1 箇所ずつ言う。3 箇所のうち 1 箇所なら、その 1 箇所を見せるほうが早い。 */
10
+ const MIN_MIXED = 5;
11
+
12
+ /** each は少ないほうを 1 箇所ずつ、mixed は文書に書き方が二通りあることを 1 件で。first は少ないほうの最初の箇所。 */
13
+ export type MinorityReport<T> =
14
+ | { readonly mode: "each"; readonly odd: readonly T[] }
15
+ | { readonly mode: "mixed"; readonly first: T; readonly odd: number; readonly spaced: number; readonly touching: number };
16
+
17
+ /** 一つの種類の境目から、何をどう言うか。混ざっていない、または少ないほうが limit に届かなければ言わない。 */
18
+ export const minorityReport = <T extends { readonly spaced: boolean }>(ofKind: readonly T[], limit: number): MinorityReport<T> | undefined => {
19
+ const minority = minorityStyle(ofKind);
20
+ if (minority === undefined) return undefined;
21
+ const odd = ofKind.filter((entry) => entry.spaced === minority);
22
+ const first = odd[0];
23
+ if (first === undefined || odd.length < limit) return undefined;
24
+ if (odd.length < MIN_MIXED || odd.length <= ofKind.length * MIXED_SHARE) return { mode: "each", odd };
25
+ const spaced = ofKind.filter((entry) => entry.spaced).length;
26
+ return { mode: "mixed", first, odd: odd.length, spaced, touching: ofKind.length - spaced };
27
+ };