chaffjs 0.34.0 → 0.35.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 (390) hide show
  1. package/dist/capitals-passage.d.ts +3 -0
  2. package/dist/capitals-passage.d.ts.map +1 -0
  3. package/dist/capitals-passage.js +26 -0
  4. package/dist/capitals-passage.js.map +1 -0
  5. package/dist/coined-hyphen-name.d.ts +13 -0
  6. package/dist/coined-hyphen-name.d.ts.map +1 -0
  7. package/dist/coined-hyphen-name.js +21 -0
  8. package/dist/coined-hyphen-name.js.map +1 -0
  9. package/dist/company-names.d.ts +6 -1
  10. package/dist/company-names.d.ts.map +1 -1
  11. package/dist/company-names.js +22 -3
  12. package/dist/company-names.js.map +1 -1
  13. package/dist/detectors/acronym-context.d.ts +1 -1
  14. package/dist/detectors/acronym-context.d.ts.map +1 -1
  15. package/dist/detectors/acronym-context.js +4 -4
  16. package/dist/detectors/acronym-context.js.map +1 -1
  17. package/dist/detectors/acronym-definition.d.ts +13 -0
  18. package/dist/detectors/acronym-definition.d.ts.map +1 -0
  19. package/dist/detectors/acronym-definition.js +90 -0
  20. package/dist/detectors/acronym-definition.js.map +1 -0
  21. package/dist/detectors/acronym-lone.d.ts +13 -0
  22. package/dist/detectors/acronym-lone.d.ts.map +1 -0
  23. package/dist/detectors/acronym-lone.js +22 -0
  24. package/dist/detectors/acronym-lone.js.map +1 -0
  25. package/dist/detectors/acronym-name.d.ts +55 -0
  26. package/dist/detectors/acronym-name.d.ts.map +1 -0
  27. package/dist/detectors/acronym-name.js +75 -0
  28. package/dist/detectors/acronym-name.js.map +1 -0
  29. package/dist/detectors/capitalised-term.d.ts +14 -2
  30. package/dist/detectors/capitalised-term.d.ts.map +1 -1
  31. package/dist/detectors/capitalised-term.js +115 -4
  32. package/dist/detectors/capitalised-term.js.map +1 -1
  33. package/dist/detectors/caps-caption.d.ts +11 -0
  34. package/dist/detectors/caps-caption.d.ts.map +1 -0
  35. package/dist/detectors/caps-caption.js +51 -0
  36. package/dist/detectors/caps-caption.js.map +1 -0
  37. package/dist/detectors/char-shape.d.ts.map +1 -1
  38. package/dist/detectors/char-shape.js +47 -18
  39. package/dist/detectors/char-shape.js.map +1 -1
  40. package/dist/detectors/clause-shape.d.ts +1 -1
  41. package/dist/detectors/clause-shape.d.ts.map +1 -1
  42. package/dist/detectors/clause-shape.js +9 -4
  43. package/dist/detectors/clause-shape.js.map +1 -1
  44. package/dist/detectors/concrete-evidence.d.ts +13 -1
  45. package/dist/detectors/concrete-evidence.d.ts.map +1 -1
  46. package/dist/detectors/concrete-evidence.js +59 -0
  47. package/dist/detectors/concrete-evidence.js.map +1 -1
  48. package/dist/detectors/contraction-minority.d.ts +11 -0
  49. package/dist/detectors/contraction-minority.d.ts.map +1 -0
  50. package/dist/detectors/contraction-minority.js +10 -0
  51. package/dist/detectors/contraction-minority.js.map +1 -0
  52. package/dist/detectors/counter-edge.d.ts +5 -0
  53. package/dist/detectors/counter-edge.d.ts.map +1 -1
  54. package/dist/detectors/counter-edge.js +23 -0
  55. package/dist/detectors/counter-edge.js.map +1 -1
  56. package/dist/detectors/date-any-year.d.ts +6 -3
  57. package/dist/detectors/date-any-year.d.ts.map +1 -1
  58. package/dist/detectors/date-any-year.js +47 -10
  59. package/dist/detectors/date-any-year.js.map +1 -1
  60. package/dist/detectors/date-heading-year.d.ts +18 -0
  61. package/dist/detectors/date-heading-year.d.ts.map +1 -0
  62. package/dist/detectors/date-heading-year.js +38 -0
  63. package/dist/detectors/date-heading-year.js.map +1 -0
  64. package/dist/detectors/date-year.d.ts +12 -2
  65. package/dist/detectors/date-year.d.ts.map +1 -1
  66. package/dist/detectors/date-year.js +31 -6
  67. package/dist/detectors/date-year.js.map +1 -1
  68. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  69. package/dist/detectors/defined-term-form.js +26 -5
  70. package/dist/detectors/defined-term-form.js.map +1 -1
  71. package/dist/detectors/due-date.d.ts.map +1 -1
  72. package/dist/detectors/due-date.js +6 -0
  73. package/dist/detectors/due-date.js.map +1 -1
  74. package/dist/detectors/en-shape.d.ts.map +1 -1
  75. package/dist/detectors/en-shape.js +5 -1
  76. package/dist/detectors/en-shape.js.map +1 -1
  77. package/dist/detectors/fact-consistency.d.ts.map +1 -1
  78. package/dist/detectors/fact-consistency.js +3 -2
  79. package/dist/detectors/fact-consistency.js.map +1 -1
  80. package/dist/detectors/gram-passages.d.ts +11 -0
  81. package/dist/detectors/gram-passages.d.ts.map +1 -0
  82. package/dist/detectors/gram-passages.js +26 -0
  83. package/dist/detectors/gram-passages.js.map +1 -0
  84. package/dist/detectors/heading-end-mark.d.ts +8 -3
  85. package/dist/detectors/heading-end-mark.d.ts.map +1 -1
  86. package/dist/detectors/heading-end-mark.js +18 -8
  87. package/dist/detectors/heading-end-mark.js.map +1 -1
  88. package/dist/detectors/heading-names.d.ts +7 -0
  89. package/dist/detectors/heading-names.d.ts.map +1 -0
  90. package/dist/detectors/heading-names.js +24 -0
  91. package/dist/detectors/heading-names.js.map +1 -0
  92. package/dist/detectors/heading-numbering.d.ts.map +1 -1
  93. package/dist/detectors/heading-numbering.js +7 -2
  94. package/dist/detectors/heading-numbering.js.map +1 -1
  95. package/dist/detectors/inch-mark.d.ts +10 -0
  96. package/dist/detectors/inch-mark.d.ts.map +1 -0
  97. package/dist/detectors/inch-mark.js +48 -0
  98. package/dist/detectors/inch-mark.js.map +1 -0
  99. package/dist/detectors/lexicon.d.ts +0 -1
  100. package/dist/detectors/lexicon.d.ts.map +1 -1
  101. package/dist/detectors/lexicon.js +29 -23
  102. package/dist/detectors/lexicon.js.map +1 -1
  103. package/dist/detectors/mark-shape.d.ts +6 -3
  104. package/dist/detectors/mark-shape.d.ts.map +1 -1
  105. package/dist/detectors/mark-shape.js +12 -6
  106. package/dist/detectors/mark-shape.js.map +1 -1
  107. package/dist/detectors/name-variant-apart.d.ts +27 -0
  108. package/dist/detectors/name-variant-apart.d.ts.map +1 -0
  109. package/dist/detectors/name-variant-apart.js +51 -0
  110. package/dist/detectors/name-variant-apart.js.map +1 -0
  111. package/dist/detectors/name-variant.d.ts.map +1 -1
  112. package/dist/detectors/name-variant.js +18 -11
  113. package/dist/detectors/name-variant.js.map +1 -1
  114. package/dist/detectors/numeral-word.d.ts +15 -0
  115. package/dist/detectors/numeral-word.d.ts.map +1 -0
  116. package/dist/detectors/numeral-word.js +26 -0
  117. package/dist/detectors/numeral-word.js.map +1 -0
  118. package/dist/detectors/organisation-run.d.ts +14 -0
  119. package/dist/detectors/organisation-run.d.ts.map +1 -0
  120. package/dist/detectors/organisation-run.js +73 -0
  121. package/dist/detectors/organisation-run.js.map +1 -0
  122. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  123. package/dist/detectors/orthographic-variant.js +13 -5
  124. package/dist/detectors/orthographic-variant.js.map +1 -1
  125. package/dist/detectors/outline-label.d.ts +14 -0
  126. package/dist/detectors/outline-label.d.ts.map +1 -0
  127. package/dist/detectors/outline-label.js +32 -0
  128. package/dist/detectors/outline-label.js.map +1 -0
  129. package/dist/detectors/page-marker.d.ts +6 -0
  130. package/dist/detectors/page-marker.d.ts.map +1 -0
  131. package/dist/detectors/page-marker.js +19 -0
  132. package/dist/detectors/page-marker.js.map +1 -0
  133. package/dist/detectors/parallel-definition.d.ts +16 -0
  134. package/dist/detectors/parallel-definition.d.ts.map +1 -0
  135. package/dist/detectors/parallel-definition.js +51 -0
  136. package/dist/detectors/parallel-definition.js.map +1 -0
  137. package/dist/detectors/reference-text.d.ts.map +1 -1
  138. package/dist/detectors/reference-text.js +1 -0
  139. package/dist/detectors/reference-text.js.map +1 -1
  140. package/dist/detectors/registry/unfilled-placeholder.d.ts.map +1 -1
  141. package/dist/detectors/registry/unfilled-placeholder.js +2 -2
  142. package/dist/detectors/registry/unfilled-placeholder.js.map +1 -1
  143. package/dist/detectors/registry/vague-density.js +1 -1
  144. package/dist/detectors/registry/vague-density.js.map +1 -1
  145. package/dist/detectors/request-owner.d.ts +6 -2
  146. package/dist/detectors/request-owner.d.ts.map +1 -1
  147. package/dist/detectors/request-owner.js +57 -7
  148. package/dist/detectors/request-owner.js.map +1 -1
  149. package/dist/detectors/requirement-smell.d.ts.map +1 -1
  150. package/dist/detectors/requirement-smell.js +10 -2
  151. package/dist/detectors/requirement-smell.js.map +1 -1
  152. package/dist/detectors/sentence-length.d.ts.map +1 -1
  153. package/dist/detectors/sentence-length.js +24 -16
  154. package/dist/detectors/sentence-length.js.map +1 -1
  155. package/dist/detectors/sentence-rhythm.d.ts.map +1 -1
  156. package/dist/detectors/sentence-rhythm.js +6 -3
  157. package/dist/detectors/sentence-rhythm.js.map +1 -1
  158. package/dist/detectors/series-label.d.ts +2 -1
  159. package/dist/detectors/series-label.d.ts.map +1 -1
  160. package/dist/detectors/series-label.js +10 -4
  161. package/dist/detectors/series-label.js.map +1 -1
  162. package/dist/detectors/signals.d.ts +4 -0
  163. package/dist/detectors/signals.d.ts.map +1 -1
  164. package/dist/detectors/signals.js +95 -36
  165. package/dist/detectors/signals.js.map +1 -1
  166. package/dist/detectors/structure-tree.d.ts.map +1 -1
  167. package/dist/detectors/structure-tree.js +8 -7
  168. package/dist/detectors/structure-tree.js.map +1 -1
  169. package/dist/detectors/template-sections.d.ts +14 -0
  170. package/dist/detectors/template-sections.d.ts.map +1 -0
  171. package/dist/detectors/template-sections.js +25 -0
  172. package/dist/detectors/template-sections.js.map +1 -0
  173. package/dist/detectors/titled-run.d.ts +22 -0
  174. package/dist/detectors/titled-run.d.ts.map +1 -0
  175. package/dist/detectors/titled-run.js +34 -0
  176. package/dist/detectors/titled-run.js.map +1 -0
  177. package/dist/detectors/unbalanced-bracket.d.ts.map +1 -1
  178. package/dist/detectors/unbalanced-bracket.js +4 -0
  179. package/dist/detectors/unbalanced-bracket.js.map +1 -1
  180. package/dist/detectors/undefined-term.d.ts.map +1 -1
  181. package/dist/detectors/undefined-term.js +8 -0
  182. package/dist/detectors/undefined-term.js.map +1 -1
  183. package/dist/detectors/unfilled-placeholder.d.ts +4 -0
  184. package/dist/detectors/unfilled-placeholder.d.ts.map +1 -0
  185. package/dist/detectors/unfilled-placeholder.js +9 -0
  186. package/dist/detectors/unfilled-placeholder.js.map +1 -0
  187. package/dist/detectors/vague-density.d.ts +5 -0
  188. package/dist/detectors/vague-density.d.ts.map +1 -0
  189. package/dist/detectors/vague-density.js +52 -0
  190. package/dist/detectors/vague-density.js.map +1 -0
  191. package/dist/facts/fact-scope.d.ts +10 -1
  192. package/dist/facts/fact-scope.d.ts.map +1 -1
  193. package/dist/facts/fact-scope.js +34 -3
  194. package/dist/facts/fact-scope.js.map +1 -1
  195. package/dist/facts/table-facts.d.ts +12 -2
  196. package/dist/facts/table-facts.d.ts.map +1 -1
  197. package/dist/facts/table-facts.js +22 -4
  198. package/dist/facts/table-facts.js.map +1 -1
  199. package/dist/grade-api.d.ts +9 -2
  200. package/dist/grade-api.d.ts.map +1 -1
  201. package/dist/grade-api.js +31 -15
  202. package/dist/grade-api.js.map +1 -1
  203. package/dist/heading-numbering.d.ts +5 -2
  204. package/dist/heading-numbering.d.ts.map +1 -1
  205. package/dist/heading-numbering.js +27 -6
  206. package/dist/heading-numbering.js.map +1 -1
  207. package/dist/name-end-letter.d.ts +5 -0
  208. package/dist/name-end-letter.d.ts.map +1 -1
  209. package/dist/name-end-letter.js +8 -0
  210. package/dist/name-end-letter.js.map +1 -1
  211. package/dist/name-given-before.d.ts +15 -0
  212. package/dist/name-given-before.d.ts.map +1 -0
  213. package/dist/name-given-before.js +30 -0
  214. package/dist/name-given-before.js.map +1 -0
  215. package/dist/name-separator.d.ts +2 -2
  216. package/dist/name-separator.d.ts.map +1 -1
  217. package/dist/name-separator.js +56 -5
  218. package/dist/name-separator.js.map +1 -1
  219. package/dist/name-variants.d.ts +3 -1
  220. package/dist/name-variants.d.ts.map +1 -1
  221. package/dist/name-variants.js +8 -7
  222. package/dist/name-variants.js.map +1 -1
  223. package/dist/name-word-order.d.ts +9 -5
  224. package/dist/name-word-order.d.ts.map +1 -1
  225. package/dist/name-word-order.js +47 -15
  226. package/dist/name-word-order.js.map +1 -1
  227. package/dist/nominal-stem.d.ts +13 -0
  228. package/dist/nominal-stem.d.ts.map +1 -0
  229. package/dist/nominal-stem.js +32 -0
  230. package/dist/nominal-stem.js.map +1 -0
  231. package/dist/sentence-load.d.ts +2 -1
  232. package/dist/sentence-load.d.ts.map +1 -1
  233. package/dist/sentence-load.js +6 -2
  234. package/dist/sentence-load.js.map +1 -1
  235. package/dist/structure/cited-document-section.d.ts +7 -0
  236. package/dist/structure/cited-document-section.d.ts.map +1 -0
  237. package/dist/structure/cited-document-section.js +33 -0
  238. package/dist/structure/cited-document-section.js.map +1 -0
  239. package/dist/structure/html-text-blocks.d.ts +8 -0
  240. package/dist/structure/html-text-blocks.d.ts.map +1 -0
  241. package/dist/structure/html-text-blocks.js +47 -0
  242. package/dist/structure/html-text-blocks.js.map +1 -0
  243. package/dist/structure/issues.d.ts +1 -1
  244. package/dist/structure/issues.d.ts.map +1 -1
  245. package/dist/structure/issues.js +19 -4
  246. package/dist/structure/issues.js.map +1 -1
  247. package/dist/structure/labelled-date-order.d.ts +4 -1
  248. package/dist/structure/labelled-date-order.d.ts.map +1 -1
  249. package/dist/structure/labelled-date-order.js +2 -2
  250. package/dist/structure/labelled-date-order.js.map +1 -1
  251. package/dist/structure/labelled-order.d.ts +3 -0
  252. package/dist/structure/labelled-order.d.ts.map +1 -1
  253. package/dist/structure/labelled-order.js +8 -3
  254. package/dist/structure/labelled-order.js.map +1 -1
  255. package/dist/structure/list-numbering.d.ts +20 -4
  256. package/dist/structure/list-numbering.d.ts.map +1 -1
  257. package/dist/structure/list-numbering.js +65 -9
  258. package/dist/structure/list-numbering.js.map +1 -1
  259. package/dist/structure/listed-paragraph.d.ts +16 -0
  260. package/dist/structure/listed-paragraph.d.ts.map +1 -0
  261. package/dist/structure/listed-paragraph.js +37 -0
  262. package/dist/structure/listed-paragraph.js.map +1 -0
  263. package/dist/structure/numbering.d.ts +3 -2
  264. package/dist/structure/numbering.d.ts.map +1 -1
  265. package/dist/structure/numbering.js +31 -6
  266. package/dist/structure/numbering.js.map +1 -1
  267. package/dist/structure/reference-text.d.ts +9 -1
  268. package/dist/structure/reference-text.d.ts.map +1 -1
  269. package/dist/structure/reference-text.js +21 -3
  270. package/dist/structure/reference-text.js.map +1 -1
  271. package/dist/structure/section-names.d.ts +12 -0
  272. package/dist/structure/section-names.d.ts.map +1 -0
  273. package/dist/structure/section-names.js +26 -0
  274. package/dist/structure/section-names.js.map +1 -0
  275. package/dist/structure/versioned-label.d.ts +16 -0
  276. package/dist/structure/versioned-label.d.ts.map +1 -0
  277. package/dist/structure/versioned-label.js +48 -0
  278. package/dist/structure/versioned-label.js.map +1 -0
  279. package/dist/structure/written-headings.d.ts +31 -0
  280. package/dist/structure/written-headings.d.ts.map +1 -0
  281. package/dist/structure/written-headings.js +119 -0
  282. package/dist/structure/written-headings.js.map +1 -0
  283. package/dist/structure/written-resolve.d.ts +12 -0
  284. package/dist/structure/written-resolve.d.ts.map +1 -0
  285. package/dist/structure/written-resolve.js +49 -0
  286. package/dist/structure/written-resolve.js.map +1 -0
  287. package/package.json +3 -3
  288. package/rules/all-caps-shouting.yaml +2 -2
  289. package/rules/concrete-evidence-density.yaml +2 -2
  290. package/rules/contraction-consistency.yaml +13 -13
  291. package/rules/dangling-reference.yaml +2 -2
  292. package/rules/date-without-year.yaml +2 -2
  293. package/rules/defined-name-repeated.yaml +2 -2
  294. package/rules/fact-conflict.yaml +2 -2
  295. package/rules/heading-end-punctuation-mix.yaml +2 -2
  296. package/rules/heading-numbering-mix.yaml +2 -2
  297. package/rules/latin-spacing.yaml +2 -2
  298. package/rules/max-kanji-continuous.yaml +5 -3
  299. package/rules/max-sentence-length.yaml +3 -2
  300. package/rules/name-variant.yaml +2 -2
  301. package/rules/ngram-repetition.yaml +2 -2
  302. package/rules/numbering-gap.yaml +2 -2
  303. package/rules/orthographic-variant.yaml +2 -2
  304. package/rules/reference-title-mismatch.yaml +2 -2
  305. package/rules/repeated-sentence-ending.yaml +3 -2
  306. package/rules/request-without-deadline.yaml +2 -2
  307. package/rules/requirement-smell.yaml +2 -2
  308. package/rules/sentence-rhythm.yaml +2 -2
  309. package/rules/title-case-consistency.yaml +2 -2
  310. package/rules/unbalanced-bracket.yaml +2 -2
  311. package/rules/undefined-acronym.yaml +11 -2
  312. package/rules/undefined-term.yaml +2 -2
  313. package/rules/unfilled-placeholder.yaml +2 -2
  314. package/rules/vague-word-density.yaml +2 -2
  315. package/src/capitals-passage.ts +31 -0
  316. package/src/coined-hyphen-name.ts +27 -0
  317. package/src/company-names.ts +24 -3
  318. package/src/detectors/acronym-context.ts +5 -6
  319. package/src/detectors/acronym-definition.ts +123 -0
  320. package/src/detectors/acronym-lone.ts +26 -0
  321. package/src/detectors/acronym-name.ts +117 -0
  322. package/src/detectors/capitalised-term.ts +154 -4
  323. package/src/detectors/caps-caption.ts +61 -0
  324. package/src/detectors/char-shape.ts +74 -22
  325. package/src/detectors/clause-shape.ts +14 -4
  326. package/src/detectors/concrete-evidence.ts +77 -1
  327. package/src/detectors/contraction-minority.ts +11 -0
  328. package/src/detectors/counter-edge.ts +23 -0
  329. package/src/detectors/date-any-year.ts +56 -10
  330. package/src/detectors/date-heading-year.ts +55 -0
  331. package/src/detectors/date-year.ts +54 -9
  332. package/src/detectors/defined-term-form.ts +26 -5
  333. package/src/detectors/due-date.ts +10 -1
  334. package/src/detectors/en-shape.ts +5 -1
  335. package/src/detectors/fact-consistency.ts +3 -2
  336. package/src/detectors/gram-passages.ts +36 -0
  337. package/src/detectors/heading-end-mark.ts +23 -10
  338. package/src/detectors/heading-names.ts +31 -0
  339. package/src/detectors/heading-numbering.ts +7 -2
  340. package/src/detectors/inch-mark.ts +49 -0
  341. package/src/detectors/lexicon.ts +32 -21
  342. package/src/detectors/mark-shape.ts +12 -6
  343. package/src/detectors/name-variant-apart.ts +63 -0
  344. package/src/detectors/name-variant.ts +30 -12
  345. package/src/detectors/numeral-word.ts +33 -0
  346. package/src/detectors/organisation-run.ts +86 -0
  347. package/src/detectors/orthographic-variant.ts +22 -6
  348. package/src/detectors/outline-label.ts +48 -0
  349. package/src/detectors/page-marker.ts +26 -0
  350. package/src/detectors/parallel-definition.ts +64 -0
  351. package/src/detectors/reference-text.ts +1 -0
  352. package/src/detectors/registry/unfilled-placeholder.ts +2 -2
  353. package/src/detectors/registry/vague-density.ts +1 -1
  354. package/src/detectors/request-owner.ts +71 -8
  355. package/src/detectors/requirement-smell.ts +15 -2
  356. package/src/detectors/sentence-length.ts +14 -6
  357. package/src/detectors/sentence-rhythm.ts +8 -4
  358. package/src/detectors/series-label.ts +13 -4
  359. package/src/detectors/signals.ts +94 -26
  360. package/src/detectors/structure-tree.ts +10 -8
  361. package/src/detectors/template-sections.ts +37 -0
  362. package/src/detectors/titled-run.ts +43 -0
  363. package/src/detectors/unbalanced-bracket.ts +3 -0
  364. package/src/detectors/undefined-term.ts +8 -0
  365. package/src/detectors/unfilled-placeholder.ts +10 -0
  366. package/src/detectors/vague-density.ts +68 -0
  367. package/src/facts/fact-scope.ts +53 -4
  368. package/src/facts/table-facts.ts +43 -6
  369. package/src/grade-api.ts +38 -16
  370. package/src/heading-numbering.ts +35 -8
  371. package/src/name-end-letter.ts +9 -0
  372. package/src/name-given-before.ts +40 -0
  373. package/src/name-separator.ts +66 -5
  374. package/src/name-variants.ts +21 -11
  375. package/src/name-word-order.ts +62 -17
  376. package/src/nominal-stem.ts +36 -0
  377. package/src/sentence-load.ts +7 -2
  378. package/src/structure/cited-document-section.ts +42 -0
  379. package/src/structure/html-text-blocks.ts +45 -0
  380. package/src/structure/issues.ts +19 -4
  381. package/src/structure/labelled-date-order.ts +5 -2
  382. package/src/structure/labelled-order.ts +12 -3
  383. package/src/structure/list-numbering.ts +100 -11
  384. package/src/structure/listed-paragraph.ts +45 -0
  385. package/src/structure/numbering.ts +48 -19
  386. package/src/structure/reference-text.ts +25 -3
  387. package/src/structure/section-names.ts +32 -0
  388. package/src/structure/versioned-label.ts +52 -0
  389. package/src/structure/written-headings.ts +137 -0
  390. package/src/structure/written-resolve.ts +57 -0
