chaffjs 0.35.0 → 0.36.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 (700) hide show
  1. package/README.ja.md +12 -0
  2. package/README.md +15 -0
  3. package/dist/announced-count.d.ts +2 -0
  4. package/dist/announced-count.d.ts.map +1 -1
  5. package/dist/announced-count.js +29 -7
  6. package/dist/announced-count.js.map +1 -1
  7. package/dist/chronology-rows.d.ts +9 -0
  8. package/dist/chronology-rows.d.ts.map +1 -0
  9. package/dist/chronology-rows.js +79 -0
  10. package/dist/chronology-rows.js.map +1 -0
  11. package/dist/company-name-run.d.ts +8 -0
  12. package/dist/company-name-run.d.ts.map +1 -0
  13. package/dist/company-name-run.js +30 -0
  14. package/dist/company-name-run.js.map +1 -0
  15. package/dist/compound-head.d.ts +7 -0
  16. package/dist/compound-head.d.ts.map +1 -0
  17. package/dist/compound-head.js +19 -0
  18. package/dist/compound-head.js.map +1 -0
  19. package/dist/date-format.d.ts +8 -3
  20. package/dist/date-format.d.ts.map +1 -1
  21. package/dist/date-format.js +36 -3
  22. package/dist/date-format.js.map +1 -1
  23. package/dist/derived/count-per-period.d.ts +23 -0
  24. package/dist/derived/count-per-period.d.ts.map +1 -0
  25. package/dist/derived/count-per-period.js +14 -0
  26. package/dist/derived/count-per-period.js.map +1 -0
  27. package/dist/detectors/acronym-context.d.ts +4 -0
  28. package/dist/detectors/acronym-context.d.ts.map +1 -1
  29. package/dist/detectors/acronym-context.js +29 -1
  30. package/dist/detectors/acronym-context.js.map +1 -1
  31. package/dist/detectors/acronym-expansion.d.ts +5 -1
  32. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  33. package/dist/detectors/acronym-expansion.js +35 -3
  34. package/dist/detectors/acronym-expansion.js.map +1 -1
  35. package/dist/detectors/acronym-known.d.ts +19 -0
  36. package/dist/detectors/acronym-known.d.ts.map +1 -0
  37. package/dist/detectors/acronym-known.js +32 -0
  38. package/dist/detectors/acronym-known.js.map +1 -0
  39. package/dist/detectors/acronym-link.d.ts +14 -0
  40. package/dist/detectors/acronym-link.d.ts.map +1 -0
  41. package/dist/detectors/acronym-link.js +32 -0
  42. package/dist/detectors/acronym-link.js.map +1 -0
  43. package/dist/detectors/acronym-name.d.ts +8 -1
  44. package/dist/detectors/acronym-name.d.ts.map +1 -1
  45. package/dist/detectors/acronym-name.js +23 -4
  46. package/dist/detectors/acronym-name.js.map +1 -1
  47. package/dist/detectors/addressee-edge.d.ts +13 -0
  48. package/dist/detectors/addressee-edge.d.ts.map +1 -0
  49. package/dist/detectors/addressee-edge.js +13 -0
  50. package/dist/detectors/addressee-edge.js.map +1 -0
  51. package/dist/detectors/announced-count.d.ts.map +1 -1
  52. package/dist/detectors/announced-count.js +1 -0
  53. package/dist/detectors/announced-count.js.map +1 -1
  54. package/dist/detectors/bare-comparative.d.ts +4 -0
  55. package/dist/detectors/bare-comparative.d.ts.map +1 -1
  56. package/dist/detectors/bare-comparative.js +28 -3
  57. package/dist/detectors/bare-comparative.js.map +1 -1
  58. package/dist/detectors/blank-structure.d.ts +7 -1
  59. package/dist/detectors/blank-structure.d.ts.map +1 -1
  60. package/dist/detectors/blank-structure.js +13 -8
  61. package/dist/detectors/blank-structure.js.map +1 -1
  62. package/dist/detectors/bracket-specified.d.ts +6 -0
  63. package/dist/detectors/bracket-specified.d.ts.map +1 -0
  64. package/dist/detectors/bracket-specified.js +23 -0
  65. package/dist/detectors/bracket-specified.js.map +1 -0
  66. package/dist/detectors/broken-link.d.ts +6 -0
  67. package/dist/detectors/broken-link.d.ts.map +1 -1
  68. package/dist/detectors/broken-link.js +23 -2
  69. package/dist/detectors/broken-link.js.map +1 -1
  70. package/dist/detectors/caps-caption.d.ts +7 -6
  71. package/dist/detectors/caps-caption.d.ts.map +1 -1
  72. package/dist/detectors/caps-caption.js +38 -0
  73. package/dist/detectors/caps-caption.js.map +1 -1
  74. package/dist/detectors/caps-phrase.d.ts +6 -0
  75. package/dist/detectors/caps-phrase.d.ts.map +1 -0
  76. package/dist/detectors/caps-phrase.js +16 -0
  77. package/dist/detectors/caps-phrase.js.map +1 -0
  78. package/dist/detectors/char-shape.d.ts +1 -0
  79. package/dist/detectors/char-shape.d.ts.map +1 -1
  80. package/dist/detectors/char-shape.js +29 -16
  81. package/dist/detectors/char-shape.js.map +1 -1
  82. package/dist/detectors/cited-title.d.ts +3 -2
  83. package/dist/detectors/cited-title.d.ts.map +1 -1
  84. package/dist/detectors/cited-title.js +14 -3
  85. package/dist/detectors/cited-title.js.map +1 -1
  86. package/dist/detectors/counter-edge.d.ts +2 -1
  87. package/dist/detectors/counter-edge.d.ts.map +1 -1
  88. package/dist/detectors/counter-edge.js +17 -2
  89. package/dist/detectors/counter-edge.js.map +1 -1
  90. package/dist/detectors/date-any-year.d.ts +7 -3
  91. package/dist/detectors/date-any-year.d.ts.map +1 -1
  92. package/dist/detectors/date-any-year.js +25 -5
  93. package/dist/detectors/date-any-year.js.map +1 -1
  94. package/dist/detectors/date-format.d.ts.map +1 -1
  95. package/dist/detectors/date-format.js +2 -1
  96. package/dist/detectors/date-format.js.map +1 -1
  97. package/dist/detectors/date-year.d.ts +9 -3
  98. package/dist/detectors/date-year.d.ts.map +1 -1
  99. package/dist/detectors/date-year.js +45 -9
  100. package/dist/detectors/date-year.js.map +1 -1
  101. package/dist/detectors/deadline-unit.d.ts +20 -0
  102. package/dist/detectors/deadline-unit.d.ts.map +1 -0
  103. package/dist/detectors/deadline-unit.js +19 -0
  104. package/dist/detectors/deadline-unit.js.map +1 -0
  105. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  106. package/dist/detectors/defined-term-form.js +33 -7
  107. package/dist/detectors/defined-term-form.js.map +1 -1
  108. package/dist/detectors/definition-use.d.ts.map +1 -1
  109. package/dist/detectors/definition-use.js +6 -3
  110. package/dist/detectors/definition-use.js.map +1 -1
  111. package/dist/detectors/derived-numbers.d.ts.map +1 -1
  112. package/dist/detectors/derived-numbers.js +17 -4
  113. package/dist/detectors/derived-numbers.js.map +1 -1
  114. package/dist/detectors/doubled-across-aside.d.ts +9 -0
  115. package/dist/detectors/doubled-across-aside.d.ts.map +1 -0
  116. package/dist/detectors/doubled-across-aside.js +27 -0
  117. package/dist/detectors/doubled-across-aside.js.map +1 -0
  118. package/dist/detectors/doubled-punctuation.d.ts +3 -3
  119. package/dist/detectors/doubled-punctuation.d.ts.map +1 -1
  120. package/dist/detectors/doubled-punctuation.js +23 -4
  121. package/dist/detectors/doubled-punctuation.js.map +1 -1
  122. package/dist/detectors/doubled-word.d.ts.map +1 -1
  123. package/dist/detectors/doubled-word.js +2 -1
  124. package/dist/detectors/doubled-word.js.map +1 -1
  125. package/dist/detectors/ellipsis.d.ts.map +1 -1
  126. package/dist/detectors/ellipsis.js +13 -1
  127. package/dist/detectors/ellipsis.js.map +1 -1
  128. package/dist/detectors/email-letter.d.ts +31 -1
  129. package/dist/detectors/email-letter.d.ts.map +1 -1
  130. package/dist/detectors/email-letter.js +76 -14
  131. package/dist/detectors/email-letter.js.map +1 -1
  132. package/dist/detectors/field-label.d.ts +7 -0
  133. package/dist/detectors/field-label.d.ts.map +1 -0
  134. package/dist/detectors/field-label.js +23 -0
  135. package/dist/detectors/field-label.js.map +1 -0
  136. package/dist/detectors/footnote-mismatch.d.ts +6 -1
  137. package/dist/detectors/footnote-mismatch.d.ts.map +1 -1
  138. package/dist/detectors/footnote-mismatch.js +28 -7
  139. package/dist/detectors/footnote-mismatch.js.map +1 -1
  140. package/dist/detectors/graded-step.d.ts +20 -0
  141. package/dist/detectors/graded-step.d.ts.map +1 -0
  142. package/dist/detectors/graded-step.js +33 -0
  143. package/dist/detectors/graded-step.js.map +1 -0
  144. package/dist/detectors/gram-openers.d.ts +8 -0
  145. package/dist/detectors/gram-openers.d.ts.map +1 -0
  146. package/dist/detectors/gram-openers.js +16 -0
  147. package/dist/detectors/gram-openers.js.map +1 -0
  148. package/dist/detectors/gram-place.d.ts +3 -1
  149. package/dist/detectors/gram-place.d.ts.map +1 -1
  150. package/dist/detectors/gram-place.js +8 -0
  151. package/dist/detectors/gram-place.js.map +1 -1
  152. package/dist/detectors/gram-predicate.d.ts.map +1 -1
  153. package/dist/detectors/gram-predicate.js +37 -3
  154. package/dist/detectors/gram-predicate.js.map +1 -1
  155. package/dist/detectors/heading-echo.d.ts.map +1 -1
  156. package/dist/detectors/heading-echo.js +28 -2
  157. package/dist/detectors/heading-echo.js.map +1 -1
  158. package/dist/detectors/ime-residue.d.ts +1 -1
  159. package/dist/detectors/ime-residue.d.ts.map +1 -1
  160. package/dist/detectors/ime-residue.js +4 -3
  161. package/dist/detectors/ime-residue.js.map +1 -1
  162. package/dist/detectors/kanji-compound-slip.d.ts +3 -0
  163. package/dist/detectors/kanji-compound-slip.d.ts.map +1 -0
  164. package/dist/detectors/kanji-compound-slip.js +50 -0
  165. package/dist/detectors/kanji-compound-slip.js.map +1 -0
  166. package/dist/detectors/kutoten-consistency.d.ts +10 -2
  167. package/dist/detectors/kutoten-consistency.d.ts.map +1 -1
  168. package/dist/detectors/kutoten-consistency.js +76 -12
  169. package/dist/detectors/kutoten-consistency.js.map +1 -1
  170. package/dist/detectors/lexicon.d.ts.map +1 -1
  171. package/dist/detectors/lexicon.js +8 -2
  172. package/dist/detectors/lexicon.js.map +1 -1
  173. package/dist/detectors/link-shape.d.ts.map +1 -1
  174. package/dist/detectors/link-shape.js +9 -3
  175. package/dist/detectors/link-shape.js.map +1 -1
  176. package/dist/detectors/list-item-ending.d.ts +18 -0
  177. package/dist/detectors/list-item-ending.d.ts.map +1 -0
  178. package/dist/detectors/list-item-ending.js +35 -0
  179. package/dist/detectors/list-item-ending.js.map +1 -0
  180. package/dist/detectors/list-parallel.d.ts +18 -5
  181. package/dist/detectors/list-parallel.d.ts.map +1 -1
  182. package/dist/detectors/list-parallel.js +116 -36
  183. package/dist/detectors/list-parallel.js.map +1 -1
  184. package/dist/detectors/list-sentence.d.ts +4 -0
  185. package/dist/detectors/list-sentence.d.ts.map +1 -1
  186. package/dist/detectors/list-sentence.js +1 -1
  187. package/dist/detectors/list-sentence.js.map +1 -1
  188. package/dist/detectors/mark-shape.d.ts +11 -4
  189. package/dist/detectors/mark-shape.d.ts.map +1 -1
  190. package/dist/detectors/mark-shape.js +58 -10
  191. package/dist/detectors/mark-shape.js.map +1 -1
  192. package/dist/detectors/mentioned-phrase.d.ts +9 -0
  193. package/dist/detectors/mentioned-phrase.d.ts.map +1 -0
  194. package/dist/detectors/mentioned-phrase.js +25 -0
  195. package/dist/detectors/mentioned-phrase.js.map +1 -0
  196. package/dist/detectors/middle-dot.d.ts +2 -2
  197. package/dist/detectors/middle-dot.d.ts.map +1 -1
  198. package/dist/detectors/middle-dot.js +4 -2
  199. package/dist/detectors/middle-dot.js.map +1 -1
  200. package/dist/detectors/modal-conflict.d.ts.map +1 -1
  201. package/dist/detectors/modal-conflict.js +5 -2
  202. package/dist/detectors/modal-conflict.js.map +1 -1
  203. package/dist/detectors/notice-paragraph.d.ts +9 -0
  204. package/dist/detectors/notice-paragraph.d.ts.map +1 -0
  205. package/dist/detectors/notice-paragraph.js +21 -0
  206. package/dist/detectors/notice-paragraph.js.map +1 -0
  207. package/dist/detectors/number-style.d.ts.map +1 -1
  208. package/dist/detectors/number-style.js +15 -2
  209. package/dist/detectors/number-style.js.map +1 -1
  210. package/dist/detectors/number-unit.d.ts.map +1 -1
  211. package/dist/detectors/number-unit.js +24 -1
  212. package/dist/detectors/number-unit.js.map +1 -1
  213. package/dist/detectors/numeral-opener.d.ts +6 -0
  214. package/dist/detectors/numeral-opener.d.ts.map +1 -1
  215. package/dist/detectors/numeral-opener.js +24 -1
  216. package/dist/detectors/numeral-opener.js.map +1 -1
  217. package/dist/detectors/obligation-without-deadline.d.ts +5 -1
  218. package/dist/detectors/obligation-without-deadline.d.ts.map +1 -1
  219. package/dist/detectors/obligation-without-deadline.js +44 -23
  220. package/dist/detectors/obligation-without-deadline.js.map +1 -1
  221. package/dist/detectors/open-end-class.d.ts +16 -0
  222. package/dist/detectors/open-end-class.d.ts.map +1 -0
  223. package/dist/detectors/open-end-class.js +65 -0
  224. package/dist/detectors/open-end-class.js.map +1 -0
  225. package/dist/detectors/open-end-condition.d.ts +7 -0
  226. package/dist/detectors/open-end-condition.d.ts.map +1 -0
  227. package/dist/detectors/open-end-condition.js +35 -0
  228. package/dist/detectors/open-end-condition.js.map +1 -0
  229. package/dist/detectors/open-end-names.d.ts +9 -3
  230. package/dist/detectors/open-end-names.d.ts.map +1 -1
  231. package/dist/detectors/open-end-names.js +5 -2
  232. package/dist/detectors/open-end-names.js.map +1 -1
  233. package/dist/detectors/open-end-question.d.ts +15 -0
  234. package/dist/detectors/open-end-question.d.ts.map +1 -0
  235. package/dist/detectors/open-end-question.js +37 -0
  236. package/dist/detectors/open-end-question.js.map +1 -0
  237. package/dist/detectors/open-end-scope.d.ts +16 -0
  238. package/dist/detectors/open-end-scope.d.ts.map +1 -0
  239. package/dist/detectors/open-end-scope.js +47 -0
  240. package/dist/detectors/open-end-scope.js.map +1 -0
  241. package/dist/detectors/opening-date.d.ts +10 -0
  242. package/dist/detectors/opening-date.d.ts.map +1 -0
  243. package/dist/detectors/opening-date.js +33 -0
  244. package/dist/detectors/opening-date.js.map +1 -0
  245. package/dist/detectors/ordinary-phrase.d.ts +20 -0
  246. package/dist/detectors/ordinary-phrase.d.ts.map +1 -0
  247. package/dist/detectors/ordinary-phrase.js +54 -0
  248. package/dist/detectors/ordinary-phrase.js.map +1 -0
  249. package/dist/detectors/organisation-run.d.ts +5 -1
  250. package/dist/detectors/organisation-run.d.ts.map +1 -1
  251. package/dist/detectors/organisation-run.js +7 -0
  252. package/dist/detectors/organisation-run.js.map +1 -1
  253. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  254. package/dist/detectors/orthographic-variant.js +56 -20
  255. package/dist/detectors/orthographic-variant.js.map +1 -1
  256. package/dist/detectors/orthography.d.ts.map +1 -1
  257. package/dist/detectors/orthography.js +6 -1
  258. package/dist/detectors/orthography.js.map +1 -1
  259. package/dist/detectors/oxford-comma.d.ts.map +1 -1
  260. package/dist/detectors/oxford-comma.js +38 -1
  261. package/dist/detectors/oxford-comma.js.map +1 -1
  262. package/dist/detectors/person-consistency.d.ts +2 -1
  263. package/dist/detectors/person-consistency.d.ts.map +1 -1
  264. package/dist/detectors/person-consistency.js +5 -3
  265. package/dist/detectors/person-consistency.js.map +1 -1
  266. package/dist/detectors/phrase-match.d.ts +2 -0
  267. package/dist/detectors/phrase-match.d.ts.map +1 -1
  268. package/dist/detectors/phrase-match.js +11 -4
  269. package/dist/detectors/phrase-match.js.map +1 -1
  270. package/dist/detectors/placeholder-text.d.ts.map +1 -1
  271. package/dist/detectors/placeholder-text.js +18 -3
  272. package/dist/detectors/placeholder-text.js.map +1 -1
  273. package/dist/detectors/placeholder.d.ts +3 -1
  274. package/dist/detectors/placeholder.d.ts.map +1 -1
  275. package/dist/detectors/placeholder.js +8 -3
  276. package/dist/detectors/placeholder.js.map +1 -1
  277. package/dist/detectors/postcode-end.d.ts +3 -0
  278. package/dist/detectors/postcode-end.d.ts.map +1 -0
  279. package/dist/detectors/postcode-end.js +15 -0
  280. package/dist/detectors/postcode-end.js.map +1 -0
  281. package/dist/detectors/prose-total-words.d.ts +5 -0
  282. package/dist/detectors/prose-total-words.d.ts.map +1 -0
  283. package/dist/detectors/prose-total-words.js +14 -0
  284. package/dist/detectors/prose-total-words.js.map +1 -0
  285. package/dist/detectors/registry/kanji-compound-slip.d.ts +3 -0
  286. package/dist/detectors/registry/kanji-compound-slip.d.ts.map +1 -0
  287. package/dist/detectors/registry/kanji-compound-slip.js +3 -0
  288. package/dist/detectors/registry/kanji-compound-slip.js.map +1 -0
  289. package/dist/detectors/registry/phrase-in-use.d.ts +3 -0
  290. package/dist/detectors/registry/phrase-in-use.d.ts.map +1 -0
  291. package/dist/detectors/registry/phrase-in-use.js +3 -0
  292. package/dist/detectors/registry/phrase-in-use.js.map +1 -0
  293. package/dist/detectors/requirement-smell.d.ts.map +1 -1
  294. package/dist/detectors/requirement-smell.js +29 -5
  295. package/dist/detectors/requirement-smell.js.map +1 -1
  296. package/dist/detectors/scaled-figure-unit.d.ts +28 -0
  297. package/dist/detectors/scaled-figure-unit.d.ts.map +1 -0
  298. package/dist/detectors/scaled-figure-unit.js +78 -0
  299. package/dist/detectors/scaled-figure-unit.js.map +1 -0
  300. package/dist/detectors/sentence-counts.d.ts +2 -0
  301. package/dist/detectors/sentence-counts.d.ts.map +1 -1
  302. package/dist/detectors/sentence-counts.js +18 -7
  303. package/dist/detectors/sentence-counts.js.map +1 -1
  304. package/dist/detectors/sentence-ending.d.ts +2 -0
  305. package/dist/detectors/sentence-ending.d.ts.map +1 -1
  306. package/dist/detectors/sentence-ending.js +10 -3
  307. package/dist/detectors/sentence-ending.js.map +1 -1
  308. package/dist/detectors/sentence-load.d.ts +4 -1
  309. package/dist/detectors/sentence-load.d.ts.map +1 -1
  310. package/dist/detectors/sentence-load.js +9 -2
  311. package/dist/detectors/sentence-load.js.map +1 -1
  312. package/dist/detectors/signals.d.ts.map +1 -1
  313. package/dist/detectors/signals.js +30 -50
  314. package/dist/detectors/signals.js.map +1 -1
  315. package/dist/detectors/structure-tree.d.ts.map +1 -1
  316. package/dist/detectors/structure-tree.js +14 -13
  317. package/dist/detectors/structure-tree.js.map +1 -1
  318. package/dist/detectors/structure.d.ts.map +1 -1
  319. package/dist/detectors/structure.js +2 -1
  320. package/dist/detectors/structure.js.map +1 -1
  321. package/dist/detectors/subject-name-line.d.ts +8 -0
  322. package/dist/detectors/subject-name-line.d.ts.map +1 -0
  323. package/dist/detectors/subject-name-line.js +53 -0
  324. package/dist/detectors/subject-name-line.js.map +1 -0
  325. package/dist/detectors/table-header.d.ts.map +1 -1
  326. package/dist/detectors/table-header.js +13 -1
  327. package/dist/detectors/table-header.js.map +1 -1
  328. package/dist/detectors/technical-docs.d.ts.map +1 -1
  329. package/dist/detectors/technical-docs.js +4 -1
  330. package/dist/detectors/technical-docs.js.map +1 -1
  331. package/dist/detectors/term-mention-sentence.d.ts +4 -0
  332. package/dist/detectors/term-mention-sentence.d.ts.map +1 -0
  333. package/dist/detectors/term-mention-sentence.js +28 -0
  334. package/dist/detectors/term-mention-sentence.js.map +1 -0
  335. package/dist/detectors/titled-run.d.ts +2 -1
  336. package/dist/detectors/titled-run.d.ts.map +1 -1
  337. package/dist/detectors/titled-run.js +9 -4
  338. package/dist/detectors/titled-run.js.map +1 -1
  339. package/dist/detectors/token-shape.d.ts.map +1 -1
  340. package/dist/detectors/token-shape.js +13 -5
  341. package/dist/detectors/token-shape.js.map +1 -1
  342. package/dist/detectors/vague-density.d.ts +5 -2
  343. package/dist/detectors/vague-density.d.ts.map +1 -1
  344. package/dist/detectors/vague-density.js +17 -4
  345. package/dist/detectors/vague-density.js.map +1 -1
  346. package/dist/document-paragraphs.d.ts +4 -0
  347. package/dist/document-paragraphs.d.ts.map +1 -0
  348. package/dist/document-paragraphs.js +24 -0
  349. package/dist/document-paragraphs.js.map +1 -0
  350. package/dist/document.d.ts.map +1 -1
  351. package/dist/document.js +2 -19
  352. package/dist/document.js.map +1 -1
  353. package/dist/figure-references.d.ts +3 -0
  354. package/dist/figure-references.d.ts.map +1 -1
  355. package/dist/figure-references.js +6 -2
  356. package/dist/figure-references.js.map +1 -1
  357. package/dist/format-template.d.ts +3 -0
  358. package/dist/format-template.d.ts.map +1 -0
  359. package/dist/format-template.js +13 -0
  360. package/dist/format-template.js.map +1 -0
  361. package/dist/formula-brackets.d.ts +7 -0
  362. package/dist/formula-brackets.d.ts.map +1 -0
  363. package/dist/formula-brackets.js +29 -0
  364. package/dist/formula-brackets.js.map +1 -0
  365. package/dist/html/html-markdown.d.ts.map +1 -1
  366. package/dist/html/html-markdown.js +2 -1
  367. package/dist/html/html-markdown.js.map +1 -1
  368. package/dist/html/html-site-chrome.d.ts +5 -0
  369. package/dist/html/html-site-chrome.d.ts.map +1 -0
  370. package/dist/html/html-site-chrome.js +33 -0
  371. package/dist/html/html-site-chrome.js.map +1 -0
  372. package/dist/joined-view.d.ts.map +1 -1
  373. package/dist/joined-view.js +1 -0
  374. package/dist/joined-view.js.map +1 -1
  375. package/dist/kanji-compound-slip.d.ts +32 -0
  376. package/dist/kanji-compound-slip.d.ts.map +1 -0
  377. package/dist/kanji-compound-slip.js +63 -0
  378. package/dist/kanji-compound-slip.js.map +1 -0
  379. package/dist/latin-company-form.d.ts +6 -0
  380. package/dist/latin-company-form.d.ts.map +1 -0
  381. package/dist/latin-company-form.js +32 -0
  382. package/dist/latin-company-form.js.map +1 -0
  383. package/dist/latin-word-boundary.d.ts +15 -0
  384. package/dist/latin-word-boundary.d.ts.map +1 -0
  385. package/dist/latin-word-boundary.js +40 -0
  386. package/dist/latin-word-boundary.js.map +1 -0
  387. package/dist/lexicon-genres.d.ts +2 -0
  388. package/dist/lexicon-genres.d.ts.map +1 -1
  389. package/dist/lexicon-genres.js +1 -1
  390. package/dist/lexicon-genres.js.map +1 -1
  391. package/dist/link-shape.d.ts +5 -0
  392. package/dist/link-shape.d.ts.map +1 -1
  393. package/dist/link-shape.js +18 -0
  394. package/dist/link-shape.js.map +1 -1
  395. package/dist/link-title.d.ts +4 -0
  396. package/dist/link-title.d.ts.map +1 -0
  397. package/dist/link-title.js +33 -0
  398. package/dist/link-title.js.map +1 -0
  399. package/dist/list-item-paragraphs.d.ts +15 -0
  400. package/dist/list-item-paragraphs.d.ts.map +1 -0
  401. package/dist/list-item-paragraphs.js +19 -0
  402. package/dist/list-item-paragraphs.js.map +1 -0
  403. package/dist/literal-quote.d.ts +12 -0
  404. package/dist/literal-quote.d.ts.map +1 -0
  405. package/dist/literal-quote.js +43 -0
  406. package/dist/literal-quote.js.map +1 -0
  407. package/dist/nado-closed-list.d.ts.map +1 -1
  408. package/dist/nado-closed-list.js +39 -5
  409. package/dist/nado-closed-list.js.map +1 -1
  410. package/dist/name-separator.d.ts.map +1 -1
  411. package/dist/name-separator.js +25 -2
  412. package/dist/name-separator.js.map +1 -1
  413. package/dist/nominal-stem.d.ts +14 -1
  414. package/dist/nominal-stem.d.ts.map +1 -1
  415. package/dist/nominal-stem.js +42 -4
  416. package/dist/nominal-stem.js.map +1 -1
  417. package/dist/person-forms.d.ts +8 -0
  418. package/dist/person-forms.d.ts.map +1 -1
  419. package/dist/person-forms.js +12 -0
  420. package/dist/person-forms.js.map +1 -1
  421. package/dist/plugin.d.ts +5 -0
  422. package/dist/plugin.d.ts.map +1 -1
  423. package/dist/preamble-paragraphs.d.ts +3 -2
  424. package/dist/preamble-paragraphs.d.ts.map +1 -1
  425. package/dist/preamble-paragraphs.js +8 -2
  426. package/dist/preamble-paragraphs.js.map +1 -1
  427. package/dist/spacing-code.d.ts +1 -0
  428. package/dist/spacing-code.d.ts.map +1 -1
  429. package/dist/spacing-code.js +4 -1
  430. package/dist/spacing-code.js.map +1 -1
  431. package/dist/split-noun.d.ts +10 -0
  432. package/dist/split-noun.d.ts.map +1 -0
  433. package/dist/split-noun.js +50 -0
  434. package/dist/split-noun.js.map +1 -0
  435. package/dist/structure/attached-parts.d.ts +11 -0
  436. package/dist/structure/attached-parts.d.ts.map +1 -0
  437. package/dist/structure/attached-parts.js +31 -0
  438. package/dist/structure/attached-parts.js.map +1 -0
  439. package/dist/structure/build.d.ts.map +1 -1
  440. package/dist/structure/build.js +7 -4
  441. package/dist/structure/build.js.map +1 -1
  442. package/dist/structure/contents-lines.d.ts +7 -0
  443. package/dist/structure/contents-lines.d.ts.map +1 -0
  444. package/dist/structure/contents-lines.js +51 -0
  445. package/dist/structure/contents-lines.js.map +1 -0
  446. package/dist/structure/defining-names.d.ts +37 -0
  447. package/dist/structure/defining-names.d.ts.map +1 -0
  448. package/dist/structure/defining-names.js +122 -0
  449. package/dist/structure/defining-names.js.map +1 -0
  450. package/dist/structure/definition-use.d.ts +3 -2
  451. package/dist/structure/definition-use.d.ts.map +1 -1
  452. package/dist/structure/definition-use.js +99 -31
  453. package/dist/structure/definition-use.js.map +1 -1
  454. package/dist/structure/issues.d.ts +4 -1
  455. package/dist/structure/issues.d.ts.map +1 -1
  456. package/dist/structure/issues.js +5 -2
  457. package/dist/structure/issues.js.map +1 -1
  458. package/dist/structure/list-lead-in.d.ts +19 -0
  459. package/dist/structure/list-lead-in.d.ts.map +1 -0
  460. package/dist/structure/list-lead-in.js +85 -0
  461. package/dist/structure/list-lead-in.js.map +1 -0
  462. package/dist/structure/modal-conflict.d.ts +7 -2
  463. package/dist/structure/modal-conflict.d.ts.map +1 -1
  464. package/dist/structure/modal-conflict.js +36 -7
  465. package/dist/structure/modal-conflict.js.map +1 -1
  466. package/dist/structure/negated-act.d.ts +17 -0
  467. package/dist/structure/negated-act.d.ts.map +1 -0
  468. package/dist/structure/negated-act.js +32 -0
  469. package/dist/structure/negated-act.js.map +1 -0
  470. package/dist/structure/numbering.d.ts +3 -1
  471. package/dist/structure/numbering.d.ts.map +1 -1
  472. package/dist/structure/numbering.js +20 -8
  473. package/dist/structure/numbering.js.map +1 -1
  474. package/dist/structure/prose-total.d.ts +4 -0
  475. package/dist/structure/prose-total.d.ts.map +1 -1
  476. package/dist/structure/prose-total.js +16 -6
  477. package/dist/structure/prose-total.js.map +1 -1
  478. package/dist/structure/repeated-number.d.ts +24 -0
  479. package/dist/structure/repeated-number.d.ts.map +1 -0
  480. package/dist/structure/repeated-number.js +89 -0
  481. package/dist/structure/repeated-number.js.map +1 -0
  482. package/dist/structure/rounded-amount.d.ts +8 -0
  483. package/dist/structure/rounded-amount.d.ts.map +1 -0
  484. package/dist/structure/rounded-amount.js +48 -0
  485. package/dist/structure/rounded-amount.js.map +1 -0
  486. package/dist/structure/side-step.d.ts +8 -0
  487. package/dist/structure/side-step.d.ts.map +1 -0
  488. package/dist/structure/side-step.js +21 -0
  489. package/dist/structure/side-step.js.map +1 -0
  490. package/dist/structure/spanned-label-blank.d.ts +8 -0
  491. package/dist/structure/spanned-label-blank.d.ts.map +1 -0
  492. package/dist/structure/spanned-label-blank.js +37 -0
  493. package/dist/structure/spanned-label-blank.js.map +1 -0
  494. package/dist/structure/universal.d.ts +3 -1
  495. package/dist/structure/universal.d.ts.map +1 -1
  496. package/dist/structure/universal.js +9 -2
  497. package/dist/structure/universal.js.map +1 -1
  498. package/dist/structure/unspaced-heading.d.ts +10 -0
  499. package/dist/structure/unspaced-heading.d.ts.map +1 -0
  500. package/dist/structure/unspaced-heading.js +24 -0
  501. package/dist/structure/unspaced-heading.js.map +1 -0
  502. package/dist/structure/written-list-path.d.ts +10 -0
  503. package/dist/structure/written-list-path.d.ts.map +1 -0
  504. package/dist/structure/written-list-path.js +37 -0
  505. package/dist/structure/written-list-path.js.map +1 -0
  506. package/dist/structure/written-resolve.d.ts +1 -1
  507. package/dist/structure/written-resolve.d.ts.map +1 -1
  508. package/dist/structure/written-resolve.js +15 -9
  509. package/dist/structure/written-resolve.js.map +1 -1
  510. package/dist/title-line.d.ts +21 -0
  511. package/dist/title-line.d.ts.map +1 -0
  512. package/dist/title-line.js +27 -0
  513. package/dist/title-line.js.map +1 -0
  514. package/package.json +3 -3
  515. package/rules/all-caps-shouting.yaml +2 -2
  516. package/rules/announced-count-mismatch.yaml +2 -2
  517. package/rules/bare-url-mix.yaml +2 -2
  518. package/rules/bracket-nesting.yaml +2 -2
  519. package/rules/bracket-width-consistency.yaml +2 -2
  520. package/rules/broken-link.yaml +2 -2
  521. package/rules/cliche.yaml +4 -3
  522. package/rules/code-fence-language-mix.yaml +2 -2
  523. package/rules/comparative-without-baseline.yaml +2 -2
  524. package/rules/dangling-figure-reference.yaml +2 -2
  525. package/rules/date-without-year.yaml +10 -2
  526. package/rules/defined-term-form.yaml +2 -2
  527. package/rules/doubled-punctuation.yaml +4 -4
  528. package/rules/doubled-word.yaml +2 -2
  529. package/rules/duplicate-definition.yaml +2 -2
  530. package/rules/duration-mismatch.yaml +2 -2
  531. package/rules/email-greeting-closing.yaml +2 -2
  532. package/rules/email-subject-length.yaml +2 -2
  533. package/rules/empty-table-cell.yaml +2 -2
  534. package/rules/footnote-mismatch.yaml +2 -2
  535. package/rules/heading-echo.yaml +2 -2
  536. package/rules/ime-residue.yaml +2 -2
  537. package/rules/kanji-compound-slip.yaml +67 -0
  538. package/rules/kutoten-consistency.yaml +4 -4
  539. package/rules/latin-spacing.yaml +2 -2
  540. package/rules/list-item-form-mix.yaml +2 -2
  541. package/rules/max-kanji-continuous.yaml +6 -4
  542. package/rules/modal-conflict.yaml +1 -0
  543. package/rules/nado-closed-list.yaml +2 -2
  544. package/rules/ngram-repetition.yaml +2 -2
  545. package/rules/no-doubled-joshi.yaml +2 -2
  546. package/rules/no-mixed-desumasu.yaml +3 -3
  547. package/rules/no-nakaguro-parallel.yaml +2 -0
  548. package/rules/number-without-unit.yaml +9 -4
  549. package/rules/numbering-gap.yaml +7 -2
  550. package/rules/numeral-sentence-start.yaml +2 -2
  551. package/rules/obligation-without-deadline.yaml +2 -2
  552. package/rules/orthographic-variant.yaml +2 -2
  553. package/rules/oxford-comma-consistency.yaml +3 -3
  554. package/rules/preamble-length.yaml +2 -2
  555. package/rules/requirement-smell.yaml +3 -3
  556. package/rules/table-header-variant.yaml +2 -2
  557. package/rules/teki-overuse.yaml +3 -2
  558. package/rules/total-mismatch.yaml +2 -2
  559. package/rules/translationese-density.yaml +2 -2
  560. package/rules/undefined-acronym.yaml +4 -2
  561. package/rules/unfilled-placeholder.yaml +4 -4
  562. package/rules/use-before-definition.yaml +2 -2
  563. package/rules/vague-word-density.yaml +2 -2
  564. package/skills/chaff/SKILL.md +7 -1
  565. package/src/announced-count.ts +35 -8
  566. package/src/chronology-rows.ts +86 -0
  567. package/src/company-name-run.ts +37 -0
  568. package/src/compound-head.ts +25 -0
  569. package/src/date-format.ts +43 -4
  570. package/src/derived/count-per-period.ts +28 -0
  571. package/src/detectors/acronym-context.ts +30 -1
  572. package/src/detectors/acronym-expansion.ts +47 -6
  573. package/src/detectors/acronym-known.ts +40 -0
  574. package/src/detectors/acronym-link.ts +37 -0
  575. package/src/detectors/acronym-name.ts +27 -4
  576. package/src/detectors/addressee-edge.ts +16 -0
  577. package/src/detectors/announced-count.ts +1 -0
  578. package/src/detectors/bare-comparative.ts +44 -3
  579. package/src/detectors/blank-structure.ts +21 -10
  580. package/src/detectors/bracket-specified.ts +25 -0
  581. package/src/detectors/broken-link.ts +26 -2
  582. package/src/detectors/caps-caption.ts +42 -0
  583. package/src/detectors/caps-phrase.ts +17 -0
  584. package/src/detectors/char-shape.ts +30 -9
  585. package/src/detectors/cited-title.ts +15 -3
  586. package/src/detectors/counter-edge.ts +17 -2
  587. package/src/detectors/date-any-year.ts +27 -6
  588. package/src/detectors/date-format.ts +2 -1
  589. package/src/detectors/date-year.ts +64 -16
  590. package/src/detectors/deadline-unit.ts +40 -0
  591. package/src/detectors/defined-term-form.ts +32 -12
  592. package/src/detectors/definition-use.ts +7 -3
  593. package/src/detectors/derived-numbers.ts +19 -4
  594. package/src/detectors/doubled-across-aside.ts +36 -0
  595. package/src/detectors/doubled-punctuation.ts +28 -7
  596. package/src/detectors/doubled-word.ts +2 -1
  597. package/src/detectors/ellipsis.ts +16 -1
  598. package/src/detectors/email-letter.ts +97 -21
  599. package/src/detectors/field-label.ts +25 -0
  600. package/src/detectors/footnote-mismatch.ts +39 -7
  601. package/src/detectors/graded-step.ts +52 -0
  602. package/src/detectors/gram-openers.ts +21 -0
  603. package/src/detectors/gram-place.ts +10 -1
  604. package/src/detectors/gram-predicate.ts +43 -3
  605. package/src/detectors/heading-echo.ts +31 -3
  606. package/src/detectors/ime-residue.ts +4 -3
  607. package/src/detectors/kanji-compound-slip.ts +61 -0
  608. package/src/detectors/kutoten-consistency.ts +93 -14
  609. package/src/detectors/lexicon.ts +12 -3
  610. package/src/detectors/link-shape.ts +10 -3
  611. package/src/detectors/list-item-ending.ts +51 -0
  612. package/src/detectors/list-parallel.ts +158 -33
  613. package/src/detectors/list-sentence.ts +5 -1
  614. package/src/detectors/mark-shape.ts +64 -10
  615. package/src/detectors/mentioned-phrase.ts +38 -0
  616. package/src/detectors/middle-dot.ts +7 -2
  617. package/src/detectors/modal-conflict.ts +5 -2
  618. package/src/detectors/notice-paragraph.ts +33 -0
  619. package/src/detectors/number-style.ts +14 -2
  620. package/src/detectors/number-unit.ts +28 -1
  621. package/src/detectors/numeral-opener.ts +27 -1
  622. package/src/detectors/obligation-without-deadline.ts +29 -4
  623. package/src/detectors/open-end-class.ts +78 -0
  624. package/src/detectors/open-end-condition.ts +40 -0
  625. package/src/detectors/open-end-names.ts +9 -3
  626. package/src/detectors/open-end-question.ts +50 -0
  627. package/src/detectors/open-end-scope.ts +62 -0
  628. package/src/detectors/opening-date.ts +47 -0
  629. package/src/detectors/ordinary-phrase.ts +62 -0
  630. package/src/detectors/organisation-run.ts +13 -2
  631. package/src/detectors/orthographic-variant.ts +85 -25
  632. package/src/detectors/orthography.ts +10 -1
  633. package/src/detectors/oxford-comma.ts +40 -1
  634. package/src/detectors/person-consistency.ts +5 -3
  635. package/src/detectors/phrase-match.ts +16 -5
  636. package/src/detectors/placeholder-text.ts +18 -3
  637. package/src/detectors/placeholder.ts +10 -3
  638. package/src/detectors/postcode-end.ts +16 -0
  639. package/src/detectors/prose-total-words.ts +18 -0
  640. package/src/detectors/registry/kanji-compound-slip.ts +4 -0
  641. package/src/detectors/registry/phrase-in-use.ts +4 -0
  642. package/src/detectors/requirement-smell.ts +38 -5
  643. package/src/detectors/scaled-figure-unit.ts +103 -0
  644. package/src/detectors/sentence-counts.ts +24 -8
  645. package/src/detectors/sentence-ending.ts +12 -3
  646. package/src/detectors/sentence-load.ts +9 -2
  647. package/src/detectors/signals.ts +33 -51
  648. package/src/detectors/structure-tree.ts +14 -14
  649. package/src/detectors/structure.ts +2 -1
  650. package/src/detectors/subject-name-line.ts +66 -0
  651. package/src/detectors/table-header.ts +15 -1
  652. package/src/detectors/technical-docs.ts +5 -1
  653. package/src/detectors/term-mention-sentence.ts +31 -0
  654. package/src/detectors/titled-run.ts +9 -4
  655. package/src/detectors/token-shape.ts +14 -5
  656. package/src/detectors/vague-density.ts +20 -4
  657. package/src/document-paragraphs.ts +27 -0
  658. package/src/document.ts +3 -23
  659. package/src/figure-references.ts +7 -2
  660. package/src/format-template.ts +15 -0
  661. package/src/formula-brackets.ts +36 -0
  662. package/src/html/html-markdown.ts +2 -1
  663. package/src/html/html-site-chrome.ts +47 -0
  664. package/src/joined-view.ts +1 -0
  665. package/src/kanji-compound-slip.ts +98 -0
  666. package/src/latin-company-form.ts +38 -0
  667. package/src/latin-word-boundary.ts +46 -0
  668. package/src/lexicon-genres.ts +1 -1
  669. package/src/link-shape.ts +19 -0
  670. package/src/link-title.ts +38 -0
  671. package/src/list-item-paragraphs.ts +22 -0
  672. package/src/literal-quote.ts +55 -0
  673. package/src/nado-closed-list.ts +48 -5
  674. package/src/name-separator.ts +27 -2
  675. package/src/nominal-stem.ts +62 -5
  676. package/src/person-forms.ts +15 -0
  677. package/src/plugin.ts +5 -0
  678. package/src/preamble-paragraphs.ts +13 -3
  679. package/src/spacing-code.ts +5 -1
  680. package/src/split-noun.ts +63 -0
  681. package/src/structure/attached-parts.ts +32 -0
  682. package/src/structure/build.ts +7 -4
  683. package/src/structure/contents-lines.ts +63 -0
  684. package/src/structure/defining-names.ts +155 -0
  685. package/src/structure/definition-use.ts +116 -25
  686. package/src/structure/issues.ts +7 -3
  687. package/src/structure/list-lead-in.ts +94 -0
  688. package/src/structure/modal-conflict.ts +42 -7
  689. package/src/structure/negated-act.ts +49 -0
  690. package/src/structure/numbering.ts +24 -9
  691. package/src/structure/prose-total.ts +20 -6
  692. package/src/structure/repeated-number.ts +110 -0
  693. package/src/structure/rounded-amount.ts +55 -0
  694. package/src/structure/side-step.ts +21 -0
  695. package/src/structure/spanned-label-blank.ts +39 -0
  696. package/src/structure/universal.ts +11 -2
  697. package/src/structure/unspaced-heading.ts +27 -0
  698. package/src/structure/written-list-path.ts +46 -0
  699. package/src/structure/written-resolve.ts +16 -9
  700. package/src/title-line.ts +52 -0
