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,229 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { API_VERSION } from "../api.ts";
|
|
3
|
+
import type { Lexicon, LexiconEntry, RuleDefinition } from "../plugin.ts";
|
|
4
|
+
import { parseCustomRules, type CustomProblem } from "../custom/parse.ts";
|
|
5
|
+
import { styleOf, type StyleDefinition } from "../style-parse.ts";
|
|
6
|
+
import type { UntrustedDetector } from "./module-detector.ts";
|
|
7
|
+
import { describeValue } from "./returned-findings.ts";
|
|
8
|
+
import { isPluginName } from "./plugin-name.ts";
|
|
9
|
+
|
|
10
|
+
// What a plugin package exports (api.ts PluginSpec), read into what chaff runs: its rules with ids under the plugin's
|
|
11
|
+
// name, their detectors, its word lists and its house styles. A rule is read exactly like a rule in custom_rules, with
|
|
12
|
+
// detect in place of type: module. Pure: the module comes in already imported.
|
|
13
|
+
|
|
14
|
+
/** What can be wrong in finding and loading a plugin (plugin-load.ts), and in what it exports (here). */
|
|
15
|
+
type PluginProblemKind =
|
|
16
|
+
| "not-a-list"
|
|
17
|
+
| "bad-specifier"
|
|
18
|
+
| "outside"
|
|
19
|
+
| "not-found"
|
|
20
|
+
| "import-failed"
|
|
21
|
+
| "duplicate-name"
|
|
22
|
+
| "bad-export"
|
|
23
|
+
| "no-api-version"
|
|
24
|
+
| "api-version"
|
|
25
|
+
| "bad-name"
|
|
26
|
+
| "name-mismatch"
|
|
27
|
+
| "no-detect"
|
|
28
|
+
| "bad-lexicon"
|
|
29
|
+
| "bad-style";
|
|
30
|
+
|
|
31
|
+
/** plugin: as chaff.yaml writes it. detail: what was there instead, the name expected, the rule, list or style at fault. */
|
|
32
|
+
export type PluginProblem =
|
|
33
|
+
| { readonly kind: PluginProblemKind; readonly plugin: string; readonly detail: string }
|
|
34
|
+
| { readonly kind: "rule"; readonly plugin: string; readonly problem: CustomProblem };
|
|
35
|
+
|
|
36
|
+
/** Where a plugin came from: as chaff.yaml writes it, the file imported, and the name its package's name gives (none for a path). */
|
|
37
|
+
export type PluginOrigin = { readonly written: string; readonly file: string; readonly expectedName: string | undefined };
|
|
38
|
+
|
|
39
|
+
export type ParsedPlugin = {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
/** The plugin as chaff.yaml writes it (chaff-plugin-foo, ./local-plugin), which a failing rule's reason names. */
|
|
42
|
+
readonly written: string;
|
|
43
|
+
readonly rules: readonly RuleDefinition[];
|
|
44
|
+
/** Each code rule's detector, by its id with the plugin's prefix. */
|
|
45
|
+
readonly detectors: Readonly<Record<string, UntrustedDetector>>;
|
|
46
|
+
/** Word lists by language, then by name with the plugin's prefix. */
|
|
47
|
+
readonly lexicons: Readonly<Record<string, Readonly<Record<string, Lexicon>>>>;
|
|
48
|
+
readonly styles: readonly StyleDefinition[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type PluginRead = { readonly plugin: ParsedPlugin | undefined; readonly problems: readonly PluginProblem[] };
|
|
52
|
+
|
|
53
|
+
type Problem = (kind: Exclude<PluginProblem["kind"], "rule">, detail: string) => PluginProblem;
|
|
54
|
+
|
|
55
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
|
+
|
|
57
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? describeValue(value);
|
|
58
|
+
|
|
59
|
+
/** A word list's or a style's own name, before the plugin's prefix. */
|
|
60
|
+
const LOCAL_NAME = /^[a-z][a-z0-9-]*$/u;
|
|
61
|
+
|
|
62
|
+
/** The plugin's name, checked against the name its package's name gives it. */
|
|
63
|
+
const nameOf = (exported: Record<string, unknown>, origin: PluginOrigin, problem: Problem): { name: string } | { problem: PluginProblem } => {
|
|
64
|
+
const name = exported["name"];
|
|
65
|
+
if (typeof name !== "string" || !isPluginName(name)) return { problem: problem("bad-name", printed(name)) };
|
|
66
|
+
if (origin.expectedName !== undefined && name !== origin.expectedName) return { problem: problem("name-mismatch", origin.expectedName) };
|
|
67
|
+
return { name };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const versionProblem = (exported: Record<string, unknown>, problem: Problem): PluginProblem | undefined => {
|
|
71
|
+
const version = exported["apiVersion"];
|
|
72
|
+
if (version === undefined) return problem("no-api-version", "");
|
|
73
|
+
return version === API_VERSION ? undefined : problem("api-version", printed(version));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ───────── rules ─────────
|
|
77
|
+
|
|
78
|
+
const detectOf = (entry: Record<string, unknown>): UntrustedDetector | undefined => {
|
|
79
|
+
const detect = entry["detect"];
|
|
80
|
+
return typeof detect === "function" ? (doc, options): unknown => Reflect.apply(detect, undefined, [doc, options]) : undefined;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** A rule with detect is read as a type: module rule whose module is the plugin's own file. */
|
|
84
|
+
const asCustomRule = (entry: unknown, file: string): unknown =>
|
|
85
|
+
isRecord(entry) && detectOf(entry) !== undefined ? { ...entry, type: "module", module: file } : entry;
|
|
86
|
+
|
|
87
|
+
/** Each code rule's detector by the id the plugin wrote. */
|
|
88
|
+
const detectorsByWrittenId = (entries: readonly unknown[]): ReadonlyMap<string, UntrustedDetector> =>
|
|
89
|
+
new Map(
|
|
90
|
+
entries.flatMap((entry): [string, UntrustedDetector][] => {
|
|
91
|
+
const detect = isRecord(entry) ? detectOf(entry) : undefined;
|
|
92
|
+
return isRecord(entry) && typeof entry["id"] === "string" && detect !== undefined ? [[entry["id"], detect]] : [];
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const withPrefix = (rule: RuleDefinition, name: string, lists: ReadonlySet<string>): RuleDefinition => ({
|
|
97
|
+
...rule,
|
|
98
|
+
id: `${name}/${rule.id}`,
|
|
99
|
+
plugin: name,
|
|
100
|
+
word_list: rule.word_list !== undefined && lists.has(rule.word_list) ? `${name}/${rule.word_list}` : rule.word_list,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
type RulesContext = { readonly name: string; readonly origin: PluginOrigin; readonly useFor: readonly string[]; readonly lists: ReadonlySet<string> };
|
|
104
|
+
|
|
105
|
+
type Rules = Pick<ParsedPlugin, "rules" | "detectors"> & { readonly problems: readonly PluginProblem[] };
|
|
106
|
+
|
|
107
|
+
const rulesOf = (raw: unknown, context: RulesContext): Rules => {
|
|
108
|
+
const { name, origin } = context;
|
|
109
|
+
const parsed = parseCustomRules(Array.isArray(raw) ? raw.map((entry: unknown) => asCustomRule(entry, origin.file)) : raw, {
|
|
110
|
+
builtIn: new Set(),
|
|
111
|
+
useFor: context.useFor,
|
|
112
|
+
baseDir: dirname(origin.file),
|
|
113
|
+
});
|
|
114
|
+
const detectors = detectorsByWrittenId(Array.isArray(raw) ? raw : []);
|
|
115
|
+
const runnable = parsed.rules.filter((rule) => rule.custom?.type !== "module" || detectors.has(rule.id));
|
|
116
|
+
const codeless = parsed.rules.filter((rule) => !runnable.includes(rule));
|
|
117
|
+
return {
|
|
118
|
+
rules: runnable.map((rule) => withPrefix(rule, name, context.lists)),
|
|
119
|
+
detectors: Object.fromEntries(
|
|
120
|
+
runnable.flatMap((rule) => {
|
|
121
|
+
const detect = detectors.get(rule.id);
|
|
122
|
+
return detect === undefined ? [] : [[`${name}/${rule.id}`, detect]];
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
problems: [
|
|
126
|
+
...parsed.problems.map((problem): PluginProblem => ({ kind: "rule", plugin: origin.written, problem })),
|
|
127
|
+
...codeless.map((rule): PluginProblem => ({ kind: "no-detect", plugin: origin.written, detail: rule.id })),
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ───────── word lists ─────────
|
|
133
|
+
|
|
134
|
+
const lexiconEntryOf = (value: unknown): LexiconEntry | undefined => {
|
|
135
|
+
if (typeof value === "string") return value.trim() === "" ? undefined : { pattern: value.trim() };
|
|
136
|
+
if (!isRecord(value) || typeof value["pattern"] !== "string" || value["pattern"].trim() === "") return undefined;
|
|
137
|
+
const insteadOf = value["instead_of"];
|
|
138
|
+
return { pattern: value["pattern"].trim(), ...(typeof insteadOf === "string" && insteadOf.trim() !== "" ? { instead_of: insteadOf.trim() } : {}) };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const lexiconOf = (words: unknown): Lexicon | undefined => {
|
|
142
|
+
const entries = Array.isArray(words) ? words.map(lexiconEntryOf) : [undefined];
|
|
143
|
+
const readable = entries.flatMap((entry) => (entry === undefined ? [] : [entry]));
|
|
144
|
+
return readable.length === entries.length ? readable : undefined;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
type ListInLanguage = { readonly language: string; readonly list: string; readonly lexicon: Lexicon };
|
|
148
|
+
|
|
149
|
+
/** One word list in each language it has. undefined when any part of it cannot be read. */
|
|
150
|
+
const listOf = (list: string, raw: unknown): ListInLanguage[] | undefined => {
|
|
151
|
+
if (!LOCAL_NAME.test(list) || !isRecord(raw)) return undefined;
|
|
152
|
+
const read = Object.entries(raw).map(([language, words]) => ({ language, lexicon: lexiconOf(words) }));
|
|
153
|
+
const readable = read.flatMap(({ language, lexicon }) => (lexicon === undefined ? [] : [{ language, list, lexicon }]));
|
|
154
|
+
return readable.length === read.length ? readable : undefined;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
type Lexicons = Pick<ParsedPlugin, "lexicons"> & { readonly lists: ReadonlySet<string>; readonly problems: readonly PluginProblem[] };
|
|
158
|
+
|
|
159
|
+
const byLanguage = (lists: readonly ListInLanguage[], name: string): Record<string, Record<string, Lexicon>> =>
|
|
160
|
+
Object.fromEntries(
|
|
161
|
+
[...new Set(lists.map((entry) => entry.language))].map((language) => [
|
|
162
|
+
language,
|
|
163
|
+
Object.fromEntries(lists.filter((entry) => entry.language === language).map((entry) => [`${name}/${entry.list}`, entry.lexicon])),
|
|
164
|
+
]),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const lexiconsOf = (raw: unknown, name: string, problem: Problem): Lexicons => {
|
|
168
|
+
if (raw === undefined) return { lexicons: {}, lists: new Set(), problems: [] };
|
|
169
|
+
if (!isRecord(raw)) return { lexicons: {}, lists: new Set(), problems: [problem("bad-lexicon", printed(raw))] };
|
|
170
|
+
const read = Object.entries(raw).map(([list, words]) => ({ list, inLanguages: listOf(list, words) }));
|
|
171
|
+
const readable = read.flatMap((entry) => entry.inLanguages ?? []);
|
|
172
|
+
return {
|
|
173
|
+
lexicons: byLanguage(readable, name),
|
|
174
|
+
lists: new Set(readable.map((entry) => entry.list)),
|
|
175
|
+
problems: read.filter((entry) => entry.inLanguages === undefined).map((entry) => problem("bad-lexicon", entry.list)),
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ───────── styles ─────────
|
|
180
|
+
|
|
181
|
+
/** One string reads the same in every language; a style's words need ja and en. */
|
|
182
|
+
const bothLanguages = (value: unknown): unknown => (typeof value === "string" ? { ja: value, en: value } : value);
|
|
183
|
+
|
|
184
|
+
const styleSpecOf = (raw: Record<string, unknown>, id: string, name: string): Record<string, unknown> => {
|
|
185
|
+
const source = isRecord(raw["source"]) ? { ...raw["source"], title: bothLanguages(raw["source"]["title"]) } : raw["source"];
|
|
186
|
+
return { ...raw, id: `${name}/${id}`, name: bothLanguages(raw["name"]), summary: bothLanguages(raw["summary"]), source };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const styleOrProblem = (entry: unknown, name: string, problem: Problem): { style: StyleDefinition } | { problem: PluginProblem } => {
|
|
190
|
+
const id = isRecord(entry) ? entry["id"] : undefined;
|
|
191
|
+
if (!isRecord(entry) || typeof id !== "string" || !LOCAL_NAME.test(id)) return { problem: problem("bad-style", printed(id)) };
|
|
192
|
+
try {
|
|
193
|
+
return { style: styleOf(styleSpecOf(entry, id, name), `${name}/${id}`) };
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return { problem: problem("bad-style", error instanceof Error ? error.message : String(error)) };
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
type Styles = Pick<ParsedPlugin, "styles"> & { readonly problems: readonly PluginProblem[] };
|
|
200
|
+
|
|
201
|
+
const stylesOf = (raw: unknown, name: string, problem: Problem): Styles => {
|
|
202
|
+
if (raw === undefined) return { styles: [], problems: [] };
|
|
203
|
+
if (!Array.isArray(raw)) return { styles: [], problems: [problem("bad-style", printed(raw))] };
|
|
204
|
+
const read = raw.map((entry: unknown) => styleOrProblem(entry, name, problem));
|
|
205
|
+
return {
|
|
206
|
+
styles: read.flatMap((entry) => ("style" in entry ? [entry.style] : [])),
|
|
207
|
+
problems: read.flatMap((entry) => ("problem" in entry ? [entry.problem] : [])),
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// ───────── the plugin ─────────
|
|
212
|
+
|
|
213
|
+
/** A plugin's default export, read. Every problem is reported; a plugin with any is left out whole, and the run is stopped. */
|
|
214
|
+
export const parsePlugin = (exported: unknown, origin: PluginOrigin, useFor: readonly string[]): PluginRead => {
|
|
215
|
+
const problem: Problem = (kind, detail) => ({ kind, plugin: origin.written, detail });
|
|
216
|
+
if (!isRecord(exported)) return { plugin: undefined, problems: [problem("bad-export", describeValue(exported))] };
|
|
217
|
+
const version = versionProblem(exported, problem);
|
|
218
|
+
if (version !== undefined) return { plugin: undefined, problems: [version] };
|
|
219
|
+
const named = nameOf(exported, origin, problem);
|
|
220
|
+
if ("problem" in named) return { plugin: undefined, problems: [named.problem] };
|
|
221
|
+
const { name } = named;
|
|
222
|
+
const lexicons = lexiconsOf(exported["lexicons"], name, problem);
|
|
223
|
+
const rules = rulesOf(exported["rules"], { name, origin, useFor, lists: lexicons.lists });
|
|
224
|
+
const styles = stylesOf(exported["styles"], name, problem);
|
|
225
|
+
const problems = [...rules.problems, ...lexicons.problems, ...styles.problems];
|
|
226
|
+
if (problems.length > 0) return { plugin: undefined, problems };
|
|
227
|
+
const plugin = { name, written: origin.written, rules: rules.rules, detectors: rules.detectors, lexicons: lexicons.lexicons, styles: styles.styles };
|
|
228
|
+
return { plugin, problems: [] };
|
|
229
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { API_VERSION } from "../api.ts";
|
|
2
|
+
import type { Config } from "../config/load.ts";
|
|
3
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
4
|
+
import { customProblemSentence } from "../custom/problems.ts";
|
|
5
|
+
import type { LoadProblem } from "./load.ts";
|
|
6
|
+
import type { PluginProblem } from "./plugin-parse.ts";
|
|
7
|
+
|
|
8
|
+
// What in the code chaff.yaml names could not be loaded, as sentences. Each names the rule or plugin and the file or
|
|
9
|
+
// package, as chaff.yaml writes it.
|
|
10
|
+
|
|
11
|
+
type ModuleText = Readonly<Record<LoadProblem["kind"], (rule: string, file: string, detail: string) => string>>;
|
|
12
|
+
|
|
13
|
+
type PluginText = {
|
|
14
|
+
readonly problems: Readonly<Record<Exclude<PluginProblem["kind"], "rule">, (plugin: string, detail: string) => string>>;
|
|
15
|
+
/** Where a plugin's rule is, for a problem a rule in custom_rules could have too. */
|
|
16
|
+
readonly ruleScope: (plugin: string) => string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const API = String(API_VERSION);
|
|
20
|
+
|
|
21
|
+
const MODULE_TEXT: Texts<ModuleText> = {
|
|
22
|
+
ja: {
|
|
23
|
+
"missing-file": (rule, file) => `custom_rules の ${rule}: module のファイル ${file} がありません`,
|
|
24
|
+
outside: (rule, file) =>
|
|
25
|
+
`custom_rules の ${rule}: ${file} はリンクをたどると chaff.yaml のあるフォルダの外です。読み込むとそのコードが動くので、外のファイルは絶対パスで名指ししてください`,
|
|
26
|
+
"import-failed": (rule, file, detail) => `custom_rules の ${rule}: ${file} を読み込めませんでした(${detail})`,
|
|
27
|
+
"bad-export": (rule, file, detail) =>
|
|
28
|
+
`custom_rules の ${rule}: ${file} の default export が検出器ではありません(${detail})。関数か defineRule({ detect }) を export default してください`,
|
|
29
|
+
"api-version": (rule, file, detail) => `custom_rules の ${rule}: ${file} はプラグイン API ${detail} 向けです。この chaff のプラグイン API は ${API} です`,
|
|
30
|
+
},
|
|
31
|
+
en: {
|
|
32
|
+
"missing-file": (rule, file) => `custom_rules ${rule}: the module file ${file} does not exist`,
|
|
33
|
+
outside: (rule, file) =>
|
|
34
|
+
`custom_rules ${rule}: ${file} leads outside the folder chaff.yaml is in through a link. Loading a module runs its code, so name a file outside by its absolute path`,
|
|
35
|
+
"import-failed": (rule, file, detail) => `custom_rules ${rule}: cannot load ${file} (${detail})`,
|
|
36
|
+
"bad-export": (rule, file, detail) =>
|
|
37
|
+
`custom_rules ${rule}: the default export of ${file} is not a detector (${detail}); export default a function or defineRule({ detect })`,
|
|
38
|
+
"api-version": (rule, file, detail) => `custom_rules ${rule}: ${file} was written for plugin API ${detail}; this chaff has plugin API ${API}`,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const PLUGIN_TEXT: Texts<PluginText> = {
|
|
43
|
+
ja: {
|
|
44
|
+
ruleScope: (plugin) => `plugins の ${plugin} の rules`,
|
|
45
|
+
problems: {
|
|
46
|
+
"not-a-list": (_plugin, detail) => `plugins はプラグインの並び(- chaff-plugin-foo や - ./local-plugin)で書いてください(${detail})`,
|
|
47
|
+
"bad-specifier": (plugin) =>
|
|
48
|
+
`plugins の ${plugin}: パッケージは chaff-plugin-<名前> か @scope/chaff-plugin-<名前> の名前で、手元のプラグインは ./ で始まるパスで書いてください`,
|
|
49
|
+
outside: (plugin) =>
|
|
50
|
+
`plugins の ${plugin}: chaff.yaml のあるフォルダの外です。読み込むとそのコードが動くので、外のプラグインは絶対パスで名指ししてください`,
|
|
51
|
+
"not-found": (plugin) => `plugins の ${plugin}: 見つかりません。パッケージなら chaff.yaml のあるフォルダで yarn add ${plugin} を実行してください`,
|
|
52
|
+
"import-failed": (plugin, detail) => `plugins の ${plugin}: 読み込めませんでした(${detail})`,
|
|
53
|
+
"duplicate-name": (plugin, detail) => `plugins の ${plugin}: 名前 ${detail} のプラグインがもう一つあります`,
|
|
54
|
+
"bad-export": (plugin, detail) =>
|
|
55
|
+
`plugins の ${plugin}: default export がプラグインではありません(${detail})。definePlugin({ name, rules }) を export default してください`,
|
|
56
|
+
"no-api-version": (plugin) => `plugins の ${plugin}: 向けているプラグイン API がありません。definePlugin で作ると付きます`,
|
|
57
|
+
"api-version": (plugin, detail) => `plugins の ${plugin}: プラグイン API ${detail} 向けです。この chaff のプラグイン API は ${API} です`,
|
|
58
|
+
"bad-name": (plugin, detail) =>
|
|
59
|
+
`plugins の ${plugin}: name: ${detail} はプラグインの名前として読めません(英小文字・数字・ハイフン。@scope/ を付けてもよい)`,
|
|
60
|
+
"name-mismatch": (plugin, detail) => `plugins の ${plugin}: name は ${detail} にしてください(パッケージの名前から決まります)`,
|
|
61
|
+
"no-detect": (plugin, detail) => `plugins の ${plugin}: ルール ${detail} に detect がありません`,
|
|
62
|
+
"bad-lexicon": (plugin, detail) => `plugins の ${plugin}: 語彙表 ${detail} が読めません(名前: { ja: [語, …], en: [語, …] })`,
|
|
63
|
+
"bad-style": (plugin, detail) => `plugins の ${plugin}: スタイルが読めません(${detail})`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
en: {
|
|
67
|
+
ruleScope: (plugin) => `plugins ${plugin} rules`,
|
|
68
|
+
problems: {
|
|
69
|
+
"not-a-list": (_plugin, detail) => `write plugins as a list (- chaff-plugin-foo or - ./local-plugin), not ${detail}`,
|
|
70
|
+
"bad-specifier": (plugin) =>
|
|
71
|
+
`plugins ${plugin}: name a package chaff-plugin-<name> or @scope/chaff-plugin-<name>, and a plugin of your own by a path starting with ./`,
|
|
72
|
+
outside: (plugin) =>
|
|
73
|
+
`plugins ${plugin}: it is outside the folder chaff.yaml is in. Loading a plugin runs its code, so name one outside by its absolute path`,
|
|
74
|
+
"not-found": (plugin) => `plugins ${plugin}: not found. For a package, run yarn add ${plugin} in the folder chaff.yaml is in`,
|
|
75
|
+
"import-failed": (plugin, detail) => `plugins ${plugin}: cannot load it (${detail})`,
|
|
76
|
+
"duplicate-name": (plugin, detail) => `plugins ${plugin}: another plugin is named ${detail}`,
|
|
77
|
+
"bad-export": (plugin, detail) => `plugins ${plugin}: the default export is not a plugin (${detail}); export default definePlugin({ name, rules })`,
|
|
78
|
+
"no-api-version": (plugin) => `plugins ${plugin}: it does not say which plugin API it was written for; definePlugin adds that`,
|
|
79
|
+
"api-version": (plugin, detail) => `plugins ${plugin}: it was written for plugin API ${detail}; this chaff has plugin API ${API}`,
|
|
80
|
+
"bad-name": (plugin, detail) =>
|
|
81
|
+
`plugins ${plugin}: cannot read name: ${detail} as a plugin's name (lowercase letters, digits and hyphens, with an optional @scope/)`,
|
|
82
|
+
"name-mismatch": (plugin, detail) => `plugins ${plugin}: its name must be ${detail}, which the package's name gives`,
|
|
83
|
+
"no-detect": (plugin, detail) => `plugins ${plugin}: the rule ${detail} has no detect`,
|
|
84
|
+
"bad-lexicon": (plugin, detail) => `plugins ${plugin}: cannot read the word list ${detail} (name: { ja: [word, …], en: [word, …] })`,
|
|
85
|
+
"bad-style": (plugin, detail) => `plugins ${plugin}: cannot read a style (${detail})`,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const pluginSentence = (problem: PluginProblem, ui: UiLanguage): string => {
|
|
91
|
+
const text = PLUGIN_TEXT[ui];
|
|
92
|
+
if (problem.kind === "rule") return customProblemSentence(problem.problem, ui, text.ruleScope(problem.plugin));
|
|
93
|
+
return text.problems[problem.kind](problem.plugin, problem.detail);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** What chaff.yaml names in code that could not be loaded: type: module rules, then plugins. Each stops the run. */
|
|
97
|
+
export const extensionProblems = (config: Pick<Config, "extensions" | "path">, ui: UiLanguage): string[] => {
|
|
98
|
+
const where = `chaff: ${config.path ?? "chaff.yaml"}`;
|
|
99
|
+
return [
|
|
100
|
+
...(config.extensions?.problems ?? []).map((problem) => `${where}: ${MODULE_TEXT[ui][problem.kind](problem.rule, problem.file, problem.detail)}`),
|
|
101
|
+
...(config.extensions?.pluginProblems ?? []).map((problem) => `${where}: ${pluginSentence(problem, ui)}`),
|
|
102
|
+
];
|
|
103
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { isInside } from "../custom/module-path.ts";
|
|
4
|
+
|
|
5
|
+
// The path check of custom/module-path.ts reads only the path as written, so a symbolic link inside the project can
|
|
6
|
+
// still point outside it. Before code is loaded, the same check runs on the real paths.
|
|
7
|
+
|
|
8
|
+
/** Whether a file written as a relative path is really inside baseDir once links are followed. An absolute path was written on purpose. */
|
|
9
|
+
export const staysInside = (written: string, file: string, baseDir: string): boolean =>
|
|
10
|
+
isAbsolute(written) || isInside(realpathSync(baseDir), realpathSync(file));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Finding, Sentence } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
// What a plugin's detector returned, checked and turned into chaff's findings. Anything else than a list of
|
|
4
|
+
// { start, end?, values? } inside the document is refused as a whole, with what was wrong: a rule that half works
|
|
5
|
+
// would report some places and silently drop others. Pure.
|
|
6
|
+
|
|
7
|
+
/** What was wrong, at which finding (from 1; 0 for the whole return), and what was there instead. */
|
|
8
|
+
export type ShapeProblem = {
|
|
9
|
+
readonly kind: "not-a-list" | "not-a-finding" | "bad-start" | "bad-end" | "bad-values";
|
|
10
|
+
readonly index: number;
|
|
11
|
+
readonly written: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type Returned = { readonly findings: readonly Finding[] } | { readonly problem: ShapeProblem };
|
|
15
|
+
|
|
16
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
|
|
18
|
+
/** What came back instead of a list, in a few words: "a Promise", "undefined", "an object". */
|
|
19
|
+
export const describeValue = (value: unknown): string => {
|
|
20
|
+
if (value === null || value === undefined) return String(value);
|
|
21
|
+
if (value instanceof Promise) return "a Promise";
|
|
22
|
+
if (Array.isArray(value)) return "a list";
|
|
23
|
+
return typeof value === "object" ? "an object" : `a ${typeof value}`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isOffsetIn = (value: unknown, low: number, high: number): value is number =>
|
|
27
|
+
typeof value === "number" && Number.isInteger(value) && value >= low && value <= high;
|
|
28
|
+
|
|
29
|
+
const isValue = (value: unknown): value is string | number => typeof value === "string" || (typeof value === "number" && Number.isFinite(value));
|
|
30
|
+
|
|
31
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? String(value);
|
|
32
|
+
|
|
33
|
+
/** The sentence the finding is in, or the line when it is in none (a heading, a table). */
|
|
34
|
+
const quoteAt = (source: string, sentences: readonly Sentence[], offset: number): string => {
|
|
35
|
+
const sentence = sentences.find((entry) => entry.span.start <= offset && offset < entry.span.end);
|
|
36
|
+
if (sentence !== undefined) return sentence.text.trim();
|
|
37
|
+
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
38
|
+
const lineEnd = source.indexOf("\n", offset);
|
|
39
|
+
return source.slice(lineStart, lineEnd === -1 ? source.length : lineEnd).trim();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type Checked = { readonly finding: Finding } | { readonly problem: ShapeProblem };
|
|
43
|
+
|
|
44
|
+
const checkedFinding = (entry: unknown, index: number, source: string, sentences: readonly Sentence[]): Checked => {
|
|
45
|
+
if (!isRecord(entry)) return { problem: { kind: "not-a-finding", index, written: describeValue(entry) } };
|
|
46
|
+
const { start, end = start, values = {} } = entry;
|
|
47
|
+
if (!isOffsetIn(start, 0, source.length)) return { problem: { kind: "bad-start", index, written: printed(start) } };
|
|
48
|
+
if (!isOffsetIn(end, start, source.length)) return { problem: { kind: "bad-end", index, written: printed(end) } };
|
|
49
|
+
if (!isRecord(values) || !Object.values(values).every(isValue)) return { problem: { kind: "bad-values", index, written: printed(values) } };
|
|
50
|
+
const filled = { matched: source.slice(start, end), ...values, offset: start };
|
|
51
|
+
return { finding: { rule: "", severity: "warning", line: 0, column: 0, quote: quoteAt(source, sentences, start), values: filled } };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The findings a detector returned, placed in the document, or the first thing wrong with them. index counts from 1. */
|
|
55
|
+
export const returnedFindings = (returned: unknown, source: string, sentences: readonly Sentence[]): Returned => {
|
|
56
|
+
if (!Array.isArray(returned)) return { problem: { kind: "not-a-list", index: 0, written: describeValue(returned) } };
|
|
57
|
+
// Array.from reads a hole in a sparse list as undefined, so it is refused rather than skipped.
|
|
58
|
+
const checked = Array.from(returned, (entry: unknown, at) => checkedFinding(entry, at + 1, source, sentences));
|
|
59
|
+
const wrong = checked.find((entry) => "problem" in entry);
|
|
60
|
+
if (wrong !== undefined && "problem" in wrong) return { problem: wrong.problem };
|
|
61
|
+
return { findings: checked.flatMap((entry) => ("finding" in entry ? [entry.finding] : [])) };
|
|
62
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { API_VERSION } from "../api.ts";
|
|
2
|
+
import { describeValue } from "./returned-findings.ts";
|
|
3
|
+
import type { UntrustedDetector } from "./module-detector.ts";
|
|
4
|
+
|
|
5
|
+
// What a type: module file exports, read as a detector. The default export is the detector itself, or a rule made with
|
|
6
|
+
// defineRule ({ apiVersion, detect }). A function carries no version and is read against this chaff's API; a rule that
|
|
7
|
+
// names another version is refused, since its detector expects another document. Pure.
|
|
8
|
+
|
|
9
|
+
/** bad-export: detail says what was exported instead. api-version: detail is the version the rule names. */
|
|
10
|
+
export type ExportProblem = { readonly kind: "bad-export" | "api-version"; readonly detail: string };
|
|
11
|
+
|
|
12
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
|
|
14
|
+
const isDetector = (value: unknown): value is UntrustedDetector => typeof value === "function";
|
|
15
|
+
|
|
16
|
+
/** The detector a module's default export gives, or what is wrong with it. */
|
|
17
|
+
export const detectorExport = (exported: unknown): { readonly detect: UntrustedDetector } | { readonly problem: ExportProblem } => {
|
|
18
|
+
if (isDetector(exported)) return { detect: exported };
|
|
19
|
+
if (!isRecord(exported) || !isDetector(exported["detect"])) return { problem: { kind: "bad-export", detail: describeValue(exported) } };
|
|
20
|
+
const version = exported["apiVersion"];
|
|
21
|
+
if (version !== undefined && version !== API_VERSION) return { problem: { kind: "api-version", detail: JSON.stringify(version) ?? describeValue(version) } };
|
|
22
|
+
return { detect: exported["detect"] };
|
|
23
|
+
};
|
package/src/feedback/draft.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type FeedbackInput = {
|
|
|
12
12
|
readonly fileName: string;
|
|
13
13
|
readonly language: string;
|
|
14
14
|
readonly genre: string;
|
|
15
|
+
/** How the check was run (--experimental, --genre …), so the reader can run the same check. */
|
|
16
|
+
readonly conditions: readonly string[];
|
|
15
17
|
readonly findings: readonly ReportedFinding[];
|
|
16
18
|
/** The line the person reported: the finding's line, or for a miss the line they named. */
|
|
17
19
|
readonly line: number;
|
|
@@ -26,6 +28,7 @@ const TEXT: Texts<{
|
|
|
26
28
|
readonly titleMissed: (fileName: string, line: number) => string;
|
|
27
29
|
readonly what: Readonly<Record<FeedbackKind, string>>;
|
|
28
30
|
readonly environment: string;
|
|
31
|
+
readonly ranWith: string;
|
|
29
32
|
readonly findings: string;
|
|
30
33
|
readonly excerpt: string;
|
|
31
34
|
readonly config: string;
|
|
@@ -38,6 +41,7 @@ const TEXT: Texts<{
|
|
|
38
41
|
titleMissed: (fileName, line) => `見逃し: ${fileName} の ${String(line)} 行目`,
|
|
39
42
|
what: { "false-positive": "この指摘は誤りだと思います。", missed: "ここで chaff は何か言うべきだと思います。" },
|
|
40
43
|
environment: "環境",
|
|
44
|
+
ranWith: "実行の条件",
|
|
41
45
|
findings: "指摘",
|
|
42
46
|
excerpt: "文書の該当箇所(この部分だけを載せています)",
|
|
43
47
|
config: "chaff.yaml",
|
|
@@ -53,6 +57,7 @@ const TEXT: Texts<{
|
|
|
53
57
|
titleMissed: (fileName, line) => `Missed: ${fileName} line ${String(line)}`,
|
|
54
58
|
what: { "false-positive": "I think this finding is wrong.", missed: "I think chaff should say something here." },
|
|
55
59
|
environment: "Environment",
|
|
60
|
+
ranWith: "Run with",
|
|
56
61
|
findings: "Findings",
|
|
57
62
|
excerpt: "The part of the document (only these lines are included)",
|
|
58
63
|
config: "chaff.yaml",
|
|
@@ -85,6 +90,7 @@ export const feedbackDraft = (input: FeedbackInput, ui: UiLanguage): FeedbackDra
|
|
|
85
90
|
`- chaffjs ${input.version}`,
|
|
86
91
|
`- ${input.runtime}`,
|
|
87
92
|
`- ${input.fileName} · ${input.language} · ${input.genre}`,
|
|
93
|
+
...(input.conditions.length === 0 ? [] : [`- ${text.ranWith}: ${input.conditions.join(" ")}`]),
|
|
88
94
|
...(input.findings.length === 0
|
|
89
95
|
? []
|
|
90
96
|
: ["", `## ${text.findings}`, "", ...input.findings.map((finding) => `- \`${finding.rule}\` (${String(finding.line)}): ${finding.message}`)]),
|
package/src/figure-references.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { escapeRegExp } from "./orthography.ts";
|
|
2
|
-
import
|
|
2
|
+
import { lowerBound } from "./detectors/token-column.ts";
|
|
3
|
+
import type { DocumentNamer, Span } from "./plugin.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 本文で番号を指した図・表・付録(図3、Table 2、Appendix B)と、それを行の頭に書いた所(キャプション、見出し)。
|
|
@@ -27,17 +28,22 @@ type Mention = { readonly start: number; readonly end: number; readonly written:
|
|
|
27
28
|
/** 語の前の字が漢字・カタカナ・英数字なら、長い語の一部(地図3、一覧表2、法別表第二、SubFigure)。 */
|
|
28
29
|
const INSIDE_WORD = "(?<![\\p{Script=Han}\\p{Script=Katakana}\\p{N}A-Za-z])";
|
|
29
30
|
/** 番号。数字(3、3.2、第3)、「第」の後の漢数字(別表第一)、大文字一つかローマ数字(Appendix B、Annex II)。 */
|
|
30
|
-
const
|
|
31
|
+
export const FIGURE_NUMBER = "[ \\t\\u00a0]?(?:第?(\\p{Nd}+(?:[.\\--]\\p{Nd}+)*)|第([一二三四五六七八九十百]+)|([IVX]{2,5}|[A-ZA-Z])(?![A-Za-z]))";
|
|
31
32
|
|
|
32
33
|
const forms = (word: string): string[] => [...new Set([word, word.toUpperCase()])];
|
|
33
34
|
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const labelAlternation = (labels: readonly LabelWord[]): string =>
|
|
36
|
+
labels
|
|
36
37
|
.flatMap((label) => forms(label.word))
|
|
37
38
|
.toSorted((a, b) => b.length - a.length)
|
|
38
|
-
.map(escapeRegExp)
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
.map(escapeRegExp)
|
|
40
|
+
.join("|");
|
|
41
|
+
|
|
42
|
+
const mentionPattern = (labels: readonly LabelWord[]): RegExp => new RegExp(`${INSIDE_WORD}(${labelAlternation(labels)})${FIGURE_NUMBER}`, "gu");
|
|
43
|
+
|
|
44
|
+
/** 番号の付いた図か、番号の無い図(「…の別表に」)。番号が無ければ後ろが長い語の続きでない(表示、別表第一の「第」は番号)。 */
|
|
45
|
+
const labelPattern = (labels: readonly LabelWord[]): RegExp =>
|
|
46
|
+
new RegExp(`${INSIDE_WORD}(${labelAlternation(labels)})(?:${FIGURE_NUMBER}|(?![\\p{Script=Han}\\p{Script=Katakana}A-Za-z]))`, "gu");
|
|
41
47
|
|
|
42
48
|
const kindOf = (written: string, labels: readonly LabelWord[]): string => labels.find((label) => forms(label.word).includes(written))?.kind ?? written;
|
|
43
49
|
|
|
@@ -95,18 +101,73 @@ const pointsHere = (text: string, mention: Mention, words: LabelWords): boolean
|
|
|
95
101
|
return !elsewhere && !words.counters.some((word) => after.startsWith(word));
|
|
96
102
|
};
|
|
97
103
|
|
|
104
|
+
/** 行の頭に書いた番号(キャプションや見出し)。図の置き場所。 */
|
|
105
|
+
const labelledIn = (source: string, labels: readonly LabelWord[]): Mention[] => mentionsIn(source, labels).filter((mention) => isLabelled(source, mention));
|
|
106
|
+
|
|
107
|
+
/** 文書が行の頭に番号を書いている種類(図、Table)。番号で名指しできる種類。 */
|
|
108
|
+
export const labelledKindsIn = (source: string, words: LabelWords): ReadonlySet<string> =>
|
|
109
|
+
new Set(labelledIn(source, words.labels).map((mention) => mention.kind));
|
|
110
|
+
|
|
111
|
+
/** 文書の名前を読む言語の知識と、その名前が届く範囲(文)。 */
|
|
112
|
+
export type Citations = { readonly namedDocument: DocumentNamer; readonly sentences: readonly Span[] };
|
|
113
|
+
|
|
114
|
+
type NamedLabel = { readonly start: number; readonly kind: string; readonly self: boolean };
|
|
115
|
+
|
|
116
|
+
/** 文書の名前のすぐ後ろに書いた図の語(「…(平成二十年厚生労働省告示第五十九号)別表第一」「…号)の別表」「この規則の別表第三」)。 */
|
|
117
|
+
const namedLabelsIn = (prose: string, labels: readonly LabelWord[], namedDocument: DocumentNamer): NamedLabel[] =>
|
|
118
|
+
[...prose.matchAll(labelPattern(labels))].flatMap((match) => {
|
|
119
|
+
const named = namedDocument(prose, match.index);
|
|
120
|
+
return named === undefined ? [] : [{ start: match.index, kind: kindOf(match[1] ?? "", labels), self: named.self }];
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** offset を含む文の頭。文の外(見出し)なら offset。文は文書の順に並んでいる。 */
|
|
124
|
+
const sentenceStartAt = (sentences: readonly Span[], starts: readonly number[], offset: number): number => {
|
|
125
|
+
const sentence = sentences[lowerBound(starts, offset + 1) - 1];
|
|
126
|
+
return sentence !== undefined && offset < sentence.end ? sentence.start : offset;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 他の文書の図か。同じ文の中で、その番号の位置までに名前を添えて書いた同じ種類の図のうち、いちばん近いものが他の文書のもの。
|
|
131
|
+
* 「(…告示第五十九号)別表第一…及び別表第二」「…別表第一から別表第三まで」の後ろの番号も、その告示の別表。
|
|
132
|
+
* 「…手数料規則の別表第二により、この規則の別表第三による」の別表第三は、この文書のものと名指している。
|
|
133
|
+
* 文と名前の位置は二分探索で引く。参照先の無い番号が何万あっても、文書の長さの二乗にしない。
|
|
134
|
+
*/
|
|
135
|
+
const citedElsewhere = (named: readonly NamedLabel[], sentences: readonly Span[]): ((mention: Mention) => boolean) => {
|
|
136
|
+
const starts = sentences.map((sentence) => sentence.start);
|
|
137
|
+
const byKind = new Map(
|
|
138
|
+
[...new Set(named.map((label) => label.kind))].map((kind) => {
|
|
139
|
+
const labels = named.filter((label) => label.kind === kind);
|
|
140
|
+
return [kind, { labels, starts: labels.map((label) => label.start) }];
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
return (mention) => {
|
|
144
|
+
const ofKind = byKind.get(mention.kind);
|
|
145
|
+
const nearest = ofKind?.labels[lowerBound(ofKind.starts, mention.start + 1) - 1];
|
|
146
|
+
return nearest !== undefined && !nearest.self && nearest.start >= sentenceStartAt(sentences, starts, mention.start);
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
98
150
|
/**
|
|
99
151
|
* 参照先の無い番号。source は行の頭を読み、prose(コードを覆った本文)は参照を読む。位置は同じ。
|
|
100
152
|
* 番号は、キャプションが 1 と書けば 1a や 1(b) の参照も 1 に当たる(番号の後ろの字は読まない)。
|
|
101
153
|
* リンクの字([Figure 2](figures.md))は、そのリンクが行き先を持つので参照として見ない。
|
|
102
154
|
*/
|
|
103
|
-
export const danglingFigures = (
|
|
155
|
+
export const danglingFigures = (
|
|
156
|
+
source: string,
|
|
157
|
+
prose: string,
|
|
158
|
+
words: LabelWords,
|
|
159
|
+
links: readonly Span[] = [],
|
|
160
|
+
citations: Citations | undefined = undefined,
|
|
161
|
+
): DanglingFigure[] => {
|
|
104
162
|
if (words.labels.length === 0) return [];
|
|
105
|
-
const labelled =
|
|
163
|
+
const labelled = labelledIn(source, words.labels);
|
|
106
164
|
const labelledKeys = new Set(labelled.map((mention) => mention.key));
|
|
107
165
|
const labelledKinds = new Set(labelled.map((mention) => mention.kind));
|
|
108
|
-
|
|
166
|
+
const dangling = mentionsIn(prose, words.labels)
|
|
109
167
|
.filter((mention) => labelledKinds.has(mention.kind) && !labelledKeys.has(mention.key))
|
|
110
|
-
.filter((mention) => pointsHere(prose, mention, words) && !links.some((link) => link.start <= mention.start && mention.end <= link.end))
|
|
111
|
-
|
|
168
|
+
.filter((mention) => pointsHere(prose, mention, words) && !links.some((link) => link.start <= mention.start && mention.end <= link.end));
|
|
169
|
+
// 他の文書の名前は、参照先の無い番号があるときだけ読む。図の語のたびに名前を後ろ向きに読む代金を、ふつうの文書に払わせない。
|
|
170
|
+
const named = dangling.length === 0 || citations === undefined ? [] : namedLabelsIn(prose, words.labels, citations.namedDocument);
|
|
171
|
+
const elsewhere = named.length === 0 || citations === undefined ? () => false : citedElsewhere(named, citations.sentences);
|
|
172
|
+
return dangling.filter((mention) => !elsewhere(mention)).map((mention) => ({ offset: mention.start, label: mention.written }));
|
|
112
173
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which of the three ways to rewrite (the guide's Light, Bold and Full) chaff recommends, from what it found alone.
|
|
3
|
+
* A request that says "from scratch" also means Full; chaff cannot read the request, so the plan says so in words.
|
|
4
|
+
*/
|
|
5
|
+
export type FixMode = "none" | "light" | "bold" | "full";
|
|
6
|
+
|
|
7
|
+
/** Why the mode was chosen, so the plan can say it. */
|
|
8
|
+
export type ModeReason = "nothing-found" | "composite" | "genre" | "signals" | "spots";
|
|
9
|
+
|
|
10
|
+
export type ModeChoice = { readonly mode: FixMode; readonly reason: ModeReason };
|
|
11
|
+
|
|
12
|
+
export type ModeInput = {
|
|
13
|
+
readonly genre: string;
|
|
14
|
+
/** The rules with at least one finding. */
|
|
15
|
+
readonly firedRules: ReadonlySet<string>;
|
|
16
|
+
/** The rules that measure the whole document: ai-generated-composite's inputs and bold-density. */
|
|
17
|
+
readonly signalRules: ReadonlySet<string>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const COMPOSITE_RULE = "ai-generated-composite";
|
|
21
|
+
|
|
22
|
+
/** Two document-wide signals mean the prose of the sections is the problem, not a spot or two. */
|
|
23
|
+
const MIN_SIGNALS_FOR_BOLD = 2;
|
|
24
|
+
|
|
25
|
+
/** A blog post or an essay is rewritten from its structure up: what the writer wants changed there is the structure. */
|
|
26
|
+
const isFreeFormGenre = (genre: string): boolean => genre.startsWith("blog/") || genre === "literature/essay";
|
|
27
|
+
|
|
28
|
+
export const recommendMode = (input: ModeInput): ModeChoice => {
|
|
29
|
+
if (input.firedRules.size === 0) return { mode: "none", reason: "nothing-found" };
|
|
30
|
+
if (input.firedRules.has(COMPOSITE_RULE)) return { mode: "full", reason: "composite" };
|
|
31
|
+
if (isFreeFormGenre(input.genre)) return { mode: "full", reason: "genre" };
|
|
32
|
+
const signals = [...input.firedRules].filter((rule) => input.signalRules.has(rule)).length;
|
|
33
|
+
return signals >= MIN_SIGNALS_FOR_BOLD ? { mode: "bold", reason: "signals" } : { mode: "light", reason: "spots" };
|
|
34
|
+
};
|