@@ -4,12 +4,17 @@ import { compacted, placeAt, type Compacted } from "./gram-place.ts";
4
4
  import { charWindows, wordWindows } from "./gram-windows.ts";
5
5
  import { furnitureMask, isFurniture } from "./gram-furniture.ts";
6
6
  import { notAcronymSpansOf, type NotAcronymSpans } from "./acronym-context.ts";
7
+ import { nameSpansOf } from "./acronym-name.ts";
7
8
  import { abbreviates, expansionAt, termEntryAcronyms, type ExpandedAt } from "./acronym-expansion.ts";
8
9
  import { isExplained } from "./acronym-compound.ts";
10
+ import { statedAcronyms } from "./acronym-definition.ts";
11
+ import { isBracketed, isInHeading, isNamePart } from "./acronym-lone.ts";
9
12
  import { conjugatedForms } from "./conjugated-form.ts";
10
- import { evidenceSpans, hasNumeral, startsWithin } from "./concrete-evidence.ts";
13
+ import { evidenceSpans, hasNumeral, isClaim, isLabelOnly, startsWithin, type AdviceWords } from "./concrete-evidence.ts";
11
14
  import { letteredIndexEntries } from "./lettered-index.ts";
15
+ import { outlineLabelSpans } from "./outline-label.ts";
12
16
  import { hasPredicateIn } from "./gram-predicate.ts";
