chaffjs 0.34.0 → 0.35.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/capitals-passage.d.ts +3 -0
- package/dist/capitals-passage.d.ts.map +1 -0
- package/dist/capitals-passage.js +26 -0
- package/dist/capitals-passage.js.map +1 -0
- package/dist/coined-hyphen-name.d.ts +13 -0
- package/dist/coined-hyphen-name.d.ts.map +1 -0
- package/dist/coined-hyphen-name.js +21 -0
- package/dist/coined-hyphen-name.js.map +1 -0
- package/dist/company-names.d.ts +6 -1
- package/dist/company-names.d.ts.map +1 -1
- package/dist/company-names.js +22 -3
- package/dist/company-names.js.map +1 -1
- package/dist/detectors/acronym-context.d.ts +1 -1
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +4 -4
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-definition.d.ts +13 -0
- package/dist/detectors/acronym-definition.d.ts.map +1 -0
- package/dist/detectors/acronym-definition.js +90 -0
- package/dist/detectors/acronym-definition.js.map +1 -0
- package/dist/detectors/acronym-lone.d.ts +13 -0
- package/dist/detectors/acronym-lone.d.ts.map +1 -0
- package/dist/detectors/acronym-lone.js +22 -0
- package/dist/detectors/acronym-lone.js.map +1 -0
- package/dist/detectors/acronym-name.d.ts +55 -0
- package/dist/detectors/acronym-name.d.ts.map +1 -0
- package/dist/detectors/acronym-name.js +75 -0
- package/dist/detectors/acronym-name.js.map +1 -0
- package/dist/detectors/capitalised-term.d.ts +14 -2
- package/dist/detectors/capitalised-term.d.ts.map +1 -1
- package/dist/detectors/capitalised-term.js +115 -4
- package/dist/detectors/capitalised-term.js.map +1 -1
- package/dist/detectors/caps-caption.d.ts +11 -0
- package/dist/detectors/caps-caption.d.ts.map +1 -0
- package/dist/detectors/caps-caption.js +51 -0
- package/dist/detectors/caps-caption.js.map +1 -0
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +47 -18
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/clause-shape.d.ts +1 -1
- package/dist/detectors/clause-shape.d.ts.map +1 -1
- package/dist/detectors/clause-shape.js +9 -4
- package/dist/detectors/clause-shape.js.map +1 -1
- package/dist/detectors/concrete-evidence.d.ts +13 -1
- package/dist/detectors/concrete-evidence.d.ts.map +1 -1
- package/dist/detectors/concrete-evidence.js +59 -0
- package/dist/detectors/concrete-evidence.js.map +1 -1
- package/dist/detectors/contraction-minority.d.ts +11 -0
- package/dist/detectors/contraction-minority.d.ts.map +1 -0
- package/dist/detectors/contraction-minority.js +10 -0
- package/dist/detectors/contraction-minority.js.map +1 -0
- package/dist/detectors/counter-edge.d.ts +5 -0
- package/dist/detectors/counter-edge.d.ts.map +1 -1
- package/dist/detectors/counter-edge.js +23 -0
- package/dist/detectors/counter-edge.js.map +1 -1
- package/dist/detectors/date-any-year.d.ts +6 -3
- package/dist/detectors/date-any-year.d.ts.map +1 -1
- package/dist/detectors/date-any-year.js +47 -10
- package/dist/detectors/date-any-year.js.map +1 -1
- package/dist/detectors/date-heading-year.d.ts +18 -0
- package/dist/detectors/date-heading-year.d.ts.map +1 -0
- package/dist/detectors/date-heading-year.js +38 -0
- package/dist/detectors/date-heading-year.js.map +1 -0
- package/dist/detectors/date-year.d.ts +12 -2
- package/dist/detectors/date-year.d.ts.map +1 -1
- package/dist/detectors/date-year.js +31 -6
- package/dist/detectors/date-year.js.map +1 -1
- package/dist/detectors/defined-term-form.d.ts.map +1 -1
- package/dist/detectors/defined-term-form.js +26 -5
- package/dist/detectors/defined-term-form.js.map +1 -1
- package/dist/detectors/due-date.d.ts.map +1 -1
- package/dist/detectors/due-date.js +6 -0
- package/dist/detectors/due-date.js.map +1 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +5 -1
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/fact-consistency.d.ts.map +1 -1
- package/dist/detectors/fact-consistency.js +3 -2
- package/dist/detectors/fact-consistency.js.map +1 -1
- package/dist/detectors/gram-passages.d.ts +11 -0
- package/dist/detectors/gram-passages.d.ts.map +1 -0
- package/dist/detectors/gram-passages.js +26 -0
- package/dist/detectors/gram-passages.js.map +1 -0
- package/dist/detectors/heading-end-mark.d.ts +8 -3
- package/dist/detectors/heading-end-mark.d.ts.map +1 -1
- package/dist/detectors/heading-end-mark.js +18 -8
- package/dist/detectors/heading-end-mark.js.map +1 -1
- package/dist/detectors/heading-names.d.ts +7 -0
- package/dist/detectors/heading-names.d.ts.map +1 -0
- package/dist/detectors/heading-names.js +24 -0
- package/dist/detectors/heading-names.js.map +1 -0
- package/dist/detectors/heading-numbering.d.ts.map +1 -1
- package/dist/detectors/heading-numbering.js +7 -2
- package/dist/detectors/heading-numbering.js.map +1 -1
- package/dist/detectors/inch-mark.d.ts +10 -0
- package/dist/detectors/inch-mark.d.ts.map +1 -0
- package/dist/detectors/inch-mark.js +48 -0
- package/dist/detectors/inch-mark.js.map +1 -0
- package/dist/detectors/lexicon.d.ts +0 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +29 -23
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/mark-shape.d.ts +6 -3
- package/dist/detectors/mark-shape.d.ts.map +1 -1
- package/dist/detectors/mark-shape.js +12 -6
- package/dist/detectors/mark-shape.js.map +1 -1
- package/dist/detectors/name-variant-apart.d.ts +27 -0
- package/dist/detectors/name-variant-apart.d.ts.map +1 -0
- package/dist/detectors/name-variant-apart.js +51 -0
- package/dist/detectors/name-variant-apart.js.map +1 -0
- package/dist/detectors/name-variant.d.ts.map +1 -1
- package/dist/detectors/name-variant.js +18 -11
- package/dist/detectors/name-variant.js.map +1 -1
- package/dist/detectors/numeral-word.d.ts +15 -0
- package/dist/detectors/numeral-word.d.ts.map +1 -0
- package/dist/detectors/numeral-word.js +26 -0
- package/dist/detectors/numeral-word.js.map +1 -0
- package/dist/detectors/organisation-run.d.ts +14 -0
- package/dist/detectors/organisation-run.d.ts.map +1 -0
- package/dist/detectors/organisation-run.js +73 -0
- package/dist/detectors/organisation-run.js.map +1 -0
- package/dist/detectors/orthographic-variant.d.ts.map +1 -1
- package/dist/detectors/orthographic-variant.js +13 -5
- package/dist/detectors/orthographic-variant.js.map +1 -1
- package/dist/detectors/outline-label.d.ts +14 -0
- package/dist/detectors/outline-label.d.ts.map +1 -0
- package/dist/detectors/outline-label.js +32 -0
- package/dist/detectors/outline-label.js.map +1 -0
- package/dist/detectors/page-marker.d.ts +6 -0
- package/dist/detectors/page-marker.d.ts.map +1 -0
- package/dist/detectors/page-marker.js +19 -0
- package/dist/detectors/page-marker.js.map +1 -0
- package/dist/detectors/parallel-definition.d.ts +16 -0
- package/dist/detectors/parallel-definition.d.ts.map +1 -0
- package/dist/detectors/parallel-definition.js +51 -0
- package/dist/detectors/parallel-definition.js.map +1 -0
- package/dist/detectors/reference-text.d.ts.map +1 -1
- package/dist/detectors/reference-text.js +1 -0
- package/dist/detectors/reference-text.js.map +1 -1
- package/dist/detectors/registry/unfilled-placeholder.d.ts.map +1 -1
- package/dist/detectors/registry/unfilled-placeholder.js +2 -2
- package/dist/detectors/registry/unfilled-placeholder.js.map +1 -1
- package/dist/detectors/registry/vague-density.js +1 -1
- package/dist/detectors/registry/vague-density.js.map +1 -1
- package/dist/detectors/request-owner.d.ts +6 -2
- package/dist/detectors/request-owner.d.ts.map +1 -1
- package/dist/detectors/request-owner.js +57 -7
- package/dist/detectors/request-owner.js.map +1 -1
- package/dist/detectors/requirement-smell.d.ts.map +1 -1
- package/dist/detectors/requirement-smell.js +10 -2
- package/dist/detectors/requirement-smell.js.map +1 -1
- package/dist/detectors/sentence-length.d.ts.map +1 -1
- package/dist/detectors/sentence-length.js +24 -16
- package/dist/detectors/sentence-length.js.map +1 -1
- package/dist/detectors/sentence-rhythm.d.ts.map +1 -1
- package/dist/detectors/sentence-rhythm.js +6 -3
- package/dist/detectors/sentence-rhythm.js.map +1 -1
- package/dist/detectors/series-label.d.ts +2 -1
- package/dist/detectors/series-label.d.ts.map +1 -1
- package/dist/detectors/series-label.js +10 -4
- package/dist/detectors/series-label.js.map +1 -1
- package/dist/detectors/signals.d.ts +4 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +95 -36
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +8 -7
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/template-sections.d.ts +14 -0
- package/dist/detectors/template-sections.d.ts.map +1 -0
- package/dist/detectors/template-sections.js +25 -0
- package/dist/detectors/template-sections.js.map +1 -0
- package/dist/detectors/titled-run.d.ts +22 -0
- package/dist/detectors/titled-run.d.ts.map +1 -0
- package/dist/detectors/titled-run.js +34 -0
- package/dist/detectors/titled-run.js.map +1 -0
- package/dist/detectors/unbalanced-bracket.d.ts.map +1 -1
- package/dist/detectors/unbalanced-bracket.js +4 -0
- package/dist/detectors/unbalanced-bracket.js.map +1 -1
- package/dist/detectors/undefined-term.d.ts.map +1 -1
- package/dist/detectors/undefined-term.js +8 -0
- package/dist/detectors/undefined-term.js.map +1 -1
- package/dist/detectors/unfilled-placeholder.d.ts +4 -0
- package/dist/detectors/unfilled-placeholder.d.ts.map +1 -0
- package/dist/detectors/unfilled-placeholder.js +9 -0
- package/dist/detectors/unfilled-placeholder.js.map +1 -0
- package/dist/detectors/vague-density.d.ts +5 -0
- package/dist/detectors/vague-density.d.ts.map +1 -0
- package/dist/detectors/vague-density.js +52 -0
- package/dist/detectors/vague-density.js.map +1 -0
- package/dist/facts/fact-scope.d.ts +10 -1
- package/dist/facts/fact-scope.d.ts.map +1 -1
- package/dist/facts/fact-scope.js +34 -3
- package/dist/facts/fact-scope.js.map +1 -1
- package/dist/facts/table-facts.d.ts +12 -2
- package/dist/facts/table-facts.d.ts.map +1 -1
- package/dist/facts/table-facts.js +22 -4
- package/dist/facts/table-facts.js.map +1 -1
- package/dist/grade-api.d.ts +9 -2
- package/dist/grade-api.d.ts.map +1 -1
- package/dist/grade-api.js +31 -15
- package/dist/grade-api.js.map +1 -1
- package/dist/heading-numbering.d.ts +5 -2
- package/dist/heading-numbering.d.ts.map +1 -1
- package/dist/heading-numbering.js +27 -6
- package/dist/heading-numbering.js.map +1 -1
- package/dist/name-end-letter.d.ts +5 -0
- package/dist/name-end-letter.d.ts.map +1 -1
- package/dist/name-end-letter.js +8 -0
- package/dist/name-end-letter.js.map +1 -1
- package/dist/name-given-before.d.ts +15 -0
- package/dist/name-given-before.d.ts.map +1 -0
- package/dist/name-given-before.js +30 -0
- package/dist/name-given-before.js.map +1 -0
- package/dist/name-separator.d.ts +2 -2
- package/dist/name-separator.d.ts.map +1 -1
- package/dist/name-separator.js +56 -5
- package/dist/name-separator.js.map +1 -1
- package/dist/name-variants.d.ts +3 -1
- package/dist/name-variants.d.ts.map +1 -1
- package/dist/name-variants.js +8 -7
- package/dist/name-variants.js.map +1 -1
- package/dist/name-word-order.d.ts +9 -5
- package/dist/name-word-order.d.ts.map +1 -1
- package/dist/name-word-order.js +47 -15
- package/dist/name-word-order.js.map +1 -1
- package/dist/nominal-stem.d.ts +13 -0
- package/dist/nominal-stem.d.ts.map +1 -0
- package/dist/nominal-stem.js +32 -0
- package/dist/nominal-stem.js.map +1 -0
- package/dist/sentence-load.d.ts +2 -1
- package/dist/sentence-load.d.ts.map +1 -1
- package/dist/sentence-load.js +6 -2
- package/dist/sentence-load.js.map +1 -1
- package/dist/structure/cited-document-section.d.ts +7 -0
- package/dist/structure/cited-document-section.d.ts.map +1 -0
- package/dist/structure/cited-document-section.js +33 -0
- package/dist/structure/cited-document-section.js.map +1 -0
- package/dist/structure/html-text-blocks.d.ts +8 -0
- package/dist/structure/html-text-blocks.d.ts.map +1 -0
- package/dist/structure/html-text-blocks.js +47 -0
- package/dist/structure/html-text-blocks.js.map +1 -0
- package/dist/structure/issues.d.ts +1 -1
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +19 -4
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/labelled-date-order.d.ts +4 -1
- package/dist/structure/labelled-date-order.d.ts.map +1 -1
- package/dist/structure/labelled-date-order.js +2 -2
- package/dist/structure/labelled-date-order.js.map +1 -1
- package/dist/structure/labelled-order.d.ts +3 -0
- package/dist/structure/labelled-order.d.ts.map +1 -1
- package/dist/structure/labelled-order.js +8 -3
- package/dist/structure/labelled-order.js.map +1 -1
- package/dist/structure/list-numbering.d.ts +20 -4
- package/dist/structure/list-numbering.d.ts.map +1 -1
- package/dist/structure/list-numbering.js +65 -9
- package/dist/structure/list-numbering.js.map +1 -1
- package/dist/structure/listed-paragraph.d.ts +16 -0
- package/dist/structure/listed-paragraph.d.ts.map +1 -0
- package/dist/structure/listed-paragraph.js +37 -0
- package/dist/structure/listed-paragraph.js.map +1 -0
- package/dist/structure/numbering.d.ts +3 -2
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +31 -6
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/reference-text.d.ts +9 -1
- package/dist/structure/reference-text.d.ts.map +1 -1
- package/dist/structure/reference-text.js +21 -3
- package/dist/structure/reference-text.js.map +1 -1
- package/dist/structure/section-names.d.ts +12 -0
- package/dist/structure/section-names.d.ts.map +1 -0
- package/dist/structure/section-names.js +26 -0
- package/dist/structure/section-names.js.map +1 -0
- package/dist/structure/versioned-label.d.ts +16 -0
- package/dist/structure/versioned-label.d.ts.map +1 -0
- package/dist/structure/versioned-label.js +48 -0
- package/dist/structure/versioned-label.js.map +1 -0
- package/dist/structure/written-headings.d.ts +31 -0
- package/dist/structure/written-headings.d.ts.map +1 -0
- package/dist/structure/written-headings.js +119 -0
- package/dist/structure/written-headings.js.map +1 -0
- package/dist/structure/written-resolve.d.ts +12 -0
- package/dist/structure/written-resolve.d.ts.map +1 -0
- package/dist/structure/written-resolve.js +49 -0
- package/dist/structure/written-resolve.js.map +1 -0
- package/package.json +3 -3
- package/rules/all-caps-shouting.yaml +2 -2
- package/rules/concrete-evidence-density.yaml +2 -2
- package/rules/contraction-consistency.yaml +13 -13
- package/rules/dangling-reference.yaml +2 -2
- package/rules/date-without-year.yaml +2 -2
- package/rules/defined-name-repeated.yaml +2 -2
- package/rules/fact-conflict.yaml +2 -2
- package/rules/heading-end-punctuation-mix.yaml +2 -2
- package/rules/heading-numbering-mix.yaml +2 -2
- package/rules/latin-spacing.yaml +2 -2
- package/rules/max-kanji-continuous.yaml +5 -3
- package/rules/max-sentence-length.yaml +3 -2
- package/rules/name-variant.yaml +2 -2
- package/rules/ngram-repetition.yaml +2 -2
- package/rules/numbering-gap.yaml +2 -2
- package/rules/orthographic-variant.yaml +2 -2
- package/rules/reference-title-mismatch.yaml +2 -2
- package/rules/repeated-sentence-ending.yaml +3 -2
- package/rules/request-without-deadline.yaml +2 -2
- package/rules/requirement-smell.yaml +2 -2
- package/rules/sentence-rhythm.yaml +2 -2
- package/rules/title-case-consistency.yaml +2 -2
- package/rules/unbalanced-bracket.yaml +2 -2
- package/rules/undefined-acronym.yaml +11 -2
- package/rules/undefined-term.yaml +2 -2
- package/rules/unfilled-placeholder.yaml +2 -2
- package/rules/vague-word-density.yaml +2 -2
- package/src/capitals-passage.ts +31 -0
- package/src/coined-hyphen-name.ts +27 -0
- package/src/company-names.ts +24 -3
- package/src/detectors/acronym-context.ts +5 -6
- package/src/detectors/acronym-definition.ts +123 -0
- package/src/detectors/acronym-lone.ts +26 -0
- package/src/detectors/acronym-name.ts +117 -0
- package/src/detectors/capitalised-term.ts +154 -4
- package/src/detectors/caps-caption.ts +61 -0
- package/src/detectors/char-shape.ts +74 -22
- package/src/detectors/clause-shape.ts +14 -4
- package/src/detectors/concrete-evidence.ts +77 -1
- package/src/detectors/contraction-minority.ts +11 -0
- package/src/detectors/counter-edge.ts +23 -0
- package/src/detectors/date-any-year.ts +56 -10
- package/src/detectors/date-heading-year.ts +55 -0
- package/src/detectors/date-year.ts +54 -9
- package/src/detectors/defined-term-form.ts +26 -5
- package/src/detectors/due-date.ts +10 -1
- package/src/detectors/en-shape.ts +5 -1
- package/src/detectors/fact-consistency.ts +3 -2
- package/src/detectors/gram-passages.ts +36 -0
- package/src/detectors/heading-end-mark.ts +23 -10
- package/src/detectors/heading-names.ts +31 -0
- package/src/detectors/heading-numbering.ts +7 -2
- package/src/detectors/inch-mark.ts +49 -0
- package/src/detectors/lexicon.ts +32 -21
- package/src/detectors/mark-shape.ts +12 -6
- package/src/detectors/name-variant-apart.ts +63 -0
- package/src/detectors/name-variant.ts +30 -12
- package/src/detectors/numeral-word.ts +33 -0
- package/src/detectors/organisation-run.ts +86 -0
- package/src/detectors/orthographic-variant.ts +22 -6
- package/src/detectors/outline-label.ts +48 -0
- package/src/detectors/page-marker.ts +26 -0
- package/src/detectors/parallel-definition.ts +64 -0
- package/src/detectors/reference-text.ts +1 -0
- package/src/detectors/registry/unfilled-placeholder.ts +2 -2
- package/src/detectors/registry/vague-density.ts +1 -1
- package/src/detectors/request-owner.ts +71 -8
- package/src/detectors/requirement-smell.ts +15 -2
- package/src/detectors/sentence-length.ts +14 -6
- package/src/detectors/sentence-rhythm.ts +8 -4
- package/src/detectors/series-label.ts +13 -4
- package/src/detectors/signals.ts +94 -26
- package/src/detectors/structure-tree.ts +10 -8
- package/src/detectors/template-sections.ts +37 -0
- package/src/detectors/titled-run.ts +43 -0
- package/src/detectors/unbalanced-bracket.ts +3 -0
- package/src/detectors/undefined-term.ts +8 -0
- package/src/detectors/unfilled-placeholder.ts +10 -0
- package/src/detectors/vague-density.ts +68 -0
- package/src/facts/fact-scope.ts +53 -4
- package/src/facts/table-facts.ts +43 -6
- package/src/grade-api.ts +38 -16
- package/src/heading-numbering.ts +35 -8
- package/src/name-end-letter.ts +9 -0
- package/src/name-given-before.ts +40 -0
- package/src/name-separator.ts +66 -5
- package/src/name-variants.ts +21 -11
- package/src/name-word-order.ts +62 -17
- package/src/nominal-stem.ts +36 -0
- package/src/sentence-load.ts +7 -2
- package/src/structure/cited-document-section.ts +42 -0
- package/src/structure/html-text-blocks.ts +45 -0
- package/src/structure/issues.ts +19 -4
- package/src/structure/labelled-date-order.ts +5 -2
- package/src/structure/labelled-order.ts +12 -3
- package/src/structure/list-numbering.ts +100 -11
- package/src/structure/listed-paragraph.ts +45 -0
- package/src/structure/numbering.ts +48 -19
- package/src/structure/reference-text.ts +25 -3
- package/src/structure/section-names.ts +32 -0
- package/src/structure/versioned-label.ts +52 -0
- package/src/structure/written-headings.ts +137 -0
- package/src/structure/written-resolve.ts +57 -0
package/src/detectors/signals.ts
CHANGED
|
@@ -4,12 +4,17 @@ import { compacted, placeAt, type Compacted } from "./gram-place.ts";
|
|
|
4
4
|
import { charWindows, wordWindows } from "./gram-windows.ts";
|
|
5
5
|
import { furnitureMask, isFurniture } from "./gram-furniture.ts";
|
|
6
6
|
import { notAcronymSpansOf, type NotAcronymSpans } from "./acronym-context.ts";
|
|
7
|
+
import { nameSpansOf } from "./acronym-name.ts";
|
|
7
8
|
import { abbreviates, expansionAt, termEntryAcronyms, type ExpandedAt } from "./acronym-expansion.ts";
|
|
8
9
|
import { isExplained } from "./acronym-compound.ts";
|
|
10
|
+
import { statedAcronyms } from "./acronym-definition.ts";
|
|
11
|
+
import { isBracketed, isInHeading, isNamePart } from "./acronym-lone.ts";
|
|
9
12
|
import { conjugatedForms } from "./conjugated-form.ts";
|
|
10
|
-
import { evidenceSpans, hasNumeral, startsWithin } from "./concrete-evidence.ts";
|
|
13
|
+
import { evidenceSpans, hasNumeral, isClaim, isLabelOnly, startsWithin, type AdviceWords } from "./concrete-evidence.ts";
|
|
11
14
|
import { letteredIndexEntries } from "./lettered-index.ts";
|
|
15
|
+
import { outlineLabelSpans } from "./outline-label.ts";
|
|
12
16
|
import { hasPredicateIn } from "./gram-predicate.ts";
|
|
17
|
+
import { gramPassagesOf } from "./gram-passages.ts";
|
|
13
18
|
import { nameSpans, touchesAny } from "../team-names.ts";
|
|
14
19
|
import { referenceListSpans } from "../reference-lists.ts";
|
|
15
20
|
import type { Detector, Finding, ProseDocument, Section, Sentence } from "../plugin.ts";
|
|
@@ -127,12 +132,23 @@ const tallySentence = (doc: ProseDocument, sentence: Sentence, tally: Tally): vo
|
|
|
127
132
|
});
|
|
128
133
|
};
|
|
129
134
|
|
|
130
|
-
const gramsOf = (doc: ProseDocument): Tally => {
|
|
135
|
+
const gramsOf = (doc: ProseDocument, sentences: readonly Sentence[]): Tally => {
|
|
131
136
|
const tally: Tally = { counts: new Map(), first: new Map() };
|
|
132
|
-
|
|
137
|
+
sentences.forEach((sentence) => tallySentence(doc, sentence, tally));
|
|
133
138
|
return tally;
|
|
134
139
|
};
|
|
135
140
|
|
|
141
|
+
type Repeat = { readonly key: string; readonly count: number; readonly at: FirstSeen | undefined };
|
|
142
|
+
|
|
143
|
+
/** 範囲(gram-passages.ts)ごとに数える。よくある質問の別々の答えは別の範囲。 */
|
|
144
|
+
const repeatsOf = (doc: ProseDocument, limit: number): Repeat[] =>
|
|
145
|
+
gramPassagesOf(doc.sections).flatMap((sentences) => {
|
|
146
|
+
const tally = gramsOf(doc, sentences);
|
|
147
|
+
return [...tally.counts.entries()]
|
|
148
|
+
.filter(([key, count]) => count > limit && isPhrasing(key, doc.lengthUnit))
|
|
149
|
+
.map(([key, count]) => ({ key, count, at: tally.first.get(key) }));
|
|
150
|
+
});
|
|
151
|
+
|
|
136
152
|
/**
|
|
137
153
|
* 語句が文の中で占める範囲に、述語になる動詞か助動詞があるか(gram-predicate.ts)。
|
|
138
154
|
* 品詞が無ければ見分けられないので、これまでどおり言い回しとして数える。
|
|
@@ -145,11 +161,8 @@ const hasPredicate = (at: FirstSeen | undefined, unit: ProseDocument["lengthUnit
|
|
|
145
161
|
|
|
146
162
|
export const ngramRepetition: Detector = (doc, options): Finding[] => {
|
|
147
163
|
if (wordsOf(doc) < MIN_DOCUMENT_LENGTH[doc.lengthUnit]) return [];
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
.filter(([key, count]) => count > options.limit && isPhrasing(key, doc.lengthUnit))
|
|
151
|
-
.toSorted(([, left], [, right]) => right - left)
|
|
152
|
-
.map(([key, count]) => ({ key, count, at: tally.first.get(key) }))
|
|
164
|
+
const worst = repeatsOf(doc, options.limit)
|
|
165
|
+
.toSorted((left, right) => right.count - left.count)
|
|
153
166
|
.find(({ at }) => hasPredicate(at, doc.lengthUnit));
|
|
154
167
|
if (worst === undefined) return [];
|
|
155
168
|
const at = worst.at?.sentence;
|
|
@@ -274,8 +287,8 @@ const expandsInHeading = (heading: string, acronym: string, expandedAt: Expanded
|
|
|
274
287
|
|
|
275
288
|
const patternsOf = (doc: ProseDocument, id: string): string[] => (doc.lexicons[id] ?? []).map((entry) => entry.pattern);
|
|
276
289
|
|
|
277
|
-
const notationOf = (doc: ProseDocument): NotAcronymSpans =>
|
|
278
|
-
notAcronymSpansOf({
|
|
290
|
+
const notationOf = (doc: ProseDocument): NotAcronymSpans => {
|
|
291
|
+
const notation = notAcronymSpansOf({
|
|
279
292
|
meridiem: patternsOf(doc, "meridiem"),
|
|
280
293
|
timeZones: patternsOf(doc, "time-zone"),
|
|
281
294
|
currencies: patternsOf(doc, "currency-code"),
|
|
@@ -287,6 +300,17 @@ const notationOf = (doc: ProseDocument): NotAcronymSpans =>
|
|
|
287
300
|
titles: patternsOf(doc, "name-title"),
|
|
288
301
|
dateTimeUnits: patternsOf(doc, "date-time-unit"),
|
|
289
302
|
});
|
|
303
|
+
const names = nameSpansOf({
|
|
304
|
+
legalForms: patternsOf(doc, "legal-form"),
|
|
305
|
+
determiners: patternsOf(doc, "name-determiner"),
|
|
306
|
+
citationSeries: patternsOf(doc, "citation-series"),
|
|
307
|
+
carriers: patternsOf(doc, "carrier-name"),
|
|
308
|
+
namePrefixes: patternsOf(doc, "name-prefix"),
|
|
309
|
+
streetSuffixes: patternsOf(doc, "street-suffix"),
|
|
310
|
+
capitalPhrases: patternsOf(doc, "capital-phrase"),
|
|
311
|
+
});
|
|
312
|
+
return (text) => [...notation(text), ...names(text)];
|
|
313
|
+
};
|
|
290
314
|
|
|
291
315
|
/** 定義の語は、語彙表の形(という)と、この文書で活用して書かれた形(といいます)の両方で照らす。 */
|
|
292
316
|
const definitionVerbsOf = (doc: ProseDocument): string[] => {
|
|
@@ -294,30 +318,57 @@ const definitionVerbsOf = (doc: ProseDocument): string[] => {
|
|
|
294
318
|
return [...verbs, ...conjugatedForms(doc.sentences, verbs)];
|
|
295
319
|
};
|
|
296
320
|
|
|
321
|
+
const acronymFinding = (acronym: string, hit: Hit, count: number, limit: number): Finding => ({
|
|
322
|
+
rule: "undefined-acronym",
|
|
323
|
+
severity: "info",
|
|
324
|
+
line: 0,
|
|
325
|
+
column: 0,
|
|
326
|
+
quote: hit.sentence.text.trim(),
|
|
327
|
+
values: { word: acronym, count, limit, offset: hit.offset },
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const isNamePartAt = (acronym: string, hit: Hit): boolean => {
|
|
331
|
+
const start = hit.offset - hit.sentence.span.start;
|
|
332
|
+
return isNamePart(hit.sentence.text, start, start + acronym.length);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 説明のない略語が上限に届かなくても、この文書が自分で上限の数だけ略語を展開しているなら、展開しなかった 1 つも指す。
|
|
337
|
+
* 書き手が展開する文書では、展開の抜けは抜けとして読める。名前の一部の略語(SRI International)は、そのときも指さない。
|
|
338
|
+
*/
|
|
297
339
|
export const undefinedAcronym: Detector = (doc, options): Finding[] => {
|
|
298
340
|
const body = bodyOf(doc);
|
|
299
|
-
// HTTP のメソッド名(GET
|
|
341
|
+
// HTTP のメソッド名(GET)は略語ではなく、税の名前(VAT、GST)は読み手が名前として知るので、通じる略語と同じく展開を求めない。
|
|
300
342
|
// 並べた名前(JAXA)も、繋いだ略語(JAXA-ISAS)の片割れとして説明済みに数える。
|
|
301
|
-
const common = new Set([
|
|
343
|
+
const common = new Set([
|
|
344
|
+
...(options.lexicon ?? []).map((entry) => entry.pattern),
|
|
345
|
+
...patternsOf(doc, "http-method"),
|
|
346
|
+
...patternsOf(doc, "tax-code"),
|
|
347
|
+
...(doc.names ?? []),
|
|
348
|
+
]);
|
|
302
349
|
const expandedAt = expansionAt({ markers: patternsOf(doc, "definition-marker"), verbs: definitionVerbsOf(doc) });
|
|
303
|
-
const
|
|
350
|
+
const stated = statedAcronyms(doc.source, { statements: patternsOf(doc, "definition-statement"), markers: patternsOf(doc, "definition-marker") });
|
|
351
|
+
const entries = new Set([...termEntryAcronyms(doc.source), ...stated]);
|
|
304
352
|
const headings = doc.sections.map((section) => section.heading);
|
|
305
353
|
const explainedAlone = (word: string): boolean =>
|
|
306
354
|
common.has(word) || entries.has(word) || isExpanded(body, word, expandedAt) || headings.some((heading) => expandsInHeading(heading, word, expandedAt));
|
|
307
|
-
const hits = outsideSpans(acronymsOf(doc, notationOf(doc)),
|
|
308
|
-
|
|
355
|
+
const hits = outsideSpans(acronymsOf(doc, notationOf(doc)), [
|
|
356
|
+
...referenceListSpans(doc.source, patternsOf(doc, "reference-list-heading")),
|
|
357
|
+
...outlineLabelSpans(doc.source, patternsOf(doc, "numbered-division")),
|
|
358
|
+
]);
|
|
359
|
+
const acronyms = [...firstHits(hits).keys()];
|
|
360
|
+
const unexplained = new Set(acronyms.filter((acronym) => !isExplained(acronym, explainedAlone)));
|
|
361
|
+
const bracketed = (acronym: string): boolean => [body, ...headings].some((text) => isBracketed(text, acronym));
|
|
362
|
+
// 定義の一覧("SOW" means …)で定義する書き手も、略語を展開する書き手と数える。
|
|
363
|
+
const definedByWriter = (acronym: string): boolean => bracketed(acronym) || stated.has(acronym);
|
|
364
|
+
const writerSpellsOut = acronyms.filter((acronym) => !common.has(acronym) && explainedAlone(acronym) && definedByWriter(acronym)).length >= options.limit;
|
|
309
365
|
// 日付を読むには文書の木を作る。上限に届かない文書では作らない。
|
|
310
|
-
if (unexplained.size < options.limit) return [];
|
|
366
|
+
if (unexplained.size < options.limit && !writerSpellsOut) return [];
|
|
311
367
|
const bare = [...firstHits(outsideSpans(hits, dateSpans(doc)))].filter(([acronym]) => unexplained.has(acronym));
|
|
312
|
-
if (bare.length
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
line: 0,
|
|
317
|
-
column: 0,
|
|
318
|
-
quote: hit.sentence.text.trim(),
|
|
319
|
-
values: { word: acronym, count: bare.length, limit: options.limit, offset: hit.offset },
|
|
320
|
-
}));
|
|
368
|
+
if (bare.length >= options.limit) return bare.map(([acronym, hit]) => acronymFinding(acronym, hit, bare.length, options.limit));
|
|
369
|
+
if (!writerSpellsOut) return [];
|
|
370
|
+
const alone = bare.filter(([acronym, hit]) => !isNamePartAt(acronym, hit) && !isInHeading(headings, acronym));
|
|
371
|
+
return alone.map(([acronym, hit]) => acronymFinding(acronym, hit, alone.length, options.limit));
|
|
321
372
|
};
|
|
322
373
|
|
|
323
374
|
/**
|
|
@@ -341,9 +392,26 @@ const openingOf = (sentence: Sentence | undefined, unit: ProseDocument["lengthUn
|
|
|
341
392
|
|
|
342
393
|
const hasNumeralIn = (section: Section): boolean => section.sentences.some((sentence) => hasNumeral(sentence.tokens ?? []));
|
|
343
394
|
|
|
395
|
+
/** これより短い節は、具体物があるべきか判断できない。 */
|
|
396
|
+
const MIN_SENTENCES = 3;
|
|
397
|
+
|
|
398
|
+
const adviceWordsOf = (doc: ProseDocument): AdviceWords => ({
|
|
399
|
+
openers: (doc.lexicons["advice-opener"] ?? []).map((entry) => entry.pattern),
|
|
400
|
+
endings: (doc.lexicons["advice-ending"] ?? []).map((entry) => entry.pattern),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
/** 文の半分以上が問いか指示の節(質問の一覧、手引き)は、数字で裏づける主張をしていない。番号だけの文(a.)は数えない。 */
|
|
404
|
+
const mostlyClaims = (section: Section, language: string, advice: AdviceWords): boolean => {
|
|
405
|
+
const said = section.sentences.filter((sentence) => !isLabelOnly(sentence));
|
|
406
|
+
return said.filter((sentence) => isClaim(sentence, language, advice)).length * 2 > said.length;
|
|
407
|
+
};
|
|
408
|
+
|
|
344
409
|
export const concreteEvidence: Detector = (doc, options): Finding[] => {
|
|
345
410
|
const entries = letteredIndexEntries(doc.sections);
|
|
346
|
-
const
|
|
411
|
+
const advice = adviceWordsOf(doc);
|
|
412
|
+
const sections = doc.sections.filter(
|
|
413
|
+
(section) => section.sentences.length >= MIN_SENTENCES && !entries.has(section) && mostlyClaims(section, doc.language, advice),
|
|
414
|
+
);
|
|
347
415
|
const bare = sections.filter(
|
|
348
416
|
(section) => !CONCRETE.test(doc.source.slice(section.span.start, section.span.end)) && !hasNumeralIn(section) && !startsWithin(section.span, doc.links),
|
|
349
417
|
);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Detector, Finding, ProseDocument, StructureNode } from "../plugin.ts";
|
|
2
2
|
import { danglingReferences, duplicateDefinitions, inDocumentOrder, type StructureIssue } from "../structure/issues.ts";
|
|
3
3
|
import { numberingBreaks } from "../structure/numbering.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { htmlTextBlocks } from "../structure/html-text-blocks.ts";
|
|
5
|
+
import { listsNumberBreaks } from "../structure/list-numbering.ts";
|
|
5
6
|
import { weekdayMismatches } from "../structure/weekday.ts";
|
|
6
7
|
import { documentDateOf } from "./document-date.ts";
|
|
7
8
|
import { dateOrderBreaks } from "../structure/date-order.ts";
|
|
@@ -48,19 +49,20 @@ const findingsOf =
|
|
|
48
49
|
values: { ...issue.values, offset: issue.offset },
|
|
49
50
|
}));
|
|
50
51
|
|
|
51
|
-
export const danglingReference: Detector =
|
|
52
|
-
|
|
52
|
+
export const danglingReference: Detector = (doc, options) =>
|
|
53
|
+
findingsOf("dangling-reference", (tree, source) => danglingReferences(tree, source, patternsOf(doc, "section-name-noun")))(doc, options);
|
|
54
|
+
/** Markdown の HTML ブロックの中の条は木に入らない。そこを挟んだ抜けは、見えなかった番号かもしれない。 */
|
|
55
|
+
const structureNumberingGap: Detector = (doc, options) =>
|
|
56
|
+
findingsOf("numbering-gap", (tree, source) => numberingBreaks(tree, doc.markup?.markdown === true ? htmlTextBlocks(source) : []))(doc, options);
|
|
53
57
|
|
|
54
58
|
const lineStartAt = (source: string, offset: number): number => source.lastIndexOf("\n", offset - 1) + 1;
|
|
55
59
|
|
|
56
|
-
/** The written numbers of the document's Markdown ordered lists that skip or repeat (structure/list-numbering.ts). */
|
|
57
|
-
const QUOTED_LINE = /^[ \t]*>/u;
|
|
58
|
-
|
|
59
60
|
/** 引用の中の箇条書きは、ほかの文書の例で、この文書の番号ではない。 */
|
|
60
|
-
const isQuotedList = (source: string, start: number): boolean =>
|
|
61
|
+
const isQuotedList = (source: string, start: number): boolean => /^[ \t]*>/u.test(source.slice(lineStartAt(source, start), start));
|
|
61
62
|
|
|
63
|
+
/** The written numbers of the document's Markdown ordered lists that skip or repeat (structure/list-numbering.ts). */
|
|
62
64
|
const listNumberingGaps = (doc: ProseDocument): StructureIssue[] =>
|
|
63
|
-
doc.lists
|
|
65
|
+
listsNumberBreaks(doc.source, doc.lists, (list) => !isQuotedList(doc.source, list.span.start));
|
|
64
66
|
|
|
65
67
|
const lineFindingOf = (doc: ProseDocument, issue: StructureIssue): Finding => ({
|
|
66
68
|
rule: "numbering-gap",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Lexicon, MarkupHeading } from "../plugin.ts";
|
|
2
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
3
|
+
|
|
4
|
+
type Span = { readonly start: number; readonly end: number };
|
|
5
|
+
|
|
6
|
+
/** Lexicon group of placeholder-word: a word that, in a heading, says what follows is a template (Template, 雛形). */
|
|
7
|
+
export const TEMPLATE = "template";
|
|
8
|
+
|
|
9
|
+
const LATIN = /\p{Script=Latin}/u;
|
|
10
|
+
|
|
11
|
+
/** Does the character beside a match end the word? A Latin edge needs a non-letter next to it; a CJK word stands anywhere. */
|
|
12
|
+
const boundsWord = (edge: string, beside: string | undefined): boolean => !LATIN.test(edge) || beside === undefined || !/[\p{L}\p{N}]/u.test(beside);
|
|
13
|
+
|
|
14
|
+
const holdsWordAt = (text: string, pattern: string, index: number): boolean =>
|
|
15
|
+
boundsWord(pattern[0] ?? "", text[index - 1]) && boundsWord(pattern.at(-1) ?? "", text[index + pattern.length]);
|
|
16
|
+
|
|
17
|
+
/** The heading names a template: one of the words appears in it as a whole word, in any case. */
|
|
18
|
+
export const namesTemplate = (heading: string, words: Lexicon): boolean => {
|
|
19
|
+
const text = heading.toLowerCase();
|
|
20
|
+
return words.some((word) => {
|
|
21
|
+
const pattern = word.pattern.toLowerCase();
|
|
22
|
+
return [...text.matchAll(new RegExp(escapeRegExp(pattern), "gu"))].some((match) => holdsWordAt(text, pattern, match.index));
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** A heading's section runs from the heading to the next one at its depth or above; text before a template title stays checked. */
|
|
27
|
+
const sectionOf = (headings: readonly MarkupHeading[], index: number, documentEnd: number): Span => {
|
|
28
|
+
const heading = headings[index];
|
|
29
|
+
const next = headings.slice(index + 1).find((later) => later.depth <= (heading?.depth ?? 0));
|
|
30
|
+
return { start: heading?.start ?? 0, end: next?.start ?? documentEnd };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** The parts of a document under a heading that names a template: their blanks are the point, not a slip. */
|
|
34
|
+
export const templateSections = (headings: readonly MarkupHeading[], documentEnd: number, words: Lexicon): Span[] =>
|
|
35
|
+
words.length === 0 ? [] : headings.flatMap((heading, index) => (namesTemplate(heading.text, words) ? [sectionOf(headings, index, documentEnd)] : []));
|
|
36
|
+
|
|
37
|
+
export const isInside = (spans: readonly Span[], offset: number): boolean => spans.some((span) => span.start <= offset && offset < span.end);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Token } from "../plugin.ts";
|
|
2
|
+
import { quotedSpans } from "../quoted-span.ts";
|
|
3
|
+
|
|
4
|
+
/** 題の付いたものの名前を閉じる語(語彙表 title-ending: 〜要綱、〜試験、〜事業、〜法)。 */
|
|
5
|
+
export type TitleWords = { readonly endings: readonly string[] };
|
|
6
|
+
|
|
7
|
+
/** 題を囲む括弧。鉤括弧と、見出しや分類の名前を囲む隅付き括弧。 */
|
|
8
|
+
const TITLE_MARKS = { "「": "」", "『": "』", "【": "】" };
|
|
9
|
+
/** 名前のすぐ後ろに付く、その名前の補い(就職氷河期世代)、略称(以下「奨励金」という。)、日付や番号。 */
|
|
10
|
+
const QUALIFIER_OPENERS = new Set(["(", "("]);
|
|
11
|
+
/** 閉じる語の前に要る字数。1 字なら、題ではなく普通の語(新法、本事業)。 */
|
|
12
|
+
const MIN_BODY_LENGTH = 2;
|
|
13
|
+
const BLANK = /^\s*$/u;
|
|
14
|
+
|
|
15
|
+
const surfaceOf = (tokens: readonly Token[]): string => tokens.map((token) => token.surface).join("");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 連なりの語(run: 隙間なく並ぶ語)が、題の名前を閉じる語で終わるか。閉じる語は語の切れ目で始まる
|
|
19
|
+
* (地方自治法の「法」は語、解決方法の「法」は方法の一部)。閉じる語の前に 2 字以上の語が要る。
|
|
20
|
+
*/
|
|
21
|
+
export const endsInTitle = (run: readonly Token[], words: TitleWords): boolean =>
|
|
22
|
+
run.some((_, at) => {
|
|
23
|
+
const tail = surfaceOf(run.slice(at));
|
|
24
|
+
return words.endings.includes(tail) && [...surfaceOf(run.slice(0, at))].length >= MIN_BODY_LENGTH;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 文(text)の start から end までの連なりが、題の名前の置き方をしているか。
|
|
29
|
+
* 括弧の中身の終わりで閉じる(「令和8年度高度統計人材育成強化拠点形成事業」)、すぐ後ろに補いの括弧が付く
|
|
30
|
+
* (国家公務員中途採用者選考試験(就職氷河期世代))、または文がその連なりだけ(要綱の題を並べた行)。
|
|
31
|
+
*/
|
|
32
|
+
export const isTitlePosition = (text: string, start: number, end: number): boolean => {
|
|
33
|
+
const closesBracket = quotedSpans(text, TITLE_MARKS).some((span) => span.end === end && span.start <= start);
|
|
34
|
+
const qualified = QUALIFIER_OPENERS.has(text.charAt(end));
|
|
35
|
+
const alone = BLANK.test(text.slice(0, start)) && BLANK.test(text.slice(end));
|
|
36
|
+
return closesBracket || qualified || alone;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 文書の中で題として置かれた名前(titled: 題の置き方をした連なりと、見出しの中の漢字の連なり)に、
|
|
41
|
+
* この連なりの字(text)が名前の終わりとして含まれるか。同じ名前は、文の中で使っても名前(認定職業訓練実施奨励金の支給額)。
|
|
42
|
+
*/
|
|
43
|
+
export const isTitledName = (text: string, titled: readonly string[]): boolean => titled.some((name) => name.endsWith(text));
|
|
@@ -5,6 +5,7 @@ import { quoteAround } from "./quote-around.ts";
|
|
|
5
5
|
import { mergeSpans } from "../span-merge.ts";
|
|
6
6
|
import { firstEndingAfter } from "../soft-break.ts";
|
|
7
7
|
import { isLabelClose, isLabelInAside } from "./bracket-label.ts";
|
|
8
|
+
import { isInchMark } from "./inch-mark.ts";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 括弧の組。どの言語でも同じ字の決まりなので、語彙表ではなくここに置く。family は形(丸・角)で、全角と半角は同じ形の別の幅。
|
|
@@ -93,6 +94,8 @@ export const bracketProblems = (text: string): BracketProblem[] => {
|
|
|
93
94
|
const scan: Scan = { open: [], problems: [] };
|
|
94
95
|
const heads = paragraphHeads(text);
|
|
95
96
|
[...text.matchAll(MARKS)].forEach((match) => {
|
|
97
|
+
const quoteOpen = scan.open.some((entry) => entry.bracket.family === "quote");
|
|
98
|
+
if (isInchMark(text, match.index, quoteOpen)) return;
|
|
96
99
|
const opening = BY_OPEN.get(match[0]);
|
|
97
100
|
const closing = BY_CLOSE.get(match[0]);
|
|
98
101
|
if (opening !== undefined) openWith(scan, opening, match.index, heads.has(match.index));
|
|
@@ -91,6 +91,8 @@ const findingOf = (doc: ProseDocument, use: UndefinedTerm): Finding => ({
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
const NAME_LISTS = ["proper-name-word", "place-region", "calendar-name", "month-name", "company-form", "party-role"] as const;
|
|
94
|
+
/** The lists whose words end a name right after a comma (Post Holdings, Inc.; Canton, Ohio). */
|
|
95
|
+
const FORM_ENDS = ["company-form", "place-region"] as const;
|
|
94
96
|
|
|
95
97
|
/** Capitalised terms, for a language whose package lists the words that join one name (name-joiner); Japanese has none. */
|
|
96
98
|
const capitalisedUses = (doc: ProseDocument, sentences: readonly (readonly Token[])[], defined: readonly Defined[]): UndefinedTerm[] => {
|
|
@@ -99,6 +101,12 @@ const capitalisedUses = (doc: ProseDocument, sentences: readonly (readonly Token
|
|
|
99
101
|
names: NAME_LISTS.flatMap((id) => patternsOf(doc, id)),
|
|
100
102
|
divisions: patternsOf(doc, "numbered-division"),
|
|
101
103
|
joiners: patternsOf(doc, "name-joiner"),
|
|
104
|
+
nameEnds: patternsOf(doc, "proper-name-word"),
|
|
105
|
+
formEnds: FORM_ENDS.flatMap((id) => patternsOf(doc, id)),
|
|
106
|
+
definitionMarkers: patternsOf(doc, "definition-marker"),
|
|
107
|
+
definedElsewhere: patternsOf(doc, "defined-elsewhere"),
|
|
108
|
+
capacityMarkers: patternsOf(doc, "capacity-marker"),
|
|
109
|
+
articles: patternsOf(doc, "article"),
|
|
102
110
|
};
|
|
103
111
|
const marked = [...(doc.markup?.headings ?? []), ...doc.links, ...setApartSpans(doc.source)];
|
|
104
112
|
return capitalisedTerms({ source: doc.source, readable: readableText(doc.source), sentences, defined, named: namedWords(doc.source), marked, words });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Detector } from "../plugin.ts";
|
|
2
|
+
import { unfilledPlaceholder } from "./ai-phrasing.ts";
|
|
3
|
+
import { TEMPLATE, isInside, templateSections } from "./template-sections.ts";
|
|
4
|
+
|
|
5
|
+
/** A blank under a heading that names a template (Template, 雛形) is what the template is for, so it is not reported. */
|
|
6
|
+
export const unfilledPlaceholderOutsideTemplates: Detector = (doc, options) => {
|
|
7
|
+
const words = (options.lexicon ?? []).filter((word) => word.group === TEMPLATE);
|
|
8
|
+
const templates = templateSections(doc.markup?.headings ?? [], doc.source.length, words);
|
|
9
|
+
return unfilledPlaceholder(doc, options).filter((finding) => !isInside(templates, Number(finding.values["offset"])));
|
|
10
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Vague words dense for the document's length (vague-word-density). Each sentence counts the vague words it holds once:
|
|
2
|
+
// a word inside a longer one ("timely" in "in a timely manner") is that longer word, and a word inside a term of art of the
|
|
3
|
+
// document's genre ("reasonably" in a contract, "adequately compensate" as the test for an injunction) is not vague
|
|
4
|
+
// there. The rule speaks once per sentence, about the document's density. Pure: the words come from the language's
|
|
5
|
+
// lexicons vague-word and vague-word-term-of-art, the genre from the options.
|
|
6
|
+
import { entriesForGenre } from "../lexicon-genres.ts";
|
|
7
|
+
import { escapeRegExp } from "../orthography.ts";
|
|
8
|
+
import type { Detector, Finding, Lexicon, LexiconEntry, Sentence } from "../plugin.ts";
|
|
9
|
+
import { comparableText, comparableWords, entryRanges } from "./lexicon-match.ts";
|
|
10
|
+
import { rateOver } from "./lexicon.ts";
|
|
11
|
+
|
|
12
|
+
const TERMS_OF_ART = "vague-word-term-of-art";
|
|
13
|
+
|
|
14
|
+
type Place = { readonly start: number; readonly end: number };
|
|
15
|
+
|
|
16
|
+
type Match = { readonly pattern: string; readonly place: Place };
|
|
17
|
+
|
|
18
|
+
/** Token spans are places in the document and text places are in the sentence, so one sentence uses one kind for every entry. */
|
|
19
|
+
const readsTokens = (sentence: Sentence, entries: Lexicon): boolean =>
|
|
20
|
+
sentence.tokens !== undefined && entries.every((entry) => (entry.tokens?.length ?? 0) > 0);
|
|
21
|
+
|
|
22
|
+
/** Where an entry stands in the sentence, as character spans: its tokens, or the comparable text. */
|
|
23
|
+
const placesOf = (sentence: Sentence, entry: LexiconEntry, byTokens: boolean): Place[] => {
|
|
24
|
+
const tokens = sentence.tokens;
|
|
25
|
+
if (byTokens && tokens !== undefined)
|
|
26
|
+
return entryRanges(sentence, entry).map((range) => ({ start: tokens[range.start]?.span.start ?? 0, end: tokens[range.end - 1]?.span.end ?? 0 }));
|
|
27
|
+
const word = comparableWords(entry.pattern);
|
|
28
|
+
if (word === "") return [];
|
|
29
|
+
return [...comparableText(sentence).matchAll(new RegExp(escapeRegExp(word), "gu"))].map((found) => ({ start: found.index, end: found.index + word.length }));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const matchesOf = (sentence: Sentence, lexicon: Lexicon, byTokens: boolean): Match[] =>
|
|
33
|
+
lexicon.flatMap((entry) => placesOf(sentence, entry, byTokens).map((place) => ({ pattern: entry.pattern, place })));
|
|
34
|
+
|
|
35
|
+
const covers = (outer: Place, inner: Place): boolean => outer.start <= inner.start && inner.end <= outer.end;
|
|
36
|
+
|
|
37
|
+
const isLonger = (outer: Match, inner: Match): boolean => outer.place.end - outer.place.start > inner.place.end - inner.place.start;
|
|
38
|
+
|
|
39
|
+
/** The vague words of one sentence, in the order written, each once: none inside a longer vague word or a term of art. */
|
|
40
|
+
export const vagueWordsIn = (sentence: Sentence, vague: Lexicon, termsOfArt: Lexicon): string[] => {
|
|
41
|
+
const byTokens = readsTokens(sentence, [...vague, ...termsOfArt]);
|
|
42
|
+
const matches = matchesOf(sentence, vague, byTokens);
|
|
43
|
+
const terms = matchesOf(sentence, termsOfArt, byTokens);
|
|
44
|
+
const kept = matches.filter(
|
|
45
|
+
(match) => !matches.some((other) => isLonger(other, match) && covers(other.place, match.place)) && !terms.some((term) => covers(term.place, match.place)),
|
|
46
|
+
);
|
|
47
|
+
return [...new Set(kept.toSorted((left, right) => left.place.start - right.place.start).map((match) => match.pattern))];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type SentenceHits = { readonly sentence: Sentence; readonly words: readonly string[] };
|
|
51
|
+
|
|
52
|
+
export const vagueDensity: Detector = (doc, options): Finding[] => {
|
|
53
|
+
const termsOfArt = entriesForGenre(doc.lexicons[TERMS_OF_ART] ?? [], options.genre);
|
|
54
|
+
const hits: SentenceHits[] = doc.sentences
|
|
55
|
+
.map((sentence) => ({ sentence, words: vagueWordsIn(sentence, options.lexicon ?? [], termsOfArt) }))
|
|
56
|
+
.filter((hit) => hit.words.length > 0);
|
|
57
|
+
const count = hits.reduce((sum, hit) => sum + hit.words.length, 0);
|
|
58
|
+
const density = rateOver(doc, count, options.limit);
|
|
59
|
+
if (density === undefined) return [];
|
|
60
|
+
return hits.map(({ sentence, words }) => ({
|
|
61
|
+
rule: "vague-word-density",
|
|
62
|
+
severity: "warning",
|
|
63
|
+
line: 0,
|
|
64
|
+
column: 0,
|
|
65
|
+
quote: sentence.text.trim(),
|
|
66
|
+
values: { matched: words[0] ?? "", count, density, limit: options.limit, offset: sentence.span.start },
|
|
67
|
+
}));
|
|
68
|
+
};
|
package/src/facts/fact-scope.ts
CHANGED
|
@@ -118,20 +118,69 @@ const headCount = (heads: ReadonlyMap<string, readonly number[]>, from: number,
|
|
|
118
118
|
|
|
119
119
|
type Lines = { readonly lines: readonly Line[]; readonly breakers: readonly number[]; readonly heads: ReadonlyMap<string, readonly number[]> };
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
/** 表の、見出しを繰り返した何番目の並びの升か(table-facts の columnGroup)。並びごとに別の範囲。 */
|
|
122
|
+
type PlacedFact = Fact & { readonly columnGroup?: number };
|
|
123
|
+
|
|
124
|
+
const scopeOf = (fact: PlacedFact, innermost: StructureNode | undefined, at: Lines): { scope: string; record: boolean } => {
|
|
122
125
|
const index = lineIndexAt(at.lines, fact.value.start);
|
|
123
126
|
const from = innermost === undefined ? 0 : lineIndexAt(at.lines, innermost.span.start);
|
|
124
127
|
const to = innermost === undefined ? at.lines.length : lineIndexAt(at.lines, innermost.span.end - 1) + 1;
|
|
125
|
-
const
|
|
128
|
+
const place = [innermost?.address ?? "doc", (at.breakers[index] ?? 0) - (at.breakers[from] ?? 0), parentItem(at.lines, index)];
|
|
129
|
+
const scope = [...place, ...(fact.columnGroup === undefined ? [] : [`g${fact.columnGroup}`])].join("#");
|
|
126
130
|
return { scope, record: headCount(at.heads, from, to, fact.key) >= RECORD_REPEATS };
|
|
127
131
|
};
|
|
128
132
|
|
|
133
|
+
const MAX_PARAGRAPH_HEAD = 40;
|
|
134
|
+
/** 文の終わりか続きの印。これで終わる行は小見出しではない。 */
|
|
135
|
+
const SENTENCE_END = /[.!?。.!?::;;,、,]$/u;
|
|
136
|
+
const NOT_PLAIN_LINE = /^[ \t]*(?:[#>|]|```|~~~)/u;
|
|
137
|
+
/** setext の見出しの下線。上の行は見出しで、節として木が分ける。 */
|
|
138
|
+
const SETEXT_UNDERLINE = /^[ \t]*(?:=+|-+)[ \t]*$/u;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 段落の頭の、短い名前だけの行(「Example」「Element 1 (standard rated portion)」「例」)。文の終わりの印も値も無く、項目や表の行でもない。
|
|
142
|
+
* 例や場合ごとに名前を付けた段落の頭で、続く値はその例の値。
|
|
143
|
+
*/
|
|
144
|
+
const isNameOnly = (line: string): boolean => {
|
|
145
|
+
const text = line.trim();
|
|
146
|
+
if (text === "" || text.length > MAX_PARAGRAPH_HEAD) return false;
|
|
147
|
+
return /\p{L}/u.test(text) && !SENTENCE_END.test(text) && !LIST_ITEM.test(line) && !NOT_PLAIN_LINE.test(line);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const isParagraphHead = (lines: readonly Line[], index: number, valueLines: ReadonlySet<number>): boolean => {
|
|
151
|
+
const startsParagraph = (lines[index - 1]?.text.trim() ?? "") === "";
|
|
152
|
+
const isSetextHeading = SETEXT_UNDERLINE.test(lines[index + 1]?.text ?? "");
|
|
153
|
+
return startsParagraph && !isSetextHeading && !valueLines.has(index) && isNameOnly(lines[index]?.text ?? "");
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** 小見出しの代わりの行の数に、段落の頭の名前だけの行も数える。 */
|
|
157
|
+
const withParagraphHeads = (lines: readonly Line[], facts: readonly Fact[]): number[] => {
|
|
158
|
+
const valueLines = new Set(facts.map((fact) => lineIndexAt(lines, fact.value.start)));
|
|
159
|
+
return lines.reduce<number[]>(
|
|
160
|
+
(counts, line, index) => {
|
|
161
|
+
counts.push((counts[index] ?? 0) + (isBreaker(line) || isParagraphHead(lines, index, valueLines) ? 1 : 0));
|
|
162
|
+
return counts;
|
|
163
|
+
},
|
|
164
|
+
[0],
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** paragraphHeads: 段落の頭の名前だけの行(Example、例)でも範囲を分ける。 */
|
|
169
|
+
export type ScopeReading = { readonly paragraphHeads?: boolean };
|
|
170
|
+
|
|
129
171
|
/** 事実ごとに、比べてよい範囲と、文書のどの部分かを付ける。 */
|
|
130
|
-
export const scopedFacts = (
|
|
172
|
+
export const scopedFacts = (
|
|
173
|
+
facts: readonly PlacedFact[],
|
|
174
|
+
tree: StructureNode,
|
|
175
|
+
source: string,
|
|
176
|
+
summaryWords: readonly string[],
|
|
177
|
+
reading: ScopeReading = {},
|
|
178
|
+
): ScopedFact[] => {
|
|
131
179
|
if (facts.length === 0) return [];
|
|
132
180
|
const nodes = inDocumentOrder(tree).filter((node) => CONTAINERS.has(node.kind));
|
|
133
181
|
const lines = linesOf(source);
|
|
134
|
-
const
|
|
182
|
+
const breakers = reading.paragraphHeads === true ? withParagraphHeads(lines, facts) : breakerCounts(lines);
|
|
183
|
+
const at: Lines = { lines, breakers, heads: headLines(lines) };
|
|
135
184
|
return facts
|
|
136
185
|
.toSorted((left, right) => left.value.start - right.value.start)
|
|
137
186
|
.map((fact) => {
|
package/src/facts/table-facts.ts
CHANGED
|
@@ -67,18 +67,21 @@ const cellLabel = (rowLabel: string, column: string, headings: readonly string[]
|
|
|
67
67
|
return headings.length > TWO_COLUMNS ? `${rowLabel} ${column}` : rowLabel;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
/**
|
|
71
|
-
|
|
70
|
+
/**
|
|
71
|
+
* 列が三つ以上の表の升は、行の見出し(row)と列の見出し(column)の二つで名前になる。
|
|
72
|
+
* columnGroup: 見出しの並びを繰り返す表(Day | Hours | Day | Hours の、第1週と第2週を横に並べた表)の、何番目の並びの升か。
|
|
73
|
+
*/
|
|
74
|
+
export type TableFact = Fact & { readonly cell?: { readonly row: string; readonly column: string }; readonly columnGroup?: number };
|
|
72
75
|
|
|
73
76
|
const rowFacts = (
|
|
74
|
-
|
|
77
|
+
cells: readonly Cell[],
|
|
75
78
|
headings: readonly string[],
|
|
76
79
|
values: readonly FactValue[],
|
|
77
80
|
source: string,
|
|
78
81
|
totals?: TotalColumns,
|
|
79
82
|
counts?: CountLabelWords,
|
|
80
83
|
): TableFact[] => {
|
|
81
|
-
const [first, ...rest] =
|
|
84
|
+
const [first, ...rest] = cells;
|
|
82
85
|
if (first === undefined) return [];
|
|
83
86
|
const rowLabel = plain(first.text);
|
|
84
87
|
const isCountHead = (value: FactValue): boolean => counts !== undefined && isCountLabelHead(source, first, value, counts);
|
|
@@ -94,11 +97,45 @@ const rowFacts = (
|
|
|
94
97
|
});
|
|
95
98
|
};
|
|
96
99
|
|
|
100
|
+
const MIN_GROUPS = 2;
|
|
101
|
+
|
|
102
|
+
/** 見出しが同じ並びを二度以上繰り返すなら、その並びの幅。並びごとに別の表として読む。 */
|
|
103
|
+
export const columnGroupWidth = (headings: readonly string[]): number | undefined => {
|
|
104
|
+
const keys = headings.map(keyOf);
|
|
105
|
+
const widths = Array.from({ length: Math.floor(keys.length / MIN_GROUPS) - 1 }, (_, index) => index + TWO_COLUMNS);
|
|
106
|
+
return widths.find((width) => keys.length % width === 0 && keys[0] !== "" && keys.every((key, index) => key === keys[index % width]));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const groupsOf = <T>(items: readonly T[], width: number | undefined): T[][] =>
|
|
110
|
+
width === undefined ? [[...items]] : Array.from({ length: Math.ceil(items.length / width) }, (_, index) => items.slice(index * width, (index + 1) * width));
|
|
111
|
+
|
|
112
|
+
/** columnGroups: 見出しを繰り返す表を、並びごとに読む(並びの升には columnGroup が付く)。 */
|
|
113
|
+
export type TableReading = { readonly columnGroups?: boolean };
|
|
114
|
+
|
|
115
|
+
type RowReading = { readonly values: readonly FactValue[]; readonly source: string; readonly totals?: TotalColumns; readonly counts?: CountLabelWords };
|
|
116
|
+
|
|
117
|
+
/** 一つの行の升の値。見出しを繰り返す表(width がある)は、並びごとに読んで columnGroup を付ける。 */
|
|
118
|
+
const groupedRowFacts = (row: Line, headings: readonly string[], width: number | undefined, at: RowReading): TableFact[] => {
|
|
119
|
+
const headingGroups = groupsOf(headings, width);
|
|
120
|
+
return groupsOf(cellsOf(row), width).flatMap((cells, group) => {
|
|
121
|
+
const facts = rowFacts(cells, headingGroups[group] ?? [], at.values, at.source, at.totals, at.counts);
|
|
122
|
+
return width === undefined ? facts : facts.map((fact) => ({ ...fact, columnGroup: group }));
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
97
126
|
/** 文書の表すべての升の値。 */
|
|
98
|
-
export const tableFacts = (
|
|
127
|
+
export const tableFacts = (
|
|
128
|
+
source: string,
|
|
129
|
+
values: readonly FactValue[],
|
|
130
|
+
totals?: TotalColumns,
|
|
131
|
+
counts?: CountLabelWords,
|
|
132
|
+
reading: TableReading = {},
|
|
133
|
+
): TableFact[] =>
|
|
99
134
|
tablesOf(linesOf(source)).flatMap((table) => {
|
|
100
135
|
const headings = cellsOf(table.header).map((cell) => plain(cell.text));
|
|
101
|
-
|
|
136
|
+
const width = reading.columnGroups === true ? columnGroupWidth(headings) : undefined;
|
|
137
|
+
const at: RowReading = { values, source, ...(totals === undefined ? {} : { totals }), ...(counts === undefined ? {} : { counts }) };
|
|
138
|
+
return table.rows.flatMap((row) => groupedRowFacts(row, headings, width, at));
|
|
102
139
|
});
|
|
103
140
|
|
|
104
141
|
/** 文書の表すべての、本体の行の升(見出しの行を除く)。text は書いたまま。 */
|