chaffjs 0.14.0 → 0.16.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 (602) hide show
  1. package/README.md +27 -2
  2. package/dist/address-chain.d.ts +4 -1
  3. package/dist/address-chain.d.ts.map +1 -1
  4. package/dist/address-chain.js +6 -3
  5. package/dist/address-chain.js.map +1 -1
  6. package/dist/backends/anthropic.d.ts +2 -1
  7. package/dist/backends/anthropic.d.ts.map +1 -1
  8. package/dist/backends/anthropic.js +4 -1
  9. package/dist/backends/anthropic.js.map +1 -1
  10. package/dist/backends/openai.d.ts +2 -1
  11. package/dist/backends/openai.d.ts.map +1 -1
  12. package/dist/backends/openai.js +1 -1
  13. package/dist/backends/openai.js.map +1 -1
  14. package/dist/backends/types.d.ts +2 -1
  15. package/dist/backends/types.d.ts.map +1 -1
  16. package/dist/backends/types.js +1 -1
  17. package/dist/backends/types.js.map +1 -1
  18. package/dist/baseline.js +1 -1
  19. package/dist/baseline.js.map +1 -1
  20. package/dist/calendar-number.d.ts +26 -0
  21. package/dist/calendar-number.d.ts.map +1 -0
  22. package/dist/calendar-number.js +51 -0
  23. package/dist/calendar-number.js.map +1 -0
  24. package/dist/cli-args.d.ts +0 -5
  25. package/dist/cli-args.d.ts.map +1 -1
  26. package/dist/cli-args.js +2 -2
  27. package/dist/cli-args.js.map +1 -1
  28. package/dist/cli-text.d.ts +10 -3
  29. package/dist/cli-text.d.ts.map +1 -1
  30. package/dist/cli-text.js +33 -14
  31. package/dist/cli-text.js.map +1 -1
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +53 -47
  34. package/dist/cli.js.map +1 -1
  35. package/dist/commands/cite.d.ts.map +1 -1
  36. package/dist/commands/cite.js +4 -4
  37. package/dist/commands/cite.js.map +1 -1
  38. package/dist/commands/eval.d.ts +4 -0
  39. package/dist/commands/eval.d.ts.map +1 -1
  40. package/dist/commands/eval.js +21 -7
  41. package/dist/commands/eval.js.map +1 -1
  42. package/dist/commands/feedback.d.ts.map +1 -1
  43. package/dist/commands/feedback.js +2 -1
  44. package/dist/commands/feedback.js.map +1 -1
  45. package/dist/commands/init-ask.d.ts +19 -0
  46. package/dist/commands/init-ask.d.ts.map +1 -0
  47. package/dist/commands/init-ask.js +43 -0
  48. package/dist/commands/init-ask.js.map +1 -0
  49. package/dist/commands/test.d.ts +8 -0
  50. package/dist/commands/test.d.ts.map +1 -1
  51. package/dist/commands/test.js +73 -40
  52. package/dist/commands/test.js.map +1 -1
  53. package/dist/commands/tree.d.ts +1 -8
  54. package/dist/commands/tree.d.ts.map +1 -1
  55. package/dist/commands/tree.js +9 -7
  56. package/dist/commands/tree.js.map +1 -1
  57. package/dist/config/by-path.d.ts.map +1 -1
  58. package/dist/config/by-path.js +10 -9
  59. package/dist/config/by-path.js.map +1 -1
  60. package/dist/config/load.d.ts +4 -0
  61. package/dist/config/load.d.ts.map +1 -1
  62. package/dist/config/load.js +19 -0
  63. package/dist/config/load.js.map +1 -1
  64. package/dist/config/name-problems.d.ts +5 -0
  65. package/dist/config/name-problems.d.ts.map +1 -0
  66. package/dist/config/name-problems.js +11 -0
  67. package/dist/config/name-problems.js.map +1 -0
  68. package/dist/date-stamp.js +1 -1
  69. package/dist/date-stamp.js.map +1 -1
  70. package/dist/detectors/acronym-compound.d.ts +8 -0
  71. package/dist/detectors/acronym-compound.d.ts.map +1 -0
  72. package/dist/detectors/acronym-compound.js +8 -0
  73. package/dist/detectors/acronym-compound.js.map +1 -0
  74. package/dist/detectors/acronym-context.d.ts +4 -0
  75. package/dist/detectors/acronym-context.d.ts.map +1 -1
  76. package/dist/detectors/acronym-context.js +17 -1
  77. package/dist/detectors/acronym-context.js.map +1 -1
  78. package/dist/detectors/acronym-expansion.d.ts +10 -0
  79. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  80. package/dist/detectors/acronym-expansion.js +63 -5
  81. package/dist/detectors/acronym-expansion.js.map +1 -1
  82. package/dist/detectors/char-shape.d.ts.map +1 -1
  83. package/dist/detectors/char-shape.js +47 -15
  84. package/dist/detectors/char-shape.js.map +1 -1
  85. package/dist/detectors/citation-key.d.ts +13 -0
  86. package/dist/detectors/citation-key.d.ts.map +1 -0
  87. package/dist/detectors/citation-key.js +9 -0
  88. package/dist/detectors/citation-key.js.map +1 -0
  89. package/dist/detectors/cited-name.d.ts +11 -0
  90. package/dist/detectors/cited-name.d.ts.map +1 -0
  91. package/dist/detectors/cited-name.js +15 -0
  92. package/dist/detectors/cited-name.js.map +1 -0
  93. package/dist/detectors/concrete-evidence.d.ts +7 -0
  94. package/dist/detectors/concrete-evidence.d.ts.map +1 -0
  95. package/dist/detectors/concrete-evidence.js +21 -0
  96. package/dist/detectors/concrete-evidence.js.map +1 -0
  97. package/dist/detectors/counter-edge.d.ts +8 -0
  98. package/dist/detectors/counter-edge.d.ts.map +1 -0
  99. package/dist/detectors/counter-edge.js +27 -0
  100. package/dist/detectors/counter-edge.js.map +1 -0
  101. package/dist/detectors/dotted-name.d.ts +13 -0
  102. package/dist/detectors/dotted-name.d.ts.map +1 -0
  103. package/dist/detectors/dotted-name.js +10 -0
  104. package/dist/detectors/dotted-name.js.map +1 -0
  105. package/dist/detectors/doubled-word.d.ts +6 -1
  106. package/dist/detectors/doubled-word.d.ts.map +1 -1
  107. package/dist/detectors/doubled-word.js +22 -2
  108. package/dist/detectors/doubled-word.js.map +1 -1
  109. package/dist/detectors/en-shape.d.ts +1 -1
  110. package/dist/detectors/en-shape.d.ts.map +1 -1
  111. package/dist/detectors/en-shape.js +112 -29
  112. package/dist/detectors/en-shape.js.map +1 -1
  113. package/dist/detectors/enumerated-runs.d.ts.map +1 -1
  114. package/dist/detectors/enumerated-runs.js +4 -2
  115. package/dist/detectors/enumerated-runs.js.map +1 -1
  116. package/dist/detectors/gram-place.d.ts +0 -4
  117. package/dist/detectors/gram-place.d.ts.map +1 -1
  118. package/dist/detectors/gram-place.js +7 -2
  119. package/dist/detectors/gram-place.js.map +1 -1
  120. package/dist/detectors/gram-predicate.d.ts +7 -0
  121. package/dist/detectors/gram-predicate.d.ts.map +1 -0
  122. package/dist/detectors/gram-predicate.js +44 -0
  123. package/dist/detectors/gram-predicate.js.map +1 -0
  124. package/dist/detectors/heading-case.d.ts +21 -0
  125. package/dist/detectors/heading-case.d.ts.map +1 -0
  126. package/dist/detectors/heading-case.js +40 -0
  127. package/dist/detectors/heading-case.js.map +1 -0
  128. package/dist/detectors/heading-echo.d.ts.map +1 -1
  129. package/dist/detectors/heading-echo.js +4 -1
  130. package/dist/detectors/heading-echo.js.map +1 -1
  131. package/dist/detectors/index.d.ts.map +1 -1
  132. package/dist/detectors/index.js +4 -3
  133. package/dist/detectors/index.js.map +1 -1
  134. package/dist/detectors/lettered-index.d.ts +9 -0
  135. package/dist/detectors/lettered-index.d.ts.map +1 -0
  136. package/dist/detectors/lettered-index.js +54 -0
  137. package/dist/detectors/lettered-index.js.map +1 -0
  138. package/dist/detectors/lexicon.d.ts +5 -1
  139. package/dist/detectors/lexicon.d.ts.map +1 -1
  140. package/dist/detectors/lexicon.js +51 -5
  141. package/dist/detectors/lexicon.js.map +1 -1
  142. package/dist/detectors/name-numeral.d.ts +16 -0
  143. package/dist/detectors/name-numeral.d.ts.map +1 -0
  144. package/dist/detectors/name-numeral.js +30 -0
  145. package/dist/detectors/name-numeral.js.map +1 -0
  146. package/dist/detectors/name-part.d.ts +8 -0
  147. package/dist/detectors/name-part.d.ts.map +1 -0
  148. package/dist/detectors/name-part.js +20 -0
  149. package/dist/detectors/name-part.js.map +1 -0
  150. package/dist/detectors/name-run.d.ts +8 -0
  151. package/dist/detectors/name-run.d.ts.map +1 -0
  152. package/dist/detectors/name-run.js +13 -0
  153. package/dist/detectors/name-run.js.map +1 -0
  154. package/dist/detectors/orthography.d.ts.map +1 -1
  155. package/dist/detectors/orthography.js +35 -7
  156. package/dist/detectors/orthography.js.map +1 -1
  157. package/dist/detectors/phrase-match.d.ts +3 -1
  158. package/dist/detectors/phrase-match.d.ts.map +1 -1
  159. package/dist/detectors/phrase-match.js +20 -15
  160. package/dist/detectors/phrase-match.js.map +1 -1
  161. package/dist/detectors/placeholder.d.ts +14 -0
  162. package/dist/detectors/placeholder.d.ts.map +1 -0
  163. package/dist/detectors/placeholder.js +24 -0
  164. package/dist/detectors/placeholder.js.map +1 -0
  165. package/dist/detectors/quoted-variant.d.ts +7 -0
  166. package/dist/detectors/quoted-variant.d.ts.map +1 -0
  167. package/dist/detectors/quoted-variant.js +18 -0
  168. package/dist/detectors/quoted-variant.js.map +1 -0
  169. package/dist/detectors/repeated-head.d.ts.map +1 -1
  170. package/dist/detectors/repeated-head.js +5 -4
  171. package/dist/detectors/repeated-head.js.map +1 -1
  172. package/dist/detectors/series-label.d.ts +13 -0
  173. package/dist/detectors/series-label.d.ts.map +1 -0
  174. package/dist/detectors/series-label.js +26 -0
  175. package/dist/detectors/series-label.js.map +1 -0
  176. package/dist/detectors/signals.d.ts.map +1 -1
  177. package/dist/detectors/signals.js +44 -29
  178. package/dist/detectors/signals.js.map +1 -1
  179. package/dist/detectors/stacked-hedge.d.ts +23 -0
  180. package/dist/detectors/stacked-hedge.d.ts.map +1 -0
  181. package/dist/detectors/stacked-hedge.js +64 -0
  182. package/dist/detectors/stacked-hedge.js.map +1 -0
  183. package/dist/detectors/structure-tree.js +1 -1
  184. package/dist/detectors/structure-tree.js.map +1 -1
  185. package/dist/detectors/superlative-amount.d.ts +9 -0
  186. package/dist/detectors/superlative-amount.d.ts.map +1 -0
  187. package/dist/detectors/superlative-amount.js +20 -0
  188. package/dist/detectors/superlative-amount.js.map +1 -0
  189. package/dist/detectors/superlative-clause.d.ts +18 -0
  190. package/dist/detectors/superlative-clause.d.ts.map +1 -0
  191. package/dist/detectors/superlative-clause.js +61 -0
  192. package/dist/detectors/superlative-clause.js.map +1 -0
  193. package/dist/detectors/superlative-name.d.ts +9 -0
  194. package/dist/detectors/superlative-name.d.ts.map +1 -0
  195. package/dist/detectors/superlative-name.js +24 -0
  196. package/dist/detectors/superlative-name.js.map +1 -0
  197. package/dist/detectors/superlative-scope.d.ts +6 -0
  198. package/dist/detectors/superlative-scope.d.ts.map +1 -1
  199. package/dist/detectors/superlative-scope.js +3 -3
  200. package/dist/detectors/superlative-scope.js.map +1 -1
  201. package/dist/detectors/team.d.ts +0 -6
  202. package/dist/detectors/team.d.ts.map +1 -1
  203. package/dist/detectors/team.js +17 -1
  204. package/dist/detectors/team.js.map +1 -1
  205. package/dist/detectors/token-shape.d.ts.map +1 -1
  206. package/dist/detectors/token-shape.js +15 -5
  207. package/dist/detectors/token-shape.js.map +1 -1
  208. package/dist/document.d.ts +3 -0
  209. package/dist/document.d.ts.map +1 -1
  210. package/dist/document.js +28 -7
  211. package/dist/document.js.map +1 -1
  212. package/dist/eval.js +1 -1
  213. package/dist/eval.js.map +1 -1
  214. package/dist/feedback/draft.js +1 -1
  215. package/dist/feedback/draft.js.map +1 -1
  216. package/dist/feedback/excerpt.d.ts +0 -1
  217. package/dist/feedback/excerpt.d.ts.map +1 -1
  218. package/dist/feedback/excerpt.js +2 -2
  219. package/dist/feedback/excerpt.js.map +1 -1
  220. package/dist/file-header.d.ts +20 -0
  221. package/dist/file-header.d.ts.map +1 -0
  222. package/dist/file-header.js +21 -0
  223. package/dist/file-header.js.map +1 -0
  224. package/dist/files.js +3 -3
  225. package/dist/files.js.map +1 -1
  226. package/dist/genre-check.d.ts +34 -0
  227. package/dist/genre-check.d.ts.map +1 -0
  228. package/dist/genre-check.js +21 -0
  229. package/dist/genre-check.js.map +1 -0
  230. package/dist/genre-load.d.ts +6 -0
  231. package/dist/genre-load.d.ts.map +1 -0
  232. package/dist/genre-load.js +22 -0
  233. package/dist/genre-load.js.map +1 -0
  234. package/dist/genre-parse.d.ts +40 -0
  235. package/dist/genre-parse.d.ts.map +1 -0
  236. package/dist/genre-parse.js +104 -0
  237. package/dist/genre-parse.js.map +1 -0
  238. package/dist/genre-suggest.d.ts +15 -0
  239. package/dist/genre-suggest.d.ts.map +1 -0
  240. package/dist/genre-suggest.js +26 -0
  241. package/dist/genre-suggest.js.map +1 -0
  242. package/dist/genre.d.ts +8 -1
  243. package/dist/genre.d.ts.map +1 -1
  244. package/dist/genre.js +19 -5
  245. package/dist/genre.js.map +1 -1
  246. package/dist/init-choice.d.ts +12 -0
  247. package/dist/init-choice.d.ts.map +1 -0
  248. package/dist/init-choice.js +41 -0
  249. package/dist/init-choice.js.map +1 -0
  250. package/dist/init.d.ts +0 -1
  251. package/dist/init.d.ts.map +1 -1
  252. package/dist/init.js +16 -17
  253. package/dist/init.js.map +1 -1
  254. package/dist/joined-view.d.ts +25 -0
  255. package/dist/joined-view.d.ts.map +1 -0
  256. package/dist/joined-view.js +76 -0
  257. package/dist/joined-view.js.map +1 -0
  258. package/dist/judge.d.ts +2 -1
  259. package/dist/judge.d.ts.map +1 -1
  260. package/dist/judge.js +1 -1
  261. package/dist/judge.js.map +1 -1
  262. package/dist/line-continues.d.ts +6 -0
  263. package/dist/line-continues.d.ts.map +1 -0
  264. package/dist/line-continues.js +33 -0
  265. package/dist/line-continues.js.map +1 -0
  266. package/dist/line-paragraphs.d.ts +4 -0
  267. package/dist/line-paragraphs.d.ts.map +1 -0
  268. package/dist/line-paragraphs.js +69 -0
  269. package/dist/line-paragraphs.js.map +1 -0
  270. package/dist/mask.d.ts.map +1 -1
  271. package/dist/mask.js +7 -6
  272. package/dist/mask.js.map +1 -1
  273. package/dist/measure.d.ts +1 -0
  274. package/dist/measure.d.ts.map +1 -1
  275. package/dist/measure.js +5 -1
  276. package/dist/measure.js.map +1 -1
  277. package/dist/number-name.d.ts +6 -3
  278. package/dist/number-name.d.ts.map +1 -1
  279. package/dist/number-name.js +31 -14
  280. package/dist/number-name.js.map +1 -1
  281. package/dist/orthography.d.ts +4 -0
  282. package/dist/orthography.d.ts.map +1 -1
  283. package/dist/orthography.js +11 -9
  284. package/dist/orthography.js.map +1 -1
  285. package/dist/plain-source.d.ts +6 -0
  286. package/dist/plain-source.d.ts.map +1 -0
  287. package/dist/plain-source.js +6 -0
  288. package/dist/plain-source.js.map +1 -0
  289. package/dist/plugin.d.ts +22 -1
  290. package/dist/plugin.d.ts.map +1 -1
  291. package/dist/profile/check.d.ts +28 -0
  292. package/dist/profile/check.d.ts.map +1 -0
  293. package/dist/profile/check.js +12 -0
  294. package/dist/profile/check.js.map +1 -0
  295. package/dist/profile/for-file.d.ts +2 -2
  296. package/dist/profile/for-file.d.ts.map +1 -1
  297. package/dist/profile/for-file.js +9 -2
  298. package/dist/profile/for-file.js.map +1 -1
  299. package/dist/profile/load.js +1 -1
  300. package/dist/profile/load.js.map +1 -1
  301. package/dist/profile/select.d.ts +4 -2
  302. package/dist/profile/select.d.ts.map +1 -1
  303. package/dist/profile/select.js +8 -3
  304. package/dist/profile/select.js.map +1 -1
  305. package/dist/quoted-span.d.ts +22 -0
  306. package/dist/quoted-span.d.ts.map +1 -0
  307. package/dist/quoted-span.js +25 -0
  308. package/dist/quoted-span.js.map +1 -0
  309. package/dist/reasons.d.ts +1 -0
  310. package/dist/reasons.d.ts.map +1 -1
  311. package/dist/reasons.js +4 -1
  312. package/dist/reasons.js.map +1 -1
  313. package/dist/render/compact.d.ts.map +1 -1
  314. package/dist/render/compact.js +1 -2
  315. package/dist/render/compact.js.map +1 -1
  316. package/dist/render/eval.d.ts +2 -1
  317. package/dist/render/eval.d.ts.map +1 -1
  318. package/dist/render/eval.js +85 -43
  319. package/dist/render/eval.js.map +1 -1
  320. package/dist/render/friendly.d.ts +2 -1
  321. package/dist/render/friendly.d.ts.map +1 -1
  322. package/dist/render/friendly.js +14 -6
  323. package/dist/render/friendly.js.map +1 -1
  324. package/dist/render/genres.d.ts +5 -0
  325. package/dist/render/genres.d.ts.map +1 -0
  326. package/dist/render/genres.js +28 -0
  327. package/dist/render/genres.js.map +1 -0
  328. package/dist/render/plural.d.ts +3 -0
  329. package/dist/render/plural.d.ts.map +1 -0
  330. package/dist/render/plural.js +5 -0
  331. package/dist/render/plural.js.map +1 -0
  332. package/dist/render/rules-json.d.ts.map +1 -1
  333. package/dist/render/rules-json.js +9 -3
  334. package/dist/render/rules-json.js.map +1 -1
  335. package/dist/render/semantic.d.ts +3 -2
  336. package/dist/render/semantic.d.ts.map +1 -1
  337. package/dist/render/semantic.js +94 -34
  338. package/dist/render/semantic.js.map +1 -1
  339. package/dist/render/summary.d.ts.map +1 -1
  340. package/dist/render/summary.js +4 -3
  341. package/dist/render/summary.js.map +1 -1
  342. package/dist/render/suppressions.d.ts.map +1 -1
  343. package/dist/render/suppressions.js +3 -2
  344. package/dist/render/suppressions.js.map +1 -1
  345. package/dist/render/text.d.ts +2 -0
  346. package/dist/render/text.d.ts.map +1 -1
  347. package/dist/render/text.js +11 -3
  348. package/dist/render/text.js.map +1 -1
  349. package/dist/resolve-genre.d.ts +11 -0
  350. package/dist/resolve-genre.d.ts.map +1 -0
  351. package/dist/resolve-genre.js +18 -0
  352. package/dist/resolve-genre.js.map +1 -0
  353. package/dist/rule-load.d.ts.map +1 -1
  354. package/dist/rule-load.js +4 -1
  355. package/dist/rule-load.js.map +1 -1
  356. package/dist/run-semantic.js +1 -1
  357. package/dist/run-semantic.js.map +1 -1
  358. package/dist/run.d.ts +2 -0
  359. package/dist/run.d.ts.map +1 -1
  360. package/dist/run.js +25 -15
  361. package/dist/run.js.map +1 -1
  362. package/dist/sentence-shape.d.ts +0 -1
  363. package/dist/sentence-shape.d.ts.map +1 -1
  364. package/dist/sentence-shape.js +3 -5
  365. package/dist/sentence-shape.js.map +1 -1
  366. package/dist/setting-problems.d.ts +5 -0
  367. package/dist/setting-problems.d.ts.map +1 -0
  368. package/dist/setting-problems.js +15 -0
  369. package/dist/setting-problems.js.map +1 -0
  370. package/dist/soft-break.d.ts +19 -0
  371. package/dist/soft-break.d.ts.map +1 -0
  372. package/dist/soft-break.js +78 -0
  373. package/dist/soft-break.js.map +1 -0
  374. package/dist/structure/build.d.ts.map +1 -1
  375. package/dist/structure/build.js +24 -14
  376. package/dist/structure/build.js.map +1 -1
  377. package/dist/structure/cite.js +1 -1
  378. package/dist/structure/cite.js.map +1 -1
  379. package/dist/structure/date-order.d.ts +3 -0
  380. package/dist/structure/date-order.d.ts.map +1 -1
  381. package/dist/structure/date-order.js +9 -1
  382. package/dist/structure/date-order.js.map +1 -1
  383. package/dist/structure/issues.d.ts +1 -1
  384. package/dist/structure/issues.d.ts.map +1 -1
  385. package/dist/structure/issues.js +19 -8
  386. package/dist/structure/issues.js.map +1 -1
  387. package/dist/structure/listed-tags.d.ts +3 -0
  388. package/dist/structure/listed-tags.d.ts.map +1 -0
  389. package/dist/structure/listed-tags.js +6 -0
  390. package/dist/structure/listed-tags.js.map +1 -0
  391. package/dist/structure/markdown-path.d.ts.map +1 -1
  392. package/dist/structure/markdown-path.js +2 -2
  393. package/dist/structure/markdown-path.js.map +1 -1
  394. package/dist/structure/number-in-sentence.d.ts +15 -0
  395. package/dist/structure/number-in-sentence.d.ts.map +1 -0
  396. package/dist/structure/number-in-sentence.js +13 -0
  397. package/dist/structure/number-in-sentence.js.map +1 -0
  398. package/dist/structure/numbering.d.ts.map +1 -1
  399. package/dist/structure/numbering.js +1 -1
  400. package/dist/structure/numbering.js.map +1 -1
  401. package/dist/structure/relative-find.js +1 -1
  402. package/dist/structure/relative-find.js.map +1 -1
  403. package/dist/structure/sexp.js +1 -1
  404. package/dist/structure/sexp.js.map +1 -1
  405. package/dist/structure/unit-word.d.ts +8 -0
  406. package/dist/structure/unit-word.d.ts.map +1 -0
  407. package/dist/structure/unit-word.js +10 -0
  408. package/dist/structure/unit-word.js.map +1 -0
  409. package/dist/structure/unnumbered.js +2 -2
  410. package/dist/structure/unnumbered.js.map +1 -1
  411. package/dist/structure/wrapped-tail.d.ts +18 -0
  412. package/dist/structure/wrapped-tail.d.ts.map +1 -0
  413. package/dist/structure/wrapped-tail.js +11 -0
  414. package/dist/structure/wrapped-tail.js.map +1 -0
  415. package/dist/team-names.d.ts +9 -0
  416. package/dist/team-names.d.ts.map +1 -0
  417. package/dist/team-names.js +34 -0
  418. package/dist/team-names.js.map +1 -0
  419. package/dist/ui.d.ts +2 -0
  420. package/dist/ui.d.ts.map +1 -1
  421. package/dist/ui.js +6 -0
  422. package/dist/ui.js.map +1 -1
  423. package/dist/version.js +1 -1
  424. package/dist/version.js.map +1 -1
  425. package/dist/watch.d.ts +2 -2
  426. package/dist/watch.d.ts.map +1 -1
  427. package/dist/watch.js +5 -5
  428. package/dist/watch.js.map +1 -1
  429. package/genres.yaml +363 -0
  430. package/package.json +4 -3
  431. package/rules/adverb-overuse.yaml +2 -2
  432. package/rules/agentless-passive.yaml +1 -1
  433. package/rules/ai-generated-composite.yaml +2 -2
  434. package/rules/ai-tell.yaml +2 -2
  435. package/rules/bold-density.yaml +2 -2
  436. package/rules/closing-cliche.yaml +1 -1
  437. package/rules/concrete-evidence-density.yaml +2 -2
  438. package/rules/contraction-consistency.yaml +1 -1
  439. package/rules/cushion-phrase-density.yaml +1 -1
  440. package/rules/dangling-reference.yaml +3 -3
  441. package/rules/date-order.yaml +3 -3
  442. package/rules/date-weekday-mismatch.yaml +1 -1
  443. package/rules/double-keigo.yaml +1 -1
  444. package/rules/doubled-word.yaml +1 -1
  445. package/rules/duplicate-definition.yaml +1 -1
  446. package/rules/emoji-density.yaml +1 -1
  447. package/rules/empty-conclusion.yaml +2 -1
  448. package/rules/empty-intensifier.yaml +1 -1
  449. package/rules/excessive-hedging.yaml +16 -6
  450. package/rules/expletive-construction.yaml +2 -2
  451. package/rules/heading-echo.yaml +1 -1
  452. package/rules/hiragana-fukushi.yaml +1 -1
  453. package/rules/internal-jargon.yaml +2 -2
  454. package/rules/latin-spacing.yaml +6 -3
  455. package/rules/max-kanji-continuous.yaml +5 -4
  456. package/rules/max-paragraph-length.yaml +9 -2
  457. package/rules/max-sentence-length.yaml +14 -2
  458. package/rules/ngram-repetition.yaml +4 -4
  459. package/rules/no-doubled-joshi.yaml +4 -4
  460. package/rules/no-em-dash.yaml +2 -2
  461. package/rules/no-mixed-desumasu.yaml +1 -1
  462. package/rules/no-nakaguro-parallel.yaml +2 -2
  463. package/rules/numbering-gap.yaml +1 -1
  464. package/rules/oxford-comma-consistency.yaml +2 -1
  465. package/rules/padded-intro.yaml +2 -1
  466. package/rules/paragraph-length-variance.yaml +1 -1
  467. package/rules/preamble-length.yaml +2 -2
  468. package/rules/preferred-term.yaml +1 -1
  469. package/rules/proper-noun-density.yaml +3 -2
  470. package/rules/repeated-conjunction.yaml +2 -2
  471. package/rules/repeated-sentence-head.yaml +2 -2
  472. package/rules/required-sections.yaml +1 -1
  473. package/rules/rule-of-three.yaml +2 -2
  474. package/rules/sasete-itadaku.yaml +3 -3
  475. package/rules/section-length-uniformity.yaml +1 -1
  476. package/rules/sentence-initial-conjunction-run.yaml +2 -2
  477. package/rules/sentence-rhythm.yaml +1 -1
  478. package/rules/taigen-dome-in-prose.yaml +2 -2
  479. package/rules/title-case-consistency.yaml +3 -3
  480. package/rules/total-mismatch.yaml +1 -1
  481. package/rules/undefined-acronym.yaml +18 -5
  482. package/rules/unqualified-superlative.yaml +3 -3
  483. package/skills/chaff/SKILL.md +28 -9
  484. package/src/address-chain.ts +6 -3
  485. package/src/backends/anthropic.ts +5 -1
  486. package/src/backends/openai.ts +2 -1
  487. package/src/backends/types.ts +3 -2
  488. package/src/baseline.ts +1 -1
  489. package/src/calendar-number.ts +75 -0
  490. package/src/cli-args.ts +2 -2
  491. package/src/cli-text.ts +48 -18
  492. package/src/cli.ts +51 -47
  493. package/src/commands/cite.ts +4 -4
  494. package/src/commands/eval.ts +27 -6
  495. package/src/commands/feedback.ts +2 -1
  496. package/src/commands/init-ask.ts +53 -0
  497. package/src/commands/test.ts +105 -47
  498. package/src/commands/tree.ts +9 -7
  499. package/src/config/by-path.ts +8 -9
  500. package/src/config/load.ts +24 -0
  501. package/src/config/name-problems.ts +15 -0
  502. package/src/date-stamp.ts +1 -1
  503. package/src/detectors/acronym-compound.ts +9 -0
  504. package/src/detectors/acronym-context.ts +22 -1
  505. package/src/detectors/acronym-expansion.ts +72 -5
  506. package/src/detectors/char-shape.ts +56 -16
  507. package/src/detectors/citation-key.ts +13 -0
  508. package/src/detectors/cited-name.ts +23 -0
  509. package/src/detectors/concrete-evidence.ts +31 -0
  510. package/src/detectors/counter-edge.ts +29 -0
  511. package/src/detectors/dotted-name.ts +14 -0
  512. package/src/detectors/doubled-word.ts +25 -2
  513. package/src/detectors/en-shape.ts +123 -38
  514. package/src/detectors/enumerated-runs.ts +3 -2
  515. package/src/detectors/gram-place.ts +9 -2
  516. package/src/detectors/gram-predicate.ts +55 -0
  517. package/src/detectors/heading-case.ts +44 -0
  518. package/src/detectors/heading-echo.ts +5 -1
  519. package/src/detectors/index.ts +4 -3
  520. package/src/detectors/lettered-index.ts +78 -0
  521. package/src/detectors/lexicon.ts +68 -8
  522. package/src/detectors/name-numeral.ts +37 -0
  523. package/src/detectors/name-part.ts +24 -0
  524. package/src/detectors/name-run.ts +16 -0
  525. package/src/detectors/orthography.ts +43 -10
  526. package/src/detectors/phrase-match.ts +23 -12
  527. package/src/detectors/placeholder.ts +38 -0
  528. package/src/detectors/quoted-variant.ts +20 -0
  529. package/src/detectors/repeated-head.ts +3 -4
  530. package/src/detectors/series-label.ts +33 -0
  531. package/src/detectors/signals.ts +49 -35
  532. package/src/detectors/stacked-hedge.ts +93 -0
  533. package/src/detectors/structure-tree.ts +2 -2
  534. package/src/detectors/superlative-amount.ts +24 -0
  535. package/src/detectors/superlative-clause.ts +82 -0
  536. package/src/detectors/superlative-name.ts +30 -0
  537. package/src/detectors/superlative-scope.ts +3 -3
  538. package/src/detectors/team.ts +18 -2
  539. package/src/detectors/token-shape.ts +13 -6
  540. package/src/document.ts +41 -8
  541. package/src/eval.ts +1 -1
  542. package/src/feedback/draft.ts +1 -1
  543. package/src/feedback/excerpt.ts +2 -2
  544. package/src/file-header.ts +31 -0
  545. package/src/files.ts +3 -3
  546. package/src/genre-check.ts +46 -0
  547. package/src/genre-load.ts +24 -0
  548. package/src/genre-parse.ts +126 -0
  549. package/src/genre-suggest.ts +37 -0
  550. package/src/genre.ts +25 -5
  551. package/src/init-choice.ts +47 -0
  552. package/src/init.ts +20 -19
  553. package/src/joined-view.ts +96 -0
  554. package/src/judge.ts +3 -1
  555. package/src/line-continues.ts +43 -0
  556. package/src/line-paragraphs.ts +83 -0
  557. package/src/mask.ts +5 -6
  558. package/src/measure.ts +9 -1
  559. package/src/number-name.ts +35 -13
  560. package/src/orthography.ts +15 -9
  561. package/src/plain-source.ts +5 -0
  562. package/src/plugin.ts +27 -2
  563. package/src/profile/check.ts +34 -0
  564. package/src/profile/for-file.ts +10 -3
  565. package/src/profile/load.ts +1 -1
  566. package/src/profile/select.ts +11 -4
  567. package/src/quoted-span.ts +39 -0
  568. package/src/reasons.ts +5 -1
  569. package/src/render/compact.ts +1 -3
  570. package/src/render/eval.ts +102 -38
  571. package/src/render/friendly.ts +21 -6
  572. package/src/render/genres.ts +31 -0
  573. package/src/render/plural.ts +6 -0
  574. package/src/render/rules-json.ts +10 -3
  575. package/src/render/semantic.ts +117 -34
  576. package/src/render/summary.ts +4 -3
  577. package/src/render/suppressions.ts +3 -2
  578. package/src/render/text.ts +18 -3
  579. package/src/resolve-genre.ts +20 -0
  580. package/src/rule-load.ts +6 -1
  581. package/src/run-semantic.ts +1 -1
  582. package/src/run.ts +28 -15
  583. package/src/sentence-shape.ts +3 -7
  584. package/src/setting-problems.ts +24 -0
  585. package/src/soft-break.ts +94 -0
  586. package/src/structure/build.ts +24 -15
  587. package/src/structure/cite.ts +1 -1
  588. package/src/structure/date-order.ts +16 -1
  589. package/src/structure/issues.ts +24 -8
  590. package/src/structure/listed-tags.ts +7 -0
  591. package/src/structure/markdown-path.ts +2 -2
  592. package/src/structure/number-in-sentence.ts +20 -0
  593. package/src/structure/numbering.ts +1 -1
  594. package/src/structure/relative-find.ts +1 -1
  595. package/src/structure/sexp.ts +1 -1
  596. package/src/structure/unit-word.ts +11 -0
  597. package/src/structure/unnumbered.ts +2 -2
  598. package/src/structure/wrapped-tail.ts +22 -0
  599. package/src/team-names.ts +39 -0
  600. package/src/ui.ts +7 -0
  601. package/src/version.ts +1 -1
  602. package/src/watch.ts +5 -5