17
+ import { gramPassagesOf } from "./gram-passages.ts";
13
18
  import { nameSpans, touchesAny } from "../team-names.ts";
14
19
  import { referenceListSpans } from "../reference-lists.ts";
15
20
  import type { Detector, Finding, ProseDocument, Section, Sentence } from "../plugin.ts";
@@ -127,12 +132,23 @@ const tallySentence = (doc: ProseDocument, sentence: Sentence, tally: Tally): vo
127
132
  });
128
133
  };
129
134
 
130
- const gramsOf = (doc: ProseDocument): Tally => {
135
+ const gramsOf = (doc: ProseDocument, sentences: readonly Sentence[]): Tally => {
131
136
  const tally: Tally = { counts: new Map(), first: new Map() };
132
- doc.sentences.forEach((sentence) => tallySentence(doc, sentence, tally));
137
+ sentences.forEach((sentence) => tallySentence(doc, sentence, tally));
133
138
  return tally;
134
139
  };
135
140
 
141
+ type Repeat = { readonly key: string; readonly count: number; readonly at: FirstSeen | undefined };
142
+
143
+ /** 範囲(gram-passages.ts)ごとに数える。よくある質問の別々の答えは別の範囲。 */
144
+ const repeatsOf = (doc: ProseDocument, limit: number): Repeat[] =>
145
+ gramPassagesOf(doc.sections).flatMap((sentences) => {
146
+ const tally = gramsOf(doc, sentences);
147
+ return [...tally.counts.entries()]
148
+ .filter(([key, count]) => count > limit && isPhrasing(key, doc.lengthUnit))
149
+ .map(([key, count]) => ({ key, count, at: tally.first.get(key) }));
150
+ });
151
+
136
152
  /**
137
153
  * 語句が文の中で占める範囲に、述語になる動詞か助動詞があるか(gram-predicate.ts)。
138
154
  * 品詞が無ければ見分けられないので、これまでどおり言い回しとして数える。
@@ -145,11 +161,8 @@ const hasPredicate = (at: FirstSeen | undefined, unit: ProseDocument["lengthUnit
145
161
 
146
162
  export const ngramRepetition: Detector = (doc, options): Finding[] => {
147
163
  if (wordsOf(doc) < MIN_DOCUMENT_LENGTH[doc.lengthUnit]) return [];
148
- const tally = gramsOf(doc);
149
- const worst = [...tally.counts.entries()]
150
- .filter(([key, count]) => count > options.limit && isPhrasing(key, doc.lengthUnit))
151
- .toSorted(([, left], [, right]) => right - left)
152
- .map(([key, count]) => ({ key, count, at: tally.first.get(key) }))
164
+ const worst = repeatsOf(doc, options.limit)
165
+ .toSorted((left, right) => right.count - left.count)
153
166
  .find(({ at }) => hasPredicate(at, doc.lengthUnit));
154
167
  if (worst === undefined) return [];
155
168
  const at = worst.at?.sentence;
@@ -274,8 +287,8 @@ const expandsInHeading = (heading: string, acronym: string, expandedAt: Expanded
274
287
 
275
288
  const patternsOf = (doc: ProseDocument, id: string): string[] => (doc.lexicons[id] ?? []).map((entry) => entry.pattern);
276
289
 
277
- const notationOf = (doc: ProseDocument): NotAcronymSpans =>
278
- notAcronymSpansOf({
290
+ const notationOf = (doc: ProseDocument): NotAcronymSpans => {
291
+ const notation = notAcronymSpansOf({
279
292
  meridiem: patternsOf(doc, "meridiem"),
280
293
  timeZones: patternsOf(doc, "time-zone"),
281
294
  currencies: patternsOf(doc, "currency-code"),
@@ -287,6 +300,17 @@ const notationOf = (doc: ProseDocument): NotAcronymSpans =>
287
300
  titles: patternsOf(doc, "name-title"),
288
301
  dateTimeUnits: patternsOf(doc, "date-time-unit"),
289
302
  });
303
+ const names = nameSpansOf({
304
+ legalForms: patternsOf(doc, "legal-form"),
305
+ determiners: patternsOf(doc, "name-determiner"),
306
+ citationSeries: patternsOf(doc, "citation-series"),
307
+ carriers: patternsOf(doc, "carrier-name"),
308
+ namePrefixes: patternsOf(doc, "name-prefix"),
309
+ streetSuffixes: patternsOf(doc, "street-suffix"),
310
+ capitalPhrases: patternsOf(doc, "capital-phrase"),
311
+ });
312
+ return (text) => [...notation(text), ...names(text)];
313
+ };
290
314
 
291
315
  /** 定義の語は、語彙表の形(という)と、この文書で活用して書かれた形(といいます)の両方で照らす。 */
292
316
  const definitionVerbsOf = (doc: ProseDocument): string[] => {
@@ -294,30 +318,57 @@ const definitionVerbsOf = (doc: ProseDocument): string[] => {
294
318
  return [...verbs, ...conjugatedForms(doc.sentences, verbs)];
295
319
  };
296
320
 
321
+ const acronymFinding = (acronym: string, hit: Hit, count: number, limit: number): Finding => ({
322
+ rule: "undefined-acronym",
323
+ severity: "info",
324
+ line: 0,
325
+ column: 0,
326
+ quote: hit.sentence.text.trim(),
327
+ values: { word: acronym, count, limit, offset: hit.offset },
328
+ });
329
+
330
+ const isNamePartAt = (acronym: string, hit: Hit): boolean => {
331
+ const start = hit.offset - hit.sentence.span.start;
332
+ return isNamePart(hit.sentence.text, start, start + acronym.length);
333
+ };
334
+
335
+ /**
336
+ * 説明のない略語が上限に届かなくても、この文書が自分で上限の数だけ略語を展開しているなら、展開しなかった 1 つも指す。
337
+ * 書き手が展開する文書では、展開の抜けは抜けとして読める。名前の一部の略語(SRI International)は、そのときも指さない。
338
+ */
297
339
  export const undefinedAcronym: Detector = (doc, options): Finding[] => {
298
340
  const body = bodyOf(doc);
299
- // HTTP のメソッド名(GET)は略語ではないので、通じる略語と同じく展開を求めない。
341
+ // HTTP のメソッド名(GET)は略語ではなく、税の名前(VAT、GST)は読み手が名前として知るので、通じる略語と同じく展開を求めない。
300
342
  // 並べた名前(JAXA)も、繋いだ略語(JAXA-ISAS)の片割れとして説明済みに数える。
301
- const common = new Set([...(options.lexicon ?? []).map((entry) => entry.pattern), ...patternsOf(doc, "http-method"), ...(doc.names ?? [])]);
343
+ const common = new Set([
344
+ ...(options.lexicon ?? []).map((entry) => entry.pattern),
345
+ ...patternsOf(doc, "http-method"),
346
+ ...patternsOf(doc, "tax-code"),
347
+ ...(doc.names ?? []),
348
+ ]);
302
349
  const expandedAt = expansionAt({ markers: patternsOf(doc, "definition-marker"), verbs: definitionVerbsOf(doc) });
303
- const entries = termEntryAcronyms(doc.source);
350
+ const stated = statedAcronyms(doc.source, { statements: patternsOf(doc, "definition-statement"), markers: patternsOf(doc, "definition-marker") });
351
+ const entries = new Set([...termEntryAcronyms(doc.source), ...stated]);
304
352
  const headings = doc.sections.map((section) => section.heading);
305
353
  const explainedAlone = (word: string): boolean =>
306
354
  common.has(word) || entries.has(word) || isExpanded(body, word, expandedAt) || headings.some((heading) => expandsInHeading(heading, word, expandedAt));
307
- const hits = outsideSpans(acronymsOf(doc, notationOf(doc)), referenceListSpans(doc.source, patternsOf(doc, "reference-list-heading")));
308
- const unexplained = new Set([...firstHits(hits).keys()].filter((acronym) => !isExplained(acronym, explainedAlone)));
355
+ const hits = outsideSpans(acronymsOf(doc, notationOf(doc)), [
356
+ ...referenceListSpans(doc.source, patternsOf(doc, "reference-list-heading")),
357
+ ...outlineLabelSpans(doc.source, patternsOf(doc, "numbered-division")),
358
+ ]);
359
+ const acronyms = [...firstHits(hits).keys()];
360
+ const unexplained = new Set(acronyms.filter((acronym) => !isExplained(acronym, explainedAlone)));
361
+ const bracketed = (acronym: string): boolean => [body, ...headings].some((text) => isBracketed(text, acronym));
362
+ // 定義の一覧("SOW" means …)で定義する書き手も、略語を展開する書き手と数える。
363
+ const definedByWriter = (acronym: string): boolean => bracketed(acronym) || stated.has(acronym);
364
+ const writerSpellsOut = acronyms.filter((acronym) => !common.has(acronym) && explainedAlone(acronym) && definedByWriter(acronym)).length >= options.limit;
309
365
  // 日付を読むには文書の木を作る。上限に届かない文書では作らない。
310
- if (unexplained.size < options.limit) return [];
366
+ if (unexplained.size < options.limit && !writerSpellsOut) return [];
311
367
  const bare = [...firstHits(outsideSpans(hits, dateSpans(doc)))].filter(([acronym]) => unexplained.has(acronym));
312
- if (bare.length < options.limit) return [];
313
- return bare.map(([acronym, hit]) => ({
314
- rule: "undefined-acronym",
315
- severity: "info",
316
- line: 0,
317
- column: 0,
318
- quote: hit.sentence.text.trim(),
319
- values: { word: acronym, count: bare.length, limit: options.limit, offset: hit.offset },
320
- }));
368
+ if (bare.length >= options.limit) return bare.map(([acronym, hit]) => acronymFinding(acronym, hit, bare.length, options.limit));
369
+ if (!writerSpellsOut) return [];
370
+ const alone = bare.filter(([acronym, hit]) => !isNamePartAt(acronym, hit) && !isInHeading(headings, acronym));
371
+ return alone.map(([acronym, hit]) => acronymFinding(acronym, hit, alone.length, options.limit));
321
372
  };
322
373
 
323
374
  /**
@@ -341,9 +392,26 @@ const openingOf = (sentence: Sentence | undefined, unit: ProseDocument["lengthUn
341
392
 
342
393
  const hasNumeralIn = (section: Section): boolean => section.sentences.some((sentence) => hasNumeral(sentence.tokens ?? []));
343
394
 
395
+ /** これより短い節は、具体物があるべきか判断できない。 */
396
+ const MIN_SENTENCES = 3;
397
+
398
+ const adviceWordsOf = (doc: ProseDocument): AdviceWords => ({
399
+ openers: (doc.lexicons["advice-opener"] ?? []).map((entry) => entry.pattern),
400
+ endings: (doc.lexicons["advice-ending"] ?? []).map((entry) => entry.pattern),
401
+ });
402
+
403
+ /** 文の半分以上が問いか指示の節(質問の一覧、手引き)は、数字で裏づける主張をしていない。番号だけの文(a.)は数えない。 */
404
+ const mostlyClaims = (section: Section, language: string, advice: AdviceWords): boolean => {
405
+ const said = section.sentences.filter((sentence) => !isLabelOnly(sentence));
406
+ return said.filter((sentence) => isClaim(sentence, language, advice)).length * 2 > said.length;
407
+ };
408
+
344
409
  export const concreteEvidence: Detector = (doc, options): Finding[] => {
345
410
  const entries = letteredIndexEntries(doc.sections);
346
- const sections = doc.sections.filter((section) => section.sentences.length >= 3 && !entries.has(section));
411
+ const advice = adviceWordsOf(doc);
412
+ const sections = doc.sections.filter(
413
+ (section) => section.sentences.length >= MIN_SENTENCES && !entries.has(section) && mostlyClaims(section, doc.language, advice),
414
+ );
347
415
  const bare = sections.filter(
348
416
  (section) => !CONCRETE.test(doc.source.slice(section.span.start, section.span.end)) && !hasNumeralIn(section) && !startsWithin(section.span, doc.links),
349
417
  );
@@ -1,7 +1,8 @@
1
1
  import type { Detector, Finding, ProseDocument, StructureNode } from "../plugin.ts";
2
2
  import { danglingReferences, duplicateDefinitions, inDocumentOrder, type StructureIssue } from "../structure/issues.ts";
3
3
  import { numberingBreaks } from "../structure/numbering.ts";
4
- import { listNumberBreaks, writtenNumbers } from "../structure/list-numbering.ts";
4
+ import { htmlTextBlocks } from "../structure/html-text-blocks.ts";
5
+ import { listsNumberBreaks } from "../structure/list-numbering.ts";
5
6
  import { weekdayMismatches } from "../structure/weekday.ts";
6
7
  import { documentDateOf } from "./document-date.ts";
7
8
  import { dateOrderBreaks } from "../structure/date-order.ts";
@@ -48,19 +49,20 @@ const findingsOf =
48
49
  values: { ...issue.values, offset: issue.offset },
49
50
  }));
