chaffjs 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -56
- package/bin/chaff.js +17 -2
- package/dist/acronym-expansions.d.ts +26 -0
- package/dist/acronym-expansions.d.ts.map +1 -0
- package/dist/acronym-expansions.js +88 -0
- package/dist/acronym-expansions.js.map +1 -0
- package/dist/announced-count.d.ts.map +1 -1
- package/dist/announced-count.js +44 -10
- package/dist/announced-count.js.map +1 -1
- package/dist/announced-list.d.ts +30 -0
- package/dist/announced-list.d.ts.map +1 -0
- package/dist/announced-list.js +95 -0
- package/dist/announced-list.js.map +1 -0
- package/dist/api.d.ts +168 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +10 -0
- package/dist/api.js.map +1 -0
- package/dist/cli-text.d.ts +0 -1
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +6 -14
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +58 -62
- package/dist/cli.js.map +1 -1
- package/dist/closing-paragraph.d.ts +7 -0
- package/dist/closing-paragraph.d.ts.map +1 -0
- package/dist/closing-paragraph.js +22 -0
- package/dist/closing-paragraph.js.map +1 -0
- package/dist/commands/compare.d.ts +8 -1
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +9 -17
- package/dist/commands/compare.js.map +1 -1
- package/dist/commands/document-commands.d.ts +9 -0
- package/dist/commands/document-commands.d.ts.map +1 -0
- package/dist/commands/document-commands.js +17 -0
- package/dist/commands/document-commands.js.map +1 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +3 -2
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/facts.d.ts +8 -0
- package/dist/commands/facts.d.ts.map +1 -0
- package/dist/commands/facts.js +30 -0
- package/dist/commands/facts.js.map +1 -0
- package/dist/commands/feedback.d.ts +10 -0
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +25 -1
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/fix-plan.d.ts +13 -0
- package/dist/commands/fix-plan.d.ts.map +1 -0
- package/dist/commands/fix-plan.js +44 -0
- package/dist/commands/fix-plan.js.map +1 -0
- package/dist/commands/outline.d.ts +8 -0
- package/dist/commands/outline.d.ts.map +1 -0
- package/dist/commands/outline.js +43 -0
- package/dist/commands/outline.js.map +1 -0
- package/dist/commands/read-document.d.ts +14 -0
- package/dist/commands/read-document.d.ts.map +1 -0
- package/dist/commands/read-document.js +21 -0
- package/dist/commands/read-document.js.map +1 -0
- package/dist/commands/suppressions.d.ts +7 -0
- package/dist/commands/suppressions.d.ts.map +1 -0
- package/dist/commands/suppressions.js +9 -0
- package/dist/commands/suppressions.js.map +1 -0
- package/dist/commands/test.js +1 -1
- package/dist/commands/test.js.map +1 -1
- package/dist/compare/clock-time.d.ts.map +1 -1
- package/dist/compare/clock-time.js +5 -2
- package/dist/compare/clock-time.js.map +1 -1
- package/dist/compare/distinct.d.ts +9 -0
- package/dist/compare/distinct.d.ts.map +1 -0
- package/dist/compare/distinct.js +12 -0
- package/dist/compare/distinct.js.map +1 -0
- package/dist/compare/extract.d.ts.map +1 -1
- package/dist/compare/extract.js +7 -5
- package/dist/compare/extract.js.map +1 -1
- package/dist/compare/fact-key.d.ts +17 -0
- package/dist/compare/fact-key.d.ts.map +1 -0
- package/dist/compare/fact-key.js +25 -0
- package/dist/compare/fact-key.js.map +1 -0
- package/dist/compare/fact-text.d.ts +5 -0
- package/dist/compare/fact-text.d.ts.map +1 -1
- package/dist/compare/fact-text.js +16 -2
- package/dist/compare/fact-text.js.map +1 -1
- package/dist/compare/facts-render.d.ts +17 -0
- package/dist/compare/facts-render.d.ts.map +1 -0
- package/dist/compare/facts-render.js +43 -0
- package/dist/compare/facts-render.js.map +1 -0
- package/dist/compare/facts-text.d.ts +15 -0
- package/dist/compare/facts-text.d.ts.map +1 -0
- package/dist/compare/facts-text.js +27 -0
- package/dist/compare/facts-text.js.map +1 -0
- package/dist/compare/match.d.ts +1 -0
- package/dist/compare/match.d.ts.map +1 -1
- package/dist/compare/match.js +1 -1
- package/dist/compare/match.js.map +1 -1
- package/dist/compare/outcome.d.ts +4 -1
- package/dist/compare/outcome.d.ts.map +1 -1
- package/dist/compare/outcome.js +10 -4
- package/dist/compare/outcome.js.map +1 -1
- package/dist/compare/proper-nouns.d.ts +3 -3
- package/dist/compare/proper-nouns.d.ts.map +1 -1
- package/dist/compare/proper-nouns.js +2 -3
- package/dist/compare/proper-nouns.js.map +1 -1
- package/dist/compare/render.d.ts +4 -0
- package/dist/compare/render.d.ts.map +1 -1
- package/dist/compare/render.js +2 -2
- package/dist/compare/render.js.map +1 -1
- package/dist/compare/text-atoms.d.ts +5 -2
- package/dist/compare/text-atoms.d.ts.map +1 -1
- package/dist/compare/text-atoms.js +7 -5
- package/dist/compare/text-atoms.js.map +1 -1
- package/dist/compare/text.d.ts.map +1 -1
- package/dist/compare/text.js +2 -2
- package/dist/compare/text.js.map +1 -1
- package/dist/config/load.d.ts +8 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +1 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/option-problems.d.ts +3 -2
- package/dist/config/option-problems.d.ts.map +1 -1
- package/dist/config/option-problems.js +4 -3
- package/dist/config/option-problems.js.map +1 -1
- package/dist/config/style.d.ts +5 -0
- package/dist/config/style.d.ts.map +1 -1
- package/dist/config/style.js +13 -1
- package/dist/config/style.js.map +1 -1
- package/dist/custom/load.d.ts +6 -3
- package/dist/custom/load.d.ts.map +1 -1
- package/dist/custom/load.js +10 -2
- package/dist/custom/load.js.map +1 -1
- package/dist/custom/module-path.d.ts +11 -0
- package/dist/custom/module-path.d.ts.map +1 -0
- package/dist/custom/module-path.js +15 -0
- package/dist/custom/module-path.js.map +1 -0
- package/dist/custom/parse.d.ts +17 -3
- package/dist/custom/parse.d.ts.map +1 -1
- package/dist/custom/parse.js +57 -14
- package/dist/custom/parse.js.map +1 -1
- package/dist/custom/problems.d.ts +4 -1
- package/dist/custom/problems.d.ts.map +1 -1
- package/dist/custom/problems.js +56 -39
- package/dist/custom/problems.js.map +1 -1
- package/dist/detect-language.js +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +9 -3
- package/dist/detect.js.map +1 -1
- package/dist/detectors/ai-phrasing.d.ts +4 -1
- package/dist/detectors/ai-phrasing.d.ts.map +1 -1
- package/dist/detectors/ai-phrasing.js +6 -2
- package/dist/detectors/ai-phrasing.js.map +1 -1
- package/dist/detectors/bold-label.d.ts +10 -0
- package/dist/detectors/bold-label.d.ts.map +1 -0
- package/dist/detectors/bold-label.js +44 -0
- package/dist/detectors/bold-label.js.map +1 -0
- package/dist/detectors/dangling-figure.d.ts +5 -2
- package/dist/detectors/dangling-figure.d.ts.map +1 -1
- package/dist/detectors/dangling-figure.js +5 -3
- package/dist/detectors/dangling-figure.js.map +1 -1
- package/dist/detectors/definition-use.d.ts +8 -0
- package/dist/detectors/definition-use.d.ts.map +1 -0
- package/dist/detectors/definition-use.js +32 -0
- package/dist/detectors/definition-use.js.map +1 -0
- package/dist/detectors/doubled-punctuation.d.ts.map +1 -1
- package/dist/detectors/doubled-punctuation.js +4 -2
- package/dist/detectors/doubled-punctuation.js.map +1 -1
- package/dist/detectors/duplicate-heading.d.ts +14 -0
- package/dist/detectors/duplicate-heading.d.ts.map +1 -0
- package/dist/detectors/duplicate-heading.js +39 -0
- package/dist/detectors/duplicate-heading.js.map +1 -0
- package/dist/detectors/empty-section.d.ts +8 -0
- package/dist/detectors/empty-section.d.ts.map +1 -0
- package/dist/detectors/empty-section.js +16 -0
- package/dist/detectors/empty-section.js.map +1 -0
- package/dist/detectors/fullwidth-alnum.d.ts +24 -0
- package/dist/detectors/fullwidth-alnum.d.ts.map +1 -0
- package/dist/detectors/fullwidth-alnum.js +88 -0
- package/dist/detectors/fullwidth-alnum.js.map +1 -0
- package/dist/detectors/hankaku-kana.d.ts +9 -0
- package/dist/detectors/hankaku-kana.d.ts.map +1 -0
- package/dist/detectors/hankaku-kana.js +27 -0
- package/dist/detectors/hankaku-kana.js.map +1 -0
- package/dist/detectors/heading-code-words.js +2 -2
- package/dist/detectors/heading-code-words.js.map +1 -1
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +36 -0
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/invisible-character.d.ts +20 -0
- package/dist/detectors/invisible-character.d.ts.map +1 -0
- package/dist/detectors/invisible-character.js +143 -0
- package/dist/detectors/invisible-character.js.map +1 -0
- package/dist/detectors/kutoten-consistency.js +2 -2
- package/dist/detectors/kutoten-consistency.js.map +1 -1
- package/dist/detectors/lexicon-match.d.ts +10 -1
- package/dist/detectors/lexicon-match.d.ts.map +1 -1
- package/dist/detectors/lexicon-match.js +26 -1
- package/dist/detectors/lexicon-match.js.map +1 -1
- package/dist/detectors/lexicon.d.ts +6 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +31 -13
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/list-sentence.d.ts +3 -1
- package/dist/detectors/list-sentence.d.ts.map +1 -1
- package/dist/detectors/list-sentence.js +26 -1
- package/dist/detectors/list-sentence.js.map +1 -1
- package/dist/detectors/long-vowel.d.ts.map +1 -1
- package/dist/detectors/long-vowel.js +18 -4
- package/dist/detectors/long-vowel.js.map +1 -1
- package/dist/detectors/markup-finding.d.ts +3 -1
- package/dist/detectors/markup-finding.d.ts.map +1 -1
- package/dist/detectors/markup-finding.js +10 -0
- package/dist/detectors/markup-finding.js.map +1 -1
- package/dist/detectors/modal-conflict.d.ts +4 -0
- package/dist/detectors/modal-conflict.d.ts.map +1 -0
- package/dist/detectors/modal-conflict.js +22 -0
- package/dist/detectors/modal-conflict.js.map +1 -0
- package/dist/detectors/name-variant.d.ts +4 -0
- package/dist/detectors/name-variant.d.ts.map +1 -0
- package/dist/detectors/name-variant.js +13 -0
- package/dist/detectors/name-variant.js.map +1 -0
- package/dist/detectors/nominalization.d.ts +11 -0
- package/dist/detectors/nominalization.d.ts.map +1 -0
- package/dist/detectors/nominalization.js +56 -0
- package/dist/detectors/nominalization.js.map +1 -0
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +59 -28
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/outside-announced-list.d.ts +4 -0
- package/dist/detectors/outside-announced-list.d.ts.map +1 -0
- package/dist/detectors/outside-announced-list.js +24 -0
- package/dist/detectors/outside-announced-list.js.map +1 -0
- package/dist/detectors/oxford-comma.d.ts.map +1 -1
- package/dist/detectors/oxford-comma.js +1 -0
- package/dist/detectors/oxford-comma.js.map +1 -1
- package/dist/detectors/phrase-match.d.ts +4 -1
- package/dist/detectors/phrase-match.d.ts.map +1 -1
- package/dist/detectors/phrase-match.js +26 -7
- package/dist/detectors/phrase-match.js.map +1 -1
- package/dist/detectors/ra-nuki.d.ts +9 -0
- package/dist/detectors/ra-nuki.d.ts.map +1 -0
- package/dist/detectors/ra-nuki.js +30 -0
- package/dist/detectors/ra-nuki.js.map +1 -0
- package/dist/detectors/requirement-modal.d.ts +5 -0
- package/dist/detectors/requirement-modal.d.ts.map +1 -0
- package/dist/detectors/requirement-modal.js +54 -0
- package/dist/detectors/requirement-modal.js.map +1 -0
- package/dist/detectors/requirement-smell.d.ts +4 -0
- package/dist/detectors/requirement-smell.d.ts.map +1 -0
- package/dist/detectors/requirement-smell.js +41 -0
- package/dist/detectors/requirement-smell.js.map +1 -0
- package/dist/detectors/space-before-punctuation.d.ts +8 -0
- package/dist/detectors/space-before-punctuation.d.ts.map +1 -0
- package/dist/detectors/space-before-punctuation.js +24 -0
- package/dist/detectors/space-before-punctuation.js.map +1 -0
- package/dist/detectors/spelling-variety.d.ts +23 -0
- package/dist/detectors/spelling-variety.d.ts.map +1 -0
- package/dist/detectors/spelling-variety.js +59 -0
- package/dist/detectors/spelling-variety.js.map +1 -0
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +3 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/structure.d.ts.map +1 -1
- package/dist/detectors/structure.js +16 -2
- package/dist/detectors/structure.js.map +1 -1
- package/dist/detectors/superlative-comparison.d.ts +20 -0
- package/dist/detectors/superlative-comparison.d.ts.map +1 -0
- package/dist/detectors/superlative-comparison.js +32 -0
- package/dist/detectors/superlative-comparison.js.map +1 -0
- package/dist/detectors/url-run-on.d.ts.map +1 -1
- package/dist/detectors/url-run-on.js +6 -1
- package/dist/detectors/url-run-on.js.map +1 -1
- package/dist/detectors/vague-figure-pointer.d.ts +10 -0
- package/dist/detectors/vague-figure-pointer.d.ts.map +1 -0
- package/dist/detectors/vague-figure-pointer.js +46 -0
- package/dist/detectors/vague-figure-pointer.js.map +1 -0
- package/dist/detectors/word-list.d.ts +2 -0
- package/dist/detectors/word-list.d.ts.map +1 -1
- package/dist/detectors/word-list.js +4 -1
- package/dist/detectors/word-list.js.map +1 -1
- package/dist/document.d.ts +9 -3
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +55 -32
- package/dist/document.js.map +1 -1
- package/dist/extension/document-view.d.ts +7 -0
- package/dist/extension/document-view.d.ts.map +1 -0
- package/dist/extension/document-view.js +91 -0
- package/dist/extension/document-view.js.map +1 -0
- package/dist/extension/failure-text.d.ts +5 -0
- package/dist/extension/failure-text.d.ts.map +1 -0
- package/dist/extension/failure-text.js +33 -0
- package/dist/extension/failure-text.js.map +1 -0
- package/dist/extension/import-default.d.ts +7 -0
- package/dist/extension/import-default.d.ts.map +1 -0
- package/dist/extension/import-default.js +21 -0
- package/dist/extension/import-default.js.map +1 -0
- package/dist/extension/load.d.ts +28 -0
- package/dist/extension/load.d.ts.map +1 -0
- package/dist/extension/load.js +51 -0
- package/dist/extension/load.js.map +1 -0
- package/dist/extension/module-detector.d.ts +21 -0
- package/dist/extension/module-detector.d.ts.map +1 -0
- package/dist/extension/module-detector.js +49 -0
- package/dist/extension/module-detector.js.map +1 -0
- package/dist/extension/plugin-load.d.ts +9 -0
- package/dist/extension/plugin-load.d.ts.map +1 -0
- package/dist/extension/plugin-load.js +68 -0
- package/dist/extension/plugin-load.js.map +1 -0
- package/dist/extension/plugin-name.d.ts +8 -0
- package/dist/extension/plugin-name.d.ts.map +1 -0
- package/dist/extension/plugin-name.js +22 -0
- package/dist/extension/plugin-name.js.map +1 -0
- package/dist/extension/plugin-parse.d.ts +41 -0
- package/dist/extension/plugin-parse.d.ts.map +1 -0
- package/dist/extension/plugin-parse.js +156 -0
- package/dist/extension/plugin-parse.js.map +1 -0
- package/dist/extension/problem-text.d.ts +5 -0
- package/dist/extension/problem-text.d.ts.map +1 -0
- package/dist/extension/problem-text.js +74 -0
- package/dist/extension/problem-text.js.map +1 -0
- package/dist/extension/real-path.d.ts +3 -0
- package/dist/extension/real-path.d.ts.map +1 -0
- package/dist/extension/real-path.js +8 -0
- package/dist/extension/real-path.js.map +1 -0
- package/dist/extension/returned-findings.d.ts +17 -0
- package/dist/extension/returned-findings.d.ts.map +1 -0
- package/dist/extension/returned-findings.js +48 -0
- package/dist/extension/returned-findings.js.map +1 -0
- package/dist/extension/rule-export.d.ts +13 -0
- package/dist/extension/rule-export.d.ts.map +1 -0
- package/dist/extension/rule-export.js +16 -0
- package/dist/extension/rule-export.js.map +1 -0
- package/dist/feedback/draft.d.ts +2 -0
- package/dist/feedback/draft.d.ts.map +1 -1
- package/dist/feedback/draft.js +3 -0
- package/dist/feedback/draft.js.map +1 -1
- package/dist/figure-references.d.ts +11 -2
- package/dist/figure-references.d.ts.map +1 -1
- package/dist/figure-references.js +50 -13
- package/dist/figure-references.js.map +1 -1
- package/dist/finding-order.d.ts +9 -0
- package/dist/finding-order.d.ts.map +1 -0
- package/dist/finding-order.js +3 -0
- package/dist/finding-order.js.map +1 -0
- package/dist/fix-plan/mode.d.ts +21 -0
- package/dist/fix-plan/mode.d.ts.map +1 -0
- package/dist/fix-plan/mode.js +16 -0
- package/dist/fix-plan/mode.js.map +1 -0
- package/dist/fix-plan/plan.d.ts +77 -0
- package/dist/fix-plan/plan.d.ts.map +1 -0
- package/dist/fix-plan/plan.js +100 -0
- package/dist/fix-plan/plan.js.map +1 -0
- package/dist/fix-plan/render.d.ts +6 -0
- package/dist/fix-plan/render.d.ts.map +1 -0
- package/dist/fix-plan/render.js +101 -0
- package/dist/fix-plan/render.js.map +1 -0
- package/dist/fix-plan/text.d.ts +35 -0
- package/dist/fix-plan/text.d.ts.map +1 -0
- package/dist/fix-plan/text.js +95 -0
- package/dist/fix-plan/text.js.map +1 -0
- package/dist/language-sample.d.ts +2 -0
- package/dist/language-sample.d.ts.map +1 -0
- package/dist/language-sample.js +41 -0
- package/dist/language-sample.js.map +1 -0
- package/dist/link-line.d.ts +8 -0
- package/dist/link-line.d.ts.map +1 -0
- package/dist/link-line.js +12 -0
- package/dist/link-line.js.map +1 -0
- package/dist/long-vowel.d.ts +9 -6
- package/dist/long-vowel.d.ts.map +1 -1
- package/dist/long-vowel.js +15 -8
- package/dist/long-vowel.js.map +1 -1
- package/dist/markup.d.ts.map +1 -1
- package/dist/markup.js +4 -2
- package/dist/markup.js.map +1 -1
- package/dist/name-variants.d.ts +25 -0
- package/dist/name-variants.d.ts.map +1 -0
- package/dist/name-variants.js +181 -0
- package/dist/name-variants.js.map +1 -0
- package/dist/node-version.d.ts +7 -0
- package/dist/node-version.d.ts.map +1 -0
- package/dist/node-version.js +19 -0
- package/dist/node-version.js.map +1 -0
- package/dist/not-run.d.ts +14 -0
- package/dist/not-run.d.ts.map +1 -0
- package/dist/not-run.js +12 -0
- package/dist/not-run.js.map +1 -0
- package/dist/orthography.d.ts +8 -1
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +33 -12
- package/dist/orthography.js.map +1 -1
- package/dist/outline/render.d.ts +18 -0
- package/dist/outline/render.d.ts.map +1 -0
- package/dist/outline/render.js +60 -0
- package/dist/outline/render.js.map +1 -0
- package/dist/outline/shape.d.ts +26 -0
- package/dist/outline/shape.d.ts.map +1 -0
- package/dist/outline/shape.js +37 -0
- package/dist/outline/shape.js.map +1 -0
- package/dist/outline/text.d.ts +21 -0
- package/dist/outline/text.d.ts.map +1 -0
- package/dist/outline/text.js +30 -0
- package/dist/outline/text.js.map +1 -0
- package/dist/plugin.d.ts +24 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/reasons.d.ts +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +2 -0
- package/dist/reasons.js.map +1 -1
- package/dist/render/compact.d.ts +3 -1
- package/dist/render/compact.d.ts.map +1 -1
- package/dist/render/compact.js +8 -2
- package/dist/render/compact.js.map +1 -1
- package/dist/render/explain.d.ts +3 -1
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +32 -15
- package/dist/render/explain.js.map +1 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +16 -12
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/suppressions.d.ts +3 -0
- package/dist/render/suppressions.d.ts.map +1 -1
- package/dist/render/suppressions.js +20 -1
- package/dist/render/suppressions.js.map +1 -1
- package/dist/rule-guide.d.ts +19 -0
- package/dist/rule-guide.d.ts.map +1 -1
- package/dist/rule-guide.js +14 -0
- package/dist/rule-guide.js.map +1 -1
- package/dist/run-semantic.d.ts.map +1 -1
- package/dist/run-semantic.js +2 -1
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +5 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +31 -14
- package/dist/run.js.map +1 -1
- package/dist/setting-problems.d.ts.map +1 -1
- package/dist/setting-problems.js +3 -1
- package/dist/setting-problems.js.map +1 -1
- package/dist/spacing-minority.d.ts +16 -0
- package/dist/spacing-minority.d.ts.map +1 -0
- package/dist/spacing-minority.js +23 -0
- package/dist/spacing-minority.js.map +1 -0
- package/dist/stet-block.d.ts +5 -0
- package/dist/stet-block.d.ts.map +1 -0
- package/dist/stet-block.js +59 -0
- package/dist/stet-block.js.map +1 -0
- package/dist/stet.d.ts +4 -0
- package/dist/stet.d.ts.map +1 -1
- package/dist/stet.js +31 -11
- package/dist/stet.js.map +1 -1
- package/dist/structure/date-range.d.ts +10 -0
- package/dist/structure/date-range.d.ts.map +1 -1
- package/dist/structure/date-range.js +33 -1
- package/dist/structure/date-range.js.map +1 -1
- package/dist/structure/definition-use.d.ts +32 -0
- package/dist/structure/definition-use.d.ts.map +1 -0
- package/dist/structure/definition-use.js +102 -0
- package/dist/structure/definition-use.js.map +1 -0
- package/dist/structure/lazy-tree.d.ts +8 -0
- package/dist/structure/lazy-tree.d.ts.map +1 -0
- package/dist/structure/lazy-tree.js +14 -0
- package/dist/structure/lazy-tree.js.map +1 -0
- package/dist/structure/modal-conflict.d.ts +28 -0
- package/dist/structure/modal-conflict.d.ts.map +1 -0
- package/dist/structure/modal-conflict.js +74 -0
- package/dist/structure/modal-conflict.js.map +1 -0
- package/dist/structure/numbered-clauses.d.ts +4 -0
- package/dist/structure/numbered-clauses.d.ts.map +1 -0
- package/dist/structure/numbered-clauses.js +9 -0
- package/dist/structure/numbered-clauses.js.map +1 -0
- package/dist/structure/universal.d.ts.map +1 -1
- package/dist/structure/universal.js +8 -2
- package/dist/structure/universal.js.map +1 -1
- package/dist/style-parse.d.ts +6 -0
- package/dist/style-parse.d.ts.map +1 -1
- package/dist/style-parse.js +32 -4
- package/dist/style-parse.js.map +1 -1
- package/dist/subheading-line.d.ts +19 -2
- package/dist/subheading-line.d.ts.map +1 -1
- package/dist/subheading-line.js +39 -11
- package/dist/subheading-line.js.map +1 -1
- package/dist/tag-coverage.d.ts +8 -0
- package/dist/tag-coverage.d.ts.map +1 -0
- package/dist/tag-coverage.js +7 -0
- package/dist/tag-coverage.js.map +1 -0
- package/genres.yaml +42 -0
- package/package.json +7 -3
- package/rules/acronym-expansion-conflict.yaml +82 -0
- package/rules/adverb-overuse.yaml +2 -2
- package/rules/agentless-passive.yaml +28 -0
- package/rules/ai-tell.yaml +34 -0
- package/rules/announced-count-mismatch.yaml +2 -2
- package/rules/announcing-opener.yaml +31 -0
- package/rules/assistant-residue.yaml +30 -0
- package/rules/bold-density.yaml +28 -0
- package/rules/bold-label-list.yaml +81 -0
- package/rules/closing-cliche.yaml +27 -2
- package/rules/colon-lead-in.yaml +28 -4
- package/rules/contrast-framing.yaml +33 -2
- package/rules/cushion-phrase-density.yaml +32 -5
- package/rules/dangling-reference.yaml +1 -0
- package/rules/date-order.yaml +1 -0
- package/rules/date-range-reversed.yaml +2 -2
- package/rules/double-negative.yaml +51 -0
- package/rules/doubled-punctuation.yaml +2 -2
- package/rules/doubled-word.yaml +2 -2
- package/rules/duplicate-heading.yaml +78 -0
- package/rules/emoji-density.yaml +5 -4
- package/rules/empty-intensifier.yaml +25 -0
- package/rules/empty-section.yaml +74 -0
- package/rules/excessive-hedging.yaml +32 -4
- package/rules/fullwidth-alnum-consistency.yaml +53 -0
- package/rules/hankaku-kana.yaml +41 -0
- package/rules/image-alt-text.yaml +1 -1
- package/rules/invisible-character.yaml +63 -0
- package/rules/katakana-long-vowel.yaml +10 -9
- package/rules/latin-spacing.yaml +12 -5
- package/rules/max-kanji-continuous.yaml +1 -0
- package/rules/max-paragraph-length.yaml +1 -0
- package/rules/max-sentence-length.yaml +26 -0
- package/rules/modal-conflict.yaml +106 -0
- package/rules/name-variant.yaml +88 -0
- package/rules/no-doubled-joshi.yaml +1 -0
- package/rules/no-em-dash.yaml +26 -2
- package/rules/no-mixed-desumasu.yaml +1 -0
- package/rules/nominalization.yaml +89 -0
- package/rules/outside-announced-list.yaml +80 -0
- package/rules/padded-intro.yaml +25 -0
- package/rules/paragraph-length-variance.yaml +26 -2
- package/rules/preferred-term.yaml +1 -0
- package/rules/proper-noun-density.yaml +14 -5
- package/rules/ra-nuki.yaml +43 -0
- package/rules/requirement-modal.yaml +94 -0
- package/rules/requirement-smell.yaml +70 -0
- package/rules/rule-of-three.yaml +46 -0
- package/rules/section-length-uniformity.yaml +26 -2
- package/rules/sentence-rhythm.yaml +26 -0
- package/rules/space-before-punctuation.yaml +41 -0
- package/rules/spelling-consistency.yaml +47 -0
- package/rules/stock-transition.yaml +33 -2
- package/rules/taigen-dome-in-prose.yaml +14 -0
- package/rules/total-mismatch.yaml +1 -0
- package/rules/undefined-acronym.yaml +1 -0
- package/rules/unqualified-superlative.yaml +5 -4
- package/rules/unused-definition.yaml +75 -0
- package/rules/use-before-definition.yaml +77 -0
- package/rules/vague-figure-reference.yaml +66 -0
- package/skills/chaff/SKILL.md +108 -8
- package/src/acronym-expansions.ts +106 -0
- package/src/announced-count.ts +53 -11
- package/src/announced-list.ts +120 -0
- package/src/api.ts +154 -0
- package/src/cli-text.ts +6 -11
- package/src/cli.ts +64 -63
- package/src/closing-paragraph.ts +24 -0
- package/src/commands/compare.ts +11 -18
- package/src/commands/document-commands.ts +19 -0
- package/src/commands/eval.ts +3 -2
- package/src/commands/facts.ts +33 -0
- package/src/commands/feedback.ts +33 -1
- package/src/commands/fix-plan.ts +53 -0
- package/src/commands/outline.ts +49 -0
- package/src/commands/read-document.ts +24 -0
- package/src/commands/suppressions.ts +19 -0
- package/src/commands/test.ts +1 -1
- package/src/compare/clock-time.ts +5 -2
- package/src/compare/distinct.ts +13 -0
- package/src/compare/extract.ts +7 -5
- package/src/compare/fact-key.ts +31 -0
- package/src/compare/fact-text.ts +21 -2
- package/src/compare/facts-render.ts +63 -0
- package/src/compare/facts-text.ts +41 -0
- package/src/compare/match.ts +1 -1
- package/src/compare/outcome.ts +14 -4
- package/src/compare/proper-nouns.ts +4 -4
- package/src/compare/render.ts +2 -2
- package/src/compare/text-atoms.ts +7 -6
- package/src/compare/text.ts +3 -2
- package/src/config/load.ts +9 -0
- package/src/config/option-problems.ts +6 -4
- package/src/config/style.ts +14 -1
- package/src/custom/load.ts +11 -3
- package/src/custom/module-path.ts +23 -0
- package/src/custom/parse.ts +67 -18
- package/src/custom/problems.ts +63 -41
- package/src/detect-language.ts +1 -1
- package/src/detect.ts +10 -3
- package/src/detectors/ai-phrasing.ts +6 -2
- package/src/detectors/bold-label.ts +55 -0
- package/src/detectors/dangling-figure.ts +8 -4
- package/src/detectors/definition-use.ts +41 -0
- package/src/detectors/doubled-punctuation.ts +5 -2
- package/src/detectors/duplicate-heading.ts +47 -0
- package/src/detectors/empty-section.ts +21 -0
- package/src/detectors/fullwidth-alnum.ts +118 -0
- package/src/detectors/hankaku-kana.ts +38 -0
- package/src/detectors/heading-code-words.ts +2 -2
- package/src/detectors/index.ts +36 -0
- package/src/detectors/invisible-character.ts +190 -0
- package/src/detectors/kutoten-consistency.ts +2 -2
- package/src/detectors/lexicon-match.ts +33 -1
- package/src/detectors/lexicon.ts +40 -13
- package/src/detectors/list-sentence.ts +45 -4
- package/src/detectors/long-vowel.ts +20 -4
- package/src/detectors/markup-finding.ts +16 -1
- package/src/detectors/modal-conflict.ts +26 -0
- package/src/detectors/name-variant.ts +15 -0
- package/src/detectors/nominalization.ts +73 -0
- package/src/detectors/orthography.ts +77 -27
- package/src/detectors/outside-announced-list.ts +25 -0
- package/src/detectors/oxford-comma.ts +1 -0
- package/src/detectors/phrase-match.ts +27 -7
- package/src/detectors/ra-nuki.ts +38 -0
- package/src/detectors/requirement-modal.ts +67 -0
- package/src/detectors/requirement-smell.ts +52 -0
- package/src/detectors/space-before-punctuation.ts +32 -0
- package/src/detectors/spelling-variety.ts +81 -0
- package/src/detectors/structure-tree.ts +3 -1
- package/src/detectors/structure.ts +17 -3
- package/src/detectors/superlative-comparison.ts +47 -0
- package/src/detectors/url-run-on.ts +7 -1
- package/src/detectors/vague-figure-pointer.ts +58 -0
- package/src/detectors/word-list.ts +6 -1
- package/src/document.ts +88 -41
- package/src/extension/document-view.ts +109 -0
- package/src/extension/failure-text.ts +46 -0
- package/src/extension/import-default.ts +13 -0
- package/src/extension/load.ts +97 -0
- package/src/extension/module-detector.ts +61 -0
- package/src/extension/plugin-load.ts +82 -0
- package/src/extension/plugin-name.ts +26 -0
- package/src/extension/plugin-parse.ts +229 -0
- package/src/extension/problem-text.ts +103 -0
- package/src/extension/real-path.ts +10 -0
- package/src/extension/returned-findings.ts +62 -0
- package/src/extension/rule-export.ts +23 -0
- package/src/feedback/draft.ts +6 -0
- package/src/figure-references.ts +73 -12
- package/src/finding-order.ts +3 -0
- package/src/fix-plan/mode.ts +34 -0
- package/src/fix-plan/plan.ts +181 -0
- package/src/fix-plan/render.ts +128 -0
- package/src/fix-plan/text.ts +133 -0
- package/src/language-sample.ts +48 -0
- package/src/link-line.ts +13 -0
- package/src/long-vowel.ts +18 -8
- package/src/markup.ts +4 -2
- package/src/name-variants.ts +217 -0
- package/src/node-version.ts +22 -0
- package/src/not-run.ts +16 -0
- package/src/orthography.ts +40 -11
- package/src/outline/render.ts +85 -0
- package/src/outline/shape.ts +72 -0
- package/src/outline/text.ts +50 -0
- package/src/plugin.ts +21 -4
- package/src/reasons.ts +3 -0
- package/src/render/compact.ts +13 -3
- package/src/render/explain.ts +47 -20
- package/src/render/rules-json.ts +18 -12
- package/src/render/suppressions.ts +30 -2
- package/src/rule-guide.ts +38 -0
- package/src/run-semantic.ts +2 -1
- package/src/run.ts +37 -17
- package/src/setting-problems.ts +3 -1
- package/src/spacing-minority.ts +27 -0
- package/src/stet-block.ts +60 -0
- package/src/stet.ts +41 -15
- package/src/structure/date-range.ts +49 -1
- package/src/structure/definition-use.ts +130 -0
- package/src/structure/lazy-tree.ts +15 -0
- package/src/structure/modal-conflict.ts +86 -0
- package/src/structure/numbered-clauses.ts +11 -0
- package/src/structure/universal.ts +10 -2
- package/src/style-parse.ts +38 -3
- package/src/subheading-line.ts +51 -13
- package/src/tag-coverage.ts +13 -0
- package/styles/ieice.yaml +5 -4
- package/styles/jis-z8301-2011.yaml +5 -2
- package/styles/jis-z8301-2019.yaml +31 -0
- package/styles/koyobun.yaml +35 -0
- package/dist/legal-address.d.ts +0 -2
- package/dist/legal-address.d.ts.map +0 -1
- package/dist/legal-address.js +0 -16
- package/dist/legal-address.js.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { spanOf, type MarkdownNode } from "./markdown-node.ts";
|
|
2
|
+
import type { Span } from "./plugin.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where a `<!-- stet: … -->` (scope next) stops: the end of the block right after the comment, as the Markdown parser
|
|
6
|
+
* reads it — a paragraph, a heading, a whole list, a table, a code block, a quote. Inside a list item, the item's next
|
|
7
|
+
* block. Plain text reads the same way: its paragraphs are the runs between blank lines.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Containers whose children are blocks. Anywhere else, an HTML comment sits inside a paragraph or a heading. */
|
|
11
|
+
const BLOCK_CONTAINERS: ReadonlySet<string> = new Set(["root", "listItem", "blockquote"]);
|
|
12
|
+
|
|
13
|
+
type Holder = { readonly parent: MarkdownNode; readonly index: number };
|
|
14
|
+
|
|
15
|
+
/** The child holding `offset`, or -1. Binary search: siblings are in source order, and a document can hold thousands of stets. */
|
|
16
|
+
const childHolding = (children: readonly MarkdownNode[], offset: number): number => {
|
|
17
|
+
let low = 0;
|
|
18
|
+
let high = children.length - 1;
|
|
19
|
+
while (low <= high) {
|
|
20
|
+
const middle = (low + high) >> 1;
|
|
21
|
+
const span = spanOf(children[middle] ?? { type: "" });
|
|
22
|
+
if (span === undefined) return -1;
|
|
23
|
+
if (offset < span.start) high = middle - 1;
|
|
24
|
+
else if (offset >= span.end) low = middle + 1;
|
|
25
|
+
else return middle;
|
|
26
|
+
}
|
|
27
|
+
return -1;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The deepest node holding `offset`, as its parent and its place among the parent's children. A loop: quotes nest without limit. */
|
|
31
|
+
const holderOf = (root: MarkdownNode, offset: number): Holder | undefined => {
|
|
32
|
+
let found: Holder | undefined;
|
|
33
|
+
let parent: MarkdownNode | undefined = root;
|
|
34
|
+
while (parent !== undefined) {
|
|
35
|
+
const children: readonly MarkdownNode[] = parent.children ?? [];
|
|
36
|
+
const index = childHolding(children, offset);
|
|
37
|
+
if (index !== -1) found = { parent, index };
|
|
38
|
+
parent = children[index];
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** The node a stet covers: the next block when the comment stands alone, else the block the comment is part of. */
|
|
44
|
+
const coveredNode = (source: string, holder: Holder, comment: Span): MarkdownNode | undefined => {
|
|
45
|
+
const siblings = holder.parent.children ?? [];
|
|
46
|
+
const node = siblings[holder.index];
|
|
47
|
+
if (node === undefined) return undefined;
|
|
48
|
+
if (!BLOCK_CONTAINERS.has(holder.parent.type)) return holder.parent;
|
|
49
|
+
const span = spanOf(node);
|
|
50
|
+
const alone = span !== undefined && source.slice(span.start, span.end).trim() === source.slice(comment.start, comment.end);
|
|
51
|
+
return alone ? siblings[holder.index + 1] : node;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The last line a `stet` (scope next) covers, for the comment at `comment` in a document parsed as `root`. */
|
|
55
|
+
export const stetBlockEnd = (source: string, root: MarkdownNode, comment: Span, lineAt: (offset: number) => number): number => {
|
|
56
|
+
const holder = holderOf(root, comment.start);
|
|
57
|
+
const covered = holder === undefined ? undefined : coveredNode(source, holder, comment);
|
|
58
|
+
const end = covered === undefined ? undefined : spanOf(covered)?.end;
|
|
59
|
+
return lineAt(end ?? comment.end);
|
|
60
|
+
};
|
package/src/stet.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { Finding, Span } from "./plugin.ts";
|
|
2
|
+
import { RULE_ID } from "./extension/plugin-name.ts";
|
|
3
|
+
import { stetBlockEnd } from "./stet-block.ts";
|
|
4
|
+
import { lineStarts, placeOf } from "./position.ts";
|
|
5
|
+
import { parse } from "./markdown-read.ts";
|
|
2
6
|
|
|
3
7
|
export type Suppression = {
|
|
4
8
|
readonly rules: readonly string[];
|
|
5
9
|
readonly reason: string | undefined;
|
|
6
10
|
readonly line: number;
|
|
11
|
+
/** The last line a `stet` (scope next) covers: the end of the block right below the comment. Other scopes: the comment's last line. */
|
|
12
|
+
readonly blockEnd: number;
|
|
7
13
|
readonly scope: "next" | "section" | "file";
|
|
8
14
|
};
|
|
9
15
|
|
|
@@ -23,10 +29,6 @@ const SCOPE: Readonly<Record<string, Suppression["scope"]>> = { stet: "next", "s
|
|
|
23
29
|
/** 理由は em dash のあと。書かなくてもよいが、書かないと chaff suppressions に並ぶ。 */
|
|
24
30
|
const REASON_MARK = "\u2014";
|
|
25
31
|
|
|
26
|
-
const RULE_ID = /^[a-z][a-z0-9-]*$/u;
|
|
27
|
-
|
|
28
|
-
const lineOf = (source: string, offset: number): number => source.slice(0, offset).split("\n").length;
|
|
29
|
-
|
|
30
32
|
const splitBody = (body: string): { rules: string[]; reason: string | undefined } => {
|
|
31
33
|
const at = body.indexOf(REASON_MARK);
|
|
32
34
|
const head = at === -1 ? body : body.slice(0, at);
|
|
@@ -43,37 +45,61 @@ const splitBody = (body: string): { rules: string[]; reason: string | undefined
|
|
|
43
45
|
};
|
|
44
46
|
};
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const scopeOf = (match: RegExpMatchArray): Suppression["scope"] => SCOPE[match[1] ?? "stet"] ?? "next";
|
|
49
|
+
|
|
50
|
+
/** 1-based line of an offset, from a table built once: a document can hold thousands of stets. */
|
|
51
|
+
const lineFinder = (source: string): ((offset: number) => number) => {
|
|
52
|
+
const starts = lineStarts(source);
|
|
53
|
+
return (offset) => placeOf(starts, offset).line;
|
|
54
|
+
};
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
const
|
|
56
|
+
export const parseSuppressions = (source: string): Suppression[] => {
|
|
57
|
+
const matches = [...source.matchAll(PATTERN)];
|
|
58
|
+
const lineAt = lineFinder(source);
|
|
59
|
+
// Only a plain stet needs the tree, to find the block it stops at; a stet-file alone does not pay for a parse.
|
|
60
|
+
const root = matches.some((match) => scopeOf(match) === "next") ? parse(source) : undefined;
|
|
61
|
+
return matches.map((match) => {
|
|
62
|
+
const comment = { start: match.index, end: match.index + match[0].length };
|
|
63
|
+
const scope = scopeOf(match);
|
|
64
|
+
return {
|
|
65
|
+
...splitBody(match[2] ?? ""),
|
|
66
|
+
line: lineAt(match.index),
|
|
67
|
+
blockEnd: root !== undefined && scope === "next" ? stetBlockEnd(source, root, comment, lineAt) : lineAt(comment.end),
|
|
68
|
+
scope,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
};
|
|
55
72
|
|
|
73
|
+
/** stet が効く範囲。next は直後の塊(段落・見出し・箇条書き・表)の終わりまで、section は次の見出しまで、file は全体。 */
|
|
56
74
|
const covers = (suppression: Suppression, finding: Finding, sectionEnds: readonly number[], lastLine: number): boolean => {
|
|
57
75
|
if (!suppression.rules.includes(finding.rule)) return false;
|
|
58
76
|
if (suppression.scope === "file") return true;
|
|
59
77
|
// 抑制は「この先の箇所」に対して書く。遡って効くと、意図せず黙る範囲が広がる。
|
|
60
78
|
if (finding.line < suppression.line) return false;
|
|
61
|
-
if (suppression.scope === "next") return finding.line
|
|
79
|
+
if (suppression.scope === "next") return finding.line <= suppression.blockEnd;
|
|
62
80
|
return finding.line <= (sectionEnds.find((line) => line > suppression.line) ?? lastLine);
|
|
63
81
|
};
|
|
64
82
|
|
|
65
83
|
export type Suppressed = { readonly finding: Finding; readonly suppression: Suppression };
|
|
66
84
|
|
|
67
|
-
export type Applied = {
|
|
85
|
+
export type Applied = {
|
|
86
|
+
readonly kept: readonly Finding[];
|
|
87
|
+
readonly suppressed: readonly Suppressed[];
|
|
88
|
+
readonly unusedReasonless: readonly Suppression[];
|
|
89
|
+
/** Every rule a stet in the document names, whether or not it silenced anything. */
|
|
90
|
+
readonly named: readonly string[];
|
|
91
|
+
};
|
|
68
92
|
|
|
69
93
|
export const applySuppressions = (source: string, findings: readonly Finding[], sections: readonly Span[]): Applied => {
|
|
70
94
|
const suppressions = parseSuppressions(source);
|
|
71
|
-
const
|
|
95
|
+
const lineAt = lineFinder(source);
|
|
96
|
+
const sectionEnds = sections.map((span) => lineAt(span.end));
|
|
72
97
|
const lastLine = source.split("\n").length;
|
|
73
98
|
const matched = findings.map((finding) => ({ finding, suppression: suppressions.find((entry) => covers(entry, finding, sectionEnds, lastLine)) }));
|
|
74
99
|
return {
|
|
75
100
|
kept: matched.filter((entry) => entry.suppression === undefined).map((entry) => entry.finding),
|
|
76
101
|
suppressed: matched.flatMap((entry) => (entry.suppression === undefined ? [] : [{ finding: entry.finding, suppression: entry.suppression }])),
|
|
77
102
|
unusedReasonless: suppressions.filter((entry) => entry.reason === undefined),
|
|
103
|
+
named: [...new Set(suppressions.flatMap((entry) => entry.rules))],
|
|
78
104
|
};
|
|
79
105
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
1
2
|
import type { StructureIssue } from "./issues.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -13,6 +14,18 @@ export type RangeWords = {
|
|
|
13
14
|
/** 終わりの日付に閉じの語が続くときだけ期間を作る語(から)。 */
|
|
14
15
|
readonly openers: readonly string[];
|
|
15
16
|
readonly closers: readonly string[];
|
|
17
|
+
/** 始まりの日付の前の語と、二つの日付の間の語の組で期間を作るもの(from … to、between … and)。 */
|
|
18
|
+
readonly frames: readonly RangeFrame[];
|
|
19
|
+
/** 同じ文にあれば、組を期間ではなく日付の変更と読む語(moved from … to、… was postponed)。 */
|
|
20
|
+
readonly changes: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type RangeFrame = { readonly lead: string; readonly joint: string };
|
|
24
|
+
|
|
25
|
+
/** 語彙表の「from … to」を、前の語と間の語に分ける。「…」の無い語は組ではない。 */
|
|
26
|
+
export const rangeFrameOf = (pattern: string): RangeFrame | undefined => {
|
|
27
|
+
const [lead, joint, ...rest] = pattern.split("…").map((part) => part.trim().toLowerCase());
|
|
28
|
+
return lead === undefined || joint === undefined || lead === "" || joint === "" || rest.length > 0 ? undefined : { lead, joint };
|
|
16
29
|
};
|
|
17
30
|
|
|
18
31
|
const COMPARABLE = [/^\d{4}-\d{2}-\d{2}$/u, /^\d{4}-\d{2}$/u];
|
|
@@ -45,10 +58,45 @@ const jointOf = (source: string, start: DatedSpan, end: DatedSpan): string | und
|
|
|
45
58
|
return next.every((line) => line.trim() === "") ? bare(first) : undefined;
|
|
46
59
|
};
|
|
47
60
|
|
|
61
|
+
/** 英字の語は語の切れ目で照らす(from が therefrom に当たらず、moved が removed に当たらない)。 */
|
|
62
|
+
const wordPattern = (word: string, tail: string): RegExp => new RegExp(`(?<![a-z])${escapeRegExp(word)}(?![a-z])${tail}`, "u");
|
|
63
|
+
|
|
64
|
+
const endsWithWord = (text: string, word: string): boolean => wordPattern(word, "$").test(text);
|
|
65
|
+
|
|
66
|
+
const hasWord = (text: string, word: string): boolean => wordPattern(word, "").test(text);
|
|
67
|
+
|
|
68
|
+
/** 段落の切れ目(空行)。 */
|
|
69
|
+
const PARAGRAPH_BREAK = "\n\n";
|
|
70
|
+
|
|
71
|
+
/** 文の終わり。後ろが大文字のときだけ切る("in Jan. from" は切らない)。切りそこねると文が長くなり、変更の語を見つけやすくなるだけ。 */
|
|
72
|
+
const SENTENCE_BREAK = /[.!?]\s+(?=[A-Z])/gu;
|
|
73
|
+
|
|
74
|
+
/** 二つの日付を含む文(小文字、空白は一つに)と、その中の始まりの日付の位置。行の折り返しをまたいで読む。 */
|
|
75
|
+
const sentenceAround = (source: string, start: DatedSpan, end: DatedSpan): { readonly text: string; readonly before: string } => {
|
|
76
|
+
const previousBreak = source.lastIndexOf(PARAGRAPH_BREAK, start.offset);
|
|
77
|
+
const paragraphStart = previousBreak === -1 ? 0 : previousBreak + PARAGRAPH_BREAK.length;
|
|
78
|
+
const paragraphEnd = source.indexOf(PARAGRAPH_BREAK, end.end);
|
|
79
|
+
const head = source.slice(paragraphStart, start.offset);
|
|
80
|
+
const tail = source.slice(end.end, paragraphEnd === -1 ? source.length : paragraphEnd);
|
|
81
|
+
const from = [...head.matchAll(SENTENCE_BREAK)].map((match) => match.index + match[0].length).at(-1) ?? 0;
|
|
82
|
+
const to = [...tail.matchAll(SENTENCE_BREAK)][0]?.index ?? tail.length;
|
|
83
|
+
const flat = (text: string): string => text.replace(/\s+/gu, " ").toLowerCase();
|
|
84
|
+
return { text: flat(source.slice(paragraphStart + from, end.end + to)), before: flat(head.slice(from)).trimEnd() };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** 前の語と間の語の組(from … to)。同じ文のどこかに変更の語(moved、postponed)があれば、日付を動かした文で、期間ではない。 */
|
|
88
|
+
const framed = (source: string, start: DatedSpan, end: DatedSpan, joint: string, words: RangeWords): boolean => {
|
|
89
|
+
const sentence = sentenceAround(source, start, end);
|
|
90
|
+
const frame = words.frames.find((candidate) => candidate.joint === joint && endsWithWord(sentence.before, candidate.lead));
|
|
91
|
+
return frame !== undefined && !words.changes.some((change) => hasWord(sentence.text, change.toLowerCase()));
|
|
92
|
+
};
|
|
93
|
+
|
|
48
94
|
const isRange = (source: string, start: DatedSpan, end: DatedSpan, words: RangeWords): boolean => {
|
|
49
95
|
const joint = jointOf(source, start, end);
|
|
50
96
|
if (joint === undefined) return false;
|
|
51
|
-
return
|
|
97
|
+
return (
|
|
98
|
+
isOneOf(joint, words.connectors) || (isOneOf(joint, words.openers) && closedAfter(source, end, words.closers)) || framed(source, start, end, joint, words)
|
|
99
|
+
);
|
|
52
100
|
};
|
|
53
101
|
|
|
54
102
|
/** 書いたままの期間。行をまたいだ期間は一行にする。 */
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Span, StructureNode } from "../plugin.ts";
|
|
2
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
3
|
+
import { inDocumentOrder } from "./issues.ts";
|
|
4
|
+
|
|
5
|
+
// 定義した語が本文で使われているか、定義より前に使われていないか。どちらも木の定義と、本文の文の字面だけで決まる。
|
|
6
|
+
|
|
7
|
+
export type DefinedTerm = { readonly term: string; readonly span: Span; readonly line: number; readonly inline: boolean };
|
|
8
|
+
|
|
9
|
+
/** 本文の一続き(文)。start は文書の中の位置。 */
|
|
10
|
+
export type BodyText = { readonly start: number; readonly text: string };
|
|
11
|
+
|
|
12
|
+
export type TermUse = { readonly term: DefinedTerm; readonly offset: number };
|
|
13
|
+
|
|
14
|
+
/** 強調の印("**Agreement**")は語の一部ではない。 */
|
|
15
|
+
const EMPHASIS_MARKS = /[*_]+/gu;
|
|
16
|
+
/** 語の長さの上限。これより長い引用(表の見出し、文)は、定義した語ではない。 */
|
|
17
|
+
const MAX_TERM_WORDS = 6;
|
|
18
|
+
/** 文の区切りや問いを含むもの("What do you think about...?")は、引いた言葉で、定義した語ではない。 */
|
|
19
|
+
const NOT_A_TERM = /(?:[?!,;:…。、?!]|\.$)/u;
|
|
20
|
+
const HAS_LETTER = /\p{L}/u;
|
|
21
|
+
|
|
22
|
+
const isTerm = (term: string): boolean => HAS_LETTER.test(term) && !NOT_A_TERM.test(term) && term.split(/\s+/u).length <= MAX_TERM_WORDS;
|
|
23
|
+
|
|
24
|
+
/** 木の定義を文書の順に。語でないものは外す。inline は文の途中で括弧に入れた定義((the "Seller")、以下「甲」という)。 */
|
|
25
|
+
export const definedTerms = (tree: StructureNode): DefinedTerm[] =>
|
|
26
|
+
inDocumentOrder(tree).flatMap((node) => {
|
|
27
|
+
const term =
|
|
28
|
+
node.kind === "definition"
|
|
29
|
+
? String(node.attrs["term"] ?? "")
|
|
30
|
+
.replaceAll(EMPHASIS_MARKS, "")
|
|
31
|
+
.trim()
|
|
32
|
+
: "";
|
|
33
|
+
return isTerm(term) ? [{ term, span: node.span, line: node.line, inline: node.attrs["placement"] === "inline" }] : [];
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const LATIN_EDGE = /^[\p{Script=Latin}\p{N}]|[\p{Script=Latin}\p{N}]$/u;
|
|
37
|
+
/** 一字の漢字の語(「法」「甲」)は、漢字の後ろに続く所(方法、法律の前の「方」)では別の語の一部。 */
|
|
38
|
+
const SINGLE_KANJI = /^\p{Script=Han}$/u;
|
|
39
|
+
|
|
40
|
+
/** loose は最初の一字の大小を問わない(文頭の "Personal data")。exact は書いたとおりだけ("Access" は "early access" に当たらない)。 */
|
|
41
|
+
type CaseMatch = "loose" | "exact";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 語の現れを探す形。英字で始まる・終わる語は語の途中に当たらない("Term" は "Terms" には当たり、"Termination" には当たらない)。
|
|
45
|
+
* 複数形と所有の形(Services、Policies、Seller's)は同じ語の使用。
|
|
46
|
+
*/
|
|
47
|
+
const termPattern = (term: string, caseMatch: CaseMatch): RegExp => {
|
|
48
|
+
const first = term.charAt(0);
|
|
49
|
+
const rest = /\p{Ll}y$/u.test(term) ? `${escapeRegExp(term.slice(1, -1))}(?:y|ie)` : escapeRegExp(term.slice(1));
|
|
50
|
+
const caseless = caseMatch === "exact" || first.toUpperCase() === first.toLowerCase();
|
|
51
|
+
const head = caseless ? escapeRegExp(first) : `[${escapeRegExp(first.toUpperCase())}${escapeRegExp(first.toLowerCase())}]`;
|
|
52
|
+
if (SINGLE_KANJI.test(term)) return new RegExp(String.raw`(?<!\p{Script=Han})${head}`, "gu");
|
|
53
|
+
if (!LATIN_EDGE.test(term)) return new RegExp(`${head}${rest}`, "gu");
|
|
54
|
+
return new RegExp(String.raw`(?<![\p{Script=Latin}\p{N}])${head}${rest}(?:e?s|['’]s?)?(?![\p{Script=Latin}\p{N}])`, "gu");
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const inside = (offset: number, spans: readonly Span[]): boolean => spans.some((span) => span.start <= offset && offset < span.end);
|
|
58
|
+
|
|
59
|
+
/** 語の現れ(文書の中の位置)を順に。定義そのもの("Seller" means、(the "Seller"))の中の現れは使用に数えない。 */
|
|
60
|
+
export const usesOf = (term: string, texts: readonly BodyText[], definitionSpans: readonly Span[], caseMatch: CaseMatch = "loose"): number[] => {
|
|
61
|
+
const pattern = termPattern(term, caseMatch);
|
|
62
|
+
return texts.flatMap((body) =>
|
|
63
|
+
[...body.text.matchAll(pattern)].map((match) => body.start + match.index).filter((offset) => !inside(offset, definitionSpans)),
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type TermGroup = { readonly first: DefinedTerm; readonly spans: readonly Span[] };
|
|
68
|
+
|
|
69
|
+
/** 語ごとに、最初の定義と、その語のすべての定義の範囲。定義が何千あっても語の数だけ回る。 */
|
|
70
|
+
const groupsOf = (terms: readonly DefinedTerm[]): TermGroup[] => {
|
|
71
|
+
const groups = new Map<string, { first: DefinedTerm; spans: Span[] }>();
|
|
72
|
+
terms.forEach((defined) => {
|
|
73
|
+
const group = groups.get(defined.term);
|
|
74
|
+
if (group === undefined) groups.set(defined.term, { first: defined, spans: [defined.span] });
|
|
75
|
+
else group.spans.push(defined.span);
|
|
76
|
+
});
|
|
77
|
+
return [...groups.values()];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** 定義したのに、定義の外で一度も使っていない語。同じ語の定義が二つあれば、最初の一つだけを言う(二つ目は duplicate-definition)。 */
|
|
81
|
+
export const unusedDefinitions = (terms: readonly DefinedTerm[], texts: readonly BodyText[]): DefinedTerm[] =>
|
|
82
|
+
groupsOf(terms)
|
|
83
|
+
.filter((group) => usesOf(group.first.term, texts, group.spans).length === 0)
|
|
84
|
+
.map((group) => group.first);
|
|
85
|
+
|
|
86
|
+
/** offset を含む文。texts は start の昇順で、二分探索で引く。 */
|
|
87
|
+
const sentenceAt = (texts: readonly BodyText[], offset: number): BodyText | undefined => {
|
|
88
|
+
let low = 0;
|
|
89
|
+
let high = texts.length - 1;
|
|
90
|
+
while (low <= high) {
|
|
91
|
+
const middle = (low + high) >> 1;
|
|
92
|
+
const body = texts[middle];
|
|
93
|
+
if (body === undefined) return undefined;
|
|
94
|
+
if (offset < body.start) high = middle - 1;
|
|
95
|
+
else if (offset >= body.start + body.text.length) low = middle + 1;
|
|
96
|
+
else return body;
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const LETTER = /\p{L}/gu;
|
|
102
|
+
/** 札の文が語のほかに持ってよい字の数("The Agreement." の The)。 */
|
|
103
|
+
const LABEL_SLACK = 3;
|
|
104
|
+
|
|
105
|
+
/** 語だけの札の文(**12.2.** Taxes.、1. The Agreement.)。条の頭に語を掲げただけで、使用ではない。 */
|
|
106
|
+
const isLabel = (body: BodyText | undefined, term: string): boolean =>
|
|
107
|
+
body !== undefined && (body.text.replaceAll(term, "").match(LETTER) ?? []).length <= LABEL_SLACK;
|
|
108
|
+
|
|
109
|
+
const CAPITAL_OR_NON_LATIN = /\p{Lu}|[^\p{Script=Latin}\p{N}\s\p{P}]/u;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 前で使ったと言える語。小文字の英語(("vendors"))はふつうの語としても使うので、前の現れが定義した語の使用とは限らない。
|
|
113
|
+
* 一字の漢字(「法」「令」)は、令和や方法の一部にも当たる。
|
|
114
|
+
*/
|
|
115
|
+
const isDistinctive = (term: string): boolean => CAPITAL_OR_NON_LATIN.test(term) && !SINGLE_KANJI.test(term);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 文の途中で括弧に入れて定義した語((the "Seller")、以下「甲」という)を、その定義の文より前で使っている所。最初の一つだけ。
|
|
119
|
+
* 定義の文の中の現れ(株式会社GovTech東京(以下「GovTech東京」という。))は、定義する名前そのものなので数えない。札の文も数えない。
|
|
120
|
+
* 定義の条("Seller" means、「甲」とは)は条の並びの頭にも終わりにも置く書き方があるので、前で使っても言わない。
|
|
121
|
+
*/
|
|
122
|
+
export const usesBeforeDefinition = (terms: readonly DefinedTerm[], texts: readonly BodyText[]): TermUse[] =>
|
|
123
|
+
groupsOf(terms)
|
|
124
|
+
.filter((group) => group.first.inline && isDistinctive(group.first.term))
|
|
125
|
+
.flatMap(({ first: defined, spans }) => {
|
|
126
|
+
const defining = sentenceAt(texts, defined.span.start)?.start ?? defined.span.start;
|
|
127
|
+
const uses = usesOf(defined.term, texts, spans, "exact");
|
|
128
|
+
const first = uses.find((offset) => !isLabel(sentenceAt(texts, offset), defined.term));
|
|
129
|
+
return first !== undefined && first < defining ? [{ term: defined, offset: first }] : [];
|
|
130
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StructureNode, StructurePatterns } from "../plugin.ts";
|
|
2
|
+
import { buildTree, type StructureInput } from "./build.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 構造の rule(参照先が無い・番号の抜け)が読む木を、読まれたときに一度だけ作る。どの rule も読まなければ作らない。
|
|
6
|
+
* 何万行の契約書で、他の rule の lint に代金を払わせない。構造を読めない言語(patterns が無い)では無い。
|
|
7
|
+
*/
|
|
8
|
+
export const lazyTree = (patterns: StructurePatterns | undefined, input: () => StructureInput): (() => StructureNode | undefined) => {
|
|
9
|
+
// null は「作ったが構造を読めない言語だった」、undefined は「まだ作っていない」。
|
|
10
|
+
const tree: { value: StructureNode | null | undefined } = { value: undefined };
|
|
11
|
+
return () => {
|
|
12
|
+
tree.value ??= patterns === undefined ? null : buildTree(input(), patterns);
|
|
13
|
+
return tree.value ?? undefined;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Sentence, Span, Token } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
// 同じ主語と行為に、逆の様相(しなければならない と してはならない、may と must not)を書いた二つの文。
|
|
4
|
+
// 主語と行為は、文の中身の語(名詞・固有名詞・動詞・形容詞)の集まりで近似する。様相の語そのものは数えない。
|
|
5
|
+
|
|
6
|
+
/** 木の義務の節点。type は must / must-not / may。 */
|
|
7
|
+
export type Obligation = { readonly start: number; readonly end: number; readonly type: string; readonly marker: string };
|
|
8
|
+
|
|
9
|
+
export type ModalStatement = { readonly offset: number; readonly type: string; readonly marker: string; readonly key: string };
|
|
10
|
+
|
|
11
|
+
export type ModalConflict = { readonly statement: ModalStatement; readonly earlier: ModalStatement };
|
|
12
|
+
|
|
13
|
+
const CONTENT: ReadonlySet<string> = new Set(["NOUN", "PROPN", "VERB", "ADJ"]);
|
|
14
|
+
|
|
15
|
+
/** 中身の語がこれより少ない文("You must not.")は、何を決めているかが文の外にあるので比べない。 */
|
|
16
|
+
const MIN_CONTENT_WORDS = 2;
|
|
17
|
+
|
|
18
|
+
/** 互いに食い違う様相。must と may は食い違わない(しなければならないことは、してよい)。 */
|
|
19
|
+
const OPPOSITE: Readonly<Record<string, readonly string[]>> = {
|
|
20
|
+
must: ["must-not"],
|
|
21
|
+
"must-not": ["must", "may"],
|
|
22
|
+
may: ["must-not"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const overlaps = (token: Token, spans: readonly Span[]): boolean => spans.some((span) => token.span.start < span.end && span.start < token.span.end);
|
|
26
|
+
|
|
27
|
+
/** 文の中身の語を、原形の小文字で、重ねずに並べたもの。様相の語の範囲にかかる語は外す。 */
|
|
28
|
+
export const contentKey = (tokens: readonly Token[], markers: readonly Span[]): string =>
|
|
29
|
+
[
|
|
30
|
+
...new Set(
|
|
31
|
+
tokens
|
|
32
|
+
.filter((token) => CONTENT.has(token.pos) && !overlaps(token, markers))
|
|
33
|
+
.map((token) => (token.lemma !== undefined && token.lemma !== "" ? token.lemma : token.surface).toLowerCase()),
|
|
34
|
+
),
|
|
35
|
+
]
|
|
36
|
+
.toSorted((left, right) => left.localeCompare(right, "en"))
|
|
37
|
+
.join(" ");
|
|
38
|
+
|
|
39
|
+
/** offset を含む文。sentences は start の昇順で、二分探索で引く。 */
|
|
40
|
+
const sentenceAt = (sentences: readonly Sentence[], offset: number): Sentence | undefined => {
|
|
41
|
+
let low = 0;
|
|
42
|
+
let high = sentences.length - 1;
|
|
43
|
+
while (low <= high) {
|
|
44
|
+
const middle = (low + high) >> 1;
|
|
45
|
+
const sentence = sentences[middle];
|
|
46
|
+
if (sentence === undefined) return undefined;
|
|
47
|
+
if (offset < sentence.span.start) high = middle - 1;
|
|
48
|
+
else if (offset >= sentence.span.end) low = middle + 1;
|
|
49
|
+
else return sentence;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 様相の語を一つだけ持つ文の、様相と中身。二つ以上持つ文(shall do A and shall not do B)は、どの語がどの行為にかかるかが
|
|
56
|
+
* 中身の語の集まりからは分からないので外す。品詞の無い文も外す。
|
|
57
|
+
*/
|
|
58
|
+
export const modalStatements = (obligations: readonly Obligation[], sentences: readonly Sentence[]): ModalStatement[] => {
|
|
59
|
+
const bySentence = new Map<Sentence, Obligation[]>();
|
|
60
|
+
obligations.forEach((obligation) => {
|
|
61
|
+
const sentence = sentenceAt(sentences, obligation.start);
|
|
62
|
+
if (sentence === undefined) return;
|
|
63
|
+
const found = bySentence.get(sentence);
|
|
64
|
+
if (found === undefined) bySentence.set(sentence, [obligation]);
|
|
65
|
+
else found.push(obligation);
|
|
66
|
+
});
|
|
67
|
+
return [...bySentence].flatMap(([sentence, found]) => {
|
|
68
|
+
const [only] = found;
|
|
69
|
+
if (found.length !== 1 || only === undefined || sentence.tokens === undefined) return [];
|
|
70
|
+
const key = contentKey(sentence.tokens, [only]);
|
|
71
|
+
return key.split(" ").length < MIN_CONTENT_WORDS ? [] : [{ offset: sentence.span.start, type: only.type, marker: only.marker, key }];
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** 前に同じ中身で逆の様相を書いた文がある文。中身と様相の組ごとに、最初の文と比べる。 */
|
|
76
|
+
export const modalConflicts = (statements: readonly ModalStatement[]): ModalConflict[] => {
|
|
77
|
+
const firsts = new Map<string, ModalStatement>();
|
|
78
|
+
return statements
|
|
79
|
+
.toSorted((left, right) => left.offset - right.offset)
|
|
80
|
+
.flatMap((statement) => {
|
|
81
|
+
const earlier = (OPPOSITE[statement.type] ?? []).map((type) => firsts.get(`${type}\u0000${statement.key}`)).find((found) => found !== undefined);
|
|
82
|
+
const own = `${statement.type}\u0000${statement.key}`;
|
|
83
|
+
if (!firsts.has(own)) firsts.set(own, statement);
|
|
84
|
+
return earlier === undefined ? [] : [{ statement, earlier }];
|
|
85
|
+
});
|
|
86
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { StructureNode } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A numbered chapter (第1章), or an article numbered at the top level (第3条, 「1 適用範囲」, "2. Payment"). Clauses
|
|
5
|
+
* number from 1 at the top, so a tree whose articles are all dotted (## 0.18.0, ## 1.0 in a changelog) holds versions,
|
|
6
|
+
* not clauses. A 号 item or a heading is not a clause.
|
|
7
|
+
*/
|
|
8
|
+
const isClause = (node: StructureNode): boolean => node.kind === "chapter" || (node.kind === "article" && !node.address.includes("."));
|
|
9
|
+
|
|
10
|
+
/** Pure: whether the tree holds a numbered clause anywhere. */
|
|
11
|
+
export const numbersClauses = (node: StructureNode): boolean => isClause(node) || node.children.some(numbersClauses);
|
|
@@ -5,7 +5,12 @@ import { numberInSentence } from "./number-in-sentence.ts";
|
|
|
5
5
|
* 「4.2 設定」「3.1.4 Scope」のような通し番号。数字と点だけで書くので言語を問わない。
|
|
6
6
|
* 各部分を 3 桁・深さを 6 までに抑え、後戻りが爆発しない形にしてある。
|
|
7
7
|
*/
|
|
8
|
-
const DOTTED = /^[ \t]{0,3}(?<number>\d{1,3}(?:\.\d{1,3}){0,5})(?<closing
|
|
8
|
+
const DOTTED = /^[ \t]{0,3}(?<number>\d{1,3}(?:\.\d{1,3}){0,5})(?<closing>[..])?[ \t\u3000]+(?<rest>\S.*)$/u;
|
|
9
|
+
/**
|
|
10
|
+
* 見出しで、点で閉じた番号に題を詰めて書いたもの(「7.委託」「7.委託」「2.Overview」)。後ろが数字なら小数(「1.5万人」)、
|
|
11
|
+
* 小文字なら版(「2.x 系」)なので読まない。
|
|
12
|
+
*/
|
|
13
|
+
const TIGHT_HEADING = /^[ \t]{0,3}(?<number>\d{1,3}(?:\.\d{1,3}){0,5})(?<closing>[..])(?<rest>[^\s\d\p{Ll}..].*)$/u;
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* RFC のようなテキストの仕様書(Markdown でないもの)の章見出し「5. Security Considerations」。Markdown では、行内のコードを伏せた空白が
|
|
@@ -18,6 +23,9 @@ const MAX_TITLE = 80;
|
|
|
18
23
|
|
|
19
24
|
const isTopSection = (line: string, rest: string): boolean => TOP_SECTION.test(line) && rest.length <= MAX_TITLE && !SENTENCE_END.test(rest);
|
|
20
25
|
|
|
26
|
+
const dottedGroups = (line: string, context: NumberingContext): Readonly<Record<string, string | undefined>> | undefined =>
|
|
27
|
+
DOTTED.exec(line)?.groups ?? (context.isHeading ? TIGHT_HEADING.exec(line)?.groups : undefined);
|
|
28
|
+
|
|
21
29
|
/** closedByDot: 番号を点で閉じたか(「5. 」)。「1.5 万人」の 1.5 は閉じていない。 */
|
|
22
30
|
type DottedLine = NumberedLine & { readonly closedByDot: boolean };
|
|
23
31
|
|
|
@@ -26,7 +34,7 @@ type DottedLine = NumberedLine & { readonly closedByDot: boolean };
|
|
|
26
34
|
* 「4.2 」のように点を含むものは、.txt の仕様書でも見出しとして書かれるので本文でも読む。
|
|
27
35
|
*/
|
|
28
36
|
export const dottedNumber = (line: string, context: NumberingContext, plainText = false): DottedLine | undefined => {
|
|
29
|
-
const groups =
|
|
37
|
+
const groups = dottedGroups(line, context);
|
|
30
38
|
const number = groups?.["number"];
|
|
31
39
|
if (number === undefined) return undefined;
|
|
32
40
|
const depth = number.split(".").length;
|
package/src/style-parse.ts
CHANGED
|
@@ -6,6 +6,9 @@ import type { Level, Localized } from "./plugin.ts";
|
|
|
6
6
|
|
|
7
7
|
export type StyleSource = { readonly title: Localized; readonly url: string };
|
|
8
8
|
|
|
9
|
+
/** { rule id: { language: limit } }. A guideline gives a number for one language (60 characters for Japanese) and none for another. */
|
|
10
|
+
export type StyleLimits = Readonly<Record<string, Readonly<Record<string, number>>>>;
|
|
11
|
+
|
|
9
12
|
export type StyleDefinition = {
|
|
10
13
|
readonly id: string;
|
|
11
14
|
readonly name: Localized;
|
|
@@ -16,6 +19,8 @@ export type StyleDefinition = {
|
|
|
16
19
|
readonly rules: Readonly<Record<string, Level>>;
|
|
17
20
|
/** { rule id: { option: value } }, checked against the rules' declared options by the tests. */
|
|
18
21
|
readonly options: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
22
|
+
/** The numbers the guideline sets, by language. The rule needs a level under rules too, or the limit decides nothing. */
|
|
23
|
+
readonly limits: StyleLimits;
|
|
19
24
|
};
|
|
20
25
|
|
|
21
26
|
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -57,12 +62,41 @@ const optionsOf = (value: unknown, where: string): Record<string, Readonly<Recor
|
|
|
57
62
|
);
|
|
58
63
|
};
|
|
59
64
|
|
|
65
|
+
const isLimit = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
66
|
+
|
|
67
|
+
const languageLimitsOf = (id: string, value: unknown, where: string): Record<string, number> => {
|
|
68
|
+
if (!isRecord(value) || Object.keys(value).length === 0) throw new Error(`${where}: limits.${id} must be a map of languages to numbers`);
|
|
69
|
+
return Object.fromEntries(
|
|
70
|
+
Object.entries(value).map(([language, limit]) => {
|
|
71
|
+
if (!isLimit(limit)) throw new Error(`${where}: limits.${id}.${language} must be a number above 0`);
|
|
72
|
+
return [language, limit];
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const limitsOf = (value: unknown, rules: Readonly<Record<string, Level>>, where: string): Record<string, Record<string, number>> => {
|
|
78
|
+
if (value === undefined) return {};
|
|
79
|
+
if (!isRecord(value)) throw new Error(`${where}: limits must be a map of rule ids`);
|
|
80
|
+
return Object.fromEntries(
|
|
81
|
+
Object.entries(value).map(([id, byLanguage]) => {
|
|
82
|
+
if (rules[id] === undefined || rules[id] === "off") throw new Error(`${where}: limits.${id} needs ${id} under rules at a level that runs`);
|
|
83
|
+
return [id, languageLimitsOf(id, byLanguage, where)];
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
60
88
|
/** One style file. Throws on anything it cannot read: the file ships with chaff, so a wrong entry is chaff's bug. */
|
|
61
89
|
export const parseStyle = (raw: unknown, file: string): StyleDefinition => {
|
|
62
|
-
|
|
90
|
+
const id = isRecord(raw) ? raw["id"] : undefined;
|
|
91
|
+
if (isRecord(raw) && (typeof id !== "string" || `${id}.yaml` !== file)) throw new Error(`${file}: id must be the file's name without .yaml`);
|
|
92
|
+
return styleOf(raw, file);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** One style, wherever it was written (where names it in what is thrown). Throws on anything it cannot read. */
|
|
96
|
+
export const styleOf = (raw: unknown, where: string): StyleDefinition => {
|
|
97
|
+
if (!isRecord(raw)) throw new Error(`${where}: a style must be a map`);
|
|
63
98
|
const id = raw["id"];
|
|
64
|
-
if (typeof id !== "string" ||
|
|
65
|
-
const where = `${file}`;
|
|
99
|
+
if (typeof id !== "string" || id === "") throw new Error(`${where}: a style needs an id`);
|
|
66
100
|
const rules = levelsOf(raw["rules"], where);
|
|
67
101
|
const options = optionsOf(raw["options"], where);
|
|
68
102
|
if (Object.keys(rules).length === 0 && Object.keys(options).length === 0) throw new Error(`${where}: a style must set rules or options`);
|
|
@@ -73,5 +107,6 @@ export const parseStyle = (raw: unknown, file: string): StyleDefinition => {
|
|
|
73
107
|
source: sourceOf(raw["source"], where),
|
|
74
108
|
rules,
|
|
75
109
|
options,
|
|
110
|
+
limits: limitsOf(raw["limits"], rules, where),
|
|
76
111
|
};
|
|
77
112
|
};
|