chaffjs 0.17.0 → 0.18.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/dist/announced-count.d.ts +26 -0
- package/dist/announced-count.d.ts.map +1 -0
- package/dist/announced-count.js +104 -0
- package/dist/announced-count.js.map +1 -0
- package/dist/bare-url.d.ts +14 -0
- package/dist/bare-url.d.ts.map +1 -0
- package/dist/bare-url.js +28 -0
- package/dist/bare-url.js.map +1 -0
- package/dist/baseline.d.ts +5 -0
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +16 -1
- package/dist/baseline.js.map +1 -1
- package/dist/body-section.d.ts +7 -0
- package/dist/body-section.d.ts.map +1 -0
- package/dist/body-section.js +6 -0
- package/dist/body-section.js.map +1 -0
- package/dist/calendar-number.d.ts +2 -0
- package/dist/calendar-number.d.ts.map +1 -1
- package/dist/calendar-number.js +9 -2
- package/dist/calendar-number.js.map +1 -1
- package/dist/cli-args.d.ts +4 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +5 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +8 -4
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.js +33 -30
- package/dist/cli.js.map +1 -1
- package/dist/commands/compare.d.ts +8 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +72 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +17 -9
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/feedback.d.ts +5 -2
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +11 -5
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +2 -4
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/tree.d.ts +13 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +35 -5
- package/dist/commands/tree.js.map +1 -1
- package/dist/compare/atom.d.ts +31 -0
- package/dist/compare/atom.d.ts.map +1 -0
- package/dist/compare/atom.js +4 -0
- package/dist/compare/atom.js.map +1 -0
- package/dist/compare/clock-time.d.ts +8 -0
- package/dist/compare/clock-time.d.ts.map +1 -0
- package/dist/compare/clock-time.js +65 -0
- package/dist/compare/clock-time.js.map +1 -0
- package/dist/compare/extract.d.ts +15 -0
- package/dist/compare/extract.d.ts.map +1 -0
- package/dist/compare/extract.js +58 -0
- package/dist/compare/extract.js.map +1 -0
- package/dist/compare/fact-text.d.ts +23 -0
- package/dist/compare/fact-text.d.ts.map +1 -0
- package/dist/compare/fact-text.js +47 -0
- package/dist/compare/fact-text.js.map +1 -0
- package/dist/compare/markup-atoms.d.ts +11 -0
- package/dist/compare/markup-atoms.d.ts.map +1 -0
- package/dist/compare/markup-atoms.js +23 -0
- package/dist/compare/markup-atoms.js.map +1 -0
- package/dist/compare/match.d.ts +19 -0
- package/dist/compare/match.d.ts.map +1 -0
- package/dist/compare/match.js +84 -0
- package/dist/compare/match.js.map +1 -0
- package/dist/compare/outcome.d.ts +33 -0
- package/dist/compare/outcome.d.ts.map +1 -0
- package/dist/compare/outcome.js +40 -0
- package/dist/compare/outcome.js.map +1 -0
- package/dist/compare/proper-nouns.d.ts +19 -0
- package/dist/compare/proper-nouns.d.ts.map +1 -0
- package/dist/compare/proper-nouns.js +44 -0
- package/dist/compare/proper-nouns.js.map +1 -0
- package/dist/compare/render.d.ts +9 -0
- package/dist/compare/render.d.ts.map +1 -0
- package/dist/compare/render.js +64 -0
- package/dist/compare/render.js.map +1 -0
- package/dist/compare/spans.d.ts +9 -0
- package/dist/compare/spans.d.ts.map +1 -0
- package/dist/compare/spans.js +11 -0
- package/dist/compare/spans.js.map +1 -0
- package/dist/compare/text-atoms.d.ts +24 -0
- package/dist/compare/text-atoms.d.ts.map +1 -0
- package/dist/compare/text-atoms.js +67 -0
- package/dist/compare/text-atoms.js.map +1 -0
- package/dist/compare/text.d.ts +24 -0
- package/dist/compare/text.d.ts.map +1 -0
- package/dist/compare/text.js +90 -0
- package/dist/compare/text.js.map +1 -0
- package/dist/compare/tree-atoms.d.ts +20 -0
- package/dist/compare/tree-atoms.d.ts.map +1 -0
- package/dist/compare/tree-atoms.js +58 -0
- package/dist/compare/tree-atoms.js.map +1 -0
- package/dist/compare/written-names.d.ts +8 -0
- package/dist/compare/written-names.d.ts.map +1 -0
- package/dist/compare/written-names.js +24 -0
- package/dist/compare/written-names.js.map +1 -0
- package/dist/config/load.d.ts +15 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +5 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/option-problems.d.ts +16 -0
- package/dist/config/option-problems.d.ts.map +1 -0
- package/dist/config/option-problems.js +58 -0
- package/dist/config/option-problems.js.map +1 -0
- package/dist/config/read.d.ts +4 -0
- package/dist/config/read.d.ts.map +1 -0
- package/dist/config/read.js +8 -0
- package/dist/config/read.js.map +1 -0
- package/dist/config/rule-problems.d.ts +2 -4
- package/dist/config/rule-problems.d.ts.map +1 -1
- package/dist/config/rule-problems.js +7 -1
- package/dist/config/rule-problems.js.map +1 -1
- package/dist/config/style.d.ts +16 -0
- package/dist/config/style.d.ts.map +1 -0
- package/dist/config/style.js +36 -0
- package/dist/config/style.js.map +1 -0
- package/dist/config/warnings.d.ts +6 -0
- package/dist/config/warnings.d.ts.map +1 -0
- package/dist/config/warnings.js +10 -0
- package/dist/config/warnings.js.map +1 -0
- package/dist/config/write.d.ts.map +1 -1
- package/dist/config/write.js +17 -4
- package/dist/config/write.js.map +1 -1
- package/dist/custom/bounded-match.d.ts +15 -0
- package/dist/custom/bounded-match.d.ts.map +1 -0
- package/dist/custom/bounded-match.js +43 -0
- package/dist/custom/bounded-match.js.map +1 -0
- package/dist/custom/load.d.ts +8 -0
- package/dist/custom/load.d.ts.map +1 -0
- package/dist/custom/load.js +11 -0
- package/dist/custom/load.js.map +1 -0
- package/dist/custom/parse.d.ts +70 -0
- package/dist/custom/parse.d.ts.map +1 -0
- package/dist/custom/parse.js +215 -0
- package/dist/custom/parse.js.map +1 -0
- package/dist/custom/problems.d.ts +5 -0
- package/dist/custom/problems.d.ts.map +1 -0
- package/dist/custom/problems.js +78 -0
- package/dist/custom/problems.js.map +1 -0
- package/dist/custom/regex-safety.d.ts +11 -0
- package/dist/custom/regex-safety.d.ts.map +1 -0
- package/dist/custom/regex-safety.js +110 -0
- package/dist/custom/regex-safety.js.map +1 -0
- package/dist/custom/token-pattern.d.ts +8 -0
- package/dist/custom/token-pattern.d.ts.map +1 -0
- package/dist/custom/token-pattern.js +75 -0
- package/dist/custom/token-pattern.js.map +1 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +24 -2
- package/dist/detect.js.map +1 -1
- package/dist/detectors/acronym-context.d.ts +2 -0
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +12 -4
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/ai-phrasing.d.ts +20 -0
- package/dist/detectors/ai-phrasing.d.ts.map +1 -0
- package/dist/detectors/ai-phrasing.js +112 -0
- package/dist/detectors/ai-phrasing.js.map +1 -0
- package/dist/detectors/announced-count.d.ts +4 -0
- package/dist/detectors/announced-count.d.ts.map +1 -0
- package/dist/detectors/announced-count.js +20 -0
- package/dist/detectors/announced-count.js.map +1 -0
- package/dist/detectors/broken-link.d.ts +17 -0
- package/dist/detectors/broken-link.d.ts.map +1 -0
- package/dist/detectors/broken-link.js +66 -0
- package/dist/detectors/broken-link.js.map +1 -0
- package/dist/detectors/cited-name.d.ts.map +1 -1
- package/dist/detectors/cited-name.js +4 -4
- package/dist/detectors/cited-name.js.map +1 -1
- package/dist/detectors/cited-title.d.ts +11 -0
- package/dist/detectors/cited-title.d.ts.map +1 -0
- package/dist/detectors/cited-title.js +75 -0
- package/dist/detectors/cited-title.js.map +1 -0
- package/dist/detectors/concrete-evidence.d.ts +1 -1
- package/dist/detectors/concrete-evidence.d.ts.map +1 -1
- package/dist/detectors/concrete-evidence.js +2 -1
- package/dist/detectors/concrete-evidence.js.map +1 -1
- package/dist/detectors/contrast-frame.d.ts +17 -0
- package/dist/detectors/contrast-frame.d.ts.map +1 -0
- package/dist/detectors/contrast-frame.js +32 -0
- package/dist/detectors/contrast-frame.js.map +1 -0
- package/dist/detectors/custom.d.ts +8 -0
- package/dist/detectors/custom.d.ts.map +1 -0
- package/dist/detectors/custom.js +71 -0
- package/dist/detectors/custom.js.map +1 -0
- package/dist/detectors/dangling-figure.d.ts +4 -0
- package/dist/detectors/dangling-figure.d.ts.map +1 -0
- package/dist/detectors/dangling-figure.js +18 -0
- package/dist/detectors/dangling-figure.js.map +1 -0
- package/dist/detectors/doubled-punctuation.d.ts +9 -0
- package/dist/detectors/doubled-punctuation.d.ts.map +1 -0
- package/dist/detectors/doubled-punctuation.js +33 -0
- package/dist/detectors/doubled-punctuation.js.map +1 -0
- package/dist/detectors/doubled-word.d.ts +5 -0
- package/dist/detectors/doubled-word.d.ts.map +1 -1
- package/dist/detectors/doubled-word.js +24 -11
- package/dist/detectors/doubled-word.js.map +1 -1
- package/dist/detectors/enumerated-runs.d.ts +1 -1
- package/dist/detectors/enumerated-runs.d.ts.map +1 -1
- package/dist/detectors/enumerated-runs.js +15 -4
- package/dist/detectors/enumerated-runs.js.map +1 -1
- package/dist/detectors/gram-furniture.d.ts +6 -0
- package/dist/detectors/gram-furniture.d.ts.map +1 -0
- package/dist/detectors/gram-furniture.js +21 -0
- package/dist/detectors/gram-furniture.js.map +1 -0
- package/dist/detectors/gram-place.d.ts +2 -0
- package/dist/detectors/gram-place.d.ts.map +1 -1
- package/dist/detectors/gram-place.js +9 -7
- package/dist/detectors/gram-place.js.map +1 -1
- package/dist/detectors/gram-windows.d.ts +9 -0
- package/dist/detectors/gram-windows.d.ts.map +1 -0
- package/dist/detectors/gram-windows.js +27 -0
- package/dist/detectors/gram-windows.js.map +1 -0
- package/dist/detectors/heading-case.d.ts +2 -0
- package/dist/detectors/heading-case.d.ts.map +1 -1
- package/dist/detectors/heading-case.js +29 -2
- package/dist/detectors/heading-case.js.map +1 -1
- package/dist/detectors/heading-code-words.d.ts +6 -0
- package/dist/detectors/heading-code-words.d.ts.map +1 -0
- package/dist/detectors/heading-code-words.js +29 -0
- package/dist/detectors/heading-code-words.js.map +1 -0
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +7 -4
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/heading-level-skip.d.ts +12 -0
- package/dist/detectors/heading-level-skip.d.ts.map +1 -0
- package/dist/detectors/heading-level-skip.js +11 -0
- package/dist/detectors/heading-level-skip.js.map +1 -0
- package/dist/detectors/image-alt-text.d.ts +3 -0
- package/dist/detectors/image-alt-text.d.ts.map +1 -0
- package/dist/detectors/image-alt-text.js +9 -0
- package/dist/detectors/image-alt-text.js.map +1 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +34 -1
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/kutoten-consistency.d.ts +24 -0
- package/dist/detectors/kutoten-consistency.d.ts.map +1 -0
- package/dist/detectors/kutoten-consistency.js +77 -0
- package/dist/detectors/kutoten-consistency.js.map +1 -0
- package/dist/detectors/lead-in.d.ts +1 -0
- package/dist/detectors/lead-in.d.ts.map +1 -1
- package/dist/detectors/lead-in.js +2 -1
- package/dist/detectors/lead-in.js.map +1 -1
- package/dist/detectors/lexicon-match.d.ts +4 -0
- package/dist/detectors/lexicon-match.d.ts.map +1 -1
- package/dist/detectors/lexicon-match.js +7 -2
- package/dist/detectors/lexicon-match.js.map +1 -1
- package/dist/detectors/list-item.d.ts +45 -0
- package/dist/detectors/list-item.d.ts.map +1 -0
- package/dist/detectors/list-item.js +88 -0
- package/dist/detectors/list-item.js.map +1 -0
- package/dist/detectors/list-items.d.ts +31 -0
- package/dist/detectors/list-items.d.ts.map +1 -0
- package/dist/detectors/list-items.js +113 -0
- package/dist/detectors/list-items.js.map +1 -0
- package/dist/detectors/list-lead-in.d.ts +7 -0
- package/dist/detectors/list-lead-in.d.ts.map +1 -0
- package/dist/detectors/list-lead-in.js +28 -0
- package/dist/detectors/list-lead-in.js.map +1 -0
- package/dist/detectors/list-sentence.d.ts +65 -0
- package/dist/detectors/list-sentence.d.ts.map +1 -0
- package/dist/detectors/list-sentence.js +105 -0
- package/dist/detectors/list-sentence.js.map +1 -0
- package/dist/detectors/list-view.d.ts +34 -0
- package/dist/detectors/list-view.d.ts.map +1 -0
- package/dist/detectors/list-view.js +137 -0
- package/dist/detectors/list-view.js.map +1 -0
- package/dist/detectors/long-vowel.d.ts +7 -0
- package/dist/detectors/long-vowel.d.ts.map +1 -0
- package/dist/detectors/long-vowel.js +57 -0
- package/dist/detectors/long-vowel.js.map +1 -0
- package/dist/detectors/markup-finding.d.ts +6 -0
- package/dist/detectors/markup-finding.d.ts.map +1 -0
- package/dist/detectors/markup-finding.js +19 -0
- package/dist/detectors/markup-finding.js.map +1 -0
- package/dist/detectors/name-numeral.d.ts +1 -1
- package/dist/detectors/name-numeral.d.ts.map +1 -1
- package/dist/detectors/name-numeral.js +2 -7
- package/dist/detectors/name-numeral.js.map +1 -1
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +14 -10
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/oxford-comma.d.ts +7 -1
- package/dist/detectors/oxford-comma.d.ts.map +1 -1
- package/dist/detectors/oxford-comma.js +135 -229
- package/dist/detectors/oxford-comma.js.map +1 -1
- package/dist/detectors/place-run.d.ts.map +1 -1
- package/dist/detectors/place-run.js +10 -6
- package/dist/detectors/place-run.js.map +1 -1
- package/dist/detectors/placeholder-text.d.ts +9 -0
- package/dist/detectors/placeholder-text.d.ts.map +1 -0
- package/dist/detectors/placeholder-text.js +18 -0
- package/dist/detectors/placeholder-text.js.map +1 -0
- package/dist/detectors/quote-around.d.ts +3 -0
- package/dist/detectors/quote-around.d.ts.map +1 -0
- package/dist/detectors/quote-around.js +8 -0
- package/dist/detectors/quote-around.js.map +1 -0
- package/dist/detectors/register.d.ts +11 -3
- package/dist/detectors/register.d.ts.map +1 -1
- package/dist/detectors/register.js +28 -9
- package/dist/detectors/register.js.map +1 -1
- package/dist/detectors/roman-numeral.d.ts +2 -0
- package/dist/detectors/roman-numeral.d.ts.map +1 -1
- package/dist/detectors/roman-numeral.js +2 -0
- package/dist/detectors/roman-numeral.js.map +1 -1
- package/dist/detectors/sentence-ending.d.ts +17 -1
- package/dist/detectors/sentence-ending.d.ts.map +1 -1
- package/dist/detectors/sentence-ending.js +13 -7
- package/dist/detectors/sentence-ending.js.map +1 -1
- package/dist/detectors/sentence-length.d.ts.map +1 -1
- package/dist/detectors/sentence-length.js +13 -4
- package/dist/detectors/sentence-length.js.map +1 -1
- package/dist/detectors/signals.d.ts +8 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +67 -46
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts +6 -0
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +37 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/structure.d.ts +1 -3
- package/dist/detectors/structure.d.ts.map +1 -1
- package/dist/detectors/structure.js +3 -4
- package/dist/detectors/structure.js.map +1 -1
- package/dist/detectors/superlative-name.d.ts.map +1 -1
- package/dist/detectors/superlative-name.js +8 -5
- package/dist/detectors/superlative-name.js.map +1 -1
- package/dist/detectors/superlative-scope.d.ts.map +1 -1
- package/dist/detectors/superlative-scope.js +9 -4
- package/dist/detectors/superlative-scope.js.map +1 -1
- package/dist/detectors/token-column.d.ts +46 -0
- package/dist/detectors/token-column.d.ts.map +1 -0
- package/dist/detectors/token-column.js +95 -0
- package/dist/detectors/token-column.js.map +1 -0
- package/dist/detectors/unbalanced-bracket.d.ts +22 -0
- package/dist/detectors/unbalanced-bracket.d.ts.map +1 -0
- package/dist/detectors/unbalanced-bracket.js +119 -0
- package/dist/detectors/unbalanced-bracket.js.map +1 -0
- package/dist/detectors/url-run-on.d.ts +9 -0
- package/dist/detectors/url-run-on.d.ts.map +1 -0
- package/dist/detectors/url-run-on.js +19 -0
- package/dist/detectors/url-run-on.js.map +1 -0
- package/dist/document-sections.d.ts +13 -0
- package/dist/document-sections.d.ts.map +1 -0
- package/dist/document-sections.js +32 -0
- package/dist/document-sections.js.map +1 -0
- package/dist/document.d.ts +11 -4
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +90 -98
- package/dist/document.js.map +1 -1
- package/dist/email-parts.d.ts +28 -0
- package/dist/email-parts.d.ts.map +1 -0
- package/dist/email-parts.js +230 -0
- package/dist/email-parts.js.map +1 -0
- package/dist/eval.d.ts +2 -1
- package/dist/eval.d.ts.map +1 -1
- package/dist/eval.js +16 -4
- package/dist/eval.js.map +1 -1
- package/dist/figure-references.d.ts +30 -0
- package/dist/figure-references.d.ts.map +1 -0
- package/dist/figure-references.js +76 -0
- package/dist/figure-references.js.map +1 -0
- package/dist/files.d.ts +2 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +4 -0
- package/dist/files.js.map +1 -1
- package/dist/heading-label.d.ts +22 -0
- package/dist/heading-label.d.ts.map +1 -0
- package/dist/heading-label.js +50 -0
- package/dist/heading-label.js.map +1 -0
- package/dist/in-page-nav.d.ts +4 -1
- package/dist/in-page-nav.d.ts.map +1 -1
- package/dist/in-page-nav.js +53 -18
- package/dist/in-page-nav.js.map +1 -1
- package/dist/joined-view.d.ts.map +1 -1
- package/dist/joined-view.js +1 -0
- package/dist/joined-view.js.map +1 -1
- package/dist/levels.d.ts +5 -1
- package/dist/levels.d.ts.map +1 -1
- package/dist/levels.js +9 -0
- package/dist/levels.js.map +1 -1
- package/dist/line-paragraphs.d.ts +8 -0
- package/dist/line-paragraphs.d.ts.map +1 -1
- package/dist/line-paragraphs.js +33 -10
- package/dist/line-paragraphs.js.map +1 -1
- package/dist/long-vowel.d.ts +35 -0
- package/dist/long-vowel.d.ts.map +1 -0
- package/dist/long-vowel.js +71 -0
- package/dist/long-vowel.js.map +1 -0
- package/dist/markdown-node.d.ts +19 -0
- package/dist/markdown-node.d.ts.map +1 -0
- package/dist/markdown-node.js +6 -0
- package/dist/markdown-node.js.map +1 -0
- package/dist/markdown-read.d.ts +17 -0
- package/dist/markdown-read.d.ts.map +1 -0
- package/dist/markdown-read.js +37 -0
- package/dist/markdown-read.js.map +1 -0
- package/dist/markup.d.ts +22 -0
- package/dist/markup.d.ts.map +1 -0
- package/dist/markup.js +180 -0
- package/dist/markup.js.map +1 -0
- package/dist/mask.js +8 -5
- package/dist/mask.js.map +1 -1
- package/dist/number-name.d.ts +21 -5
- package/dist/number-name.d.ts.map +1 -1
- package/dist/number-name.js +119 -17
- package/dist/number-name.js.map +1 -1
- package/dist/orthography.d.ts +2 -0
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +9 -4
- package/dist/orthography.js.map +1 -1
- package/dist/page-furniture.d.ts +5 -2
- package/dist/page-furniture.d.ts.map +1 -1
- package/dist/page-furniture.js +8 -2
- package/dist/page-furniture.js.map +1 -1
- package/dist/plain-source.d.ts +1 -0
- package/dist/plain-source.d.ts.map +1 -1
- package/dist/plain-source.js +2 -1
- package/dist/plain-source.js.map +1 -1
- package/dist/plugin.d.ts +115 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/for-file.d.ts.map +1 -1
- package/dist/profile/for-file.js +3 -1
- package/dist/profile/for-file.js.map +1 -1
- package/dist/quoted-span.d.ts.map +1 -1
- package/dist/quoted-span.js +8 -3
- package/dist/quoted-span.js.map +1 -1
- package/dist/reasons.d.ts +3 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +6 -0
- package/dist/reasons.js.map +1 -1
- package/dist/reference-lists.d.ts +3 -0
- package/dist/reference-lists.d.ts.map +1 -0
- package/dist/reference-lists.js +75 -0
- package/dist/reference-lists.js.map +1 -0
- package/dist/render/explain.d.ts +7 -1
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +36 -12
- package/dist/render/explain.js.map +1 -1
- package/dist/render/friendly.d.ts.map +1 -1
- package/dist/render/friendly.js +19 -9
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/options.d.ts +7 -0
- package/dist/render/options.d.ts.map +1 -0
- package/dist/render/options.js +50 -0
- package/dist/render/options.js.map +1 -0
- package/dist/render/rules-json.d.ts +4 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +102 -12
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/rules-table.d.ts +4 -0
- package/dist/render/rules-table.d.ts.map +1 -0
- package/dist/render/rules-table.js +70 -0
- package/dist/render/rules-table.js.map +1 -0
- package/dist/render/sarif.js +4 -4
- package/dist/render/sarif.js.map +1 -1
- package/dist/render/semantic.js +3 -3
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/severity-name.d.ts +6 -0
- package/dist/render/severity-name.d.ts.map +1 -0
- package/dist/render/severity-name.js +7 -0
- package/dist/render/severity-name.js.map +1 -0
- package/dist/render/text.d.ts +7 -2
- package/dist/render/text.d.ts.map +1 -1
- package/dist/render/text.js +15 -4
- package/dist/render/text.js.map +1 -1
- package/dist/resolve-genre.d.ts +1 -1
- package/dist/resolve-genre.d.ts.map +1 -1
- package/dist/resolve-genre.js +4 -1
- package/dist/resolve-genre.js.map +1 -1
- package/dist/rule-genres.d.ts +24 -0
- package/dist/rule-genres.d.ts.map +1 -0
- package/dist/rule-genres.js +11 -0
- package/dist/rule-genres.js.map +1 -0
- package/dist/rule-guide.d.ts +54 -0
- package/dist/rule-guide.d.ts.map +1 -0
- package/dist/rule-guide.js +81 -0
- package/dist/rule-guide.js.map +1 -0
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +87 -33
- package/dist/rule-load.js.map +1 -1
- package/dist/rule-options.d.ts +50 -0
- package/dist/rule-options.d.ts.map +1 -0
- package/dist/rule-options.js +106 -0
- package/dist/rule-options.js.map +1 -0
- package/dist/run.d.ts +13 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +54 -10
- package/dist/run.js.map +1 -1
- package/dist/sentence-shape.d.ts.map +1 -1
- package/dist/sentence-shape.js +4 -4
- package/dist/sentence-shape.js.map +1 -1
- package/dist/setting-problems.d.ts +2 -1
- package/dist/setting-problems.d.ts.map +1 -1
- package/dist/setting-problems.js +6 -1
- package/dist/setting-problems.js.map +1 -1
- package/dist/span-cut.d.ts +6 -0
- package/dist/span-cut.d.ts.map +1 -0
- package/dist/span-cut.js +22 -0
- package/dist/span-cut.js.map +1 -0
- package/dist/structure/aside-depth.d.ts +21 -0
- package/dist/structure/aside-depth.d.ts.map +1 -0
- package/dist/structure/aside-depth.js +65 -0
- package/dist/structure/aside-depth.js.map +1 -0
- package/dist/structure/build.d.ts +5 -1
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +29 -17
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/closed-pairs.d.ts +8 -0
- package/dist/structure/closed-pairs.d.ts.map +1 -0
- package/dist/structure/closed-pairs.js +22 -0
- package/dist/structure/closed-pairs.js.map +1 -0
- package/dist/structure/date-range.d.ts +21 -0
- package/dist/structure/date-range.d.ts.map +1 -0
- package/dist/structure/date-range.js +45 -0
- package/dist/structure/date-range.js.map +1 -0
- package/dist/structure/known-mentions.d.ts +18 -0
- package/dist/structure/known-mentions.d.ts.map +1 -0
- package/dist/structure/known-mentions.js +78 -0
- package/dist/structure/known-mentions.js.map +1 -0
- package/dist/structure/numbering.d.ts +1 -0
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +11 -6
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/of.d.ts +3 -1
- package/dist/structure/of.d.ts.map +1 -1
- package/dist/structure/of.js +5 -1
- package/dist/structure/of.js.map +1 -1
- package/dist/structure/percent-sum.d.ts +16 -0
- package/dist/structure/percent-sum.d.ts.map +1 -0
- package/dist/structure/percent-sum.js +104 -0
- package/dist/structure/percent-sum.js.map +1 -0
- package/dist/structure/quoted-number.d.ts +8 -0
- package/dist/structure/quoted-number.d.ts.map +1 -0
- package/dist/structure/quoted-number.js +34 -0
- package/dist/structure/quoted-number.js.map +1 -0
- package/dist/structure/relative-find.d.ts.map +1 -1
- package/dist/structure/relative-find.js +34 -46
- package/dist/structure/relative-find.js.map +1 -1
- package/dist/structure/relative-resolve.d.ts.map +1 -1
- package/dist/structure/relative-resolve.js +76 -30
- package/dist/structure/relative-resolve.js.map +1 -1
- package/dist/structure/runs.d.ts +2 -0
- package/dist/structure/runs.d.ts.map +1 -1
- package/dist/structure/runs.js +2 -2
- package/dist/structure/runs.js.map +1 -1
- package/dist/structure/sexp.d.ts +1 -1
- package/dist/structure/sexp.d.ts.map +1 -1
- package/dist/structure/sexp.js +18 -6
- package/dist/structure/sexp.js.map +1 -1
- package/dist/structure/total.d.ts +3 -0
- package/dist/structure/total.d.ts.map +1 -1
- package/dist/structure/total.js +2 -2
- package/dist/structure/total.js.map +1 -1
- package/dist/structure/universal.d.ts +14 -2
- package/dist/structure/universal.d.ts.map +1 -1
- package/dist/structure/universal.js +17 -2
- package/dist/structure/universal.js.map +1 -1
- package/dist/structure/wrapped-tail.d.ts +14 -7
- package/dist/structure/wrapped-tail.d.ts.map +1 -1
- package/dist/structure/wrapped-tail.js +33 -8
- package/dist/structure/wrapped-tail.js.map +1 -1
- package/dist/style-load.d.ts +4 -0
- package/dist/style-load.d.ts.map +1 -0
- package/dist/style-load.js +28 -0
- package/dist/style-load.js.map +1 -0
- package/dist/style-parse.d.ts +19 -0
- package/dist/style-parse.d.ts.map +1 -0
- package/dist/style-parse.js +60 -0
- package/dist/style-parse.js.map +1 -0
- package/dist/subheading-line.d.ts +6 -0
- package/dist/subheading-line.d.ts.map +1 -0
- package/dist/subheading-line.js +57 -0
- package/dist/subheading-line.js.map +1 -0
- package/dist/template-syntax.d.ts +17 -0
- package/dist/template-syntax.d.ts.map +1 -0
- package/dist/template-syntax.js +140 -0
- package/dist/template-syntax.js.map +1 -0
- package/dist/text-above.d.ts +19 -0
- package/dist/text-above.d.ts.map +1 -0
- package/dist/text-above.js +43 -0
- package/dist/text-above.js.map +1 -0
- package/dist/text-figures.d.ts +10 -0
- package/dist/text-figures.d.ts.map +1 -0
- package/dist/text-figures.js +131 -0
- package/dist/text-figures.js.map +1 -0
- package/dist/trailing-aside.d.ts +2 -0
- package/dist/trailing-aside.d.ts.map +1 -0
- package/dist/trailing-aside.js +53 -0
- package/dist/trailing-aside.js.map +1 -0
- package/dist/tree-walk.d.ts +18 -0
- package/dist/tree-walk.d.ts.map +1 -0
- package/dist/tree-walk.js +45 -0
- package/dist/tree-walk.js.map +1 -0
- package/genres.yaml +26 -0
- package/package.json +5 -3
- package/reference-headings.yaml +17 -0
- package/rules/adverb-overuse.yaml +19 -0
- package/rules/agentless-passive.yaml +19 -0
- package/rules/agreement-slip.yaml +14 -1
- package/rules/ai-generated-composite.yaml +102 -0
- package/rules/ai-tell.yaml +23 -0
- package/rules/announced-count-mismatch.yaml +57 -0
- package/rules/announcing-opener.yaml +51 -0
- package/rules/assistant-residue.yaml +52 -0
- package/rules/bold-density.yaml +31 -0
- package/rules/broken-link.yaml +65 -0
- package/rules/closing-cliche.yaml +33 -0
- package/rules/colon-lead-in.yaml +59 -0
- package/rules/concrete-evidence-density.yaml +41 -0
- package/rules/contraction-consistency.yaml +18 -0
- package/rules/contrast-framing.yaml +57 -0
- package/rules/cushion-phrase-density.yaml +25 -0
- package/rules/dangling-figure-reference.yaml +61 -0
- package/rules/dangling-reference.yaml +53 -1
- package/rules/date-order.yaml +33 -1
- package/rules/date-range-reversed.yaml +46 -0
- package/rules/date-weekday-mismatch.yaml +17 -1
- package/rules/double-keigo.yaml +16 -0
- package/rules/doubled-punctuation.yaml +45 -0
- package/rules/doubled-word.yaml +17 -1
- package/rules/duplicate-definition.yaml +59 -1
- package/rules/emoji-density.yaml +25 -0
- package/rules/empty-conclusion.yaml +28 -0
- package/rules/empty-intensifier.yaml +19 -0
- package/rules/excessive-hedging.yaml +19 -0
- package/rules/expletive-construction.yaml +19 -0
- package/rules/heading-echo.yaml +31 -0
- package/rules/heading-level-skip.yaml +78 -0
- package/rules/hiragana-fukushi.yaml +16 -0
- package/rules/image-alt-text.yaml +54 -0
- package/rules/internal-jargon.yaml +21 -0
- package/rules/katakana-long-vowel.yaml +98 -0
- package/rules/kutoten-consistency.yaml +53 -0
- package/rules/latin-spacing.yaml +23 -4
- package/rules/max-kanji-continuous.yaml +16 -0
- package/rules/max-paragraph-length.yaml +33 -0
- package/rules/max-sentence-length.yaml +32 -0
- package/rules/ngram-repetition.yaml +29 -2
- package/rules/no-doubled-joshi.yaml +16 -0
- package/rules/no-em-dash.yaml +25 -0
- package/rules/no-mixed-desumasu.yaml +27 -2
- package/rules/no-nakaguro-parallel.yaml +20 -0
- package/rules/numbering-gap.yaml +53 -1
- package/rules/oxford-comma-consistency.yaml +18 -0
- package/rules/padded-intro.yaml +19 -0
- package/rules/paragraph-length-variance.yaml +49 -0
- package/rules/percent-sum-mismatch.yaml +58 -0
- package/rules/preamble-length.yaml +56 -0
- package/rules/preferred-term.yaml +27 -0
- package/rules/proper-noun-density.yaml +26 -0
- package/rules/repeated-conjunction.yaml +47 -0
- package/rules/repeated-sentence-head.yaml +23 -0
- package/rules/required-sections.yaml +49 -0
- package/rules/risk-disclosure.yaml +22 -0
- package/rules/rule-of-three.yaml +97 -0
- package/rules/sasete-itadaku.yaml +18 -0
- package/rules/section-length-uniformity.yaml +89 -0
- package/rules/sentence-initial-conjunction-run.yaml +18 -0
- package/rules/sentence-rhythm.yaml +25 -0
- package/rules/stock-transition.yaml +55 -0
- package/rules/stray-space.yaml +18 -0
- package/rules/taigen-dome-in-prose.yaml +19 -0
- package/rules/title-case-consistency.yaml +42 -0
- package/rules/total-mismatch.yaml +37 -1
- package/rules/unbalanced-bracket.yaml +56 -0
- package/rules/undefined-acronym.yaml +22 -0
- package/rules/unfilled-placeholder.yaml +50 -0
- package/rules/unqualified-superlative.yaml +19 -0
- package/rules/unsourced-number.yaml +16 -0
- package/rules/url-run-on.yaml +45 -0
- package/skills/chaff/SKILL.md +70 -0
- package/src/announced-count.ts +148 -0
- package/src/bare-url.ts +35 -0
- package/src/baseline.ts +17 -1
- package/src/body-section.ts +7 -0
- package/src/calendar-number.ts +18 -4
- package/src/cli-args.ts +6 -0
- package/src/cli-text.ts +8 -4
- package/src/cli.ts +37 -37
- package/src/commands/compare.ts +80 -0
- package/src/commands/eval.ts +19 -8
- package/src/commands/feedback.ts +12 -6
- package/src/commands/test.ts +2 -4
- package/src/commands/tree.ts +41 -5
- package/src/compare/atom.ts +33 -0
- package/src/compare/clock-time.ts +86 -0
- package/src/compare/extract.ts +78 -0
- package/src/compare/fact-text.ts +68 -0
- package/src/compare/markup-atoms.ts +29 -0
- package/src/compare/match.ts +105 -0
- package/src/compare/outcome.ts +70 -0
- package/src/compare/proper-nouns.ts +65 -0
- package/src/compare/render.ts +102 -0
- package/src/compare/spans.ts +17 -0
- package/src/compare/text-atoms.ts +95 -0
- package/src/compare/text.ts +120 -0
- package/src/compare/tree-atoms.ts +80 -0
- package/src/compare/written-names.ts +26 -0
- package/src/config/load.ts +21 -0
- package/src/config/option-problems.ts +70 -0
- package/src/config/read.ts +8 -0
- package/src/config/rule-problems.ts +12 -2
- package/src/config/style.ts +42 -0
- package/src/config/warnings.ts +13 -0
- package/src/config/write.ts +18 -4
- package/src/custom/bounded-match.ts +58 -0
- package/src/custom/load.ts +15 -0
- package/src/custom/parse.ts +274 -0
- package/src/custom/problems.ts +89 -0
- package/src/custom/regex-safety.ts +118 -0
- package/src/custom/token-pattern.ts +84 -0
- package/src/detect.ts +28 -2
- package/src/detectors/acronym-context.ts +16 -4
- package/src/detectors/ai-phrasing.ts +137 -0
- package/src/detectors/announced-count.ts +25 -0
- package/src/detectors/broken-link.ts +90 -0
- package/src/detectors/cited-name.ts +6 -5
- package/src/detectors/cited-title.ts +96 -0
- package/src/detectors/concrete-evidence.ts +3 -2
- package/src/detectors/contrast-frame.ts +54 -0
- package/src/detectors/custom.ts +89 -0
- package/src/detectors/dangling-figure.ts +22 -0
- package/src/detectors/doubled-punctuation.ts +49 -0
- package/src/detectors/doubled-word.ts +19 -4
- package/src/detectors/enumerated-runs.ts +13 -4
- package/src/detectors/gram-furniture.ts +30 -0
- package/src/detectors/gram-place.ts +9 -5
- package/src/detectors/gram-windows.ts +31 -0
- package/src/detectors/heading-case.ts +30 -2
- package/src/detectors/heading-code-words.ts +35 -0
- package/src/detectors/heading-echo.ts +8 -4
- package/src/detectors/heading-level-skip.ts +19 -0
- package/src/detectors/image-alt-text.ts +11 -0
- package/src/detectors/index.ts +43 -1
- package/src/detectors/kutoten-consistency.ts +96 -0
- package/src/detectors/lead-in.ts +3 -1
- package/src/detectors/lexicon-match.ts +9 -2
- package/src/detectors/list-item.ts +122 -0
- package/src/detectors/list-items.ts +163 -0
- package/src/detectors/list-lead-in.ts +33 -0
- package/src/detectors/list-sentence.ts +157 -0
- package/src/detectors/list-view.ts +157 -0
- package/src/detectors/long-vowel.ts +66 -0
- package/src/detectors/markup-finding.ts +24 -0
- package/src/detectors/name-numeral.ts +4 -2
- package/src/detectors/orthography.ts +23 -17
- package/src/detectors/oxford-comma.ts +178 -283
- package/src/detectors/place-run.ts +7 -4
- package/src/detectors/placeholder-text.ts +27 -0
- package/src/detectors/quote-around.ts +9 -0
- package/src/detectors/register.ts +37 -10
- package/src/detectors/roman-numeral.ts +3 -0
- package/src/detectors/sentence-ending.ts +14 -7
- package/src/detectors/sentence-length.ts +16 -5
- package/src/detectors/signals.ts +80 -46
- package/src/detectors/structure-tree.ts +45 -1
- package/src/detectors/structure.ts +3 -4
- package/src/detectors/superlative-name.ts +7 -4
- package/src/detectors/superlative-scope.ts +8 -3
- package/src/detectors/token-column.ts +149 -0
- package/src/detectors/unbalanced-bracket.ts +153 -0
- package/src/detectors/url-run-on.ts +27 -0
- package/src/document-sections.ts +46 -0
- package/src/document.ts +106 -138
- package/src/email-parts.ts +269 -0
- package/src/eval.ts +14 -4
- package/src/figure-references.ts +112 -0
- package/src/files.ts +5 -0
- package/src/heading-label.ts +55 -0
- package/src/in-page-nav.ts +48 -19
- package/src/joined-view.ts +1 -0
- package/src/levels.ts +14 -1
- package/src/line-paragraphs.ts +52 -9
- package/src/long-vowel.ts +99 -0
- package/src/markdown-node.ts +19 -0
- package/src/markdown-read.ts +44 -0
- package/src/markup.ts +210 -0
- package/src/mask.ts +8 -8
- package/src/number-name.ts +151 -24
- package/src/orthography.ts +10 -3
- package/src/page-furniture.ts +11 -2
- package/src/plain-source.ts +2 -1
- package/src/plugin.ts +101 -1
- package/src/profile/for-file.ts +3 -1
- package/src/quoted-span.ts +9 -3
- package/src/reasons.ts +9 -0
- package/src/reference-lists.ts +91 -0
- package/src/render/explain.ts +47 -13
- package/src/render/friendly.ts +20 -9
- package/src/render/options.ts +58 -0
- package/src/render/rules-json.ts +130 -14
- package/src/render/rules-table.ts +91 -0
- package/src/render/sarif.ts +4 -4
- package/src/render/semantic.ts +3 -3
- package/src/render/severity-name.ts +10 -0
- package/src/render/text.ts +21 -7
- package/src/resolve-genre.ts +4 -1
- package/src/rule-genres.ts +25 -0
- package/src/rule-guide.ts +135 -0
- package/src/rule-load.ts +91 -34
- package/src/rule-options.ts +131 -0
- package/src/run.ts +74 -11
- package/src/sentence-shape.ts +4 -5
- package/src/setting-problems.ts +7 -1
- package/src/span-cut.ts +28 -0
- package/src/structure/aside-depth.ts +67 -0
- package/src/structure/build.ts +38 -19
- package/src/structure/closed-pairs.ts +20 -0
- package/src/structure/date-range.ts +69 -0
- package/src/structure/known-mentions.ts +96 -0
- package/src/structure/numbering.ts +9 -5
- package/src/structure/of.ts +11 -3
- package/src/structure/percent-sum.ts +135 -0
- package/src/structure/quoted-number.ts +36 -0
- package/src/structure/relative-find.ts +34 -47
- package/src/structure/relative-resolve.ts +77 -30
- package/src/structure/runs.ts +2 -2
- package/src/structure/sexp.ts +17 -5
- package/src/structure/total.ts +2 -2
- package/src/structure/universal.ts +24 -4
- package/src/structure/wrapped-tail.ts +38 -13
- package/src/style-load.ts +28 -0
- package/src/style-parse.ts +77 -0
- package/src/subheading-line.ts +68 -0
- package/src/template-syntax.ts +164 -0
- package/src/text-above.ts +49 -0
- package/src/text-figures.ts +147 -0
- package/src/trailing-aside.ts +50 -0
- package/src/tree-walk.ts +48 -0
- package/styles/bunkacho.yaml +25 -0
- package/styles/ieice.yaml +29 -0
- package/styles/jis-z8301-2011.yaml +25 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import type { CustomSpec, LevelTable, Localized, RuleDefinition, Severity, TokenCondition } from "../plugin.ts";
|
|
2
|
+
import { regexRefusal, type RegexRefusal } from "./regex-safety.ts";
|
|
3
|
+
import { posTags } from "./token-pattern.ts";
|
|
4
|
+
|
|
5
|
+
// custom_rules in chaff.yaml: a team's own deterministic rules, written without code. Each becomes a RuleDefinition like the
|
|
6
|
+
// built-in ones, so findings, explain, rules --json, stet, the baseline and SARIF treat it the same. Pure: the YAML comes in parsed.
|
|
7
|
+
|
|
8
|
+
export const CUSTOM_TYPES = ["words", "pattern", "tokens"] as const;
|
|
9
|
+
|
|
10
|
+
/** Reserved for a small Node function a team writes. Refused with its own message until it is supported. */
|
|
11
|
+
const RESERVED_TYPES: readonly string[] = ["module"];
|
|
12
|
+
|
|
13
|
+
export type CustomProblem =
|
|
14
|
+
| { readonly kind: "not-a-list" }
|
|
15
|
+
| { readonly kind: "not-a-map"; readonly at: string }
|
|
16
|
+
| { readonly kind: "bad-id"; readonly at: string }
|
|
17
|
+
| { readonly kind: "duplicate-id"; readonly at: string }
|
|
18
|
+
| { readonly kind: "built-in-id"; readonly at: string }
|
|
19
|
+
| { readonly kind: "unknown-type"; readonly at: string; readonly written: string }
|
|
20
|
+
| { readonly kind: "not-yet"; readonly at: string; readonly written: string }
|
|
21
|
+
| { readonly kind: "missing"; readonly at: string; readonly field: string }
|
|
22
|
+
| { readonly kind: "unpaired-example"; readonly at: string }
|
|
23
|
+
| { readonly kind: "bad-level"; readonly at: string; readonly written: string }
|
|
24
|
+
| { readonly kind: "bad-languages"; readonly at: string }
|
|
25
|
+
| { readonly kind: "no-words"; readonly at: string }
|
|
26
|
+
| { readonly kind: "bad-pattern"; readonly at: string; readonly refusal: RegexRefusal }
|
|
27
|
+
| { readonly kind: "no-tokens"; readonly at: string }
|
|
28
|
+
| { readonly kind: "bad-token"; readonly at: string; readonly index: number }
|
|
29
|
+
| { readonly kind: "unknown-pos"; readonly at: string; readonly written: string };
|
|
30
|
+
|
|
31
|
+
export type CustomRules = { readonly rules: readonly RuleDefinition[]; readonly problems: readonly CustomProblem[] };
|
|
32
|
+
|
|
33
|
+
/** What parsing needs to know: the built-in rule ids (a team's rule may not take one) and the genres a rule applies to. */
|
|
34
|
+
export type CustomContext = { readonly builtIn: ReadonlySet<string>; readonly useFor: readonly string[] };
|
|
35
|
+
|
|
36
|
+
const RULE_ID = /^[a-z][a-z0-9-]*$/u;
|
|
37
|
+
const SEVERITIES: readonly Severity[] = ["info", "warning", "error"];
|
|
38
|
+
|
|
39
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
|
|
41
|
+
const nonEmpty = (value: unknown): value is string => typeof value === "string" && value.trim() !== "";
|
|
42
|
+
|
|
43
|
+
/** Texts by language ({ ja, en } or any language's id). undefined when there is none. */
|
|
44
|
+
const byLanguageOf = (value: unknown): Localized | undefined => {
|
|
45
|
+
if (!isRecord(value)) return undefined;
|
|
46
|
+
const entries = Object.entries(value).filter((entry): entry is [string, string] => nonEmpty(entry[1]));
|
|
47
|
+
return entries.length === 0 ? undefined : Object.fromEntries(entries.map(([language, text]) => [language, text.trim()]));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** A text by language, or one string for ja and en (and, through the "en" fallback, for any other language). */
|
|
51
|
+
const localizedOf = (value: unknown): Localized | undefined => (nonEmpty(value) ? { ja: value.trim(), en: value.trim() } : byLanguageOf(value));
|
|
52
|
+
|
|
53
|
+
/** The key an example side written as one string is kept under: it reads the same in every language. */
|
|
54
|
+
const EVERY_LANGUAGE = "*";
|
|
55
|
+
|
|
56
|
+
const exampleSideOf = (value: unknown): Localized | undefined => (nonEmpty(value) ? { [EVERY_LANGUAGE]: value.trim() } : byLanguageOf(value));
|
|
57
|
+
|
|
58
|
+
/** One rule's own problems, with the rule's id (or its place in the list) as where. */
|
|
59
|
+
type Checked<T> = { readonly value: T | undefined; readonly problems: readonly CustomProblem[] };
|
|
60
|
+
|
|
61
|
+
const ok = <T>(value: T): Checked<T> => ({ value, problems: [] });
|
|
62
|
+
const failed = <T>(...problems: CustomProblem[]): Checked<T> => ({ value: undefined, problems });
|
|
63
|
+
|
|
64
|
+
type Pair = { readonly avoid: string; readonly use: string };
|
|
65
|
+
|
|
66
|
+
/** words as a map (avoid: use) or a list (words to point at, with nothing to use instead). */
|
|
67
|
+
const pairsOf = (raw: unknown): Pair[] => {
|
|
68
|
+
if (Array.isArray(raw)) return raw.filter(nonEmpty).map((avoid) => ({ avoid: avoid.trim(), use: "" }));
|
|
69
|
+
if (!isRecord(raw)) return [];
|
|
70
|
+
return Object.entries(raw).flatMap(([avoid, use]) => (avoid.trim() === "" ? [] : [{ avoid: avoid.trim(), use: nonEmpty(use) ? use.trim() : "" }]));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? "undefined";
|
|
74
|
+
|
|
75
|
+
const wordsOf = (raw: unknown, at: string): Checked<CustomSpec> => {
|
|
76
|
+
const usable = pairsOf(raw).filter((pair) => pair.avoid !== pair.use);
|
|
77
|
+
return usable.length === 0 ? failed({ kind: "no-words", at }) : ok({ type: "words", words: usable });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const patternOf = (raw: Record<string, unknown>, at: string): Checked<CustomSpec> => {
|
|
81
|
+
const pattern = raw["pattern"];
|
|
82
|
+
if (!nonEmpty(pattern)) return failed({ kind: "missing", at, field: "pattern" });
|
|
83
|
+
const flags = raw["ignore_case"] === true ? "iu" : "u";
|
|
84
|
+
const refusal = regexRefusal(pattern, flags);
|
|
85
|
+
return refusal === undefined ? ok({ type: "pattern", pattern, flags }) : failed({ kind: "bad-pattern", at, refusal });
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const posOf = (raw: unknown, at: string): Checked<readonly string[]> => {
|
|
89
|
+
if (raw === undefined) return ok([]);
|
|
90
|
+
const written = (Array.isArray(raw) ? raw : [raw]).map((entry: unknown) => (typeof entry === "string" ? entry : printed(entry)));
|
|
91
|
+
const unknown = written.find((name) => posTags(name) === undefined);
|
|
92
|
+
if (unknown !== undefined) return failed({ kind: "unknown-pos", at, written: unknown });
|
|
93
|
+
return ok([...new Set(written.flatMap((name) => posTags(name) ?? []))]);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const conditionOf = (raw: unknown, index: number, at: string): Checked<TokenCondition> => {
|
|
97
|
+
if (!isRecord(raw)) return failed({ kind: "bad-token", at, index });
|
|
98
|
+
const pos = posOf(raw["pos"], at);
|
|
99
|
+
if (pos.value === undefined) return failed(...pos.problems);
|
|
100
|
+
// lemma is the linguists' word for the base form; either may be written.
|
|
101
|
+
const written = raw["base"] ?? raw["lemma"];
|
|
102
|
+
const base = nonEmpty(written) ? written.trim() : undefined;
|
|
103
|
+
const surface = nonEmpty(raw["surface"]) ? raw["surface"].trim() : undefined;
|
|
104
|
+
if (pos.value.length === 0 && base === undefined && surface === undefined) return failed({ kind: "bad-token", at, index });
|
|
105
|
+
return ok({ ...(pos.value.length === 0 ? {} : { pos: pos.value }), ...(base === undefined ? {} : { base }), ...(surface === undefined ? {} : { surface }) });
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const tokensOf = (raw: unknown, at: string): Checked<CustomSpec> => {
|
|
109
|
+
if (!Array.isArray(raw) || raw.length === 0) return failed({ kind: "no-tokens", at });
|
|
110
|
+
const checked = raw.map((entry: unknown, index) => conditionOf(entry, index + 1, at));
|
|
111
|
+
const problems = checked.flatMap((entry) => entry.problems);
|
|
112
|
+
const tokens = checked.flatMap((entry) => (entry.value === undefined ? [] : [entry.value]));
|
|
113
|
+
return problems.length > 0 ? failed(...problems) : ok({ type: "tokens", tokens });
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const specOf = (raw: Record<string, unknown>, type: string, at: string): Checked<CustomSpec> => {
|
|
117
|
+
if (type === "words") return wordsOf(raw["words"], at);
|
|
118
|
+
if (type === "pattern") return patternOf(raw, at);
|
|
119
|
+
return tokensOf(raw["tokens"], at);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const typeOf = (raw: Record<string, unknown>, at: string): Checked<string> => {
|
|
123
|
+
const type = raw["type"];
|
|
124
|
+
if (!nonEmpty(type)) return failed({ kind: "missing", at, field: "type" });
|
|
125
|
+
if (RESERVED_TYPES.includes(type)) return failed({ kind: "not-yet", at, written: type });
|
|
126
|
+
return CUSTOM_TYPES.some((known) => known === type) ? ok(type) : failed({ kind: "unknown-type", at, written: type });
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const severityOf = (raw: unknown, at: string): Checked<Severity> => {
|
|
130
|
+
if (raw === undefined) return ok("warning");
|
|
131
|
+
const severity = SEVERITIES.find((entry) => entry === raw);
|
|
132
|
+
return severity === undefined ? failed({ kind: "bad-level", at, written: typeof raw === "string" ? raw : printed(raw) }) : ok(severity);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** The four words as severities: normal is the rule's own level, relaxed one lower, strict one higher, where there is one. */
|
|
136
|
+
const levelsFor = (severity: Severity): LevelTable => {
|
|
137
|
+
const rank = SEVERITIES.indexOf(severity) + 1;
|
|
138
|
+
return { ...(rank < SEVERITIES.length ? { strict: rank + 1 } : {}), normal: rank, ...(rank > 1 ? { relaxed: rank - 1 } : {}) };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
type Texts = { readonly name: Localized; readonly why: Localized; readonly how_to_fix: Localized; readonly before: Localized; readonly after: Localized };
|
|
142
|
+
|
|
143
|
+
const textsOf = (raw: Record<string, unknown>, at: string): Checked<Texts> => {
|
|
144
|
+
const example = isRecord(raw["example"]) ? raw["example"] : {};
|
|
145
|
+
const found = {
|
|
146
|
+
name: localizedOf(raw["name"]),
|
|
147
|
+
why: localizedOf(raw["why"]),
|
|
148
|
+
how_to_fix: localizedOf(raw["how_to_fix"]),
|
|
149
|
+
before: exampleSideOf(example["before"]),
|
|
150
|
+
after: exampleSideOf(example["after"]),
|
|
151
|
+
};
|
|
152
|
+
const missing = Object.entries(found).flatMap(([field, text]) =>
|
|
153
|
+
text === undefined ? [{ kind: "missing" as const, at, field: field === "before" || field === "after" ? `example.${field}` : field }] : [],
|
|
154
|
+
);
|
|
155
|
+
const { name, why, how_to_fix: howToFix, before, after } = found;
|
|
156
|
+
if (name === undefined || why === undefined || howToFix === undefined || before === undefined || after === undefined) return failed(...missing);
|
|
157
|
+
return ok({ name, why, how_to_fix: howToFix, before, after });
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const languagesOf = (raw: unknown, at: string): Checked<readonly string[] | undefined> => {
|
|
161
|
+
if (raw === undefined) return ok(undefined);
|
|
162
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
163
|
+
return list.length > 0 && list.every(nonEmpty) ? ok(list.map((entry: string) => entry.trim())) : failed({ kind: "bad-languages", at });
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** The message when a rule does not write one: the replacement for a word list, else the rule's name and what was found. */
|
|
167
|
+
const defaultMessage = (spec: CustomSpec, name: Localized): Localized => {
|
|
168
|
+
if (spec.type === "words" && spec.words.every((pair) => pair.use !== ""))
|
|
169
|
+
return { ja: "「{matched}」は「{preferred}」と書きます", en: 'Write "{preferred}", not "{matched}"' };
|
|
170
|
+
return Object.fromEntries(Object.entries(name).map(([language, text]) => [language, language === "ja" ? `${text}:「{matched}」` : `${text}: "{matched}"`]));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const PLACEHOLDERS: Readonly<Record<string, Localized>> = {
|
|
174
|
+
matched: { ja: "見つけた語句", en: "the text found" },
|
|
175
|
+
preferred: { ja: "使う書き方", en: "the spelling to use" },
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const HOW_TO_FIND: Readonly<Record<CustomSpec["type"], string>> = { words: "custom-words", pattern: "custom-pattern", tokens: "custom-tokens" };
|
|
179
|
+
|
|
180
|
+
type Example = { readonly before: string; readonly after: string };
|
|
181
|
+
|
|
182
|
+
type Parts = {
|
|
183
|
+
readonly spec: CustomSpec;
|
|
184
|
+
readonly severity: Severity;
|
|
185
|
+
readonly texts: Texts;
|
|
186
|
+
readonly languages: readonly string[] | undefined;
|
|
187
|
+
readonly examples: Readonly<Record<string, Example>>;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const READER_LANGUAGES: readonly string[] = ["ja", "en"];
|
|
191
|
+
|
|
192
|
+
/** The example in each language the rule checks, where both before and after are written in it. A string counts for both. */
|
|
193
|
+
const examplesOf = (texts: Texts, languages: readonly string[] | undefined): Record<string, Example> =>
|
|
194
|
+
Object.fromEntries(
|
|
195
|
+
(languages ?? READER_LANGUAGES).flatMap((language) => {
|
|
196
|
+
const [before, after] = [texts.before[language] ?? texts.before[EVERY_LANGUAGE], texts.after[language] ?? texts.after[EVERY_LANGUAGE]];
|
|
197
|
+
return before === undefined || after === undefined ? [] : [[language, { before, after }]];
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const definitionOf = (id: string, raw: Record<string, unknown>, parts: Parts, useFor: readonly string[]): RuleDefinition => ({
|
|
202
|
+
id,
|
|
203
|
+
layer: parts.spec.type === "tokens" ? "L3" : "L2",
|
|
204
|
+
status: "stable",
|
|
205
|
+
name: parts.texts.name,
|
|
206
|
+
why: parts.texts.why,
|
|
207
|
+
how_to_fix: parts.texts.how_to_fix,
|
|
208
|
+
message: localizedOf(raw["message"]) ?? defaultMessage(parts.spec, parts.texts.name),
|
|
209
|
+
messages: {},
|
|
210
|
+
placeholders: PLACEHOLDERS,
|
|
211
|
+
levels: levelsFor(parts.severity),
|
|
212
|
+
level_sets: "severity",
|
|
213
|
+
by_genre: {},
|
|
214
|
+
how_to_find: HOW_TO_FIND[parts.spec.type],
|
|
215
|
+
word_list: undefined,
|
|
216
|
+
extra_word_lists: [],
|
|
217
|
+
what_to_check: undefined,
|
|
218
|
+
where: undefined,
|
|
219
|
+
full_sentence: undefined,
|
|
220
|
+
requires: parts.spec.type === "tokens" ? ["pos"] : [],
|
|
221
|
+
uses: [],
|
|
222
|
+
from: [],
|
|
223
|
+
languages: parts.languages,
|
|
224
|
+
use_for: useFor,
|
|
225
|
+
severity: parts.severity,
|
|
226
|
+
custom: parts.spec,
|
|
227
|
+
// The reference and explain read a rule's plain-language fields from guide; a team's rule is listed with the team's words.
|
|
228
|
+
guide: {
|
|
229
|
+
group: "team",
|
|
230
|
+
summary: parts.texts.name,
|
|
231
|
+
examples: parts.examples,
|
|
232
|
+
notFlagged: {},
|
|
233
|
+
levelMeaning: {},
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const idOf = (raw: Record<string, unknown>, index: number, seen: ReadonlySet<string>, builtIn: ReadonlySet<string>): Checked<string> => {
|
|
238
|
+
const id = raw["id"];
|
|
239
|
+
const at = typeof id === "string" && id !== "" ? id : `#${String(index + 1)}`;
|
|
240
|
+
if (typeof id !== "string" || !RULE_ID.test(id)) return failed({ kind: "bad-id", at });
|
|
241
|
+
if (builtIn.has(id)) return failed({ kind: "built-in-id", at });
|
|
242
|
+
return seen.has(id) ? failed({ kind: "duplicate-id", at }) : ok(id);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const ruleOf = (raw: unknown, index: number, seen: ReadonlySet<string>, context: CustomContext): Checked<RuleDefinition> => {
|
|
246
|
+
if (!isRecord(raw)) return failed({ kind: "not-a-map", at: `#${String(index + 1)}` });
|
|
247
|
+
const id = idOf(raw, index, seen, context.builtIn);
|
|
248
|
+
const at = id.value ?? (nonEmpty(raw["id"]) ? raw["id"] : `#${String(index + 1)}`);
|
|
249
|
+
const type = typeOf(raw, at);
|
|
250
|
+
const spec = type.value === undefined ? failed<CustomSpec>() : specOf(raw, type.value, at);
|
|
251
|
+
const severity = severityOf(raw["level"], at);
|
|
252
|
+
const texts = textsOf(raw, at);
|
|
253
|
+
const languages = languagesOf(raw["languages"], at);
|
|
254
|
+
const examples = texts.value === undefined ? {} : examplesOf(texts.value, languages.value);
|
|
255
|
+
const unpaired: CustomProblem[] = texts.value !== undefined && Object.keys(examples).length === 0 ? [{ kind: "unpaired-example", at }] : [];
|
|
256
|
+
const problems = [...[id, type, spec, severity, texts, languages].flatMap((checked) => checked.problems), ...unpaired];
|
|
257
|
+
if (id.value === undefined || spec.value === undefined || severity.value === undefined || texts.value === undefined || problems.length > 0)
|
|
258
|
+
return failed(...problems);
|
|
259
|
+
const parts = { spec: spec.value, severity: severity.value, texts: texts.value, languages: languages.value, examples };
|
|
260
|
+
return ok(definitionOf(id.value, raw, parts, context.useFor));
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/** custom_rules as written. Every problem is reported; a rule with a problem is left out, and the run is stopped by the caller. */
|
|
264
|
+
export const parseCustomRules = (raw: unknown, context: CustomContext): CustomRules => {
|
|
265
|
+
if (raw === undefined || raw === null) return { rules: [], problems: [] };
|
|
266
|
+
if (!Array.isArray(raw)) return { rules: [], problems: [{ kind: "not-a-list" }] };
|
|
267
|
+
return raw.reduce<{ rules: RuleDefinition[]; problems: CustomProblem[] }>(
|
|
268
|
+
(acc, entry: unknown, index) => {
|
|
269
|
+
const checked = ruleOf(entry, index, new Set(acc.rules.map((rule) => rule.id)), context);
|
|
270
|
+
return { rules: checked.value === undefined ? acc.rules : [...acc.rules, checked.value], problems: [...acc.problems, ...checked.problems] };
|
|
271
|
+
},
|
|
272
|
+
{ rules: [], problems: [] },
|
|
273
|
+
);
|
|
274
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Config } from "../config/load.ts";
|
|
2
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
3
|
+
import type { CustomProblem } from "./parse.ts";
|
|
4
|
+
import { customRulesOf } from "./load.ts";
|
|
5
|
+
import { MAX_PATTERN_LENGTH, MAX_REPEATS, type RegexRefusal } from "./regex-safety.ts";
|
|
6
|
+
import { POS_WRITTEN_NAMES } from "./token-pattern.ts";
|
|
7
|
+
|
|
8
|
+
/** Each problem's sentence, with {at}, {written}, {field}, {index}, {refusal} and {names} filled in from the problem. */
|
|
9
|
+
type Text = { readonly problems: Readonly<Record<CustomProblem["kind"], string>>; readonly refusal: Readonly<Record<RegexRefusal, string>> };
|
|
10
|
+
|
|
11
|
+
const TEXT: Texts<Text> = {
|
|
12
|
+
ja: {
|
|
13
|
+
refusal: {
|
|
14
|
+
"too-long": `${String(MAX_PATTERN_LENGTH)} 字を超えています。いくつかのルールに分けてください`,
|
|
15
|
+
"nested-quantifier": "繰り返しの中に繰り返しがあります((a+)+ のような形)。長い行で止まらなくなるので使えません",
|
|
16
|
+
"too-many-repeats": `回数の決まらない繰り返し(* や + や {1,9})が ${String(MAX_REPEATS)} つを超えています。長い行で止まらなくなることがあります`,
|
|
17
|
+
backreference: "後方参照(\\1)は使えません。長い行で止まらなくなることがあるためです",
|
|
18
|
+
"empty-match": "何も無い所にも当たります。すべての文で指摘が出てしまいます",
|
|
19
|
+
invalid: "正規表現として読めません",
|
|
20
|
+
},
|
|
21
|
+
problems: {
|
|
22
|
+
"not-a-list": "custom_rules はルールの並び(- id: …)で書いてください",
|
|
23
|
+
"not-a-map": "custom_rules の {at}: id や type を持つ項目として書いてください",
|
|
24
|
+
"bad-id": "custom_rules の {at}: id は英小文字で始まり、英小文字・数字・ハイフンだけで書きます(例: team-no-tbd)",
|
|
25
|
+
"duplicate-id": "custom_rules の {at}: 同じ id のルールがもう一つあります",
|
|
26
|
+
"built-in-id": "custom_rules の {at}: chaff のルールと同じ id です。別の id にしてください",
|
|
27
|
+
"unknown-type": "custom_rules の {at}: type: {written} は使えません(words / pattern / tokens)",
|
|
28
|
+
"not-yet": "custom_rules の {at}: type: {written} はまだ使えません(words / pattern / tokens)",
|
|
29
|
+
missing: "custom_rules の {at}: {field} がありません",
|
|
30
|
+
"unpaired-example": "custom_rules の {at}: example の before と after を、ルールが見る言語の同じ言語で書いてください",
|
|
31
|
+
"bad-level": "custom_rules の {at}: level: {written} は読めません(error / warning / info)",
|
|
32
|
+
"bad-languages": "custom_rules の {at}: languages は言語の並びで書いてください([ja] や [ja, en])",
|
|
33
|
+
"no-words": "custom_rules の {at}: words に語がありません(「使わない書き方: 使う書き方」か語の並び)",
|
|
34
|
+
"no-tokens": "custom_rules の {at}: tokens に語の条件がありません(- { pos: 名詞 } のように並べます)",
|
|
35
|
+
"bad-token": "custom_rules の {at}: tokens の {index} 番目に pos・base・surface のどれもありません",
|
|
36
|
+
"unknown-pos": "custom_rules の {at}: 品詞 {written} は知りません({names})",
|
|
37
|
+
"bad-pattern": "custom_rules の {at}: pattern を使えません。{refusal}",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
en: {
|
|
41
|
+
refusal: {
|
|
42
|
+
"too-long": `it is longer than ${String(MAX_PATTERN_LENGTH)} characters; split it into several rules`,
|
|
43
|
+
"nested-quantifier": "it repeats something that repeats (a shape like (a+)+), which can run for minutes on a long line",
|
|
44
|
+
"too-many-repeats": `it has more than ${String(MAX_REPEATS)} repeats of varying count (*, + or {1,9}), which can run for minutes on a long line`,
|
|
45
|
+
backreference: "backreferences (\\1) are not allowed, since they can run for minutes on a long line",
|
|
46
|
+
"empty-match": "it matches an empty string, so it would report every sentence",
|
|
47
|
+
invalid: "it is not a regular expression",
|
|
48
|
+
},
|
|
49
|
+
problems: {
|
|
50
|
+
"not-a-list": "write custom_rules as a list of rules (- id: …)",
|
|
51
|
+
"not-a-map": "custom_rules {at}: write each rule as an entry with id and type",
|
|
52
|
+
"bad-id": "custom_rules {at}: an id starts with a lowercase letter and has only lowercase letters, digits and hyphens (team-no-tbd)",
|
|
53
|
+
"duplicate-id": "custom_rules {at}: another rule has the same id",
|
|
54
|
+
"built-in-id": "custom_rules {at}: chaff has a rule with this id; choose another",
|
|
55
|
+
"unknown-type": "custom_rules {at}: type: {written} is not a type (words / pattern / tokens)",
|
|
56
|
+
"not-yet": "custom_rules {at}: type: {written} is not supported yet (words / pattern / tokens)",
|
|
57
|
+
missing: "custom_rules {at}: {field} is missing",
|
|
58
|
+
"unpaired-example": "custom_rules {at}: write example's before and after in the same language, one the rule checks",
|
|
59
|
+
"bad-level": "custom_rules {at}: cannot read level: {written} (error / warning / info)",
|
|
60
|
+
"bad-languages": "custom_rules {at}: write languages as a list ([en] or [ja, en])",
|
|
61
|
+
"no-words": "custom_rules {at}: words has no words (avoid: use pairs, or a list)",
|
|
62
|
+
"no-tokens": "custom_rules {at}: tokens has no conditions (- { pos: noun } and so on)",
|
|
63
|
+
"bad-token": "custom_rules {at}: token {index} has none of pos, base and surface",
|
|
64
|
+
"unknown-pos": "custom_rules {at}: unknown part of speech {written} ({names})",
|
|
65
|
+
"bad-pattern": "custom_rules {at}: the pattern cannot be used: {refusal}",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const PLACEHOLDER = /\{(at|written|field|index|refusal|names)\}/gu;
|
|
71
|
+
|
|
72
|
+
/** The values a problem's sentence names. Each kind carries only some of them. */
|
|
73
|
+
const valuesOf = (problem: CustomProblem, text: Text): Readonly<Record<string, string>> => ({
|
|
74
|
+
at: "at" in problem ? problem.at : "",
|
|
75
|
+
written: "written" in problem ? problem.written : "",
|
|
76
|
+
field: "field" in problem ? problem.field : "",
|
|
77
|
+
index: "index" in problem ? String(problem.index) : "",
|
|
78
|
+
refusal: "refusal" in problem ? text.refusal[problem.refusal] : "",
|
|
79
|
+
names: POS_WRITTEN_NAMES.join(" / "),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const sentenceOf = (problem: CustomProblem, text: Text): string => {
|
|
83
|
+
const values = valuesOf(problem, text);
|
|
84
|
+
return text.problems[problem.kind].replace(PLACEHOLDER, (_whole, key: string) => values[key] ?? "");
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** What in custom_rules cannot run. Each stops the run: a team's rule that silently does not run looks like a clean document. */
|
|
88
|
+
export const customRuleProblems = (config: Pick<Config, "customRules" | "path">, ui: UiLanguage): string[] =>
|
|
89
|
+
customRulesOf(config).problems.map((problem) => `chaff: ${config.path ?? "chaff.yaml"}: ${sentenceOf(problem, TEXT[ui])}`);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Whether a team's regular expression can run away. JavaScript's engine backtracks, so a pattern such as (a+)+ takes time
|
|
2
|
+
// exponential in the length of a line that almost matches. Pure: the pattern is read as text, never run on a document.
|
|
3
|
+
|
|
4
|
+
/** Longer patterns are refused: a rule that needs one is better written as several. */
|
|
5
|
+
export const MAX_PATTERN_LENGTH = 500;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* More repeats of varying count (*, +, {1,9}) than this are refused. Repeats side by side (a*a*a*b, a{0,99}a{0,99}…) do
|
|
9
|
+
* not nest, but their work multiplies: a line that almost matches costs its length to the power of their number.
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_REPEATS = 3;
|
|
12
|
+
|
|
13
|
+
export type RegexRefusal = "too-long" | "nested-quantifier" | "too-many-repeats" | "backreference" | "empty-match" | "invalid";
|
|
14
|
+
|
|
15
|
+
/** An open group: where its body starts, and whether it holds a repeat of varying count (a+, a?, a{1,3}) or an alternation. */
|
|
16
|
+
type Group = { readonly start: number; varies: boolean; alternation: boolean };
|
|
17
|
+
|
|
18
|
+
/** A quantifier: its length, whether it has no upper bound, whether its count varies, and whether it can repeat more than once. */
|
|
19
|
+
type Quantifier = { readonly length: number; readonly unbounded: boolean; readonly varies: boolean; readonly many: boolean };
|
|
20
|
+
|
|
21
|
+
type Scan = { at: number; repeats: number; readonly stack: Group[] };
|
|
22
|
+
|
|
23
|
+
const QUANTIFIER_START: ReadonlySet<string> = new Set(["*", "+", "?", "{"]);
|
|
24
|
+
const BRACE = /^\{(\d+)(,(\d*))?\}\??/u;
|
|
25
|
+
const BACKREFERENCE = /^\\(?:[1-9]|k<)/u;
|
|
26
|
+
const CLASS_BODY = /^\^?\]?(?:\\.|[^\]\\])*\]/u;
|
|
27
|
+
|
|
28
|
+
/** The quantifier at pattern[at], or undefined when none is there. */
|
|
29
|
+
const quantifierAt = (pattern: string, at: number): Quantifier | undefined => {
|
|
30
|
+
const char = pattern[at];
|
|
31
|
+
if (char === undefined || !QUANTIFIER_START.has(char)) return undefined;
|
|
32
|
+
if (char !== "{") return { length: pattern[at + 1] === "?" ? 2 : 1, unbounded: char !== "?", varies: true, many: char !== "?" };
|
|
33
|
+
const brace = BRACE.exec(pattern.slice(at));
|
|
34
|
+
if (brace === null) return undefined;
|
|
35
|
+
const [least, most] = [Number(brace[1]), brace[2] === undefined ? Number(brace[1]) : Number(brace[3] || Infinity)];
|
|
36
|
+
return { length: brace[0].length, unbounded: most === Infinity, varies: most !== least, many: most > 1 };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Just past the "]" that closes the character class opened at pattern[at]. */
|
|
40
|
+
const classEnd = (pattern: string, at: number): number => {
|
|
41
|
+
const close = CLASS_BODY.exec(pattern.slice(at + 1));
|
|
42
|
+
return close === null ? pattern.length : at + 1 + close[0].length;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const innermost = (scan: Scan): Group => scan.stack[scan.stack.length - 1] ?? { start: 0, varies: false, alternation: false };
|
|
46
|
+
|
|
47
|
+
/** A fixed string: plain characters, and punctuation escaped (Mr\.). \d, \w and the like are classes, not literals. */
|
|
48
|
+
const LITERAL = /^(?:[^\\[\](){}*+?.|^$]|\\[^A-Za-z0-9])+$/u;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Alternatives that are plain words starting with different characters ((cat|dog)+) never compete for the same text, so
|
|
52
|
+
* repeating them is linear. Any other alternation under a repeat ((a|aa)*) may try every split of the line.
|
|
53
|
+
*/
|
|
54
|
+
const distinctWords = (body: string): boolean => {
|
|
55
|
+
const alternatives = body.replace(/^\?:/u, "").split("|");
|
|
56
|
+
const firsts = alternatives.map((alternative) => (alternative.startsWith("\\") ? alternative.charAt(1) : alternative.charAt(0)));
|
|
57
|
+
return alternatives.every((alternative) => LITERAL.test(alternative)) && new Set(firsts).size === firsts.length;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Whether the group's text can be split between repeats in more than one way: it varies in length, or its alternatives compete. */
|
|
61
|
+
const competes = (pattern: string, closed: Group, end: number): boolean =>
|
|
62
|
+
closed.varies || (closed.alternation && !distinctWords(pattern.slice(closed.start, end - 1)));
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* After an atom that ends at end (closed is the group it closes, if any). A group repeated a varying number of times, more than
|
|
66
|
+
* once ((…)+, (…)*, (…){1,10}), that can itself match in more than one way ((a+), (a{1,10}), (a|aa)) is refused.
|
|
67
|
+
*/
|
|
68
|
+
const afterAtom = (pattern: string, scan: Scan, end: number, closed: Group | undefined): RegexRefusal | undefined => {
|
|
69
|
+
const quantifier = quantifierAt(pattern, end);
|
|
70
|
+
scan.at = end + (quantifier?.length ?? 0);
|
|
71
|
+
const repeatsVariably = quantifier !== undefined && quantifier.varies && quantifier.many;
|
|
72
|
+
if (repeatsVariably) scan.repeats += 1;
|
|
73
|
+
if (scan.repeats > MAX_REPEATS) return "too-many-repeats";
|
|
74
|
+
// A group that can match in more than one way makes the group around it so too, quantified or not: ((a|aa))+ is (a|aa)+.
|
|
75
|
+
const closedCompetes = closed !== undefined && competes(pattern, closed, end);
|
|
76
|
+
if (closedCompetes && repeatsVariably) return "nested-quantifier";
|
|
77
|
+
if (quantifier?.varies === true || closedCompetes) innermost(scan).varies = true;
|
|
78
|
+
return undefined;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Reads one escape, class, bracket or character at scan.at. */
|
|
82
|
+
const step = (pattern: string, scan: Scan): RegexRefusal | undefined => {
|
|
83
|
+
const char = pattern[scan.at];
|
|
84
|
+
if (char === "\\") return BACKREFERENCE.test(pattern.slice(scan.at)) ? "backreference" : afterAtom(pattern, scan, scan.at + 2, undefined);
|
|
85
|
+
if (char === "[") return afterAtom(pattern, scan, classEnd(pattern, scan.at), undefined);
|
|
86
|
+
if (char === "(") {
|
|
87
|
+
scan.stack.push({ start: scan.at + 1, varies: false, alternation: false });
|
|
88
|
+
scan.at += 1;
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (char === ")") return afterAtom(pattern, scan, scan.at + 1, scan.stack.length > 1 ? scan.stack.pop() : undefined);
|
|
92
|
+
if (char === "|") innermost(scan).alternation = true;
|
|
93
|
+
return afterAtom(pattern, scan, scan.at + 1, undefined);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A group repeated without bound that holds a repeat without bound or an alternation ((a+)+, (a|aa)*, (\w+\s?)*) is the shape
|
|
98
|
+
* that backtracks exponentially. The pattern is read once, left to right, with a stack of open groups.
|
|
99
|
+
*/
|
|
100
|
+
const nestedRepeat = (pattern: string): RegexRefusal | undefined => {
|
|
101
|
+
const scan: Scan = { at: 0, repeats: 0, stack: [{ start: 0, varies: false, alternation: false }] };
|
|
102
|
+
while (scan.at < pattern.length) {
|
|
103
|
+
const refusal = step(pattern, scan);
|
|
104
|
+
if (refusal !== undefined) return refusal;
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Why the pattern is refused, or undefined when it may run. flags are the ones the rule runs with ("u", "iu"). */
|
|
110
|
+
export const regexRefusal = (pattern: string, flags: string): RegexRefusal | undefined => {
|
|
111
|
+
if (pattern.length > MAX_PATTERN_LENGTH) return "too-long";
|
|
112
|
+
try {
|
|
113
|
+
if (new RegExp(pattern, flags).test("")) return "empty-match";
|
|
114
|
+
} catch {
|
|
115
|
+
return "invalid";
|
|
116
|
+
}
|
|
117
|
+
return nestedRepeat(pattern);
|
|
118
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Token, TokenCondition } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
// A team's morphology pattern: a run of tokens, each matching a condition on its part of speech, base form or surface.
|
|
4
|
+
// 「〜を行う」 is [{ pos: 名詞 }, { surface: を }, { base: 行う }]. Pure: tokens come from the language adapter.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parts of speech are Universal Dependencies tags (NOUN, VERB, ADP), the same for every language. A team may also write the
|
|
8
|
+
* everyday name in Japanese or English; each maps to the tags the adapters give for it.
|
|
9
|
+
*/
|
|
10
|
+
const POS_NAMES: Readonly<Record<string, readonly string[]>> = {
|
|
11
|
+
名詞: ["NOUN", "PROPN", "PRON", "NUM"],
|
|
12
|
+
固有名詞: ["PROPN"],
|
|
13
|
+
代名詞: ["PRON"],
|
|
14
|
+
動詞: ["VERB"],
|
|
15
|
+
形容詞: ["ADJ"],
|
|
16
|
+
副詞: ["ADV"],
|
|
17
|
+
助詞: ["ADP", "PART", "SCONJ", "CCONJ"],
|
|
18
|
+
助動詞: ["AUX"],
|
|
19
|
+
接続詞: ["CCONJ", "SCONJ"],
|
|
20
|
+
連体詞: ["DET"],
|
|
21
|
+
記号: ["PUNCT", "SYM"],
|
|
22
|
+
noun: ["NOUN", "PROPN", "PRON"],
|
|
23
|
+
verb: ["VERB"],
|
|
24
|
+
adjective: ["ADJ"],
|
|
25
|
+
adverb: ["ADV"],
|
|
26
|
+
preposition: ["ADP"],
|
|
27
|
+
determiner: ["DET"],
|
|
28
|
+
pronoun: ["PRON"],
|
|
29
|
+
conjunction: ["CCONJ", "SCONJ"],
|
|
30
|
+
auxiliary: ["AUX"],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const UPOS: ReadonlySet<string> = new Set([
|
|
34
|
+
"ADJ",
|
|
35
|
+
"ADP",
|
|
36
|
+
"ADV",
|
|
37
|
+
"AUX",
|
|
38
|
+
"CCONJ",
|
|
39
|
+
"DET",
|
|
40
|
+
"INTJ",
|
|
41
|
+
"NOUN",
|
|
42
|
+
"NUM",
|
|
43
|
+
"PART",
|
|
44
|
+
"PRON",
|
|
45
|
+
"PROPN",
|
|
46
|
+
"PUNCT",
|
|
47
|
+
"SCONJ",
|
|
48
|
+
"SYM",
|
|
49
|
+
"VERB",
|
|
50
|
+
"X",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/** The tags a written part of speech stands for, or undefined when chaff does not know the name. */
|
|
54
|
+
export const posTags = (written: string): readonly string[] | undefined => {
|
|
55
|
+
const upper = written.toUpperCase();
|
|
56
|
+
if (UPOS.has(upper)) return [upper];
|
|
57
|
+
return POS_NAMES[written] ?? POS_NAMES[written.toLowerCase()];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** The names a team may write, for the message about an unknown one. */
|
|
61
|
+
export const POS_WRITTEN_NAMES: readonly string[] = [...UPOS, ...Object.keys(POS_NAMES)];
|
|
62
|
+
|
|
63
|
+
const sameWord = (left: string, right: string): boolean => left.toLowerCase() === right.toLowerCase();
|
|
64
|
+
|
|
65
|
+
const holds = (token: Token, condition: TokenCondition): boolean =>
|
|
66
|
+
(condition.pos === undefined || condition.pos.includes(token.pos)) &&
|
|
67
|
+
(condition.surface === undefined || sameWord(token.surface, condition.surface)) &&
|
|
68
|
+
(condition.base === undefined || sameWord(token.lemma ?? token.surface, condition.base));
|
|
69
|
+
|
|
70
|
+
/** Every run of consecutive tokens where each holds its condition, as [first, last] token indexes. Runs do not overlap. */
|
|
71
|
+
export const tokenRuns = (tokens: readonly Token[], conditions: readonly TokenCondition[]): (readonly [number, number])[] => {
|
|
72
|
+
if (conditions.length === 0) return [];
|
|
73
|
+
return tokens.reduce<{ runs: (readonly [number, number])[]; next: number }>(
|
|
74
|
+
(acc, _token, start) => {
|
|
75
|
+
if (start < acc.next || start + conditions.length > tokens.length) return acc;
|
|
76
|
+
const matches = conditions.every((condition, offset) => {
|
|
77
|
+
const token = tokens[start + offset];
|
|
78
|
+
return token !== undefined && holds(token, condition);
|
|
79
|
+
});
|
|
80
|
+
return matches ? { runs: [...acc.runs, [start, start + conditions.length - 1]], next: start + conditions.length } : acc;
|
|
81
|
+
},
|
|
82
|
+
{ runs: [], next: 0 },
|
|
83
|
+
).runs;
|
|
84
|
+
};
|
package/src/detect.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parse } from "yaml";
|
|
1
5
|
import { japaneseRatio, latinRatio } from "./detect-language.ts";
|
|
6
|
+
import { referenceListSpans } from "./reference-lists.ts";
|
|
7
|
+
import { withoutSpans } from "./soft-break.ts";
|
|
2
8
|
|
|
3
9
|
export type LanguageGuess = { readonly language: string; readonly confidence: number; readonly from: string };
|
|
4
10
|
|
|
@@ -8,8 +14,28 @@ export type LanguageGuess = { readonly language: string; readonly confidence: nu
|
|
|
8
14
|
*/
|
|
9
15
|
const JAPANESE_FLOOR = 0.15;
|
|
10
16
|
|
|
17
|
+
const HEADINGS_FILE = join(dirname(fileURLToPath(import.meta.url)), "..", "reference-headings.yaml");
|
|
18
|
+
|
|
19
|
+
const isStringList = (value: unknown): value is string[] => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
20
|
+
|
|
21
|
+
const readHeadings = (): string[] => {
|
|
22
|
+
const raw: unknown = parse(readFileSync(HEADINGS_FILE, "utf8"));
|
|
23
|
+
const headings: unknown = typeof raw === "object" && raw !== null && "headings" in raw ? raw.headings : undefined;
|
|
24
|
+
if (!isStringList(headings)) throw new Error(`${HEADINGS_FILE}: headings は文字列の並びであること`);
|
|
25
|
+
return headings;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const REFERENCE_HEADINGS = readHeadings();
|
|
29
|
+
|
|
30
|
+
/** 本文だけ。文献一覧は引いた文献の言語で書かれるので数えない。本文に字が無ければ(文献一覧だけの文書)全体。 */
|
|
31
|
+
const bodyOf = (source: string): string => {
|
|
32
|
+
const body = withoutSpans(source, referenceListSpans(source, REFERENCE_HEADINGS));
|
|
33
|
+
return body.trim() === "" ? source : body;
|
|
34
|
+
};
|
|
35
|
+
|
|
11
36
|
export const guessLanguage = (source: string): LanguageGuess => {
|
|
12
|
-
const
|
|
37
|
+
const body = bodyOf(source);
|
|
38
|
+
const japanese = japaneseRatio(body);
|
|
13
39
|
if (japanese >= JAPANESE_FLOOR) return { language: "ja", confidence: japanese, from: "content" };
|
|
14
|
-
return { language: "en", confidence: latinRatio(
|
|
40
|
+
return { language: "en", confidence: latinRatio(body), from: "content" };
|
|
15
41
|
};
|