50
51
 
51
- export const danglingReference: Detector = findingsOf("dangling-reference", danglingReferences);
52
- const structureNumberingGap: Detector = findingsOf("numbering-gap", numberingBreaks);
52
+ export const danglingReference: Detector = (doc, options) =>
53
+ findingsOf("dangling-reference", (tree, source) => danglingReferences(tree, source, patternsOf(doc, "section-name-noun")))(doc, options);
54
+ /** Markdown の HTML ブロックの中の条は木に入らない。そこを挟んだ抜けは、見えなかった番号かもしれない。 */
55
+ const structureNumberingGap: Detector = (doc, options) =>
56
+ findingsOf("numbering-gap", (tree, source) => numberingBreaks(tree, doc.markup?.markdown === true ? htmlTextBlocks(source) : []))(doc, options);
53
57
 
54
58
  const lineStartAt = (source: string, offset: number): number => source.lastIndexOf("\n", offset - 1) + 1;
55
59
 
56
- /** The written numbers of the document's Markdown ordered lists that skip or repeat (structure/list-numbering.ts). */
57
- const QUOTED_LINE = /^[ \t]*>/u;
58
-
59
60
  /** 引用の中の箇条書きは、ほかの文書の例で、この文書の番号ではない。 */
60
- const isQuotedList = (source: string, start: number): boolean => QUOTED_LINE.test(source.slice(lineStartAt(source, start), start));
61
+ const isQuotedList = (source: string, start: number): boolean => /^[ \t]*>/u.test(source.slice(lineStartAt(source, start), start));
61
62
 
