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
@@ -18,13 +18,26 @@ export const withStyle = (config: Config, styles: readonly StyleDefinition[]): C
18
18
  const style = styles.find((entry) => entry.id === config.style);
19
19
  if (style === undefined) return config;
20
20
  const fromStyle = Object.keys(style.rules).filter((id) => config.rules[id] === undefined && config.limits[id] === undefined);
21
+ const limits = Object.fromEntries(Object.entries(style.limits).filter(([id]) => fromStyle.includes(id)));
21
22
  return {
22
23
  ...config,
23
24
  rules: { ...style.rules, ...config.rules },
24
- applied: { style: style.id, levelsFrom: fromStyle, options: style.options },
25
+ applied: { style: style.id, levelsFrom: fromStyle, options: style.options, limits },
25
26
  };
26
27
  };
27
28
 
29
+ /**
30
+ * The numeric limits a run of a document in this language uses: chaff.yaml's own, and under them the style's for this
31
+ * language. A style that gives a number only for Japanese leaves an English document at the level's number.
32
+ */
33
+ export const limitsFor = (config: Pick<Config, "limits" | "applied">, language: string): Readonly<Record<string, number>> => {
34
+ const fromStyle = Object.entries(config.applied?.limits ?? {}).flatMap(([id, byLanguage]): [string, number][] => {
35
+ const limit = byLanguage[language];
36
+ return limit === undefined ? [] : [[id, limit]];
37
+ });
38
+ return { ...Object.fromEntries(fromStyle), ...config.limits };
39
+ };
40
+
28
41
  const TEXT: Texts<{ readonly unknown: (where: string, style: string, known: string) => string }> = {
29
42
  ja: {
30
43
  unknown: (where, style, known) => `${where}: style: ${style} というスタイルはありません。使えるのは ${known} です`,
@@ -5,11 +5,19 @@ import { loadGenres } from "../genre-load.ts";
5
5
  import { parseCustomRules, type CustomRules } from "./parse.ts";
6
6
 
7
7
  /** The team's custom_rules as rules, with what could not be read. A team's rule applies to every genre, like the team's words. */
8
- export const customRulesOf = (config: Pick<Config, "customRules">): CustomRules =>
8
+ export const customRulesOf = (config: Pick<Config, "customRules" | "baseDir">): CustomRules =>
9
9
  parseCustomRules(config.customRules, {
10
10
  builtIn: new Set(loadRules("en").map((rule) => rule.id)),
11
11
  useFor: loadGenres().groups.map((group) => group.id),
12
+ baseDir: config.baseDir,
12
13
  });
13
14
 
14
- /** Every rule a run knows for a language: chaff's own, then the team's. Where the team's cannot be read, settingProblems stops the run. */
15
- export const rulesOf = (language: string, config: Pick<Config, "customRules">): RuleDefinition[] => [...loadRules(language), ...customRulesOf(config).rules];
15
+ /**
16
+ * Every rule a run knows for a language: chaff's own, the team's, then the plugins'. Where the team's or a plugin's
17
+ * cannot be read, settingProblems stops the run.
18
+ */
19
+ export const rulesOf = (language: string, config: Pick<Config, "customRules" | "baseDir" | "extensions">): RuleDefinition[] => [
20
+ ...loadRules(language),
21
+ ...customRulesOf(config).rules,
22
+ ...(config.extensions?.rules ?? []),
23
+ ];
@@ -0,0 +1,23 @@
1
+ import { isAbsolute, relative, resolve, sep } from "node:path";
2
+
3
+ // Where a type: module rule's file is. Loading it runs its code, so a relative path may not leave the folder chaff.yaml
4
+ // is in: a chaff.yaml copied from elsewhere cannot reach for ../../somewhere. An absolute path is written on purpose and
5
+ // is used as written. Pure: no file is read.
6
+
7
+ export type ModulePathRefusal = "outside";
8
+
9
+ export type ModulePath = { readonly file: string } | { readonly refusal: ModulePathRefusal };
10
+
11
+ /** Whether file is inside dir (dir itself is not). Reads no file: the same check runs on real paths at load time. */
12
+ export const isInside = (dir: string, file: string): boolean => {
13
+ const fromDir = relative(dir, file);
14
+ const leaves = fromDir === ".." || fromDir.startsWith(`..${sep}`);
15
+ return fromDir !== "" && !leaves && !isAbsolute(fromDir);
16
+ };
17
+
18
+ /** The file a module path names, relative to baseDir (where chaff.yaml is) unless it is absolute. */
19
+ export const modulePathOf = (written: string, baseDir: string): ModulePath => {
20
+ if (isAbsolute(written)) return { file: written };
21
+ const file = resolve(baseDir, written);
22
+ return isInside(baseDir, file) ? { file } : { refusal: "outside" };
23
+ };
@@ -1,14 +1,12 @@
1
1
  import type { CustomSpec, LevelTable, Localized, RuleDefinition, Severity, TokenCondition } from "../plugin.ts";
2
2
  import { regexRefusal, type RegexRefusal } from "./regex-safety.ts";
3
3
  import { posTags } from "./token-pattern.ts";
4
+ import { modulePathOf, type ModulePathRefusal } from "./module-path.ts";
4
5
 
5
6
  // custom_rules in chaff.yaml: a team's own deterministic rules, written without code. Each becomes a RuleDefinition like the
6
7
  // built-in ones, so findings, explain, rules --json, stet, the baseline and SARIF treat it the same. Pure: the YAML comes in parsed.
7
8
 
8
- export const CUSTOM_TYPES = ["words", "pattern", "tokens"] as const;
9
-
10
- /** Reserved for a small Node function a team writes. Refused with its own message until it is supported. */
11
- const RESERVED_TYPES: readonly string[] = ["module"];
9
+ export const CUSTOM_TYPES = ["words", "pattern", "tokens", "module"] as const;
12
10
 
13
11
  export type CustomProblem =
14
12
  | { readonly kind: "not-a-list" }
@@ -17,7 +15,9 @@ export type CustomProblem =
17
15
  | { readonly kind: "duplicate-id"; readonly at: string }
18
16
  | { readonly kind: "built-in-id"; readonly at: string }
19
17
  | { readonly kind: "unknown-type"; readonly at: string; readonly written: string }
20
- | { readonly kind: "not-yet"; readonly at: string; readonly written: string }
18
+ | { readonly kind: "bad-module"; readonly at: string; readonly written: string; readonly refusal: ModulePathRefusal }
19
+ | { readonly kind: "bad-requires"; readonly at: string; readonly written: string }
20
+ | { readonly kind: "bad-word-list"; readonly at: string; readonly written: string }
21
21
  | { readonly kind: "missing"; readonly at: string; readonly field: string }
22
22
  | { readonly kind: "unpaired-example"; readonly at: string }
23
23
  | { readonly kind: "bad-level"; readonly at: string; readonly written: string }
@@ -30,8 +30,11 @@ export type CustomProblem =
30
30
 
31
31
  export type CustomRules = { readonly rules: readonly RuleDefinition[]; readonly problems: readonly CustomProblem[] };
32
32
 
33
- /** What parsing needs to know: the built-in rule ids (a team's rule may not take one) and the genres a rule applies to. */
34
- export type CustomContext = { readonly builtIn: ReadonlySet<string>; readonly useFor: readonly string[] };
33
+ /**
34
+ * What parsing needs to know: the built-in rule ids (a team's rule may not take one), the genres a rule applies to, and
35
+ * the folder a module's path is relative to (where chaff.yaml is).
36
+ */
37
+ export type CustomContext = { readonly builtIn: ReadonlySet<string>; readonly useFor: readonly string[]; readonly baseDir: string };
35
38
 
36
39
  const RULE_ID = /^[a-z][a-z0-9-]*$/u;
37
40
  const SEVERITIES: readonly Severity[] = ["info", "warning", "error"];
@@ -113,16 +116,24 @@ const tokensOf = (raw: unknown, at: string): Checked<CustomSpec> => {
113
116
  return problems.length > 0 ? failed(...problems) : ok({ type: "tokens", tokens });
114
117
  };
115
118
 
116
- const specOf = (raw: Record<string, unknown>, type: string, at: string): Checked<CustomSpec> => {
119
+ const moduleOf = (raw: Record<string, unknown>, at: string, baseDir: string): Checked<CustomSpec> => {
120
+ const written = raw["module"];
121
+ if (!nonEmpty(written)) return failed({ kind: "missing", at, field: "module" });
122
+ const path = modulePathOf(written.trim(), baseDir);
123
+ if ("refusal" in path) return failed({ kind: "bad-module", at, written, refusal: path.refusal });
124
+ return ok({ type: "module", module: written.trim(), file: path.file });
125
+ };
126
+
127
+ const specOf = (raw: Record<string, unknown>, type: string, at: string, baseDir: string): Checked<CustomSpec> => {
117
128
  if (type === "words") return wordsOf(raw["words"], at);
118
129
  if (type === "pattern") return patternOf(raw, at);
130
+ if (type === "module") return moduleOf(raw, at, baseDir);
119
131
  return tokensOf(raw["tokens"], at);
120
132
  };
121
133
 
122
134
  const typeOf = (raw: Record<string, unknown>, at: string): Checked<string> => {
123
135
  const type = raw["type"];
124
136
  if (!nonEmpty(type)) return failed({ kind: "missing", at, field: "type" });
125
- if (RESERVED_TYPES.includes(type)) return failed({ kind: "not-yet", at, written: type });
126
137
  return CUSTOM_TYPES.some((known) => known === type) ? ok(type) : failed({ kind: "unknown-type", at, written: type });
127
138
  };
128
139
 
@@ -157,6 +168,33 @@ const textsOf = (raw: Record<string, unknown>, at: string): Checked<Texts> => {
157
168
  return ok({ name, why, how_to_fix: howToFix, before, after });
158
169
  };
159
170
 
171
+ /** What a module's detector may ask the adapter for. Only parts of speech: without them, sentences have no tokens. */
172
+ const REQUIRABLE: readonly string[] = ["pos"];
173
+
174
+ const requiresOf = (raw: unknown, type: string, at: string): Checked<readonly string[]> => {
175
+ if (type === "tokens") return ok(["pos"]);
176
+ if (raw === undefined || type !== "module") return ok([]);
177
+ const written = (Array.isArray(raw) ? raw : [raw]).map((entry: unknown) => (typeof entry === "string" ? entry : printed(entry)));
178
+ const unknown = written.find((need) => !REQUIRABLE.includes(need));
179
+ return unknown === undefined ? ok([...new Set(written)]) : failed({ kind: "bad-requires", at, written: unknown });
180
+ };
181
+
182
+ /** A module's word list by name; its detector gets it as options.lexicon. The other types carry their words themselves. */
183
+ const wordListOf = (raw: unknown, type: string, at: string): Checked<string | undefined> => {
184
+ if (raw === undefined || type !== "module") return ok(undefined);
185
+ return nonEmpty(raw) ? ok(raw.trim()) : failed({ kind: "bad-word-list", at, written: printed(raw) });
186
+ };
187
+
188
+ /** What a rule's detector needs besides the document: tokens from the adapter, and a word list. */
189
+ type Needs = { readonly requires: readonly string[]; readonly wordList: string | undefined };
190
+
191
+ const needsOf = (raw: Record<string, unknown>, type: string, at: string): Checked<Needs> => {
192
+ const requires = requiresOf(raw["requires"], type, at);
193
+ const wordList = wordListOf(raw["word_list"], type, at);
194
+ if (requires.value === undefined || wordList.problems.length > 0) return failed(...requires.problems, ...wordList.problems);
195
+ return ok({ requires: requires.value, wordList: wordList.value });
196
+ };
197
+
160
198
  const languagesOf = (raw: unknown, at: string): Checked<readonly string[] | undefined> => {
161
199
  if (raw === undefined) return ok(undefined);
162
200
  const list = Array.isArray(raw) ? raw : [raw];
@@ -175,12 +213,19 @@ const PLACEHOLDERS: Readonly<Record<string, Localized>> = {
175
213
  preferred: { ja: "使う書き方", en: "the spelling to use" },
176
214
  };
177
215
 
178
- const HOW_TO_FIND: Readonly<Record<CustomSpec["type"], string>> = { words: "custom-words", pattern: "custom-pattern", tokens: "custom-tokens" };
216
+ /** A module's detector is not in chaff's table: it is loaded from the module and found by the rule's id. */
217
+ const HOW_TO_FIND: Readonly<Record<CustomSpec["type"], string>> = {
218
+ words: "custom-words",
219
+ pattern: "custom-pattern",
220
+ tokens: "custom-tokens",
221
+ module: "module",
222
+ };
179
223
 
180
224
  type Example = { readonly before: string; readonly after: string };
181
225
 
182
226
  type Parts = {
183
227
  readonly spec: CustomSpec;
228
+ readonly needs: Needs;
184
229
  readonly severity: Severity;
185
230
  readonly texts: Texts;
186
231
  readonly languages: readonly string[] | undefined;
@@ -200,7 +245,7 @@ const examplesOf = (texts: Texts, languages: readonly string[] | undefined): Rec
200
245
 
201
246
  const definitionOf = (id: string, raw: Record<string, unknown>, parts: Parts, useFor: readonly string[]): RuleDefinition => ({
202
247
  id,
203
- layer: parts.spec.type === "tokens" ? "L3" : "L2",
248
+ layer: parts.needs.requires.includes("pos") ? "L3" : "L2",
204
249
  status: "stable",
205
250
  name: parts.texts.name,
206
251
  why: parts.texts.why,
@@ -212,12 +257,12 @@ const definitionOf = (id: string, raw: Record<string, unknown>, parts: Parts, us
212
257
  level_sets: "severity",
213
258
  by_genre: {},
214
259
  how_to_find: HOW_TO_FIND[parts.spec.type],
215
- word_list: undefined,
260
+ word_list: parts.needs.wordList,
216
261
  extra_word_lists: [],
217
262
  what_to_check: undefined,
218
263
  where: undefined,
219
264
  full_sentence: undefined,
220
- requires: parts.spec.type === "tokens" ? ["pos"] : [],
265
+ requires: parts.needs.requires,
221
266
  uses: [],
222
267
  from: [],
223
268
  languages: parts.languages,
@@ -231,6 +276,8 @@ const definitionOf = (id: string, raw: Record<string, unknown>, parts: Parts, us
231
276
  examples: parts.examples,
232
277
  notFlagged: {},
233
278
  levelMeaning: {},
279
+ sources: [],
280
+ rewrite: {},
234
281
  },
235
282
  });
236
283
 
@@ -247,16 +294,18 @@ const ruleOf = (raw: unknown, index: number, seen: ReadonlySet<string>, context:
247
294
  const id = idOf(raw, index, seen, context.builtIn);
248
295
  const at = id.value ?? (nonEmpty(raw["id"]) ? raw["id"] : `#${String(index + 1)}`);
249
296
  const type = typeOf(raw, at);
250
- const spec = type.value === undefined ? failed<CustomSpec>() : specOf(raw, type.value, at);
297
+ const spec = type.value === undefined ? failed<CustomSpec>() : specOf(raw, type.value, at, context.baseDir);
298
+ const needs = needsOf(raw, type.value ?? "", at);
251
299
  const severity = severityOf(raw["level"], at);
252
300
  const texts = textsOf(raw, at);
253
301
  const languages = languagesOf(raw["languages"], at);
254
302
  const examples = texts.value === undefined ? {} : examplesOf(texts.value, languages.value);
255
303
  const unpaired: CustomProblem[] = texts.value !== undefined && Object.keys(examples).length === 0 ? [{ kind: "unpaired-example", at }] : [];
256
- const problems = [...[id, type, spec, severity, texts, languages].flatMap((checked) => checked.problems), ...unpaired];
257
- if (id.value === undefined || spec.value === undefined || severity.value === undefined || texts.value === undefined || problems.length > 0)
258
- return failed(...problems);
259
- const parts = { spec: spec.value, severity: severity.value, texts: texts.value, languages: languages.value, examples };
304
+ const problems = [...[id, type, spec, needs, severity, texts, languages].flatMap((checked) => checked.problems), ...unpaired];
305
+ if (problems.length > 0) return failed(...problems);
306
+ if (id.value === undefined || spec.value === undefined || needs.value === undefined || severity.value === undefined || texts.value === undefined)
307
+ return failed();
308
+ const parts = { spec: spec.value, needs: needs.value, severity: severity.value, texts: texts.value, languages: languages.value, examples };
260
309
  return ok(definitionOf(id.value, raw, parts, context.useFor));
261
310
  };
262
311
 
@@ -4,12 +4,20 @@ import type { CustomProblem } from "./parse.ts";
4
4
  import { customRulesOf } from "./load.ts";
5
5
  import { MAX_PATTERN_LENGTH, MAX_REPEATS, type RegexRefusal } from "./regex-safety.ts";
6
6
  import { POS_WRITTEN_NAMES } from "./token-pattern.ts";
7
+ import type { ModulePathRefusal } from "./module-path.ts";
7
8
 
8
9
  /** Each problem's sentence, with {at}, {written}, {field}, {index}, {refusal} and {names} filled in from the problem. */
9
- type Text = { readonly problems: Readonly<Record<CustomProblem["kind"], string>>; readonly refusal: Readonly<Record<RegexRefusal, string>> };
10
+ type Text = {
11
+ readonly problems: Readonly<Record<CustomProblem["kind"], string>>;
12
+ readonly refusal: Readonly<Record<RegexRefusal, string>>;
13
+ readonly modulePath: Readonly<Record<ModulePathRefusal, string>>;
14
+ };
10
15
 
11
16
  const TEXT: Texts<Text> = {
12
17
  ja: {
18
+ modulePath: {
19
+ outside: "chaff.yaml のあるフォルダの外です。読み込むとそのコードが動くので、外のファイルは絶対パスで名指ししてください",
20
+ },
13
21
  refusal: {
14
22
  "too-long": `${String(MAX_PATTERN_LENGTH)} 字を超えています。いくつかのルールに分けてください`,
15
23
  "nested-quantifier": "繰り返しの中に繰り返しがあります((a+)+ のような形)。長い行で止まらなくなるので使えません",
@@ -19,25 +27,30 @@ const TEXT: Texts<Text> = {
19
27
  invalid: "正規表現として読めません",
20
28
  },
21
29
  problems: {
22
- "not-a-list": "custom_rules はルールの並び(- id: …)で書いてください",
23
- "not-a-map": "custom_rules の {at}: id や type を持つ項目として書いてください",
24
- "bad-id": "custom_rules の {at}: id は英小文字で始まり、英小文字・数字・ハイフンだけで書きます(例: team-no-tbd)",
25
- "duplicate-id": "custom_rules の {at}: 同じ id のルールがもう一つあります",
26
- "built-in-id": "custom_rules の {at}: chaff のルールと同じ id です。別の id にしてください",
27
- "unknown-type": "custom_rules の {at}: type: {written} は使えません(words / pattern / tokens)",
28
- "not-yet": "custom_rules の {at}: type: {written} はまだ使えません(words / pattern / tokens)",
29
- missing: "custom_rules の {at}: {field} がありません",
30
- "unpaired-example": "custom_rules の {at}: example の before と after を、ルールが見る言語の同じ言語で書いてください",
31
- "bad-level": "custom_rules の {at}: level: {written} は読めません(error / warning / info)",
32
- "bad-languages": "custom_rules の {at}: languages は言語の並びで書いてください([ja] や [ja, en])",
33
- "no-words": "custom_rules の {at}: words に語がありません(「使わない書き方: 使う書き方」か語の並び)",
34
- "no-tokens": "custom_rules の {at}: tokens に語の条件がありません(- { pos: 名詞 } のように並べます)",
35
- "bad-token": "custom_rules の {at}: tokens の {index} 番目に pos・base・surface のどれもありません",
36
- "unknown-pos": "custom_rules の {at}: 品詞 {written} は知りません({names})",
37
- "bad-pattern": "custom_rules の {at}: pattern を使えません。{refusal}",
30
+ "not-a-list": "{scope} はルールの並び(- id: …)で書いてください",
31
+ "not-a-map": "{scope} の {at}: id や type を持つ項目として書いてください",
32
+ "bad-id": "{scope} の {at}: id は英小文字で始まり、英小文字・数字・ハイフンだけで書きます(例: team-no-tbd)",
33
+ "duplicate-id": "{scope} の {at}: 同じ id のルールがもう一つあります",
34
+ "built-in-id": "{scope} の {at}: chaff のルールと同じ id です。別の id にしてください",
35
+ "unknown-type": "{scope} の {at}: type: {written} は使えません(words / pattern / tokens / module)",
36
+ "bad-module": "{scope} の {at}: module: {written} は使えません。{refusal}",
37
+ "bad-requires": "{scope} の {at}: requires: {written} は求められません(求められるのは pos だけです)",
38
+ "bad-word-list": "{scope} の {at}: word_list: {written} は語彙表の名前として読めません",
39
+ missing: "{scope} の {at}: {field} がありません",
40
+ "unpaired-example": "{scope} の {at}: example の before と after を、ルールが見る言語の同じ言語で書いてください",
41
+ "bad-level": "{scope} の {at}: level: {written} は読めません(error / warning / info)",
42
+ "bad-languages": "{scope} の {at}: languages は言語の並びで書いてください([ja] や [ja, en])",
43
+ "no-words": "{scope} の {at}: words に語がありません(「使わない書き方: 使う書き方」か語の並び)",
44
+ "no-tokens": "{scope} の {at}: tokens に語の条件がありません(- { pos: 名詞 } のように並べます)",
45
+ "bad-token": "{scope} の {at}: tokens の {index} 番目に pos・base・surface のどれもありません",
46
+ "unknown-pos": "{scope} の {at}: 品詞 {written} は知りません({names})",
47
+ "bad-pattern": "{scope} の {at}: pattern を使えません。{refusal}",
38
48
  },
39
49
  },
40
50
  en: {
51
+ modulePath: {
52
+ outside: "it is outside the folder chaff.yaml is in. Loading a module runs its code, so name a file outside by its absolute path",
53
+ },
41
54
  refusal: {
42
55
  "too-long": `it is longer than ${String(MAX_PATTERN_LENGTH)} characters; split it into several rules`,
43
56
  "nested-quantifier": "it repeats something that repeats (a shape like (a+)+), which can run for minutes on a long line",
@@ -47,43 +60,52 @@ const TEXT: Texts<Text> = {
47
60
  invalid: "it is not a regular expression",
48
61
  },
49
62
  problems: {
50
- "not-a-list": "write custom_rules as a list of rules (- id: …)",
51
- "not-a-map": "custom_rules {at}: write each rule as an entry with id and type",
52
- "bad-id": "custom_rules {at}: an id starts with a lowercase letter and has only lowercase letters, digits and hyphens (team-no-tbd)",
53
- "duplicate-id": "custom_rules {at}: another rule has the same id",
54
- "built-in-id": "custom_rules {at}: chaff has a rule with this id; choose another",
55
- "unknown-type": "custom_rules {at}: type: {written} is not a type (words / pattern / tokens)",
56
- "not-yet": "custom_rules {at}: type: {written} is not supported yet (words / pattern / tokens)",
57
- missing: "custom_rules {at}: {field} is missing",
58
- "unpaired-example": "custom_rules {at}: write example's before and after in the same language, one the rule checks",
59
- "bad-level": "custom_rules {at}: cannot read level: {written} (error / warning / info)",
60
- "bad-languages": "custom_rules {at}: write languages as a list ([en] or [ja, en])",
61
- "no-words": "custom_rules {at}: words has no words (avoid: use pairs, or a list)",
62
- "no-tokens": "custom_rules {at}: tokens has no conditions (- { pos: noun } and so on)",
63
- "bad-token": "custom_rules {at}: token {index} has none of pos, base and surface",
64
- "unknown-pos": "custom_rules {at}: unknown part of speech {written} ({names})",
65
- "bad-pattern": "custom_rules {at}: the pattern cannot be used: {refusal}",
63
+ "not-a-list": "write {scope} as a list of rules (- id: …)",
64
+ "not-a-map": "{scope} {at}: write each rule as an entry with id and type",
65
+ "bad-id": "{scope} {at}: an id starts with a lowercase letter and has only lowercase letters, digits and hyphens (team-no-tbd)",
66
+ "duplicate-id": "{scope} {at}: another rule has the same id",
67
+ "built-in-id": "{scope} {at}: chaff has a rule with this id; choose another",
68
+ "unknown-type": "{scope} {at}: type: {written} is not a type (words / pattern / tokens / module)",
69
+ "bad-module": "{scope} {at}: module: {written} cannot be used: {refusal}",
70
+ "bad-requires": "{scope} {at}: requires: {written} cannot be asked for (only pos can)",
71
+ "bad-word-list": "{scope} {at}: cannot read word_list: {written} as the name of a word list",
72
+ missing: "{scope} {at}: {field} is missing",
73
+ "unpaired-example": "{scope} {at}: write example's before and after in the same language, one the rule checks",
74
+ "bad-level": "{scope} {at}: cannot read level: {written} (error / warning / info)",
75
+ "bad-languages": "{scope} {at}: write languages as a list ([en] or [ja, en])",
76
+ "no-words": "{scope} {at}: words has no words (avoid: use pairs, or a list)",
77
+ "no-tokens": "{scope} {at}: tokens has no conditions (- { pos: noun } and so on)",
78
+ "bad-token": "{scope} {at}: token {index} has none of pos, base and surface",
79
+ "unknown-pos": "{scope} {at}: unknown part of speech {written} ({names})",
80
+ "bad-pattern": "{scope} {at}: the pattern cannot be used: {refusal}",
66
81
  },
67
82
  },
68
83
  };
69
84
 
70
- const PLACEHOLDER = /\{(at|written|field|index|refusal|names)\}/gu;
85
+ const PLACEHOLDER = /\{(scope|at|written|field|index|refusal|names)\}/gu;
86
+
87
+ const refusalOf = (problem: CustomProblem, text: Text): string => {
88
+ if (problem.kind === "bad-pattern") return text.refusal[problem.refusal];
89
+ return problem.kind === "bad-module" ? text.modulePath[problem.refusal] : "";
90
+ };
71
91
 
72
92
  /** The values a problem's sentence names. Each kind carries only some of them. */
73
- const valuesOf = (problem: CustomProblem, text: Text): Readonly<Record<string, string>> => ({
93
+ const valuesOf = (problem: CustomProblem, text: Text, scope: string): Readonly<Record<string, string>> => ({
94
+ scope,
74
95
  at: "at" in problem ? problem.at : "",
75
96
  written: "written" in problem ? problem.written : "",
76
97
  field: "field" in problem ? problem.field : "",
77
98
  index: "index" in problem ? String(problem.index) : "",
78
- refusal: "refusal" in problem ? text.refusal[problem.refusal] : "",
99
+ refusal: refusalOf(problem, text),
79
100
  names: POS_WRITTEN_NAMES.join(" / "),
80
101
  });
81
102
 
82
- const sentenceOf = (problem: CustomProblem, text: Text): string => {
83
- const values = valuesOf(problem, text);
84
- return text.problems[problem.kind].replace(PLACEHOLDER, (_whole, key: string) => values[key] ?? "");
103
+ /** One problem as a sentence. scope names the list the rule is in: custom_rules, or a plugin's rules. */
104
+ export const customProblemSentence = (problem: CustomProblem, ui: UiLanguage, scope = "custom_rules"): string => {
105
+ const values = valuesOf(problem, TEXT[ui], scope);
106
+ return TEXT[ui].problems[problem.kind].replace(PLACEHOLDER, (_whole, key: string) => values[key] ?? "");
85
107
  };
86
108
 
87
109
  /** What in custom_rules cannot run. Each stops the run: a team's rule that silently does not run looks like a clean document. */
88
- export const customRuleProblems = (config: Pick<Config, "customRules" | "path">, ui: UiLanguage): string[] =>
89
- customRulesOf(config).problems.map((problem) => `chaff: ${config.path ?? "chaff.yaml"}: ${sentenceOf(problem, TEXT[ui])}`);
110
+ export const customRuleProblems = (config: Pick<Config, "customRules" | "path" | "baseDir">, ui: UiLanguage): string[] =>
111
+ customRulesOf(config).problems.map((problem) => `chaff: ${config.path ?? "chaff.yaml"}: ${customProblemSentence(problem, ui)}`);
@@ -6,7 +6,7 @@
6
6
  * 権威ある点数で、こちらは何も読み込んでいない状態での見当付け。
7
7
  * 似た計算を両方が持つのは、この順序から来る避けられない重複。
8
8
  *
9
- * 呼ぶ側が、コードブロック・インラインコード・URL・front matter を先に落としておくこと。
9
+ * 呼ぶ側が、コードブロック・インラインコード・URL・front matter を先に落としておくこと(guessLanguage は language-sample.ts で落とす)。
10
10
  * 技術文書は英語のコードを大量に含むため、落とさないと日本語文書が英語と判定される。
11
11
  */
12
12
  const JAPANESE = /[぀-ゟ゠-ヿ一-鿿]/gu;
package/src/detect.ts CHANGED
@@ -5,6 +5,7 @@ import { parse } from "yaml";
5
5
  import { japaneseRatio, latinRatio } from "./detect-language.ts";
6
6
  import { referenceListSpans } from "./reference-lists.ts";
7
7
  import { withoutSpans } from "./soft-break.ts";
8
+ import { languageSample } from "./language-sample.ts";
8
9
 
9
10
  export type LanguageGuess = { readonly language: string; readonly confidence: number; readonly from: string };
10
11
 
@@ -33,9 +34,15 @@ const bodyOf = (source: string): string => {
33
34
  return body.trim() === "" ? source : body;
34
35
  };
35
36
 
37
+ /** The body without code, URLs and markup, or the whole body when nothing else is left (a document of only code). */
38
+ const proseOf = (body: string): string => {
39
+ const sample = languageSample(body);
40
+ return sample.trim() === "" ? body : sample;
41
+ };
42
+
36
43
  export const guessLanguage = (source: string): LanguageGuess => {
37
- const body = bodyOf(source);
38
- const japanese = japaneseRatio(body);
44
+ const prose = proseOf(bodyOf(source));
45
+ const japanese = japaneseRatio(prose);
39
46
  if (japanese >= JAPANESE_FLOOR) return { language: "ja", confidence: japanese, from: "content" };
40
- return { language: "en", confidence: latinRatio(body), from: "content" };
47
+ return { language: "en", confidence: latinRatio(prose), from: "content" };
41
48
  };
@@ -73,9 +73,13 @@ export const openerPile: Detector = (doc, options): Finding[] => {
73
73
  return hits.length === 0 || hits.length < options.limit ? [] : hits.map((hit) => findingOf(hit, { count: hits.length, limit: options.limit }));
74
74
  };
75
75
 
76
- /** コロンで箇条書きへ渡す文(以下の通りです:)。1 つなら案内だが、節ごとに続くと説明が箇条書きの前置きだけになる。 */
76
+ /**
77
+ * コロンで箇条書きへ渡す文(以下の通りです:)。1 つなら案内だが、節ごとに続くと説明が箇条書きの前置きだけになる。
78
+ * 数えるのは文書の言語の文だけ。和文の中の英文(Examples include:)は、英語ではふつうの書き方なので数えない。
79
+ */
77
80
  export const colonLeadIn: Detector = (doc, options): Finding[] => {
78
- const hits = colonLeadIns(doc.sentences, doc.lists, doc.listSpans, doc.source).map((sentence) => ({ sentence, matched: sentence.text.trim() }));
81
+ const inDocumentLanguage = doc.sentences.filter((sentence) => sentence.embeddedLanguage === undefined);
82
+ const hits = colonLeadIns(inDocumentLanguage, doc.lists, doc.listSpans, doc.source).map((sentence) => ({ sentence, matched: sentence.text.trim() }));
79
83
  return densityFindings(doc, hits, options.limit);
80
84
  };
81
85
 
@@ -0,0 +1,55 @@
1
+ import type { Detector, Finding, ProseDocument, Span } from "../plugin.ts";
2
+
3
+ /** A bold run longer than this is a bold sentence, not a label. */
4
+ const MAX_LABEL_CHARS = 40;
5
+
6
+ /** The bullet or number that opens a list item. */
7
+ const ITEM_MARKER = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+/u;
8
+
9
+ /** A task box, after the marker or at the start of an item whose marker was already cut. */
10
+ const TASK_BOX = /^\[[ xX]\][ \t]+/u;
11
+
12
+ /** `**Label**: text` or `**Label:** text`. The label holds no delimiter, so `**a** and **b**: text` is not one label. */
13
+ const BOLD_LABEL = new RegExp(String.raw`^(\*\*|__)(?!\s)((?:(?!\1)[^\n]){1,${MAX_LABEL_CHARS}}?)(?:\1[ \t]*[::]|[::]\1)[ \t]*\S`, "u");
14
+
15
+ /** A label that is only code (`--flag`, `timeout_ms`) names an option or a field: the way reference documentation lists them. */
16
+ const CODE_SPAN = /^`[^`]+`$/u;
17
+
18
+ /** A link around the whole label: inline, reference, collapsed or shortcut. Its text is what the reader sees. */
19
+ const WHOLE_LINK = /^\[([^\]\n]+)\](?:\([^)\s]*\)|\[[^\]\n]*\])?$/u;
20
+
21
+ const isCodeOnly = (label: string): boolean => CODE_SPAN.test(label.replace(WHOLE_LINK, "$1"));
22
+
23
+ /** The label a list item opens with in bold before a colon and more text, or undefined. `item` is the item as written, marker and all. */
24
+ export const boldLabelOf = (item: string): string | undefined => {
25
+ const label = BOLD_LABEL.exec(item.replace(ITEM_MARKER, "").replace(TASK_BOX, ""))?.[2]?.trim();
26
+ return label === undefined || isCodeOnly(label) ? undefined : label;
27
+ };
28
+
29
+ /** Whether the line holding `offset` is quoted (`> - **a**:…`): someone else's words, not the writer's to fix. */
30
+ export const isQuotedAt = (source: string, offset: number): boolean => source.slice(source.lastIndexOf("\n", offset - 1) + 1, offset).includes(">");
31
+
32
+ /** The list items, in document order, that open with a bold label and are not quoted. */
33
+ export const boldLabelItems = (doc: ProseDocument): Span[] =>
34
+ doc.lists
35
+ .flatMap((list) => list.itemSpans)
36
+ .filter((span) => !isQuotedAt(doc.source, span.start) && boldLabelOf(doc.source.slice(span.start, span.end)) !== undefined)
37
+ .toSorted((left, right) => left.start - right.start);
38
+
39
+ /** A count, not a density: a person writes one or two labels even in a long article. */
40
+ export const boldLabelList: Detector = (doc, options): Finding[] => {
41
+ const items = boldLabelItems(doc);
42
+ const first = items[0];
43
+ if (first === undefined || items.length < options.limit) return [];
44
+ const written = doc.source.slice(first.start, first.end);
45
+ return [
46
+ {
47
+ rule: "bold-label-list",
48
+ severity: "info",
49
+ line: 0,
50
+ column: 0,
51
+ quote: (written.split("\n")[0] ?? "").trim(),
52
+ values: { label: boldLabelOf(written) ?? "", count: items.length, limit: options.limit, offset: first.start },
53
+ },
54
+ ];
55
+ };
@@ -1,18 +1,22 @@
1
1
  import type { Detector, Finding, ProseDocument } from "../plugin.ts";
2
- import { danglingFigures, type LabelWords } from "../figure-references.ts";
2
+ import { danglingFigures, type Citations, type LabelWords } from "../figure-references.ts";
3
3
  import { quoteAt } from "./structure-tree.ts";
4
4
 
5
5
  const patternsOf = (doc: ProseDocument, id: string): string[] => (doc.lexicons[id] ?? []).map((entry) => entry.pattern);
6
6
 
7
- const labelWordsOf = (doc: ProseDocument): LabelWords => ({
7
+ /** The language's words for figures, tables and appendices, and for a number that points elsewhere or counts. */
8
+ export const labelWordsOf = (doc: ProseDocument): LabelWords => ({
8
9
  labels: (doc.lexicons["figure-label"] ?? []).map((entry) => ({ word: entry.pattern, kind: entry.instead_of ?? entry.pattern })),
9
10
  elsewhere: patternsOf(doc, "figure-elsewhere"),
10
11
  counters: patternsOf(doc, "count-counter"),
11
12
  });
12
13
 
13
- /** 本文が指す図・表・付録の番号が、キャプションにも見出しにも無い。 */
14
+ const citationsOf = (doc: ProseDocument): Citations | undefined =>
15
+ doc.namedDocument === undefined ? undefined : { namedDocument: doc.namedDocument, sentences: doc.sentences.map((sentence) => sentence.span) };
16
+
17
+ /** 本文が指す図・表・付録の番号が、キャプションにも見出しにも無い。他の文書の名前の後ろに書いた番号は、その文書の図。 */
14
18
  export const danglingFigure: Detector = (doc): Finding[] =>
15
- danglingFigures(doc.source, doc.prose ?? doc.source, labelWordsOf(doc), doc.links).map((dangling) => ({
19
+ danglingFigures(doc.source, doc.prose ?? doc.source, labelWordsOf(doc), doc.links, citationsOf(doc)).map((dangling) => ({
16
20
  rule: "dangling-figure-reference",
17
21
  severity: "warning",
18
22
  line: 0,
@@ -0,0 +1,41 @@
1
+ import type { Detector, Finding, ProseDocument } from "../plugin.ts";
2
+ import { definedTerms, unusedDefinitions, usesBeforeDefinition, type BodyText } from "../structure/definition-use.ts";
3
+ import { expansionConflicts, expansionsIn } from "../acronym-expansions.ts";
4
+ import { linesOf, lineNumberAt } from "../structure/lines.ts";
5
+ import { quoteAt } from "./structure-tree.ts";
6
+
7
+ const bodyOf = (doc: ProseDocument): BodyText[] => doc.sentences.map((sentence) => ({ start: sentence.span.start, text: sentence.text }));
8
+
9
+ const findingAt = (doc: ProseDocument, offset: number, values: Readonly<Record<string, string | number>>): Finding => ({
10
+ rule: "",
11
+ severity: "warning",
12
+ line: 0,
13
+ column: 0,
14
+ quote: quoteAt(doc.source, offset),
15
+ values: { ...values, offset },
16
+ });
17
+
18
+ /** 定義したのに、定義の外で一度も使っていない語。 */
19
+ export const unusedDefinition: Detector = (doc): Finding[] =>
20
+ doc.structure === undefined
21
+ ? []
22
+ : unusedDefinitions(definedTerms(doc.structure), bodyOf(doc)).map((defined) => findingAt(doc, defined.span.start, { term: defined.term }));
23
+
24
+ /** 文の途中で括弧に入れて定義した語を、定義より前で使っている所。 */
25
+ export const useBeforeDefinition: Detector = (doc): Finding[] =>
26
+ doc.structure === undefined
27
+ ? []
28
+ : usesBeforeDefinition(definedTerms(doc.structure), bodyOf(doc)).map((use) => findingAt(doc, use.offset, { term: use.term.term, defined: use.term.line }));
29
+
30
+ /** 同じ略語を、文書の中で二通りに展開している所。 */
31
+ export const acronymExpansionConflict: Detector = (doc): Finding[] => {
32
+ const lines = linesOf(doc.source);
33
+ return expansionConflicts(bodyOf(doc).flatMap(expansionsIn)).map(({ expansion, first }) =>
34
+ findingAt(doc, expansion.offset, {
35
+ acronym: expansion.acronym,
36
+ name: expansion.name,
37
+ first: first.name,
38
+ firstLine: lineNumberAt(lines, first.offset) ?? 0,
39
+ }),
40
+ );
41
+ };
@@ -3,7 +3,7 @@ import { quoteAround } from "./quote-around.ts";
3
3
 
4
4
  /**
5
5
  * 句読点が二つ並んだところ(「。。」「、。」「,,」「..」)。打ち直しで残った印。
6
- * 同じ印を三つ以上並べたもの(「...」「。。。」)は、わざと伸ばした書き方なので数えない。
6
+ * 同じ印を三つ以上並べたもの(「...」「。。。」)は、わざと伸ばした書き方なので数えない。その後ろに一つだけ付けた印(「...。」)も数えない。
7
7
  * コロンは「::」(C++ の名前、IPv6)があるので見ない。
8
8
  */
9
9
  const PUNCTUATION_RUN = /[、。,.,.;;]{2,}/gu;
@@ -18,6 +18,9 @@ const JOINED = /[\p{L}\p{N}/]/u;
18
18
 
19
19
  const isDrawnOut = (run: string): boolean => run.length >= 3 && [...run].every((mark) => mark === run.charAt(0));
20
20
 
21
+ /** 伸ばした印(「...」「、、、」)は三点リーダーの代わりで、すぐ後ろの一つの印(「...。」「、、、。」)は文を閉じる印。 */
22
+ const isDrawnOutThenClosed = (run: string): boolean => isDrawnOut(run.slice(0, -1));
23
+
21
24
  const isJoinedDots = (text: string, start: number, run: string): boolean =>
22
25
  run === ".." && (JOINED.test(text.charAt(start + run.length)) || text.charAt(start - 1) === "/");
23
26
 
@@ -30,7 +33,7 @@ const withoutWordDot = (text: string, found: DoubledMarks): DoubledMarks =>
30
33
  : found;
31
34
 
32
35
  const isSlip = (text: string, { start, run }: DoubledMarks): boolean =>
33
- run.length >= 2 && !isDrawnOut(run) && !AFTER_ABBREVIATION.test(run) && !isJoinedDots(text, start, run);
36
+ run.length >= 2 && !isDrawnOut(run) && !isDrawnOutThenClosed(run) && !AFTER_ABBREVIATION.test(run) && !isJoinedDots(text, start, run);
34
37
 
35
38
  /** text の中の、句読点が重なった並び(位置と並び)。 */
36
39
  export const doubledMarks = (text: string): DoubledMarks[] =>