@@ -6,6 +6,8 @@ import { readMarkdown } from "../markdown-read.ts";
6
6
  import { spanOf, type MarkdownNode } from "../markdown-node.ts";
7
7
  import { eachPreOrder } from "../tree-walk.ts";
8
8
  import { quoteAt } from "./structure-tree.ts";
9
+ import { formsOf } from "./currency-notation.ts";
10
+ import { scaledFiguresIn, scaleGaps, type ScaleWords } from "./scaled-figure-unit.ts";
9
11
 
10
12
  /** One value of a kind: where its number starts, as written, and its unit ("" when bare). */
11
13
  export type Measured = { readonly kind: string; readonly name: string; readonly offset: number; readonly written: string; readonly unit: string };
@@ -157,7 +159,30 @@ const pieceCountOf = (doc: ProseDocument): PieceCount => {
157
159
  return { columns: patterns("column"), periods: patterns("period") };
158
160
  };
159
161
 
160
- export const numberWithoutUnit: Detector = (doc: ProseDocument): Finding[] => {
162
+ const patternsOf = (doc: ProseDocument, name: string): string[] => (doc.lexicons[name] ?? []).map((entry) => entry.pattern);
163
+
164
+ const scaleWordsOf = (doc: ProseDocument): ScaleWords => ({
165
+ scales: patternsOf(doc, "figure-scale-word"),
166
+ forms: formsOf(doc),
167
+ joiners: ["figure-scale-joiner", "range-connector", "enumeration-joiner"].flatMap((name) => patternsOf(doc, name)),
168
+ });
169
+
170
+ /** The one number in prose with a word of scale and no unit, where that word otherwise always carries one (83,494百万). */
171
+ const scaleFindings = (doc: ProseDocument): Finding[] => {
172
+ const prose = doc.prose ?? doc.source;
173
+ const words = scaleWordsOf(doc);
174
+ return scaleGaps(prose, scaledFiguresIn(prose, words), words.joiners).map(({ figure, unit }) => ({
175
+ rule: "number-without-unit",
176
+ severity: "warning",
177
+ line: 0,
178
+ column: 0,
179
+ variant: "scale",
180
+ quote: quoteAt(doc.source, figure.offset),
181
+ values: { value: figure.written, name: figure.scale, unit, offset: figure.offset },
182
+ }));
183
+ };
184
+
185
+ const columnFindings = (doc: ProseDocument): Finding[] => {
161
186
  const count = pieceCountOf(doc);
162
187
  return unitGaps(measuredValues(doc.source, doc.prose ?? doc.source, unitsOf(doc)))
163
188
  .filter((gap) => !isPieceCount(gap, count))
@@ -170,3 +195,5 @@ export const numberWithoutUnit: Detector = (doc: ProseDocument): Finding[] => {
170
195
  values: { value: gap.value.written, name: gap.value.name, unit: gap.unit, offset: gap.value.offset },
171
196
  }));