63
+ /** The written numbers of the document's Markdown ordered lists that skip or repeat (structure/list-numbering.ts). */
62
64
  const listNumberingGaps = (doc: ProseDocument): StructureIssue[] =>
63
- doc.lists.filter((list) => !isQuotedList(doc.source, list.span.start)).flatMap((list) => listNumberBreaks(writtenNumbers(doc.source, list.itemSpans) ?? []));
65
+ listsNumberBreaks(doc.source, doc.lists, (list) => !isQuotedList(doc.source, list.span.start));
64
66
 
65
67
  const lineFindingOf = (doc: ProseDocument, issue: StructureIssue): Finding => ({
66
68
  rule: "numbering-gap",
@@ -0,0 +1,37 @@
1
+ import type { Lexicon, MarkupHeading } from "../plugin.ts";
2
+ import { escapeRegExp } from "../orthography.ts";
3
+
4
+ type Span = { readonly start: number; readonly end: number };
5
+
6
+ /** Lexicon group of placeholder-word: a word that, in a heading, says what follows is a template (Template, 雛形). */
7
+ export const TEMPLATE = "template";
8
+
9
+ const LATIN = /\p{Script=Latin}/u;
10
+
11
+ /** Does the character beside a match end the word? A Latin edge needs a non-letter next to it; a CJK word stands anywhere. */
12
+ const boundsWord = (edge: string, beside: string | undefined): boolean => !LATIN.test(edge) || beside === undefined || !/[\p{L}\p{N}]/u.test(beside);
13
+
14
+ const holdsWordAt = (text: string, pattern: string, index: number): boolean =>
15
+ boundsWord(pattern[0] ?? "", text[index - 1]) && boundsWord(pattern.at(-1) ?? "", text[index + pattern.length]);
16
+
17
+ /** The heading names a template: one of the words appears in it as a whole word, in any case. */
18
+ export const namesTemplate = (heading: string, words: Lexicon): boolean => {
19
+ const text = heading.toLowerCase();
20
+ return words.some((word) => {
21
+ const pattern = word.pattern.toLowerCase();
22
+ return [...text.matchAll(new RegExp(escapeRegExp(pattern), "gu"))].some((match) => holdsWordAt(text, pattern, match.index));
23
+ });
24
+ };
25
+
26
+ /** A heading's section runs from the heading to the next one at its depth or above; text before a template title stays checked. */
27
+ const sectionOf = (headings: readonly MarkupHeading[], index: number, documentEnd: number): Span => {
28
+ const heading = headings[index];
29
+ const next = headings.slice(index + 1).find((later) => later.depth <= (heading?.depth ?? 0));
30
+ return { start: heading?.start ?? 0, end: next?.start ?? documentEnd };
31
+ };
32
+
33
+ /** The parts of a document under a heading that names a template: their blanks are the point, not a slip. */
34
+ export const templateSections = (headings: readonly MarkupHeading[], documentEnd: number, words: Lexicon): Span[] =>
35
+ words.length === 0 ? [] : headings.flatMap((heading, index) => (namesTemplate(heading.text, words) ? [sectionOf(headings, index, documentEnd)] : []));
36
+
37
+ export const isInside = (spans: readonly Span[], offset: number): boolean => spans.some((span) => span.start <= offset && offset < span.end);
@@ -0,0 +1,43 @@
1
+ import type { Token } from "../plugin.ts";
2
+ import { quotedSpans } from "../quoted-span.ts";
3
+
4
+ /** 題の付いたものの名前を閉じる語(語彙表 title-ending: 〜要綱、〜試験、〜事業、〜法)。 */
5
+ export type TitleWords = { readonly endings: readonly string[] };
6
+
7
+ /** 題を囲む括弧。鉤括弧と、見出しや分類の名前を囲む隅付き括弧。 */
8
+ const TITLE_MARKS = { "「": "」", "『": "』", "【": "】" };
9
+ /** 名前のすぐ後ろに付く、その名前の補い(就職氷河期世代)、略称(以下「奨励金」という。)、日付や番号。 */
10
+ const QUALIFIER_OPENERS = new Set(["(", "("]);
11
+ /** 閉じる語の前に要る字数。1 字なら、題ではなく普通の語(新法、本事業)。 */
12
+ const MIN_BODY_LENGTH = 2;
13
+ const BLANK = /^\s*$/u;
14
+
15
+ const surfaceOf = (tokens: readonly Token[]): string => tokens.map((token) => token.surface).join("");
16
+
17
+ /**
18
+ * 連なりの語(run: 隙間なく並ぶ語)が、題の名前を閉じる語で終わるか。閉じる語は語の切れ目で始まる
19
+ * (地方自治法の「法」は語、解決方法の「法」は方法の一部)。閉じる語の前に 2 字以上の語が要る。
20
+ */
21
+ export const endsInTitle = (run: readonly Token[], words: TitleWords): boolean =>
22
+ run.some((_, at) => {
23
+ const tail = surfaceOf(run.slice(at));
24
+ return words.endings.includes(tail) && [...surfaceOf(run.slice(0, at))].length >= MIN_BODY_LENGTH;
25
+ });
26
+
27
+ /**
28
+ * 文(text)の start から end までの連なりが、題の名前の置き方をしているか。
29
+ * 括弧の中身の終わりで閉じる(「令和8年度高度統計人材育成強化拠点形成事業」)、すぐ後ろに補いの括弧が付く
30
+ * (国家公務員中途採用者選考試験(就職氷河期世代))、または文がその連なりだけ(要綱の題を並べた行)。
31
+ */
32
+ export const isTitlePosition = (text: string, start: number, end: number): boolean => {
33
+ const closesBracket = quotedSpans(text, TITLE_MARKS).some((span) => span.end === end && span.start <= start);
34
+ const qualified = QUALIFIER_OPENERS.has(text.charAt(end));
35
+ const alone = BLANK.test(text.slice(0, start)) && BLANK.test(text.slice(end));
36
+ return closesBracket || qualified || alone;
37
+ };
38
+
39
+ /**
40
+ * 文書の中で題として置かれた名前(titled: 題の置き方をした連なりと、見出しの中の漢字の連なり)に、
41
+ * この連なりの字(text)が名前の終わりとして含まれるか。同じ名前は、文の中で使っても名前(認定職業訓練実施奨励金の支給額)。
42
+ */
43
+ export const isTitledName = (text: string, titled: readonly string[]): boolean => titled.some((name) => name.endsWith(text));
@@ -5,6 +5,7 @@ import { quoteAround } from "./quote-around.ts";
5
5
  import { mergeSpans } from "../span-merge.ts";
6
6
  import { firstEndingAfter } from "../soft-break.ts";
7
7
  import { isLabelClose, isLabelInAside } from "./bracket-label.ts";
8
+ import { isInchMark } from "./inch-mark.ts";
8
9
 
9
10
  /**
10
11
  * 括弧の組。どの言語でも同じ字の決まりなので、語彙表ではなくここに置く。family は形(丸・角)で、全角と半角は同じ形の別の幅。
@@ -93,6 +94,8 @@ export const bracketProblems = (text: string): BracketProblem[] => {
93
94
  const scan: Scan = { open: [], problems: [] };
94
95
  const heads = paragraphHeads(text);
95
96
  [...text.matchAll(MARKS)].forEach((match) => {
97
+ const quoteOpen = scan.open.some((entry) => entry.bracket.family === "quote");
98
+ if (isInchMark(text, match.index, quoteOpen)) return;
96
99
  const opening = BY_OPEN.get(match[0]);
97
100
  const closing = BY_CLOSE.get(match[0]);
98
101
  if (opening !== undefined) openWith(scan, opening, match.index, heads.has(match.index));
@@ -91,6 +91,8 @@ const findingOf = (doc: ProseDocument, use: UndefinedTerm): Finding => ({
91
91
  });
92
92
 
93
93
  const NAME_LISTS = ["proper-name-word", "place-region", "calendar-name", "month-name", "company-form", "party-role"] as const;
94
+ /** The lists whose words end a name right after a comma (Post Holdings, Inc.; Canton, Ohio). */
95
+ const FORM_ENDS = ["company-form", "place-region"] as const;
94
96
 
95
97
  /** Capitalised terms, for a language whose package lists the words that join one name (name-joiner); Japanese has none. */
96
98
  const capitalisedUses = (doc: ProseDocument, sentences: readonly (readonly Token[])[], defined: readonly Defined[]): UndefinedTerm[] => {
@@ -99,6 +101,12 @@ const capitalisedUses = (doc: ProseDocument, sentences: readonly (readonly Token
99
101
  names: NAME_LISTS.flatMap((id) => patternsOf(doc, id)),
100
102
  divisions: patternsOf(doc, "numbered-division"),
101
103
  joiners: patternsOf(doc, "name-joiner"),
104
+ nameEnds: patternsOf(doc, "proper-name-word"),
105
+ formEnds: FORM_ENDS.flatMap((id) => patternsOf(doc, id)),
106
+ definitionMarkers: patternsOf(doc, "definition-marker"),
107
+ definedElsewhere: patternsOf(doc, "defined-elsewhere"),
108
+ capacityMarkers: patternsOf(doc, "capacity-marker"),
109
+ articles: patternsOf(doc, "article"),
102
110
  };
103
111
  const marked = [...(doc.markup?.headings ?? []), ...doc.links, ...setApartSpans(doc.source)];
104
112
  return capitalisedTerms({ source: doc.source, readable: readableText(doc.source), sentences, defined, named: namedWords(doc.source), marked, words });
@@ -0,0 +1,10 @@
1
+ import type { Detector } from "../plugin.ts";
2
+ import { unfilledPlaceholder } from "./ai-phrasing.ts";
3
+ import { TEMPLATE, isInside, templateSections } from "./template-sections.ts";
4
+
5
+ /** A blank under a heading that names a template (Template, 雛形) is what the template is for, so it is not reported. */
6
+ export const unfilledPlaceholderOutsideTemplates: Detector = (doc, options) => {
7
+ const words = (options.lexicon ?? []).filter((word) => word.group === TEMPLATE);
8
+ const templates = templateSections(doc.markup?.headings ?? [], doc.source.length, words);
9
+ return unfilledPlaceholder(doc, options).filter((finding) => !isInside(templates, Number(finding.values["offset"])));
10
+ };
@@ -0,0 +1,68 @@
1
+ // Vague words dense for the document's length (vague-word-density). Each sentence counts the vague words it holds once:
2
+ // a word inside a longer one ("timely" in "in a timely manner") is that longer word, and a word inside a term of art of the
3
+ // document's genre ("reasonably" in a contract, "adequately compensate" as the test for an injunction) is not vague
4
+ // there. The rule speaks once per sentence, about the document's density. Pure: the words come from the language's
5
+ // lexicons vague-word and vague-word-term-of-art, the genre from the options.
6
+ import { entriesForGenre } from "../lexicon-genres.ts";
7
+ import { escapeRegExp } from "../orthography.ts";
8
+ import type { Detector, Finding, Lexicon, LexiconEntry, Sentence } from "../plugin.ts";
9
+ import { comparableText, comparableWords, entryRanges } from "./lexicon-match.ts";
10
+ import { rateOver } from "./lexicon.ts";
11
+
12
+ const TERMS_OF_ART = "vague-word-term-of-art";
13
+
14
+ type Place = { readonly start: number; readonly end: number };
15
+
16
+ type Match = { readonly pattern: string; readonly place: Place };
17
+
18
+ /** Token spans are places in the document and text places are in the sentence, so one sentence uses one kind for every entry. */
19
+ const readsTokens = (sentence: Sentence, entries: Lexicon): boolean =>
20
+ sentence.tokens !== undefined && entries.every((entry) => (entry.tokens?.length ?? 0) > 0);
21
+
22
+ /** Where an entry stands in the sentence, as character spans: its tokens, or the comparable text. */
23
+ const placesOf = (sentence: Sentence, entry: LexiconEntry, byTokens: boolean): Place[] => {
24
+ const tokens = sentence.tokens;
25
+ if (byTokens && tokens !== undefined)
26
+ return entryRanges(sentence, entry).map((range) => ({ start: tokens[range.start]?.span.start ?? 0, end: tokens[range.end - 1]?.span.end ?? 0 }));
27
+ const word = comparableWords(entry.pattern);
28
+ if (word === "") return [];
29
+ return [...comparableText(sentence).matchAll(new RegExp(escapeRegExp(word), "gu"))].map((found) => ({ start: found.index, end: found.index + word.length }));
30
+ };
31
+
32
+ const matchesOf = (sentence: Sentence, lexicon: Lexicon, byTokens: boolean): Match[] =>
33
+ lexicon.flatMap((entry) => placesOf(sentence, entry, byTokens).map((place) => ({ pattern: entry.pattern, place })));
34
+
35
+ const covers = (outer: Place, inner: Place): boolean => outer.start <= inner.start && inner.end <= outer.end;
36
+
37
+ const isLonger = (outer: Match, inner: Match): boolean => outer.place.end - outer.place.start > inner.place.end - inner.place.start;
38
+
39
+ /** The vague words of one sentence, in the order written, each once: none inside a longer vague word or a term of art. */
40
+ export const vagueWordsIn = (sentence: Sentence, vague: Lexicon, termsOfArt: Lexicon): string[] => {
41
+ const byTokens = readsTokens(sentence, [...vague, ...termsOfArt]);
42
+ const matches = matchesOf(sentence, vague, byTokens);
43
+ const terms = matchesOf(sentence, termsOfArt, byTokens);
44
+ const kept = matches.filter(
45
+ (match) => !matches.some((other) => isLonger(other, match) && covers(other.place, match.place)) && !terms.some((term) => covers(term.place, match.place)),
46
+ );
47
+ return [...new Set(kept.toSorted((left, right) => left.place.start - right.place.start).map((match) => match.pattern))];
48
+ };
49
+
50
+ type SentenceHits = { readonly sentence: Sentence; readonly words: readonly string[] };
51
+
52
+ export const vagueDensity: Detector = (doc, options): Finding[] => {
53
+ const termsOfArt = entriesForGenre(doc.lexicons[TERMS_OF_ART] ?? [], options.genre);
54
+ const hits: SentenceHits[] = doc.sentences
55
+ .map((sentence) => ({ sentence, words: vagueWordsIn(sentence, options.lexicon ?? [], termsOfArt) }))
56
+ .filter((hit) => hit.words.length > 0);
57
+ const count = hits.reduce((sum, hit) => sum + hit.words.length, 0);
58
+ const density = rateOver(doc, count, options.limit);
59
+ if (density === undefined) return [];
60
+ return hits.map(({ sentence, words }) => ({
61
+ rule: "vague-word-density",
62
+ severity: "warning",
63
+ line: 0,
64
+ column: 0,
65
+ quote: sentence.text.trim(),
66
+ values: { matched: words[0] ?? "", count, density, limit: options.limit, offset: sentence.span.start },
67
+ }));
68
+ };
@@ -118,20 +118,69 @@ const headCount = (heads: ReadonlyMap<string, readonly number[]>, from: number,
118
118
 
119
119
  type Lines = { readonly lines: readonly Line[]; readonly breakers: readonly number[]; readonly heads: ReadonlyMap<string, readonly number[]> };
120
120
 
121
- const scopeOf = (fact: Fact, innermost: StructureNode | undefined, at: Lines): { scope: string; record: boolean } => {
121
+ /** 表の、見出しを繰り返した何番目の並びの升か(table-facts の columnGroup)。並びごとに別の範囲。 */
122
+ type PlacedFact = Fact & { readonly columnGroup?: number };
123
+
124
+ const scopeOf = (fact: PlacedFact, innermost: StructureNode | undefined, at: Lines): { scope: string; record: boolean } => {
122
125
  const index = lineIndexAt(at.lines, fact.value.start);
123
126
  const from = innermost === undefined ? 0 : lineIndexAt(at.lines, innermost.span.start);
124
127
  const to = innermost === undefined ? at.lines.length : lineIndexAt(at.lines, innermost.span.end - 1) + 1;
125
- const scope = [innermost?.address ?? "doc", (at.breakers[index] ?? 0) - (at.breakers[from] ?? 0), parentItem(at.lines, index)].join("#");
128
+ const place = [innermost?.address ?? "doc", (at.breakers[index] ?? 0) - (at.breakers[from] ?? 0), parentItem(at.lines, index)];
129
+ const scope = [...place, ...(fact.columnGroup === undefined ? [] : [`g${fact.columnGroup}`])].join("#");
126
130
  return { scope, record: headCount(at.heads, from, to, fact.key) >= RECORD_REPEATS };
127
131
  };
128
132
 
133
+ const MAX_PARAGRAPH_HEAD = 40;
134
+ /** 文の終わりか続きの印。これで終わる行は小見出しではない。 */
135
+ const SENTENCE_END = /[.!?。.!?::;;,、,]$/u;
136
+ const NOT_PLAIN_LINE = /^[ \t]*(?:[#>|]|```|~~~)/u;
137
+ /** setext の見出しの下線。上の行は見出しで、節として木が分ける。 */
138
+ const SETEXT_UNDERLINE = /^[ \t]*(?:=+|-+)[ \t]*$/u;
139
+
140
+ /**
141
+ * 段落の頭の、短い名前だけの行(「Example」「Element 1 (standard rated portion)」「例」)。文の終わりの印も値も無く、項目や表の行でもない。
142
+ * 例や場合ごとに名前を付けた段落の頭で、続く値はその例の値。
143
+ */
144
+ const isNameOnly = (line: string): boolean => {
145
+ const text = line.trim();
146
+ if (text === "" || text.length > MAX_PARAGRAPH_HEAD) return false;
147
+ return /\p{L}/u.test(text) && !SENTENCE_END.test(text) && !LIST_ITEM.test(line) && !NOT_PLAIN_LINE.test(line);
148
+ };
149
+
150
+ const isParagraphHead = (lines: readonly Line[], index: number, valueLines: ReadonlySet<number>): boolean => {
151
+ const startsParagraph = (lines[index - 1]?.text.trim() ?? "") === "";
152
+ const isSetextHeading = SETEXT_UNDERLINE.test(lines[index + 1]?.text ?? "");
153
+ return startsParagraph && !isSetextHeading && !valueLines.has(index) && isNameOnly(lines[index]?.text ?? "");
154
+ };
155
+
156
+ /** 小見出しの代わりの行の数に、段落の頭の名前だけの行も数える。 */
157
+ const withParagraphHeads = (lines: readonly Line[], facts: readonly Fact[]): number[] => {
158
+ const valueLines = new Set(facts.map((fact) => lineIndexAt(lines, fact.value.start)));
159
+ return lines.reduce<number[]>(
160
+ (counts, line, index) => {
161
+ counts.push((counts[index] ?? 0) + (isBreaker(line) || isParagraphHead(lines, index, valueLines) ? 1 : 0));
162
+ return counts;
163
+ },
164
+ [0],
165
+ );
166
+ };
167
+
168
+ /** paragraphHeads: 段落の頭の名前だけの行(Example、例)でも範囲を分ける。 */
169
+ export type ScopeReading = { readonly paragraphHeads?: boolean };
170
+
129
171
  /** 事実ごとに、比べてよい範囲と、文書のどの部分かを付ける。 */
130
- export const scopedFacts = (facts: readonly Fact[], tree: StructureNode, source: string, summaryWords: readonly string[]): ScopedFact[] => {
172
+ export const scopedFacts = (
173
+ facts: readonly PlacedFact[],
174
+ tree: StructureNode,
175
+ source: string,
176
+ summaryWords: readonly string[],
177
+ reading: ScopeReading = {},
178
+ ): ScopedFact[] => {
131
179
  if (facts.length === 0) return [];
132
180
  const nodes = inDocumentOrder(tree).filter((node) => CONTAINERS.has(node.kind));
133
181
  const lines = linesOf(source);
134
- const at: Lines = { lines, breakers: breakerCounts(lines), heads: headLines(lines) };
182
+ const breakers = reading.paragraphHeads === true ? withParagraphHeads(lines, facts) : breakerCounts(lines);
183
+ const at: Lines = { lines, breakers, heads: headLines(lines) };
135
184
  return facts
136
185
  .toSorted((left, right) => left.value.start - right.value.start)
137
186
  .map((fact) => {
@@ -67,18 +67,21 @@ const cellLabel = (rowLabel: string, column: string, headings: readonly string[]
67
67
  return headings.length > TWO_COLUMNS ? `${rowLabel} ${column}` : rowLabel;
68
68
  };
69
69
 
70
- /** 列が三つ以上の表の升は、行の見出し(row)と列の見出し(column)の二つで名前になる。 */
71
- export type TableFact = Fact & { readonly cell?: { readonly row: string; readonly column: string } };
70
+ /**
71
+ * 列が三つ以上の表の升は、行の見出し(row)と列の見出し(column)の二つで名前になる。
72
+ * columnGroup: 見出しの並びを繰り返す表(Day | Hours | Day | Hours の、第1週と第2週を横に並べた表)の、何番目の並びの升か。
73
+ */
74
+ export type TableFact = Fact & { readonly cell?: { readonly row: string; readonly column: string }; readonly columnGroup?: number };
72
75
 
73
76
  const rowFacts = (
74
- row: Line,
77
+ cells: readonly Cell[],
75
78
  headings: readonly string[],
76
79
  values: readonly FactValue[],
77
80
  source: string,
78
81
  totals?: TotalColumns,
79
82
  counts?: CountLabelWords,
80
83
  ): TableFact[] => {
81
- const [first, ...rest] = cellsOf(row);
84
+ const [first, ...rest] = cells;
82
85
  if (first === undefined) return [];
83
86
  const rowLabel = plain(first.text);
84
87
  const isCountHead = (value: FactValue): boolean => counts !== undefined && isCountLabelHead(source, first, value, counts);
@@ -94,11 +97,45 @@ const rowFacts = (
94
97
  });
95
98
  };
96
99
 
100
+ const MIN_GROUPS = 2;
101
+
102
+ /** 見出しが同じ並びを二度以上繰り返すなら、その並びの幅。並びごとに別の表として読む。 */
103
+ export const columnGroupWidth = (headings: readonly string[]): number | undefined => {
104
+ const keys = headings.map(keyOf);
105
+ const widths = Array.from({ length: Math.floor(keys.length / MIN_GROUPS) - 1 }, (_, index) => index + TWO_COLUMNS);
106
+ return widths.find((width) => keys.length % width === 0 && keys[0] !== "" && keys.every((key, index) => key === keys[index % width]));
107
+ };
108
+
109
+ const groupsOf = <T>(items: readonly T[], width: number | undefined): T[][] =>
110
+ width === undefined ? [[...items]] : Array.from({ length: Math.ceil(items.length / width) }, (_, index) => items.slice(index * width, (index + 1) * width));
111
+
112
+ /** columnGroups: 見出しを繰り返す表を、並びごとに読む(並びの升には columnGroup が付く)。 */
113
+ export type TableReading = { readonly columnGroups?: boolean };
114
+
115
+ type RowReading = { readonly values: readonly FactValue[]; readonly source: string; readonly totals?: TotalColumns; readonly counts?: CountLabelWords };
116
+
117
+ /** 一つの行の升の値。見出しを繰り返す表(width がある)は、並びごとに読んで columnGroup を付ける。 */
118
+ const groupedRowFacts = (row: Line, headings: readonly string[], width: number | undefined, at: RowReading): TableFact[] => {
119
+ const headingGroups = groupsOf(headings, width);
120
+ return groupsOf(cellsOf(row), width).flatMap((cells, group) => {
121
+ const facts = rowFacts(cells, headingGroups[group] ?? [], at.values, at.source, at.totals, at.counts);
122
+ return width === undefined ? facts : facts.map((fact) => ({ ...fact, columnGroup: group }));
123
+ });
124
+ };
125
+
97
126
  /** 文書の表すべての升の値。 */
98
- export const tableFacts = (source: string, values: readonly FactValue[], totals?: TotalColumns, counts?: CountLabelWords): TableFact[] =>
127
+ export const tableFacts = (
128
+ source: string,
129
+ values: readonly FactValue[],
130
+ totals?: TotalColumns,
131
+ counts?: CountLabelWords,
132
+ reading: TableReading = {},
133
+ ): TableFact[] =>
99
134
  tablesOf(linesOf(source)).flatMap((table) => {
100
135
  const headings = cellsOf(table.header).map((cell) => plain(cell.text));
101
- return table.rows.flatMap((row) => rowFacts(row, headings, values, source, totals, counts));
136
+ const width = reading.columnGroups === true ? columnGroupWidth(headings) : undefined;
137
+ const at: RowReading = { values, source, ...(totals === undefined ? {} : { totals }), ...(counts === undefined ? {} : { counts }) };
138
+ return table.rows.flatMap((row) => groupedRowFacts(row, headings, width, at));
102
139
  });
103
140
 
104
141
  /** 文書の表すべての、本体の行の升(見出しの行を除く)。text は書いたまま。 */