chaffjs 0.15.0 → 0.16.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 +27 -2
- package/dist/address-chain.js +1 -1
- package/dist/address-chain.js.map +1 -1
- package/dist/backends/anthropic.d.ts +2 -1
- package/dist/backends/anthropic.d.ts.map +1 -1
- package/dist/backends/anthropic.js +4 -1
- package/dist/backends/anthropic.js.map +1 -1
- package/dist/backends/openai.d.ts +2 -1
- package/dist/backends/openai.d.ts.map +1 -1
- package/dist/backends/openai.js +1 -1
- package/dist/backends/openai.js.map +1 -1
- package/dist/backends/types.d.ts +2 -1
- package/dist/backends/types.d.ts.map +1 -1
- package/dist/backends/types.js +1 -1
- package/dist/backends/types.js.map +1 -1
- package/dist/baseline.js +1 -1
- package/dist/baseline.js.map +1 -1
- package/dist/calendar-number.d.ts +26 -0
- package/dist/calendar-number.d.ts.map +1 -0
- package/dist/calendar-number.js +51 -0
- package/dist/calendar-number.js.map +1 -0
- package/dist/cli-args.d.ts +0 -5
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +2 -2
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-text.d.ts +6 -2
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +19 -12
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -37
- package/dist/cli.js.map +1 -1
- package/dist/commands/cite.d.ts.map +1 -1
- package/dist/commands/cite.js +4 -4
- package/dist/commands/cite.js.map +1 -1
- package/dist/commands/eval.js +1 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init-ask.d.ts +19 -0
- package/dist/commands/init-ask.d.ts.map +1 -0
- package/dist/commands/init-ask.js +43 -0
- package/dist/commands/init-ask.js.map +1 -0
- package/dist/commands/test.d.ts +8 -0
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +70 -38
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/tree.d.ts +1 -8
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +7 -6
- package/dist/commands/tree.js.map +1 -1
- package/dist/config/by-path.d.ts.map +1 -1
- package/dist/config/by-path.js +10 -9
- package/dist/config/by-path.js.map +1 -1
- package/dist/config/load.d.ts +4 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +19 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/name-problems.d.ts +5 -0
- package/dist/config/name-problems.d.ts.map +1 -0
- package/dist/config/name-problems.js +11 -0
- package/dist/config/name-problems.js.map +1 -0
- package/dist/date-stamp.js +1 -1
- package/dist/date-stamp.js.map +1 -1
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +16 -6
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +101 -7
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/enumerated-runs.d.ts.map +1 -1
- package/dist/detectors/enumerated-runs.js +4 -2
- package/dist/detectors/enumerated-runs.js.map +1 -1
- package/dist/detectors/gram-place.js +1 -1
- package/dist/detectors/gram-place.js.map +1 -1
- package/dist/detectors/heading-case.d.ts.map +1 -1
- package/dist/detectors/heading-case.js +3 -1
- package/dist/detectors/heading-case.js.map +1 -1
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +4 -3
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/lettered-index.d.ts +9 -0
- package/dist/detectors/lettered-index.d.ts.map +1 -0
- package/dist/detectors/lettered-index.js +54 -0
- package/dist/detectors/lettered-index.js.map +1 -0
- package/dist/detectors/lexicon.d.ts +5 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +46 -3
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +24 -6
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/phrase-match.d.ts +3 -1
- package/dist/detectors/phrase-match.d.ts.map +1 -1
- package/dist/detectors/phrase-match.js +20 -15
- package/dist/detectors/phrase-match.js.map +1 -1
- package/dist/detectors/repeated-head.d.ts.map +1 -1
- package/dist/detectors/repeated-head.js +5 -4
- package/dist/detectors/repeated-head.js.map +1 -1
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +16 -6
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/stacked-hedge.d.ts +23 -0
- package/dist/detectors/stacked-hedge.d.ts.map +1 -0
- package/dist/detectors/stacked-hedge.js +64 -0
- package/dist/detectors/stacked-hedge.js.map +1 -0
- package/dist/detectors/superlative-amount.d.ts +9 -0
- package/dist/detectors/superlative-amount.d.ts.map +1 -0
- package/dist/detectors/superlative-amount.js +20 -0
- package/dist/detectors/superlative-amount.js.map +1 -0
- package/dist/detectors/superlative-clause.d.ts +18 -0
- package/dist/detectors/superlative-clause.d.ts.map +1 -0
- package/dist/detectors/superlative-clause.js +61 -0
- package/dist/detectors/superlative-clause.js.map +1 -0
- package/dist/detectors/superlative-scope.d.ts +6 -0
- package/dist/detectors/superlative-scope.d.ts.map +1 -1
- package/dist/detectors/superlative-scope.js +3 -3
- package/dist/detectors/superlative-scope.js.map +1 -1
- package/dist/detectors/team.d.ts +0 -6
- package/dist/detectors/team.d.ts.map +1 -1
- package/dist/detectors/team.js +17 -1
- package/dist/detectors/team.js.map +1 -1
- package/dist/detectors/token-shape.d.ts.map +1 -1
- package/dist/detectors/token-shape.js +8 -6
- package/dist/detectors/token-shape.js.map +1 -1
- package/dist/document.d.ts +3 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +18 -3
- package/dist/document.js.map +1 -1
- package/dist/eval.js +1 -1
- package/dist/eval.js.map +1 -1
- package/dist/feedback/draft.js +1 -1
- package/dist/feedback/draft.js.map +1 -1
- package/dist/feedback/excerpt.d.ts +0 -1
- package/dist/feedback/excerpt.d.ts.map +1 -1
- package/dist/feedback/excerpt.js +2 -2
- package/dist/feedback/excerpt.js.map +1 -1
- package/dist/file-header.d.ts +20 -0
- package/dist/file-header.d.ts.map +1 -0
- package/dist/file-header.js +21 -0
- package/dist/file-header.js.map +1 -0
- package/dist/files.js +3 -3
- package/dist/files.js.map +1 -1
- package/dist/genre-check.js +1 -1
- package/dist/genre-check.js.map +1 -1
- package/dist/genre-load.d.ts +6 -0
- package/dist/genre-load.d.ts.map +1 -0
- package/dist/genre-load.js +22 -0
- package/dist/genre-load.js.map +1 -0
- package/dist/genre-parse.d.ts +40 -0
- package/dist/genre-parse.d.ts.map +1 -0
- package/dist/genre-parse.js +104 -0
- package/dist/genre-parse.js.map +1 -0
- package/dist/genre-suggest.d.ts +15 -0
- package/dist/genre-suggest.d.ts.map +1 -0
- package/dist/genre-suggest.js +26 -0
- package/dist/genre-suggest.js.map +1 -0
- package/dist/genre.d.ts +1 -1
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +3 -13
- package/dist/genre.js.map +1 -1
- package/dist/init-choice.d.ts +12 -0
- package/dist/init-choice.d.ts.map +1 -0
- package/dist/init-choice.js +41 -0
- package/dist/init-choice.js.map +1 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +16 -5
- package/dist/init.js.map +1 -1
- package/dist/judge.d.ts +2 -1
- package/dist/judge.d.ts.map +1 -1
- package/dist/judge.js +1 -1
- package/dist/judge.js.map +1 -1
- package/dist/line-paragraphs.d.ts +4 -0
- package/dist/line-paragraphs.d.ts.map +1 -0
- package/dist/line-paragraphs.js +69 -0
- package/dist/line-paragraphs.js.map +1 -0
- package/dist/mask.d.ts.map +1 -1
- package/dist/mask.js +7 -6
- package/dist/mask.js.map +1 -1
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/check.d.ts +28 -0
- package/dist/profile/check.d.ts.map +1 -0
- package/dist/profile/check.js +12 -0
- package/dist/profile/check.js.map +1 -0
- package/dist/profile/for-file.d.ts +2 -2
- package/dist/profile/for-file.d.ts.map +1 -1
- package/dist/profile/for-file.js +9 -2
- package/dist/profile/for-file.js.map +1 -1
- package/dist/profile/load.js +1 -1
- package/dist/profile/load.js.map +1 -1
- package/dist/profile/select.d.ts +4 -2
- package/dist/profile/select.d.ts.map +1 -1
- package/dist/profile/select.js +8 -3
- package/dist/profile/select.js.map +1 -1
- package/dist/quoted-span.d.ts +15 -3
- package/dist/quoted-span.d.ts.map +1 -1
- package/dist/quoted-span.js +16 -11
- package/dist/quoted-span.js.map +1 -1
- package/dist/reasons.d.ts +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +4 -1
- package/dist/reasons.js.map +1 -1
- package/dist/render/compact.d.ts.map +1 -1
- package/dist/render/compact.js +1 -2
- package/dist/render/compact.js.map +1 -1
- package/dist/render/eval.d.ts.map +1 -1
- package/dist/render/eval.js +4 -3
- package/dist/render/eval.js.map +1 -1
- package/dist/render/friendly.d.ts +2 -1
- package/dist/render/friendly.d.ts.map +1 -1
- package/dist/render/friendly.js +14 -6
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/genres.d.ts +5 -0
- package/dist/render/genres.d.ts.map +1 -0
- package/dist/render/genres.js +28 -0
- package/dist/render/genres.js.map +1 -0
- package/dist/render/plural.d.ts +3 -0
- package/dist/render/plural.d.ts.map +1 -0
- package/dist/render/plural.js +5 -0
- package/dist/render/plural.js.map +1 -0
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +9 -3
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/semantic.d.ts +3 -2
- package/dist/render/semantic.d.ts.map +1 -1
- package/dist/render/semantic.js +94 -34
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/summary.d.ts.map +1 -1
- package/dist/render/summary.js +4 -3
- package/dist/render/summary.js.map +1 -1
- package/dist/render/suppressions.d.ts.map +1 -1
- package/dist/render/suppressions.js +3 -2
- package/dist/render/suppressions.js.map +1 -1
- package/dist/render/text.d.ts +2 -0
- package/dist/render/text.d.ts.map +1 -1
- package/dist/render/text.js +11 -3
- package/dist/render/text.js.map +1 -1
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +4 -1
- package/dist/rule-load.js.map +1 -1
- package/dist/run-semantic.js +1 -1
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +2 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +25 -15
- package/dist/run.js.map +1 -1
- package/dist/sentence-shape.d.ts +0 -1
- package/dist/sentence-shape.d.ts.map +1 -1
- package/dist/sentence-shape.js +3 -5
- package/dist/sentence-shape.js.map +1 -1
- package/dist/setting-problems.d.ts +5 -0
- package/dist/setting-problems.d.ts.map +1 -0
- package/dist/setting-problems.js +15 -0
- package/dist/setting-problems.js.map +1 -0
- package/dist/structure/build.js +5 -5
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/cite.js +1 -1
- package/dist/structure/cite.js.map +1 -1
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +5 -5
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/markdown-path.d.ts.map +1 -1
- package/dist/structure/markdown-path.js +2 -2
- package/dist/structure/markdown-path.js.map +1 -1
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +1 -1
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/relative-find.js +1 -1
- package/dist/structure/relative-find.js.map +1 -1
- package/dist/structure/sexp.js +1 -1
- package/dist/structure/sexp.js.map +1 -1
- package/dist/structure/unnumbered.js +2 -2
- package/dist/structure/unnumbered.js.map +1 -1
- package/dist/team-names.d.ts +9 -0
- package/dist/team-names.d.ts.map +1 -0
- package/dist/team-names.js +34 -0
- package/dist/team-names.js.map +1 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +6 -0
- package/dist/ui.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +5 -5
- package/dist/watch.js.map +1 -1
- package/genres.yaml +363 -0
- package/package.json +4 -3
- package/rules/adverb-overuse.yaml +2 -2
- package/rules/agentless-passive.yaml +1 -1
- package/rules/ai-generated-composite.yaml +2 -2
- package/rules/ai-tell.yaml +2 -2
- package/rules/bold-density.yaml +2 -2
- package/rules/closing-cliche.yaml +1 -1
- package/rules/concrete-evidence-density.yaml +2 -2
- package/rules/contraction-consistency.yaml +1 -1
- package/rules/cushion-phrase-density.yaml +1 -1
- package/rules/dangling-reference.yaml +1 -1
- package/rules/date-order.yaml +1 -1
- package/rules/date-weekday-mismatch.yaml +1 -1
- package/rules/double-keigo.yaml +1 -1
- package/rules/doubled-word.yaml +1 -1
- package/rules/duplicate-definition.yaml +1 -1
- package/rules/emoji-density.yaml +1 -1
- package/rules/empty-conclusion.yaml +2 -1
- package/rules/empty-intensifier.yaml +1 -1
- package/rules/excessive-hedging.yaml +16 -6
- package/rules/expletive-construction.yaml +2 -2
- package/rules/heading-echo.yaml +1 -1
- package/rules/hiragana-fukushi.yaml +1 -1
- package/rules/internal-jargon.yaml +2 -2
- package/rules/latin-spacing.yaml +5 -4
- package/rules/max-kanji-continuous.yaml +4 -3
- package/rules/max-paragraph-length.yaml +9 -2
- package/rules/max-sentence-length.yaml +14 -2
- package/rules/ngram-repetition.yaml +4 -4
- package/rules/no-doubled-joshi.yaml +2 -2
- package/rules/no-em-dash.yaml +2 -2
- package/rules/no-mixed-desumasu.yaml +1 -1
- package/rules/no-nakaguro-parallel.yaml +2 -2
- package/rules/numbering-gap.yaml +1 -1
- package/rules/oxford-comma-consistency.yaml +2 -1
- package/rules/padded-intro.yaml +2 -1
- package/rules/paragraph-length-variance.yaml +1 -1
- package/rules/preamble-length.yaml +2 -2
- package/rules/preferred-term.yaml +1 -1
- package/rules/proper-noun-density.yaml +3 -2
- package/rules/repeated-conjunction.yaml +2 -2
- package/rules/repeated-sentence-head.yaml +2 -2
- package/rules/required-sections.yaml +1 -1
- package/rules/rule-of-three.yaml +2 -2
- package/rules/sasete-itadaku.yaml +3 -3
- package/rules/section-length-uniformity.yaml +1 -1
- package/rules/sentence-initial-conjunction-run.yaml +2 -2
- package/rules/sentence-rhythm.yaml +1 -1
- package/rules/taigen-dome-in-prose.yaml +2 -2
- package/rules/title-case-consistency.yaml +1 -1
- package/rules/total-mismatch.yaml +1 -1
- package/rules/undefined-acronym.yaml +4 -4
- package/rules/unqualified-superlative.yaml +2 -2
- package/skills/chaff/SKILL.md +28 -9
- package/src/address-chain.ts +1 -1
- package/src/backends/anthropic.ts +5 -1
- package/src/backends/openai.ts +2 -1
- package/src/backends/types.ts +3 -2
- package/src/baseline.ts +1 -1
- package/src/calendar-number.ts +75 -0
- package/src/cli-args.ts +2 -2
- package/src/cli-text.ts +29 -15
- package/src/cli.ts +42 -41
- package/src/commands/cite.ts +4 -4
- package/src/commands/eval.ts +1 -1
- package/src/commands/init-ask.ts +53 -0
- package/src/commands/test.ts +102 -45
- package/src/commands/tree.ts +7 -6
- package/src/config/by-path.ts +8 -9
- package/src/config/load.ts +24 -0
- package/src/config/name-problems.ts +15 -0
- package/src/date-stamp.ts +1 -1
- package/src/detectors/char-shape.ts +21 -9
- package/src/detectors/en-shape.ts +106 -8
- package/src/detectors/enumerated-runs.ts +3 -2
- package/src/detectors/gram-place.ts +1 -1
- package/src/detectors/heading-case.ts +4 -1
- package/src/detectors/index.ts +4 -3
- package/src/detectors/lettered-index.ts +78 -0
- package/src/detectors/lexicon.ts +63 -6
- package/src/detectors/orthography.ts +31 -7
- package/src/detectors/phrase-match.ts +23 -12
- package/src/detectors/repeated-head.ts +3 -4
- package/src/detectors/signals.ts +15 -6
- package/src/detectors/stacked-hedge.ts +93 -0
- package/src/detectors/superlative-amount.ts +24 -0
- package/src/detectors/superlative-clause.ts +82 -0
- package/src/detectors/superlative-scope.ts +3 -3
- package/src/detectors/team.ts +18 -2
- package/src/detectors/token-shape.ts +8 -10
- package/src/document.ts +29 -3
- package/src/eval.ts +1 -1
- package/src/feedback/draft.ts +1 -1
- package/src/feedback/excerpt.ts +2 -2
- package/src/file-header.ts +31 -0
- package/src/files.ts +3 -3
- package/src/genre-check.ts +1 -1
- package/src/genre-load.ts +24 -0
- package/src/genre-parse.ts +126 -0
- package/src/genre-suggest.ts +37 -0
- package/src/genre.ts +4 -13
- package/src/init-choice.ts +47 -0
- package/src/init.ts +20 -6
- package/src/judge.ts +3 -1
- package/src/line-paragraphs.ts +83 -0
- package/src/mask.ts +5 -6
- package/src/plugin.ts +6 -0
- package/src/profile/check.ts +34 -0
- package/src/profile/for-file.ts +10 -3
- package/src/profile/load.ts +1 -1
- package/src/profile/select.ts +11 -4
- package/src/quoted-span.ts +26 -12
- package/src/reasons.ts +5 -1
- package/src/render/compact.ts +1 -3
- package/src/render/eval.ts +4 -3
- package/src/render/friendly.ts +21 -6
- package/src/render/genres.ts +31 -0
- package/src/render/plural.ts +6 -0
- package/src/render/rules-json.ts +10 -3
- package/src/render/semantic.ts +117 -34
- package/src/render/summary.ts +4 -3
- package/src/render/suppressions.ts +3 -2
- package/src/render/text.ts +18 -3
- package/src/rule-load.ts +6 -1
- package/src/run-semantic.ts +1 -1
- package/src/run.ts +28 -15
- package/src/sentence-shape.ts +3 -7
- package/src/setting-problems.ts +24 -0
- package/src/structure/build.ts +5 -5
- package/src/structure/cite.ts +1 -1
- package/src/structure/issues.ts +6 -5
- package/src/structure/markdown-path.ts +2 -2
- package/src/structure/numbering.ts +1 -1
- package/src/structure/relative-find.ts +1 -1
- package/src/structure/sexp.ts +1 -1
- package/src/structure/unnumbered.ts +2 -2
- package/src/team-names.ts +39 -0
- package/src/ui.ts +7 -0
- package/src/version.ts +1 -1
- package/src/watch.ts +5 -5
package/src/render/rules-json.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { definedLevels, resolve } from "../levels.ts";
|
|
|
2
2
|
import type { Config } from "../config/load.ts";
|
|
3
3
|
import type { RuleDefinition } from "../plugin.ts";
|
|
4
4
|
import { uiLanguageOf, type Texts } from "../ui.ts";
|
|
5
|
+
import { presetLevels } from "../genre-load.ts";
|
|
6
|
+
import type { PresetLevels } from "../genre-parse.ts";
|
|
5
7
|
|
|
6
8
|
const TEXT: Texts<{
|
|
7
9
|
readonly offBySetting: string;
|
|
10
|
+
readonly offByGenre: (genre: string) => string;
|
|
8
11
|
readonly offExperimental: string;
|
|
9
12
|
readonly valuesNote: string;
|
|
10
13
|
readonly reason: string;
|
|
@@ -12,6 +15,7 @@ const TEXT: Texts<{
|
|
|
12
15
|
}> = {
|
|
13
16
|
ja: {
|
|
14
17
|
offBySetting: "設定で止めている",
|
|
18
|
+
offByGenre: (genre) => `ジャンル ${genre} では見ない`,
|
|
15
19
|
offExperimental: "experimental な rule は既定で動かさない",
|
|
16
20
|
valuesNote: "この 4 語のかわりに数字を直接書いてもよい。4 語のほうを勧める。",
|
|
17
21
|
reason: "<理由>",
|
|
@@ -19,6 +23,7 @@ const TEXT: Texts<{
|
|
|
19
23
|
},
|
|
20
24
|
en: {
|
|
21
25
|
offBySetting: "turned off in the settings",
|
|
26
|
+
offByGenre: (genre) => `the ${genre} genre does not check it`,
|
|
22
27
|
offExperimental: "experimental rules do not run by default",
|
|
23
28
|
valuesNote: "A number may be written instead of these four words. The words are recommended.",
|
|
24
29
|
reason: "<reason>",
|
|
@@ -26,8 +31,9 @@ const TEXT: Texts<{
|
|
|
26
31
|
},
|
|
27
32
|
};
|
|
28
33
|
|
|
29
|
-
const now = (rule: RuleDefinition, config: Config, genre: string, text: (typeof TEXT)["ja"]): Record<string, unknown> => {
|
|
30
|
-
const explicit = config.rules[rule.id];
|
|
34
|
+
const now = (rule: RuleDefinition, config: Config, genre: string, text: (typeof TEXT)["ja"], preset: PresetLevels): Record<string, unknown> => {
|
|
35
|
+
const explicit = config.rules[rule.id] ?? preset[rule.id];
|
|
36
|
+
if (explicit === "off" && config.rules[rule.id] === undefined) return { level: "off", why_off: text.offByGenre(genre) };
|
|
31
37
|
if (explicit === "off") return { level: "off", why_off: text.offBySetting };
|
|
32
38
|
const limit = rule.layer === "L4" ? undefined : config.limits[rule.id];
|
|
33
39
|
if (limit !== undefined) return { level: "normal", limit, set_as: "number" };
|
|
@@ -59,6 +65,7 @@ const levelsOf = (rule: RuleDefinition, genre: string): Record<string, unknown>
|
|
|
59
65
|
/** AI に設定を書かせるときの入口。推測せずに書けるだけの情報を 1 つに入れる。spec §19.3。 */
|
|
60
66
|
export const rulesJson = (rules: readonly RuleDefinition[], config: Config, language: string, genre: string): string => {
|
|
61
67
|
const text = TEXT[uiLanguageOf(language)];
|
|
68
|
+
const preset = presetLevels(genre);
|
|
62
69
|
return JSON.stringify(
|
|
63
70
|
{
|
|
64
71
|
schema_version: 1,
|
|
@@ -77,7 +84,7 @@ export const rulesJson = (rules: readonly RuleDefinition[], config: Config, lang
|
|
|
77
84
|
...levelsOf(rule, genre),
|
|
78
85
|
levels_you_can_set: definedLevels(rule),
|
|
79
86
|
your_setting: yourSetting(rule, config),
|
|
80
|
-
now: now(rule, config, genre, text),
|
|
87
|
+
now: now(rule, config, genre, text, preset),
|
|
81
88
|
})),
|
|
82
89
|
how_to_change: {
|
|
83
90
|
by_command: ["relax", "strict", "off"].map((command) => `npx chaff ${command} <rule-id> --why "${text.reason}"`),
|
package/src/render/semantic.ts
CHANGED
|
@@ -1,35 +1,113 @@
|
|
|
1
1
|
import type { Finding, RuleDefinition } from "../plugin.ts";
|
|
2
2
|
import type { UserCheck } from "../checks.ts";
|
|
3
|
+
import type { Narrowing } from "../look-at.ts";
|
|
3
4
|
import type { Job as Plan, SemanticResult } from "../run-semantic.ts";
|
|
5
|
+
import { uiLanguageOf, type Texts, type UiLanguage } from "../ui.ts";
|
|
4
6
|
import { MARK, localized } from "./text.ts";
|
|
7
|
+
import { counted, formFor } from "./plural.ts";
|
|
5
8
|
|
|
6
9
|
const RULE = 60;
|
|
7
10
|
|
|
11
|
+
const TEXT: Texts<{
|
|
12
|
+
readonly machineBanner: readonly string[];
|
|
13
|
+
readonly aiTitle: string;
|
|
14
|
+
readonly aiNote: readonly string[];
|
|
15
|
+
readonly read: (asked: number, seen: number) => string;
|
|
16
|
+
readonly wholeRead: (name: string) => readonly string[];
|
|
17
|
+
readonly word: (word: string) => string;
|
|
18
|
+
readonly number: string;
|
|
19
|
+
readonly narrowedRead: (name: string, what: string, kept: number, total: number) => string;
|
|
20
|
+
readonly line: (line: number) => string;
|
|
21
|
+
readonly confidence: (value: string) => string;
|
|
22
|
+
readonly disagree: readonly string[];
|
|
23
|
+
readonly silence: (rule: string) => string;
|
|
24
|
+
readonly relax: (rule: string) => string;
|
|
25
|
+
readonly notRunChecks: (count: number) => string;
|
|
26
|
+
readonly byMachine: string;
|
|
27
|
+
readonly whole: string;
|
|
28
|
+
readonly containing: (parts: string) => string;
|
|
29
|
+
readonly planLine: (count: number, name: string, how: string) => string;
|
|
30
|
+
readonly firstSample: string;
|
|
31
|
+
readonly planHead: (path: string, sentences: number, asked: number) => string;
|
|
32
|
+
}> = {
|
|
33
|
+
ja: {
|
|
34
|
+
machineBanner: ["", `═══ 機械による判定 ${"═".repeat(RULE - 18)}`, " 同じ文章なら何度実行しても同じ結果になります", ""],
|
|
35
|
+
aiTitle: `═══ AI による判定 ${"═".repeat(RULE - 17)}`,
|
|
36
|
+
aiNote: [" 文章の意味を読んでいます。実行するたび結果が変わることが", " あります。おかしいと思ったら、そのまま無視して構いません。"],
|
|
37
|
+
read: (asked, seen) => ` ${seen} 文のうち ${asked} 箇所を読みました(残りは機械が対象外と判断)`,
|
|
38
|
+
wholeRead: (name) => [` ${name}: 見るところを絞れず、全文を読みました`, ` look_at に「」で語を書くと、その語を含む文だけになります`],
|
|
39
|
+
word: (word) => `「${word}」`,
|
|
40
|
+
number: "数字",
|
|
41
|
+
narrowedRead: (name, what, kept, total) => ` ${name}: ${what} を含む ${kept} 文だけを読みました(全 ${total} 文)`,
|
|
42
|
+
line: (line) => `${line} 行目`,
|
|
43
|
+
confidence: (value) => `確からしさ ${value}`,
|
|
44
|
+
disagree: [" この指摘が違うと思ったら:"],
|
|
45
|
+
silence: (rule) => ` この箇所だけ黙らせる <!-- stet: ${rule} — 理由 -->`,
|
|
46
|
+
relax: (rule) => ` ルールごとゆるめる npx chaff relax ${rule}`,
|
|
47
|
+
notRunChecks: (count) => ` ${count} 件の検査は、見るところが無いため動いていません。`,
|
|
48
|
+
byMachine: "機械で絞り込み済み",
|
|
49
|
+
whole: "絞り込めず全文",
|
|
50
|
+
containing: (parts) => `${parts} を含む文`,
|
|
51
|
+
planLine: (count, name, how) => ` ${String(count).padStart(3)} 箇所 ${name} (${how})`,
|
|
52
|
+
firstSample: " 最初の 1 件に送る文章:",
|
|
53
|
+
planHead: (path, sentences, asked) => `${path} 全 ${sentences} 文のうち ${asked} 箇所を送ります(API は呼んでいません)`,
|
|
54
|
+
},
|
|
55
|
+
en: {
|
|
56
|
+
machineBanner: ["", `═══ Judged by machine ${"═".repeat(RULE - 21)}`, " The same text gives the same result every time", ""],
|
|
57
|
+
aiTitle: `═══ Judged by AI ${"═".repeat(RULE - 16)}`,
|
|
58
|
+
aiNote: [" These read the meaning of the text. The result can change", " from run to run. If one looks wrong, feel free to ignore it."],
|
|
59
|
+
read: (asked, seen) => ` Read ${counted(asked, "passage")} out of ${counted(seen, "sentence")} (the machine ruled out the rest)`,
|
|
60
|
+
wholeRead: (name) => [
|
|
61
|
+
` ${name}: could not narrow what to read, so read the whole text`,
|
|
62
|
+
` Put words in quotes in look_at ("like this") to read only the sentences that contain them`,
|
|
63
|
+
],
|
|
64
|
+
word: (word) => `"${word}"`,
|
|
65
|
+
number: "a number",
|
|
66
|
+
narrowedRead: (name, what, kept, total) => ` ${name}: read only the ${counted(kept, "sentence")} containing ${what} (of ${total})`,
|
|
67
|
+
line: (line) => `line ${line}`,
|
|
68
|
+
confidence: (value) => `confidence ${value}`,
|
|
69
|
+
disagree: [" If you think this finding is wrong:"],
|
|
70
|
+
silence: (rule) => ` Silence this spot only <!-- stet: ${rule} — reason -->`,
|
|
71
|
+
relax: (rule) => ` Relax the whole rule npx chaff relax ${rule}`,
|
|
72
|
+
notRunChecks: (count) => ` ${counted(count, "check")} did not run: there was nothing for ${formFor(count, "it", "them")} to look at.`,
|
|
73
|
+
byMachine: "narrowed by machine",
|
|
74
|
+
whole: "not narrowed: the whole text",
|
|
75
|
+
containing: (parts) => `sentences containing ${parts}`,
|
|
76
|
+
planLine: (count, name, how) => ` ${String(count).padStart(3)} to send ${name} (${how})`,
|
|
77
|
+
firstSample: " The text sent for the first one:",
|
|
78
|
+
planHead: (path, sentences, asked) => `${path} sends ${counted(asked, "passage")} out of ${counted(sentences, "sentence")} (the API was not called)`,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
8
82
|
const indent = (text: string, pad: string): string[] => text.split("\n").map((line) => `${pad}${line}`);
|
|
9
83
|
|
|
10
|
-
|
|
84
|
+
/** The banner over a file's machine findings, in the file's language. */
|
|
85
|
+
export const machineBanner = (language: string): readonly string[] => TEXT[uiLanguageOf(language)].machineBanner;
|
|
86
|
+
|
|
87
|
+
/** The words a narrowing looks for, as the screen names them. */
|
|
88
|
+
const lookedFor = (narrowing: Narrowing, language: UiLanguage): string[] => {
|
|
89
|
+
const text = TEXT[language];
|
|
90
|
+
return [...narrowing.words.map((word) => text.word(word)), ...(narrowing.needsNumber ? [text.number] : [])];
|
|
91
|
+
};
|
|
11
92
|
|
|
12
93
|
/**
|
|
13
94
|
* 自分の書いた検査が、どこまで絞り込めたかを見せる。
|
|
14
95
|
* 絞り込めないまま全文を読んでいることに気づけないと、遅さの原因が分からない。
|
|
15
96
|
*/
|
|
16
|
-
const narrowingLines = (result: SemanticResult): string[] =>
|
|
97
|
+
const narrowingLines = (result: SemanticResult, language: UiLanguage): string[] =>
|
|
17
98
|
result.narrowed.flatMap(({ name, narrowing }) => {
|
|
18
|
-
if (narrowing.words.length === 0 && !narrowing.needsNumber)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const what = [...narrowing.words.map((word) => `「${word}」`), ...(narrowing.needsNumber ? ["数字"] : [])].join(" ");
|
|
22
|
-
return [` ${name}: ${what} を含む ${narrowing.kept} 文だけを読みました(全 ${narrowing.total} 文)`];
|
|
99
|
+
if (narrowing.words.length === 0 && !narrowing.needsNumber) return [...TEXT[language].wholeRead(name)];
|
|
100
|
+
const what = lookedFor(narrowing, language).join(language === "ja" ? " " : ", ");
|
|
101
|
+
return [TEXT[language].narrowedRead(name, what, narrowing.kept, narrowing.total)];
|
|
23
102
|
});
|
|
24
103
|
|
|
25
|
-
const aiBanner = (result: SemanticResult): string[] => [
|
|
104
|
+
const aiBanner = (result: SemanticResult, language: UiLanguage): string[] => [
|
|
26
105
|
"",
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
" あります。おかしいと思ったら、そのまま無視して構いません。",
|
|
106
|
+
TEXT[language].aiTitle,
|
|
107
|
+
...TEXT[language].aiNote,
|
|
30
108
|
"",
|
|
31
|
-
|
|
32
|
-
...narrowingLines(result),
|
|
109
|
+
TEXT[language].read(result.asked, result.sentencesSeen),
|
|
110
|
+
...narrowingLines(result, language),
|
|
33
111
|
"",
|
|
34
112
|
];
|
|
35
113
|
|
|
@@ -46,32 +124,37 @@ const nameOf = (rule: string, rules: readonly RuleDefinition[], checks: readonly
|
|
|
46
124
|
* AI の指摘にだけ確からしさを出し、納得できないときの逃げ道を 2 つ添える。
|
|
47
125
|
* 揺れる判定だと読む人が知らないと、誤検知に振り回される。
|
|
48
126
|
*/
|
|
49
|
-
const block = (finding: Finding, named: Named): string[] => {
|
|
127
|
+
const block = (finding: Finding, named: Named, language: UiLanguage): string[] => {
|
|
128
|
+
const text = TEXT[language];
|
|
50
129
|
const confidence = finding.values["confidence"];
|
|
51
130
|
const reason = String(finding.values["reason"] ?? "");
|
|
52
|
-
const sure = confidence === undefined ? "" : `
|
|
131
|
+
const sure = confidence === undefined ? "" : ` ${text.confidence(String(confidence))}`;
|
|
53
132
|
const fix = named.howToFix.length > 0 ? ["", ...indent(`→ ${named.howToFix}`, " ")] : [];
|
|
54
133
|
return [
|
|
55
134
|
"",
|
|
56
|
-
|
|
135
|
+
// "12 行目" and "line 12" take the same width on a terminal, so the rule ends in the same column in both.
|
|
136
|
+
`─── ${text.line(finding.line)} ${"─".repeat(Math.max(0, RULE - String(finding.line).length - 8))}`,
|
|
57
137
|
"",
|
|
58
138
|
` ${MARK[finding.severity] ?? "·"} ${named.name}${sure}`,
|
|
59
139
|
"",
|
|
60
140
|
...indent(reason, " "),
|
|
61
141
|
...fix,
|
|
62
142
|
"",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
143
|
+
...text.disagree,
|
|
144
|
+
text.silence(finding.rule),
|
|
145
|
+
text.relax(finding.rule),
|
|
66
146
|
"",
|
|
67
147
|
];
|
|
68
148
|
};
|
|
69
149
|
|
|
70
|
-
export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefinition[], checks: readonly UserCheck[], language: string): string[] =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
150
|
+
export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefinition[], checks: readonly UserCheck[], language: string): string[] => {
|
|
151
|
+
const ui = uiLanguageOf(language);
|
|
152
|
+
return [
|
|
153
|
+
...aiBanner(result, ui),
|
|
154
|
+
...result.findings.flatMap((finding) => block(finding, nameOf(finding.rule, rules, checks, language), ui)),
|
|
155
|
+
...(result.skipped.length > 0 ? [TEXT[ui].notRunChecks(result.skipped.length), ""] : []),
|
|
156
|
+
];
|
|
157
|
+
};
|
|
75
158
|
|
|
76
159
|
/**
|
|
77
160
|
* 何が送られるのかを、API を呼ばずに見せる。`chaff test --dry-run`。
|
|
@@ -79,19 +162,19 @@ export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefin
|
|
|
79
162
|
* 鍵が無い環境でも二段構えの 1 段目まで確かめられる。全文が送られようとしていることに
|
|
80
163
|
* 請求書で気づくのでは遅い。spec §14。
|
|
81
164
|
*/
|
|
82
|
-
const howNarrowed = (job: Plan): string => {
|
|
165
|
+
const howNarrowed = (job: Plan, language: UiLanguage): string => {
|
|
83
166
|
const narrowing = job.narrowing;
|
|
84
|
-
if (narrowing === undefined) return
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return `${parts.join(" ")} を含む文`;
|
|
167
|
+
if (narrowing === undefined) return TEXT[language].byMachine;
|
|
168
|
+
const parts = lookedFor(narrowing, language);
|
|
169
|
+
if (parts.length === 0) return TEXT[language].whole;
|
|
170
|
+
return TEXT[language].containing(parts.join(language === "ja" ? " " : ", "));
|
|
89
171
|
};
|
|
90
172
|
|
|
91
|
-
export const renderPlan = (path: string, jobs: readonly Plan[], sentences: number): string[] => {
|
|
173
|
+
export const renderPlan = (path: string, jobs: readonly Plan[], sentences: number, language: string): string[] => {
|
|
174
|
+
const text = TEXT[uiLanguageOf(language)];
|
|
92
175
|
const asked = jobs.reduce((sum, job) => sum + job.candidates.length, 0);
|
|
93
|
-
const lines = jobs.map((job) =>
|
|
176
|
+
const lines = jobs.map((job) => text.planLine(job.candidates.length, job.name, howNarrowed(job, uiLanguageOf(language))));
|
|
94
177
|
const first = jobs.find((job) => job.candidates.length > 0);
|
|
95
|
-
const sample = first === undefined ? [] : ["",
|
|
96
|
-
return ["",
|
|
178
|
+
const sample = first === undefined ? [] : ["", text.firstSample, "", ...indent(String(first.candidates[0]?.text ?? "").slice(0, 300), " ")];
|
|
179
|
+
return ["", text.planHead(path, sentences, asked), "", ...lines, ...sample, ""];
|
|
97
180
|
};
|
package/src/render/summary.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Finding } from "../plugin.ts";
|
|
2
2
|
import type { Texts, UiLanguage } from "../ui.ts";
|
|
3
|
+
import { counted } from "./plural.ts";
|
|
3
4
|
|
|
4
5
|
export type FileOutcome = { readonly path: string; readonly findings: readonly Finding[]; readonly notRun: number };
|
|
5
6
|
|
|
@@ -21,9 +22,9 @@ const TEXT: Texts<{
|
|
|
21
22
|
},
|
|
22
23
|
en: {
|
|
23
24
|
counts: {
|
|
24
|
-
error: (n) =>
|
|
25
|
-
warning: (n) =>
|
|
26
|
-
info: (n) =>
|
|
25
|
+
error: (n) => counted(n, "error"),
|
|
26
|
+
warning: (n) => counted(n, "warning"),
|
|
27
|
+
info: (n) => counted(n, "note"),
|
|
27
28
|
},
|
|
28
29
|
join: ", ",
|
|
29
30
|
none: "No findings",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Suppressed, Suppression } from "../stet.ts";
|
|
2
2
|
import type { Texts, UiLanguage } from "../ui.ts";
|
|
3
|
+
import { counted } from "./plural.ts";
|
|
3
4
|
|
|
4
5
|
const TEXT: Texts<{
|
|
5
6
|
readonly nudge: string;
|
|
@@ -23,7 +24,7 @@ const TEXT: Texts<{
|
|
|
23
24
|
},
|
|
24
25
|
en: {
|
|
25
26
|
nudge: " <- consider changing the setting instead",
|
|
26
|
-
more: (n) => ` and ${n
|
|
27
|
+
more: (n) => ` and ${counted(n, "more file")}`,
|
|
27
28
|
count: (n) => `${String(n).padStart(3)}`,
|
|
28
29
|
reasons: (list) => `reasons: ${list}`,
|
|
29
30
|
relaxAll: (rule) => `relax the whole rule: npx chaff relax ${rule} --why "..."`,
|
|
@@ -53,7 +54,7 @@ const groupByRule = (files: readonly PerFile[]): Group[] => {
|
|
|
53
54
|
reasons: [...new Set(mine.flatMap((row) => (row.reason === undefined ? [] : [row.reason])))],
|
|
54
55
|
};
|
|
55
56
|
})
|
|
56
|
-
.
|
|
57
|
+
.toSorted((left, right) => right.count - left.count);
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
const groupLines = (group: Group, ui: UiLanguage): string[] => {
|
package/src/render/text.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import type { Finding, Localized, RuleDefinition } from "../plugin.ts";
|
|
2
|
+
import { formFor } from "./plural.ts";
|
|
2
3
|
|
|
3
4
|
export const localized = (field: Localized, language: string): string => field[language] ?? field["en"] ?? Object.values(field)[0] ?? "";
|
|
4
5
|
|
|
6
|
+
/** `{count}` is the value; `{count|word|words}` is the form that agrees with it (English only; Japanese has no plural). */
|
|
7
|
+
const PLACEHOLDER = /\{(\w+)(?:\|([^|{}]*)\|([^|{}]*))?\}/gu;
|
|
8
|
+
|
|
5
9
|
const fill = (template: string, values: Readonly<Record<string, string | number>>): string =>
|
|
6
|
-
template.replace(
|
|
10
|
+
template.replace(PLACEHOLDER, (whole, key: string, singular: string | undefined, plural: string | undefined) => {
|
|
7
11
|
const value = values[key];
|
|
8
|
-
|
|
12
|
+
if (value === undefined) return whole;
|
|
13
|
+
return singular === undefined || plural === undefined ? String(value) : formFor(value, singular, plural);
|
|
9
14
|
});
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
/** A template as the rule reference shows it: each agreeing form as its plural ("{count} such words"). */
|
|
17
|
+
export const templateForReading = (template: string): string =>
|
|
18
|
+
template.replace(PLACEHOLDER, (whole, _key: string, singular: string | undefined, plural: string | undefined) =>
|
|
19
|
+
singular === undefined || plural === undefined ? whole : plural,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const templateOf = (rule: RuleDefinition, finding: Finding): Localized =>
|
|
23
|
+
(finding.variant === undefined ? undefined : rule.messages[finding.variant]) ?? rule.message;
|
|
24
|
+
|
|
25
|
+
export const messageOf = (rule: RuleDefinition, finding: Finding, language: string): string =>
|
|
26
|
+
fill(localized(templateOf(rule, finding), language), finding.values);
|
|
12
27
|
|
|
13
28
|
export const MARK: Readonly<Record<string, string>> = { error: "✖", warning: "⚠", info: "·" };
|
package/src/rule-load.ts
CHANGED
|
@@ -68,6 +68,10 @@ const missingFields = (raw: Record<string, unknown>, levels: LevelTable | undefi
|
|
|
68
68
|
|
|
69
69
|
const localizedOf = (value: unknown): Record<string, string> => (isLocalized(value) ? value : {});
|
|
70
70
|
|
|
71
|
+
/** 見つけ方ごとの message。読めないものは捨てる。 */
|
|
72
|
+
const messagesOf = (value: unknown): Record<string, Record<string, string>> =>
|
|
73
|
+
isRecord(value) ? Object.fromEntries(Object.entries(value).flatMap(([variant, text]) => (isLocalized(text) ? [[variant, text]] : []))) : {};
|
|
74
|
+
|
|
71
75
|
/**
|
|
72
76
|
* severity も言語別に書ける。levels と同じ畳みかた。
|
|
73
77
|
* 記号の許容度は言語で大きく違う(ダッシュは英語では普通、日本語の組版では扱いが難しい)。
|
|
@@ -95,6 +99,7 @@ const toRule = (raw: unknown, language: string, file: string): RuleDefinition =>
|
|
|
95
99
|
why: localizedOf(raw["why"]),
|
|
96
100
|
how_to_fix: localizedOf(raw["how_to_fix"]),
|
|
97
101
|
message: localizedOf(raw["message"]),
|
|
102
|
+
messages: messagesOf(raw["messages"]),
|
|
98
103
|
levels,
|
|
99
104
|
by_genre: genreTables(raw["by_genre"], language),
|
|
100
105
|
how_to_find: String(raw["how_to_find"]),
|
|
@@ -126,5 +131,5 @@ const parseRule = (dir: string, file: string): unknown => {
|
|
|
126
131
|
export const loadRules = (language: string, dir: string = RULES_DIR): RuleDefinition[] =>
|
|
127
132
|
readdirSync(dir)
|
|
128
133
|
.filter((file) => file.endsWith(".yaml"))
|
|
129
|
-
.
|
|
134
|
+
.toSorted((left, right) => left.localeCompare(right, "en"))
|
|
130
135
|
.map((file) => toRule(parseRule(dir, file), language, file));
|
package/src/run-semantic.ts
CHANGED
|
@@ -129,7 +129,7 @@ export const runSemantic = async (
|
|
|
129
129
|
),
|
|
130
130
|
);
|
|
131
131
|
return {
|
|
132
|
-
findings: answers.filter((finding) => finding !== undefined).
|
|
132
|
+
findings: answers.filter((finding) => finding !== undefined).toSorted((left, right) => left.line - right.line),
|
|
133
133
|
skipped,
|
|
134
134
|
asked: live.reduce((sum, job) => sum + job.candidates.length, 0),
|
|
135
135
|
sentencesSeen: doc.sentences.length,
|
package/src/run.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { isMarkdownPath } from "./structure/markdown-path.ts";
|
|
|
10
10
|
import { maskSpans } from "./mask.ts";
|
|
11
11
|
import { textOutline } from "./page-furniture.ts";
|
|
12
12
|
import { uiLanguageOf } from "./ui.ts";
|
|
13
|
+
import { presetLevels } from "./genre-load.ts";
|
|
13
14
|
|
|
14
15
|
export type Skipped = { readonly rule: string; readonly why: string };
|
|
15
16
|
|
|
@@ -18,18 +19,29 @@ export type RunResult = {
|
|
|
18
19
|
readonly skipped: readonly Skipped[];
|
|
19
20
|
/** 設定で明示的に有効にした experimental な rule。実行ごとに一度報告する。spec §18.4。 */
|
|
20
21
|
readonly forcedExperimental: readonly string[];
|
|
22
|
+
/** The experimental rules the genre's preset turns on. Reported apart from forcedExperimental: chaff.yaml did not name them. */
|
|
23
|
+
readonly presetExperimental: readonly string[];
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
export type Settings = Readonly<Record<string, Level>>;
|
|
24
27
|
|
|
25
|
-
const levelFor = (rule: RuleDefinition, settings: Settings, experimental: boolean): Level => {
|
|
28
|
+
const levelFor = (rule: RuleDefinition, settings: Settings, experimental: boolean, preset: Settings): Level => {
|
|
26
29
|
const explicit = settings[rule.id];
|
|
27
30
|
// 明示設定は status の既定に勝つ。名指しで有効にしたものを黙って無効にしない。
|
|
28
31
|
if (explicit !== undefined) return explicit;
|
|
32
|
+
// ジャンルの既定は chaff.yaml より弱く、status の既定より強い。--experimental でも、ジャンルが止めたものは止めたまま。
|
|
33
|
+
const fromPreset = preset[rule.id];
|
|
34
|
+
if (fromPreset !== undefined) return fromPreset;
|
|
29
35
|
if (rule.status === "experimental" && !experimental) return "off";
|
|
30
36
|
return "normal";
|
|
31
37
|
};
|
|
32
38
|
|
|
39
|
+
/** Why a rule at off did not run: chaff.yaml turned it off, the genre's preset did, or it is experimental. */
|
|
40
|
+
const offReason = (rule: RuleDefinition, settings: Settings, preset: Settings, genre: string, reasons: Reasons): string => {
|
|
41
|
+
if (settings[rule.id] !== undefined) return reasons.turnedOff;
|
|
42
|
+
return preset[rule.id] === undefined ? reasons.experimental : reasons.presetOff(genre);
|
|
43
|
+
};
|
|
44
|
+
|
|
33
45
|
const reasonsFor = (doc: ProseDocument): Reasons => REASONS[uiLanguageOf(doc.language)];
|
|
34
46
|
|
|
35
47
|
/**
|
|
@@ -41,11 +53,11 @@ const reasonsFor = (doc: ProseDocument): Reasons => REASONS[uiLanguageOf(doc.lan
|
|
|
41
53
|
* 読むので、番号を読めなかった文書でも動く。どちらも capability ではなく、adapter が木を作れるかで決まる。
|
|
42
54
|
*/
|
|
43
55
|
const TREE_NEEDS: ReadonlySet<string> = new Set(["structure", "dates", "quantities"]);
|
|
44
|
-
const VALUE_NEEDS:
|
|
56
|
+
const VALUE_NEEDS: ReadonlySet<string> = new Set(["dates", "quantities"]);
|
|
45
57
|
|
|
46
58
|
const treeNeed = (rule: RuleDefinition, doc: ProseDocument): string | undefined => {
|
|
47
59
|
if (rule.requires.includes("structure")) return treeProblem(doc);
|
|
48
|
-
return rule.requires.some((need) => VALUE_NEEDS.
|
|
60
|
+
return rule.requires.some((need) => VALUE_NEEDS.has(need)) && doc.structure === undefined ? reasonsFor(doc).noStructure(doc.language) : undefined;
|
|
49
61
|
};
|
|
50
62
|
|
|
51
63
|
const treeProblem = (doc: ProseDocument): string | undefined => {
|
|
@@ -106,7 +118,7 @@ const forGenre = (rules: readonly RuleDefinition[], genre: string): RuleDefiniti
|
|
|
106
118
|
*/
|
|
107
119
|
export const neededBy = (rules: readonly RuleDefinition[], settings: Settings, experimental: boolean, genre: string, language: string): AdapterNeeds => ({
|
|
108
120
|
pos: forGenre(rules, genre)
|
|
109
|
-
.filter((rule) => rule.layer !== "L4" && levelFor(rule, settings, experimental) !== "off")
|
|
121
|
+
.filter((rule) => rule.layer !== "L4" && levelFor(rule, settings, experimental, presetLevels(genre)) !== "off")
|
|
110
122
|
.filter((rule) => rule.languages === undefined || rule.languages.includes(language))
|
|
111
123
|
.some((rule) => [...rule.requires, ...rule.uses].some((need) => need === "pos" || need === "lemma")),
|
|
112
124
|
});
|
|
@@ -153,11 +165,12 @@ export const runRules = (
|
|
|
153
165
|
genre: string,
|
|
154
166
|
limits: Limits = {},
|
|
155
167
|
): RunResult => {
|
|
168
|
+
const preset = presetLevels(genre);
|
|
156
169
|
const starts = lineStarts(doc.source);
|
|
157
170
|
const applicable = forGenre(rules, genre);
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
171
|
+
const experimentalOn = applicable.filter((rule) => rule.status === "experimental" && levelFor(rule, settings, experimental, preset) !== "off");
|
|
172
|
+
const forced = experimentalOn.filter((rule) => settings[rule.id] !== undefined).map((rule) => rule.id);
|
|
173
|
+
const presetOn = experimentalOn.filter((rule) => settings[rule.id] === undefined && preset[rule.id] !== undefined).map((rule) => rule.id);
|
|
161
174
|
const outcome = applicable.reduce<{ findings: Finding[]; skipped: Skipped[] }>(
|
|
162
175
|
(acc, rule) => {
|
|
163
176
|
// L4 は意味を読む検査。chaff test が扱う。ここで「検出器が無い」と言わせない。
|
|
@@ -166,11 +179,9 @@ export const runRules = (
|
|
|
166
179
|
}
|
|
167
180
|
const blocked = unmet(rule, doc);
|
|
168
181
|
if (blocked !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: blocked }] };
|
|
169
|
-
const level = levelFor(rule, settings, experimental);
|
|
170
|
-
if (level === "off")
|
|
171
|
-
|
|
172
|
-
return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why }] };
|
|
173
|
-
}
|
|
182
|
+
const level = levelFor(rule, settings, experimental, preset);
|
|
183
|
+
if (level === "off")
|
|
184
|
+
return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: offReason(rule, settings, preset, genre, reasonsFor(doc)) }] };
|
|
174
185
|
const noTags = untagged(rule, doc);
|
|
175
186
|
if (noTags !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: noTags }] };
|
|
176
187
|
// 木は capability ではなく、adapter が structure を持つかで決まる。持たない言語で動かすと「参照先が無い」が 0 件に見える。
|
|
@@ -197,8 +208,10 @@ export const runRules = (
|
|
|
197
208
|
{ findings: [], skipped: [] },
|
|
198
209
|
);
|
|
199
210
|
const composites = applicable
|
|
200
|
-
.filter((rule) => rule.from.length > 0 && levelFor(rule, settings, experimental) !== "off")
|
|
201
|
-
.flatMap((rule) =>
|
|
211
|
+
.filter((rule) => rule.from.length > 0 && levelFor(rule, settings, experimental, preset) !== "off")
|
|
212
|
+
.flatMap((rule) =>
|
|
213
|
+
compositeOf(rule, outcome.findings, limitFor(rule, levelFor(rule, settings, experimental, preset), genre, limits), starts, doc.language),
|
|
214
|
+
);
|
|
202
215
|
const all = [...outcome.findings, ...composites];
|
|
203
|
-
return { ...outcome, findings:
|
|
216
|
+
return { ...outcome, findings: all.toSorted((left, right) => left.line - right.line), forcedExperimental: forced, presetExperimental: presetOn };
|
|
204
217
|
};
|
package/src/sentence-shape.ts
CHANGED
|
@@ -11,10 +11,6 @@ const ENDS = /[。..!?!?]["))」』\s]*$/u;
|
|
|
11
11
|
|
|
12
12
|
export const isClosed = (sentence: Sentence): boolean => ENDS.test(sentence.text);
|
|
13
13
|
|
|
14
|
-
const PREDICATE = new Set(["AUX", "VERB", "ADJ"]);
|
|
15
|
-
|
|
16
|
-
export const hasPredicate = (sentence: Sentence): boolean => (sentence.tokens ?? []).some((token) => PREDICATE.has(token.pos));
|
|
17
|
-
|
|
18
14
|
export const hasParticle = (sentence: Sentence): boolean => (sentence.tokens ?? []).some((token) => token.pos === "ADP" || token.pos === "SCONJ");
|
|
19
15
|
|
|
20
16
|
const SKIP = new Set(["PUNCT", "PART", "SYM"]);
|
|
@@ -46,11 +42,11 @@ const CLOSING_QUOTES = ["」", "』", "\u201d"];
|
|
|
46
42
|
* 引用で終わる文(画面には「確認します。」。)は、書き手の文末が無いので空。
|
|
47
43
|
*/
|
|
48
44
|
export const endingTokens = (sentence: Sentence): Token[] => {
|
|
49
|
-
const reversed =
|
|
45
|
+
const reversed = beforeTrailing(sentence).toReversed();
|
|
50
46
|
const last = reversed.findIndex((token) => !SKIP.has(token.pos));
|
|
51
47
|
if (last === -1 || reversed.slice(0, last).some((token) => CLOSING_QUOTES.some((quote) => token.surface.includes(quote)))) return [];
|
|
52
48
|
const head = reversed.findIndex((token, index) => index >= last && token.pos !== "AUX");
|
|
53
|
-
return reversed.slice(last, head === -1 ? reversed.length : head + 1).
|
|
49
|
+
return reversed.slice(last, head === -1 ? reversed.length : head + 1).toReversed();
|
|
54
50
|
};
|
|
55
51
|
|
|
56
|
-
export const lastContent = (sentence: Sentence): Token | undefined =>
|
|
52
|
+
export const lastContent = (sentence: Sentence): Token | undefined => beforeTrailing(sentence).findLast((token) => !SKIP.has(token.pos));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Config } from "./config/load.ts";
|
|
2
|
+
import type { CliText } from "./cli-text.ts";
|
|
3
|
+
import { GENRES } from "./genre.ts";
|
|
4
|
+
import { unknownGenres, writtenGenres } from "./genre-check.ts";
|
|
5
|
+
import { loadProfiles } from "./profile/load.ts";
|
|
6
|
+
import { unknownProfiles, writtenProfiles } from "./profile/check.ts";
|
|
7
|
+
|
|
8
|
+
const genreProblems = (command: string, genreFlag: string | undefined, config: Config, text: CliText): string[] =>
|
|
9
|
+
unknownGenres(writtenGenres(command, genreFlag, config), GENRES).map((entry) =>
|
|
10
|
+
text.unknownGenre(entry.genre, text.genreWhere(entry.where, entry.files), GENRES),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const profileProblems = (command: string, config: Config, text: CliText): string[] => {
|
|
14
|
+
const known = loadProfiles().map((definition) => definition.id);
|
|
15
|
+
return unknownProfiles(writtenProfiles(command, config), known).map((entry) =>
|
|
16
|
+
text.unknownProfile(entry.profile, text.profileWhere(entry.where, entry.files), known),
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Settings that name something chaff does not have. Each would silently check less than was asked, so the run stops on them. */
|
|
21
|
+
export const settingProblems = (command: string, genreFlag: string | undefined, config: Config, text: CliText): string[] => [
|
|
22
|
+
...genreProblems(command, genreFlag, config, text),
|
|
23
|
+
...profileProblems(command, config, text),
|
|
24
|
+
];
|
package/src/structure/build.ts
CHANGED
|
@@ -73,9 +73,9 @@ const open = (state: State, frame: Frame): void => {
|
|
|
73
73
|
state.stack.push(frame);
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
const nearestNumbered = (state: State): Frame | undefined =>
|
|
76
|
+
const nearestNumbered = (state: State): Frame | undefined => state.stack.findLast((frame) => frame.numbered !== undefined);
|
|
77
77
|
|
|
78
|
-
const nearestSection = (state: State): Frame | undefined =>
|
|
78
|
+
const nearestSection = (state: State): Frame | undefined => state.stack.findLast((frame) => frame.draft.kind === "section");
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* 条番号や 4.2 はそれだけで番地になる。項・号・(a) は親の番地に続ける。親が無ければ節の中の番号とする。
|
|
@@ -160,12 +160,12 @@ const universalNumber = (patterns: StructurePatterns, text: string, context: Num
|
|
|
160
160
|
};
|
|
161
161
|
|
|
162
162
|
/** 行の中の定義・参照・義務・数量を、いま開いている最も内側の節点の子にする。 */
|
|
163
|
-
const enclosingArticle = (state: State): Draft | undefined =>
|
|
163
|
+
const enclosingArticle = (state: State): Draft | undefined => state.stack.findLast((frame) => frame.draft.kind === "article")?.draft;
|
|
164
164
|
|
|
165
165
|
/** 条より広い範囲(「In this Part」)なら、その深さの開いているまとまり。開いていなければ(Part の見出しの無い文書)無い。 */
|
|
166
166
|
const widerScope = (state: State, patterns: StructurePatterns, text: string): Draft | undefined => {
|
|
167
167
|
const depth = patterns.definitionScopeDepth?.(text);
|
|
168
|
-
return depth === undefined ? undefined :
|
|
168
|
+
return depth === undefined ? undefined : state.stack.findLast((frame) => frame.numbered?.depth === depth)?.draft;
|
|
169
169
|
};
|
|
170
170
|
|
|
171
171
|
/** 範囲を宣言した行を持つまとまり。条の中なら条、条の外(Markdown の見出しの下、Part の直下)なら開いている一番内側のもの。 */
|
|
@@ -200,7 +200,7 @@ const addLeaves = (state: State, patterns: StructurePatterns, line: Line, wrappe
|
|
|
200
200
|
attrs: mention.attrs,
|
|
201
201
|
})),
|
|
202
202
|
)
|
|
203
|
-
.
|
|
203
|
+
.toSorted((left, right) => left.start - right.start);
|
|
204
204
|
leaves.forEach((leaf) =>
|
|
205
205
|
parent.children.push({
|
|
206
206
|
kind: leaf.kind,
|
package/src/structure/cite.ts
CHANGED
|
@@ -73,7 +73,7 @@ const addressedNodes = (tree: StructureNode): Addressed[] => {
|
|
|
73
73
|
const current = pending.pop();
|
|
74
74
|
if (current === undefined) break;
|
|
75
75
|
if (current.node.address !== "") found.push(current);
|
|
76
|
-
|
|
76
|
+
current.node.children.toReversed().forEach((child) => pending.push({ node: child, depth: current.depth + 1 }));
|
|
77
77
|
}
|
|
78
78
|
return found;
|
|
79
79
|
};
|
package/src/structure/issues.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { namesAbsentUnit } from "./unit-word.ts";
|
|
|
7
7
|
|
|
8
8
|
export type StructureIssue = { readonly offset: number; readonly values: Readonly<Record<string, string | number>> };
|
|
9
9
|
|
|
10
|
-
const NUMBERED:
|
|
10
|
+
const NUMBERED: ReadonlySet<string> = new Set(["chapter", "article", "item"]);
|
|
11
11
|
|
|
12
12
|
/** 木を上から順に平らにする。文書の中での順番と同じになる。再帰にしないのは、深い木でスタックを使い切らないため。 */
|
|
13
13
|
export const inDocumentOrder = (tree: StructureNode): StructureNode[] => {
|
|
@@ -17,7 +17,7 @@ export const inDocumentOrder = (tree: StructureNode): StructureNode[] => {
|
|
|
17
17
|
const node = pending.pop();
|
|
18
18
|
if (node === undefined) break;
|
|
19
19
|
order.push(node);
|
|
20
|
-
pending.push(...
|
|
20
|
+
pending.push(...node.children.toReversed());
|
|
21
21
|
}
|
|
22
22
|
return order;
|
|
23
23
|
};
|
|
@@ -57,6 +57,8 @@ const citesOtherDocument =
|
|
|
57
57
|
(node: StructureNode): boolean =>
|
|
58
58
|
node.attrs["document"] !== undefined || (node.attrs["citedTag"] !== undefined && listed.has(textOf(node, "citedTag")));
|
|
59
59
|
|
|
60
|
+
const numbering = (node: StructureNode): unknown => node.attrs["numbering"];
|
|
61
|
+
|
|
60
62
|
/**
|
|
61
63
|
* 参照の番地が木に無い。他の文書の名前が付いた参照(民法第709条、Section 9 of the Master Agreement)は引かない。
|
|
62
64
|
* 条を 1 つも持たない文書も見ない。他の文書を指しているだけかもしれない。
|
|
@@ -67,10 +69,9 @@ export const danglingReferences = (tree: StructureNode, source: string): Structu
|
|
|
67
69
|
if (!nodes.some((node) => node.kind === "article")) return [];
|
|
68
70
|
const tagged = nodes.some((node) => node.attrs["citedTag"] !== undefined);
|
|
69
71
|
const citesOther = citesOtherDocument(tagged ? listedTags(source) : new Set());
|
|
70
|
-
const addresses = new Set(nodes.filter((node) => NUMBERED.
|
|
72
|
+
const addresses = new Set(nodes.filter((node) => NUMBERED.has(node.kind)).flatMap(keysOf));
|
|
71
73
|
// Section で組んだ法令が "Articles 13 to 21 of the UK GDPR" と別の文書の Article を名指ししていれば、名の無い "Article 6(3)" もそちら。
|
|
72
74
|
// 名指しがあれば、名の無い "Article 9" が書き間違いか向こうの条かは区別できないので黙る。名指しが無い文書では報告する。
|
|
73
|
-
const numbering = (node: StructureNode): unknown => node.attrs["numbering"];
|
|
74
75
|
const numberings = new Set(nodes.flatMap((node) => (node.kind === "article" && node.numbering !== undefined ? [node.numbering] : [])));
|
|
75
76
|
const citedElsewhere = new Set(nodes.filter((node) => node.kind === "reference" && citesOther(node)).map(numbering));
|
|
76
77
|
const otherNumbering = (node: StructureNode): boolean =>
|
|
@@ -94,7 +95,7 @@ const definitionsInOrder = (tree: StructureNode): Definition[] => {
|
|
|
94
95
|
if (current === undefined) break;
|
|
95
96
|
if (current.node.kind === "definition") found.push(current);
|
|
96
97
|
const article = current.node.kind === "article" ? current.node.address : current.article;
|
|
97
|
-
|
|
98
|
+
current.node.children.toReversed().forEach((child) => pending.push({ node: child, article }));
|
|
98
99
|
}
|
|
99
100
|
return found;
|
|
100
101
|
};
|