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.
- package/README.md +27 -2
- package/dist/address-chain.d.ts +4 -1
- package/dist/address-chain.d.ts.map +1 -1
- package/dist/address-chain.js +6 -3
- package/dist/address-chain.js.map +1 -1
- package/dist/backends/anthropic.d.ts +2 -1
- package/dist/backends/anthropic.d.ts.map +1 -1
- package/dist/backends/anthropic.js +4 -1
- package/dist/backends/anthropic.js.map +1 -1
- package/dist/backends/openai.d.ts +2 -1
- package/dist/backends/openai.d.ts.map +1 -1
- package/dist/backends/openai.js +1 -1
- package/dist/backends/openai.js.map +1 -1
- package/dist/backends/types.d.ts +2 -1
- package/dist/backends/types.d.ts.map +1 -1
- package/dist/backends/types.js +1 -1
- package/dist/backends/types.js.map +1 -1
- package/dist/baseline.js +1 -1
- package/dist/baseline.js.map +1 -1
- package/dist/calendar-number.d.ts +26 -0
- package/dist/calendar-number.d.ts.map +1 -0
- package/dist/calendar-number.js +51 -0
- package/dist/calendar-number.js.map +1 -0
- package/dist/cli-args.d.ts +0 -5
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +2 -2
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-text.d.ts +10 -3
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +33 -14
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -47
- package/dist/cli.js.map +1 -1
- package/dist/commands/cite.d.ts.map +1 -1
- package/dist/commands/cite.js +4 -4
- package/dist/commands/cite.js.map +1 -1
- package/dist/commands/eval.d.ts +4 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +21 -7
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +2 -1
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/init-ask.d.ts +19 -0
- package/dist/commands/init-ask.d.ts.map +1 -0
- package/dist/commands/init-ask.js +43 -0
- package/dist/commands/init-ask.js.map +1 -0
- package/dist/commands/test.d.ts +8 -0
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +73 -40
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/tree.d.ts +1 -8
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +9 -7
- package/dist/commands/tree.js.map +1 -1
- package/dist/config/by-path.d.ts.map +1 -1
- package/dist/config/by-path.js +10 -9
- package/dist/config/by-path.js.map +1 -1
- package/dist/config/load.d.ts +4 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +19 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/name-problems.d.ts +5 -0
- package/dist/config/name-problems.d.ts.map +1 -0
- package/dist/config/name-problems.js +11 -0
- package/dist/config/name-problems.js.map +1 -0
- package/dist/date-stamp.js +1 -1
- package/dist/date-stamp.js.map +1 -1
- package/dist/detectors/acronym-compound.d.ts +8 -0
- package/dist/detectors/acronym-compound.d.ts.map +1 -0
- package/dist/detectors/acronym-compound.js +8 -0
- package/dist/detectors/acronym-compound.js.map +1 -0
- package/dist/detectors/acronym-context.d.ts +4 -0
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +17 -1
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-expansion.d.ts +10 -0
- package/dist/detectors/acronym-expansion.d.ts.map +1 -1
- package/dist/detectors/acronym-expansion.js +63 -5
- package/dist/detectors/acronym-expansion.js.map +1 -1
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +47 -15
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/citation-key.d.ts +13 -0
- package/dist/detectors/citation-key.d.ts.map +1 -0
- package/dist/detectors/citation-key.js +9 -0
- package/dist/detectors/citation-key.js.map +1 -0
- package/dist/detectors/cited-name.d.ts +11 -0
- package/dist/detectors/cited-name.d.ts.map +1 -0
- package/dist/detectors/cited-name.js +15 -0
- package/dist/detectors/cited-name.js.map +1 -0
- package/dist/detectors/concrete-evidence.d.ts +7 -0
- package/dist/detectors/concrete-evidence.d.ts.map +1 -0
- package/dist/detectors/concrete-evidence.js +21 -0
- package/dist/detectors/concrete-evidence.js.map +1 -0
- package/dist/detectors/counter-edge.d.ts +8 -0
- package/dist/detectors/counter-edge.d.ts.map +1 -0
- package/dist/detectors/counter-edge.js +27 -0
- package/dist/detectors/counter-edge.js.map +1 -0
- package/dist/detectors/dotted-name.d.ts +13 -0
- package/dist/detectors/dotted-name.d.ts.map +1 -0
- package/dist/detectors/dotted-name.js +10 -0
- package/dist/detectors/dotted-name.js.map +1 -0
- package/dist/detectors/doubled-word.d.ts +6 -1
- package/dist/detectors/doubled-word.d.ts.map +1 -1
- package/dist/detectors/doubled-word.js +22 -2
- package/dist/detectors/doubled-word.js.map +1 -1
- package/dist/detectors/en-shape.d.ts +1 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +112 -29
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/enumerated-runs.d.ts.map +1 -1
- package/dist/detectors/enumerated-runs.js +4 -2
- package/dist/detectors/enumerated-runs.js.map +1 -1
- package/dist/detectors/gram-place.d.ts +0 -4
- package/dist/detectors/gram-place.d.ts.map +1 -1
- package/dist/detectors/gram-place.js +7 -2
- package/dist/detectors/gram-place.js.map +1 -1
- package/dist/detectors/gram-predicate.d.ts +7 -0
- package/dist/detectors/gram-predicate.d.ts.map +1 -0
- package/dist/detectors/gram-predicate.js +44 -0
- package/dist/detectors/gram-predicate.js.map +1 -0
- package/dist/detectors/heading-case.d.ts +21 -0
- package/dist/detectors/heading-case.d.ts.map +1 -0
- package/dist/detectors/heading-case.js +40 -0
- package/dist/detectors/heading-case.js.map +1 -0
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +4 -1
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +4 -3
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/lettered-index.d.ts +9 -0
- package/dist/detectors/lettered-index.d.ts.map +1 -0
- package/dist/detectors/lettered-index.js +54 -0
- package/dist/detectors/lettered-index.js.map +1 -0
- package/dist/detectors/lexicon.d.ts +5 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +51 -5
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/name-numeral.d.ts +16 -0
- package/dist/detectors/name-numeral.d.ts.map +1 -0
- package/dist/detectors/name-numeral.js +30 -0
- package/dist/detectors/name-numeral.js.map +1 -0
- package/dist/detectors/name-part.d.ts +8 -0
- package/dist/detectors/name-part.d.ts.map +1 -0
- package/dist/detectors/name-part.js +20 -0
- package/dist/detectors/name-part.js.map +1 -0
- package/dist/detectors/name-run.d.ts +8 -0
- package/dist/detectors/name-run.d.ts.map +1 -0
- package/dist/detectors/name-run.js +13 -0
- package/dist/detectors/name-run.js.map +1 -0
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +35 -7
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/phrase-match.d.ts +3 -1
- package/dist/detectors/phrase-match.d.ts.map +1 -1
- package/dist/detectors/phrase-match.js +20 -15
- package/dist/detectors/phrase-match.js.map +1 -1
- package/dist/detectors/placeholder.d.ts +14 -0
- package/dist/detectors/placeholder.d.ts.map +1 -0
- package/dist/detectors/placeholder.js +24 -0
- package/dist/detectors/placeholder.js.map +1 -0
- package/dist/detectors/quoted-variant.d.ts +7 -0
- package/dist/detectors/quoted-variant.d.ts.map +1 -0
- package/dist/detectors/quoted-variant.js +18 -0
- package/dist/detectors/quoted-variant.js.map +1 -0
- package/dist/detectors/repeated-head.d.ts.map +1 -1
- package/dist/detectors/repeated-head.js +5 -4
- package/dist/detectors/repeated-head.js.map +1 -1
- package/dist/detectors/series-label.d.ts +13 -0
- package/dist/detectors/series-label.d.ts.map +1 -0
- package/dist/detectors/series-label.js +26 -0
- package/dist/detectors/series-label.js.map +1 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +44 -29
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/stacked-hedge.d.ts +23 -0
- package/dist/detectors/stacked-hedge.d.ts.map +1 -0
- package/dist/detectors/stacked-hedge.js +64 -0
- package/dist/detectors/stacked-hedge.js.map +1 -0
- package/dist/detectors/structure-tree.js +1 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/superlative-amount.d.ts +9 -0
- package/dist/detectors/superlative-amount.d.ts.map +1 -0
- package/dist/detectors/superlative-amount.js +20 -0
- package/dist/detectors/superlative-amount.js.map +1 -0
- package/dist/detectors/superlative-clause.d.ts +18 -0
- package/dist/detectors/superlative-clause.d.ts.map +1 -0
- package/dist/detectors/superlative-clause.js +61 -0
- package/dist/detectors/superlative-clause.js.map +1 -0
- package/dist/detectors/superlative-name.d.ts +9 -0
- package/dist/detectors/superlative-name.d.ts.map +1 -0
- package/dist/detectors/superlative-name.js +24 -0
- package/dist/detectors/superlative-name.js.map +1 -0
- package/dist/detectors/superlative-scope.d.ts +6 -0
- package/dist/detectors/superlative-scope.d.ts.map +1 -1
- package/dist/detectors/superlative-scope.js +3 -3
- package/dist/detectors/superlative-scope.js.map +1 -1
- package/dist/detectors/team.d.ts +0 -6
- package/dist/detectors/team.d.ts.map +1 -1
- package/dist/detectors/team.js +17 -1
- package/dist/detectors/team.js.map +1 -1
- package/dist/detectors/token-shape.d.ts.map +1 -1
- package/dist/detectors/token-shape.js +15 -5
- package/dist/detectors/token-shape.js.map +1 -1
- package/dist/document.d.ts +3 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +28 -7
- package/dist/document.js.map +1 -1
- package/dist/eval.js +1 -1
- package/dist/eval.js.map +1 -1
- package/dist/feedback/draft.js +1 -1
- package/dist/feedback/draft.js.map +1 -1
- package/dist/feedback/excerpt.d.ts +0 -1
- package/dist/feedback/excerpt.d.ts.map +1 -1
- package/dist/feedback/excerpt.js +2 -2
- package/dist/feedback/excerpt.js.map +1 -1
- package/dist/file-header.d.ts +20 -0
- package/dist/file-header.d.ts.map +1 -0
- package/dist/file-header.js +21 -0
- package/dist/file-header.js.map +1 -0
- package/dist/files.js +3 -3
- package/dist/files.js.map +1 -1
- package/dist/genre-check.d.ts +34 -0
- package/dist/genre-check.d.ts.map +1 -0
- package/dist/genre-check.js +21 -0
- package/dist/genre-check.js.map +1 -0
- package/dist/genre-load.d.ts +6 -0
- package/dist/genre-load.d.ts.map +1 -0
- package/dist/genre-load.js +22 -0
- package/dist/genre-load.js.map +1 -0
- package/dist/genre-parse.d.ts +40 -0
- package/dist/genre-parse.d.ts.map +1 -0
- package/dist/genre-parse.js +104 -0
- package/dist/genre-parse.js.map +1 -0
- package/dist/genre-suggest.d.ts +15 -0
- package/dist/genre-suggest.d.ts.map +1 -0
- package/dist/genre-suggest.js +26 -0
- package/dist/genre-suggest.js.map +1 -0
- package/dist/genre.d.ts +8 -1
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +19 -5
- package/dist/genre.js.map +1 -1
- package/dist/init-choice.d.ts +12 -0
- package/dist/init-choice.d.ts.map +1 -0
- package/dist/init-choice.js +41 -0
- package/dist/init-choice.js.map +1 -0
- package/dist/init.d.ts +0 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +16 -17
- package/dist/init.js.map +1 -1
- package/dist/joined-view.d.ts +25 -0
- package/dist/joined-view.d.ts.map +1 -0
- package/dist/joined-view.js +76 -0
- package/dist/joined-view.js.map +1 -0
- package/dist/judge.d.ts +2 -1
- package/dist/judge.d.ts.map +1 -1
- package/dist/judge.js +1 -1
- package/dist/judge.js.map +1 -1
- package/dist/line-continues.d.ts +6 -0
- package/dist/line-continues.d.ts.map +1 -0
- package/dist/line-continues.js +33 -0
- package/dist/line-continues.js.map +1 -0
- package/dist/line-paragraphs.d.ts +4 -0
- package/dist/line-paragraphs.d.ts.map +1 -0
- package/dist/line-paragraphs.js +69 -0
- package/dist/line-paragraphs.js.map +1 -0
- package/dist/mask.d.ts.map +1 -1
- package/dist/mask.js +7 -6
- package/dist/mask.js.map +1 -1
- package/dist/measure.d.ts +1 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +5 -1
- package/dist/measure.js.map +1 -1
- package/dist/number-name.d.ts +6 -3
- package/dist/number-name.d.ts.map +1 -1
- package/dist/number-name.js +31 -14
- package/dist/number-name.js.map +1 -1
- package/dist/orthography.d.ts +4 -0
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +11 -9
- package/dist/orthography.js.map +1 -1
- package/dist/plain-source.d.ts +6 -0
- package/dist/plain-source.d.ts.map +1 -0
- package/dist/plain-source.js +6 -0
- package/dist/plain-source.js.map +1 -0
- package/dist/plugin.d.ts +22 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/check.d.ts +28 -0
- package/dist/profile/check.d.ts.map +1 -0
- package/dist/profile/check.js +12 -0
- package/dist/profile/check.js.map +1 -0
- package/dist/profile/for-file.d.ts +2 -2
- package/dist/profile/for-file.d.ts.map +1 -1
- package/dist/profile/for-file.js +9 -2
- package/dist/profile/for-file.js.map +1 -1
- package/dist/profile/load.js +1 -1
- package/dist/profile/load.js.map +1 -1
- package/dist/profile/select.d.ts +4 -2
- package/dist/profile/select.d.ts.map +1 -1
- package/dist/profile/select.js +8 -3
- package/dist/profile/select.js.map +1 -1
- package/dist/quoted-span.d.ts +22 -0
- package/dist/quoted-span.d.ts.map +1 -0
- package/dist/quoted-span.js +25 -0
- package/dist/quoted-span.js.map +1 -0
- package/dist/reasons.d.ts +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +4 -1
- package/dist/reasons.js.map +1 -1
- package/dist/render/compact.d.ts.map +1 -1
- package/dist/render/compact.js +1 -2
- package/dist/render/compact.js.map +1 -1
- package/dist/render/eval.d.ts +2 -1
- package/dist/render/eval.d.ts.map +1 -1
- package/dist/render/eval.js +85 -43
- package/dist/render/eval.js.map +1 -1
- package/dist/render/friendly.d.ts +2 -1
- package/dist/render/friendly.d.ts.map +1 -1
- package/dist/render/friendly.js +14 -6
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/genres.d.ts +5 -0
- package/dist/render/genres.d.ts.map +1 -0
- package/dist/render/genres.js +28 -0
- package/dist/render/genres.js.map +1 -0
- package/dist/render/plural.d.ts +3 -0
- package/dist/render/plural.d.ts.map +1 -0
- package/dist/render/plural.js +5 -0
- package/dist/render/plural.js.map +1 -0
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +9 -3
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/semantic.d.ts +3 -2
- package/dist/render/semantic.d.ts.map +1 -1
- package/dist/render/semantic.js +94 -34
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/summary.d.ts.map +1 -1
- package/dist/render/summary.js +4 -3
- package/dist/render/summary.js.map +1 -1
- package/dist/render/suppressions.d.ts.map +1 -1
- package/dist/render/suppressions.js +3 -2
- package/dist/render/suppressions.js.map +1 -1
- package/dist/render/text.d.ts +2 -0
- package/dist/render/text.d.ts.map +1 -1
- package/dist/render/text.js +11 -3
- package/dist/render/text.js.map +1 -1
- package/dist/resolve-genre.d.ts +11 -0
- package/dist/resolve-genre.d.ts.map +1 -0
- package/dist/resolve-genre.js +18 -0
- package/dist/resolve-genre.js.map +1 -0
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +4 -1
- package/dist/rule-load.js.map +1 -1
- package/dist/run-semantic.js +1 -1
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +2 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +25 -15
- package/dist/run.js.map +1 -1
- package/dist/sentence-shape.d.ts +0 -1
- package/dist/sentence-shape.d.ts.map +1 -1
- package/dist/sentence-shape.js +3 -5
- package/dist/sentence-shape.js.map +1 -1
- package/dist/setting-problems.d.ts +5 -0
- package/dist/setting-problems.d.ts.map +1 -0
- package/dist/setting-problems.js +15 -0
- package/dist/setting-problems.js.map +1 -0
- package/dist/soft-break.d.ts +19 -0
- package/dist/soft-break.d.ts.map +1 -0
- package/dist/soft-break.js +78 -0
- package/dist/soft-break.js.map +1 -0
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +24 -14
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/cite.js +1 -1
- package/dist/structure/cite.js.map +1 -1
- package/dist/structure/date-order.d.ts +3 -0
- package/dist/structure/date-order.d.ts.map +1 -1
- package/dist/structure/date-order.js +9 -1
- package/dist/structure/date-order.js.map +1 -1
- package/dist/structure/issues.d.ts +1 -1
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +19 -8
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/listed-tags.d.ts +3 -0
- package/dist/structure/listed-tags.d.ts.map +1 -0
- package/dist/structure/listed-tags.js +6 -0
- package/dist/structure/listed-tags.js.map +1 -0
- package/dist/structure/markdown-path.d.ts.map +1 -1
- package/dist/structure/markdown-path.js +2 -2
- package/dist/structure/markdown-path.js.map +1 -1
- package/dist/structure/number-in-sentence.d.ts +15 -0
- package/dist/structure/number-in-sentence.d.ts.map +1 -0
- package/dist/structure/number-in-sentence.js +13 -0
- package/dist/structure/number-in-sentence.js.map +1 -0
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +1 -1
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/relative-find.js +1 -1
- package/dist/structure/relative-find.js.map +1 -1
- package/dist/structure/sexp.js +1 -1
- package/dist/structure/sexp.js.map +1 -1
- package/dist/structure/unit-word.d.ts +8 -0
- package/dist/structure/unit-word.d.ts.map +1 -0
- package/dist/structure/unit-word.js +10 -0
- package/dist/structure/unit-word.js.map +1 -0
- package/dist/structure/unnumbered.js +2 -2
- package/dist/structure/unnumbered.js.map +1 -1
- package/dist/structure/wrapped-tail.d.ts +18 -0
- package/dist/structure/wrapped-tail.d.ts.map +1 -0
- package/dist/structure/wrapped-tail.js +11 -0
- package/dist/structure/wrapped-tail.js.map +1 -0
- package/dist/team-names.d.ts +9 -0
- package/dist/team-names.d.ts.map +1 -0
- package/dist/team-names.js +34 -0
- package/dist/team-names.js.map +1 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +6 -0
- package/dist/ui.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +5 -5
- package/dist/watch.js.map +1 -1
- package/genres.yaml +363 -0
- package/package.json +4 -3
- package/rules/adverb-overuse.yaml +2 -2
- package/rules/agentless-passive.yaml +1 -1
- package/rules/ai-generated-composite.yaml +2 -2
- package/rules/ai-tell.yaml +2 -2
- package/rules/bold-density.yaml +2 -2
- package/rules/closing-cliche.yaml +1 -1
- package/rules/concrete-evidence-density.yaml +2 -2
- package/rules/contraction-consistency.yaml +1 -1
- package/rules/cushion-phrase-density.yaml +1 -1
- package/rules/dangling-reference.yaml +3 -3
- package/rules/date-order.yaml +3 -3
- package/rules/date-weekday-mismatch.yaml +1 -1
- package/rules/double-keigo.yaml +1 -1
- package/rules/doubled-word.yaml +1 -1
- package/rules/duplicate-definition.yaml +1 -1
- package/rules/emoji-density.yaml +1 -1
- package/rules/empty-conclusion.yaml +2 -1
- package/rules/empty-intensifier.yaml +1 -1
- package/rules/excessive-hedging.yaml +16 -6
- package/rules/expletive-construction.yaml +2 -2
- package/rules/heading-echo.yaml +1 -1
- package/rules/hiragana-fukushi.yaml +1 -1
- package/rules/internal-jargon.yaml +2 -2
- package/rules/latin-spacing.yaml +6 -3
- package/rules/max-kanji-continuous.yaml +5 -4
- package/rules/max-paragraph-length.yaml +9 -2
- package/rules/max-sentence-length.yaml +14 -2
- package/rules/ngram-repetition.yaml +4 -4
- package/rules/no-doubled-joshi.yaml +4 -4
- package/rules/no-em-dash.yaml +2 -2
- package/rules/no-mixed-desumasu.yaml +1 -1
- package/rules/no-nakaguro-parallel.yaml +2 -2
- package/rules/numbering-gap.yaml +1 -1
- package/rules/oxford-comma-consistency.yaml +2 -1
- package/rules/padded-intro.yaml +2 -1
- package/rules/paragraph-length-variance.yaml +1 -1
- package/rules/preamble-length.yaml +2 -2
- package/rules/preferred-term.yaml +1 -1
- package/rules/proper-noun-density.yaml +3 -2
- package/rules/repeated-conjunction.yaml +2 -2
- package/rules/repeated-sentence-head.yaml +2 -2
- package/rules/required-sections.yaml +1 -1
- package/rules/rule-of-three.yaml +2 -2
- package/rules/sasete-itadaku.yaml +3 -3
- package/rules/section-length-uniformity.yaml +1 -1
- package/rules/sentence-initial-conjunction-run.yaml +2 -2
- package/rules/sentence-rhythm.yaml +1 -1
- package/rules/taigen-dome-in-prose.yaml +2 -2
- package/rules/title-case-consistency.yaml +3 -3
- package/rules/total-mismatch.yaml +1 -1
- package/rules/undefined-acronym.yaml +18 -5
- package/rules/unqualified-superlative.yaml +3 -3
- package/skills/chaff/SKILL.md +28 -9
- package/src/address-chain.ts +6 -3
- package/src/backends/anthropic.ts +5 -1
- package/src/backends/openai.ts +2 -1
- package/src/backends/types.ts +3 -2
- package/src/baseline.ts +1 -1
- package/src/calendar-number.ts +75 -0
- package/src/cli-args.ts +2 -2
- package/src/cli-text.ts +48 -18
- package/src/cli.ts +51 -47
- package/src/commands/cite.ts +4 -4
- package/src/commands/eval.ts +27 -6
- package/src/commands/feedback.ts +2 -1
- package/src/commands/init-ask.ts +53 -0
- package/src/commands/test.ts +105 -47
- package/src/commands/tree.ts +9 -7
- package/src/config/by-path.ts +8 -9
- package/src/config/load.ts +24 -0
- package/src/config/name-problems.ts +15 -0
- package/src/date-stamp.ts +1 -1
- package/src/detectors/acronym-compound.ts +9 -0
- package/src/detectors/acronym-context.ts +22 -1
- package/src/detectors/acronym-expansion.ts +72 -5
- package/src/detectors/char-shape.ts +56 -16
- package/src/detectors/citation-key.ts +13 -0
- package/src/detectors/cited-name.ts +23 -0
- package/src/detectors/concrete-evidence.ts +31 -0
- package/src/detectors/counter-edge.ts +29 -0
- package/src/detectors/dotted-name.ts +14 -0
- package/src/detectors/doubled-word.ts +25 -2
- package/src/detectors/en-shape.ts +123 -38
- package/src/detectors/enumerated-runs.ts +3 -2
- package/src/detectors/gram-place.ts +9 -2
- package/src/detectors/gram-predicate.ts +55 -0
- package/src/detectors/heading-case.ts +44 -0
- package/src/detectors/heading-echo.ts +5 -1
- package/src/detectors/index.ts +4 -3
- package/src/detectors/lettered-index.ts +78 -0
- package/src/detectors/lexicon.ts +68 -8
- package/src/detectors/name-numeral.ts +37 -0
- package/src/detectors/name-part.ts +24 -0
- package/src/detectors/name-run.ts +16 -0
- package/src/detectors/orthography.ts +43 -10
- package/src/detectors/phrase-match.ts +23 -12
- package/src/detectors/placeholder.ts +38 -0
- package/src/detectors/quoted-variant.ts +20 -0
- package/src/detectors/repeated-head.ts +3 -4
- package/src/detectors/series-label.ts +33 -0
- package/src/detectors/signals.ts +49 -35
- package/src/detectors/stacked-hedge.ts +93 -0
- package/src/detectors/structure-tree.ts +2 -2
- package/src/detectors/superlative-amount.ts +24 -0
- package/src/detectors/superlative-clause.ts +82 -0
- package/src/detectors/superlative-name.ts +30 -0
- package/src/detectors/superlative-scope.ts +3 -3
- package/src/detectors/team.ts +18 -2
- package/src/detectors/token-shape.ts +13 -6
- package/src/document.ts +41 -8
- package/src/eval.ts +1 -1
- package/src/feedback/draft.ts +1 -1
- package/src/feedback/excerpt.ts +2 -2
- package/src/file-header.ts +31 -0
- package/src/files.ts +3 -3
- package/src/genre-check.ts +46 -0
- package/src/genre-load.ts +24 -0
- package/src/genre-parse.ts +126 -0
- package/src/genre-suggest.ts +37 -0
- package/src/genre.ts +25 -5
- package/src/init-choice.ts +47 -0
- package/src/init.ts +20 -19
- package/src/joined-view.ts +96 -0
- package/src/judge.ts +3 -1
- package/src/line-continues.ts +43 -0
- package/src/line-paragraphs.ts +83 -0
- package/src/mask.ts +5 -6
- package/src/measure.ts +9 -1
- package/src/number-name.ts +35 -13
- package/src/orthography.ts +15 -9
- package/src/plain-source.ts +5 -0
- package/src/plugin.ts +27 -2
- package/src/profile/check.ts +34 -0
- package/src/profile/for-file.ts +10 -3
- package/src/profile/load.ts +1 -1
- package/src/profile/select.ts +11 -4
- package/src/quoted-span.ts +39 -0
- package/src/reasons.ts +5 -1
- package/src/render/compact.ts +1 -3
- package/src/render/eval.ts +102 -38
- package/src/render/friendly.ts +21 -6
- package/src/render/genres.ts +31 -0
- package/src/render/plural.ts +6 -0
- package/src/render/rules-json.ts +10 -3
- package/src/render/semantic.ts +117 -34
- package/src/render/summary.ts +4 -3
- package/src/render/suppressions.ts +3 -2
- package/src/render/text.ts +18 -3
- package/src/resolve-genre.ts +20 -0
- package/src/rule-load.ts +6 -1
- package/src/run-semantic.ts +1 -1
- package/src/run.ts +28 -15
- package/src/sentence-shape.ts +3 -7
- package/src/setting-problems.ts +24 -0
- package/src/soft-break.ts +94 -0
- package/src/structure/build.ts +24 -15
- package/src/structure/cite.ts +1 -1
- package/src/structure/date-order.ts +16 -1
- package/src/structure/issues.ts +24 -8
- package/src/structure/listed-tags.ts +7 -0
- package/src/structure/markdown-path.ts +2 -2
- package/src/structure/number-in-sentence.ts +20 -0
- package/src/structure/numbering.ts +1 -1
- package/src/structure/relative-find.ts +1 -1
- package/src/structure/sexp.ts +1 -1
- package/src/structure/unit-word.ts +11 -0
- package/src/structure/unnumbered.ts +2 -2
- package/src/structure/wrapped-tail.ts +22 -0
- package/src/team-names.ts +39 -0
- package/src/ui.ts +7 -0
- package/src/version.ts +1 -1
- package/src/watch.ts +5 -5
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 大文字で始まる語のすぐ後ろの、I・V・X だけで書いたローマ数字(Engineer II、Leopold III、World War II)。名前に付く
|
|
3
|
+
* 番号で、略語ではない。語の前に文字が無ければ(文の頭、箇条書きの番号の後ろ)、大文字は名前の印ではなく文頭の書き方なので
|
|
4
|
+
* 外さない(Start IV fluids、1. Give IV fluids)。
|
|
5
|
+
* CD、MD、DC、CI、MIX のように C・D・L・M を含む数字は略語であることが多いので、この位置でも略語のまま数える。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type Span = { readonly start: number; readonly end: number };
|
|
9
|
+
|
|
10
|
+
const SMALL_NUMERAL = "(?=[IVX])X{0,3}(?:IX|IV|V?I{0,3})";
|
|
11
|
+
const NAMED_NUMERAL = new RegExp(String.raw`(?<![\p{L}\p{N}_])\p{Lu}\p{Ll}+\s+(?<numeral>${SMALL_NUMERAL})(?![\p{L}\p{N}_&])`, "gu");
|
|
12
|
+
const LETTER = /\p{L}/u;
|
|
13
|
+
|
|
14
|
+
/** 文の中の、名前の後ろの番号の範囲(数字だけ)。 */
|
|
15
|
+
export const nameNumeralSpans = (text: string): Span[] =>
|
|
16
|
+
[...text.matchAll(NAMED_NUMERAL)]
|
|
17
|
+
.filter((match) => LETTER.test(text.slice(0, match.index)))
|
|
18
|
+
.map((match) => {
|
|
19
|
+
const numeral = match.groups?.["numeral"] ?? "";
|
|
20
|
+
const end = match.index + match[0].length;
|
|
21
|
+
return { start: end - numeral.length, end };
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 文の頭か文の終わりの . の後ろに、. を付けて 1 語で立つ数字(Survey of Cool Stars. VII. The Inner Disk、II. Background)。
|
|
26
|
+
* 続き物や区切りの番号で、略語ではない。日本語の文分けは英文の . で切らないので、文の中の . の後ろも見る。
|
|
27
|
+
* C・D・L・M を含む数字(CD.)は略語でもあるので外さない。
|
|
28
|
+
*/
|
|
29
|
+
// 前置きは後ろ読みにしない。長さの決まらない後ろ読みは、長い空白の上で位置ごとに遡って二乗の時間がかかる。
|
|
30
|
+
const LONE_NUMERAL = new RegExp(String.raw`(?<lead>^\s*|[.。]\s+)(?<numeral>${SMALL_NUMERAL})(?=\.(?:\s|$))`, "gu");
|
|
31
|
+
|
|
32
|
+
/** 文の中の、1 語で立つ番号の範囲(数字だけ)。 */
|
|
33
|
+
export const loneNumeralSpans = (text: string): Span[] =>
|
|
34
|
+
[...text.matchAll(LONE_NUMERAL)].map((match) => {
|
|
35
|
+
const start = match.index + (match.groups?.["lead"] ?? "").length;
|
|
36
|
+
return { start, end: start + (match.groups?.["numeral"] ?? "").length };
|
|
37
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Token } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
const LETTER = /\p{L}/u;
|
|
4
|
+
const CAPITAL_HEAD = /^\p{Lu}/u;
|
|
5
|
+
|
|
6
|
+
/** 文字を含む語の添字。&、括弧、句読点は名前の並びを切らない(Learning & Development、Guide (PAPPG))。 */
|
|
7
|
+
const wordIndexes = (tokens: readonly Token[]): number[] => tokens.flatMap((token, index) => (LETTER.test(token.surface) ? [index] : []));
|
|
8
|
+
|
|
9
|
+
/** 文の最初の語より後ろで、大文字で始まる語。文頭の大文字は位置のしるしで、名前のしるしではない。 */
|
|
10
|
+
const isCapitalisedAt = (tokens: readonly Token[], words: readonly number[], at: number): boolean => {
|
|
11
|
+
const index = words[at];
|
|
12
|
+
return at > 0 && index !== undefined && CAPITAL_HEAD.test(tokens[index]?.surface ?? "");
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 語(添字)が、大文字で書いた名前の一部か。文の途中で大文字で始まり、隣の語も文の途中の大文字(Procedures Guide、
|
|
17
|
+
* the Location Object、Learning & Development)。解析器はこういう語を動詞と読むことがあるが、述語ではない。
|
|
18
|
+
* 文頭の語は隣に数えない。Select Save (if applicable) の Save は述語のまま。
|
|
19
|
+
*/
|
|
20
|
+
export const isNamePart = (tokens: readonly Token[], index: number): boolean => {
|
|
21
|
+
const words = wordIndexes(tokens);
|
|
22
|
+
const at = words.indexOf(index);
|
|
23
|
+
return isCapitalisedAt(tokens, words, at) && (isCapitalisedAt(tokens, words, at - 1) || isCapitalisedAt(tokens, words, at + 1));
|
|
24
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Token } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
const nameType = (token: Token | undefined): string | undefined => token?.features?.["NameType"];
|
|
4
|
+
|
|
5
|
+
/** 人の姓と名(田中太郎)。姓が 2 つ続けば(田中山田)、名前の並び。 */
|
|
6
|
+
const isFullName = (named: readonly (Token | undefined)[]): boolean => named.length === 2 && nameType(named[0]) === "Sur" && nameType(named[1]) === "Giv";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 漢字の連なりの語(covering: tokens の添字)が 1 つの名前か。辞書が固有名詞と読む 1 語(日本銀行)か、人の姓と名。
|
|
10
|
+
* 名前は書き手が変えられない。固有名詞に普通の語が続けば(武蔵野美術大学造形構想学部、東京都知事選挙管理委員会事務局)、
|
|
11
|
+
* 名前から組み立てた語で、どこで切れるかは読み手に見えない。名前が続けば(日本銀行日本電信電話)、並び。どちらも名前とは言わない。
|
|
12
|
+
*/
|
|
13
|
+
export const isOneName = (tokens: readonly Token[], covering: readonly number[]): boolean => {
|
|
14
|
+
const named = covering.map((index) => tokens[index]);
|
|
15
|
+
return (named.length === 1 && named[0]?.pos === "PROPN") || isFullName(named);
|
|
16
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { Detector, Finding, Sentence } from "../plugin.ts";
|
|
1
|
+
import type { Detector, Finding, ProseDocument, Sentence, Span } from "../plugin.ts";
|
|
2
|
+
import { calendarRuns, type CalendarRun, type CalendarUnits } from "../calendar-number.ts";
|
|
2
3
|
import { latinBoundaries, minorityStyle, occurrencesOutside, type Boundary, type SpacingKind } from "../orthography.ts";
|
|
4
|
+
import { isWithinAny, quotedSpans } from "../quoted-span.ts";
|
|
3
5
|
import { digitRunAround, isNumberName, sequenceLabelStarts } from "../number-name.ts";
|
|
4
6
|
|
|
5
7
|
/** チームが chaff.yaml の prefer に書いた「使わない書き方」。書いていなければ何も言わない。 */
|
|
@@ -30,25 +32,56 @@ const digitBeside = (boundary: Boundary): number => {
|
|
|
30
32
|
return boundary.spaced ? boundary.offset + 1 : boundary.offset;
|
|
31
33
|
};
|
|
32
34
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
+
/** 文書全体で一度だけ読むもの。sequence は続き番号の行の頭、topUnits は都道府県の単位、calendar は日付・時刻の単位。 */
|
|
36
|
+
type NumberContext = { readonly sequence: ReadonlySet<number>; readonly topUnits: ReadonlySet<string>; readonly calendar: CalendarUnits };
|
|
37
|
+
|
|
38
|
+
/** 文の中の日付・時刻の数の、並びの頭の位置と、日付の中の位置。 */
|
|
39
|
+
const calendarPlaces = (sentence: Sentence, units: CalendarUnits): ReadonlyMap<number, CalendarRun["place"]> =>
|
|
40
|
+
new Map(calendarRuns(sentence.text, sentence.tokens, sentence.span.start, units).map(({ run, place }) => [run.start, place]));
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 日付・時刻の中の境目(「9月」の数と単位のあいだ、「2026年9月」の年と 9 のあいだ)は、詰めて書く決まりで好みではない。
|
|
44
|
+
* 日付の前の境目(「は 9月」の空白)は、数の前をどう空けるかという書き手の書き方なので数える。
|
|
45
|
+
*/
|
|
46
|
+
const isInsideCalendar = (boundary: Boundary, place: CalendarRun["place"] | undefined): boolean =>
|
|
47
|
+
place !== undefined && (boundary.kind === "after-digit" || place === "inner");
|
|
48
|
+
|
|
49
|
+
/** 番号・識別子として書かれた数(number-name.ts)と日付・時刻の中の境目は、空け方の好みではないので数えない。 */
|
|
50
|
+
const isCounted = (sentence: Sentence, boundary: Boundary, context: NumberContext, calendar: ReadonlyMap<number, CalendarRun["place"]>): boolean => {
|
|
35
51
|
if (boundary.kind === "letter") return true;
|
|
36
52
|
const run = digitRunAround(sentence.text, digitBeside(boundary));
|
|
37
|
-
|
|
53
|
+
if (run === undefined) return true;
|
|
54
|
+
if (isInsideCalendar(boundary, calendar.get(run.start))) return false;
|
|
55
|
+
return !isNumberName(sentence.text, run, sentence.tokens, sentence.span.start, context.sequence, context.topUnits);
|
|
38
56
|
};
|
|
39
57
|
|
|
58
|
+
const patternList = (doc: ProseDocument, id: string): string[] => (doc.lexicons[id] ?? []).map((entry) => entry.pattern);
|
|
59
|
+
const patternsOf = (doc: ProseDocument, id: string): ReadonlySet<string> => new Set(patternList(doc, id));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 鉤括弧で引いた題名や発言(「…ガイドライン ver. 1.1」)の中の境目。空け方は引いた元のもので、書き手の書き方ではない。
|
|
63
|
+
* 括弧は日本語の字でも英数字でもないので、境目はまるごと括弧の中か外にある。
|
|
64
|
+
*/
|
|
65
|
+
const isQuoted = (quoted: readonly Span[], boundary: Boundary): boolean => isWithinAny(quoted, { start: boundary.offset, end: boundary.offset });
|
|
66
|
+
|
|
40
67
|
/**
|
|
41
68
|
* 日本語と英字・数字のあいだを、空けるか詰めるか。文書の中で混ざっていたら、少ないほうを指摘する。
|
|
42
69
|
* どちらが正しいかは決めない。決めるのはチームで、chaff はそろっているかだけを見る。
|
|
43
70
|
*/
|
|
44
71
|
export const latinSpacing: Detector = (doc, options): Finding[] => {
|
|
45
72
|
// 覆った文(prose)で探す。コードの中の「1 件」は並びに入れない。
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
73
|
+
const context: NumberContext = {
|
|
74
|
+
sequence: sequenceLabelStarts(doc.prose ?? doc.source),
|
|
75
|
+
topUnits: patternsOf(doc, "prefecture-unit"),
|
|
76
|
+
calendar: { chained: patternList(doc, "date-time-unit"), positional: patternsOf(doc, "calendar-unit"), year: patternsOf(doc, "calendar-year-unit") },
|
|
77
|
+
};
|
|
78
|
+
const located: Located[] = doc.sentences.flatMap((sentence) => {
|
|
79
|
+
const quoted = quotedSpans(sentence.text);
|
|
80
|
+
const calendar = calendarPlaces(sentence, context.calendar);
|
|
81
|
+
return latinBoundaries(sentence.text, doc.source.slice(sentence.span.start, sentence.span.end))
|
|
82
|
+
.filter((boundary) => !isQuoted(quoted, boundary) && isCounted(sentence, boundary, context, calendar))
|
|
83
|
+
.map((boundary) => ({ sentence, ...boundary }));
|
|
84
|
+
});
|
|
52
85
|
return KINDS.flatMap((kind) => {
|
|
53
86
|
const ofKind = located.filter((entry) => entry.kind === kind);
|
|
54
87
|
const minority = minorityStyle(ofKind);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Detector, Finding, Lexicon, ProseDocument, Sentence } from "../plugin.ts";
|
|
1
|
+
import type { Detector, DetectorOptions, Finding, Lexicon, ProseDocument, Sentence } from "../plugin.ts";
|
|
2
2
|
import { entryIn } from "./lexicon-match.ts";
|
|
3
3
|
|
|
4
4
|
/** 冒頭は先頭 2 段落ぶん。「どの記事にも当てはまる書き出し」は冒頭にあるときだけ問題。 */
|
|
@@ -22,21 +22,32 @@ const hitsIn = (sentences: readonly Sentence[], lexicon: Lexicon): Hit[] =>
|
|
|
22
22
|
return matched === undefined ? [] : [{ sentence, matched: matched.pattern }];
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* これが L2 の全体像で、新しい言語は語彙表を書くだけで動く。
|
|
28
|
-
*/
|
|
29
|
-
export const phraseMatch: Detector = (doc, options): Finding[] => {
|
|
30
|
-
const lexicon = options.lexicon ?? [];
|
|
31
|
-
if (lexicon.length === 0) return [];
|
|
32
|
-
const hits = hitsIn(inScope(doc, options.where), lexicon);
|
|
33
|
-
if (hits.length < options.limit) return [];
|
|
34
|
-
return hits.map((hit) => ({
|
|
25
|
+
const findingsOf = (hits: readonly Hit[], limit: number): Finding[] =>
|
|
26
|
+
hits.map((hit) => ({
|
|
35
27
|
rule: "",
|
|
36
28
|
severity: "warning",
|
|
37
29
|
line: 0,
|
|
38
30
|
column: 0,
|
|
39
31
|
quote: hit.sentence.text.trim(),
|
|
40
|
-
values: { matched: hit.matched, count: hits.length, limit
|
|
32
|
+
values: { matched: hit.matched, count: hits.length, limit, offset: hit.sentence.span.start },
|
|
41
33
|
}));
|
|
34
|
+
|
|
35
|
+
const phraseHits = (doc: ProseDocument, options: DetectorOptions): Hit[] => {
|
|
36
|
+
const lexicon = options.lexicon ?? [];
|
|
37
|
+
return lexicon.length === 0 ? [] : hitsIn(inScope(doc, options.where), lexicon);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 語彙表との照合。detector は共通で、語彙だけが言語別に差し替わる。spec §11。
|
|
42
|
+
* これが L2 の全体像で、新しい言語は語彙表を書くだけで動く。limit は指摘に要る数で、1 なら 1 つ目から言う。
|
|
43
|
+
*/
|
|
44
|
+
export const phraseMatch: Detector = (doc, options): Finding[] => {
|
|
45
|
+
const hits = phraseHits(doc, options);
|
|
46
|
+
return hits.length < options.limit ? [] : findingsOf(hits, options.limit);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** 回数に上限がある語。limit は許す回数で、それを超えたときだけ言う。message の「N 回まで」と同じ向き。 */
|
|
50
|
+
export const phraseCount: Detector = (doc, options): Finding[] => {
|
|
51
|
+
const hits = phraseHits(doc, options);
|
|
52
|
+
return hits.length <= options.limit ? [] : findingsOf(hits, options.limit);
|
|
42
53
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 日付や時刻の数字の代わりに置く、同じ大文字の繰り返し(令和YY年MM月DD日 HH:MM、MM/DD/YYYY、YYYY-MM-DD)。
|
|
5
|
+
* 定型文の書式で、略語ではない。繰り返しの語が、日付・時刻の単位の語(年、月、日)の前にあるか、
|
|
6
|
+
* 区切り(/、:、-、.)で別の繰り返しの語か数字と繋がっているときだけ認める。単位の語は言語パッケージの語彙表から読む。
|
|
7
|
+
* AI時代の AI や CI/CD の CD は、同じ文字の繰り返しではないので略語のまま数える。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type Span = { readonly start: number; readonly end: number };
|
|
11
|
+
|
|
12
|
+
const REPEATED = /^(?<letter>[A-Z])\k<letter>+$/u;
|
|
13
|
+
const PART = String.raw`(?:[A-Z]{2,}|\d+)`;
|
|
14
|
+
const SEPARATOR = "[//::.-]";
|
|
15
|
+
const JOINED = new RegExp(String.raw`(?<![A-Za-z0-9_&])${PART}(?:${SEPARATOR}${PART})+(?![A-Za-z0-9_&])`, "gu");
|
|
16
|
+
const CAPITALS = /[A-Z]+/gu;
|
|
17
|
+
|
|
18
|
+
const isRepeated = (word: string): boolean => REPEATED.test(word);
|
|
19
|
+
|
|
20
|
+
const spanOf = (match: RegExpExecArray): Span => ({ start: match.index, end: match.index + match[0].length });
|
|
21
|
+
|
|
22
|
+
/** 区切りで繋いだ一続きのうち、大文字の部品がどれも同じ文字の繰り返しのもの(数字だけの 2025/01/31 は略語と関係がない)。 */
|
|
23
|
+
const joinedSpans = (text: string): Span[] =>
|
|
24
|
+
[...text.matchAll(JOINED)]
|
|
25
|
+
.filter((match) => {
|
|
26
|
+
const capitals = match[0].match(CAPITALS) ?? [];
|
|
27
|
+
return capitals.length > 0 && capitals.every(isRepeated);
|
|
28
|
+
})
|
|
29
|
+
.map(spanOf);
|
|
30
|
+
|
|
31
|
+
const beforeUnitSpans = (text: string, units: readonly string[]): Span[] => {
|
|
32
|
+
if (units.length === 0) return [];
|
|
33
|
+
const pattern = new RegExp(String.raw`(?<![A-Za-z0-9_&])[A-Z]{2,}(?=${units.map(escapeRegExp).join("|")})`, "gu");
|
|
34
|
+
return [...text.matchAll(pattern)].filter((match) => isRepeated(match[0])).map(spanOf);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** 文の中の、日付・時刻の書式に置いた大文字の範囲。units は日付・時刻の単位の語。 */
|
|
38
|
+
export const placeholderSpans = (text: string, units: readonly string[]): Span[] => [...joinedSpans(text), ...beforeUnitSpans(text, units)];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// 引用符の中身。英語の ’ と ' はアポストロフィ(organisation’s, don't)にも使われるので、字のあとに続くものは閉じとみなさない。
|
|
2
|
+
const QUOTATIONS: readonly RegExp[] = [
|
|
3
|
+
/“([^“”\n]*)”/gu,
|
|
4
|
+
/‘((?:[^‘’\n]|’(?=\p{L}))*)’(?!\p{L})/gu,
|
|
5
|
+
/"([^"\n]*)"/gu,
|
|
6
|
+
/(?<![\p{L}\p{N}])'((?:[^'\n]|'(?=\p{L}))*)'(?![\p{L}\p{N}])/gu,
|
|
7
|
+
/「([^「」\n]*)」/gu,
|
|
8
|
+
/『([^『』\n]*)』/gu,
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// 大文字小文字は畳まない。表記の手引きは大文字小文字だけ違う形も引用して退ける(Not “Open Source software”)。
|
|
12
|
+
const sameText = (left: string, right: string): boolean => left.trim() === right.trim();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 文から、見出しとは違う形を引用した部分を抜いた文字列。
|
|
16
|
+
* 「Not “datacentre”.」の “datacentre” は語の書き方として挙げたもの(言及)で、見出し「data centre」を繰り返してはいない。
|
|
17
|
+
* 見出しそのままの引用(「利用規約への同意」について説明します)は見出しを指しているので残す。
|
|
18
|
+
*/
|
|
19
|
+
export const withoutQuotedVariants = (sentence: string, heading: string): string =>
|
|
20
|
+
QUOTATIONS.reduce((text, quotation) => text.replace(quotation, (whole: string, inner: string) => (sameText(inner, heading) ? whole : " ")), sentence);
|
|
@@ -35,10 +35,9 @@ const runsOf = (sentences: readonly Sentence[], unit: LengthUnit, lists: readonl
|
|
|
35
35
|
// 短すぎる書き出しは「同じ」と言えない。単位ぶん揃って初めて連なりと見る。
|
|
36
36
|
const full = unit === "char" ? head.length === HEAD_CHARS : head.split(" ").length === HEAD_WORDS;
|
|
37
37
|
const joins = last !== undefined && last.head === head && last.container === container && !listBetween(last.members.at(-1), sentence, lists);
|
|
38
|
-
if (last !== undefined && joins && full) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return [...acc, { head, container, members: [sentence] }];
|
|
38
|
+
if (last !== undefined && joins && full) acc[acc.length - 1] = { head, container, members: [...last.members, sentence] };
|
|
39
|
+
else acc.push({ head, container, members: [sentence] });
|
|
40
|
+
return acc;
|
|
42
41
|
}, []);
|
|
43
42
|
|
|
44
43
|
export const repeatedHead: Detector = (doc, options): Finding[] =>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* - で区切った文書番号のすぐ前の大文字の語(NIST SP 800-61 の SP、NSF 19-582、IEC 19757-2)。番号の一部で、略語ではない。
|
|
3
|
+
* 空白で離して書くほかは、- で繋いだ識別子(AC-2)と同じ。番号の直前の 1 語だけで、その前の語(NIST)は略語のまま数える。
|
|
4
|
+
* 区切りの無い番号(RFC 9110)、. で区切る版(SDK 3.1)、年の範囲(FY 2024-25)の前の語も略語のまま数える。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type Span = { readonly start: number; readonly end: number };
|
|
8
|
+
|
|
9
|
+
const YEAR_RANGE = String.raw`(?:19|20)\d\d-\d`;
|
|
10
|
+
const SERIES_LABEL = new RegExp(String.raw`(?<![A-Za-z0-9_&.-])[A-Z]+ (?!${YEAR_RANGE})(?<number>\d+(?:-\d+)+)[A-Za-z0-9]*(?![A-Za-z0-9_&-])`, "gu");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 文書番号は長い通し番号を持つ(800-61、19757-2)。2 桁ずつの番号(BOD 25-01)は範囲・得点・日付(SRE 1-2、NFL 3-1、
|
|
14
|
+
* 01-02)と見分けられないので、その前の語は略語のまま数える。同じ桁数で小さいほうから書いた 2 つの数(SLO 100-200、
|
|
15
|
+
* SLA 500-599)は範囲。
|
|
16
|
+
*/
|
|
17
|
+
const SERIAL_DIGITS = 3;
|
|
18
|
+
|
|
19
|
+
const isRange = (groups: readonly string[]): boolean => {
|
|
20
|
+
const [low, high, ...rest] = groups;
|
|
21
|
+
return rest.length === 0 && low !== undefined && high !== undefined && low.length === high.length && Number(low) < Number(high);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isDocumentNumber = (number: string): boolean => {
|
|
25
|
+
const groups = number.split("-");
|
|
26
|
+
return groups.some((group) => group.length >= SERIAL_DIGITS) && !isRange(groups);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** 文の中の、番号と、その前の大文字の語の範囲。 */
|
|
30
|
+
export const seriesLabelSpans = (text: string): Span[] =>
|
|
31
|
+
[...text.matchAll(SERIES_LABEL)]
|
|
32
|
+
.filter((match) => isDocumentNumber(match.groups?.["number"] ?? ""))
|
|
33
|
+
.map((match) => ({ start: match.index, end: match.index + match[0].length }));
|
package/src/detectors/signals.ts
CHANGED
|
@@ -3,8 +3,13 @@ import { wordsOf } from "./structure.ts";
|
|
|
3
3
|
import { compacted, placeOf } from "./gram-place.ts";
|
|
4
4
|
import { notAcronymSpansOf, type NotAcronymSpans } from "./acronym-context.ts";
|
|
5
5
|
import { expansionAt, type ExpandedAt } from "./acronym-expansion.ts";
|
|
6
|
+
import { isExplained } from "./acronym-compound.ts";
|
|
6
7
|
import { conjugatedForms } from "./conjugated-form.ts";
|
|
7
|
-
import
|
|
8
|
+
import { evidenceSpans, hasNumeral, startsWithin } from "./concrete-evidence.ts";
|
|
9
|
+
import { letteredIndexEntries } from "./lettered-index.ts";
|
|
10
|
+
import { hasPredicateIn } from "./gram-predicate.ts";
|
|
11
|
+
import { nameSpans, touchesAny } from "../team-names.ts";
|
|
12
|
+
import type { Detector, Finding, ProseDocument, Section, Sentence } from "../plugin.ts";
|
|
8
13
|
|
|
9
14
|
const PER = 1000;
|
|
10
15
|
|
|
@@ -59,9 +64,12 @@ export const emojiDensity: Detector = (doc, options): Finding[] => {
|
|
|
59
64
|
*/
|
|
60
65
|
const GRAM = { char: 8, word: 20 };
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
/** 並べた名前にかかる語句は数えない。名前が繰り返されれば、その前後(「the …」)も一緒に繰り返されるのは当たり前。 */
|
|
68
|
+
const countGrams = (text: string, width: number, names: readonly string[]): Map<string, number> => {
|
|
63
69
|
const counts = new Map<string, number>();
|
|
70
|
+
const named = nameSpans(text, names);
|
|
64
71
|
Array.from({ length: Math.max(0, text.length - width + 1) }).forEach((_, index) => {
|
|
72
|
+
if (touchesAny(named, index, index + width)) return;
|
|
65
73
|
const gram = text.slice(index, index + width);
|
|
66
74
|
counts.set(gram, (counts.get(gram) ?? 0) + 1);
|
|
67
75
|
});
|
|
@@ -90,7 +98,8 @@ const gramsOf = (doc: ProseDocument): Map<string, number> => {
|
|
|
90
98
|
const counts = new Map<string, number>();
|
|
91
99
|
doc.sentences.forEach((sentence) => {
|
|
92
100
|
const text = doc.lengthUnit === "char" ? proseText(sentence).replace(/\s+/gu, "") : proseText(sentence);
|
|
93
|
-
|
|
101
|
+
const names = doc.lengthUnit === "char" ? (doc.names ?? []).map((name) => name.replace(/\s+/gu, "")) : (doc.names ?? []);
|
|
102
|
+
countGrams(text, GRAM[doc.lengthUnit], names).forEach((count, gram) => counts.set(gram, (counts.get(gram) ?? 0) + count));
|
|
94
103
|
});
|
|
95
104
|
return counts;
|
|
96
105
|
};
|
|
@@ -98,29 +107,15 @@ const gramsOf = (doc: ProseDocument): Map<string, number> => {
|
|
|
98
107
|
/** 数えたときと同じ形の文。char 単位では空白を詰めてから数えている。 */
|
|
99
108
|
const gramText = (sentence: Sentence, unit: ProseDocument["lengthUnit"]): string => compacted(sentence.text, unit).text;
|
|
100
109
|
|
|
101
|
-
/** 動詞と助動詞。言い回し(ることができます、it is important to)にはこれが入り、主題の名前(state and local tax)には入らない。 */
|
|
102
|
-
const PREDICATE_POS = new Set(["VERB", "AUX"]);
|
|
103
|
-
|
|
104
|
-
const NOUNS = new Set(["NOUN", "PROPN", "ADJ"]);
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 英語で名詞のすぐ前の動詞は、名詞を飾る語(形容詞を挟んでも同じ)(the upcoming fiscal year、the Disclosing Party、the borrow checker)で、
|
|
108
|
-
* 述語ではない。日本語の名詞の前の動詞(〜にあるコンポーネント)は言い回しの一部になるので、word 単位だけで見る。
|
|
109
|
-
*/
|
|
110
|
-
const modifiesNoun = (tokens: readonly Token[], index: number, unit: ProseDocument["lengthUnit"]): boolean =>
|
|
111
|
-
unit === "word" && tokens[index]?.pos === "VERB" && NOUNS.has(tokens[index + 1]?.pos ?? "");
|
|
112
|
-
|
|
113
110
|
/**
|
|
114
|
-
*
|
|
111
|
+
* 語句が文の中で占める範囲に、述語になる動詞か助動詞があるか(gram-predicate.ts)。
|
|
115
112
|
* 品詞が無ければ見分けられないので、これまでどおり言い回しとして数える。
|
|
116
113
|
*/
|
|
117
114
|
const hasPredicate = (gram: string, sentence: Sentence, unit: ProseDocument["lengthUnit"]): boolean => {
|
|
118
115
|
if (sentence.tokens === undefined) return true;
|
|
119
116
|
const place = placeOf(compacted(sentence.text, unit), gram);
|
|
120
117
|
if (place === undefined) return true;
|
|
121
|
-
|
|
122
|
-
const tokens = sentence.tokens;
|
|
123
|
-
return tokens.some((token, index) => PREDICATE_POS.has(token.pos) && token.span.start < end && start < token.span.end && !modifiesNoun(tokens, index, unit));
|
|
118
|
+
return hasPredicateIn(sentence.tokens, { start: sentence.span.start + place.start, end: sentence.span.start + place.end }, unit);
|
|
124
119
|
};
|
|
125
120
|
|
|
126
121
|
export const ngramRepetition: Detector = (doc, options): Finding[] => {
|
|
@@ -128,7 +123,7 @@ export const ngramRepetition: Detector = (doc, options): Finding[] => {
|
|
|
128
123
|
const sentenceWith = (gram: string): Sentence | undefined => doc.sentences.find((sentence) => gramText(sentence, doc.lengthUnit).includes(gram));
|
|
129
124
|
const worst = [...gramsOf(doc).entries()]
|
|
130
125
|
.filter(([gram, count]) => count > options.limit && isPhrasing(gram, doc.lengthUnit))
|
|
131
|
-
.
|
|
126
|
+
.toSorted(([, left], [, right]) => right - left)
|
|
132
127
|
.map(([gram, count]) => ({ gram, count, at: sentenceWith(gram) }))
|
|
133
128
|
.find(({ gram, at }) => at === undefined || hasPredicate(gram, at, doc.lengthUnit));
|
|
134
129
|
if (worst === undefined) return [];
|
|
@@ -150,10 +145,14 @@ export const ngramRepetition: Detector = (doc, options): Finding[] => {
|
|
|
150
145
|
* 展開は「略語の直前か直後の括弧」か「括弧の中の略語」で書かれる。
|
|
151
146
|
*
|
|
152
147
|
* & で繋いだ大文字は 1 語として読む(ATT&CK、M&IE)。割ると CK や IE が別の略語に見える。
|
|
148
|
+
* - で繋いだ略語どうしも 1 語(RT-PCR)。割ると RT が別の略語に見える。数字を含むもの(COVID-19)は識別子として外す。
|
|
153
149
|
*/
|
|
154
150
|
const EDGE_BEFORE = String.raw`(?<![A-Za-z0-9_&])`;
|
|
155
151
|
const EDGE_AFTER = String.raw`(?![A-Za-z0-9_&])`;
|
|
156
|
-
|
|
152
|
+
/** 略語として読む大文字の長さの上限。これより長い大文字の語(BILLING)は、普通の語を大文字で書いたもの。 */
|
|
153
|
+
const ACRONYM_MAX = 6;
|
|
154
|
+
const ACRONYM_PART = String.raw`(?:[A-Z]+(?:&[A-Z]+)+|[A-Z]{2,${ACRONYM_MAX}})`;
|
|
155
|
+
const ACRONYM = new RegExp(String.raw`${EDGE_BEFORE}${ACRONYM_PART}(?:-${ACRONYM_PART})*${EDGE_AFTER}`, "gu");
|
|
157
156
|
|
|
158
157
|
/**
|
|
159
158
|
* 大文字で書かれていても略語ではないもの。
|
|
@@ -162,9 +161,17 @@ const ACRONYM = new RegExp(String.raw`${EDGE_BEFORE}(?:[A-Z]+(?:&[A-Z]+)+|[A-Z]{
|
|
|
162
161
|
* 強調は読点や括弧をまたいで続くので(THE AUTHOR, THE COMPANY (IF ANY))、そういう続きを含む
|
|
163
162
|
* 「大文字以外の文字を含まない一続き」を丸ごと強調と見なす。小文字の語が挟まれば切れるので、
|
|
164
163
|
* 「SRE, SLO, MTTR」のような略語の並びは残る。引用符に包まれた大文字の句(“AS IS”)も強調。
|
|
164
|
+
* 2 語でも、片方が略語にしては長い語なら強調(BILLING CODE)。略語 2 つの並び(NIST SP)は残る。
|
|
165
165
|
*/
|
|
166
166
|
const CAPS_WORD = String.raw`[A-Z]+(?:&[A-Z]+)*`;
|
|
167
|
-
const
|
|
167
|
+
const LONG_CAPS_WORD = String.raw`[A-Z]{${ACRONYM_MAX + 1},}`;
|
|
168
|
+
const CAPS_GAP = String.raw`[\s"“”'‘’]+`;
|
|
169
|
+
const SHOUTED_RUN = new RegExp(String.raw`${EDGE_BEFORE}${CAPS_WORD}(?:${CAPS_GAP}${CAPS_WORD}){2,}${EDGE_AFTER}`, "u");
|
|
170
|
+
/** 2 語の強調は、その 2 語だけを外す。続きまで外すと、区切りの後ろの略語(NEW GUIDELINES: SRE)まで消える。 */
|
|
171
|
+
const SHOUTED_PAIR = new RegExp(
|
|
172
|
+
String.raw`${EDGE_BEFORE}(?:${LONG_CAPS_WORD}${CAPS_GAP}${CAPS_WORD}|${CAPS_WORD}${CAPS_GAP}${LONG_CAPS_WORD})${EDGE_AFTER}`,
|
|
173
|
+
"gu",
|
|
174
|
+
);
|
|
168
175
|
const UNCASED_STRETCH = /(?:[A-Z]|\P{L})+/gu;
|
|
169
176
|
const QUOTED_CAPS = new RegExp(String.raw`["“'‘]${CAPS_WORD}(?:\s+${CAPS_WORD})+["”'’]`, "gu");
|
|
170
177
|
|
|
@@ -187,15 +194,16 @@ const spanOf = (match: RegExpExecArray): Span => ({ start: match.index, end: mat
|
|
|
187
194
|
const spansOf = (text: string, notation: NotAcronymSpans): Span[] => [
|
|
188
195
|
...[...text.matchAll(UNCASED_STRETCH)].filter((match) => SHOUTED_RUN.test(match[0])).map(spanOf),
|
|
189
196
|
...[...text.matchAll(IDENTIFIER)].filter((match) => /\d/u.test(match[0])).map(spanOf),
|
|
190
|
-
...[QUOTED_CAPS, REQUIREMENT_WORD, LICENCE].flatMap((pattern) => [...text.matchAll(pattern)].map(spanOf)),
|
|
197
|
+
...[SHOUTED_PAIR, QUOTED_CAPS, REQUIREMENT_WORD, LICENCE].flatMap((pattern) => [...text.matchAll(pattern)].map(spanOf)),
|
|
191
198
|
...notation(text),
|
|
192
199
|
];
|
|
193
200
|
|
|
194
201
|
type AcronymHit = { readonly word: string; readonly hit: Hit };
|
|
195
202
|
|
|
203
|
+
/** チームが並べた名前の中の略語(NTT Docomo の NTT)は、名前の一部であって説明を待つ略語ではない。 */
|
|
196
204
|
const acronymsOf = (doc: ProseDocument, notation: NotAcronymSpans): AcronymHit[] =>
|
|
197
205
|
doc.sentences.flatMap((sentence) => {
|
|
198
|
-
const excluded = spansOf(sentence.text, notation);
|
|
206
|
+
const excluded = [...spansOf(sentence.text, notation), ...nameSpans(sentence.text, doc.names ?? [])];
|
|
199
207
|
return [...sentence.text.matchAll(ACRONYM)]
|
|
200
208
|
.filter((match) => !excluded.some((span) => span.start <= match.index && match.index + match[0].length <= span.end))
|
|
201
209
|
.map((match) => ({ word: match[0], hit: { sentence, offset: sentence.span.start + match.index } }));
|
|
@@ -218,6 +226,8 @@ const notationOf = (doc: ProseDocument): NotAcronymSpans =>
|
|
|
218
226
|
usStates: patternsOf(doc, "us-state-code"),
|
|
219
227
|
emphasis: patternsOf(doc, "emphasis-word"),
|
|
220
228
|
divisions: patternsOf(doc, "numbered-division"),
|
|
229
|
+
honorifics: patternsOf(doc, "honorific"),
|
|
230
|
+
dateTimeUnits: patternsOf(doc, "date-time-unit"),
|
|
221
231
|
});
|
|
222
232
|
|
|
223
233
|
/** 定義の語は、語彙表の形(という)と、この文書で活用して書かれた形(といいます)の両方で照らす。 */
|
|
@@ -228,13 +238,16 @@ const definitionVerbsOf = (doc: ProseDocument): string[] => {
|
|
|
228
238
|
|
|
229
239
|
export const undefinedAcronym: Detector = (doc, options): Finding[] => {
|
|
230
240
|
const body = bodyOf(doc);
|
|
231
|
-
|
|
241
|
+
// HTTP のメソッド名(GET)は略語ではないので、通じる略語と同じく展開を求めない。
|
|
242
|
+
// 並べた名前(JAXA)も、繋いだ略語(JAXA-ISAS)の片割れとして説明済みに数える。
|
|
243
|
+
const common = new Set([...(options.lexicon ?? []).map((entry) => entry.pattern), ...patternsOf(doc, "http-method"), ...(doc.names ?? [])]);
|
|
232
244
|
const seen = new Map<string, Hit>();
|
|
233
245
|
acronymsOf(doc, notationOf(doc)).forEach(({ word, hit }) => {
|
|
234
|
-
if (!
|
|
246
|
+
if (!seen.has(word)) seen.set(word, hit);
|
|
235
247
|
});
|
|
236
248
|
const expandedAt = expansionAt({ markers: patternsOf(doc, "definition-marker"), verbs: definitionVerbsOf(doc) });
|
|
237
|
-
const
|
|
249
|
+
const explainedAlone = (word: string): boolean => common.has(word) || isExpanded(body, word, expandedAt);
|
|
250
|
+
const bare = [...seen.entries()].filter(([acronym]) => !isExplained(acronym, explainedAlone));
|
|
238
251
|
if (bare.length < options.limit) return [];
|
|
239
252
|
return bare.map(([acronym, hit]) => ({
|
|
240
253
|
rule: "undefined-acronym",
|
|
@@ -265,16 +278,17 @@ const openingOf = (sentence: Sentence | undefined, unit: ProseDocument["lengthUn
|
|
|
265
278
|
return pieces.length <= OPENING[unit] ? pieces.join(joiner) : `${pieces.slice(0, OPENING[unit]).join(joiner)}…`;
|
|
266
279
|
};
|
|
267
280
|
|
|
268
|
-
|
|
269
|
-
* 形態素解析が数と読んだ語(UD の NumType=Card)。漢数字の「二割」「十五分」も、算用数字と同じ具体的な数。
|
|
270
|
-
* 言い回しの「二人三脚」「一人ひとり」は、辞書が一語として持つので数にならない。品詞が無ければ、数字だけを見る。
|
|
271
|
-
*/
|
|
272
|
-
const hasNumeral = (section: Section): boolean =>
|
|
273
|
-
section.sentences.some((sentence) => (sentence.tokens ?? []).some((token) => token.features?.["NumType"] === "Card"));
|
|
281
|
+
const hasNumeralIn = (section: Section): boolean => section.sentences.some((sentence) => hasNumeral(sentence.tokens ?? []));
|
|
274
282
|
|
|
275
283
|
export const concreteEvidence: Detector = (doc, options): Finding[] => {
|
|
276
|
-
const
|
|
277
|
-
const
|
|
284
|
+
const entries = letteredIndexEntries(doc.sections);
|
|
285
|
+
const sections = doc.sections.filter((section) => section.sentences.length >= 3 && !entries.has(section));
|
|
286
|
+
const bare = sections.filter(
|
|
287
|
+
(section) => !CONCRETE.test(doc.source.slice(section.span.start, section.span.end)) && !hasNumeralIn(section) && !startsWithin(section.span, doc.links),
|
|
288
|
+
);
|
|
289
|
+
// 構造の木は作るのに手間がかかる。指摘に届かない文書では作らない。
|
|
290
|
+
const nodes = bare.length < options.limit ? [] : evidenceSpans(doc.structure);
|
|
291
|
+
const empty = bare.filter((section) => !startsWithin(section.span, nodes));
|
|
278
292
|
if (empty.length < options.limit) return [];
|
|
279
293
|
return empty.map((section) => ({
|
|
280
294
|
rule: "concrete-evidence-density",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Lexicon, Sentence, Span, Token } from "../plugin.ts";
|
|
2
|
+
import { entryRanges, type TokenRange } from "./lexicon-match.ts";
|
|
3
|
+
import { isWithinAny, quotedIn, QUOTATION_MARKS } from "../quoted-span.ts";
|
|
4
|
+
|
|
5
|
+
/** 重ねたと言うのに要る数。1 つなら慎重さで、2 つ目からは読み手が何を言い切ったのか分からなくなる。 */
|
|
6
|
+
export const STACKED_AT = 2;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 語彙表 3 つ。hedges は逃げの表現そのもの、frames はそれだけでは逃げていない包む言い方(「という状況です」、may)、
|
|
10
|
+
* scope は hedges のうち、どれだけ確かかではなくどこで成り立つかを言うもの(「場合がある」、in some cases)。
|
|
11
|
+
*/
|
|
12
|
+
export type HedgeWords = { readonly hedges: Lexicon; readonly frames: Lexicon; readonly scope: Lexicon };
|
|
13
|
+
|
|
14
|
+
type Device = { readonly range: TokenRange; readonly hedge: boolean };
|
|
15
|
+
|
|
16
|
+
export type StackedHedge = { readonly sentence: Sentence; readonly spans: readonly Span[] };
|
|
17
|
+
|
|
18
|
+
const rangesOf = (sentence: Sentence, lexicon: Lexicon): TokenRange[] => lexicon.flatMap((entry) => entryRanges(sentence, entry));
|
|
19
|
+
|
|
20
|
+
const contains = (outer: TokenRange, inner: TokenRange): boolean => outer.start <= inner.start && inner.end <= outer.end;
|
|
21
|
+
|
|
22
|
+
const sameRange = (left: TokenRange, right: TokenRange): boolean => left.start === right.start && left.end === right.end;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 同じ語に当たった見出しは 1 つに数える。「could」は「it could be」の一部。
|
|
26
|
+
* 同じ範囲に hedge と frame が両方当たったら hedge として残す(devices は hedge が先に並んでいる)。
|
|
27
|
+
*/
|
|
28
|
+
const outermost = (devices: readonly Device[]): Device[] =>
|
|
29
|
+
devices.filter(
|
|
30
|
+
(device, index) =>
|
|
31
|
+
!devices.some((other, at) => at !== index && contains(other.range, device.range) && (!sameRange(other.range, device.range) || at < index)),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** 語の並びの、文書全体の座標での範囲。 */
|
|
35
|
+
const spanOf = (tokens: readonly Token[], range: TokenRange): Span | undefined => {
|
|
36
|
+
const first = tokens[range.start];
|
|
37
|
+
const last = tokens[range.end - 1];
|
|
38
|
+
return first === undefined || last === undefined ? undefined : { start: first.span.start, end: last.span.end };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** 句読点と等位接続詞(and、but、及び)は節を切る。別の節の逃げは、同じ主張を二重にぼかしていない。 */
|
|
42
|
+
const CLAUSE_BREAK = new Set(["PUNCT", "CCONJ"]);
|
|
43
|
+
|
|
44
|
+
/** 語ごとの、文の頭から数えた節の番号。 */
|
|
45
|
+
const clauseOf = (tokens: readonly Token[]): number[] =>
|
|
46
|
+
tokens.reduce<number[]>((clauses, token) => [...clauses, (clauses.at(-1) ?? 0) + (CLAUSE_BREAK.has(token.pos) ? 1 : 0)], []);
|
|
47
|
+
|
|
48
|
+
const PREDICATE = new Set(["VERB", "AUX"]);
|
|
49
|
+
|
|
50
|
+
/** 見せる範囲。活用する語で終わる見出し(と考えられる)は、後ろの助動詞(ます)まで含めて書いたとおりに見せる。 */
|
|
51
|
+
const shownEnd = (tokens: readonly Token[], range: TokenRange): number => {
|
|
52
|
+
const last = tokens[range.end - 1];
|
|
53
|
+
if (last === undefined || !PREDICATE.has(last.pos)) return range.end;
|
|
54
|
+
const stop = tokens.findIndex((token, at) => at >= range.end && token.pos !== "AUX");
|
|
55
|
+
return stop === -1 ? tokens.length : stop;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** 書き手の言葉で、どこで成り立つかを言う語ではない見出し。引用(「」『』“” ")の中は話し手の言葉。 */
|
|
59
|
+
const devicesIn = (sentence: Sentence, words: HedgeWords): Device[] => {
|
|
60
|
+
const tokens = sentence.tokens ?? [];
|
|
61
|
+
const quoted = quotedIn(sentence, QUOTATION_MARKS);
|
|
62
|
+
const scope = rangesOf(sentence, words.scope);
|
|
63
|
+
const hedges = rangesOf(sentence, words.hedges).filter((range) => !scope.some((outer) => contains(outer, range)));
|
|
64
|
+
const devices = [...hedges.map((range) => ({ range, hedge: true })), ...rangesOf(sentence, words.frames).map((range) => ({ range, hedge: false }))];
|
|
65
|
+
return outermost(devices).filter((device) => {
|
|
66
|
+
const span = spanOf(tokens, device.range);
|
|
67
|
+
return span !== undefined && !isWithinAny(quoted, span);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** 1 つの節に 2 つ以上あり、そのうち 1 つ以上が逃げの表現そのもの。包む言い方だけ(「という状況です」)は逃げていない。 */
|
|
72
|
+
const stackedClause = (devices: readonly Device[], clauses: readonly number[]): Device[] => {
|
|
73
|
+
const clauseAt = (device: Device): number => clauses[device.range.start] ?? 0;
|
|
74
|
+
const groups = [...new Set(devices.map(clauseAt))].map((clause) => devices.filter((device) => clauseAt(device) === clause));
|
|
75
|
+
return groups.find((group) => group.length >= STACKED_AT && group.some((device) => device.hedge)) ?? [];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 1 つの文の 1 つの節に、逃げの表現を重ねているか。business-blog-harness-spec §7.2。見せる範囲を文の中の順で返す。重ねていなければ空。
|
|
80
|
+
* 品詞が無ければ語の位置が分からないので何も言わない。
|
|
81
|
+
*/
|
|
82
|
+
export const stackedHedgeIn = (sentence: Sentence, words: HedgeWords): Span[] => {
|
|
83
|
+
const tokens = sentence.tokens ?? [];
|
|
84
|
+
return stackedClause(devicesIn(sentence, words), clauseOf(tokens))
|
|
85
|
+
.toSorted((left, right) => left.range.start - right.range.start)
|
|
86
|
+
.flatMap((device) => spanOf(tokens, { start: device.range.start, end: shownEnd(tokens, device.range) }) ?? []);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const stackedHedges = (sentences: readonly Sentence[], words: HedgeWords): StackedHedge[] =>
|
|
90
|
+
sentences.flatMap((sentence) => {
|
|
91
|
+
const spans = stackedHedgeIn(sentence, words);
|
|
92
|
+
return spans.length === 0 ? [] : [{ sentence, spans }];
|
|
93
|
+
});
|
|
@@ -17,11 +17,11 @@ const quoteAt = (source: string, offset: number): string => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const findingsOf =
|
|
20
|
-
(rule: string, issuesOf: (tree: NonNullable<ProseDocument["structure"]
|
|
20
|
+
(rule: string, issuesOf: (tree: NonNullable<ProseDocument["structure"]>, source: string) => StructureIssue[]): Detector =>
|
|
21
21
|
(doc): Finding[] =>
|
|
22
22
|
doc.structure === undefined
|
|
23
23
|
? []
|
|
24
|
-
: issuesOf(doc.structure).map((issue) => ({
|
|
24
|
+
: issuesOf(doc.structure, doc.source).map((issue) => ({
|
|
25
25
|
rule,
|
|
26
26
|
severity: "error",
|
|
27
27
|
line: 0,
|