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
package/src/document.ts
CHANGED
|
@@ -5,9 +5,11 @@ import { maskSpans } from "./mask.ts";
|
|
|
5
5
|
import { spansWithin, unmaskedSoftBreaks } from "./soft-break.ts";
|
|
6
6
|
import { segmentJoined } from "./joined-view.ts";
|
|
7
7
|
import { documentLineParagraphs } from "./line-paragraphs.ts";
|
|
8
|
-
import { subheadingPieces } from "./subheading-line.ts";
|
|
8
|
+
import { closingRun, standaloneLines, subheadingPieces, type StandsAlone } from "./subheading-line.ts";
|
|
9
|
+
import { isLinkLine } from "./link-line.ts";
|
|
9
10
|
import { speakerLabels } from "./speaker-labels.ts";
|
|
10
|
-
import {
|
|
11
|
+
import type { Outline } from "./structure/build.ts";
|
|
12
|
+
import { lazyTree } from "./structure/lazy-tree.ts";
|
|
11
13
|
import { isMarkdownPath } from "./structure/markdown-path.ts";
|
|
12
14
|
import { layoutMasks, textOutline } from "./page-furniture.ts";
|
|
13
15
|
import { tokenizedLexicons } from "./lexicon-tokens.ts";
|
|
@@ -98,7 +100,7 @@ const collectMasks = (root: Node, source: string, anchors: InPageAnchors, syntax
|
|
|
98
100
|
return spans;
|
|
99
101
|
};
|
|
100
102
|
|
|
101
|
-
const emphasisSpans = (root: Node, source: string): Span[] => {
|
|
103
|
+
export const emphasisSpans = (root: Node, source: string): Span[] => {
|
|
102
104
|
const spans: Span[] = [];
|
|
103
105
|
eachPreOrder(root, (node) => {
|
|
104
106
|
if (EMPHASIS.has(node.type)) spans.push(...emphasisChrome(node, source));
|
|
@@ -171,9 +173,54 @@ const shift = (span: Span, by: number): Span => ({ start: by + span.start, end:
|
|
|
171
173
|
|
|
172
174
|
const breaksWithin = (breaks: readonly Span[], paragraph: Span): Span[] => spansWithin(breaks, paragraph).map((span) => shift(span, -paragraph.start));
|
|
173
175
|
|
|
174
|
-
/**
|
|
175
|
-
const
|
|
176
|
-
|
|
176
|
+
/** 段落の中の行(段落の中の位置)が、一つのリンクだけの行か。 */
|
|
177
|
+
const linkLineIn =
|
|
178
|
+
(source: string, links: readonly Span[], paragraph: Span): StandsAlone =>
|
|
179
|
+
(start, end) =>
|
|
180
|
+
isLinkLine(source, shift({ start, end }, paragraph.start), links);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 段落を、中の小見出しの行(「(経済再生)」)とリンクだけの行の後ろで切った片。片ごとに分割すれば、
|
|
184
|
+
* 小見出しが次の文に入らず、1 行ずつ並べたリンクが一つの長い文にならない。
|
|
185
|
+
*/
|
|
186
|
+
const piecesOf = (prose: string, paragraphs: readonly Span[], source: string, links: readonly Span[]): Span[] =>
|
|
187
|
+
paragraphs.flatMap((paragraph) =>
|
|
188
|
+
subheadingPieces(prose.slice(paragraph.start, paragraph.end), linkLineIn(source, links, paragraph)).map((piece) => shift(piece, paragraph.start)),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 段落の終わりまで続く、一つで立つリンクだけの行(文書の位置)。記事の一覧のように本文の後に 1 行ずつ並べたリンクは、
|
|
193
|
+
* 箇条書きの項目と同じに読む。段落の途中のリンクの行は、その段落の文の一つのまま。
|
|
194
|
+
*/
|
|
195
|
+
const linkItemsOf = (prose: string, paragraphs: readonly Span[], source: string, links: readonly Span[]): Span[] =>
|
|
196
|
+
paragraphs.flatMap((paragraph) => {
|
|
197
|
+
const isLink = linkLineIn(source, links, paragraph);
|
|
198
|
+
const text = prose.slice(paragraph.start, paragraph.end);
|
|
199
|
+
const linkLines = standaloneLines(text, isLink).filter((line) => isLink(line.start, line.end));
|
|
200
|
+
return closingRun(linkLines, text.length).map((line) => shift({ start: line.start, end: line.end }, paragraph.start));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* links: リンク。pieces: 文を分ける片。listSpans: 箇条書きの項目と同じに読む範囲(Markdown の項目と、1 行ずつ並べたリンクの行)。
|
|
205
|
+
* paragraphs: 段落として数える範囲。リンクの行を切り取る(「関連記事:」に続くリンクの行は、箇条書きと同じく段落の外)。
|
|
206
|
+
*/
|
|
207
|
+
type LineLayout = {
|
|
208
|
+
readonly links: readonly Span[];
|
|
209
|
+
readonly pieces: readonly Span[];
|
|
210
|
+
readonly listSpans: readonly Span[];
|
|
211
|
+
readonly paragraphs: readonly Span[];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const lineLayoutOf = (root: Node, source: string, prose: string, paragraphs: readonly Span[]): LineLayout => {
|
|
215
|
+
const links = [...spansOfType(root, "link"), ...spansOfType(root, "linkReference")];
|
|
216
|
+
const linkItems = linkItemsOf(prose, paragraphs, source, links);
|
|
217
|
+
return {
|
|
218
|
+
links,
|
|
219
|
+
pieces: piecesOf(prose, paragraphs, source, links),
|
|
220
|
+
listSpans: [...spansOfType(root, "listItem"), ...linkItems],
|
|
221
|
+
paragraphs: cutTextSpans(paragraphs, linkItems, source),
|
|
222
|
+
};
|
|
223
|
+
};
|
|
177
224
|
|
|
178
225
|
const sentencesOf = (prose: string, paragraphs: readonly Span[], adapter: LanguageAdapter, softBreaks: readonly Span[]): Sentence[] =>
|
|
179
226
|
paragraphs.flatMap((paragraph) =>
|
|
@@ -216,11 +263,12 @@ const listsOf = (root: Node, source: string, anchors: InPageAnchors): BulletList
|
|
|
216
263
|
if (node.type !== "list" || isNavigationList(node, anchors)) return;
|
|
217
264
|
const span = spanOf(node);
|
|
218
265
|
if (span === undefined) return;
|
|
219
|
-
const
|
|
266
|
+
const itemSpans = (node.children ?? []).flatMap((child) => {
|
|
220
267
|
const item = spanOf(child);
|
|
221
|
-
return child.type === "listItem" && item !== undefined ? [
|
|
268
|
+
return child.type === "listItem" && item !== undefined ? [item] : [];
|
|
222
269
|
});
|
|
223
|
-
|
|
270
|
+
const items = itemSpans.map((item) => source.slice(item.start, item.end).replace(/\s+/gu, "").length);
|
|
271
|
+
if (items.length > 0) found.push({ span, items, itemSpans });
|
|
224
272
|
});
|
|
225
273
|
return found;
|
|
226
274
|
};
|
|
@@ -233,21 +281,28 @@ export type TeamRules = {
|
|
|
233
281
|
readonly prefer?: Readonly<Record<string, string>>;
|
|
234
282
|
/** チームの固有名詞。1 つの名前として読む rule が見る。 */
|
|
235
283
|
readonly names?: readonly string[];
|
|
284
|
+
/** The word lists the plugins ship for the document's language, by name. */
|
|
285
|
+
readonly lexicons?: LanguageAdapter["lexicons"];
|
|
236
286
|
};
|
|
237
287
|
|
|
238
288
|
const EMPTY_TEAM: TeamRules = { jargon: [], requiredSections: [] };
|
|
239
289
|
|
|
240
|
-
/** Config から取り出す。document は Config の形を知らない。 */
|
|
241
|
-
export const teamRules = (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
290
|
+
/** Config から取り出す。document は Config の形を知らない。language: the document's, for the plugins' word lists. */
|
|
291
|
+
export const teamRules = (
|
|
292
|
+
config: {
|
|
293
|
+
readonly jargon: readonly string[];
|
|
294
|
+
readonly requiredSections: readonly string[];
|
|
295
|
+
readonly prefer?: Readonly<Record<string, string>>;
|
|
296
|
+
readonly names?: readonly string[];
|
|
297
|
+
readonly extensions?: { readonly lexicons: Readonly<Record<string, LanguageAdapter["lexicons"]>> };
|
|
298
|
+
},
|
|
299
|
+
language?: string,
|
|
300
|
+
): TeamRules => ({
|
|
247
301
|
jargon: config.jargon,
|
|
248
302
|
prefer: config.prefer ?? {},
|
|
249
303
|
requiredSections: config.requiredSections,
|
|
250
304
|
names: config.names ?? [],
|
|
305
|
+
lexicons: language === undefined ? {} : (config.extensions?.lexicons[language] ?? {}),
|
|
251
306
|
});
|
|
252
307
|
|
|
253
308
|
/**
|
|
@@ -261,6 +316,7 @@ const proseOf = (source: string, masked: readonly Span[]): string => {
|
|
|
261
316
|
|
|
262
317
|
const teamLexicons = (adapter: LanguageAdapter, team: TeamRules): LanguageAdapter["lexicons"] => ({
|
|
263
318
|
...adapter.lexicons,
|
|
319
|
+
...team.lexicons,
|
|
264
320
|
"internal-jargon": team.jargon.map((pattern) => ({ pattern })),
|
|
265
321
|
// 使わない書き方を pattern に、使う書き方を instead_of に置く。語彙表の「同じことの別の書き方」と同じ向き。
|
|
266
322
|
"preferred-term": Object.entries(team.prefer ?? {}).map(([pattern, use]) => ({ pattern, instead_of: use })),
|
|
@@ -283,15 +339,21 @@ const documentOf = (path: string, source: string, adapter: LanguageAdapter, team
|
|
|
283
339
|
source,
|
|
284
340
|
);
|
|
285
341
|
const headings = headingsOf(root, source, maskSpans(source, syntax), emailLayout.replyQuotes);
|
|
286
|
-
const listItems = spansOfType(root, "listItem");
|
|
287
342
|
// Markdown は段落を流し込んで表示するので、段落の中の改行は読み手に見えない。テキストの文書は行をそのまま見せる(法令は 1 行 1 号)。
|
|
288
343
|
const softBreaks = markdown ? unmaskedSoftBreaks(source, prose) : [];
|
|
289
|
-
const
|
|
344
|
+
const layout = lineLayoutOf(root, source, prose, paragraphSpans);
|
|
345
|
+
const sentences = sentencesOf(prose, layout.pieces, adapter, softBreaks);
|
|
290
346
|
const lexicons = teamLexicons(adapter, team);
|
|
291
347
|
const tagged = sentences.some((sentence) => sentence.tokens !== undefined);
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
348
|
+
const structure = lazyTree(adapter.structure, () => ({
|
|
349
|
+
path,
|
|
350
|
+
source,
|
|
351
|
+
language: adapter.id,
|
|
352
|
+
// テキストの文書は、ページの飾りを覆って読む。フッターの「Section 9」を木の節にしない。
|
|
353
|
+
outline: markdown ? outlineOf(root, source, syntax, emailLayout.replyQuotes) : textOutline(source, emailLayout.replyQuotes),
|
|
354
|
+
markdown,
|
|
355
|
+
profile,
|
|
356
|
+
}));
|
|
295
357
|
return {
|
|
296
358
|
path,
|
|
297
359
|
source,
|
|
@@ -300,34 +362,19 @@ const documentOf = (path: string, source: string, adapter: LanguageAdapter, team
|
|
|
300
362
|
capabilities: adapter.capabilities,
|
|
301
363
|
sections: sectionsOf(headings, sentences, strongSpans(root, blocks), source.length, headingReadersOf(adapter, tagged, lexicons)),
|
|
302
364
|
sentences,
|
|
303
|
-
listSpans:
|
|
304
|
-
paragraphs: paragraphsOf(prose,
|
|
365
|
+
listSpans: layout.listSpans,
|
|
366
|
+
paragraphs: paragraphsOf(prose, layout.paragraphs, sentences, layout.listSpans),
|
|
305
367
|
lists: listsOf(root, source, anchors),
|
|
306
|
-
links:
|
|
368
|
+
links: layout.links,
|
|
307
369
|
lexicons: tagged ? tokenizedLexicons(lexicons, adapter) : lexicons,
|
|
308
370
|
requiredSections: team.requiredSections,
|
|
309
371
|
names: team.names ?? [],
|
|
310
|
-
// 構造の rule(参照先が無い・番号の抜け)が読む木。どの rule も読まなければ作らない。何万行の契約書で、他の rule の lint に代金を払わせない。
|
|
311
372
|
get structure(): StructureNode | undefined {
|
|
312
|
-
|
|
313
|
-
patterns === undefined
|
|
314
|
-
? null
|
|
315
|
-
: buildTree(
|
|
316
|
-
{
|
|
317
|
-
path,
|
|
318
|
-
source,
|
|
319
|
-
language: adapter.id,
|
|
320
|
-
// テキストの文書は、ページの飾りを覆って読む。フッターの「Section 9」を木の節にしない。
|
|
321
|
-
outline: markdown ? outlineOf(root, source, syntax, emailLayout.replyQuotes) : textOutline(source, emailLayout.replyQuotes),
|
|
322
|
-
markdown: isMarkdownPath(path),
|
|
323
|
-
profile,
|
|
324
|
-
},
|
|
325
|
-
patterns,
|
|
326
|
-
);
|
|
327
|
-
return tree.value ?? undefined;
|
|
373
|
+
return structure();
|
|
328
374
|
},
|
|
329
375
|
profile,
|
|
330
376
|
prose,
|
|
377
|
+
namedDocument: adapter.structure?.namedDocument,
|
|
331
378
|
replyQuotes: emailLayout.replyQuotes,
|
|
332
379
|
get markup(): Markup {
|
|
333
380
|
return documentMarkup(root, source, markdown, [...emailLayout.replyQuotes, ...syntax]);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type * as Api from "../api.ts";
|
|
2
|
+
import type { BulletList, Lexicon, Markup, Paragraph, ProseDocument, Section, Sentence, Span, Token } from "../plugin.ts";
|
|
3
|
+
|
|
4
|
+
// The document as the plugin API shows it (api.ts RuleDocument): copies of only the public fields, frozen. A copy, so
|
|
5
|
+
// chaff's own shapes can change without breaking a plugin; frozen, so a plugin's rule cannot change what the next rule reads.
|
|
6
|
+
|
|
7
|
+
const freezeAll = (value: unknown): void => {
|
|
8
|
+
if (typeof value !== "object" || value === null || Object.isFrozen(value)) return;
|
|
9
|
+
Object.values(value).forEach(freezeAll);
|
|
10
|
+
Object.freeze(value);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const deepFreeze = <T>(value: T): T => {
|
|
14
|
+
freezeAll(value);
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const spanOf = (span: Span): Api.Span => ({ start: span.start, end: span.end });
|
|
19
|
+
|
|
20
|
+
const tokenOf = (token: Token): Api.Token => ({
|
|
21
|
+
span: spanOf(token.span),
|
|
22
|
+
surface: token.surface,
|
|
23
|
+
pos: token.pos,
|
|
24
|
+
...(token.lemma === undefined ? {} : { lemma: token.lemma }),
|
|
25
|
+
...(token.reading === undefined ? {} : { reading: token.reading }),
|
|
26
|
+
...(token.features === undefined ? {} : { features: { ...token.features } }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** Sentences are shared by the document, its paragraphs and its sections; each is copied once, so they stay one object. */
|
|
30
|
+
type SentenceCopies = (sentence: Sentence) => Api.Sentence;
|
|
31
|
+
|
|
32
|
+
const sentenceCopier = (): SentenceCopies => {
|
|
33
|
+
const copies = new Map<Sentence, Api.Sentence>();
|
|
34
|
+
return (sentence) => {
|
|
35
|
+
const known = copies.get(sentence);
|
|
36
|
+
if (known !== undefined) return known;
|
|
37
|
+
const copy = { span: spanOf(sentence.span), text: sentence.text, ...(sentence.tokens === undefined ? {} : { tokens: sentence.tokens.map(tokenOf) }) };
|
|
38
|
+
copies.set(sentence, copy);
|
|
39
|
+
return copy;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const paragraphOf = (paragraph: Paragraph, copy: SentenceCopies): Api.Paragraph => ({
|
|
44
|
+
span: spanOf(paragraph.span),
|
|
45
|
+
sentences: paragraph.sentences.map(copy),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const sectionOf = (section: Section, copy: SentenceCopies): Api.Section => ({
|
|
49
|
+
depth: section.depth,
|
|
50
|
+
heading: section.heading,
|
|
51
|
+
span: spanOf(section.span),
|
|
52
|
+
sentences: section.sentences.map(copy),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const listOf = (list: BulletList): Api.List => ({ span: spanOf(list.span), itemLengths: [...list.items] });
|
|
56
|
+
|
|
57
|
+
const lexiconOf = (lexicon: Lexicon): Api.Lexicon =>
|
|
58
|
+
lexicon.map((entry) => ({ pattern: entry.pattern, ...(entry.instead_of === undefined ? {} : { instead_of: entry.instead_of }) }));
|
|
59
|
+
|
|
60
|
+
const markupOf = (markup: Markup): Api.Markup => ({
|
|
61
|
+
markdown: markup.markdown,
|
|
62
|
+
headings: markup.headings.map((heading) => ({ depth: heading.depth, text: heading.text, span: { start: heading.start, end: heading.end } })),
|
|
63
|
+
images: markup.images.map((image) => ({ alt: image.alt, span: { start: image.start, end: image.end } })),
|
|
64
|
+
links: markup.links.map((link) => ({ destination: link.destination, span: { start: link.start, end: link.end } })),
|
|
65
|
+
ids: [...markup.ids].toSorted((left, right) => left.localeCompare(right, "en")),
|
|
66
|
+
texts: markup.texts.map(spanOf),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const EMPTY_MARKUP: Markup = { markdown: false, headings: [], images: [], links: [], ids: new Set(), texts: [] };
|
|
70
|
+
|
|
71
|
+
const viewOf = (doc: ProseDocument): Api.RuleDocument => {
|
|
72
|
+
const copy = sentenceCopier();
|
|
73
|
+
const fields = deepFreeze({
|
|
74
|
+
path: doc.path,
|
|
75
|
+
source: doc.source,
|
|
76
|
+
language: doc.language,
|
|
77
|
+
lengthUnit: doc.lengthUnit,
|
|
78
|
+
sentences: doc.sentences.map(copy),
|
|
79
|
+
paragraphs: doc.paragraphs.map((paragraph) => paragraphOf(paragraph, copy)),
|
|
80
|
+
sections: doc.sections.map((section) => sectionOf(section, copy)),
|
|
81
|
+
lists: doc.lists.map(listOf),
|
|
82
|
+
listItems: doc.listSpans.map(spanOf),
|
|
83
|
+
links: doc.links.map(spanOf),
|
|
84
|
+
lexicons: Object.fromEntries(Object.entries(doc.lexicons).map(([name, lexicon]) => [name, lexiconOf(lexicon)])),
|
|
85
|
+
});
|
|
86
|
+
// The markup is read from the Markdown tree only when a rule asks for it, as chaff's own rules do.
|
|
87
|
+
const markup: { value: Api.Markup | undefined } = { value: undefined };
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
...fields,
|
|
90
|
+
get markup(): Api.Markup {
|
|
91
|
+
markup.value ??= deepFreeze(markupOf(doc.markup ?? EMPTY_MARKUP));
|
|
92
|
+
return markup.value;
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** A word list as a detector is given it (DetectorOptions.lexicon): public fields only, frozen. */
|
|
98
|
+
export const frozenLexiconOf = (lexicon: Lexicon): Api.Lexicon => deepFreeze(lexiconOf(lexicon));
|
|
99
|
+
|
|
100
|
+
const views = new WeakMap<ProseDocument, Api.RuleDocument>();
|
|
101
|
+
|
|
102
|
+
/** The document as a plugin's detector sees it. Made once per document, however many plugin rules read it. */
|
|
103
|
+
export const ruleDocumentOf = (doc: ProseDocument): Api.RuleDocument => {
|
|
104
|
+
const known = views.get(doc);
|
|
105
|
+
if (known !== undefined) return known;
|
|
106
|
+
const view = viewOf(doc);
|
|
107
|
+
views.set(doc, view);
|
|
108
|
+
return view;
|
|
109
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
2
|
+
import type { RuleFailure } from "./module-detector.ts";
|
|
3
|
+
import type { ShapeProblem } from "./returned-findings.ts";
|
|
4
|
+
|
|
5
|
+
// Why a plugin's rule is listed as not run on a document, in the reader's language. It names where the detector came
|
|
6
|
+
// from, so the person who wrote it knows which file to open.
|
|
7
|
+
|
|
8
|
+
type Text = {
|
|
9
|
+
readonly reason: (origin: string, what: string) => string;
|
|
10
|
+
readonly threw: (message: string) => string;
|
|
11
|
+
/** Each wrong return, with the finding's number and what was there. */
|
|
12
|
+
readonly shape: Readonly<Record<ShapeProblem["kind"], (index: string, written: string) => string>>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const TEXT: Texts<Text> = {
|
|
16
|
+
ja: {
|
|
17
|
+
reason: (origin, what) => `${origin} の検出器が${what}ため`,
|
|
18
|
+
threw: (message) => `エラーを投げた(${message})`,
|
|
19
|
+
shape: {
|
|
20
|
+
"not-a-list": (_index, written) => `指摘の並びでなく ${written} を返した`,
|
|
21
|
+
"not-a-finding": (index, written) => `${index} 番目の指摘に { start } の形でないもの(${written})を返した`,
|
|
22
|
+
"bad-start": (index, written) => `${index} 番目の指摘の start に ${written} を返した(文書の中の位置を整数で返します)`,
|
|
23
|
+
"bad-end": (index, written) => `${index} 番目の指摘の end に ${written} を返した(start 以上、文書の長さ以下の整数で返します)`,
|
|
24
|
+
"bad-values": (index, written) => `${index} 番目の指摘の values に文字列と数でない値を入れた(${written})`,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
en: {
|
|
28
|
+
reason: (origin, what) => `the detector in ${origin} ${what}`,
|
|
29
|
+
threw: (message) => `threw an error (${message})`,
|
|
30
|
+
shape: {
|
|
31
|
+
"not-a-list": (_index, written) => `returned ${written}, not a list of findings`,
|
|
32
|
+
"not-a-finding": (index, written) => `returned ${written} as finding ${index}, not a { start } finding`,
|
|
33
|
+
"bad-start": (index, written) => `returned start ${written} in finding ${index} (an offset in the document, a whole number)`,
|
|
34
|
+
"bad-end": (index, written) => `returned end ${written} in finding ${index} (a whole number from start to the document's length)`,
|
|
35
|
+
"bad-values": (index, written) => `put a value that is not a string or a number in finding ${index}'s values (${written})`,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** The reason a rule whose detector failed is listed as not run. */
|
|
41
|
+
export const failureReason = (origin: string, failure: RuleFailure, ui: UiLanguage): string => {
|
|
42
|
+
const text = TEXT[ui];
|
|
43
|
+
if (failure.kind === "threw") return text.reason(origin, text.threw(failure.message));
|
|
44
|
+
const { kind, index, written } = failure.problem;
|
|
45
|
+
return text.reason(origin, text.shape[kind](String(index), written));
|
|
46
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
|
|
3
|
+
const firstLine = (error: unknown): string => (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
4
|
+
|
|
5
|
+
/** A module's default export, or the first line of why it could not be imported (a syntax error, a missing import). Running it runs its code. */
|
|
6
|
+
export const importDefault = async (file: string): Promise<{ readonly exported: unknown } | { readonly message: string }> => {
|
|
7
|
+
try {
|
|
8
|
+
const namespace: unknown = await import(pathToFileURL(file).href);
|
|
9
|
+
return { exported: typeof namespace === "object" && namespace !== null && "default" in namespace ? namespace.default : undefined };
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return { message: firstLine(error) };
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import type { Config } from "../config/load.ts";
|
|
3
|
+
import type { Detector, Lexicon, RuleDefinition } from "../plugin.ts";
|
|
4
|
+
import type { StyleDefinition } from "../style-parse.ts";
|
|
5
|
+
import { customRulesOf } from "../custom/load.ts";
|
|
6
|
+
import { withStyle } from "../config/style.ts";
|
|
7
|
+
import { moduleDetector } from "./module-detector.ts";
|
|
8
|
+
import { detectorExport, type ExportProblem } from "./rule-export.ts";
|
|
9
|
+
import { importDefault } from "./import-default.ts";
|
|
10
|
+
import { loadPlugins } from "./plugin-load.ts";
|
|
11
|
+
import type { ParsedPlugin, PluginProblem } from "./plugin-parse.ts";
|
|
12
|
+
import { staysInside } from "./real-path.ts";
|
|
13
|
+
|
|
14
|
+
// The code chaff.yaml names, loaded once before anything runs: each type: module rule's file, and each plugin under
|
|
15
|
+
// plugins:. Loading runs that code, which is why only chaff.yaml can name it. What cannot be loaded stops the run
|
|
16
|
+
// (settingProblems), like a rule in custom_rules that cannot be read: a rule that silently does not run looks like a clean document.
|
|
17
|
+
|
|
18
|
+
/** A rule whose file could not be loaded. file is the path as chaff.yaml writes it; detail: why, or what ExportProblem says. */
|
|
19
|
+
export type LoadProblem = {
|
|
20
|
+
readonly kind: "missing-file" | "outside" | "import-failed" | ExportProblem["kind"];
|
|
21
|
+
readonly rule: string;
|
|
22
|
+
readonly file: string;
|
|
23
|
+
readonly detail: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** What was loaded, and what could not be. */
|
|
27
|
+
export type Extensions = {
|
|
28
|
+
/** The detector of each type: module rule and each plugin rule written in code, by the rule's id. */
|
|
29
|
+
readonly detectors: Readonly<Record<string, Detector>>;
|
|
30
|
+
/** The plugins' rules, with their ids under each plugin's name. */
|
|
31
|
+
readonly rules: readonly RuleDefinition[];
|
|
32
|
+
/** The plugins' word lists by language, then by name. */
|
|
33
|
+
readonly lexicons: Readonly<Record<string, Readonly<Record<string, Lexicon>>>>;
|
|
34
|
+
/** The plugins' house styles. */
|
|
35
|
+
readonly styles: readonly StyleDefinition[];
|
|
36
|
+
readonly problems: readonly LoadProblem[];
|
|
37
|
+
readonly pluginProblems: readonly PluginProblem[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type Loaded = { readonly detector: Detector } | { readonly problem: LoadProblem };
|
|
41
|
+
|
|
42
|
+
/** A type: module rule: its id, its file, and the path as chaff.yaml writes it, which every message names. */
|
|
43
|
+
type ModuleRule = { readonly id: string; readonly file: string; readonly written: string };
|
|
44
|
+
|
|
45
|
+
const loadRule = async ({ id, file, written }: ModuleRule, baseDir: string): Promise<Loaded> => {
|
|
46
|
+
const at = { rule: id, file: written };
|
|
47
|
+
if (!existsSync(file)) return { problem: { ...at, kind: "missing-file", detail: "" } };
|
|
48
|
+
if (!staysInside(written, file, baseDir)) return { problem: { ...at, kind: "outside", detail: "" } };
|
|
49
|
+
const imported = await importDefault(file);
|
|
50
|
+
if ("message" in imported) return { problem: { ...at, kind: "import-failed", detail: imported.message } };
|
|
51
|
+
const read = detectorExport(imported.exported);
|
|
52
|
+
return "problem" in read ? { problem: { ...at, ...read.problem } } : { detector: moduleDetector(read.detect, written) };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const moduleRulesOf = (rules: readonly RuleDefinition[]): ModuleRule[] =>
|
|
56
|
+
rules.flatMap((rule) => (rule.custom?.type === "module" ? [{ id: rule.id, file: rule.custom.file, written: rule.custom.module }] : []));
|
|
57
|
+
|
|
58
|
+
/** Every type: module rule in chaff.yaml, loaded one by one in the order written, so the same chaff.yaml loads the same way. */
|
|
59
|
+
const loadModuleRules = async (config: Config): Promise<readonly { readonly id: string; readonly loaded: Loaded }[]> =>
|
|
60
|
+
moduleRulesOf(customRulesOf(config).rules).reduce<Promise<{ id: string; loaded: Loaded }[]>>(
|
|
61
|
+
async (done, rule) => [...(await done), { id: rule.id, loaded: await loadRule(rule, config.baseDir) }],
|
|
62
|
+
Promise.resolve([]),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/** A plugin's code rules, each run as one of chaff's; a failure names the plugin as chaff.yaml writes it. */
|
|
66
|
+
const pluginDetectors = (plugin: ParsedPlugin): [string, Detector][] =>
|
|
67
|
+
Object.entries(plugin.detectors).map(([id, detect]) => [id, moduleDetector(detect, plugin.written)]);
|
|
68
|
+
|
|
69
|
+
const lexiconsByLanguage = (plugins: readonly ParsedPlugin[]): Record<string, Record<string, Lexicon>> => {
|
|
70
|
+
const languages = [...new Set(plugins.flatMap((plugin) => Object.keys(plugin.lexicons)))];
|
|
71
|
+
return Object.fromEntries(
|
|
72
|
+
languages.map((language) => [language, plugins.reduce<Record<string, Lexicon>>((merged, plugin) => ({ ...merged, ...plugin.lexicons[language] }), {})]),
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Everything chaff.yaml names in code, loaded. */
|
|
77
|
+
const loadExtensions = async (config: Config): Promise<Extensions> => {
|
|
78
|
+
const modules = await loadModuleRules(config);
|
|
79
|
+
const { plugins, problems: pluginProblems } = await loadPlugins(config);
|
|
80
|
+
return {
|
|
81
|
+
detectors: Object.fromEntries([
|
|
82
|
+
...modules.flatMap((entry): [string, Detector][] => ("detector" in entry.loaded ? [[entry.id, entry.loaded.detector]] : [])),
|
|
83
|
+
...plugins.flatMap(pluginDetectors),
|
|
84
|
+
]),
|
|
85
|
+
rules: plugins.flatMap((plugin) => plugin.rules),
|
|
86
|
+
lexicons: lexiconsByLanguage(plugins),
|
|
87
|
+
styles: plugins.flatMap((plugin) => plugin.styles),
|
|
88
|
+
problems: modules.flatMap((entry) => ("problem" in entry.loaded ? [entry.loaded.problem] : [])),
|
|
89
|
+
pluginProblems,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** chaff.yaml with the code it names loaded, and the style it names applied when a plugin ships it. */
|
|
94
|
+
export const withExtensions = async (config: Config): Promise<Config> => {
|
|
95
|
+
const extensions = await loadExtensions(config);
|
|
96
|
+
return withStyle({ ...config, extensions }, extensions.styles);
|
|
97
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type * as Api from "../api.ts";
|
|
2
|
+
import type { Detector, Finding, ProseDocument } from "../plugin.ts";
|
|
3
|
+
import { frozenLexiconOf, ruleDocumentOf } from "./document-view.ts";
|
|
4
|
+
import { returnedFindings, type ShapeProblem } from "./returned-findings.ts";
|
|
5
|
+
|
|
6
|
+
// A detector a team or a plugin wrote, run as one of chaff's. It reads the document through the plugin API, and what it
|
|
7
|
+
// returns is checked before chaff uses it. A throw or a wrong return is that rule's failure only: run.ts lists the rule
|
|
8
|
+
// as not run with the reason, and every other rule still runs.
|
|
9
|
+
|
|
10
|
+
/** A detector from a plugin. It is JavaScript nobody has type-checked against this chaff, so what it returns is unknown. */
|
|
11
|
+
export type UntrustedDetector = (doc: Api.RuleDocument, options: Api.DetectorOptions) => unknown;
|
|
12
|
+
|
|
13
|
+
export type RuleFailure = { readonly kind: "threw"; readonly message: string } | { readonly kind: "returned"; readonly problem: ShapeProblem };
|
|
14
|
+
|
|
15
|
+
/** A plugin's rule that failed on a document. origin is where the detector came from: its file, or its package. */
|
|
16
|
+
export class PluginRuleFailure extends Error {
|
|
17
|
+
readonly failure: RuleFailure;
|
|
18
|
+
readonly origin: string;
|
|
19
|
+
constructor(failure: RuleFailure, origin: string) {
|
|
20
|
+
super(`${origin}: ${failure.kind === "threw" ? failure.message : failure.problem.kind}`);
|
|
21
|
+
this.name = "PluginRuleFailure";
|
|
22
|
+
this.failure = failure;
|
|
23
|
+
this.origin = origin;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The first line of what was thrown. What a plugin throws is its own object, so printing it may throw too. */
|
|
28
|
+
const messageOf = (error: unknown): string => {
|
|
29
|
+
try {
|
|
30
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
31
|
+
return text.split("\n")[0] ?? text;
|
|
32
|
+
} catch {
|
|
33
|
+
return "an error that cannot be printed";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What the detector returned, as findings, or how it failed. Reading the return can run the plugin's code too (a getter,
|
|
39
|
+
* a Proxy), so it is checked inside the try. A Promise is a wrong return; its rejection must not end the run later.
|
|
40
|
+
*/
|
|
41
|
+
const findingsOf = (detect: UntrustedDetector, doc: ProseDocument, options: Api.DetectorOptions): { findings: Finding[] } | { failure: RuleFailure } => {
|
|
42
|
+
const view = ruleDocumentOf(doc);
|
|
43
|
+
try {
|
|
44
|
+
const returned: unknown = detect(view, options);
|
|
45
|
+
if (returned instanceof Promise) returned.catch(() => undefined);
|
|
46
|
+
const result = returnedFindings(returned, doc.source, doc.sentences);
|
|
47
|
+
return "problem" in result ? { failure: { kind: "returned", problem: result.problem } } : { findings: [...result.findings] };
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return { failure: { kind: "threw", message: messageOf(error) } };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** detect as one of chaff's detectors. Throws PluginRuleFailure when it throws or returns anything but findings. */
|
|
54
|
+
export const moduleDetector =
|
|
55
|
+
(detect: UntrustedDetector, origin: string): Detector =>
|
|
56
|
+
(doc, options): Finding[] => {
|
|
57
|
+
const lexicon = options.lexicon === undefined ? undefined : frozenLexiconOf(options.lexicon);
|
|
58
|
+
const outcome = findingsOf(detect, doc, Object.freeze({ lexicon }));
|
|
59
|
+
if ("failure" in outcome) throw new PluginRuleFailure(outcome.failure, origin);
|
|
60
|
+
return outcome.findings;
|
|
61
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CONFIG_FILE, type Config } from "../config/load.ts";
|
|
4
|
+
import { modulePathOf } from "../custom/module-path.ts";
|
|
5
|
+
import { loadGenres } from "../genre-load.ts";
|
|
6
|
+
import { importDefault } from "./import-default.ts";
|
|
7
|
+
import { staysInside } from "./real-path.ts";
|
|
8
|
+
import { isPluginPath, pluginNameOfPackage } from "./plugin-name.ts";
|
|
9
|
+
import { parsePlugin, type ParsedPlugin, type PluginOrigin, type PluginProblem } from "./plugin-parse.ts";
|
|
10
|
+
|
|
11
|
+
// The plugins chaff.yaml lists under plugins:, found and loaded. A package is found from the folder chaff.yaml is in,
|
|
12
|
+
// as Node finds it (node_modules); a path is the project's own plugin and, like a module rule, may not leave that
|
|
13
|
+
// folder unless written as an absolute path. What cannot be found or read stops the run.
|
|
14
|
+
|
|
15
|
+
export type LoadedPlugins = { readonly plugins: readonly ParsedPlugin[]; readonly problems: readonly PluginProblem[] };
|
|
16
|
+
|
|
17
|
+
type Located = { readonly origin: PluginOrigin } | { readonly problem: PluginProblem };
|
|
18
|
+
|
|
19
|
+
const problemOf = (kind: Exclude<PluginProblem["kind"], "rule">, plugin: string, detail = ""): { problem: PluginProblem } => ({
|
|
20
|
+
problem: { kind, plugin, detail },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** The file Node would load for specifier from the folder chaff.yaml is in, or undefined when there is none. */
|
|
24
|
+
const resolveFrom = (baseDir: string, specifier: string): string | undefined => {
|
|
25
|
+
try {
|
|
26
|
+
return createRequire(join(baseDir, CONFIG_FILE)).resolve(specifier);
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const locate = (written: string, baseDir: string): Located => {
|
|
33
|
+
if (isPluginPath(written)) {
|
|
34
|
+
const path = modulePathOf(written, baseDir);
|
|
35
|
+
if ("refusal" in path) return problemOf("outside", written);
|
|
36
|
+
const file = resolveFrom(baseDir, path.file);
|
|
37
|
+
if (file === undefined) return problemOf("not-found", written);
|
|
38
|
+
return staysInside(written, file, baseDir) ? { origin: { written, file, expectedName: undefined } } : problemOf("outside", written);
|
|
39
|
+
}
|
|
40
|
+
const expectedName = pluginNameOfPackage(written);
|
|
41
|
+
if (expectedName === undefined) return problemOf("bad-specifier", written);
|
|
42
|
+
const file = resolveFrom(baseDir, written);
|
|
43
|
+
return file === undefined ? problemOf("not-found", written) : { origin: { written, file, expectedName } };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const loadPlugin = async (written: string, baseDir: string, useFor: readonly string[]): Promise<LoadedPlugins> => {
|
|
47
|
+
const located = locate(written, baseDir);
|
|
48
|
+
if ("problem" in located) return { plugins: [], problems: [located.problem] };
|
|
49
|
+
const imported = await importDefault(located.origin.file);
|
|
50
|
+
if ("message" in imported) return { plugins: [], problems: [problemOf("import-failed", written, imported.message).problem] };
|
|
51
|
+
const read = parsePlugin(imported.exported, located.origin, useFor);
|
|
52
|
+
return { plugins: read.plugin === undefined ? [] : [read.plugin], problems: read.problems };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** A second plugin with a name already taken is refused: its rules' ids would be the first one's. */
|
|
56
|
+
const withoutDuplicates = (loaded: readonly { written: string; result: LoadedPlugins }[]): LoadedPlugins =>
|
|
57
|
+
loaded.reduce<{ plugins: ParsedPlugin[]; problems: PluginProblem[] }>(
|
|
58
|
+
(acc, { written, result }) => {
|
|
59
|
+
const taken = result.plugins.filter((plugin) => acc.plugins.some((seen) => seen.name === plugin.name));
|
|
60
|
+
return {
|
|
61
|
+
plugins: [...acc.plugins, ...result.plugins.filter((plugin) => !taken.includes(plugin))],
|
|
62
|
+
problems: [...acc.problems, ...result.problems, ...taken.map((plugin) => problemOf("duplicate-name", written, plugin.name).problem)],
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
{ plugins: [], problems: [] },
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const isStringList = (value: unknown): value is readonly string[] =>
|
|
69
|
+
Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim() !== "");
|
|
70
|
+
|
|
71
|
+
/** Every plugin chaff.yaml lists, loaded one by one in the order written. */
|
|
72
|
+
export const loadPlugins = async (config: Pick<Config, "plugins" | "baseDir">): Promise<LoadedPlugins> => {
|
|
73
|
+
const written: unknown = config.plugins;
|
|
74
|
+
if (written === undefined || written === null) return { plugins: [], problems: [] };
|
|
75
|
+
if (!isStringList(written)) return { plugins: [], problems: [problemOf("not-a-list", "plugins", JSON.stringify(written) ?? "").problem] };
|
|
76
|
+
const useFor = loadGenres().groups.map((group) => group.id);
|
|
77
|
+
const loaded = await written.reduce<Promise<{ written: string; result: LoadedPlugins }[]>>(
|
|
78
|
+
async (done, entry) => [...(await done), { written: entry.trim(), result: await loadPlugin(entry.trim(), config.baseDir, useFor) }],
|
|
79
|
+
Promise.resolve([]),
|
|
80
|
+
);
|
|
81
|
+
return withoutDuplicates(loaded);
|
|
82
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
|
|
3
|
+
// The names a plugin goes by. A package is chaff-plugin-<name> or @scope/chaff-plugin-<name>, and its rules are named
|
|
4
|
+
// <name>/<rule> or @scope/<name>/<rule>, so they cannot take a built-in rule's id or another plugin's. Pure.
|
|
5
|
+
|
|
6
|
+
/** A plugin's name, the prefix of its ids: foo, @scope/foo, or @scope for @scope/chaff-plugin. */
|
|
7
|
+
const PLUGIN_NAME = /^(?:@[a-z0-9][a-z0-9._-]*(?:\/[a-z][a-z0-9-]*)?|[a-z][a-z0-9-]*)$/u;
|
|
8
|
+
|
|
9
|
+
const PACKAGE = /^(?:(@[a-z0-9][a-z0-9._-]*)\/)?chaff-plugin(?:-([a-z][a-z0-9-]*))?$/u;
|
|
10
|
+
|
|
11
|
+
/** A rule id as chaff.yaml, stet and baseline may write it: a built-in or team id, or a plugin's id with its prefix. */
|
|
12
|
+
export const RULE_ID = /^(?:@[a-z0-9][a-z0-9._-]*\/)?(?:[a-z][a-z0-9-]*\/)?[a-z][a-z0-9-]*$/u;
|
|
13
|
+
|
|
14
|
+
export const isPluginName = (name: string): boolean => PLUGIN_NAME.test(name);
|
|
15
|
+
|
|
16
|
+
/** The name a plugin package must declare: chaff-plugin-foo → foo, @scope/chaff-plugin-foo → @scope/foo. undefined for a name that is not a plugin package's. */
|
|
17
|
+
export const pluginNameOfPackage = (packageName: string): string | undefined => {
|
|
18
|
+
const match = PACKAGE.exec(packageName);
|
|
19
|
+
if (match === null) return undefined;
|
|
20
|
+
const [, scope, name] = match;
|
|
21
|
+
if (scope === undefined) return name;
|
|
22
|
+
return name === undefined ? scope : `${scope}/${name}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** A path to a plugin of the project's own (./local-plugin), rather than a package's name. */
|
|
26
|
+
export const isPluginPath = (written: string): boolean => written.startsWith(".") || isAbsolute(written);
|