172
197
  };
198
+
199
+ export const numberWithoutUnit: Detector = (doc: ProseDocument): Finding[] => [...columnFindings(doc), ...scaleFindings(doc)];
@@ -1,3 +1,4 @@
1
+ import { escapeRegExp } from "../orthography.ts";
1
2
  import type { Detector, Finding, Sentence, Span } from "../plugin.ts";
2
3
 
3
4
  /**
@@ -56,6 +57,31 @@ export const followsSentenceEnd = (source: string, offset: number, labels: Reado
56
57
  return DIGITS.test(word) || (PLAIN_WORD.test(word) && !labels.has(word.toLowerCase()));
57
58
  };
58
59
 
60
+ const CLAUSE_NUMBER = /^(?<parent>\d{1,3})\.(?<child>\d{1,3})$/u;
61
+ const BLANK = /^[ \t]*$/u;
62
+
63
+ /** Whether offset is the first thing on its line, after spaces only. */
64
+ const startsLine = (source: string, offset: number): boolean => BLANK.test(source.slice(source.lastIndexOf("\n", offset - 1) + 1, offset));
65
+
66
+ /** Whether a line opens with this clause number (2.2) and a space. */
67
+ const opensLine = (source: string, clause: string): boolean => new RegExp(String.raw`(?:^|\n)[ \t]*${escapeRegExp(clause)}[ \t]`, "u").test(source);
68
+
69
+ /**
70
+ * A clause number that opens a numbered paragraph (2.1 the term starts on signing.), not a figure: it opens a line, and a
71
+ * clause next to it in the same numbering (2.0 or 2.2) opens a line too. A figure at a line's start (2.5 million units
72
+ * were sold.) has no such neighbour.
73
+ */
74
+ export const opensNumberedParagraph = (source: string, offset: number, numeral: string): boolean => {
75
+ const clause = CLAUSE_NUMBER.exec(numeral)?.groups;
76
+ if (clause === undefined || !startsLine(source, offset)) return false;
77
+ const child = clause["child"] ?? "";
78
+ // 3.10 sits next to 3.9 or, numbered with a zero, 3.09.
79
+ const neighbours = [Number(child) - 1, Number(child) + 1]
80
+ .filter((neighbour) => neighbour >= 0)
81
+ .flatMap((neighbour) => [String(neighbour), String(neighbour).padStart(child.length, "0")]);
82
+ return neighbours.some((neighbour) => opensLine(source, `${clause["parent"] ?? ""}.${neighbour}`));
83
+ };
84
+
59
85
  const inside = (spans: readonly Span[], offset: number): boolean => spans.some((span) => span.start <= offset && offset < span.end);