@@ -1,5 +1,6 @@
1
1
  import { lengthOf } from "../measure.ts";
2
2
  import { isClosed } from "../sentence-shape.ts";
3
+ import { isTitleCase, minorityCase, pageTitleOf } from "./heading-case.ts";
3
4
  import type { Detector, Finding, ProseDocument, Section, Sentence, Token } from "../plugin.ts";
4
5
 
5
6
  const PER = 1000;
@@ -78,11 +79,11 @@ const firstWord = (sentence: Sentence): string =>
78
79
  * 何が主張なのかが分からなくなる。
79
80
  */
80
81
  export const conjunctionRun: Detector = (doc, options): Finding[] => {
81
- const heads = (options.lexicon ?? []).map((entry) => entry.pattern.toLowerCase());
82
+ const heads = new Set((options.lexicon ?? []).map((entry) => entry.pattern.toLowerCase()));
82
83
  const runs = doc.sentences.filter(isClosed).reduce<Sentence[][]>(
83
84
  (acc, sentence) => {
84
85
  const last = acc.at(-1) ?? [];
85
- if (!heads.includes(firstWord(sentence).toLowerCase())) return [...acc.slice(0, -1), last, []];
86
+ if (!heads.has(firstWord(sentence).toLowerCase())) return [...acc.slice(0, -1), last, []];
86
87
  return [...acc.slice(0, -1), [...last, sentence]];
87
88
  },
88
89
  [[]],
@@ -106,19 +107,6 @@ export const conjunctionRun: Detector = (doc, options): Finding[] => {
106
107
  });
107
108
  };
