chaffjs 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/api.d.ts +16 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js.map +1 -1
- package/dist/cli-text.d.ts +2 -0
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +18 -5
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -11
- package/dist/cli.js.map +1 -1
- package/dist/command-help.d.ts +7 -0
- package/dist/command-help.d.ts.map +1 -0
- package/dist/command-help.js +18 -0
- package/dist/command-help.js.map +1 -0
- package/dist/commands/eval.js +2 -2
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/fix-plan.d.ts.map +1 -1
- package/dist/commands/fix-plan.js +11 -1
- package/dist/commands/fix-plan.js.map +1 -1
- package/dist/commands/help.d.ts +4 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +7 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/init-command.d.ts +4 -0
- package/dist/commands/init-command.d.ts.map +1 -0
- package/dist/commands/init-command.js +15 -0
- package/dist/commands/init-command.js.map +1 -0
- package/dist/commands/plugin-init.d.ts +20 -0
- package/dist/commands/plugin-init.d.ts.map +1 -0
- package/dist/commands/plugin-init.js +129 -0
- package/dist/commands/plugin-init.js.map +1 -0
- package/dist/commands/plugin-test.d.ts +11 -0
- package/dist/commands/plugin-test.d.ts.map +1 -0
- package/dist/commands/plugin-test.js +81 -0
- package/dist/commands/plugin-test.js.map +1 -0
- package/dist/config/load.d.ts +2 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +1 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/option-problems.js +2 -2
- package/dist/config/option-problems.js.map +1 -1
- package/dist/config/rule-problems.js +2 -2
- package/dist/config/rule-problems.js.map +1 -1
- package/dist/custom/checked.d.ts +9 -0
- package/dist/custom/checked.d.ts.map +1 -0
- package/dist/custom/checked.js +3 -0
- package/dist/custom/checked.js.map +1 -0
- package/dist/custom/dsl-fields.d.ts +27 -0
- package/dist/custom/dsl-fields.d.ts.map +1 -0
- package/dist/custom/dsl-fields.js +86 -0
- package/dist/custom/dsl-fields.js.map +1 -0
- package/dist/custom/load.d.ts.map +1 -1
- package/dist/custom/load.js +2 -0
- package/dist/custom/load.js.map +1 -1
- package/dist/custom/parse.d.ts +14 -3
- package/dist/custom/parse.d.ts.map +1 -1
- package/dist/custom/parse.js +44 -40
- package/dist/custom/parse.js.map +1 -1
- package/dist/custom/problems.d.ts.map +1 -1
- package/dist/custom/problems.js +10 -1
- package/dist/custom/problems.js.map +1 -1
- package/dist/detectors/amount-for-count.d.ts +21 -0
- package/dist/detectors/amount-for-count.d.ts.map +1 -0
- package/dist/detectors/amount-for-count.js +55 -0
- package/dist/detectors/amount-for-count.js.map +1 -0
- package/dist/detectors/blank-structure.d.ts.map +1 -1
- package/dist/detectors/blank-structure.js +6 -4
- package/dist/detectors/blank-structure.js.map +1 -1
- package/dist/detectors/clause-shape.d.ts +22 -0
- package/dist/detectors/clause-shape.d.ts.map +1 -0
- package/dist/detectors/clause-shape.js +149 -0
- package/dist/detectors/clause-shape.js.map +1 -0
- package/dist/detectors/code-fences.d.ts +34 -0
- package/dist/detectors/code-fences.d.ts.map +1 -0
- package/dist/detectors/code-fences.js +100 -0
- package/dist/detectors/code-fences.js.map +1 -0
- package/dist/detectors/cross-definitions.d.ts +4 -0
- package/dist/detectors/cross-definitions.d.ts.map +1 -0
- package/dist/detectors/cross-definitions.js +43 -0
- package/dist/detectors/cross-definitions.js.map +1 -0
- package/dist/detectors/cross-facts.d.ts +4 -0
- package/dist/detectors/cross-facts.d.ts.map +1 -0
- package/dist/detectors/cross-facts.js +53 -0
- package/dist/detectors/cross-facts.js.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts +3 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js +3 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts +3 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.js +3 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.js.map +1 -0
- package/dist/detectors/currency-notation.d.ts +29 -0
- package/dist/detectors/currency-notation.d.ts.map +1 -0
- package/dist/detectors/currency-notation.js +58 -0
- package/dist/detectors/currency-notation.js.map +1 -0
- package/dist/detectors/custom.d.ts.map +1 -1
- package/dist/detectors/custom.js +4 -1
- package/dist/detectors/custom.js.map +1 -1
- package/dist/detectors/dangling-opener.d.ts +14 -0
- package/dist/detectors/dangling-opener.d.ts.map +1 -0
- package/dist/detectors/dangling-opener.js +55 -0
- package/dist/detectors/dangling-opener.js.map +1 -0
- package/dist/detectors/defined-term-form.d.ts +25 -0
- package/dist/detectors/defined-term-form.d.ts.map +1 -0
- package/dist/detectors/defined-term-form.js +170 -0
- package/dist/detectors/defined-term-form.js.map +1 -0
- package/dist/detectors/email-letter.d.ts +46 -0
- package/dist/detectors/email-letter.d.ts.map +1 -0
- package/dist/detectors/email-letter.js +234 -0
- package/dist/detectors/email-letter.js.map +1 -0
- package/dist/detectors/footnote-mismatch.d.ts +11 -0
- package/dist/detectors/footnote-mismatch.d.ts.map +1 -0
- package/dist/detectors/footnote-mismatch.js +55 -0
- package/dist/detectors/footnote-mismatch.js.map +1 -0
- package/dist/detectors/form-minority.d.ts +13 -0
- package/dist/detectors/form-minority.d.ts.map +1 -0
- package/dist/detectors/form-minority.js +19 -0
- package/dist/detectors/form-minority.js.map +1 -0
- package/dist/detectors/heading-end-mark.d.ts +10 -0
- package/dist/detectors/heading-end-mark.d.ts.map +1 -0
- package/dist/detectors/heading-end-mark.js +59 -0
- package/dist/detectors/heading-end-mark.js.map +1 -0
- package/dist/detectors/impossible-date.d.ts +20 -0
- package/dist/detectors/impossible-date.d.ts.map +1 -0
- package/dist/detectors/impossible-date.js +105 -0
- package/dist/detectors/impossible-date.js.map +1 -0
- package/dist/detectors/impossible-time.d.ts +18 -0
- package/dist/detectors/impossible-time.d.ts.map +1 -0
- package/dist/detectors/impossible-time.js +116 -0
- package/dist/detectors/impossible-time.js.map +1 -0
- package/dist/detectors/link-shape.d.ts +11 -0
- package/dist/detectors/link-shape.d.ts.map +1 -0
- package/dist/detectors/link-shape.js +56 -0
- package/dist/detectors/link-shape.js.map +1 -0
- package/dist/detectors/list-marker.d.ts +7 -0
- package/dist/detectors/list-marker.d.ts.map +1 -0
- package/dist/detectors/list-marker.js +50 -0
- package/dist/detectors/list-marker.js.map +1 -0
- package/dist/detectors/mark-shape.d.ts +53 -0
- package/dist/detectors/mark-shape.d.ts.map +1 -0
- package/dist/detectors/mark-shape.js +160 -0
- package/dist/detectors/mark-shape.js.map +1 -0
- package/dist/detectors/markdown-slip.d.ts +19 -0
- package/dist/detectors/markdown-slip.d.ts.map +1 -0
- package/dist/detectors/markdown-slip.js +122 -0
- package/dist/detectors/markdown-slip.js.map +1 -0
- package/dist/detectors/numeral-opener.d.ts +10 -0
- package/dist/detectors/numeral-opener.d.ts.map +1 -0
- package/dist/detectors/numeral-opener.js +65 -0
- package/dist/detectors/numeral-opener.js.map +1 -0
- package/dist/detectors/ordinal-suffix.d.ts +12 -0
- package/dist/detectors/ordinal-suffix.d.ts.map +1 -0
- package/dist/detectors/ordinal-suffix.js +33 -0
- package/dist/detectors/ordinal-suffix.js.map +1 -0
- package/dist/detectors/range-notation.d.ts +15 -0
- package/dist/detectors/range-notation.d.ts.map +1 -0
- package/dist/detectors/range-notation.js +58 -0
- package/dist/detectors/range-notation.js.map +1 -0
- package/dist/detectors/registry/adverb-polarity.d.ts +3 -0
- package/dist/detectors/registry/adverb-polarity.d.ts.map +1 -0
- package/dist/detectors/registry/adverb-polarity.js +3 -0
- package/dist/detectors/registry/adverb-polarity.js.map +1 -0
- package/dist/detectors/registry/amount-for-count.d.ts +3 -0
- package/dist/detectors/registry/amount-for-count.d.ts.map +1 -0
- package/dist/detectors/registry/amount-for-count.js +3 -0
- package/dist/detectors/registry/amount-for-count.js.map +1 -0
- package/dist/detectors/registry/attachment-missing.d.ts +3 -0
- package/dist/detectors/registry/attachment-missing.d.ts.map +1 -0
- package/dist/detectors/registry/attachment-missing.js +3 -0
- package/dist/detectors/registry/attachment-missing.js.map +1 -0
- package/dist/detectors/registry/bare-url-mix.d.ts +3 -0
- package/dist/detectors/registry/bare-url-mix.d.ts.map +1 -0
- package/dist/detectors/registry/bare-url-mix.js +3 -0
- package/dist/detectors/registry/bare-url-mix.js.map +1 -0
- package/dist/detectors/registry/caps-shouting.d.ts +3 -0
- package/dist/detectors/registry/caps-shouting.d.ts.map +1 -0
- package/dist/detectors/registry/caps-shouting.js +3 -0
- package/dist/detectors/registry/caps-shouting.js.map +1 -0
- package/dist/detectors/registry/code-fence-language.d.ts +3 -0
- package/dist/detectors/registry/code-fence-language.d.ts.map +1 -0
- package/dist/detectors/registry/code-fence-language.js +3 -0
- package/dist/detectors/registry/code-fence-language.js.map +1 -0
- package/dist/detectors/registry/currency-notation.d.ts +3 -0
- package/dist/detectors/registry/currency-notation.d.ts.map +1 -0
- package/dist/detectors/registry/currency-notation.js +3 -0
- package/dist/detectors/registry/currency-notation.js.map +1 -0
- package/dist/detectors/registry/dangling-opener.d.ts +3 -0
- package/dist/detectors/registry/dangling-opener.d.ts.map +1 -0
- package/dist/detectors/registry/dangling-opener.js +3 -0
- package/dist/detectors/registry/dangling-opener.js.map +1 -0
- package/dist/detectors/registry/defined-name-repeated.d.ts +3 -0
- package/dist/detectors/registry/defined-name-repeated.d.ts.map +1 -0
- package/dist/detectors/registry/defined-name-repeated.js +3 -0
- package/dist/detectors/registry/defined-name-repeated.js.map +1 -0
- package/dist/detectors/registry/defined-term-form.d.ts +3 -0
- package/dist/detectors/registry/defined-term-form.d.ts.map +1 -0
- package/dist/detectors/registry/defined-term-form.js +3 -0
- package/dist/detectors/registry/defined-term-form.js.map +1 -0
- package/dist/detectors/registry/email-frame.d.ts +3 -0
- package/dist/detectors/registry/email-frame.d.ts.map +1 -0
- package/dist/detectors/registry/email-frame.js +3 -0
- package/dist/detectors/registry/email-frame.js.map +1 -0
- package/dist/detectors/registry/email-subject.d.ts +3 -0
- package/dist/detectors/registry/email-subject.d.ts.map +1 -0
- package/dist/detectors/registry/email-subject.js +3 -0
- package/dist/detectors/registry/email-subject.js.map +1 -0
- package/dist/detectors/registry/ending-run.d.ts +3 -0
- package/dist/detectors/registry/ending-run.d.ts.map +1 -0
- package/dist/detectors/registry/ending-run.js +3 -0
- package/dist/detectors/registry/ending-run.js.map +1 -0
- package/dist/detectors/registry/footnote-mismatch.d.ts +3 -0
- package/dist/detectors/registry/footnote-mismatch.d.ts.map +1 -0
- package/dist/detectors/registry/footnote-mismatch.js +3 -0
- package/dist/detectors/registry/footnote-mismatch.js.map +1 -0
- package/dist/detectors/registry/heading-command.d.ts +3 -0
- package/dist/detectors/registry/heading-command.d.ts.map +1 -0
- package/dist/detectors/registry/heading-command.js +3 -0
- package/dist/detectors/registry/heading-command.js.map +1 -0
- package/dist/detectors/registry/heading-end-punctuation.d.ts +3 -0
- package/dist/detectors/registry/heading-end-punctuation.d.ts.map +1 -0
- package/dist/detectors/registry/heading-end-punctuation.js +3 -0
- package/dist/detectors/registry/heading-end-punctuation.js.map +1 -0
- package/dist/detectors/registry/hyphen-dash.d.ts +3 -0
- package/dist/detectors/registry/hyphen-dash.d.ts.map +1 -0
- package/dist/detectors/registry/hyphen-dash.js +3 -0
- package/dist/detectors/registry/hyphen-dash.js.map +1 -0
- package/dist/detectors/registry/image-file-name-alt.d.ts +3 -0
- package/dist/detectors/registry/image-file-name-alt.d.ts.map +1 -0
- package/dist/detectors/registry/image-file-name-alt.js +3 -0
- package/dist/detectors/registry/image-file-name-alt.js.map +1 -0
- package/dist/detectors/registry/impossible-date.d.ts +3 -0
- package/dist/detectors/registry/impossible-date.d.ts.map +1 -0
- package/dist/detectors/registry/impossible-date.js +3 -0
- package/dist/detectors/registry/impossible-date.js.map +1 -0
- package/dist/detectors/registry/impossible-time.d.ts +3 -0
- package/dist/detectors/registry/impossible-time.d.ts.map +1 -0
- package/dist/detectors/registry/impossible-time.js +3 -0
- package/dist/detectors/registry/impossible-time.js.map +1 -0
- package/dist/detectors/registry/latin-abbreviation.d.ts +3 -0
- package/dist/detectors/registry/latin-abbreviation.d.ts.map +1 -0
- package/dist/detectors/registry/latin-abbreviation.js +3 -0
- package/dist/detectors/registry/latin-abbreviation.js.map +1 -0
- package/dist/detectors/registry/link-text-url-mismatch.d.ts +3 -0
- package/dist/detectors/registry/link-text-url-mismatch.d.ts.map +1 -0
- package/dist/detectors/registry/link-text-url-mismatch.js +3 -0
- package/dist/detectors/registry/link-text-url-mismatch.js.map +1 -0
- package/dist/detectors/registry/list-marker.d.ts +3 -0
- package/dist/detectors/registry/list-marker.d.ts.map +1 -0
- package/dist/detectors/registry/list-marker.js +3 -0
- package/dist/detectors/registry/list-marker.js.map +1 -0
- package/dist/detectors/registry/mark-consistency.d.ts +3 -0
- package/dist/detectors/registry/mark-consistency.d.ts.map +1 -0
- package/dist/detectors/registry/mark-consistency.js +3 -0
- package/dist/detectors/registry/mark-consistency.js.map +1 -0
- package/dist/detectors/registry/numeral-opener.d.ts +3 -0
- package/dist/detectors/registry/numeral-opener.d.ts.map +1 -0
- package/dist/detectors/registry/numeral-opener.js +3 -0
- package/dist/detectors/registry/numeral-opener.js.map +1 -0
- package/dist/detectors/registry/ordinal-suffix.d.ts +3 -0
- package/dist/detectors/registry/ordinal-suffix.d.ts.map +1 -0
- package/dist/detectors/registry/ordinal-suffix.js +3 -0
- package/dist/detectors/registry/ordinal-suffix.js.map +1 -0
- package/dist/detectors/registry/origin-particle.d.ts +3 -0
- package/dist/detectors/registry/origin-particle.d.ts.map +1 -0
- package/dist/detectors/registry/origin-particle.js +3 -0
- package/dist/detectors/registry/origin-particle.js.map +1 -0
- package/dist/detectors/registry/range-notation.d.ts +3 -0
- package/dist/detectors/registry/range-notation.d.ts.map +1 -0
- package/dist/detectors/registry/range-notation.js +3 -0
- package/dist/detectors/registry/range-notation.js.map +1 -0
- package/dist/detectors/registry/relative-pronoun-mix.d.ts +3 -0
- package/dist/detectors/registry/relative-pronoun-mix.d.ts.map +1 -0
- package/dist/detectors/registry/relative-pronoun-mix.js +3 -0
- package/dist/detectors/registry/relative-pronoun-mix.js.map +1 -0
- package/dist/detectors/registry/request-without-deadline.d.ts +3 -0
- package/dist/detectors/registry/request-without-deadline.d.ts.map +1 -0
- package/dist/detectors/registry/request-without-deadline.js +3 -0
- package/dist/detectors/registry/request-without-deadline.js.map +1 -0
- package/dist/detectors/registry/sentence-spacing.d.ts +3 -0
- package/dist/detectors/registry/sentence-spacing.d.ts.map +1 -0
- package/dist/detectors/registry/sentence-spacing.js +3 -0
- package/dist/detectors/registry/sentence-spacing.js.map +1 -0
- package/dist/detectors/registry/share-over-hundred.d.ts +3 -0
- package/dist/detectors/registry/share-over-hundred.d.ts.map +1 -0
- package/dist/detectors/registry/share-over-hundred.js +3 -0
- package/dist/detectors/registry/share-over-hundred.js.map +1 -0
- package/dist/detectors/registry/shell-prompt.d.ts +3 -0
- package/dist/detectors/registry/shell-prompt.d.ts.map +1 -0
- package/dist/detectors/registry/shell-prompt.js +3 -0
- package/dist/detectors/registry/shell-prompt.js.map +1 -0
- package/dist/detectors/registry/step-reference.d.ts +3 -0
- package/dist/detectors/registry/step-reference.d.ts.map +1 -0
- package/dist/detectors/registry/step-reference.js +3 -0
- package/dist/detectors/registry/step-reference.js.map +1 -0
- package/dist/detectors/registry/step-statement.d.ts +3 -0
- package/dist/detectors/registry/step-statement.d.ts.map +1 -0
- package/dist/detectors/registry/step-statement.js +3 -0
- package/dist/detectors/registry/step-statement.js.map +1 -0
- package/dist/detectors/registry/table-row-overflow.d.ts +3 -0
- package/dist/detectors/registry/table-row-overflow.d.ts.map +1 -0
- package/dist/detectors/registry/table-row-overflow.js +3 -0
- package/dist/detectors/registry/table-row-overflow.js.map +1 -0
- package/dist/detectors/registry/toc-heading.d.ts +3 -0
- package/dist/detectors/registry/toc-heading.d.ts.map +1 -0
- package/dist/detectors/registry/toc-heading.js +3 -0
- package/dist/detectors/registry/toc-heading.js.map +1 -0
- package/dist/detectors/registry/unclosed-code-fence.d.ts +3 -0
- package/dist/detectors/registry/unclosed-code-fence.d.ts.map +1 -0
- package/dist/detectors/registry/unclosed-code-fence.js +3 -0
- package/dist/detectors/registry/unclosed-code-fence.js.map +1 -0
- package/dist/detectors/registry/unpaired-parallel.d.ts +3 -0
- package/dist/detectors/registry/unpaired-parallel.d.ts.map +1 -0
- package/dist/detectors/registry/unpaired-parallel.js +3 -0
- package/dist/detectors/registry/unpaired-parallel.js.map +1 -0
- package/dist/detectors/registry/unrendered-emphasis.d.ts +3 -0
- package/dist/detectors/registry/unrendered-emphasis.d.ts.map +1 -0
- package/dist/detectors/registry/unrendered-emphasis.js +3 -0
- package/dist/detectors/registry/unrendered-emphasis.js.map +1 -0
- package/dist/detectors/relative-pronoun-mix.d.ts +19 -0
- package/dist/detectors/relative-pronoun-mix.d.ts.map +1 -0
- package/dist/detectors/relative-pronoun-mix.js +60 -0
- package/dist/detectors/relative-pronoun-mix.js.map +1 -0
- package/dist/detectors/request-owner.d.ts +23 -0
- package/dist/detectors/request-owner.d.ts.map +1 -0
- package/dist/detectors/request-owner.js +110 -0
- package/dist/detectors/request-owner.js.map +1 -0
- package/dist/detectors/share-over-hundred.d.ts +20 -0
- package/dist/detectors/share-over-hundred.d.ts.map +1 -0
- package/dist/detectors/share-over-hundred.js +55 -0
- package/dist/detectors/share-over-hundred.js.map +1 -0
- package/dist/detectors/step-reference.d.ts +20 -0
- package/dist/detectors/step-reference.d.ts.map +1 -0
- package/dist/detectors/step-reference.js +57 -0
- package/dist/detectors/step-reference.js.map +1 -0
- package/dist/detectors/technical-docs.d.ts +32 -0
- package/dist/detectors/technical-docs.d.ts.map +1 -0
- package/dist/detectors/technical-docs.js +199 -0
- package/dist/detectors/technical-docs.js.map +1 -0
- package/dist/detectors/toc-heading.d.ts +10 -0
- package/dist/detectors/toc-heading.d.ts.map +1 -0
- package/dist/detectors/toc-heading.js +99 -0
- package/dist/detectors/toc-heading.js.map +1 -0
- package/dist/extension/pack-read.d.ts +21 -0
- package/dist/extension/pack-read.d.ts.map +1 -0
- package/dist/extension/pack-read.js +117 -0
- package/dist/extension/pack-read.js.map +1 -0
- package/dist/extension/plugin-load.d.ts.map +1 -1
- package/dist/extension/plugin-load.js +44 -10
- package/dist/extension/plugin-load.js.map +1 -1
- package/dist/extension/plugin-parse.d.ts.map +1 -1
- package/dist/extension/plugin-parse.js +10 -3
- package/dist/extension/plugin-parse.js.map +1 -1
- package/dist/facts/address-values.d.ts +5 -0
- package/dist/facts/address-values.d.ts.map +1 -0
- package/dist/facts/address-values.js +64 -0
- package/dist/facts/address-values.js.map +1 -0
- package/dist/facts/cross-facts.d.ts +20 -0
- package/dist/facts/cross-facts.d.ts.map +1 -0
- package/dist/facts/cross-facts.js +41 -0
- package/dist/facts/cross-facts.js.map +1 -0
- package/dist/fix-plan/chosen-depth.d.ts +10 -0
- package/dist/fix-plan/chosen-depth.d.ts.map +1 -0
- package/dist/fix-plan/chosen-depth.js +24 -0
- package/dist/fix-plan/chosen-depth.js.map +1 -0
- package/dist/fix-plan/mode.d.ts +13 -4
- package/dist/fix-plan/mode.d.ts.map +1 -1
- package/dist/fix-plan/mode.js +38 -7
- package/dist/fix-plan/mode.js.map +1 -1
- package/dist/fix-plan/plan.d.ts +20 -0
- package/dist/fix-plan/plan.d.ts.map +1 -1
- package/dist/fix-plan/plan.js +23 -5
- package/dist/fix-plan/plan.js.map +1 -1
- package/dist/fix-plan/render.d.ts.map +1 -1
- package/dist/fix-plan/render.js +24 -0
- package/dist/fix-plan/render.js.map +1 -1
- package/dist/fix-plan/text.d.ts +8 -0
- package/dist/fix-plan/text.d.ts.map +1 -1
- package/dist/fix-plan/text.js +36 -10
- package/dist/fix-plan/text.js.map +1 -1
- package/dist/genre-load.d.ts +1 -1
- package/dist/genre-load.d.ts.map +1 -1
- package/dist/genre-load.js +7 -4
- package/dist/genre-load.js.map +1 -1
- package/dist/genre-parse.d.ts +6 -0
- package/dist/genre-parse.d.ts.map +1 -1
- package/dist/genre-parse.js +18 -0
- package/dist/genre-parse.js.map +1 -1
- package/dist/init.js +10 -10
- package/dist/init.js.map +1 -1
- package/dist/known-genres.d.ts +8 -0
- package/dist/known-genres.d.ts.map +1 -0
- package/dist/known-genres.js +17 -0
- package/dist/known-genres.js.map +1 -0
- package/dist/link-shape.d.ts +20 -0
- package/dist/link-shape.d.ts.map +1 -0
- package/dist/link-shape.js +58 -0
- package/dist/link-shape.js.map +1 -0
- package/dist/list-marks.d.ts +21 -0
- package/dist/list-marks.d.ts.map +1 -0
- package/dist/list-marks.js +82 -0
- package/dist/list-marks.js.map +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +15 -4
- package/dist/reasons.js.map +1 -1
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +11 -2
- package/dist/render/explain.js.map +1 -1
- package/dist/render/friendly.js +1 -1
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +12 -3
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/rules-table.js +6 -6
- package/dist/render/rules-table.js.map +1 -1
- package/dist/render/semantic.js +2 -2
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/suppressions.js +2 -2
- package/dist/render/suppressions.js.map +1 -1
- package/dist/rewrite-depth.d.ts +22 -0
- package/dist/rewrite-depth.d.ts.map +1 -0
- package/dist/rewrite-depth.js +42 -0
- package/dist/rewrite-depth.js.map +1 -0
- package/dist/rule-fields.d.ts +28 -0
- package/dist/rule-fields.d.ts.map +1 -0
- package/dist/rule-fields.js +85 -0
- package/dist/rule-fields.js.map +1 -0
- package/dist/rule-guide.d.ts +3 -0
- package/dist/rule-guide.d.ts.map +1 -1
- package/dist/rule-guide.js +6 -0
- package/dist/rule-guide.js.map +1 -1
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +13 -5
- package/dist/rule-load.js.map +1 -1
- package/dist/rule-offs.d.ts +14 -0
- package/dist/rule-offs.d.ts.map +1 -0
- package/dist/rule-offs.js +45 -0
- package/dist/rule-offs.js.map +1 -0
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +14 -1
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/cross-definitions.d.ts +23 -0
- package/dist/structure/cross-definitions.d.ts.map +1 -0
- package/dist/structure/cross-definitions.js +34 -0
- package/dist/structure/cross-definitions.js.map +1 -0
- package/genres.yaml +14 -313
- package/package.json +3 -3
- package/rules/absolute-exception.yaml +3 -1
- package/rules/abstract-length.yaml +7 -1
- package/rules/acronym-expansion-conflict.yaml +2 -1
- package/rules/adverb-overuse.yaml +7 -1
- package/rules/adverb-without-negation.yaml +60 -0
- package/rules/adversative-ga-repeat.yaml +2 -1
- package/rules/agentless-passive.yaml +10 -1
- package/rules/agreement-slip.yaml +4 -1
- package/rules/ai-generated-composite.yaml +6 -1
- package/rules/ai-structure.yaml +8 -2
- package/rules/ai-tell.yaml +7 -1
- package/rules/all-caps-shouting.yaml +61 -0
- package/rules/announced-count-mismatch.yaml +3 -1
- package/rules/announcing-opener.yaml +7 -1
- package/rules/article-sound.yaml +6 -2
- package/rules/assistant-residue.yaml +4 -1
- package/rules/attachment-not-attached.yaml +112 -0
- package/rules/baito-keigo.yaml +64 -0
- package/rules/bare-url-mix.yaml +86 -0
- package/rules/bold-density.yaml +1 -0
- package/rules/bold-label-list.yaml +9 -1
- package/rules/bracket-nesting.yaml +2 -1
- package/rules/bracket-width-consistency.yaml +64 -0
- package/rules/broken-link.yaml +1 -1
- package/rules/chat-citation-residue.yaml +2 -1
- package/rules/citation-reference-mismatch.yaml +7 -1
- package/rules/citation-style-mix.yaml +7 -1
- package/rules/clause-chain.yaml +6 -1
- package/rules/cliche.yaml +67 -0
- package/rules/closing-cliche.yaml +7 -0
- package/rules/code-fence-language-mix.yaml +176 -0
- package/rules/colloquial-opener.yaml +62 -0
- package/rules/colon-lead-in.yaml +7 -1
- package/rules/concrete-evidence-density.yaml +6 -0
- package/rules/contraction-consistency.yaml +4 -1
- package/rules/contrast-framing.yaml +7 -1
- package/rules/cross-doc-duplicate-definition.yaml +70 -0
- package/rules/cross-doc-fact-conflict.yaml +64 -0
- package/rules/currency-notation-consistency.yaml +77 -0
- package/rules/cushion-phrase-density.yaml +8 -1
- package/rules/dangling-figure-reference.yaml +3 -1
- package/rules/dangling-participle.yaml +58 -0
- package/rules/dangling-reference.yaml +3 -1
- package/rules/date-format-consistency.yaml +3 -2
- package/rules/date-order.yaml +3 -1
- package/rules/date-range-reversed.yaml +3 -1
- package/rules/date-weekday-mismatch.yaml +3 -1
- package/rules/date-without-year.yaml +4 -3
- package/rules/defined-name-repeated.yaml +97 -0
- package/rules/defined-term-form.yaml +113 -0
- package/rules/demonstrative-opener-run.yaml +2 -1
- package/rules/double-keigo.yaml +6 -1
- package/rules/double-negative.yaml +5 -1
- package/rules/doubled-nado.yaml +2 -1
- package/rules/doubled-punctuation.yaml +1 -1
- package/rules/doubled-word.yaml +4 -1
- package/rules/duplicate-definition.yaml +3 -1
- package/rules/duplicate-heading.yaml +1 -1
- package/rules/duration-mismatch.yaml +2 -1
- package/rules/elapsed-years-mismatch.yaml +4 -1
- package/rules/email-greeting-closing.yaml +122 -0
- package/rules/email-subject-length.yaml +112 -0
- package/rules/emoji-density.yaml +3 -1
- package/rules/emoji-heading.yaml +7 -1
- package/rules/empty-intensifier.yaml +4 -0
- package/rules/empty-list-item.yaml +2 -1
- package/rules/empty-section.yaml +1 -1
- package/rules/empty-table-cell.yaml +2 -1
- package/rules/excessive-hedging.yaml +7 -1
- package/rules/exclamation-width-consistency.yaml +64 -0
- package/rules/expletive-construction.yaml +7 -1
- package/rules/fact-conflict.yaml +4 -1
- package/rules/figure-reference-order.yaml +7 -1
- package/rules/footnote-mismatch.yaml +91 -0
- package/rules/fullwidth-alnum-consistency.yaml +2 -2
- package/rules/hankaku-kana.yaml +1 -1
- package/rules/heading-command-missing.yaml +109 -0
- package/rules/heading-echo.yaml +6 -1
- package/rules/heading-end-punctuation-mix.yaml +158 -0
- package/rules/heading-level-skip.yaml +3 -3
- package/rules/heading-numbering-mix.yaml +4 -3
- package/rules/hiragana-fukushi.yaml +4 -1
- package/rules/homophone-slip.yaml +5 -1
- package/rules/humble-for-others.yaml +63 -0
- package/rules/hyphen-as-dash.yaml +59 -0
- package/rules/idiom-misuse.yaml +4 -1
- package/rules/image-file-name-alt.yaml +83 -0
- package/rules/impossible-date.yaml +79 -0
- package/rules/impossible-time.yaml +76 -0
- package/rules/internal-jargon.yaml +1 -1
- package/rules/invisible-character.yaml +1 -1
- package/rules/katakana-density.yaml +2 -1
- package/rules/katakana-long-vowel.yaml +1 -1
- package/rules/known-misspelling.yaml +2 -1
- package/rules/kutoten-consistency.yaml +1 -1
- package/rules/latin-abbreviation-form.yaml +58 -0
- package/rules/latin-spacing.yaml +6 -2
- package/rules/less-for-fewer.yaml +59 -0
- package/rules/link-text-url-mismatch.yaml +72 -0
- package/rules/list-item-form-mix.yaml +7 -1
- package/rules/list-marker-mix.yaml +111 -0
- package/rules/max-kanji-continuous.yaml +10 -2
- package/rules/max-paragraph-length.yaml +4 -1
- package/rules/max-sentence-length.yaml +29 -21
- package/rules/max-ten.yaml +4 -1
- package/rules/misconversion.yaml +2 -1
- package/rules/modal-conflict.yaml +2 -1
- package/rules/name-variant.yaml +4 -3
- package/rules/ngram-repetition.yaml +5 -0
- package/rules/no-doubled-joshi.yaml +5 -1
- package/rules/no-em-dash.yaml +4 -1
- package/rules/no-lead.yaml +6 -1
- package/rules/no-mixed-desumasu.yaml +5 -2
- package/rules/no-nakaguro-parallel.yaml +3 -0
- package/rules/nominalization.yaml +5 -1
- package/rules/nonstandard-word.yaml +80 -0
- package/rules/number-style-consistency.yaml +6 -1
- package/rules/number-without-unit.yaml +2 -1
- package/rules/numbering-gap.yaml +1 -1
- package/rules/numeral-sentence-start.yaml +56 -0
- package/rules/one-sentence-paragraph-run.yaml +5 -1
- package/rules/ordinal-suffix-mismatch.yaml +65 -0
- package/rules/orthographic-variant.yaml +4 -1
- package/rules/outside-announced-list.yaml +2 -1
- package/rules/oxford-comma-consistency.yaml +3 -1
- package/rules/padded-intro.yaml +7 -0
- package/rules/paragraph-length-variance.yaml +7 -1
- package/rules/paragraph-restatement.yaml +6 -1
- package/rules/percent-sum-mismatch.yaml +3 -1
- package/rules/person-consistency.yaml +4 -1
- package/rules/preamble-length.yaml +6 -0
- package/rules/preferred-term.yaml +1 -1
- package/rules/proper-noun-density.yaml +8 -1
- package/rules/quote-style-consistency.yaml +66 -0
- package/rules/ra-nuki.yaml +4 -1
- package/rules/range-notation-consistency.yaml +73 -0
- package/rules/redundant-expression.yaml +4 -1
- package/rules/reference-title-mismatch.yaml +4 -3
- package/rules/reference-topic-missing.yaml +2 -1
- package/rules/relative-date-mismatch.yaml +2 -1
- package/rules/repeated-conjunction.yaml +4 -1
- package/rules/repeated-sentence-ending.yaml +62 -0
- package/rules/repeated-sentence-head.yaml +4 -0
- package/rules/request-without-deadline.yaml +92 -0
- package/rules/required-sections.yaml +1 -1
- package/rules/requirement-modal.yaml +1 -1
- package/rules/requirement-smell.yaml +12 -4
- package/rules/rule-of-three.yaml +8 -1
- package/rules/sasete-itadaku.yaml +7 -1
- package/rules/section-length-uniformity.yaml +7 -1
- package/rules/sentence-initial-conjunction-run.yaml +5 -1
- package/rules/sentence-initial-so.yaml +6 -1
- package/rules/sentence-rhythm.yaml +8 -1
- package/rules/sentence-spacing-consistency.yaml +63 -0
- package/rules/share-over-hundred.yaml +65 -0
- package/rules/shell-prompt-mix.yaml +128 -0
- package/rules/space-before-punctuation.yaml +1 -1
- package/rules/spelling-consistency.yaml +3 -1
- package/rules/step-reference-missing.yaml +85 -0
- package/rules/step-statement-mix.yaml +76 -0
- package/rules/stock-transition.yaml +8 -1
- package/rules/stray-space.yaml +2 -2
- package/rules/summary-fact-mismatch.yaml +4 -1
- package/rules/table-header-variant.yaml +2 -1
- package/rules/table-row-overflow.yaml +90 -0
- package/rules/taigen-dome-in-prose.yaml +6 -0
- package/rules/tari-unpaired.yaml +58 -0
- package/rules/teki-overuse.yaml +2 -1
- package/rules/title-case-consistency.yaml +1 -1
- package/rules/title-length.yaml +7 -1
- package/rules/toc-heading-mismatch.yaml +136 -0
- package/rules/topic-predicate-distance.yaml +6 -1
- package/rules/total-mismatch.yaml +3 -1
- package/rules/translationese-density.yaml +4 -1
- package/rules/unbalanced-bracket.yaml +3 -3
- package/rules/unclosed-code-fence.yaml +107 -0
- package/rules/uncomparable-graded.yaml +65 -0
- package/rules/undefined-acronym.yaml +8 -1
- package/rules/unfilled-placeholder.yaml +1 -1
- package/rules/unit-mismatch.yaml +2 -1
- package/rules/unknown-word.yaml +4 -1
- package/rules/unqualified-superlative.yaml +6 -1
- package/rules/unrendered-emphasis.yaml +73 -0
- package/rules/unused-definition.yaml +4 -3
- package/rules/url-run-on.yaml +1 -1
- package/rules/use-before-definition.yaml +4 -3
- package/rules/vague-figure-reference.yaml +3 -1
- package/rules/vague-link-text.yaml +2 -1
- package/rules/vague-word-density.yaml +2 -1
- package/rules/very-adjective.yaml +5 -1
- package/rules/weasel-word.yaml +6 -1
- package/rules/which-that-consistency.yaml +64 -0
- package/rules/wordy-phrase.yaml +6 -1
- package/rules/yori-as-from.yaml +61 -0
- package/skills/chaff/SKILL.md +7 -7
- package/src/api.ts +13 -1
- package/src/cli-text.ts +22 -7
- package/src/cli.ts +8 -9
- package/src/command-help.ts +26 -0
- package/src/commands/eval.ts +2 -2
- package/src/commands/fix-plan.ts +13 -1
- package/src/commands/help.ts +8 -0
- package/src/commands/init-command.ts +13 -0
- package/src/commands/plugin-init.ts +150 -0
- package/src/commands/plugin-test.ts +111 -0
- package/src/config/load.ts +3 -0
- package/src/config/option-problems.ts +2 -2
- package/src/config/rule-problems.ts +2 -2
- package/src/custom/checked.ts +8 -0
- package/src/custom/dsl-fields.ts +102 -0
- package/src/custom/load.ts +2 -0
- package/src/custom/parse.ts +72 -50
- package/src/custom/problems.ts +16 -2
- package/src/detectors/amount-for-count.ts +90 -0
- package/src/detectors/blank-structure.ts +12 -4
- package/src/detectors/clause-shape.ts +199 -0
- package/src/detectors/code-fences.ts +113 -0
- package/src/detectors/cross-definitions.ts +49 -0
- package/src/detectors/cross-facts.ts +60 -0
- package/src/detectors/cross-registry/cross-doc-duplicate-definition.ts +4 -0
- package/src/detectors/cross-registry/cross-doc-fact-conflict.ts +4 -0
- package/src/detectors/currency-notation.ts +90 -0
- package/src/detectors/custom.ts +10 -2
- package/src/detectors/dangling-opener.ts +74 -0
- package/src/detectors/defined-term-form.ts +200 -0
- package/src/detectors/email-letter.ts +262 -0
- package/src/detectors/footnote-mismatch.ts +72 -0
- package/src/detectors/form-minority.ts +21 -0
- package/src/detectors/heading-end-mark.ts +82 -0
- package/src/detectors/impossible-date.ts +141 -0
- package/src/detectors/impossible-time.ts +151 -0
- package/src/detectors/link-shape.ts +70 -0
- package/src/detectors/list-marker.ts +60 -0
- package/src/detectors/mark-shape.ts +202 -0
- package/src/detectors/markdown-slip.ts +140 -0
- package/src/detectors/numeral-opener.ts +72 -0
- package/src/detectors/ordinal-suffix.ts +40 -0
- package/src/detectors/range-notation.ts +76 -0
- package/src/detectors/registry/adverb-polarity.ts +4 -0
- package/src/detectors/registry/amount-for-count.ts +4 -0
- package/src/detectors/registry/attachment-missing.ts +4 -0
- package/src/detectors/registry/bare-url-mix.ts +4 -0
- package/src/detectors/registry/caps-shouting.ts +4 -0
- package/src/detectors/registry/code-fence-language.ts +4 -0
- package/src/detectors/registry/currency-notation.ts +4 -0
- package/src/detectors/registry/dangling-opener.ts +4 -0
- package/src/detectors/registry/defined-name-repeated.ts +4 -0
- package/src/detectors/registry/defined-term-form.ts +4 -0
- package/src/detectors/registry/email-frame.ts +4 -0
- package/src/detectors/registry/email-subject.ts +4 -0
- package/src/detectors/registry/ending-run.ts +4 -0
- package/src/detectors/registry/footnote-mismatch.ts +4 -0
- package/src/detectors/registry/heading-command.ts +4 -0
- package/src/detectors/registry/heading-end-punctuation.ts +4 -0
- package/src/detectors/registry/hyphen-dash.ts +4 -0
- package/src/detectors/registry/image-file-name-alt.ts +4 -0
- package/src/detectors/registry/impossible-date.ts +4 -0
- package/src/detectors/registry/impossible-time.ts +4 -0
- package/src/detectors/registry/latin-abbreviation.ts +4 -0
- package/src/detectors/registry/link-text-url-mismatch.ts +4 -0
- package/src/detectors/registry/list-marker.ts +4 -0
- package/src/detectors/registry/mark-consistency.ts +4 -0
- package/src/detectors/registry/numeral-opener.ts +4 -0
- package/src/detectors/registry/ordinal-suffix.ts +4 -0
- package/src/detectors/registry/origin-particle.ts +4 -0
- package/src/detectors/registry/range-notation.ts +4 -0
- package/src/detectors/registry/relative-pronoun-mix.ts +4 -0
- package/src/detectors/registry/request-without-deadline.ts +4 -0
- package/src/detectors/registry/sentence-spacing.ts +4 -0
- package/src/detectors/registry/share-over-hundred.ts +4 -0
- package/src/detectors/registry/shell-prompt.ts +4 -0
- package/src/detectors/registry/step-reference.ts +4 -0
- package/src/detectors/registry/step-statement.ts +4 -0
- package/src/detectors/registry/table-row-overflow.ts +4 -0
- package/src/detectors/registry/toc-heading.ts +4 -0
- package/src/detectors/registry/unclosed-code-fence.ts +4 -0
- package/src/detectors/registry/unpaired-parallel.ts +4 -0
- package/src/detectors/registry/unrendered-emphasis.ts +4 -0
- package/src/detectors/relative-pronoun-mix.ts +79 -0
- package/src/detectors/request-owner.ts +141 -0
- package/src/detectors/share-over-hundred.ts +75 -0
- package/src/detectors/step-reference.ts +83 -0
- package/src/detectors/technical-docs.ts +230 -0
- package/src/detectors/toc-heading.ts +125 -0
- package/src/extension/pack-read.ts +128 -0
- package/src/extension/plugin-load.ts +49 -9
- package/src/extension/plugin-parse.ts +10 -3
- package/src/facts/address-values.ts +74 -0
- package/src/facts/cross-facts.ts +54 -0
- package/src/fix-plan/chosen-depth.ts +28 -0
- package/src/fix-plan/mode.ts +57 -11
- package/src/fix-plan/plan.ts +37 -5
- package/src/fix-plan/render.ts +24 -0
- package/src/fix-plan/text.ts +47 -10
- package/src/genre-load.ts +7 -4
- package/src/genre-parse.ts +19 -0
- package/src/init.ts +10 -10
- package/src/known-genres.ts +19 -0
- package/src/link-shape.ts +62 -0
- package/src/list-marks.ts +106 -0
- package/src/reasons.ts +16 -4
- package/src/render/explain.ts +14 -2
- package/src/render/friendly.ts +1 -1
- package/src/render/rules-json.ts +14 -3
- package/src/render/rules-table.ts +6 -6
- package/src/render/semantic.ts +2 -2
- package/src/render/suppressions.ts +2 -2
- package/src/rewrite-depth.ts +60 -0
- package/src/rule-fields.ts +123 -0
- package/src/rule-guide.ts +9 -0
- package/src/rule-load.ts +10 -4
- package/src/rule-offs.ts +50 -0
- package/src/structure/build.ts +21 -1
- package/src/structure/cross-definitions.ts +60 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { API_VERSION } from "../api.ts";
|
|
5
|
+
import { isInside } from "../custom/module-path.ts";
|
|
6
|
+
|
|
7
|
+
// A rule pack: a plugin written in YAML alone. Its folder holds a manifest (chaff-plugin.yaml, or a chaff field in
|
|
8
|
+
// package.json), rules/*.yaml, lexicons/<language>/<list>.yaml and styles/*.yaml. Read here into the shape a code
|
|
9
|
+
// plugin's default export has, so plugin-parse.ts checks a pack exactly as it checks a code plugin. Nothing is run.
|
|
10
|
+
|
|
11
|
+
export const PACK_MANIFEST = "chaff-plugin.yaml";
|
|
12
|
+
|
|
13
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
|
|
15
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? typeof value;
|
|
16
|
+
|
|
17
|
+
const firstLine = (error: unknown): string => (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
18
|
+
|
|
19
|
+
/** A YAML file, or why it cannot be read, naming it as the pack's own path (rules/no-tbd.yaml). */
|
|
20
|
+
const readYaml = (dir: string, path: string): { readonly value: unknown } | { readonly message: string } => {
|
|
21
|
+
try {
|
|
22
|
+
// A link inside the pack that points out of it would read a file the pack does not hold.
|
|
23
|
+
if (!isInside(realpathSync(dir), realpathSync(join(dir, path)))) return { message: `${path}: it links to a file outside the pack` };
|
|
24
|
+
return { value: parse(readFileSync(join(dir, path), "utf8")) };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return { message: `${path}: ${firstLine(error)}` };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const yamlFilesIn = (dir: string, folder: string): string[] => {
|
|
31
|
+
const full = join(dir, folder);
|
|
32
|
+
if (!existsSync(full) || !statSync(full).isDirectory()) return [];
|
|
33
|
+
return readdirSync(full)
|
|
34
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"))
|
|
35
|
+
.toSorted((left, right) => left.localeCompare(right, "en"))
|
|
36
|
+
.map((file) => `${folder}/${file}`);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const foldersIn = (dir: string, folder: string): string[] => {
|
|
40
|
+
const full = join(dir, folder);
|
|
41
|
+
if (!existsSync(full) || !statSync(full).isDirectory()) return [];
|
|
42
|
+
return readdirSync(full)
|
|
43
|
+
.filter((name) => statSync(join(full, name)).isDirectory())
|
|
44
|
+
.toSorted((left, right) => left.localeCompare(right, "en"));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const packageJsonOf = (dir: string): Record<string, unknown> | undefined => {
|
|
48
|
+
const read = existsSync(join(dir, "package.json")) ? readYaml(dir, "package.json") : undefined;
|
|
49
|
+
return read !== undefined && "value" in read && isRecord(read.value) ? read.value : undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** The name in the folder's package.json, if it has one. */
|
|
53
|
+
export const packageNameOf = (dir: string): string | undefined => {
|
|
54
|
+
const name = packageJsonOf(dir)?.["name"];
|
|
55
|
+
return typeof name === "string" ? name : undefined;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The manifest of a pack folder: chaff-plugin.yaml, else package.json's chaff field. undefined when the folder is not
|
|
60
|
+
* a pack (a code plugin, loaded by importing it). A manifest that cannot be read is reported, not taken for "not a pack".
|
|
61
|
+
*/
|
|
62
|
+
export const packManifestOf = (dir: string): { readonly manifest: Record<string, unknown> } | { readonly message: string } | undefined => {
|
|
63
|
+
if (existsSync(join(dir, PACK_MANIFEST))) {
|
|
64
|
+
const read = readYaml(dir, PACK_MANIFEST);
|
|
65
|
+
if ("message" in read) return read;
|
|
66
|
+
// An empty manifest only marks the folder; anything else must be a map.
|
|
67
|
+
if (read.value === null || read.value === undefined) return { manifest: {} };
|
|
68
|
+
return isRecord(read.value) ? { manifest: read.value } : { message: `${PACK_MANIFEST}: write a map (apiVersion: 1), not ${printed(read.value)}` };
|
|
69
|
+
}
|
|
70
|
+
const chaff = packageJsonOf(dir)?.["chaff"];
|
|
71
|
+
if (chaff === undefined) return undefined;
|
|
72
|
+
return isRecord(chaff) ? { manifest: chaff } : { message: `package.json: chaff must be a map ({ "apiVersion": 1 }), not ${printed(chaff)}` };
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type Read<T> = { readonly value: T } | { readonly message: string };
|
|
76
|
+
|
|
77
|
+
/** Every file's value in the order of the files, or the first that cannot be read. */
|
|
78
|
+
const readAll = (dir: string, paths: readonly string[]): Read<unknown[]> =>
|
|
79
|
+
paths.reduce<Read<unknown[]>>(
|
|
80
|
+
(done, path) => {
|
|
81
|
+
if ("message" in done) return done;
|
|
82
|
+
const read = readYaml(dir, path);
|
|
83
|
+
return "message" in read ? read : { value: [...done.value, read.value] };
|
|
84
|
+
},
|
|
85
|
+
{ value: [] },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/** rules/*.yaml: each file holds one rule, or a list of them. */
|
|
89
|
+
const rulesOf = (dir: string): Read<unknown[]> => {
|
|
90
|
+
const read = readAll(dir, yamlFilesIn(dir, "rules"));
|
|
91
|
+
return "message" in read ? read : { value: read.value.flatMap((value) => (Array.isArray(value) ? value.map((rule: unknown) => rule) : [value])) };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** lexicons/<language>/<list>.yaml, as a code plugin writes its lexicons: { list: { language: entries } }. */
|
|
95
|
+
const lexiconsOf = (dir: string): Read<Record<string, Record<string, unknown>>> =>
|
|
96
|
+
foldersIn(dir, "lexicons").reduce<Read<Record<string, Record<string, unknown>>>>(
|
|
97
|
+
(done, language) => {
|
|
98
|
+
if ("message" in done) return done;
|
|
99
|
+
const paths = yamlFilesIn(dir, `lexicons/${language}`);
|
|
100
|
+
const read = readAll(dir, paths);
|
|
101
|
+
if ("message" in read) return read;
|
|
102
|
+
const merged = paths.reduce<Record<string, Record<string, unknown>>>((acc, path, index) => {
|
|
103
|
+
const list = basename(path).replace(/\.ya?ml$/u, "");
|
|
104
|
+
return { ...acc, [list]: { ...acc[list], [language]: read.value[index] } };
|
|
105
|
+
}, done.value);
|
|
106
|
+
return { value: merged };
|
|
107
|
+
},
|
|
108
|
+
{ value: {} },
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/** The pack as a plugin's default export: its name and API version from the manifest, its rules, lexicons and styles from its folders. */
|
|
112
|
+
export const readPack = (dir: string, manifest: Readonly<Record<string, unknown>>, expectedName: string | undefined): Read<Record<string, unknown>> => {
|
|
113
|
+
const rules = rulesOf(dir);
|
|
114
|
+
if ("message" in rules) return rules;
|
|
115
|
+
const lexicons = lexiconsOf(dir);
|
|
116
|
+
if ("message" in lexicons) return lexicons;
|
|
117
|
+
const styles = readAll(dir, yamlFilesIn(dir, "styles"));
|
|
118
|
+
if ("message" in styles) return styles;
|
|
119
|
+
return {
|
|
120
|
+
value: {
|
|
121
|
+
apiVersion: manifest["apiVersion"] ?? API_VERSION,
|
|
122
|
+
name: manifest["name"] ?? expectedName,
|
|
123
|
+
rules: rules.value,
|
|
124
|
+
lexicons: lexicons.value,
|
|
125
|
+
styles: styles.value,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { CONFIG_FILE, type Config } from "../config/load.ts";
|
|
@@ -7,6 +8,7 @@ import { importDefault } from "./import-default.ts";
|
|
|
7
8
|
import { staysInside } from "./real-path.ts";
|
|
8
9
|
import { isPluginPath, pluginNameOfPackage } from "./plugin-name.ts";
|
|
9
10
|
import { parsePlugin, type ParsedPlugin, type PluginOrigin, type PluginProblem } from "./plugin-parse.ts";
|
|
11
|
+
import { PACK_MANIFEST, packManifestOf, packageNameOf, readPack } from "./pack-read.ts";
|
|
10
12
|
|
|
11
13
|
// The plugins chaff.yaml lists under plugins:, found and loaded. A package is found from the folder chaff.yaml is in,
|
|
12
14
|
// as Node finds it (node_modules); a path is the project's own plugin and, like a module rule, may not leave that
|
|
@@ -14,7 +16,8 @@ import { parsePlugin, type ParsedPlugin, type PluginOrigin, type PluginProblem }
|
|
|
14
16
|
|
|
15
17
|
export type LoadedPlugins = { readonly plugins: readonly ParsedPlugin[]; readonly problems: readonly PluginProblem[] };
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
/** pack: the folder of a rule pack (YAML only), which is read rather than imported. */
|
|
20
|
+
type Located = { readonly origin: PluginOrigin; readonly pack?: string } | { readonly problem: PluginProblem };
|
|
18
21
|
|
|
19
22
|
const problemOf = (kind: Exclude<PluginProblem["kind"], "rule">, plugin: string, detail = ""): { problem: PluginProblem } => ({
|
|
20
23
|
problem: { kind, plugin, detail },
|
|
@@ -29,24 +32,61 @@ const resolveFrom = (baseDir: string, specifier: string): string | undefined =>
|
|
|
29
32
|
}
|
|
30
33
|
};
|
|
31
34
|
|
|
35
|
+
const isFolder = (path: string): boolean => existsSync(path) && statSync(path).isDirectory();
|
|
36
|
+
|
|
37
|
+
/** A folder that is a rule pack: it has a manifest. A folder that is a code plugin is imported as Node imports it. */
|
|
38
|
+
const isPack = (dir: string): boolean => packManifestOf(dir) !== undefined;
|
|
39
|
+
|
|
40
|
+
/** The folder Node would find package name in from the folder chaff.yaml is in, whatever the package exports. */
|
|
41
|
+
const packageFolder = (baseDir: string, name: string): string | undefined =>
|
|
42
|
+
(createRequire(join(baseDir, CONFIG_FILE)).resolve.paths(name) ?? [])
|
|
43
|
+
.map((nodeModules) => join(nodeModules, name))
|
|
44
|
+
.find((dir) => existsSync(join(dir, "package.json")));
|
|
45
|
+
|
|
46
|
+
const packAt = (written: string, dir: string, expectedName: string | undefined): Located => ({
|
|
47
|
+
origin: { written, file: join(dir, PACK_MANIFEST), expectedName },
|
|
48
|
+
pack: dir,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** A pack of the project's own takes its name from its package.json (chaff-plugin-foo is foo) when its manifest does not give one. */
|
|
52
|
+
const localPackName = (dir: string): string | undefined => {
|
|
53
|
+
const name = packageNameOf(dir);
|
|
54
|
+
return name === undefined ? undefined : pluginNameOfPackage(name);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** A plugin of the project's own, by its path: a pack folder, or a file (or folder) Node imports. Neither may leave the project. */
|
|
58
|
+
const locatePath = (written: string, baseDir: string): Located => {
|
|
59
|
+
const path = modulePathOf(written, baseDir);
|
|
60
|
+
if ("refusal" in path) return problemOf("outside", written);
|
|
61
|
+
if (isFolder(path.file) && isPack(path.file))
|
|
62
|
+
return staysInside(written, path.file, baseDir) ? packAt(written, path.file, localPackName(path.file)) : problemOf("outside", written);
|
|
63
|
+
const file = resolveFrom(baseDir, path.file);
|
|
64
|
+
if (file === undefined) return problemOf("not-found", written);
|
|
65
|
+
return staysInside(written, file, baseDir) ? { origin: { written, file, expectedName: undefined } } : problemOf("outside", written);
|
|
66
|
+
};
|
|
67
|
+
|
|
32
68
|
const locate = (written: string, baseDir: string): Located => {
|
|
33
|
-
if (isPluginPath(written))
|
|
34
|
-
const path = modulePathOf(written, baseDir);
|
|
35
|
-
if ("refusal" in path) return problemOf("outside", written);
|
|
36
|
-
const file = resolveFrom(baseDir, path.file);
|
|
37
|
-
if (file === undefined) return problemOf("not-found", written);
|
|
38
|
-
return staysInside(written, file, baseDir) ? { origin: { written, file, expectedName: undefined } } : problemOf("outside", written);
|
|
39
|
-
}
|
|
69
|
+
if (isPluginPath(written)) return locatePath(written, baseDir);
|
|
40
70
|
const expectedName = pluginNameOfPackage(written);
|
|
41
71
|
if (expectedName === undefined) return problemOf("bad-specifier", written);
|
|
72
|
+
const folder = packageFolder(baseDir, written);
|
|
73
|
+
if (folder !== undefined && isPack(folder)) return packAt(written, folder, expectedName);
|
|
42
74
|
const file = resolveFrom(baseDir, written);
|
|
43
75
|
return file === undefined ? problemOf("not-found", written) : { origin: { written, file, expectedName } };
|
|
44
76
|
};
|
|
45
77
|
|
|
78
|
+
/** A rule pack read into what a code plugin's default export would be. Nothing in it runs. */
|
|
79
|
+
const readPackAt = (dir: string, expectedName: string | undefined): { readonly exported: unknown } | { readonly message: string } => {
|
|
80
|
+
const manifest = packManifestOf(dir);
|
|
81
|
+
if (manifest !== undefined && "message" in manifest) return manifest;
|
|
82
|
+
const read = readPack(dir, manifest?.manifest ?? {}, expectedName);
|
|
83
|
+
return "message" in read ? read : { exported: read.value };
|
|
84
|
+
};
|
|
85
|
+
|
|
46
86
|
const loadPlugin = async (written: string, baseDir: string, useFor: readonly string[]): Promise<LoadedPlugins> => {
|
|
47
87
|
const located = locate(written, baseDir);
|
|
48
88
|
if ("problem" in located) return { plugins: [], problems: [located.problem] };
|
|
49
|
-
const imported = await importDefault(located.origin.file);
|
|
89
|
+
const imported = located.pack === undefined ? await importDefault(located.origin.file) : readPackAt(located.pack, located.origin.expectedName);
|
|
50
90
|
if ("message" in imported) return { plugins: [], problems: [problemOf("import-failed", written, imported.message).problem] };
|
|
51
91
|
const read = parsePlugin(imported.exported, located.origin, useFor);
|
|
52
92
|
return { plugins: read.plugin === undefined ? [] : [read.plugin], problems: read.problems };
|
|
@@ -6,6 +6,7 @@ import { styleOf, type StyleDefinition } from "../style-parse.ts";
|
|
|
6
6
|
import type { UntrustedDetector } from "./module-detector.ts";
|
|
7
7
|
import { describeValue } from "./returned-findings.ts";
|
|
8
8
|
import { isPluginName } from "./plugin-name.ts";
|
|
9
|
+
import { knownGenres } from "../known-genres.ts";
|
|
9
10
|
|
|
10
11
|
// What a plugin package exports (api.ts PluginSpec), read into what chaff runs: its rules with ids under the plugin's
|
|
11
12
|
// name, their detectors, its word lists and its house styles. A rule is read exactly like a rule in custom_rules, with
|
|
@@ -109,6 +110,7 @@ const rulesOf = (raw: unknown, context: RulesContext): Rules => {
|
|
|
109
110
|
const parsed = parseCustomRules(Array.isArray(raw) ? raw.map((entry: unknown) => asCustomRule(entry, origin.file)) : raw, {
|
|
110
111
|
builtIn: new Set(),
|
|
111
112
|
useFor: context.useFor,
|
|
113
|
+
genres: knownGenres(),
|
|
112
114
|
baseDir: dirname(origin.file),
|
|
113
115
|
});
|
|
114
116
|
const detectors = detectorsByWrittenId(Array.isArray(raw) ? raw : []);
|
|
@@ -134,14 +136,19 @@ const rulesOf = (raw: unknown, context: RulesContext): Rules => {
|
|
|
134
136
|
const lexiconEntryOf = (value: unknown): LexiconEntry | undefined => {
|
|
135
137
|
if (typeof value === "string") return value.trim() === "" ? undefined : { pattern: value.trim() };
|
|
136
138
|
if (!isRecord(value) || typeof value["pattern"] !== "string" || value["pattern"].trim() === "") return undefined;
|
|
137
|
-
const insteadOf = value["instead_of"];
|
|
138
|
-
return {
|
|
139
|
+
const [insteadOf, rewrite] = [value["instead_of"], value["rewrite"]];
|
|
140
|
+
return {
|
|
141
|
+
pattern: value["pattern"].trim(),
|
|
142
|
+
...(typeof insteadOf === "string" && insteadOf.trim() !== "" ? { instead_of: insteadOf.trim() } : {}),
|
|
143
|
+
...(typeof rewrite === "string" && rewrite.trim() !== "" ? { rewrite: rewrite.trim() } : {}),
|
|
144
|
+
};
|
|
139
145
|
};
|
|
140
146
|
|
|
141
147
|
const lexiconOf = (words: unknown): Lexicon | undefined => {
|
|
142
148
|
const entries = Array.isArray(words) ? words.map(lexiconEntryOf) : [undefined];
|
|
143
149
|
const readable = entries.flatMap((entry) => (entry === undefined ? [] : [entry]));
|
|
144
|
-
|
|
150
|
+
// An empty list would leave its rules running and finding nothing, which reads as a clean document.
|
|
151
|
+
return readable.length === entries.length && readable.length > 0 ? readable : undefined;
|
|
145
152
|
};
|
|
146
153
|
|
|
147
154
|
type ListInLanguage = { readonly language: string; readonly list: string; readonly lexicon: Lexicon };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Span } from "../plugin.ts";
|
|
2
|
+
|
|
3
|
+
// Two kinds of value a docs site repeats from page to page, read by scanning rather than by a backtracking pattern:
|
|
4
|
+
// an email address (help@example.com) and a version of three parts or more, or one written with v (2.4.1, v3).
|
|
5
|
+
|
|
6
|
+
const LOCAL_CHAR = /[\p{L}\p{N}._%+-]/u;
|
|
7
|
+
const DOMAIN_CHAR = /[\p{L}\p{N}.-]/u;
|
|
8
|
+
const DOT = ".";
|
|
9
|
+
|
|
10
|
+
/** The longest address there is (RFC 5321), so a scan from one @ never reads the whole document. */
|
|
11
|
+
const MAX_ADDRESS = 254;
|
|
12
|
+
|
|
13
|
+
/** Where the run of isPart characters that ends at from begins (UTF-16 units, like the offsets). */
|
|
14
|
+
const extendLeft = (source: string, from: number, isPart: RegExp): number => {
|
|
15
|
+
const windowStart = Math.max(0, from - MAX_ADDRESS);
|
|
16
|
+
const stop = source
|
|
17
|
+
.slice(windowStart, from)
|
|
18
|
+
.split("")
|
|
19
|
+
.findLastIndex((char) => !isPart.test(char));
|
|
20
|
+
return windowStart + stop + 1;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Where the run of isPart characters that starts at from ends. */
|
|
24
|
+
const extendRight = (source: string, from: number, isPart: RegExp): number => {
|
|
25
|
+
const stop = source
|
|
26
|
+
.slice(from, from + MAX_ADDRESS)
|
|
27
|
+
.split("")
|
|
28
|
+
.findIndex((char) => !isPart.test(char));
|
|
29
|
+
return stop === -1 ? Math.min(source.length, from + MAX_ADDRESS) : from + stop;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** A sentence's full stop after an address or a version is not part of it. */
|
|
33
|
+
const withoutTrailingDots = (text: string): string => text.slice(0, text.split("").findLastIndex((char) => char !== DOT) + 1);
|
|
34
|
+
|
|
35
|
+
/** The address around one @: a local part, and the labels of the domain up to the first empty one, two or more. */
|
|
36
|
+
const addressAt = (source: string, at: number): Span | undefined => {
|
|
37
|
+
const start = extendLeft(source, at, LOCAL_CHAR);
|
|
38
|
+
const written = source.slice(at + 1, extendRight(source, at + 1, DOMAIN_CHAR)).split(DOT);
|
|
39
|
+
const empty = written.indexOf("");
|
|
40
|
+
const labels = empty === -1 ? written : written.slice(0, empty);
|
|
41
|
+
if (start === at || labels.length < 2) return undefined;
|
|
42
|
+
return { start, end: at + 1 + labels.join(DOT).length };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The addresses in the text, left to right. An @ inside the address before it starts none. */
|
|
46
|
+
export const emailSpans = (source: string): Span[] =>
|
|
47
|
+
[...source.matchAll(/@/gu)].reduce<Span[]>((found, match) => {
|
|
48
|
+
const span = addressAt(source, match.index);
|
|
49
|
+
const previous = found.at(-1);
|
|
50
|
+
return span === undefined || (previous !== undefined && span.start < previous.end) ? found : [...found, span];
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
/** A run of digits and dots, with a v before it or not, that starts a word. */
|
|
54
|
+
const VERSION_RUN = /(?<![\p{L}\p{N}.])v?\d[\d.]*/gu;
|
|
55
|
+
const WORD_CHAR = /[\p{L}\p{N}]/u;
|
|
56
|
+
const MIN_PARTS = 3;
|
|
57
|
+
const MAX_PARTS = 4;
|
|
58
|
+
const DIGITS = /^\d+$/u;
|
|
59
|
+
|
|
60
|
+
/** A version: v and one part or more (v3, v2.1), or three or four parts without it (2.4.1). Not a run that a word continues. */
|
|
61
|
+
const versionIn = (written: string, next: string): string | undefined => {
|
|
62
|
+
const run = withoutTrailingDots(written);
|
|
63
|
+
const prefixed = run.startsWith("v");
|
|
64
|
+
const parts = (prefixed ? run.slice(1) : run).split(DOT);
|
|
65
|
+
if (WORD_CHAR.test(next) && run === written) return undefined;
|
|
66
|
+
if (!parts.every((part) => DIGITS.test(part))) return undefined;
|
|
67
|
+
return prefixed || (parts.length >= MIN_PARTS && parts.length <= MAX_PARTS) ? run : undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const versionSpans = (source: string): Span[] =>
|
|
71
|
+
[...source.matchAll(VERSION_RUN)].flatMap((match) => {
|
|
72
|
+
const run = versionIn(match[0], source.charAt(match.index + match[0].length));
|
|
73
|
+
return run === undefined ? [] : [{ start: match.index, end: match.index + run.length }];
|
|
74
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { comparable, sameValue, type FactValue } from "./fact-values.ts";
|
|
2
|
+
import type { ScopedFact } from "./fact-scope.ts";
|
|
3
|
+
import { tallyVotes, type Vote } from "../cross-votes.ts";
|
|
4
|
+
|
|
5
|
+
// The same labelled fact given different values in different files of one run ("Monthly fee: $12" in one page, "$15" in
|
|
6
|
+
// another). Pure: the facts come in already read from each file. Within a file, fact-conflict compares section by section.
|
|
7
|
+
|
|
8
|
+
/** A file's labelled facts, by its path. */
|
|
9
|
+
export type FileFacts = { readonly path: string; readonly facts: readonly ScopedFact[] };
|
|
10
|
+
|
|
11
|
+
/** A file whose value for a label differs from the value most files give it: its first fact, and one file of the usual value. */
|
|
12
|
+
export type FactOutOfStep = { readonly path: string; readonly fact: ScopedFact; readonly usual: Vote<ScopedFact>; readonly files: number };
|
|
13
|
+
|
|
14
|
+
/** Two values the files can be said to agree or disagree on: comparable, and the same. */
|
|
15
|
+
const sameFact = (left: ScopedFact, right: ScopedFact): boolean => sameValue(left.value, right.value);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A file's vote for a label: its first fact, when every fact of the label in the file gives the same value. A file giving
|
|
19
|
+
* the label two values is either a list (a fee per plan) or a conflict within the file, which fact-conflict reports.
|
|
20
|
+
*/
|
|
21
|
+
const voteOf = (file: FileFacts, key: string): Vote<ScopedFact>[] => {
|
|
22
|
+
const facts = file.facts.filter((fact) => fact.key === key && !fact.record);
|
|
23
|
+
const [first] = facts;
|
|
24
|
+
if (first === undefined) return [];
|
|
25
|
+
return facts.every((fact) => comparable(first.value, fact.value) && sameFact(first, fact)) ? [{ path: file.path, value: first }] : [];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** The votes split into groups whose values can be compared at all (one unit, overlapping date precision). */
|
|
29
|
+
const comparableGroups = (votes: readonly Vote<ScopedFact>[]): Vote<ScopedFact>[][] =>
|
|
30
|
+
votes.reduce<Vote<ScopedFact>[][]>((groups, vote) => {
|
|
31
|
+
const home = groups.find((group) => group[0] !== undefined && comparable(group[0].value.value, vote.value.value));
|
|
32
|
+
if (home === undefined) groups.push([vote]);
|
|
33
|
+
else home.push(vote);
|
|
34
|
+
return groups;
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
/** Two values across the files, as with fact-conflict in one section: three or more is read as a list, and left alone. */
|
|
38
|
+
const MAX_VALUES = 2;
|
|
39
|
+
|
|
40
|
+
const outOfStep = (votes: readonly Vote<ScopedFact>[]): FactOutOfStep[] => {
|
|
41
|
+
const tally = tallyVotes(votes, sameFact);
|
|
42
|
+
const [usual] = tally?.usual ?? [];
|
|
43
|
+
if (tally === undefined || usual === undefined || tally.values !== MAX_VALUES) return [];
|
|
44
|
+
return tally.odd.map((vote) => ({ path: vote.path, fact: vote.value, usual, files: tally.usual.length }));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** The files whose value for a label differs from the value most files of the run give it. */
|
|
48
|
+
export const factsOutOfStep = (files: readonly FileFacts[]): FactOutOfStep[] => {
|
|
49
|
+
const keys = [...new Set(files.flatMap((file) => file.facts.map((fact) => fact.key)))];
|
|
50
|
+
return keys.flatMap((key) => comparableGroups(files.flatMap((file) => voteOf(file, key))).flatMap(outOfStep));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** The value as written, for a message. */
|
|
54
|
+
export const writtenValue = (source: string, value: FactValue): string => source.slice(value.start, value.end);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readDepth, unknownDepthSentence } from "../rewrite-depth.ts";
|
|
2
|
+
import type { UiLanguage } from "../ui.ts";
|
|
3
|
+
import type { ChosenDepth } from "./plan.ts";
|
|
4
|
+
|
|
5
|
+
// The depth a fix plan goes to: --depth, else chaff.yaml's fix_plan.depth, else none (chaff recommends one). Pure.
|
|
6
|
+
|
|
7
|
+
export type DepthChoice = { readonly chosen: ChosenDepth | undefined } | { readonly problem: string };
|
|
8
|
+
|
|
9
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
|
|
11
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? String(value);
|
|
12
|
+
|
|
13
|
+
/** fix_plan as chaff.yaml writes it: nothing, or a map whose depth is a depth. */
|
|
14
|
+
const configured = (fixPlan: unknown, ui: UiLanguage): DepthChoice => {
|
|
15
|
+
if (fixPlan === undefined || fixPlan === null) return { chosen: undefined };
|
|
16
|
+
if (!isRecord(fixPlan)) return { problem: unknownDepthSentence("chaff.yaml fix_plan", printed(fixPlan), ui) };
|
|
17
|
+
const read = readDepth(fixPlan["depth"]);
|
|
18
|
+
if ("unknown" in read) return { problem: unknownDepthSentence("chaff.yaml fix_plan.depth", read.unknown, ui) };
|
|
19
|
+
return { chosen: read.depth === undefined ? undefined : { depth: read.depth, from: "config" } };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** flag: --depth's value, or undefined when it is not given. An empty value is a value, and not a depth. */
|
|
23
|
+
export const chosenDepthOf = (flag: string | undefined, fixPlan: unknown, ui: UiLanguage): DepthChoice => {
|
|
24
|
+
if (flag === undefined) return configured(fixPlan, ui);
|
|
25
|
+
const read = readDepth(flag);
|
|
26
|
+
if ("depth" in read && read.depth !== undefined) return { chosen: { depth: read.depth, from: "flag" } };
|
|
27
|
+
return { problem: unknownDepthSentence("--depth", flag, ui) };
|
|
28
|
+
};
|
package/src/fix-plan/mode.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import { depthIncludes, type RewriteDepth } from "../rewrite-depth.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Which
|
|
4
|
+
* Which way to rewrite: the guide's Light, Bold and Full, and Register. Each is a rewrite depth (rewrite-depth.ts):
|
|
5
|
+
* Light and Bold keep the structure (light), Full reorganises it (structure), Register converts the style (register).
|
|
6
|
+
* chaff recommends a way from what it found; --depth or chaff.yaml's fix_plan.depth chooses the depth instead.
|
|
3
7
|
* A request that says "from scratch" also means Full; chaff cannot read the request, so the plan says so in words.
|
|
4
8
|
*/
|
|
5
|
-
export type FixMode = "none" | "light" | "bold" | "full";
|
|
9
|
+
export type FixMode = "none" | "light" | "bold" | "full" | "register";
|
|
6
10
|
|
|
7
|
-
/** Why the mode was chosen, so the plan can say it. */
|
|
8
|
-
export type ModeReason = "nothing-found" | "composite" | "structure" | "genre" | "signals" | "spots";
|
|
11
|
+
/** Why the mode was chosen, so the plan can say it. chosen: the depth was given; depth-limit: chaff would go deeper than the depth given. */
|
|
12
|
+
export type ModeReason = "nothing-found" | "all-deeper" | "composite" | "structure" | "genre" | "signals" | "spots" | "chosen" | "depth-limit";
|
|
9
13
|
|
|
10
|
-
export type ModeChoice = {
|
|
14
|
+
export type ModeChoice = {
|
|
15
|
+
readonly mode: FixMode;
|
|
16
|
+
readonly reason: ModeReason;
|
|
17
|
+
/** How deep the rewrite goes. None when there is nothing to fix. */
|
|
18
|
+
readonly depth: RewriteDepth | undefined;
|
|
19
|
+
};
|
|
11
20
|
|
|
12
21
|
export type ModeInput = {
|
|
13
22
|
readonly genre: string;
|
|
@@ -17,10 +26,25 @@ export type ModeInput = {
|
|
|
17
26
|
readonly signalRules: ReadonlySet<string>;
|
|
18
27
|
/** The structure score (measures past 90% of human articles) and the count at which the outline itself is the problem. */
|
|
19
28
|
readonly structure?: { readonly score: number; readonly limit: number };
|
|
29
|
+
/** The depth --depth or chaff.yaml chose. Without one, chaff recommends. */
|
|
30
|
+
readonly chosen?: RewriteDepth | undefined;
|
|
31
|
+
/** How many fired rules the chosen depth sets apart; they are not in firedRules. */
|
|
32
|
+
readonly setApart?: number;
|
|
20
33
|
};
|
|
21
34
|
|
|
22
35
|
export const COMPOSITE_RULE = "ai-generated-composite";
|
|
23
36
|
|
|
37
|
+
const DEPTH_OF: Readonly<Record<FixMode, RewriteDepth | undefined>> = {
|
|
38
|
+
none: undefined,
|
|
39
|
+
light: "light",
|
|
40
|
+
bold: "light",
|
|
41
|
+
full: "structure",
|
|
42
|
+
register: "register",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The way that goes exactly as deep as a chosen depth, beyond light, where chaff's recommendation picks Light or Bold. */
|
|
46
|
+
const MODE_AT: Readonly<Record<Exclude<RewriteDepth, "light">, FixMode>> = { structure: "full", register: "register" };
|
|
47
|
+
|
|
24
48
|
/** Two document-wide signals mean the prose of the sections is the problem, not a spot or two. */
|
|
25
49
|
const MIN_SIGNALS_FOR_BOLD = 2;
|
|
26
50
|
|
|
@@ -30,11 +54,33 @@ const isFreeFormGenre = (genre: string): boolean => genre.startsWith("blog/") ||
|
|
|
30
54
|
/** An outline far from human articles is rewritten from the outline, in any genre and whether or not a rule fired. */
|
|
31
55
|
const hasStructureToFix = (input: ModeInput): boolean => input.structure !== undefined && input.structure.score >= input.structure.limit;
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (isFreeFormGenre(input.genre)) return { mode: "full", reason: "genre" };
|
|
57
|
+
const choice = (mode: FixMode, reason: ModeReason): ModeChoice => ({ mode, reason, depth: DEPTH_OF[mode] });
|
|
58
|
+
|
|
59
|
+
/** Two document-wide signals mean Bold, else Light: the deepest way that keeps the structure. */
|
|
60
|
+
const lightOrBold = (input: ModeInput): ModeChoice => {
|
|
38
61
|
const signals = [...input.firedRules].filter((rule) => input.signalRules.has(rule)).length;
|
|
39
|
-
return signals >= MIN_SIGNALS_FOR_BOLD ?
|
|
62
|
+
return signals >= MIN_SIGNALS_FOR_BOLD ? choice("bold", "signals") : choice("light", "spots");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** What chaff recommends from its findings alone. */
|
|
66
|
+
const recommended = (input: ModeInput): ModeChoice => {
|
|
67
|
+
if (input.firedRules.has(COMPOSITE_RULE)) return choice("full", "composite");
|
|
68
|
+
if (hasStructureToFix(input)) return choice("full", "structure");
|
|
69
|
+
if (input.firedRules.size === 0) return choice("none", (input.setApart ?? 0) > 0 ? "all-deeper" : "nothing-found");
|
|
70
|
+
if (isFreeFormGenre(input.genre)) return choice("full", "genre");
|
|
71
|
+
return lightOrBold(input);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** A chosen depth sets how deep the rewrite goes. At light, chaff still picks Light or Bold, and says when it would go deeper. */
|
|
75
|
+
const atChosenDepth = (input: ModeInput, chosen: RewriteDepth, ownChoice: ModeChoice): ModeChoice => {
|
|
76
|
+
if (chosen !== "light") return ownChoice.depth === chosen ? ownChoice : choice(MODE_AT[chosen], "chosen");
|
|
77
|
+
if (ownChoice.depth !== undefined && depthIncludes(chosen, ownChoice.depth)) return ownChoice;
|
|
78
|
+
const within = lightOrBold(input);
|
|
79
|
+
return { ...within, reason: "depth-limit" };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const recommendMode = (input: ModeInput): ModeChoice => {
|
|
83
|
+
const ownChoice = recommended(input);
|
|
84
|
+
if (input.chosen === undefined || ownChoice.mode === "none") return ownChoice;
|
|
85
|
+
return atChosenDepth(input, input.chosen, ownChoice);
|
|
40
86
|
};
|
package/src/fix-plan/plan.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { splitWords } from "../detectors/word-list.ts";
|
|
|
7
7
|
import { COMPOSITE_RULE, recommendMode, type ModeChoice } from "./mode.ts";
|
|
8
8
|
import type { StructureScore } from "../structure-shape/score.ts";
|
|
9
9
|
import { structureTargetsOf, type StructureTarget } from "./structure-targets.ts";
|
|
10
|
+
import { DEFAULT_DEPTH, depthIncludes, type RewriteDepth } from "../rewrite-depth.ts";
|
|
10
11
|
|
|
11
12
|
/** One flagged spot, where it is and what chaff said about it. */
|
|
12
13
|
export type PlanSpot = { readonly line: number; readonly column: number; readonly quote: string; readonly message: string };
|
|
@@ -18,6 +19,8 @@ export type PhraseHint = { readonly phrase: string; readonly rewrite: string };
|
|
|
18
19
|
export type RulePlan = {
|
|
19
20
|
readonly rule: string;
|
|
20
21
|
readonly name: string;
|
|
22
|
+
/** How deep the rule's direction reaches (rewrite.depth); light when the rule does not say. */
|
|
23
|
+
readonly depth: RewriteDepth;
|
|
21
24
|
/** The rule's rewrite direction, or its how_to_fix when it has no rewrite block. */
|
|
22
25
|
readonly direction: string;
|
|
23
26
|
readonly keep: readonly string[];
|
|
@@ -31,6 +34,12 @@ export type RulePlan = {
|
|
|
31
34
|
/** A rule that measures the whole document, with what it said. */
|
|
32
35
|
type DocumentSignal = { readonly rule: string; readonly message: string };
|
|
33
36
|
|
|
37
|
+
/** A rule that fired but reaches deeper than the chosen depth: named, not planned, so its silence is not read as fixed. */
|
|
38
|
+
export type DeeperRule = { readonly rule: string; readonly name: string; readonly depth: RewriteDepth; readonly spots: number };
|
|
39
|
+
|
|
40
|
+
/** The depth chosen for the plan, and where: --depth on the command line, or fix_plan.depth in chaff.yaml. */
|
|
41
|
+
export type ChosenDepth = { readonly depth: RewriteDepth; readonly from: "flag" | "config" };
|
|
42
|
+
|
|
34
43
|
export type FixPlan = {
|
|
35
44
|
readonly path: string;
|
|
36
45
|
/** Where the plan suggests saving the rewrite, so the check commands can be pasted as they are. */
|
|
@@ -39,6 +48,8 @@ export type FixPlan = {
|
|
|
39
48
|
readonly genre: string;
|
|
40
49
|
readonly experimental: boolean;
|
|
41
50
|
readonly mode: ModeChoice;
|
|
51
|
+
/** None when chaff recommends the depth itself. */
|
|
52
|
+
readonly chosenDepth: ChosenDepth | undefined;
|
|
42
53
|
readonly signals: readonly DocumentSignal[];
|
|
43
54
|
readonly outline: { readonly unit: LengthUnit; readonly shape: Shape };
|
|
44
55
|
/** The structure score, the count at which the plan rewrites from the outline, and how many measures were compared. */
|
|
@@ -46,6 +57,8 @@ export type FixPlan = {
|
|
|
46
57
|
/** The structure measures past 90% of human articles, each with the human numbers to bring it back to. */
|
|
47
58
|
readonly targets: readonly StructureTarget[];
|
|
48
59
|
readonly rules: readonly RulePlan[];
|
|
60
|
+
/** The rules that fired but reach deeper than the chosen depth. Empty without a chosen depth. */
|
|
61
|
+
readonly deeper: readonly DeeperRule[];
|
|
49
62
|
/** The rewrite and signal rules that did not run, and why: silence from them is not "checked and fine". */
|
|
50
63
|
readonly notRun: readonly Skipped[];
|
|
51
64
|
readonly checks: readonly string[];
|
|
@@ -66,6 +79,7 @@ export type PlanInput = {
|
|
|
66
79
|
readonly structure: StructureScore;
|
|
67
80
|
/** The ai-tell lexicon of the document's language, whose entries may carry their own hint. */
|
|
68
81
|
readonly phrases: Lexicon;
|
|
82
|
+
readonly chosenDepth?: ChosenDepth | undefined;
|
|
69
83
|
};
|
|
70
84
|
|
|
71
85
|
const BOLD_DENSITY_RULE = "bold-density";
|
|
@@ -114,6 +128,7 @@ const rulePlanOf = (rule: RuleDefinition, findings: readonly Finding[], input: P
|
|
|
114
128
|
return {
|
|
115
129
|
rule: rule.id,
|
|
116
130
|
name: localized(rule.name, input.language),
|
|
131
|
+
depth: depthOfRule(rule),
|
|
117
132
|
direction: rewrite?.direction ?? localized(rule.how_to_fix, input.language),
|
|
118
133
|
keep: rewrite?.keep ?? [],
|
|
119
134
|
avoid: rewrite?.avoid ?? [],
|
|
@@ -123,6 +138,18 @@ const rulePlanOf = (rule: RuleDefinition, findings: readonly Finding[], input: P
|
|
|
123
138
|
};
|
|
124
139
|
};
|
|
125
140
|
|
|
141
|
+
const depthOfRule = (rule: RuleDefinition): RewriteDepth => rule.guide?.rewriteDepth ?? DEFAULT_DEPTH;
|
|
142
|
+
|
|
143
|
+
/** Every rule that fired, planned; then the ones a chosen depth leaves out, set apart. */
|
|
144
|
+
const byDepth = (plans: readonly RulePlan[], chosen: ChosenDepth | undefined): Pick<FixPlan, "rules" | "deeper"> => {
|
|
145
|
+
if (chosen === undefined) return { rules: plans, deeper: [] };
|
|
146
|
+
const within = (plan: RulePlan): boolean => depthIncludes(chosen.depth, plan.depth);
|
|
147
|
+
return {
|
|
148
|
+
rules: plans.filter(within),
|
|
149
|
+
deeper: plans.filter((plan) => !within(plan)).map((plan) => ({ rule: plan.rule, name: plan.name, depth: plan.depth, spots: plan.spots.length })),
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
126
153
|
/** The rules in the order their first finding appears; the composite has no spot of its own and is a signal only. */
|
|
127
154
|
const rulePlansOf = (input: PlanInput): RulePlan[] => {
|
|
128
155
|
const order = [...new Set(input.findings.map((finding) => finding.rule))].filter((id) => id !== COMPOSITE_RULE);
|
|
@@ -159,12 +186,12 @@ const notRunOf = (input: PlanInput, signalRules: ReadonlySet<string>): Skipped[]
|
|
|
159
186
|
return input.skipped.filter((skipped) => watched.has(skipped.rule));
|
|
160
187
|
};
|
|
161
188
|
|
|
162
|
-
/** The commands to run on the rewrite. A
|
|
189
|
+
/** The commands to run on the rewrite. A rewrite past light cuts repeats and rebuilds headings, which compare is told. */
|
|
163
190
|
const checksOf = (input: PlanInput, choice: ModeChoice): string[] => {
|
|
164
191
|
const original = shellPath(input.path);
|
|
165
192
|
const rewritten = shellPath(rewrittenPathOf(input.path));
|
|
166
193
|
const lintFlags = [...(input.experimental ? ["--experimental"] : []), ...(input.genreFlag === undefined ? [] : ["--genre", input.genreFlag])];
|
|
167
|
-
const compareFlags = choice.
|
|
194
|
+
const compareFlags = choice.depth !== undefined && choice.depth !== "light" ? ["--distinct", "--allow-dropped heading", "--allow-added heading"] : [];
|
|
168
195
|
return [
|
|
169
196
|
["npx chaffjs", rewritten, ...lintFlags].join(" "),
|
|
170
197
|
["npx chaffjs compare", original, rewritten, ...compareFlags].join(" "),
|
|
@@ -182,8 +209,12 @@ const documentLength = (outline: Outline): number => outline.entries.reduce((sum
|
|
|
182
209
|
export const buildFixPlan = (input: PlanInput): FixPlan => {
|
|
183
210
|
const signalRules = signalRulesOf(input.rules);
|
|
184
211
|
const structure = { score: input.structure.score, limit: structureLimitOf(input.rules), compared: input.structure.compared };
|
|
185
|
-
const
|
|
186
|
-
|
|
212
|
+
const planned = byDepth(rulePlansOf(input), input.chosenDepth);
|
|
213
|
+
// A rule the depth sets apart is not something this rewrite fixes, so it does not decide the way either.
|
|
214
|
+
const setApart = new Set(planned.deeper.map((rule) => rule.rule));
|
|
215
|
+
const firedRules = new Set(input.findings.map((finding) => finding.rule).filter((rule) => !setApart.has(rule)));
|
|
216
|
+
const chosen = input.chosenDepth?.depth;
|
|
217
|
+
const mode = recommendMode({ genre: input.genre, firedRules, signalRules, structure, chosen, setApart: setApart.size });
|
|
187
218
|
return {
|
|
188
219
|
path: input.path,
|
|
189
220
|
rewrittenPath: rewrittenPathOf(input.path),
|
|
@@ -191,11 +222,12 @@ export const buildFixPlan = (input: PlanInput): FixPlan => {
|
|
|
191
222
|
genre: input.genre,
|
|
192
223
|
experimental: input.experimental,
|
|
193
224
|
mode,
|
|
225
|
+
chosenDepth: input.chosenDepth,
|
|
194
226
|
signals: signalsOf(input, signalRules),
|
|
195
227
|
outline: { unit: input.outline.unit, shape: input.outline.shape },
|
|
196
228
|
structure,
|
|
197
229
|
targets: structureTargetsOf(input.structure, documentLength(input.outline)),
|
|
198
|
-
|
|
230
|
+
...planned,
|
|
199
231
|
notRun: notRunOf(input, signalRules),
|
|
200
232
|
checks: checksOf(input, mode),
|
|
201
233
|
};
|