60
86
 
61
87
  const leadingSpace = (sentence: Sentence): number => sentence.text.length - sentence.text.trimStart().length;
@@ -66,7 +92,7 @@ export const numeralOpener: Detector = (doc): Finding[] => {
66
92
  const offset = sentence.span.start + leadingSpace(sentence);
67
93
  if (sentence.embeddedLanguage !== undefined || inside(doc.listSpans, offset)) return [];
68
94
  const numeral = openingNumeral(sentence.text);
69
- if (numeral === undefined || !followsSentenceEnd(doc.source, offset, labels)) return [];
95
+ if (numeral === undefined || !followsSentenceEnd(doc.source, offset, labels) || opensNumberedParagraph(doc.source, offset, numeral)) return [];
70
96
  return [{ rule: "", severity: "warning", line: 0, column: 0, quote: sentence.text.trim(), values: { numeral, offset } }];
71
97
  });
72
98
  };
@@ -6,14 +6,19 @@
6
6
  // duty-marker (shall, will), duty-ending (う, するものとする: what may follow a Japanese act at the sentence end),
7
7
  // stated-deadline (by the, までに) with concrete-deadline and vague-deadline, deadline-permission and currency-notation. Pure.
8
8
  import { quoteAt } from "./structure-tree.ts";
9
+ import { actsTimedAt, type NamedDeadline, type Units } from "./deadline-unit.ts";
10
+ import { inDocumentOrder } from "../structure/issues.ts";
9
11
  import { escapeRegExp } from "../orthography.ts";