108
109
 
109
- const WORD = /[A-Za-z][A-Za-z'-]*/gu;
110
-
111
- /** 小さい語は Title Case でも小文字のままなので、大文字化の判定から外す。 */
112
- const MINOR = new Set(["a", "an", "the", "and", "or", "but", "of", "in", "on", "at", "to", "for", "with", "as", "by", "from", "is"]);
113
-
114
- const isTitleCase = (heading: string): boolean | undefined => {
115
- const words = [...heading.matchAll(WORD)].map((match) => match[0]).filter((word) => !MINOR.has(word.toLowerCase()));
116
- // 1 語の見出しは、どちらの流儀でも先頭が大文字になる。判定できない。
117
- if (words.length < 2) return undefined;
118
- const capitalized = words.filter((word) => word[0] === word[0]?.toUpperCase()).length;
119
- return capitalized === words.length;
120
- };
121
-
122
110
  const headingsOf = (sections: readonly Section[]): { readonly section: Section; readonly title: boolean }[] =>
123
111
  sections.flatMap((section) => {
124
112
  const title = isTitleCase(section.heading);
@@ -127,25 +115,24 @@ const headingsOf = (sections: readonly Section[]): { readonly section: Section;
127
115
 
128
116
  /**
129
117
  * 見出しの大文字化が混ざっている。どちらの流儀が正しいかは決めない。spec §12.3。
130
- * 見るのは文書の中で揃っているかだけで、少数派のほうを指摘する。
118
+ * 見るのは文書の中で揃っているかだけで、少数派のほうを指摘する。題名は指摘しない(minorityCase)。
131
119
  */
132
120
  export const titleCaseMix: Detector = (doc, options): Finding[] => {
133
- const judged = headingsOf(doc.sections);
134
- const title = judged.filter((entry) => entry.title).length;
135
- const minorityIsTitle = title <= judged.length - title;
136
- const few = minorityIsTitle ? title : judged.length - title;
137
- // 同数なら少数派は無い。どちらかを「他と違う」と呼ぶのは、選びかたが恣意的になる。
138
- if (few === 0 || few * 2 === judged.length || few > options.limit) return [];
139
- return judged
140
- .filter((entry) => entry.title === minorityIsTitle)
141
- .map(({ section }) => ({
142
- rule: "title-case-consistency",
143
- severity: "info",
144
- line: 0,
145
- column: 0,
146
- quote: section.heading,
147
- values: { count: few, limit: options.limit, offset: section.span.start },
148
- }));
121
+ const pageTitle = pageTitleOf(doc.sections);
122
+ const judged = headingsOf(doc.sections.filter((section) => section !== pageTitle));
123
+ const titleCase = judged.filter((entry) => entry.title).length;
124
+ const pageTitleCase = pageTitle === undefined ? undefined : isTitleCase(pageTitle.heading);
125
+ const minorityIsTitle = minorityCase({ titleCase, sentenceCase: judged.length - titleCase }, pageTitleCase);
126
+ const few = minorityIsTitle === undefined ? [] : judged.filter((entry) => entry.title === minorityIsTitle);
127
+ if (few.length > options.limit) return [];
128
+ return few.map(({ section }) => ({
129
+ rule: "title-case-consistency",
130
+ severity: "info",
131
+ line: 0,
132
+ column: 0,
133
+ quote: section.heading,
134
+ values: { count: few.length, limit: options.limit, offset: section.span.start },
135
+ }));
149
136
  };
150
137
 
151
138
  const LIST_CONJUNCTION = new Set(["and", "or"]);
@@ -166,7 +153,8 @@ const depthsOf = (tokens: readonly Token[]): number[] =>
166
153
  { depths: [], open: 0 },
167
154
  ).depths;
168
155
 
169
- type Clause = { readonly tokens: readonly Token[]; readonly depths: readonly number[] };
156
+ /** participleWords は、解析器が分詞と読まないが読点のあとで分詞の句を始める語(meaning)。 */
157
+ type Clause = { readonly tokens: readonly Token[]; readonly depths: readonly number[]; readonly participleWords: ReadonlySet<string> };
170
158
 
171
159
  /** 冠詞や引用符を飛ばした、項目の頭の品詞。the parser と an exporter と samples を同じ形と見る。 */
172
160
  const NOMINAL = new Set(["NOUN", "PROPN", "PRON", "NUM", "ADJ"]);
@@ -204,6 +192,21 @@ const itemsBefore = (clause: Clause, at: number): Token[][] => {
204
192
  .filter((item) => item.length > 0);
205
193
  };
206
194
 
195
+ /**
196
+ * and / or で始まる項目で、前の並びは閉じている(offering, giving, or receiving, directly or indirectly)。
197
+ * 次の並びはその項目から始まる。項目が動詞で始まれば、その目的語から(Develop, maintain, and track courses, materials and events)。
198
+ */
199
+ const afterClosedList = (items: readonly Token[][]): Token[][] => {
200
+ const closed = items.findLastIndex((item) => LIST_CONJUNCTION.has(openingOf(item)?.surface.toLowerCase() ?? ""));
201
+ const closing = items[closed];
202
+ if (closing === undefined) return [...items];
203
+ const body = closing.slice(closing.findIndex((token) => LIST_CONJUNCTION.has(token.surface.toLowerCase())) + 1);
204
+ const head = body.findIndex(isContent);
205
+ const rest = VERBAL.has(body[head]?.pos ?? "") ? body.slice(head + 1) : body;
206
+ // 項目が残らなければ、後ろは新しい句の頭。導入の句(directly or indirectly,)を外す。
207
+ return rest.some(isContent) ? [rest, ...items.slice(closed + 1)] : [...withoutLead(items.slice(closed + 1))];
208
+ };
209
+
207
210
  /** and / or の後ろの項目。次の読点か節の切れ目まで。 */
208
211
  const itemAfter = (tokens: readonly Token[], at: number): Token[] => {
209
212
  const end = tokens.findIndex((token, index) => index > at && (isComma(token) || CLAUSE_BREAK.has(token.surface)));
@@ -245,7 +248,9 @@ const joinAdjectives = (items: readonly Token[][]): Token[][] =>
245
248
  items.reduce<Token[][]>((joined, item) => {
246
249
  const previous = joined.at(-1);
247
250
  const stacked = previous !== undefined && lastContent(previous)?.pos === "ADJ" && lastContent(item)?.pos !== "ADJ";
248
- return stacked ? [...joined.slice(0, -1), [...previous, ...item]] : [...joined, item];
251
+ if (stacked) joined[joined.length - 1] = [...previous, ...item];
252
+ else joined.push(item);
253
+ return joined;
249
254
  }, []);
250
255
 
251
256
  /**
@@ -268,14 +273,93 @@ const hasVerb = (item: readonly Token[]): boolean => item.some((token) => VERBAL
268
273
 
269
274
  const predicatesAgree = (items: readonly Token[][], shape: string): boolean => !VERBAL.has(shape) || items.every(hasVerb);
270
275
 
276
+ /** -ing 形(VerbForm=Ger)と過去分詞(Part)。filling the gaps と sentenced to prison は並べても並列にならない。 */
277
+ const VERB_FORMS = new Set(["Ger", "Part"]);
278
+
279
+ const verbFormOf = (token: Token): string | undefined => {
280
+ const form = token.features?.["VerbForm"];
281
+ return form !== undefined && VERB_FORMS.has(form) ? form : undefined;
282
+ };
283
+
284
+ /** 項目の頭の分詞とその形。副詞は飛ばす(originally written)。引用符の中の語(‘modelling’)は語の例なので分詞と読まない。 */
285
+ const participleOpening = (item: readonly Token[], words: ReadonlySet<string>): { readonly at: number; readonly form: string } | undefined => {
286
+ const at = item.findIndex((token) => token.pos !== "PUNCT" && token.pos !== "ADV");
287
+ const opening = item[at];
288
+ if (opening === undefined) return undefined;
289
+ const form = words.has(opening.surface.toLowerCase()) ? "Ger" : verbFormOf(opening);
290
+ return form === undefined ? undefined : { at, form };
291
+ };
292
+
293
+ const hasParticiple = (item: readonly Token[], form: string): boolean => item.some((token) => verbFormOf(token) === form);
294
+
295
+ /**
296
+ * 最初より後ろの項目が分詞で始まるとき。最初の項目にも同じ形の分詞があれば分詞の並び
297
+ * (are hiring engineers, buying laptops and renting desks / Tested, reviewed and approved)。後ろの項目が動詞で始まれば述語の並びで、
298
+ * 分詞は解析器の読み違い(the jury, cross-examined the witnesses, presented witnesses, and made)。そうでなければ分詞の句
299
+ * (from Latin, meaning ship or boat / the judge, sitting without a jury, and sentenced)で、並びがあるならその句の中から始まる。
300
+ * 分詞より前の項目と分詞そのものは項目ではない(programs, including the office, the desk and the team)。句の中の項目の形が揃わなければ
301
+ * 句は挿入で、並びは句の後ろだけ(relief, including an injunction, in any court ... and without / including before the date, to a
302
+ * Recipient ... “confidential”, “proprietary”, or the like)。
303
+ */
304
+ /** 解析器は過去形と過去分詞(fixed)を見分けられない。動詞のある項目のあとの「過去分詞」の並びは、述語の並びと読む。 */
305
+ const pastPredicates = (first: readonly Token[], form: string, after: readonly Token[], words: ReadonlySet<string>): boolean =>
306
+ form === "Part" && participleOpening(after, words)?.form === "Part" && hasVerb(first);
307
+
308
+ const fromParticiplePhrase = (items: readonly Token[][], after: readonly Token[], words: ReadonlySet<string>): readonly Token[][] => {
309
+ const at = items.findIndex((item, index) => index > 0 && participleOpening(item, words) !== undefined);
310
+ const phrase = items[at];
311
+ const first = items[0];
312
+ const opening = phrase === undefined ? undefined : participleOpening(phrase, words);
313
+ if (first === undefined || phrase === undefined || opening === undefined) return items;
314
+ const rest = items.slice(at + 1);
315
+ const predicates = rest.some((item) => VERBAL.has(shapeOf(item) ?? "")) || pastPredicates(first, opening.form, after, words);
316
+ if (hasParticiple(first, opening.form) || predicates) return items;
317
+ const inPhrase = withoutLead([phrase.slice(opening.at + 1), ...rest].filter((item) => item.some(isContent)));
318
+ return new Set(inPhrase.map(shapeOf)).size > 1 ? rest : inPhrase;
319
+ };
320
+
321
+ const APPOSITIVE_ANCHOR = new Set(["NOUN", "PROPN"]);
322
+
323
+ /**
324
+ * 名詞のすぐ後ろの、読点で挟まれた X and Y は同格(two HPV types, HPV16 and HPV18, that account / Governments, both state and
325
+ * federal, spend)。項目が 2 つに見え、and の前に読点が無く、and の後ろの項目が読点で閉じ、後ろの 2 つに動詞が無いときに同格と読む。
326
+ * 2 つを結ぶ同格に Oxford comma は打たないので、and の前に読点があれば 3 つの並び(the subcommittee, full committee, and
327
+ * chamber levels, as well as)。読点の無い本当の並び(apples, pears and plums, then ...)はこの形と見分けられず、判定から外れる。
328
+ */
329
+ const isAppositive = (clause: Clause, at: number, items: readonly Token[][], after: readonly Token[]): boolean => {
330
+ const [anchor, head] = items;
331
+ if (items.length !== 2 || anchor === undefined || head === undefined || commaBefore(clause.tokens, at)) return false;
332
+ const end = at + 1 + after.length;
333
+ // 閉じた読点のあとに and / or が続くなら、並びのあとに節をつないでいる(apples, pears and plums, and went home)。
334
+ const joinsClause = LIST_CONJUNCTION.has(clause.tokens[end + 1]?.surface.toLowerCase() ?? "");
335
+ const closed = clause.tokens[end]?.surface === "," && clause.depths[end] === clause.depths[at] && !joinsClause;
336
+ return closed && APPOSITIVE_ANCHOR.has(lastContent(anchor)?.pos ?? "") && !hasVerb(head) && !hasVerb(after);
337
+ };
338
+
339
+ /** 副詞を飛ばした頭の形。explain and justify と ultimately ensure は同じ動詞の項目。 */
340
+ const leadShape = (item: readonly Token[]): string | undefined => shapeOf(item.filter((token) => token.pos !== "ADV"));
341
+
342
+ /**
343
+ * and の後ろの項目に読点と、同じ形の項目を連れた and / or が続くなら、この and は項目の中にある(searches and seizures, and
344
+ * the Eighth's ban)。頭の形か、節かどうかが違えば別の節をつなぐ and で、この and までが並び(apples, pears and plums, and went home / and figs fell)。
345
+ */
346
+ const listContinues = (clause: Clause, at: number, after: readonly Token[]): boolean => {
347
+ const next = at + 1 + after.length;
348
+ if (clause.tokens[next]?.surface !== "," || !LIST_CONJUNCTION.has(clause.tokens[next + 1]?.surface.toLowerCase() ?? "")) return false;
349
+ const following = itemAfter(clause.tokens, next + 1);
350
+ const shape = leadShape(following);
351
+ return shape !== undefined && shape === leadShape(after) && isClause(following) === isClause(after);
352
+ };
353
+
271
354
  /**
272
355
  * 読点で区切った項目が and / or の前に 2 つ以上あり、最後の項目と and の後ろが同じ形のときだけ並列。
273
356
  * 導入の読点(After the review, the team fixed the bug and shipped it.)や、節をつなぐ読点
274
357
  * (We tested it, and the team shipped it.)は、Oxford comma を打つかどうかの選択を見せない。
275
358
  */
276
359
  const listAt = (clause: Clause, at: number): boolean | undefined => {
277
- const items = withoutLead(joinAdjectives(itemsBefore(clause, at)));
278
360
  const after = itemAfter(clause.tokens, at);
361
+ const items = fromParticiplePhrase(afterClosedList(withoutLead(joinAdjectives(itemsBefore(clause, at)))), after, clause.participleWords);
362
+ if (listContinues(clause, at, after) || isAppositive(clause, at, items, after)) return undefined;
279
363
  const last = items.at(-1);
280
364
  const shape = last === undefined ? undefined : shapeOf(last);
281
365
  if (items.length < 2 || shape === undefined || shape !== shapeOf(after)) return undefined;
@@ -289,8 +373,8 @@ const listAt = (clause: Clause, at: number): boolean | undefined => {
289
373
  * どちらが正しいかは決めない。スタイルガイドで割れる論点に立場を取ると rule ごと無視される。
290
374
  * 見るのは 1 つの文書で揃っているかだけ。spec §12.3。
291
375
  */
292
- const oxfordIn = (tokens: readonly Token[]): boolean | undefined => {
293
- const clause = { tokens, depths: depthsOf(tokens) };
376
+ const oxfordIn = (tokens: readonly Token[], participleWords: ReadonlySet<string>): boolean | undefined => {
377
+ const clause = { tokens, depths: depthsOf(tokens), participleWords };
294
378
  return tokens.reduce<boolean | undefined>(
295
379
  (found, token, at) => found ?? (at > 0 && LIST_CONJUNCTION.has(token.surface.toLowerCase()) ? listAt(clause, at) : undefined),
296
380
  undefined,
@@ -298,8 +382,9 @@ const oxfordIn = (tokens: readonly Token[]): boolean | undefined => {
298
382
  };
299
383
 
300
384
  export const oxfordComma: Detector = (doc, options): Finding[] => {
385
+ const participleWords = new Set((doc.lexicons["participle-word"] ?? []).map((entry) => entry.pattern.toLowerCase()));
301
386
  const judged = doc.sentences.flatMap((sentence) => {
302
- const oxford = oxfordIn(sentence.tokens ?? []);
387
+ const oxford = oxfordIn(sentence.tokens ?? [], participleWords);
303
388
  return oxford === undefined ? [] : [{ sentence, oxford }];
304
389
  });
305
390
  const withComma = judged.filter((entry) => entry.oxford).length;
@@ -61,8 +61,9 @@ export const enumeratedRuns = (paragraphs: readonly Span[], starts: readonly num
61
61
  const enumerated = paragraphs.filter((paragraph) => opensWithItem(paragraph, starts, source));
62
62
  const runs = enumerated.reduce<Span[]>((joined, paragraph) => {
63
63
  const last = joined.at(-1);
64
- if (last === undefined || !isBlank(source, last.end, paragraph.start)) return [...joined, paragraph];
65
- return [...joined.slice(0, -1), { start: last.start, end: paragraph.end }];
64
+ if (last === undefined || !isBlank(source, last.end, paragraph.start)) joined.push(paragraph);
65
+ else joined[joined.length - 1] = { start: last.start, end: paragraph.end };
66
+ return joined;
66
67
  }, []);
67
68
  return runs.filter((run) => itemsIn(run, starts, source) >= MIN_ITEMS);
68
69
  };
@@ -1,3 +1,4 @@
1
+ import { softBreaks } from "../soft-break.ts";
1
2
  import type { LengthUnit, Span } from "../plugin.ts";
2
3
 
3
4
  /** 数えるために詰めた文と、詰めた文の各文字が元の文のどこにあったか。 */
@@ -7,15 +8,21 @@ export type Compacted = { readonly text: string; readonly offsets: readonly numb
7
8
  * n-gram を数えるときと同じ詰め方。word 単位は空白の並びを 1 つにして前後を削り、char 単位は空白を全部除く。
8
9
  * 語句が元の文のどこにあるかを戻せるよう、残した文字ごとに元の位置を持つ。
9
10
  */
11
+ /** spans が覆う位置(UTF-16 の単位)。 */
12
+ const offsetsIn = (spans: readonly Span[]): Set<number> =>
13
+ new Set(spans.flatMap((span) => Array.from({ length: span.end - span.start }, (_, step) => span.start + step)));
14
+
10
15
  export const compacted = (text: string, unit: LengthUnit): Compacted => {
11
16
  const chars: string[] = [];
12
17
  const offsets: number[] = [];
13
18
  const state = { pendingSpace: -1, at: 0 };
19
+ // 読み手に見えない改行は空白 1 つにもしない。proseText がつないだ語句を、ここでも同じ形で探せるように。
20
+ const hidden = offsetsIn(unit === "word" ? softBreaks(text) : []);
14
21
  Array.from(text).forEach((char) => {
15
22
  const at = state.at;
16
23
  state.at += char.length;
17
24
  if (/\s/u.test(char)) {
18
- if (unit === "word" && chars.length > 0 && state.pendingSpace === -1) state.pendingSpace = at;
25
+ if (unit === "word" && chars.length > 0 && state.pendingSpace === -1 && !hidden.has(at)) state.pendingSpace = at;
19
26
  return;
20
27
  }
21
28
  if (state.pendingSpace !== -1) {
@@ -25,7 +32,7 @@ export const compacted = (text: string, unit: LengthUnit): Compacted => {
25
32
  }
26
33
  chars.push(char);
27
34
  // 位置は UTF-16 の単位で持つ。placeOf は indexOf の位置で引くので、絵文字(2 単位)には 2 つ置く。
28
- Array.from({ length: char.length }, (_, unit) => offsets.push(at + unit));
35
+ Array.from({ length: char.length }, (_, codeUnit) => offsets.push(at + codeUnit));
29
36
  });
30
37
  return { text: chars.join(""), offsets };
31
38
  };
@@ -0,0 +1,55 @@
1
+ import { isNamePart } from "./name-part.ts";
2
+ import type { LengthUnit, Span, Token } from "../plugin.ts";
3
+
4
+ /** 動詞と助動詞。言い回し(ることができます、it is important to)にはこれが入り、主題の名前(state and local tax)には入らない。 */
5
+ const PREDICATE_POS = new Set(["VERB", "AUX"]);
6
+
7
+ const NOUNS = new Set(["NOUN", "PROPN", "ADJ"]);
8
+
9
+ /** 中身を持たない語。名前の前後に付いても、名前の繰り返しを言い回しに変えない。 */
10
+ const FUNCTION_POS = new Set(["ADP", "DET", "CCONJ", "SCONJ", "PART", "PUNCT", "SYM", "X"]);
11
+
12
+ /**
13
+ * 英語で名詞のすぐ前の動詞は、名詞を飾る語(形容詞を挟んでも同じ)(the upcoming fiscal year、the Disclosing Party、the borrow checker)で、
14
+ * 述語ではない。日本語の名詞の前の動詞(〜にあるコンポーネント)は言い回しの一部になるので、word 単位だけで見る。
15
+ */
16
+ const modifiesNoun = (tokens: readonly Token[], index: number, unit: LengthUnit): boolean =>
17
+ unit === "word" && tokens[index]?.pos === "VERB" && NOUNS.has(tokens[index + 1]?.pos ?? "");
18
+
19
+ /** 英語で大文字の名前の一部になった動詞(Procedures Guide、the Location Object)も、述語ではない。 */
20
+ const namePartAt = (tokens: readonly Token[], index: number, unit: LengthUnit): boolean => unit === "word" && isNamePart(tokens, index);
21
+
22
+ const isName = (tokens: readonly Token[], index: number, unit: LengthUnit): boolean => {
23
+ const token = tokens[index];
24
+ return token !== undefined && (token.pos === "PROPN" || token.features?.["NameType"] !== undefined || namePartAt(tokens, index, unit));
25
+ };
26
+
27
+ const overlaps = (token: Token, window: Span): boolean => token.span.start < window.end && window.start < token.span.end;
28
+
29
+ const isCut = (token: Token, window: Span): boolean => token.span.start < window.start || window.end < token.span.end;
30
+
31
+ /**
32
+ * 窓が名前だけを繰り返しているか。predicate(添字)のほかに窓が触れる語が、名前と中身の無い語だけで、名前が 1 つはある。
33
+ * 窓の端で切れた動詞("ed in the NSF Propos" の identified、"ed by Applicable Law" の prohibited)は語尾しか入っておらず、
34
+ * 動詞は出てくるたびに違ってよい。繰り返されているのは名前のほう。
35
+ */
36
+ const onlyNamesBeside = (tokens: readonly Token[], predicate: number, window: Span, unit: LengthUnit): boolean => {
37
+ const touched = tokens.flatMap((token, index) => (index !== predicate && overlaps(token, window) ? [index] : []));
38
+ const names = touched.filter((index) => isName(tokens, index, unit));
39
+ const rest = touched.filter((index) => !isName(tokens, index, unit));
40
+ return names.length > 0 && rest.every((index) => FUNCTION_POS.has(tokens[index]?.pos ?? ""));
41
+ };
42
+
43
+ const countsAsPredicate = (tokens: readonly Token[], index: number, window: Span, unit: LengthUnit): boolean => {
44
+ const token = tokens[index];
45
+ if (token === undefined || !PREDICATE_POS.has(token.pos) || !overlaps(token, window)) return false;
46
+ if (modifiesNoun(tokens, index, unit) || namePartAt(tokens, index, unit)) return false;
47
+ return !isCut(token, window) || !onlyNamesBeside(tokens, index, window, unit);
48
+ };
49
+
50
+ /**
51
+ * 語句が文の中で占める範囲(window、文書の中の位置)に、述語になる動詞か助動詞があるか。
52
+ * 範囲で見る。文字で探すと、同じ文の別の所の動詞「use」が、語句の中の名詞「use」に当たる。
53
+ */
54
+ export const hasPredicateIn = (tokens: readonly Token[], window: Span, unit: LengthUnit): boolean =>
55
+ tokens.some((_, index) => countsAsPredicate(tokens, index, window, unit));
@@ -0,0 +1,44 @@
1
+ const WORD = /[A-Za-z][A-Za-z'-]*/gu;
2
+
3
+ /** 小さい語は Title Case でも小文字のままなので、大文字化の判定から外す。 */
4
+ const MINOR = new Set(["a", "an", "the", "and", "or", "but", "of", "in", "on", "at", "to", "for", "with", "as", "by", "from", "is"]);
5
+
6
+ /** 大文字だけの語(PR・FCPs)は略語で、どちらの流儀でも大文字のまま。Title Case の証拠にならない。 */
7
+ const ACRONYM = /^[A-Z]{2,}s?$/u;
8
+
9
+ const TITLE_DEPTH = 1;
10
+
11
+ /** 見出しが Title Case か。判定できなければ undefined。 */
12
+ export const isTitleCase = (heading: string): boolean | undefined => {
13
+ const words = [...heading.matchAll(WORD)].map((match) => match[0]).filter((word) => !MINOR.has(word.toLowerCase()) && !ACRONYM.test(word));
14
+ // 1 語の見出しは、どちらの流儀でも先頭が大文字になる。判定できない。
15
+ if (words.length < 2) return undefined;
16
+ const capitalized = words.filter((word) => word[0] === word[0]?.toUpperCase()).length;
17
+ return capitalized === words.length;
18
+ };
19
+
20
+ /**
21
+ * 文書の題名。最初の見出しが深さ 1 で、深さ 1 の見出しがほかに無いときの、その見出し。
22
+ * 深さ 1 が複数あると、最初のものが題名なのか章なのかを構造からは決められないので、題名とは呼ばない。
23
+ * 見出しより前の導入部(深さ 0)は見出しではないので数えない。
24
+ */
25
+ export const pageTitleOf = <T extends { readonly depth: number }>(sections: readonly T[]): T | undefined => {
26
+ const headed = sections.filter((section) => section.depth > 0);
27
+ const first = headed[0];
28
+ if (first?.depth !== TITLE_DEPTH) return undefined;
29
+ return headed.filter((section) => section.depth === TITLE_DEPTH).length === 1 ? first : undefined;
30
+ };
31
+
32
+ export type CaseCounts = { readonly titleCase: number; readonly sentenceCase: number };
33
+
34
+ /**
35
+ * 節の見出しのうち少数派の流儀(true なら Title Case)。少数派が無ければ undefined。
36
+ * 題名は数に入れない。題名は名前なので、節と流儀を分けるのはよくある揃えかた。
37
+ * 同数のときだけ題名の流儀で決める。題名と同じ流儀のほうが、書き手の選んだ流儀である見込みが高い。
38
+ */
39
+ export const minorityCase = (sections: CaseCounts, pageTitleCase: boolean | undefined): boolean | undefined => {
40
+ if (sections.titleCase === sections.sentenceCase) return sections.titleCase === 0 || pageTitleCase === undefined ? undefined : !pageTitleCase;
41
+ const minorityIsTitle = sections.titleCase < sections.sentenceCase;
42
+ const few = minorityIsTitle ? sections.titleCase : sections.sentenceCase;
43
+ return few === 0 ? undefined : minorityIsTitle;
44
+ };
@@ -1,5 +1,6 @@
1
1
  import { lengthOf } from "../measure.ts";
2
2
  import { handsOver } from "./lead-in.ts";
3
+ import { withoutQuotedVariants } from "./quoted-variant.ts";
3
4
  import type { Detector, Finding, LengthUnit, ProseDocument, Section } from "../plugin.ts";
4
5
 
5
6
  /** 見出しが短すぎると、偶然の一致で 100% になる。これ未満の見出しは見ない。 */
@@ -58,12 +59,15 @@ const leadsIn = (doc: ProseDocument, section: Section, phrases: readonly string[
58
59
  return first !== undefined && handsOver(first.text, doc.source.slice(first.span.end, section.span.end), phrases);
59
60
  };
60
61
 
62
+ /** 見出しとの重なりを測る文。用語集や表記の手引きは見出しの語の別の書き方を引用する(Not “datacentre”)ので、それは数えない。 */
63
+ const echoedText = (section: Section): string => withoutQuotedVariants(section.firstSentence?.text ?? "", section.heading);
64
+
61
65
  export const headingEcho: Detector = (doc, options): Finding[] => {
62
66
  const leadIns = (doc.lexicons["lead-in"] ?? []).map((entry) => entry.pattern);
63
67
  return doc.sections
64
68
  .filter((section) => section.heading.length > 0 && section.firstSentence !== undefined && addsLittle(section, doc.lengthUnit))
65
69
  .filter((section) => !leadsIn(doc, section, leadIns))
66
- .map((section) => ({ section, overlap: Math.round(containment(trigrams(section.heading), trigrams(section.firstSentence?.text ?? "")) * 100) }))
70
+ .map((section) => ({ section, overlap: Math.round(containment(trigrams(section.heading), trigrams(echoedText(section))) * 100) }))
67
71
  .filter(({ overlap }) => overlap >= options.limit)
68
72
  .map(({ section, overlap }) => ({
69
73
  rule: "heading-echo",
@@ -4,7 +4,7 @@ import { sentenceLength } from "./sentence-length.ts";
4
4
  import { headingEcho } from "./heading-echo.ts";
5
5
  import { repeatedHead } from "./repeated-head.ts";
6
6
  import { sentenceRhythm } from "./sentence-rhythm.ts";
7
- import { phraseMatch } from "./phrase-match.ts";
7
+ import { phraseCount, phraseMatch } from "./phrase-match.ts";
8
8
  import { agentlessPassive } from "./agentless-passive.ts";
9
9
  import { doubledWord } from "./doubled-word.ts";
10
10
  import { sentenceEnding } from "./sentence-ending.ts";
@@ -13,7 +13,7 @@ import { kanjiRun, middleDot } from "./char-shape.ts";
13
13
  import { adverbDensity, conjunctionRun, expletive, oxfordComma, titleCaseMix } from "./en-shape.ts";
14
14
  import { paragraphLength, paragraphVariance, preambleLength, ruleOfThree, sectionUniformity } from "./structure.ts";
15
15
  import { concreteEvidence, dashDensity, emojiDensity, ngramRepetition, undefinedAcronym } from "./signals.ts";
16
- import { aiTell, contractionMix, cushionDensity, hedgingDensity, repeatedConjunction, unqualifiedSuperlative } from "./lexicon.ts";
16
+ import { aiTell, contractionMix, cushionDensity, hedging, repeatedConjunction, unqualifiedSuperlative } from "./lexicon.ts";
17
17
  import { internalJargon, properNounDensity, requiredSections } from "./team.ts";
18
18
  import { latinSpacing, preferredTerm } from "./orthography.ts";
19
19
  import { danglingReference, dateOrder, dateWeekdayMismatch, duplicateDefinition, numberingGap, totalMismatch } from "./structure-tree.ts";
@@ -26,6 +26,7 @@ export const DETECTORS: Readonly<Record<string, Detector>> = {
26
26
  "repeated-head": repeatedHead,
27
27
  "sentence-rhythm": sentenceRhythm,
28
28
  "phrase-match": phraseMatch,
29
+ "phrase-count": phraseCount,
29
30
  "agentless-passive": agentlessPassive,
30
31
  "sentence-ending": sentenceEnding,
31
32
  "noun-ending": nounEnding,
@@ -48,7 +49,7 @@ export const DETECTORS: Readonly<Record<string, Detector>> = {
48
49
  "ngram-repetition": ngramRepetition,
49
50
  "undefined-acronym": undefinedAcronym,
50
51
  "concrete-evidence": concreteEvidence,
51
- "hedging-density": hedgingDensity,
52
+ hedging: hedging,
52
53
  "cushion-density": cushionDensity,
53
54
  "unqualified-superlative": unqualifiedSuperlative,
54
55
  "repeated-conjunction": repeatedConjunction,
@@ -0,0 +1,78 @@
1
+ import type { Section } from "../plugin.ts";
2
+
3
+ /** 索引の区切りは 1 文字の見出し(A, あ)で、それ自体は本文を持たない。 */
4
+ const SINGLE_LETTER = /^\p{L}$/u;
5
+ const FIRST_LETTER = /\p{L}/u;
6
+
7
+ /** 区切りが 1 つだけなら、たまたま 1 文字の見出しかもしれない。並んで初めて索引と読む。 */
8
+ const MIN_DIVIDERS = 2;
9
+
10
+ /** カタカナとひらがなは Unicode で同じ並びにあり、この差だけ離れている。 */
11
+ const KATAKANA = { first: 0x30a1, last: 0x30f6, toHiragana: 0x60 };
12
+
13
+ type Group = { readonly run: number; readonly letter: string; readonly entries: Section[] };
14
+
15
+ /** 索引で引くときの文字。最初の文字から濁点やアクセントを外し、カタカナはひらがなに、小文字は大文字にする。 */
16
+ export const indexLetterOf = (text: string): string => {
17
+ const letter = FIRST_LETTER.exec(text.normalize("NFD"))?.[0] ?? "";
18
+ const code = letter.codePointAt(0) ?? 0;
19
+ const base = code >= KATAKANA.first && code <= KATAKANA.last ? String.fromCodePoint(code - KATAKANA.toHiragana) : letter;
20
+ return base.toUpperCase();
21
+ };
22
+
23
+ const isDivider = (section: Section): boolean => SINGLE_LETTER.test(section.heading.normalize("NFC")) && section.sentences.length === 0;
24
+
25
+ /**
26
+ * ある深さの区切りごとに、その下の節を集める。区切りと同じ深さか浅い見出しで閉じる。
27
+ * 区切りでない見出しを挟んだ区切りは、別の並び(run)になる。別の親の下の A と B を 1 つの索引に綴じないため。
28
+ */
29
+ const groupsAt = (sections: readonly Section[], depth: number): Group[] => {
30
+ const groups: Group[] = [];
31
+ sections.reduce<{ run: number; open: Group | undefined }>(
32
+ ({ run, open }, section) => {
33
+ if (section.depth === depth && isDivider(section)) {
34
+ const group = { run, letter: indexLetterOf(section.heading), entries: [] };
35
+ groups.push(group);
36
+ return { run, open: group };
37
+ }
38
+ if (open !== undefined && section.depth > depth) {
39
+ open.entries.push(section);
40
+ return { run, open };
41
+ }
42
+ return { run: run + 1, open: undefined };
43
+ },
44
+ { run: 0, open: undefined },
45
+ );
46
+ return groups;
47
+ };
48
+
49
+ const runsOf = (groups: readonly Group[]): Group[][] =>
50
+ [...new Set(groups.map((group) => group.run))].map((run) => groups.filter((group) => group.run === run));
51
+
52
+ const ascending = (groups: readonly Group[]): boolean => groups.every((group, index) => index === 0 || (groups[index - 1]?.letter ?? "") < group.letter);
53
+
54
+ /** 区切りのすぐ下の見出し(見出し語)の過半が、区切りの文字で始まる。語りの節を 1 文字の見出しで束ねただけなら、こうはならない。 */
55
+ const headwordsFollowLetters = (groups: readonly Group[], depth: number): boolean => {
56
+ const matches = groups.flatMap((group) =>
57
+ group.entries.filter((entry) => entry.depth === depth + 1).map((entry) => indexLetterOf(entry.heading) === group.letter),
58
+ );
59
+ return matches.filter((match) => match).length * 2 > matches.length;
60
+ };
61
+
62
+ const isIndex = (groups: readonly Group[], depth: number): boolean =>
63
+ groups.length >= MIN_DIVIDERS && ascending(groups) && headwordsFollowLetters(groups, depth);
64
+
65
+ /**
66
+ * 文字で区切った索引(用語集・A to Z)の項目。区切りの見出しより深い節が、次の同じ深さ以上の見出しまで続く。
67
+ * 項目は語の定義や表記の決まりで、根拠の要る主張ではない。
68
+ */
69
+ export const letteredIndexEntries = (sections: readonly Section[]): ReadonlySet<Section> => {
70
+ const depths = new Set(sections.filter(isDivider).map((section) => section.depth));
71
+ return new Set(
72
+ [...depths].flatMap((depth) =>
73
+ runsOf(groupsAt(sections, depth))
74
+ .filter((run) => isIndex(run, depth))
75
+ .flatMap((run) => run.flatMap((group) => group.entries)),
76
+ ),
77
+ );
78
+ };
@@ -1,8 +1,12 @@
1
1
  import { joinWords } from "./word-list.ts";
2
2
  import { wordsOf } from "./structure.ts";
3
- import type { Detector, Finding, Lexicon, LexiconEntry, ProseDocument, Sentence } from "../plugin.ts";
4
- import { entryIn, entryOpens, entryRanges } from "./lexicon-match.ts";
3
+ import type { Detector, Finding, Lexicon, LexiconEntry, ProseDocument, Sentence, Span, Token } from "../plugin.ts";
4
+ import { entryIn, entryOpens, entryRanges, type TokenRange } from "./lexicon-match.ts";
5
5
  import { scoped, scopeMarkersOf, type ScopeMarkers } from "./superlative-scope.ts";
6
+ import { namesQuantity } from "./superlative-name.ts";
7
+ import { namesAmount } from "./superlative-amount.ts";
8
+ import { restricted, type Restrictors } from "./superlative-clause.ts";
9
+ import { stackedHedges, type StackedHedge } from "./stacked-hedge.ts";
6
10
 
7
11
  const PER = 1000;
8
12
 
@@ -36,27 +40,83 @@ const densityRule =
36
40
  }));
37
41
  };
38
42
 
39
- export const hedgingDensity = densityRule("excessive-hedging");
43
+ const hedgingDensity = densityRule("excessive-hedging");
40
44
  export const cushionDensity = densityRule("cushion-phrase-density");
41
45
 
46
+ /** 書いたとおりの字。行の折り返しをまたいでいたら空白 1 つに。 */
47
+ const writtenAt = (source: string, span: Span): string => source.slice(span.start, span.end).replace(/\s+/gu, " ").trim();
48
+
49
+ const stackedFinding = (doc: ProseDocument, stacked: StackedHedge): Finding => ({
50
+ rule: "excessive-hedging",
51
+ severity: "warning",
52
+ line: 0,
53
+ column: 0,
54
+ quote: stacked.sentence.text.trim(),
55
+ variant: "stacked",
56
+ values: {
57
+ matched: joinWords(
58
+ stacked.spans.map((span) => writtenAt(doc.source, span)),
59
+ doc.language,
60
+ ),
61
+ count: stacked.spans.length,
62
+ offset: stacked.spans[0]?.start ?? stacked.sentence.span.start,
63
+ },
64
+ });
65
+
66
+ /**
67
+ * 逃げの表現は 2 つの見方で探す。1 つの文に重ねたもの(短い文書でも 1 文で分かる)と、文書全体の密度。
68
+ * 重ねた文は密度の側では出さない。同じ文を同じ rule で 2 度言わない。
69
+ */
70
+ export const hedging: Detector = (doc, options): Finding[] => {
71
+ const stacked = stackedHedges(doc.sentences, {
72
+ hedges: options.lexicon ?? [],
73
+ frames: doc.lexicons["hedge-frame"] ?? [],
74
+ scope: doc.lexicons["hedge-scope"] ?? [],
75
+ });
76
+ const reported = new Set(stacked.map((entry) => entry.sentence.span.start));
77
+ const density = hedgingDensity(doc, options).filter((finding) => !reported.has(Number(finding.values["offset"])));
78
+ return [...stacked.map((entry) => stackedFinding(doc, entry)), ...density];
79
+ };
80
+
42
81
  /** 数字は語ではないので言語を問わない。数があれば測った結果を言っている。品詞の数(NUM)は "the best one" の one まで含むので使わない。 */
43
82
  const DIGIT = /\d/u;
44
83
 
45
- type Qualifiers = { readonly comparison: Lexicon; readonly scope: ScopeMarkers };
84
+ type Qualifiers = {
85
+ readonly comparison: Lexicon;
86
+ readonly scope: ScopeMarkers;
87
+ readonly quantityNouns: Lexicon;
88
+ readonly amounts: Lexicon;
89
+ readonly restrictors: Restrictors;
90
+ };
46
91
 
47
92
  const qualifiersOf = (doc: ProseDocument): Qualifiers => ({
48
93
  comparison: doc.lexicons["comparison-marker"] ?? [],
49
94
  scope: scopeMarkersOf(doc.lexicons["superlative-scope"] ?? []),
95
+ quantityNouns: doc.lexicons["quantity-noun"] ?? [],
96
+ amounts: doc.lexicons["superlative-amount"] ?? [],
97
+ restrictors: {
98
+ relatives: doc.lexicons["relative-word"] ?? [],
99
+ bounds: doc.lexicons["superlative-bound"] ?? [],
100
+ subjects: doc.lexicons["subject-pronoun"] ?? [],
101
+ },
50
102
  });
51
103
 
52
- /** どの出現も範囲を持つときだけ。1 つでも範囲の無い出現があれば、その文には限定の無い最上級がある。 */
53
- const everyScoped = (sentence: Sentence, entry: LexiconEntry, scope: ScopeMarkers): boolean => {
104
+ /** 範囲・節・分詞・形容詞のどれかが最上級を限っているか、最上級が量か名前を言っているか。 */
105
+ const qualifiedAt = (tokens: readonly Token[], range: TokenRange, qualifiers: Qualifiers): boolean =>
106
+ scoped(tokens, range, qualifiers.scope) ||
107
+ restricted(tokens, range, qualifiers.restrictors) ||
108
+ namesQuantity(tokens, range, qualifiers.quantityNouns) ||
109
+ namesAmount(tokens, range, qualifiers.amounts);
110
+
111
+ /** どの出現も限られているか量を言うときだけ。1 つでもそうでない出現があれば、その文には限定の無い最上級がある。 */
112
+ const everyQualified = (sentence: Sentence, entry: LexiconEntry, qualifiers: Qualifiers): boolean => {
113
+ const tokens = sentence.tokens ?? [];
54
114
  const ranges = entryRanges(sentence, entry);
55
- return ranges.length > 0 && ranges.every((range) => scoped(sentence.tokens ?? [], range, scope));
115
+ return ranges.length > 0 && ranges.every((range) => qualifiedAt(tokens, range, qualifiers));
56
116
  };
57
117
 
58
118
  const qualified = (sentence: Sentence, entry: LexiconEntry, qualifiers: Qualifiers): boolean =>
59
- DIGIT.test(sentence.text) || qualifiers.comparison.some((marker) => entryIn(sentence, marker)) || everyScoped(sentence, entry, qualifiers.scope);
119
+ DIGIT.test(sentence.text) || qualifiers.comparison.some((marker) => entryIn(sentence, marker)) || everyQualified(sentence, entry, qualifiers);
60
120
 
61
121
  const bareHits = (doc: ProseDocument, lexicon: Lexicon): Hit[] => {
62
122
  const qualifiers = qualifiersOf(doc);