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
package/src/document.ts
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { gfmTable } from "micromark-extension-gfm-table";
|
|
3
|
-
import { gfmTableFromMarkdown } from "mdast-util-gfm-table";
|
|
4
|
-
import { frontmatter } from "micromark-extension-frontmatter";
|
|
5
|
-
import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
|
1
|
+
import { headingReadersOf, sectionsOf } from "./document-sections.ts";
|
|
6
2
|
import { atxHeadingText, headingText } from "./heading-text.ts";
|
|
7
3
|
import { hasTitle } from "./heading-title.ts";
|
|
8
4
|
import { maskSpans } from "./mask.ts";
|
|
9
5
|
import { spansWithin, unmaskedSoftBreaks } from "./soft-break.ts";
|
|
10
6
|
import { segmentJoined } from "./joined-view.ts";
|
|
11
|
-
import {
|
|
7
|
+
import { documentLineParagraphs } from "./line-paragraphs.ts";
|
|
8
|
+
import { subheadingPieces } from "./subheading-line.ts";
|
|
12
9
|
import { speakerLabels } from "./speaker-labels.ts";
|
|
13
10
|
import { buildTree, type Outline } from "./structure/build.ts";
|
|
14
11
|
import { isMarkdownPath } from "./structure/markdown-path.ts";
|
|
15
|
-
import {
|
|
12
|
+
import { layoutMasks, textOutline } from "./page-furniture.ts";
|
|
16
13
|
import { tokenizedLexicons } from "./lexicon-tokens.ts";
|
|
14
|
+
import { plainSource } from "./plain-source.ts";
|
|
17
15
|
import { inPageAnchors, isInPageNavigation, isNavigationList, type InPageAnchors } from "./in-page-nav.ts";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type Node
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
16
|
+
import { eachPreOrder } from "./tree-walk.ts";
|
|
17
|
+
import { alertReader } from "./template-syntax.ts";
|
|
18
|
+
import { opaqueSpans, parse, readMarkdown, spansOfType } from "./markdown-read.ts";
|
|
19
|
+
import { spanOf, type MarkdownNode as Node } from "./markdown-node.ts";
|
|
20
|
+
import { emailParts, emailVocabulary } from "./email-parts.ts";
|
|
21
|
+
import { cutTextSpans } from "./span-cut.ts";
|
|
22
|
+
import { markdownFigures } from "./text-figures.ts";
|
|
23
|
+
import { documentMarkup } from "./markup.ts";
|
|
24
|
+
import { BARE_URL } from "./bare-url.ts";
|
|
25
|
+
import type { BulletList, LanguageAdapter, Markup, Paragraph, ProseDocument, Sentence, Span, StructureNode, DocumentProfile } from "./plugin.ts";
|
|
29
26
|
|
|
30
27
|
/**
|
|
31
28
|
* 本文として数えないもの。
|
|
@@ -36,22 +33,8 @@ type Node = {
|
|
|
36
33
|
*/
|
|
37
34
|
const NOT_PROSE = new Set(["code", "inlineCode", "html", "yaml", "toml", "table", "blockquote", "thematicBreak", "definition", "image", "imageReference"]);
|
|
38
35
|
|
|
39
|
-
const spanOf = (node: Node): Span | undefined => {
|
|
40
|
-
const start = node.position?.start.offset;
|
|
41
|
-
const end = node.position?.end.offset;
|
|
42
|
-
return start === undefined || end === undefined ? undefined : { start, end };
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const walk = (node: Node, visit: (node: Node) => void): void => {
|
|
46
|
-
visit(node);
|
|
47
|
-
(node.children ?? []).forEach((child) => walk(child, visit));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const parse = (source: string): Node =>
|
|
51
|
-
fromMarkdown(source, { extensions: [gfmTable(), frontmatter(["yaml"])], mdastExtensions: [gfmTableFromMarkdown(), frontmatterFromMarkdown(["yaml"])] });
|
|
52
|
-
|
|
53
36
|
/** link は `[text](url)` の外側だけを覆う。表示される文字は本文なので残す。 */
|
|
54
|
-
const linkChrome = (node: Node): Span[] => {
|
|
37
|
+
export const linkChrome = (node: Node): Span[] => {
|
|
55
38
|
const whole = spanOf(node);
|
|
56
39
|
const first = node.children?.[0];
|
|
57
40
|
const last = node.children?.at(-1);
|
|
@@ -88,27 +71,26 @@ const EMPHASIS = new Set(["strong", "emphasis", "delete"]);
|
|
|
88
71
|
* `:::note` `:::` のようなディレクティブ。Zenn・Docusaurus・VitePress が使う記法で、
|
|
89
72
|
* 標準の Markdown には無いため段落として解析される。囲みの指定であって文章ではない。
|
|
90
73
|
*/
|
|
91
|
-
const DIRECTIVE =
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* `https://…` をそのまま書いた URL。GFM の autolink 拡張を入れていないので mdast では
|
|
95
|
-
* ただのテキストになり、本文として残る。残すと、見出しと URL の中の識別子が一致して
|
|
96
|
-
* 「見出しの繰り返し」と読まれる。表示される文字も本文ではない。
|
|
97
|
-
*/
|
|
98
|
-
const BARE_URL = /https?:\/\/[^\s)<>"'\]]+/gu;
|
|
74
|
+
const DIRECTIVE = /^[ \t]*:::[^\n]*/gmu;
|
|
99
75
|
|
|
100
76
|
const matchSpans = (source: string, pattern: RegExp): Span[] =>
|
|
101
77
|
[...source.matchAll(pattern)].map((match) => ({ start: match.index, end: match.index + match[0].length }));
|
|
102
78
|
|
|
103
79
|
const directiveSpans = (source: string): Span[] => [...matchSpans(source, DIRECTIVE), ...matchSpans(source, BARE_URL)];
|
|
104
80
|
|
|
105
|
-
const collectMasks = (root: Node, source: string, anchors: InPageAnchors): Span[] => {
|
|
106
|
-
const spans: Span[] = [...directiveSpans(source)];
|
|
107
|
-
|
|
81
|
+
const collectMasks = (root: Node, source: string, anchors: InPageAnchors, syntax: readonly Span[]): Span[] => {
|
|
82
|
+
const spans: Span[] = [...directiveSpans(source), ...syntax];
|
|
83
|
+
const alertOf = alertReader(source);
|
|
84
|
+
eachPreOrder(root, (node) => {
|
|
108
85
|
if (node.type === "link" || node.type === "linkReference") {
|
|
109
86
|
spans.push(...linkChrome(node));
|
|
110
87
|
return;
|
|
111
88
|
}
|
|
89
|
+
const alert = alertOf(node);
|
|
90
|
+
if (alert !== undefined) {
|
|
91
|
+
spans.push(...alert);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
112
94
|
if (!NOT_PROSE.has(node.type) && node.type !== "heading" && !isInPageNavigation(node, anchors)) return;
|
|
113
95
|
const span = spanOf(node);
|
|
114
96
|
if (span !== undefined) spans.push(span);
|
|
@@ -118,7 +100,7 @@ const collectMasks = (root: Node, source: string, anchors: InPageAnchors): Span[
|
|
|
118
100
|
|
|
119
101
|
const emphasisSpans = (root: Node, source: string): Span[] => {
|
|
120
102
|
const spans: Span[] = [];
|
|
121
|
-
|
|
103
|
+
eachPreOrder(root, (node) => {
|
|
122
104
|
if (EMPHASIS.has(node.type)) spans.push(...emphasisChrome(node, source));
|
|
123
105
|
});
|
|
124
106
|
return spans;
|
|
@@ -126,7 +108,7 @@ const emphasisSpans = (root: Node, source: string): Span[] => {
|
|
|
126
108
|
|
|
127
109
|
const textOf = (node: Node, source: string): string => {
|
|
128
110
|
const parts: string[] = [];
|
|
129
|
-
|
|
111
|
+
eachPreOrder(node, (child) => {
|
|
130
112
|
if (child.type !== "text" && child.type !== "inlineCode") return;
|
|
131
113
|
const span = spanOf(child);
|
|
132
114
|
if (span !== undefined) parts.push(source.slice(span.start, span.end));
|
|
@@ -136,17 +118,25 @@ const textOf = (node: Node, source: string): string => {
|
|
|
136
118
|
|
|
137
119
|
export type Heading = { readonly depth: number; readonly text: string; readonly start: number; readonly end: number };
|
|
138
120
|
|
|
139
|
-
|
|
121
|
+
/**
|
|
122
|
+
* `neutral` is the source with the template syntax blanked: `## Usage {% if x %}` is headed "Usage". Whether a
|
|
123
|
+
* heading has a title is read from the source, since `## {{ product }}` names something.
|
|
124
|
+
* 引用した返信(outside)の中の見出しは、ほかの人の文書の見出しなので数えない。
|
|
125
|
+
*/
|
|
126
|
+
const headingsOf = (root: Node, source: string, neutral: string, outside: readonly Span[] = []): Heading[] => {
|
|
140
127
|
const found: Heading[] = [];
|
|
141
|
-
|
|
128
|
+
eachPreOrder(root, (node) => {
|
|
142
129
|
if (node.type !== "heading") return;
|
|
143
130
|
const span = spanOf(node);
|
|
144
131
|
const depth = typeof node === "object" && "depth" in node && typeof node.depth === "number" ? node.depth : 1;
|
|
145
|
-
if (span === undefined) return;
|
|
132
|
+
if (span === undefined || startsInside(span, outside)) return;
|
|
146
133
|
// ATX(行頭が #)なら閉じの # も外す。setext の見出しの末尾の # は言葉なので残す。
|
|
147
134
|
const read = source.startsWith("#", span.start) ? atxHeadingText : headingText;
|
|
148
|
-
const
|
|
149
|
-
|
|
135
|
+
const written = textOf(node, source);
|
|
136
|
+
const shown = textOf(node, neutral);
|
|
137
|
+
// A blanked tag leaves a run of spaces where the reader sees one gap.
|
|
138
|
+
const text = read(shown === written ? written : shown.replace(/ {2,}/gu, " "));
|
|
139
|
+
if (hasTitle(read(written))) found.push({ depth, text, start: span.start, end: span.end });
|
|
150
140
|
});
|
|
151
141
|
return found;
|
|
152
142
|
};
|
|
@@ -162,7 +152,7 @@ const startsInside = (span: Span, regions: readonly Span[]): boolean => regions.
|
|
|
162
152
|
*/
|
|
163
153
|
const strongSpans = (root: Node, masked: readonly Span[]): Span[] => {
|
|
164
154
|
const found: Span[] = [];
|
|
165
|
-
|
|
155
|
+
eachPreOrder(root, (node) => {
|
|
166
156
|
if (node.type !== "strong") return;
|
|
167
157
|
const span = spanOf(node);
|
|
168
158
|
if (span !== undefined && !startsInside(span, masked)) found.push(span);
|
|
@@ -170,18 +160,6 @@ const strongSpans = (root: Node, masked: readonly Span[]): Span[] => {
|
|
|
170
160
|
return found;
|
|
171
161
|
};
|
|
172
162
|
|
|
173
|
-
const within = (span: Span, from: number, to: number): boolean => span.start >= from && span.start < to;
|
|
174
|
-
|
|
175
|
-
const spansOfType = (root: Node, type: string, keep: (node: Node) => boolean = () => true): Span[] => {
|
|
176
|
-
const found: Span[] = [];
|
|
177
|
-
walk(root, (node) => {
|
|
178
|
-
if (node.type !== type || !keep(node)) return;
|
|
179
|
-
const span = spanOf(node);
|
|
180
|
-
if (span !== undefined) found.push(span);
|
|
181
|
-
});
|
|
182
|
-
return found;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
163
|
/**
|
|
186
164
|
* 文の分割は**段落ごと**に行う。
|
|
187
165
|
*
|
|
@@ -193,6 +171,10 @@ const shift = (span: Span, by: number): Span => ({ start: by + span.start, end:
|
|
|
193
171
|
|
|
194
172
|
const breaksWithin = (breaks: readonly Span[], paragraph: Span): Span[] => spansWithin(breaks, paragraph).map((span) => shift(span, -paragraph.start));
|
|
195
173
|
|
|
174
|
+
/** 段落を、中の小見出しの行(「(経済再生)」)の後ろで切った片。片ごとに分割すれば、小見出しが次の文に入らない。 */
|
|
175
|
+
const piecesOf = (prose: string, paragraphs: readonly Span[]): Span[] =>
|
|
176
|
+
paragraphs.flatMap((paragraph) => subheadingPieces(prose.slice(paragraph.start, paragraph.end)).map((piece) => shift(piece, paragraph.start)));
|
|
177
|
+
|
|
196
178
|
const sentencesOf = (prose: string, paragraphs: readonly Span[], adapter: LanguageAdapter, softBreaks: readonly Span[]): Sentence[] =>
|
|
197
179
|
paragraphs.flatMap((paragraph) =>
|
|
198
180
|
segmentJoined(prose.slice(paragraph.start, paragraph.end), breaksWithin(softBreaks, paragraph), (text) => adapter.segment(text))
|
|
@@ -202,38 +184,10 @@ const sentencesOf = (prose: string, paragraphs: readonly Span[], adapter: Langua
|
|
|
202
184
|
text: sentence.text,
|
|
203
185
|
...(sentence.tokens === undefined ? {} : { tokens: sentence.tokens.map((token) => ({ ...token, span: shift(token.span, paragraph.start) })) }),
|
|
204
186
|
...(sentence.wrapBreaks === undefined ? {} : { wrapBreaks: sentence.wrapBreaks.map((span) => shift(span, paragraph.start)) }),
|
|
187
|
+
...(sentence.embeddedLanguage === undefined ? {} : { embeddedLanguage: sentence.embeddedLanguage }),
|
|
205
188
|
})),
|
|
206
189
|
);
|
|
207
190
|
|
|
208
|
-
/** 見出しの語。品詞を読んでいない文書(文が tokens を持たない)では分けない。 */
|
|
209
|
-
const headingTokensOf = (adapter: LanguageAdapter, tagged: boolean, heading: string): { headingTokens?: readonly Token[] } =>
|
|
210
|
-
tagged && heading !== "" ? { headingTokens: adapter.segment(heading).sentences.flatMap((sentence) => sentence.tokens ?? []) } : {};
|
|
211
|
-
|
|
212
|
-
const sectionsOf = (
|
|
213
|
-
headings: readonly Heading[],
|
|
214
|
-
sentences: readonly Sentence[],
|
|
215
|
-
strongs: readonly Span[],
|
|
216
|
-
length: number,
|
|
217
|
-
tokensOf: (heading: string) => { headingTokens?: readonly Token[] },
|
|
218
|
-
): Section[] => {
|
|
219
|
-
const bounds = headings.map((heading, index) => ({ heading, from: heading.end, to: headings[index + 1]?.start ?? length }));
|
|
220
|
-
const lead = { heading: { depth: 0, text: "", start: 0, end: 0 }, from: 0, to: headings[0]?.start ?? length };
|
|
221
|
-
return [lead, ...bounds]
|
|
222
|
-
.filter((bound) => bound.to > bound.from)
|
|
223
|
-
.map(({ heading, from, to }) => {
|
|
224
|
-
const inside = sentences.filter((sentence) => within(sentence.span, from, to));
|
|
225
|
-
return {
|
|
226
|
-
depth: heading.depth,
|
|
227
|
-
heading: heading.text,
|
|
228
|
-
...tokensOf(heading.text),
|
|
229
|
-
span: { start: from, end: to },
|
|
230
|
-
sentences: inside,
|
|
231
|
-
strongCount: strongs.filter((span) => within(span, from, to)).length,
|
|
232
|
-
firstSentence: inside[0],
|
|
233
|
-
};
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
|
|
237
191
|
/** sentences(並び順)の中で、start が offset 以上の最初の添字。1 行 1 段落の何万行でも、段落ごとに全部をなめない。 */
|
|
238
192
|
const firstStartingAt = (sentences: readonly Sentence[], offset: number): number => {
|
|
239
193
|
const search = (low: number, high: number): number => {
|
|
@@ -247,24 +201,18 @@ const firstStartingAt = (sentences: readonly Sentence[], offset: number): number
|
|
|
247
201
|
const sentencesWithin = (sentences: readonly Sentence[], span: Span): Sentence[] =>
|
|
248
202
|
sentences.slice(firstStartingAt(sentences, span.start), firstStartingAt(sentences, span.end));
|
|
249
203
|
|
|
250
|
-
const paragraphsOf = (prose: string, spans: readonly Span[], sentences: readonly Sentence[], listSpans: readonly Span[]): Paragraph[] =>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
prose,
|
|
258
|
-
span,
|
|
259
|
-
sentencesWithin(sentences, span).map((sentence) => sentence.span),
|
|
260
|
-
),
|
|
261
|
-
)
|
|
262
|
-
.map((span) => ({ span, sentences: sentencesWithin(sentences, span) }));
|
|
204
|
+
const paragraphsOf = (prose: string, spans: readonly Span[], sentences: readonly Sentence[], listSpans: readonly Span[]): Paragraph[] => {
|
|
205
|
+
// 箇条書きの中の段落は「段落」として数えない。項目 1 つを 1 段落と読むと、
|
|
206
|
+
// 段落あたりの文数も長さのばらつきも、箇条書きの多い文書で壊れる。
|
|
207
|
+
const outsideLists = spans.filter((span) => !listSpans.some((list) => span.start >= list.start && span.start < list.end));
|
|
208
|
+
const withSentences = outsideLists.map((span) => ({ span, sentences: sentencesWithin(sentences, span).map((sentence) => sentence.span) }));
|
|
209
|
+
return documentLineParagraphs(prose, withSentences).map((span) => ({ span, sentences: sentencesWithin(sentences, span) }));
|
|
210
|
+
};
|
|
263
211
|
|
|
264
212
|
/** 箇条書きは list ノードの直下の項目を数える。入れ子の項目は内側の list のものとして数える。目次のような案内だけの箇条書きは数えない。 */
|
|
265
213
|
const listsOf = (root: Node, source: string, anchors: InPageAnchors): BulletList[] => {
|
|
266
214
|
const found: BulletList[] = [];
|
|
267
|
-
|
|
215
|
+
eachPreOrder(root, (node) => {
|
|
268
216
|
if (node.type !== "list" || isNavigationList(node, anchors)) return;
|
|
269
217
|
const span = spanOf(node);
|
|
270
218
|
if (span === undefined) return;
|
|
@@ -311,33 +259,35 @@ const proseOf = (source: string, masked: readonly Span[]): string => {
|
|
|
311
259
|
return maskSpans(unlabelled, speakerLabels(unlabelled));
|
|
312
260
|
};
|
|
313
261
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
262
|
+
const teamLexicons = (adapter: LanguageAdapter, team: TeamRules): LanguageAdapter["lexicons"] => ({
|
|
263
|
+
...adapter.lexicons,
|
|
264
|
+
"internal-jargon": team.jargon.map((pattern) => ({ pattern })),
|
|
265
|
+
// 使わない書き方を pattern に、使う書き方を instead_of に置く。語彙表の「同じことの別の書き方」と同じ向き。
|
|
266
|
+
"preferred-term": Object.entries(team.prefer ?? {}).map(([pattern, use]) => ({ pattern, instead_of: use })),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const documentOf = (path: string, source: string, adapter: LanguageAdapter, team: TeamRules, profile: DocumentProfile | undefined): ProseDocument => {
|
|
270
|
+
const markdown = isMarkdownPath(path);
|
|
271
|
+
const { root, syntax } = markdown ? readMarkdown(source) : { root: parse(source), syntax: [] };
|
|
322
272
|
const anchors = inPageAnchors(root);
|
|
273
|
+
const emailLayout = emailParts(source, emailVocabulary(adapter.lexicons));
|
|
323
274
|
// 強調の記号は「本文でないもの」だが、太字の数を数えるときの「覆われた場所」ではない。
|
|
324
275
|
// 同じ集合にすると、太字が自分の記号のせいで覆われた場所にあることになり、1 つも数えられなくなる。
|
|
325
|
-
//
|
|
326
|
-
const blocks = [...collectMasks(root, source, anchors), ...(
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
276
|
+
// ページのヘッダーとフッター(テキストの文書)と、線で描いた図も本文ではない。
|
|
277
|
+
const blocks = [...collectMasks(root, source, anchors, syntax), ...layoutMasks(root, source, markdown), ...emailLayout.furniture];
|
|
278
|
+
const prose = proseOf(source, [...blocks, ...emphasisSpans(root, source)]);
|
|
279
|
+
// ページの案内は段落としても数えない。数えると、目次の行が「本題までの段落」に入る。メールのヘッダーや署名の行は段落から切り取る。
|
|
280
|
+
const paragraphSpans = cutTextSpans(
|
|
281
|
+
spansOfType(root, "paragraph", (node) => !isInPageNavigation(node, anchors)),
|
|
282
|
+
emailLayout.furniture,
|
|
283
|
+
source,
|
|
284
|
+
);
|
|
285
|
+
const headings = headingsOf(root, source, maskSpans(source, syntax), emailLayout.replyQuotes);
|
|
331
286
|
const listItems = spansOfType(root, "listItem");
|
|
332
287
|
// Markdown は段落を流し込んで表示するので、段落の中の改行は読み手に見えない。テキストの文書は行をそのまま見せる(法令は 1 行 1 号)。
|
|
333
|
-
const softBreaks =
|
|
334
|
-
const sentences = sentencesOf(prose, paragraphSpans, adapter, softBreaks);
|
|
335
|
-
const lexicons =
|
|
336
|
-
...adapter.lexicons,
|
|
337
|
-
"internal-jargon": team.jargon.map((pattern) => ({ pattern })),
|
|
338
|
-
// 使わない書き方を pattern に、使う書き方を instead_of に置く。語彙表の「同じことの別の書き方」と同じ向き。
|
|
339
|
-
"preferred-term": Object.entries(team.prefer ?? {}).map(([pattern, use]) => ({ pattern, instead_of: use })),
|
|
340
|
-
};
|
|
288
|
+
const softBreaks = markdown ? unmaskedSoftBreaks(source, prose) : [];
|
|
289
|
+
const sentences = sentencesOf(prose, piecesOf(prose, paragraphSpans), adapter, softBreaks);
|
|
290
|
+
const lexicons = teamLexicons(adapter, team);
|
|
341
291
|
const tagged = sentences.some((sentence) => sentence.tokens !== undefined);
|
|
342
292
|
const patterns = adapter.structure;
|
|
343
293
|
// null は「作ったが構造を読めない言語だった」、undefined は「まだ作っていない」。
|
|
@@ -348,7 +298,7 @@ export const buildDocument = (
|
|
|
348
298
|
language: adapter.id,
|
|
349
299
|
lengthUnit: adapter.capabilities.lengthUnit,
|
|
350
300
|
capabilities: adapter.capabilities,
|
|
351
|
-
sections: sectionsOf(
|
|
301
|
+
sections: sectionsOf(headings, sentences, strongSpans(root, blocks), source.length, headingReadersOf(adapter, tagged, lexicons)),
|
|
352
302
|
sentences,
|
|
353
303
|
listSpans: listItems,
|
|
354
304
|
paragraphs: paragraphsOf(prose, paragraphSpans, sentences, listItems),
|
|
@@ -368,7 +318,7 @@ export const buildDocument = (
|
|
|
368
318
|
source,
|
|
369
319
|
language: adapter.id,
|
|
370
320
|
// テキストの文書は、ページの飾りを覆って読む。フッターの「Section 9」を木の節にしない。
|
|
371
|
-
outline:
|
|
321
|
+
outline: markdown ? outlineOf(root, source, syntax, emailLayout.replyQuotes) : textOutline(source, emailLayout.replyQuotes),
|
|
372
322
|
markdown: isMarkdownPath(path),
|
|
373
323
|
profile,
|
|
374
324
|
},
|
|
@@ -378,16 +328,34 @@ export const buildDocument = (
|
|
|
378
328
|
},
|
|
379
329
|
profile,
|
|
380
330
|
prose,
|
|
331
|
+
replyQuotes: emailLayout.replyQuotes,
|
|
332
|
+
get markup(): Markup {
|
|
333
|
+
return documentMarkup(root, source, markdown, [...emailLayout.replyQuotes, ...syntax]);
|
|
334
|
+
},
|
|
381
335
|
};
|
|
382
336
|
};
|
|
383
337
|
|
|
384
|
-
/**
|
|
385
|
-
|
|
338
|
+
/**
|
|
339
|
+
* 文書モデルを作る。text はファイルの中身のままでよい。先頭の BOM を外し、CRLF と CR を LF にそろえた doc.source を読む。
|
|
340
|
+
* 位置(span・offset)はすべて doc.source の上の位置で、渡した text の上の位置ではない。行と桁は同じ。
|
|
341
|
+
*/
|
|
342
|
+
export const buildDocument = (
|
|
343
|
+
path: string,
|
|
344
|
+
text: string,
|
|
345
|
+
adapter: LanguageAdapter,
|
|
346
|
+
team: TeamRules = EMPTY_TEAM,
|
|
347
|
+
profile: DocumentProfile | undefined = undefined,
|
|
348
|
+
): ProseDocument => documentOf(path, plainSource(text), adapter, team, profile);
|
|
386
349
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
350
|
+
/** 引用した返信も中を読まない。ほかの人の言葉の中の定義や番号は、この文書のものではない。 */
|
|
351
|
+
const outlineOf = (root: Node, source: string, syntax: readonly Span[], replyQuotes: readonly Span[]): Outline => ({
|
|
352
|
+
headings: headingsOf(root, source, maskSpans(source, syntax), replyQuotes),
|
|
353
|
+
opaque: [...opaqueSpans(root), ...markdownFigures(root, source), ...syntax, ...replyQuotes],
|
|
354
|
+
tables: spansOfType(root, "table"),
|
|
390
355
|
});
|
|
391
356
|
|
|
392
|
-
/** 構造を読むための Markdown
|
|
393
|
-
export const markdownOutline = (source: string): Outline =>
|
|
357
|
+
/** 構造を読むための Markdown の手がかり。見出しと、中を読まない範囲。返信の引用は、言語パッケージの語彙表で見分ける。 */
|
|
358
|
+
export const markdownOutline = (source: string, lexicons: LanguageAdapter["lexicons"] = {}): Outline => {
|
|
359
|
+
const { root, syntax } = readMarkdown(source);
|
|
360
|
+
return outlineOf(root, source, syntax, emailParts(source, emailVocabulary(lexicons)).replyQuotes);
|
|
361
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Lexicon, Span } from "./plugin.ts";
|
|
2
|
+
import { linesOf, type Line } from "./structure/lines.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The parts of a plain-text or Markdown email that are not the writer's prose: the header, the separator lines between
|
|
6
|
+
* messages, the signature, and a quoted reply. Found by shape, in any genre; the field names and the attribution words
|
|
7
|
+
* come from the language package's lexicons.
|
|
8
|
+
*/
|
|
9
|
+
export type EmailVocabulary = {
|
|
10
|
+
/** Header field names, lower-cased. */
|
|
11
|
+
readonly headerFields: ReadonlySet<string>;
|
|
12
|
+
/** The fields whose value the writer types (Subject, To), lower-cased. Their value stays prose, on a line of its own. */
|
|
13
|
+
readonly writtenFields: ReadonlySet<string>;
|
|
14
|
+
/** Words an attribution line ends with, before its colon ("wrote", 「書きました」). */
|
|
15
|
+
readonly attributions: readonly string[];
|
|
16
|
+
/** How an archive's note in place of a removed attachment ends ("was scrubbed...", 「を保管しました...」). */
|
|
17
|
+
readonly attachmentNotes: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type EmailParts = {
|
|
21
|
+
/**
|
|
22
|
+
* Header lines (all of them but a written field's value), separator lines, signatures and attribution lines: masked
|
|
23
|
+
* from prose, and paragraphs are cut around them.
|
|
24
|
+
*/
|
|
25
|
+
readonly furniture: readonly Span[];
|
|
26
|
+
/** An attribution line and the quoted lines after it: someone else's words, so not this document's headings or structure. */
|
|
27
|
+
readonly replyQuotes: readonly Span[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const lowerCased = (lexicon: Lexicon | undefined): ReadonlySet<string> => new Set((lexicon ?? []).map((entry) => entry.pattern.toLowerCase()));
|
|
31
|
+
|
|
32
|
+
export const emailVocabulary = (lexicons: Readonly<Record<string, Lexicon>>): EmailVocabulary => ({
|
|
33
|
+
headerFields: lowerCased(lexicons["email-header-field"]),
|
|
34
|
+
writtenFields: lowerCased(lexicons["email-written-field"]),
|
|
35
|
+
attributions: (lexicons["email-attribution"] ?? []).map((entry) => entry.pattern),
|
|
36
|
+
attachmentNotes: (lexicons["email-attachment-note"] ?? []).map((entry) => entry.pattern),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** A signature has at most this many lines after its "-- " line (the convention is four). More is prose again. */
|
|
40
|
+
const SIGNATURE_MAX_LINES = 4;
|
|
41
|
+
/** An attribution paragraph longer than this is a paragraph of the writer's that happens to end with "wrote:". */
|
|
42
|
+
const ATTRIBUTION_MAX_LINES = 3;
|
|
43
|
+
/** Away from the top of a message, one known field ("Date: the first Monday") is a sentence; a header names several. */
|
|
44
|
+
const HEADER_MIN_FIELDS = 2;
|
|
45
|
+
/** An attachment stub is its note and a few fields: Mailman's longest is the note, Name, Type, Size, Desc and URL. */
|
|
46
|
+
const ATTACHMENT_STUB_MAX_LINES = 6;
|
|
47
|
+
|
|
48
|
+
/** "Field: value" or 「件名:値」. A colon followed by text, as in a URL, is not a field. */
|
|
49
|
+
const FIELD = /^([^\s::]{1,40})(?::(?:[ \t]+|$)|:[ \t]*)/u;
|
|
50
|
+
const FOLDED = /^[ \t]+\S/u;
|
|
51
|
+
/** An mbox archive's envelope line (RFC 4155): "From ", the sender, then the date. It has no colon after From. */
|
|
52
|
+
const ENVELOPE = /^From [^\s:]+ /u;
|
|
53
|
+
const SIGNATURE = /^-- ?$/u;
|
|
54
|
+
/** A line of dashes, underscores or equals signs, with a few words between them or none: "-----Original Message-----". */
|
|
55
|
+
const SEPARATOR = /^[-_=]{3,}(?:([^-_=]{1,40})[-_=]{2,})?$/u;
|
|
56
|
+
/** Two rules with only spaces or strokes between them are a drawing (an RFC's diagram), not a separator. */
|
|
57
|
+
const LETTER = /\p{L}/u;
|
|
58
|
+
const QUOTED = /^ {0,3}>/u;
|
|
59
|
+
const QUOTED_TEXT = /^ {0,3}>[\s>]*\S/u;
|
|
60
|
+
/** Gmail's attribution ends with the sender's address and a colon: 「… 山田太郎 <taro@example.com>:」. An archive writes "taro at example.com". */
|
|
61
|
+
const BRACKETED_THEN_COLON = /<([^<>]*)>[ \t]*[::]$/u;
|
|
62
|
+
const ADDRESS_MARK = /@| at /u;
|
|
63
|
+
const LATIN_LETTER = /[A-Za-z]/u;
|
|
64
|
+
/** The stub's last field: its value is a URL alone, bare or in angle brackets. */
|
|
65
|
+
const URL_VALUE = /^<?[a-z][a-z0-9+.-]*:\/\/\S+?>?$/iu;
|
|
66
|
+
|
|
67
|
+
const isBlank = (line: Line | undefined): boolean => line !== undefined && line.text.trim() === "";
|
|
68
|
+
const isSeparator = (line: Line | undefined): boolean => {
|
|
69
|
+
const match = line === undefined ? null : SEPARATOR.exec(line.text.trim());
|
|
70
|
+
return match !== null && (match[1] === undefined || LETTER.test(match[1]));
|
|
71
|
+
};
|
|
72
|
+
const isQuoted = (line: Line | undefined): boolean => line !== undefined && QUOTED.test(line.text);
|
|
73
|
+
const endOf = (line: Line): number => line.start + line.text.length;
|
|
74
|
+
const spanOfLines = (first: Line, last: Line): Span => ({ start: first.start, end: endOf(last) });
|
|
75
|
+
|
|
76
|
+
/** The first index at or after `from` whose line meets `test`, or -1. Scans forward only, so a long document stays linear. */
|
|
77
|
+
const indexFrom = (lines: readonly Line[], from: number, test: (line: Line, index: number) => boolean): number => {
|
|
78
|
+
for (let index = Math.max(0, from); index < lines.length; index += 1) {
|
|
79
|
+
const line = lines[index];
|
|
80
|
+
if (line !== undefined && test(line, index)) return index;
|
|
81
|
+
}
|
|
82
|
+
return -1;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** The last index at or before `from` whose line meets `test`, or -1. */
|
|
86
|
+
const indexBackFrom = (lines: readonly Line[], from: number, test: (line: Line) => boolean): number => {
|
|
87
|
+
for (let index = Math.min(from, lines.length - 1); index >= 0; index -= 1) {
|
|
88
|
+
const line = lines[index];
|
|
89
|
+
if (line !== undefined && test(line)) return index;
|
|
90
|
+
}
|
|
91
|
+
return -1;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Runs of lines between blank lines and separator lines. */
|
|
95
|
+
const blocksOf = (lines: readonly Line[]): Line[][] =>
|
|
96
|
+
lines.reduce<Line[][]>((blocks, line, index) => {
|
|
97
|
+
if (isBlank(line) || isSeparator(line) || SIGNATURE.test(line.text)) return blocks;
|
|
98
|
+
const previous = lines[index - 1];
|
|
99
|
+
if (previous !== undefined && !isBlank(previous) && !isSeparator(previous) && !SIGNATURE.test(previous.text)) blocks.at(-1)?.push(line);
|
|
100
|
+
else blocks.push([line]);
|
|
101
|
+
return blocks;
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
/** The block opens a message: nothing but blank lines before it, or before it and the nearest separator line. */
|
|
105
|
+
const opensMessage = (lines: readonly Line[], first: Line): boolean => {
|
|
106
|
+
const last = indexBackFrom(lines, first.number - 2, (line) => !isBlank(line));
|
|
107
|
+
return last === -1 || isSeparator(lines[last]);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const knownField = (text: string, vocabulary: EmailVocabulary): boolean => {
|
|
111
|
+
const name = FIELD.exec(text)?.[1];
|
|
112
|
+
return name !== undefined && vocabulary.headerFields.has(name.toLowerCase());
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isHeaderLine = (line: Line, index: number): boolean =>
|
|
116
|
+
FIELD.test(line.text) || (index > 0 && FOLDED.test(line.text)) || (index === 0 && ENVELOPE.test(line.text));
|
|
117
|
+
|
|
118
|
+
const isHeader = (block: readonly Line[], atTop: boolean, vocabulary: EmailVocabulary): boolean => {
|
|
119
|
+
if (!block.every(isHeaderLine)) return false;
|
|
120
|
+
const known = block.filter((line) => knownField(line.text, vocabulary)).length;
|
|
121
|
+
if (known >= HEADER_MIN_FIELDS) return true;
|
|
122
|
+
// Fewer known fields make a header only at the top of a message, and only if no other field sits beside them: "Summary:" is the writer's.
|
|
123
|
+
return atTop && known > 0 && block.every((line) => !FIELD.test(line.text) || knownField(line.text, vocabulary));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** A header's fields: each line with the folded lines under it. */
|
|
127
|
+
const fieldsOf = (block: readonly Line[]): Line[][] =>
|
|
128
|
+
block.reduce<Line[][]>((fields, line, index) => {
|
|
129
|
+
if (index > 0 && FOLDED.test(line.text)) fields.at(-1)?.push(line);
|
|
130
|
+
else fields.push([line]);
|
|
131
|
+
return fields;
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
/** A written field loses only its label and keeps its value; any other field, and the envelope line, go whole. */
|
|
135
|
+
const fieldMask = (field: readonly Line[], vocabulary: EmailVocabulary): Span | undefined => {
|
|
136
|
+
const first = field[0];
|
|
137
|
+
const last = field.at(-1);
|
|
138
|
+
if (first === undefined || last === undefined) return undefined;
|
|
139
|
+
const label = FIELD.exec(first.text);
|
|
140
|
+
const name = label?.[1]?.toLowerCase();
|
|
141
|
+
if (label === null || name === undefined || !vocabulary.writtenFields.has(name)) return spanOfLines(first, last);
|
|
142
|
+
return { start: first.start, end: first.start + label[0].length };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const headerSpans = (lines: readonly Line[], vocabulary: EmailVocabulary): Span[] =>
|
|
146
|
+
blocksOf(lines).flatMap((block) => {
|
|
147
|
+
const first = block[0];
|
|
148
|
+
if (first === undefined || !isHeader(block, opensMessage(lines, first), vocabulary)) return [];
|
|
149
|
+
return fieldsOf(block).flatMap((field) => fieldMask(field, vocabulary) ?? []);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const isUrlField = (line: Line | undefined): boolean => {
|
|
153
|
+
const label = line === undefined ? null : FIELD.exec(line.text);
|
|
154
|
+
return line !== undefined && label !== null && URL_VALUE.test(line.text.slice(label[0].length).trim());
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* An archive's note where it removed an attachment: right after a separator, a line ending as the lexicon says, then
|
|
159
|
+
* only field lines, the last one a URL. Mailman's "An HTML attachment was scrubbed..." and its "URL: <…>" line.
|
|
160
|
+
*/
|
|
161
|
+
const isAttachmentStub = (lines: readonly Line[], block: readonly Line[], vocabulary: EmailVocabulary): boolean => {
|
|
162
|
+
const [note, ...fields] = block;
|
|
163
|
+
if (note === undefined || block.length > ATTACHMENT_STUB_MAX_LINES) return false;
|
|
164
|
+
const noteText = note.text.trim();
|
|
165
|
+
if (!vocabulary.attachmentNotes.some((ending) => noteText.endsWith(ending))) return false;
|
|
166
|
+
return fields.every((line) => FIELD.test(line.text)) && isUrlField(fields.at(-1)) && isSeparator(lines[note.number - 2]);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const attachmentStubSpans = (lines: readonly Line[], vocabulary: EmailVocabulary): Span[] =>
|
|
170
|
+
blocksOf(lines).flatMap((block) => {
|
|
171
|
+
const first = block[0];
|
|
172
|
+
const last = block.at(-1);
|
|
173
|
+
return first !== undefined && last !== undefined && isAttachmentStub(lines, block, vocabulary) ? [spanOfLines(first, last)] : [];
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/** From the "-- " line: the blank lines right after it, then its lines up to a blank line, a separator or a quote. */
|
|
177
|
+
const signatureAt = (lines: readonly Line[], delimiter: Line): Span => {
|
|
178
|
+
const firstText = indexFrom(lines, delimiter.number, (line) => !isBlank(line));
|
|
179
|
+
if (firstText === -1) return spanOfLines(delimiter, delimiter);
|
|
180
|
+
const stop = indexFrom(
|
|
181
|
+
lines,
|
|
182
|
+
firstText,
|
|
183
|
+
(line, index) => index - firstText >= SIGNATURE_MAX_LINES || isBlank(line) || isSeparator(line) || isQuoted(line) || SIGNATURE.test(line.text),
|
|
184
|
+
);
|
|
185
|
+
return spanOfLines(delimiter, lines[(stop === -1 ? lines.length : stop) - 1] ?? delimiter);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const signatureSpans = (lines: readonly Line[]): Span[] => lines.filter((line) => SIGNATURE.test(line.text)).map((line) => signatureAt(lines, line));
|
|
189
|
+
|
|
190
|
+
const endsWithAttribution = (text: string, vocabulary: EmailVocabulary): boolean => {
|
|
191
|
+
const trimmed = text.trimEnd();
|
|
192
|
+
if (ADDRESS_MARK.test(BRACKETED_THEN_COLON.exec(trimmed)?.[1] ?? "")) return true;
|
|
193
|
+
if (!/[::]$/u.test(trimmed)) return false;
|
|
194
|
+
const words = trimmed.slice(0, -1).trimEnd();
|
|
195
|
+
return vocabulary.attributions.some((word) => {
|
|
196
|
+
if (!words.endsWith(word)) return false;
|
|
197
|
+
const before = words.at(-word.length - 1) ?? "";
|
|
198
|
+
return !(LATIN_LETTER.test(word.charAt(0)) && LATIN_LETTER.test(before));
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** The paragraph that ends right before `index`, skipping blank lines: its lines, if it is short enough to be an attribution. */
|
|
203
|
+
const attributionBefore = (lines: readonly Line[], index: number, vocabulary: EmailVocabulary): Line[] | undefined => {
|
|
204
|
+
const lastText = indexBackFrom(lines, index - 1, (line) => !isBlank(line));
|
|
205
|
+
if (lastText === -1) return undefined;
|
|
206
|
+
const floor = lastText - ATTRIBUTION_MAX_LINES;
|
|
207
|
+
const window = lines.slice(Math.max(0, floor), lastText + 1);
|
|
208
|
+
const opening = window.findLastIndex((line) => isBlank(line) || isSeparator(line) || isQuoted(line));
|
|
209
|
+
if (opening === -1 && floor > 0) return undefined;
|
|
210
|
+
const paragraph = window.slice(opening + 1);
|
|
211
|
+
if (paragraph.length === 0 || paragraph.length > ATTRIBUTION_MAX_LINES) return undefined;
|
|
212
|
+
return endsWithAttribution(paragraph.map((line) => line.text.trim()).join(" "), vocabulary) ? paragraph : undefined;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** A quoted line, or a line with text run on from a quoted line with text (a lazy continuation, as Markdown reads it). */
|
|
216
|
+
const continuesQuote = (lines: readonly Line[], index: number): boolean => {
|
|
217
|
+
const line = lines[index];
|
|
218
|
+
if (isQuoted(line)) return true;
|
|
219
|
+
const previous = lines[index - 1];
|
|
220
|
+
return line !== undefined && !isBlank(line) && !isSeparator(line) && previous !== undefined && QUOTED_TEXT.test(previous.text);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/** The last line of the quote that starts at `index`. Blank lines between quoted lines stay inside it. */
|
|
224
|
+
const quoteEnd = (lines: readonly Line[], index: number): number => {
|
|
225
|
+
const chunk = { start: index };
|
|
226
|
+
for (;;) {
|
|
227
|
+
const next = indexFrom(lines, chunk.start + 1, (_, at) => !continuesQuote(lines, at));
|
|
228
|
+
if (next === -1) return lines.length - 1;
|
|
229
|
+
const resumes = indexFrom(lines, next, (line) => !isBlank(line));
|
|
230
|
+
if (resumes === -1 || !isQuoted(lines[resumes])) return next - 1;
|
|
231
|
+
chunk.start = resumes;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const quoteStarts = (lines: readonly Line[]): number[] => lines.flatMap((line, index) => (isQuoted(line) && !continuesQuote(lines, index - 1) ? [index] : []));
|
|
236
|
+
|
|
237
|
+
type Reply = { readonly attribution: Span; readonly quote: Span };
|
|
238
|
+
|
|
239
|
+
const repliesOf = (lines: readonly Line[], vocabulary: EmailVocabulary): Reply[] =>
|
|
240
|
+
quoteStarts(lines).reduce<Reply[]>((replies, start) => {
|
|
241
|
+
const covered = replies.at(-1);
|
|
242
|
+
const line = lines[start];
|
|
243
|
+
if (line === undefined || (covered !== undefined && line.start < covered.quote.end)) return replies;
|
|
244
|
+
const attribution = attributionBefore(lines, start, vocabulary);
|
|
245
|
+
const first = attribution?.[0];
|
|
246
|
+
const last = attribution?.at(-1);
|
|
247
|
+
if (first === undefined || last === undefined) return replies;
|
|
248
|
+
const end = lines[quoteEnd(lines, start)];
|
|
249
|
+
if (end === undefined) return replies;
|
|
250
|
+
return [...replies, { attribution: spanOfLines(first, last), quote: spanOfLines(first, end) }];
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
const bySpanStart = (left: Span, right: Span): number => left.start - right.start;
|
|
254
|
+
|
|
255
|
+
export const emailParts = (source: string, vocabulary: EmailVocabulary): EmailParts => {
|
|
256
|
+
const lines = linesOf(source);
|
|
257
|
+
const replies = repliesOf(lines, vocabulary);
|
|
258
|
+
const separators = lines.filter((line) => isSeparator(line)).map((line) => spanOfLines(line, line));
|
|
259
|
+
return {
|
|
260
|
+
furniture: [
|
|
261
|
+
...headerSpans(lines, vocabulary),
|
|
262
|
+
...attachmentStubSpans(lines, vocabulary),
|
|
263
|
+
...separators,
|
|
264
|
+
...signatureSpans(lines),
|
|
265
|
+
...replies.map((reply) => reply.attribution),
|
|
266
|
+
].toSorted(bySpanStart),
|
|
267
|
+
replyQuotes: replies.map((reply) => reply.quote),
|
|
268
|
+
};
|
|
269
|
+
};
|