10
12
  import { grantsRight, statesConcrete, vagueIn } from "./vague-deadline.ts";
11
13
  import type { Detector, Finding, LexiconEntry, ProseDocument } from "../plugin.ts";
12
14
 
13
15
  export type UntimedDuty = { readonly offset: number; readonly act: string };
14
16
 
15
- /** sameLine: the sentence goes on the line of the one before it (no line break between them). */
16
- type Text = { readonly start: number; readonly text: string; readonly sameLine?: boolean };
17
+ /**
18
+ * sameLine: the sentence goes on the line of the one before it (no line break between them). timedActs: the acts whose
19
+ * dated deadline (支払期日 for 支払) its article, else its list or paragraph, names; a duty to do one of them is timed.
20
+ */
21
+ type Text = { readonly start: number; readonly text: string; readonly sameLine?: boolean; readonly timedActs?: readonly string[] };
17
22
 
18
23
  /** What the word lists give the decision. */
19
24
  export type DutyWords = {
@@ -155,6 +160,7 @@ export const untimedDuties = (texts: readonly Text[], words: DutyWords): Untimed
155
160
  if (grantsRight(text, words.permissions) || statesAmount(text.text, words.currencies)) return [];
156
161
  const act = actIn(text.text, words);
157
162
  if (act === undefined || (LATIN.test(act.act) && !isWordStart(text.text, act.at))) return [];
163
+ if (text.timedActs?.some((timed) => timed.toLowerCase() === act.act.toLowerCase()) === true) return [];
158
164
  return [{ offset: text.start + act.at, act: text.text.slice(act.at, act.at + act.act.length) }];
159
165
  });
160
166
  const limitedSentences = texts.filter((text) => statesLimit(text, words)).length;
@@ -163,12 +169,30 @@ export const untimedDuties = (texts: readonly Text[], words: DutyWords): Untimed
163
169
 
164
170
  const patternsOf = (doc: ProseDocument, lexicon: string): string[] => (doc.lexicons[lexicon] ?? []).map((entry) => entry.pattern);
165
171
 
166
- export const obligationWithoutDeadline: Detector = (doc): Finding[] =>
167
- untimedDuties(
172
+ const unitsOf = (doc: ProseDocument): Units => ({
173
+ articles: doc.structure === undefined ? [] : inDocumentOrder(doc.structure).flatMap((node) => (node.kind === "article" ? [node.span] : [])),
174
+ lists: doc.lists.map((list) => list.span),
175
+ paragraphs: doc.paragraphs.map((paragraph) => paragraph.span),
176
+ });
177
+
178
+ /** The acts a sentence's unit names a dated deadline for (named-deadline: the name, and the act as its group). */
179
+ const timedActsReader = (doc: ProseDocument): ((offset: number) => string[]) => {
180
+ const deadlines: NamedDeadline[] = (doc.lexicons["named-deadline"] ?? []).flatMap((entry) =>
181
+ entry.group === undefined ? [] : [{ name: entry.pattern, act: entry.group }],
182
+ );
183
+ if (deadlines.length === 0) return () => [];
184
+ const units = unitsOf(doc);
185
+ return (offset) => actsTimedAt(doc.source, units, offset, deadlines, phraseIn);
186
+ };
187
+
188
+ export const obligationWithoutDeadline: Detector = (doc): Finding[] => {
189
+ const timedActs = timedActsReader(doc);
190
+ return untimedDuties(
168
191
  doc.sentences.map((sentence, index) => ({
169
192
  start: sentence.span.start,
170
193
  text: sentence.text,
171
194
  sameLine: index > 0 && !doc.source.slice(doc.sentences[index - 1]?.span.end ?? 0, sentence.span.start).includes("\n"),
195
+ timedActs: timedActs(sentence.span.start),
172
196
  })),
173
197
  {
174
198
  acts: patternsOf(doc, "timed-duty"),
@@ -190,3 +214,4 @@ export const obligationWithoutDeadline: Detector = (doc): Finding[] =>
190
214
  quote: quoteAt(doc.source, duty.offset),
191
215
  values: { word: duty.act, offset: duty.offset },
192
216
  }));
217
+ };
@@ -0,0 +1,78 @@
1
+ import type { LexiconEntry, Token } from "../plugin.ts";
2
+ import type { TokenRange } from "./lexicon-match.ts";
3
+
4
+ // An open-end word on a list of acts that gives examples of a kind of act named right after it: 印刷等、二次的に利用される
5
+ // 場合 and 印刷等の二次的利用 say "secondary use, such as printing". The kind bounds the list, so it leaves nothing open.
6
+ // A list followed by the manner of the act the requirement asks for (ログ等、具体的に保存すること) names no kind and stays
7
+ // open. The link (、 の) and the suffix that makes a word name a kind (的) come from the language's requirement-open-end
8
+ // lexicon. Pure.
9
+
10
+ /** The lexicon groups: a link right after the open word, and a suffix that makes the noun after it name a kind. */
11
+ const LINK = "class-link";
12
+ const CLASS_SUFFIX = "class-suffix";
13
+ const NAME_POS: ReadonlySet<string> = new Set(["NOUN", "PROPN"]);
14
+ /** The tagger's mark of a noun that names an act (印刷, 利用: a verbal noun). */
15
+ const ACT_NOUN = "Vnoun";
16
+ const PUNCT = "PUNCT";
17
+ /** The parts of speech a phrase about the act ends in before its comma (場合、 には、). */
18
+ const PHRASE_END_POS: ReadonlySet<string> = new Set(["NOUN", "PROPN", "ADP"]);
19
+ /** How many tokens may stand between the suffix and the act it qualifies (二次的に利用: に). */
20
+ const SUFFIX_REACH = 2;
21
+
22
+ export type ClassWords = { readonly links: ReadonlySet<string>; readonly suffixes: ReadonlySet<string> };
23
+
24
+ const patternsIn = (entries: readonly LexiconEntry[], group: string): ReadonlySet<string> =>
25
+ new Set(entries.filter((entry) => entry.group === group).map((entry) => entry.pattern));
26
+
27
+ export const classWordsOf = (entries: readonly LexiconEntry[]): ClassWords => ({
28
+ links: patternsIn(entries, LINK),
29
+ suffixes: patternsIn(entries, CLASS_SUFFIX),
30
+ });
31
+
32
+ /** Whether an entry is one of the class cues rather than an open-end word. */
33
+ export const isClassCue = (entry: LexiconEntry): boolean => entry.group === LINK || entry.group === CLASS_SUFFIX;
34
+
35
+ const isActNoun = (token: Token | undefined): boolean => token !== undefined && token.features?.VerbForm === ACT_NOUN;
36
+
37
+ const attached = (left: Token | undefined, right: Token | undefined): boolean =>
38
+ left !== undefined && right !== undefined && left.span.end === right.span.start;
39
+
40
+ /** The index of the last noun written one after another from index on, or index - 1 when no noun stands there. */
41
+ const nounRunEnd = (tokens: readonly Token[], index: number): number => {
42
+ const token = tokens[index];
43
+ if (token === undefined || !NAME_POS.has(token.pos)) return index - 1;
44
+ return attached(token, tokens[index + 1]) ? nounRunEnd(tokens, index + 1) : index;
45
+ };
46
+
47
+ /**
48
+ * Whether the act at index heads a phrase the requirement is about (利用される場合、 二次的利用には、): its clause ends in
49
+ * a noun or a particle before a comma inside the sentence. An act whose clause runs to the end of the sentence, or ends
50
+ * in a verb (保存すること。 保存し、), is what the requirement asks for, and the list before it stays open.
51
+ */
52
+ const headsPhrase = (tokens: readonly Token[], index: number): boolean => {
53
+ const comma = tokens.findIndex((token, at) => at > index && token.pos === PUNCT);
54
+ const last = tokens[comma - 1];
55
+ const goesOn = tokens.slice(comma + 1).some((token) => token.pos !== PUNCT && token.surface.trim() !== "");
56
+ return comma !== -1 && goesOn && last !== undefined && PHRASE_END_POS.has(last.pos);
57
+ };
58
+
59
+ /** Whether the noun run that starts at index holds a class suffix (二次的, 営利的) and an act that heads a phrase follows within reach. */
60
+ const kindOfActAt = (tokens: readonly Token[], index: number, words: ClassWords): boolean => {
61
+ const end = nounRunEnd(tokens, index);
62
+ const suffix = tokens.slice(index + 1, end + 1).findIndex((token) => words.suffixes.has(token.surface));
63
+ if (suffix === -1) return false;
64
+ const afterSuffix = index + 1 + suffix + 1;
65
+ const act = tokens.slice(afterSuffix, afterSuffix + SUFFIX_REACH).findIndex((token) => isActNoun(token));
66
+ return act !== -1 && headsPhrase(tokens, afterSuffix + act);
67
+ };
68
+
69
+ /**
70
+ * Whether the open word ends a list of acts that gives examples of a kind of act named right after it: the item before
71
+ * the word is an act (印刷), a link follows (、 の), then a noun built with a class suffix (二次的) and an act (利用).
72
+ * range: the tokens the open word covers.
73
+ */
74
+ export const namesClassAfter = (tokens: readonly Token[], range: TokenRange, words: ClassWords): boolean => {
75
+ const link = tokens[range.end];
76
+ if (!isActNoun(tokens[range.start - 1]) || link === undefined || !words.links.has(link.surface)) return false;
77
+ return kindOfActAt(tokens, range.end + 1, words);
78
+ };
@@ -0,0 +1,40 @@
1
+ import type { Sentence } from "../plugin.ts";
2
+
3
+ // An open list inside brackets that gloss a condition (必要ない場合(単一テーブルへの SELECT 等)も付与すること): the list
4
+ // gives examples of when the duty applies, not what the duty covers, so leaving it open is no smell. The condition words
5
+ // come from the language's requirement-open-condition lexicon. Pure.
6
+
7
+ /** Each closing bracket and the opening bracket it closes. */
8
+ const PAIRS: ReadonlyMap<string, string> = new Map([
9
+ [")", "("],
10
+ [")", "("],
11
+ ]);
12
+ const OPENING: ReadonlySet<string> = new Set(PAIRS.values());
13
+
14
+ type Open = { readonly char: string; readonly index: number };
15
+
16
+ /**
17
+ * The brackets still open before `end` of `text`, outermost first. A closing bracket closes only its own kind, so a
18
+ * stray half-width ")" leaves a full-width "(" open. One pass, no recursion: a statute's sentence is long.
19
+ */
20
+ const openBracketsBefore = (text: string, end: number): Open[] => {
21
+ const open: Open[] = [];
22
+ text
23
+ .slice(0, Math.max(0, end))
24
+ .split("")
25
+ .forEach((char, index) => {
26
+ if (OPENING.has(char)) open.push({ char, index });
27
+ else if (PAIRS.has(char) && open.at(-1)?.char === PAIRS.get(char)) open.pop();
28
+ });
29
+ return open;
30
+ };
31
+
32
+ /**
33
+ * Whether the open word starting at document offset `start` stands inside brackets, at any depth, that open right after
34
+ * a condition word (場合(…等), とき(…など), 場合(A(B 等))).
35
+ */
36
+ export const glossesCondition = (sentence: Sentence, start: number, conditions: readonly string[]): boolean =>
37
+ openBracketsBefore(sentence.text, start - sentence.span.start).some((bracket) => {
38
+ const before = sentence.text.slice(0, bracket.index).trimEnd();
39
+ return conditions.some((word) => word !== "" && before.endsWith(word));
40
+ });
@@ -135,6 +135,12 @@ const isNamed = (source: string, occurrence: Occurrence, names: OpenEndNames): b
135
135
  inQuotedName(source, occurrence, names.quoted) ||
136
136
  partsHoldingWord(source, occurrence).some((part) => names.defined.has(part) || names.heads.some((head) => head.startsWith(part)));
137
137
 
138
- /** Where the word first stands outside a name the document gives (its document offset), or undefined when it never does. */
139
- export const openEndAt = (doc: ProseDocument, sentence: Sentence, word: LexiconEntry, names: OpenEndNames): number | undefined =>
140
- occurrencesIn(sentence, word).find((occurrence) => !isNamed(doc.source, occurrence, names))?.start;
138
+ /** Whether the words around an occurrence bound its list; range: the tokens the word covers. */
139
+ export type BoundedAfter = (tokens: readonly Token[], range: TokenRange) => boolean;
140
+
141
+ /**
142
+ * Where the word first stands outside a name the document gives and is not bounded by the words after it (its document
143
+ * offset), or undefined when it never does.
144
+ */
145
+ export const openEndAt = (doc: ProseDocument, sentence: Sentence, word: LexiconEntry, names: OpenEndNames, boundedAfter: BoundedAfter): number | undefined =>
146
+ occurrencesIn(sentence, word).find((occurrence) => !isNamed(doc.source, occurrence, names) && !boundedAfter(occurrence.tokens, occurrence.range))?.start;
@@ -0,0 +1,50 @@
1
+ import type { LexiconEntry, Sentence } from "../plugin.ts";
2
+
3
+ // An open list right before a question that asks the reader to choose (…措置など、どのような措置を求めるのかを記載する):
4
+ // the list gives example answers, and leaving it open is the point. The question's words come from the language's
5
+ // requirement-open-question lexicon. Pure.
6
+
7
+ const INTERROGATIVE = "interrogative";
8
+ const QUESTION_END = "question-end";
9
+ /** Words that start like an interrogative and ask how instead (どのように), which offers no list of answers. */
10
+ const NOT_INTERROGATIVE = "not-interrogative";
11
+ /** A particle that turns the interrogative into "any" (どの部署でも, 何をも): no question follows. */
12
+ const UNIVERSAL_MARK = "universal-mark";
13
+ /** The marks that may stand between the list's last word and the question (、). */
14
+ const LINK = /^[\s、,,]*/u;
15
+ const PARTICLE = "PART";
16
+
17
+ export type QuestionWords = {
18
+ readonly interrogatives: readonly string[];
19
+ readonly notInterrogatives: readonly string[];
20
+ readonly ends: ReadonlySet<string>;
21
+ readonly universalMarks: ReadonlySet<string>;
22
+ };
23
+
24
+ const patternsIn = (entries: readonly LexiconEntry[], group: string): string[] =>
25
+ entries.filter((entry) => entry.group === group && entry.pattern !== "").map((entry) => entry.pattern);
26
+
27
+ export const questionWordsOf = (entries: readonly LexiconEntry[]): QuestionWords => ({
28
+ interrogatives: patternsIn(entries, INTERROGATIVE),
29
+ notInterrogatives: patternsIn(entries, NOT_INTERROGATIVE),
30
+ ends: new Set(patternsIn(entries, QUESTION_END)),
31
+ universalMarks: new Set(patternsIn(entries, UNIVERSAL_MARK)),
32
+ });
33
+
34
+ /**
35
+ * Whether a question starts right after the open word that ends at document offset end, and closes later in the
36
+ * sentence with a question particle, before any particle that makes the interrogative mean "any" (どの部署でも): the list
37
+ * before the word gives examples of what the question asks for.
38
+ */
39
+ export const asksAfter = (sentence: Sentence, end: number, words: QuestionWords): boolean => {
40
+ const rest = sentence.text.slice(end - sentence.span.start);
41
+ const link = LINK.exec(rest)?.[0].length ?? 0;
42
+ const question = rest.slice(link);
43
+ const startsWithAny = (patterns: readonly string[]): boolean => patterns.some((word) => question.startsWith(word));
44
+ const asks = startsWithAny(words.interrogatives) && !startsWithAny(words.notInterrogatives);
45
+ const questionStart = end + link;
46
+ const after = (sentence.tokens ?? []).filter((token) => token.span.start >= questionStart);
47
+ const close = after.findIndex((token) => token.pos === PARTICLE && words.ends.has(token.surface));
48
+ const universal = after.slice(0, close).some((token) => words.universalMarks.has(token.surface));
49
+ return asks && close !== -1 && !universal;
50
+ };
@@ -0,0 +1,62 @@
1
+ import type { LexiconEntry, Token } from "../plugin.ts";
2
+ import { entryEndsAt, type TokenRange } from "./lexicon-match.ts";
3
+
4
+ // An open-end word in a requirement is a smell only when its list is part of what the reader is asked to do (ログ等を保存
5
+ // すること): the reader cannot tell when the duty is met. A list in the condition the duty depends on (訂正等を行った際に、
6
+ // 通知する), in a right the writer grants (投稿データ等を消去できるものとします), or in what is forbidden or excluded
7
+ // (複製、転載等をしてはならない, 事情などにより責任を負わないものとします) is left open on purpose, and is the usual way a
8
+ // contract is drafted. The words that tell the four apart come from the language's requirement-open-end lexicon, in the
9
+ // groups below. Pure.
10
+
11
+ /** The lexicon groups, in the order a tie at one token is decided: a duty word wins over the words that open a list on purpose. */
12
+ const DUTY = "scope-duty";
13
+ const ON_PURPOSE = ["scope-permission", "scope-prohibition"] as const;
14
+ const CONDITION = "scope-condition";
15
+ const SCOPE_GROUPS: ReadonlySet<string> = new Set([DUTY, CONDITION, ...ON_PURPOSE]);
16
+
17
+ /** The parts of speech that end a clause with its own predicate before a comma (消去し、 行い、). */
18
+ const PREDICATE_POS: ReadonlySet<string> = new Set(["VERB", "AUX", "ADJ"]);
19
+ const PUNCT = "PUNCT";
20
+
21
+ export type ScopeWords = { readonly duty: readonly LexiconEntry[]; readonly onPurpose: readonly LexiconEntry[]; readonly condition: readonly LexiconEntry[] };
22
+
23
+ const inGroup = (entries: readonly LexiconEntry[], groups: readonly string[]): LexiconEntry[] => entries.filter((entry) => groups.includes(entry.group ?? ""));
24
+
25
+ export const scopeWordsOf = (entries: readonly LexiconEntry[]): ScopeWords => ({
26
+ duty: inGroup(entries, [DUTY]),
27
+ onPurpose: inGroup(entries, ON_PURPOSE),
28
+ condition: inGroup(entries, [CONDITION]),
29
+ });
30
+
31
+ /** Whether an entry is one of the scope cues rather than an open-end word. */
32
+ export const isScopeCue = (entry: LexiconEntry): boolean => SCOPE_GROUPS.has(entry.group ?? "");
33
+
34
+ const startsAt = (tokens: readonly Token[], entries: readonly LexiconEntry[], index: number): boolean =>
35
+ entries.some((entry) => entryEndsAt(tokens, entry, index + (entry.tokens?.length ?? 0)));
36
+
37
+ /** A comma right after a predicate closes the clause the list stands in (消去し、). */
38
+ const closesClause = (tokens: readonly Token[], index: number): boolean => tokens[index]?.pos === PUNCT && PREDICATE_POS.has(tokens[index - 1]?.pos ?? "");
39
+
40
+ /** A condition word that closes a clause about an act (行った際, 判断される場合): it follows a predicate. */
41
+ const conditionAt = (tokens: readonly Token[], index: number, words: ScopeWords): boolean =>
42
+ PREDICATE_POS.has(tokens[index - 1]?.pos ?? "") && startsAt(tokens, words.condition, index);
43
+
44
+ type Scope = "duty" | "on-purpose" | undefined;
45
+
46
+ /**
47
+ * What the list belongs to: the first duty, permission or prohibition word after it, or a condition word before the
48
+ * clause the list stands in closes. undefined when none follows (the sentence's own ending decides: こと。).
49
+ */
50
+ const scopeFrom = (tokens: readonly Token[], index: number, words: ScopeWords, inClause: boolean): Scope => {
51
+ if (index >= tokens.length) return undefined;
52
+ if (startsAt(tokens, words.duty, index)) return "duty";
53
+ if (startsAt(tokens, words.onPurpose, index) || (inClause && conditionAt(tokens, index, words))) return "on-purpose";
54
+ return scopeFrom(tokens, index + 1, words, inClause && !closesClause(tokens, index));
55
+ };
56
+
57
+ /**
58
+ * Whether the open word at range ends a list that is open on purpose: a condition, a permission or a prohibition holds
59
+ * it, not the duty the requirement states. range: the tokens the open word covers.
60
+ */
61
+ export const openOnPurpose = (tokens: readonly Token[], range: TokenRange, words: ScopeWords): boolean =>
62
+ scopeFrom(tokens, range.end, words, true) === "on-purpose";
@@ -0,0 +1,47 @@
1
+ // The date in a document's opening lines: a line that is only a date, or a labelled one, under the title ("January 17,
2
+ // 2024" below a centred heading in plain-text minutes, where the indented title block is no paragraph). Pure.
3
+ import type { Span } from "../plugin.ts";
4
+ import { dateStampIndexes, stampCandidates } from "../date-stamp.ts";
5
+
6
+ /** How many non-blank lines after the front matter make the title block. */
7
+ const OPENING_LINES = 5;
8
+ /** How many lines from the top are read at most, blank ones and front matter included. */
9
+ const LINE_REACH = 64;
10
+ const FRONT_MATTER_FENCE = "---";
11
+
12
+ export type DatedSpan = Span & { readonly value: string };
13
+
14
+ type Line = Span & { readonly text: string };
15
+
16
+ const firstLines = (source: string): Line[] =>
17
+ source.split("\n", LINE_REACH).reduce<Line[]>((lines, text) => {
18
+ const start = (lines.at(-1)?.end ?? -1) + 1;
19
+ lines.push({ start, end: start + text.length, text });
20
+ return lines;
21
+ }, []);
22
+
23
+ /** The lines after a Markdown front matter block ("---" … "---" at the very top), or all of them. */
24
+ const afterFrontMatter = (lines: readonly Line[]): readonly Line[] => {
25
+ if (lines[0]?.text.trim() !== FRONT_MATTER_FENCE) return lines;
26
+ const close = lines.findIndex((line, index) => index > 0 && line.text.trim() === FRONT_MATTER_FENCE);
27
+ return close === -1 ? lines : lines.slice(close + 1);
28
+ };
29
+
30
+ const openingLines = (source: string): Line[] =>
31
+ afterFrontMatter(firstLines(source))
32
+ .filter((line) => line.text.trim() !== "")
33
+ .slice(0, OPENING_LINES);
34
+
35
+ const firstDateIn = (dates: readonly DatedSpan[], line: Span | undefined): string | undefined =>
36
+ line === undefined ? undefined : dates.find((date) => date.start >= line.start && date.end <= line.end)?.value;
37
+
38
+ /**
39
+ * The first date of the first opening line that is a date stamp, as the tree reads it ("2024-01-17"). A label on a line of
40
+ * its own ("Date:") takes the date on the line after it.
41
+ */
42
+ export const openingDateOf = (source: string, dates: readonly DatedSpan[], labels: readonly string[]): string | undefined => {
43
+ const lines = openingLines(source);
44
+ const stamps = dateStampIndexes(stampCandidates(source, lines, dates), labels);
45
+ const at = lines.findIndex((_, index) => stamps.has(index));
46
+ return at === -1 ? undefined : (firstDateIn(dates, lines[at]) ?? firstDateIn(dates, lines[at + 1]));
47
+ };
@@ -0,0 +1,62 @@
1
+ // Where a lower-case word that a defined term shares is the ordinary word in a phrase of its own, not the term, and where
2
+ // a quoted term is named rather than used. Pure: the tokens, the source and the lexicons' words are passed in.
3
+ import type { Token } from "../plugin.ts";
4
+
5
+ /** Words that narrow a noun into a kind of its own (a common parent, a limited liability company). */
6
+ const NARROWING = new Set(["ADJ", "NOUN"]);
7
+ const LOWER_START = /^\p{Ll}/u;
8
+ /** How far back from an opening quote the words that name it as a term are looked for. */
9
+ const CUE_LOOK_BACK = 40;
10
+ const LETTER = /\p{L}/u;
11
+ const LATIN = /\p{Script=Latin}/u;
12
+
13
+ /**
14
+ * An adjective or a noun of the same Latin-script phrase right before the use narrows it into a phrase of its own (the
15
+ * common parent, a limited liability company), unless it is a word that points at the defined thing (the other party).
16
+ */
17
+ export const isNarrowedUse = (tokens: readonly Token[], at: number, pointers: readonly string[]): boolean => {
18
+ const before = tokens[at - 1];
19
+ if (before === undefined || !NARROWING.has(before.pos) || !LATIN.test(before.surface)) return false;
20
+ return !pointers.some((pointer) => pointer.toLowerCase() === before.surface.toLowerCase());
21
+ };
22
+
23
+ /**
24
+ * The use heads a phrase that names a kind of thing with "of" and a bare noun (certificates of deposit, letters of credit);
25
+ * "of" followed by an article, a pronoun or a name (the certificate of the Company) is the defined thing again.
26
+ * last is the index of the use's last token.
27
+ */
28
+ export const headsKindPhrase = (tokens: readonly Token[], last: number): boolean => {
29
+ const of = tokens[last + 1];
30
+ const noun = tokens[last + 2];
31
+ return of !== undefined && noun !== undefined && of.surface.toLowerCase() === "of" && noun.pos === "NOUN" && LOWER_START.test(noun.surface);
32
+ };
33
+
34
+ /** Every position of part inside whole, overlapping ones too. */
35
+ const positionsOf = (whole: string, part: string): number[] =>
36
+ Array.from({ length: whole.length }, (_, index) => index).filter((index) => part !== "" && whole.startsWith(part, index));
37
+
38
+ /** The use sits inside another defined term written out in any case (certificate of merger, where Certificate of Merger is defined). */
39
+ export const insideLongerTerm = (source: string, offset: number, term: string, defined: readonly string[]): boolean =>
40
+ defined.some((longer) => {
41
+ if (longer.length <= term.length) return false;
42
+ const lowerLonger = longer.toLowerCase();
43
+ return positionsOf(lowerLonger, term.toLowerCase()).some((at) => {
44
+ const start = offset - at;
45
+ return start >= 0 && source.slice(start, start + longer.length).toLowerCase() === lowerLonger;
46
+ });
47
+ });
48
+
49
+ /**
50
+ * A quoted term that the words before its opening quote name as a term (the definition of "Material Adverse Effect",
51
+ * the term "Affiliate"): the term is mentioned, not used again. opening is the offset of the opening quote.
52
+ */
53
+ export const isNamedAsTerm = (source: string, opening: number, cues: readonly string[]): boolean => {
54
+ const before = source
55
+ .slice(Math.max(0, opening - CUE_LOOK_BACK), opening)
56
+ .trimEnd()
57
+ .toLowerCase();
58
+ return cues.some((cue) => {
59
+ const lower = cue.toLowerCase();
60
+ return lower !== "" && before.endsWith(lower) && !LETTER.test(before.charAt(before.length - lower.length - 1));
61
+ });
62
+ };
@@ -1,7 +1,10 @@
1
1
  import type { Token } from "../plugin.ts";
2
2
 
3
- /** 組織の名前の前に立つ形(語彙表 organisation-form)と、組織や単位の名前を閉じる語(organisation-unit)。 */
4
- export type OrganisationWords = { readonly forms: readonly string[]; readonly units: readonly string[] };
3
+ /**
4
+ * 組織の名前の前に立つ形(語彙表 organisation-form)、そのうち名前の後ろにも立つ法人格(closingForms。株式会社、監査法人)と、
5
+ * 組織や単位の名前を閉じる語(organisation-unit)。
6
+ */
7
+ export type OrganisationWords = { readonly forms: readonly string[]; readonly closingForms: readonly string[]; readonly units: readonly string[] };
5
8
 
6
9
  /** 区切りの前に要る字数。1 字なら、普通の語の一部(外部・本部・課題)と見分けられない。 */
7
10
  const MIN_BODY_LENGTH = 2;
@@ -70,6 +73,13 @@ const segmentCount = (run: readonly Token[], units: readonly string[]): number =
70
73
  return counts[0] ?? 0;
71
74
  };
72
75
 
76
+ /**
77
+ * 法人格で閉じ、その前に 2 字以上の名前がある(東日本自動車販売株式会社、青葉監査法人)。数を含む連なり(2024年株式会社、第三東京販売株式会社)は名前と読まない。
78
+ * 呼び出し側は、名前に固有名詞か地名があることも求める。法令の語(株式交換完全親株式会社)は法人格で閉じても名前ではない。
79
+ */
80
+ const endsInForm = (text: string, closingForms: readonly string[]): boolean =>
81
+ !/[0-90-9]/u.test(text) && closingForms.some((form) => text.endsWith(form) && [...text].length - [...form].length >= MIN_BODY_LENGTH);
82
+
73
83
  /**
74
84
  * 漢字の連なりの語(covering: tokens の添字)が 1 つの組織の名前か。名前は書き手が変えられない。
75
85
  * 法人格か地名・固有名詞で始まって組織の語で閉じるもの(国立研究開発法人産業技術総合研究所、紀美野町青少年育成町民会議)、
@@ -79,6 +89,7 @@ const segmentCount = (run: readonly Token[], units: readonly string[]): number =
79
89
  export const isOrganisationRun = (tokens: readonly Token[], covering: readonly number[], words: OrganisationWords): boolean => {
80
90
  const run = contiguous(tokens, withClosingUnit(tokens, covering, words.units));
81
91
  if (run === undefined || run.length === 0) return false;
92
+ if (endsInForm(surfaceOf(run), words.closingForms) && run.some(isProperPiece) && !run.some(isNumber)) return true;
82
93
  const form = formLength(run, new Set(words.forms));
83
94
  const head = form > 0 ? form : properLength(run);
84
95
  const segments = segmentCount(run.slice(head), words.units);