@yuu1111/quality-check 0.11.0 → 1.0.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.ja.md CHANGED
@@ -42,15 +42,11 @@ export default defineConfig({
42
42
  },
43
43
  config: {
44
44
  "comment-check": {
45
- enable: ["japanese-period"],
46
45
  ignore: ["another-project"],
46
+ rules: { "japanese-period": "on" },
47
47
  },
48
- "document-style-check": {
49
- enable: ["japanese-period"],
50
- },
51
- "tsdoc-check": {
52
- error: ["missing-doc"],
53
- },
48
+ "document-style-check": { enable: true },
49
+ "tsdoc-check": { error: true },
54
50
  },
55
51
  });
56
52
  ```
@@ -91,9 +87,9 @@ engineは `biome` → `typecheck` → `knip` → `code-style-check` → `comment
91
87
  | `typecheck` | `tsc --noEmit` | `args`、`projects` 設定は `tsconfig.json` が持つ |
92
88
  | `knip` | `knip` | `args` 設定は `knip.ts` が持つ |
93
89
  | `code-style-check` | `code-style-check --json` | `ignore`、`targets`、`args` |
94
- | `comment-check` | `comment-check --json` | `ignore`、`targets`、`args`、`enable`(有効にするrule名) |
95
- | `document-style-check` | `document-style-check lint --json` | `ignore`、`targets`、`args`、`enable`(有効にするrule名) |
96
- | `tsdoc-check` | `tsdoc-check --json` | `ignore`、`targets`、`args`、`enable`(有効にするrule名)、`error`(違反として扱うrule名) |
90
+ | `comment-check` | `comment-check --json` | `ignore`、`targets`、`args`、`enable`、`rules` |
91
+ | `document-style-check` | `document-style-check lint --json` | `ignore`、`targets`、`args`、`enable`、`rules` |
92
+ | `tsdoc-check` | `tsdoc-check --json` | `ignore`、`targets`、`args`、`enable`、`error`、`rules` |
97
93
 
98
94
  `args` はengineの既定引数の後ろへ足す
99
95
  設定fileで表せない起動条件や、engineの引数が変わったときの逃げ道として使う
@@ -105,8 +101,13 @@ engineが受け取らない条件は渡さず、その旨をそのengineのsecti
105
101
  biome: ignore skipped (biome.json holds its settings)
106
102
  ```
107
103
 
108
- `enable` は `comment-check` と `document-style-check` `tsdoc-check` の `--enable <rule>` になり、他のengineでは拒否される
109
- 値は導入済みengineが `rule-ids` で公開するrule名で型付けするため、engineが知らない名前は起動時の失敗ではなく型errorになる
104
+ `enable` と `error` はruleをまとめて選び、`rules` ruleを1つずつ選ぶ
105
+ `enable: true` はengineが既定で無効にしているruleを全て有効にする
106
+ `error: true` は `tsdoc-check` の全ruleを違反として扱い、opt-inのruleは先に有効にする 違反へ上げられるのはこのengineだけ
107
+ `rules` はrule名をkeyにして `off`、`on`、`error` のいずれかを渡す `on` はengineの既定のまま、`error` はそのruleだけpresetと同じ扱いにする
108
+ keyがrule名そのものなので導入済みengineのunionで型付けし、engineが知らない名前は起動時の失敗ではなく型errorになる
109
+ `error` を取れるのは `tsdoc-check` だけ、`off` を取れるのは既定で無効のruleだけで、既定で有効なruleを無効にする引数をengineは持たない
110
+ 選んだruleは `--enable <rule>` になり、`tsdoc-check` では `--error <rule>` にもなる 他のengineはこれらのoptionを拒否する
110
111
 
111
112
  `comment-check` はbaseline差分を無効化する未作成のpathを渡して起動する
112
113
  新規と解消済みの判定はengineごとではなく統合CLIが1つのbaseline fileで行うため、既存の `comment-baseline.json` がある場合は `--update-baseline` で移す
package/README.md CHANGED
@@ -43,15 +43,11 @@ export default defineConfig({
43
43
  },
44
44
  config: {
45
45
  "comment-check": {
46
- enable: ["japanese-period"],
47
46
  ignore: ["another-project"],
47
+ rules: { "japanese-period": "on" },
48
48
  },
49
- "document-style-check": {
50
- enable: ["japanese-period"],
51
- },
52
- "tsdoc-check": {
53
- error: ["missing-doc"],
54
- },
49
+ "document-style-check": { enable: true },
50
+ "tsdoc-check": { error: true },
55
51
  },
56
52
  });
57
53
  ```
@@ -94,9 +90,9 @@ Each engine runs:
94
90
  | `typecheck` | `tsc --noEmit` | `args`, `projects`; `tsconfig.json` holds the settings |
95
91
  | `knip` | `knip` | `args`; `knip.ts` holds the settings |
96
92
  | `code-style-check` | `code-style-check --json` | `ignore`, `targets`, `args` |
97
- | `comment-check` | `comment-check --json` | `ignore`, `targets`, `args`, `enable` (rule names to turn on) |
98
- | `document-style-check` | `document-style-check lint --json` | `ignore`, `targets`, `args`, `enable` (rule names to turn on) |
99
- | `tsdoc-check` | `tsdoc-check --json` | `ignore`, `targets`, `args`, `enable` (rule names to turn on), `error` (rule names to fail on) |
93
+ | `comment-check` | `comment-check --json` | `ignore`, `targets`, `args`, `enable`, `rules` |
94
+ | `document-style-check` | `document-style-check lint --json` | `ignore`, `targets`, `args`, `enable`, `rules` |
95
+ | `tsdoc-check` | `tsdoc-check --json` | `ignore`, `targets`, `args`, `enable`, `error`, `rules` |
100
96
 
101
97
  `args` is appended after the engine defaults, for conditions the config cannot express and for the case where an engine changes its arguments.
102
98
 
@@ -107,8 +103,13 @@ A condition that an engine does not take is not passed on, and the section says
107
103
  biome: ignore skipped (biome.json holds its settings)
108
104
  ```
109
105
 
110
- `enable` becomes `--enable <rule>` on `comment-check`, `document-style-check`, and `tsdoc-check`, and the other engines reject the field.
111
- Each value is typed with the rule names the installed engine publishes from its `rule-ids` entry, so a name that engine does not know is a type error rather than a start-up failure.
106
+ `enable` and `error` pick rules in bulk, and `rules` picks one rule at a time.
107
+ `enable: true` turns on every rule the engine keeps off by default.
108
+ `error: true` turns every rule of `tsdoc-check` into an error, enabling the opt-in ones first; it is the only condition that raises a rule.
109
+ A `rules` entry names one rule and gives it `off`, `on`, or `error`, where `on` keeps the engine default and `error` means the same as the `error` preset for that rule.
110
+ The `rules` key is the rule name itself, so the installed engine's union types it and a name that engine does not know is a type error rather than a start-up failure.
111
+ Only `tsdoc-check` takes `error` in `rules`, and only rules that are off by default take `off`, because no engine can turn off a rule that is on by default.
112
+ Each selected rule becomes `--enable <rule>` and, on `tsdoc-check`, `--error <rule>`; the other engines reject the fields.
112
113
 
113
114
  `comment-check` runs with an unwritten baseline path so that it reports every finding.
114
115
  The new-and-resolved diff is done by this CLI from a single baseline file, so an existing `comment-baseline.json` is moved over with `--update-baseline`.
package/dist/cli.js CHANGED
@@ -194,6 +194,18 @@ function colorEnabled(stream, env) {
194
194
  import { existsSync as existsSync2 } from "fs";
195
195
  import { resolve } from "path";
196
196
  import { pathToFileURL } from "url";
197
+ import {
198
+ OPT_IN_RULE_IDS as COMMENT_CHECK_OPT_IN_RULE_IDS,
199
+ RULE_IDS as COMMENT_CHECK_RULE_IDS
200
+ } from "@yuu1111/comment-check/rule-ids";
201
+ import {
202
+ OPT_IN_RULE_IDS as DOCUMENT_STYLE_CHECK_OPT_IN_RULE_IDS,
203
+ RULE_IDS as DOCUMENT_STYLE_CHECK_RULE_IDS
204
+ } from "@yuu1111/document-style-check/rule-ids";
205
+ import {
206
+ OPT_IN_RULE_IDS as TSDOC_CHECK_OPT_IN_RULE_IDS,
207
+ KNOWN_RULE_NAMES as TSDOC_CHECK_RULE_NAMES
208
+ } from "@yuu1111/tsdoc-check/rule-ids";
197
209
  var ENGINE_NAMES = [
198
210
  "biome",
199
211
  "typecheck",
@@ -203,6 +215,27 @@ var ENGINE_NAMES = [
203
215
  "document-style-check",
204
216
  "tsdoc-check"
205
217
  ];
218
+ var RULE_VOCABULARY = {
219
+ biome: { all: [], optIn: [], promotes: false },
220
+ typecheck: { all: [], optIn: [], promotes: false },
221
+ knip: { all: [], optIn: [], promotes: false },
222
+ "code-style-check": { all: [], optIn: [], promotes: false },
223
+ "comment-check": {
224
+ all: COMMENT_CHECK_RULE_IDS,
225
+ optIn: COMMENT_CHECK_OPT_IN_RULE_IDS,
226
+ promotes: false
227
+ },
228
+ "document-style-check": {
229
+ all: DOCUMENT_STYLE_CHECK_RULE_IDS,
230
+ optIn: DOCUMENT_STYLE_CHECK_OPT_IN_RULE_IDS,
231
+ promotes: false
232
+ },
233
+ "tsdoc-check": {
234
+ all: TSDOC_CHECK_RULE_NAMES,
235
+ optIn: TSDOC_CHECK_OPT_IN_RULE_IDS,
236
+ promotes: true
237
+ }
238
+ };
206
239
  var DEFAULT_CONFIG_FILES = [
207
240
  "quality.config.ts",
208
241
  "quality.config.mts",
@@ -221,9 +254,9 @@ var ENGINE_OPTION_KEYS = {
221
254
  typecheck: ["args", "ignore", "projects", "targets"],
222
255
  knip: ["args", "ignore", "targets"],
223
256
  "code-style-check": ["args", "ignore", "targets"],
224
- "comment-check": ["args", "enable", "ignore", "targets"],
225
- "document-style-check": ["args", "enable", "ignore", "targets"],
226
- "tsdoc-check": ["args", "enable", "error", "ignore", "targets"]
257
+ "comment-check": ["args", "enable", "ignore", "rules", "targets"],
258
+ "document-style-check": ["args", "enable", "ignore", "rules", "targets"],
259
+ "tsdoc-check": ["args", "enable", "error", "ignore", "rules", "targets"]
227
260
  };
228
261
  function readStringArray(value, field) {
229
262
  if (value === undefined) {
@@ -234,6 +267,41 @@ function readStringArray(value, field) {
234
267
  }
235
268
  return [...value];
236
269
  }
270
+ function readBoolean(value, field) {
271
+ if (value === undefined) {
272
+ return;
273
+ }
274
+ if (typeof value !== "boolean") {
275
+ throw new Error(`${field} must be a boolean`);
276
+ }
277
+ return value;
278
+ }
279
+ function readRuleStates(value, field, name) {
280
+ if (value === undefined) {
281
+ return;
282
+ }
283
+ if (!isJsonObject2(value)) {
284
+ throw new Error(`${field} must be an object`);
285
+ }
286
+ const vocabulary = RULE_VOCABULARY[name];
287
+ const states = {};
288
+ for (const [rule, state] of Object.entries(value)) {
289
+ if (!vocabulary.all.includes(rule)) {
290
+ throw new Error(`${field} has an unknown rule: ${rule}`);
291
+ }
292
+ if (state !== "off" && state !== "on" && state !== "error") {
293
+ throw new Error(`${field}.${rule} must be "off", "on" or "error"`);
294
+ }
295
+ if (state === "error" && !vocabulary.promotes) {
296
+ throw new Error(`${field}.${rule}: ${name} cannot treat a rule as an error`);
297
+ }
298
+ if (state === "off" && !vocabulary.optIn.includes(rule)) {
299
+ throw new Error(`${field}.${rule}: ${name} cannot turn off a rule that is on by default`);
300
+ }
301
+ states[rule] = state;
302
+ }
303
+ return states;
304
+ }
237
305
  function readBaseline2(value, source) {
238
306
  if (value === undefined || value === false) {
239
307
  return value;
@@ -265,13 +333,17 @@ function parseEngines(value, source) {
265
333
  function parseEngineExtras(value, source, name) {
266
334
  const extras = {};
267
335
  if (name === "comment-check" || name === "document-style-check" || name === "tsdoc-check") {
268
- const enable = readStringArray(value.enable, `${source}: config.${name}.enable`);
336
+ const enable = readBoolean(value.enable, `${source}: config.${name}.enable`);
269
337
  if (enable !== undefined) {
270
338
  extras.enable = enable;
271
339
  }
340
+ const rules = readRuleStates(value.rules, `${source}: config.${name}.rules`, name);
341
+ if (rules !== undefined) {
342
+ extras.rules = rules;
343
+ }
272
344
  }
273
345
  if (name === "tsdoc-check") {
274
- const error = readStringArray(value.error, `${source}: config.${name}.error`);
346
+ const error = readBoolean(value.error, `${source}: config.${name}.error`);
275
347
  if (error !== undefined) {
276
348
  extras.error = error;
277
349
  }
@@ -460,19 +532,54 @@ function buildTypecheckCommand(executable, context, project) {
460
532
  ...options.args ?? []
461
533
  ];
462
534
  }
463
- function enableArguments(options) {
464
- return (options?.enable ?? []).flatMap((rule) => ["--enable", rule]);
535
+ function applyRuleState(vocabulary, selected, rule, state) {
536
+ if (state === "off") {
537
+ selected.enable.delete(rule);
538
+ selected.error.delete(rule);
539
+ return;
540
+ }
541
+ if (state === "on") {
542
+ selected.error.delete(rule);
543
+ } else {
544
+ selected.error.add(rule);
545
+ }
546
+ if (vocabulary.optIn.includes(rule)) {
547
+ selected.enable.add(rule);
548
+ }
549
+ }
550
+ function selectRules(name, options) {
551
+ const selection = options ?? {};
552
+ const vocabulary = RULE_VOCABULARY[name];
553
+ const selected = { enable: new Set, error: new Set };
554
+ if (selection.enable === true) {
555
+ for (const rule of vocabulary.optIn) {
556
+ selected.enable.add(rule);
557
+ }
558
+ }
559
+ if (selection.error === true) {
560
+ for (const rule of vocabulary.all) {
561
+ applyRuleState(vocabulary, selected, rule, "error");
562
+ }
563
+ }
564
+ for (const [rule, state] of Object.entries(selection.rules ?? {})) {
565
+ applyRuleState(vocabulary, selected, rule, state);
566
+ }
567
+ return { enable: [...selected.enable], error: [...selected.error] };
465
568
  }
466
569
  function buildEngineCommand(name, executable, context) {
467
570
  const options = engineConfig(context.config, name) ?? {};
468
571
  const limits = ENGINE_LIMITS[name];
572
+ const selection = selectRules(name, options);
573
+ const enableArguments = selection.enable.flatMap((rule) => [
574
+ "--enable",
575
+ rule
576
+ ]);
577
+ const errorArguments = selection.error.flatMap((rule) => ["--error", rule]);
469
578
  const extra = options.args ?? [];
470
579
  const ignores = limits.ignore === undefined ? [...options.ignore ?? [], ...context.overrides.ignore] : [];
471
580
  const requested = context.overrides.targets.length > 0 ? context.overrides.targets : options.targets ?? ["."];
472
581
  const targets = limits.targets === undefined ? requested : [];
473
582
  const ignoreArguments = ignores.flatMap((ignore) => ["--ignore", ignore]);
474
- const rules = engineConfig(context.config, "tsdoc-check")?.error ?? [];
475
- const errorArguments = rules.flatMap((rule) => ["--error", rule]);
476
583
  if (name === "biome") {
477
584
  return [
478
585
  executable,
@@ -494,7 +601,7 @@ function buildEngineCommand(name, executable, context) {
494
601
  "--json",
495
602
  "--baseline",
496
603
  context.rawBaseline,
497
- ...enableArguments(engineConfig(context.config, "comment-check")),
604
+ ...enableArguments,
498
605
  ...targets,
499
606
  ...ignoreArguments,
500
607
  ...extra
@@ -505,7 +612,7 @@ function buildEngineCommand(name, executable, context) {
505
612
  executable,
506
613
  "lint",
507
614
  "--json",
508
- ...enableArguments(engineConfig(context.config, "document-style-check")),
615
+ ...enableArguments,
509
616
  ...targets,
510
617
  ...ignoreArguments,
511
618
  ...extra
@@ -517,7 +624,7 @@ function buildEngineCommand(name, executable, context) {
517
624
  return [
518
625
  executable,
519
626
  "--json",
520
- ...enableArguments(engineConfig(context.config, "tsdoc-check")),
627
+ ...enableArguments,
521
628
  ...errorArguments,
522
629
  ...targets,
523
630
  ...ignoreArguments,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuu1111/quality-check",
3
- "version": "0.11.0",
3
+ "version": "1.0.0",
4
4
  "description": "Integrated quality check runner",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,7 +23,7 @@
23
23
  "dist"
24
24
  ],
25
25
  "scripts": {
26
- "build": "bun build src/cli.ts --target=bun --outdir=dist"
26
+ "build": "bun build src/cli.ts --target=bun --outdir=dist --external @yuu1111/comment-check/rule-ids --external @yuu1111/document-style-check/rule-ids --external @yuu1111/tsdoc-check/rule-ids"
27
27
  },
28
28
  "keywords": [
29
29
  "quality",
package/src/config.ts CHANGED
@@ -1,11 +1,20 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- import type { OptInRuleId as CommentCheckRuleName } from "@yuu1111/comment-check/rule-ids";
5
- import type { OptInRuleId as DocumentStyleCheckRuleName } from "@yuu1111/document-style-check/rule-ids";
6
- import type {
7
- OptInRuleId as TsdocCheckRuleName,
8
- TsdocRule,
4
+ import {
5
+ OPT_IN_RULE_IDS as COMMENT_CHECK_OPT_IN_RULE_IDS,
6
+ RULE_IDS as COMMENT_CHECK_RULE_IDS,
7
+ type OptInRuleId as CommentCheckRuleName,
8
+ } from "@yuu1111/comment-check/rule-ids";
9
+ import {
10
+ OPT_IN_RULE_IDS as DOCUMENT_STYLE_CHECK_OPT_IN_RULE_IDS,
11
+ RULE_IDS as DOCUMENT_STYLE_CHECK_RULE_IDS,
12
+ type OptInRuleId as DocumentStyleCheckRuleName,
13
+ } from "@yuu1111/document-style-check/rule-ids";
14
+ import {
15
+ OPT_IN_RULE_IDS as TSDOC_CHECK_OPT_IN_RULE_IDS,
16
+ KNOWN_RULE_NAMES as TSDOC_CHECK_RULE_NAMES,
17
+ type TsdocRule,
9
18
  } from "@yuu1111/tsdoc-check/rule-ids";
10
19
 
11
20
  /**
@@ -42,26 +51,32 @@ export interface EngineOptions {
42
51
  * comment-checkへ渡す起動条件
43
52
  */
44
53
  export interface CommentCheckOptions extends EngineOptions {
45
- /** 既定で無効のopt-in ruleのうち有効にするrule名 comment-checkが公開するunionで縛る */
46
- enable?: CommentCheckRuleName[];
54
+ /** 既定で無効のopt-in ruleを全て有効にする */
55
+ enable?: boolean;
56
+ /** rule名ごとの状態 offで無効 onで有効 省略したruleはengineの既定に従う */
57
+ rules?: Partial<Record<CommentCheckRuleName, "off" | "on">>;
47
58
  }
48
59
 
49
60
  /**
50
61
  * document-style-checkへ渡す起動条件
51
62
  */
52
63
  export interface DocumentStyleCheckOptions extends EngineOptions {
53
- /** 既定で無効のopt-in ruleのうち有効にするrule名 document-style-checkが公開するunionで縛る */
54
- enable?: DocumentStyleCheckRuleName[];
64
+ /** 既定で無効のopt-in ruleを全て有効にする */
65
+ enable?: boolean;
66
+ /** rule名ごとの状態 offで無効 onで有効 省略したruleはengineの既定に従う */
67
+ rules?: Partial<Record<DocumentStyleCheckRuleName, "off" | "on">>;
55
68
  }
56
69
 
57
70
  /**
58
71
  * TSDoc検査へ渡す起動条件
59
72
  */
60
73
  export interface TsdocCheckOptions extends EngineOptions {
61
- /** 既定で無効のopt-in ruleのうち有効にするrule名 tsdoc-checkが公開するunionで縛る */
62
- enable?: TsdocCheckRuleName[];
63
- /** 違反として扱うrule tsdoc-checkが公開するunionで縛る */
64
- error?: TsdocRule[];
74
+ /** 既定で無効のopt-in ruleを全て有効にする */
75
+ enable?: boolean;
76
+ /** 全てのruleを違反として扱う opt-in ruleは有効にしてから上げる */
77
+ error?: boolean;
78
+ /** rule名ごとの状態 offで無効 onで既定のseverity errorで違反として扱う */
79
+ rules?: Partial<Record<TsdocRule, "off" | "on" | "error">>;
65
80
  }
66
81
 
67
82
  /**
@@ -85,6 +100,39 @@ export interface EngineConfigMap {
85
100
  "tsdoc-check": TsdocCheckOptions;
86
101
  }
87
102
 
103
+ /**
104
+ * rule名ごとの状態
105
+ */
106
+ export type RuleState = "off" | "on" | "error";
107
+
108
+ /**
109
+ * engineが公開するrule語彙 全ruleと既定で無効のopt-in ruleを持つ
110
+ */
111
+ export const RULE_VOCABULARY: Record<
112
+ EngineName,
113
+ { all: readonly string[]; optIn: readonly string[]; promotes: boolean }
114
+ > = {
115
+ biome: { all: [], optIn: [], promotes: false },
116
+ typecheck: { all: [], optIn: [], promotes: false },
117
+ knip: { all: [], optIn: [], promotes: false },
118
+ "code-style-check": { all: [], optIn: [], promotes: false },
119
+ "comment-check": {
120
+ all: COMMENT_CHECK_RULE_IDS,
121
+ optIn: COMMENT_CHECK_OPT_IN_RULE_IDS,
122
+ promotes: false,
123
+ },
124
+ "document-style-check": {
125
+ all: DOCUMENT_STYLE_CHECK_RULE_IDS,
126
+ optIn: DOCUMENT_STYLE_CHECK_OPT_IN_RULE_IDS,
127
+ promotes: false,
128
+ },
129
+ "tsdoc-check": {
130
+ all: TSDOC_CHECK_RULE_NAMES,
131
+ optIn: TSDOC_CHECK_OPT_IN_RULE_IDS,
132
+ promotes: true,
133
+ },
134
+ };
135
+
88
136
  /**
89
137
  * quality.config.tsが受け付ける統合検査の設定
90
138
  */
@@ -137,9 +185,9 @@ const ENGINE_OPTION_KEYS: Record<EngineName, readonly string[]> = {
137
185
  typecheck: ["args", "ignore", "projects", "targets"],
138
186
  knip: ["args", "ignore", "targets"],
139
187
  "code-style-check": ["args", "ignore", "targets"],
140
- "comment-check": ["args", "enable", "ignore", "targets"],
141
- "document-style-check": ["args", "enable", "ignore", "targets"],
142
- "tsdoc-check": ["args", "enable", "error", "ignore", "targets"],
188
+ "comment-check": ["args", "enable", "ignore", "rules", "targets"],
189
+ "document-style-check": ["args", "enable", "ignore", "rules", "targets"],
190
+ "tsdoc-check": ["args", "enable", "error", "ignore", "rules", "targets"],
143
191
  };
144
192
 
145
193
  function readStringArray(value: unknown, field: string): string[] | undefined {
@@ -155,6 +203,54 @@ function readStringArray(value: unknown, field: string): string[] | undefined {
155
203
  return [...value] as string[];
156
204
  }
157
205
 
206
+ function readBoolean(value: unknown, field: string): boolean | undefined {
207
+ if (value === undefined) {
208
+ return undefined;
209
+ }
210
+ if (typeof value !== "boolean") {
211
+ throw new Error(`${field} must be a boolean`);
212
+ }
213
+ return value;
214
+ }
215
+
216
+ /**
217
+ * rule名ごとの状態を読み取る engineが公開する語彙とengineが受け付ける状態で検証する
218
+ */
219
+ function readRuleStates(
220
+ value: unknown,
221
+ field: string,
222
+ name: EngineName,
223
+ ): Record<string, RuleState> | undefined {
224
+ if (value === undefined) {
225
+ return undefined;
226
+ }
227
+ if (!isJsonObject(value)) {
228
+ throw new Error(`${field} must be an object`);
229
+ }
230
+ const vocabulary = RULE_VOCABULARY[name];
231
+ const states: Record<string, RuleState> = {};
232
+ for (const [rule, state] of Object.entries(value)) {
233
+ if (!vocabulary.all.includes(rule)) {
234
+ throw new Error(`${field} has an unknown rule: ${rule}`);
235
+ }
236
+ if (state !== "off" && state !== "on" && state !== "error") {
237
+ throw new Error(`${field}.${rule} must be "off", "on" or "error"`);
238
+ }
239
+ if (state === "error" && !vocabulary.promotes) {
240
+ throw new Error(
241
+ `${field}.${rule}: ${name} cannot treat a rule as an error`,
242
+ );
243
+ }
244
+ if (state === "off" && !vocabulary.optIn.includes(rule)) {
245
+ throw new Error(
246
+ `${field}.${rule}: ${name} cannot turn off a rule that is on by default`,
247
+ );
248
+ }
249
+ states[rule] = state;
250
+ }
251
+ return states;
252
+ }
253
+
158
254
  function readBaseline(
159
255
  value: unknown,
160
256
  source: string,
@@ -194,9 +290,10 @@ function parseEngines(
194
290
  }
195
291
 
196
292
  type ParsedEngineOptions = EngineOptions & {
197
- enable?: string[];
198
- error?: string[];
293
+ enable?: boolean;
294
+ error?: boolean;
199
295
  projects?: string[];
296
+ rules?: Record<string, RuleState>;
200
297
  };
201
298
 
202
299
  /**
@@ -213,19 +310,24 @@ function parseEngineExtras(
213
310
  name === "document-style-check" ||
214
311
  name === "tsdoc-check"
215
312
  ) {
216
- const enable = readStringArray(
313
+ const enable = readBoolean(
217
314
  value.enable,
218
315
  `${source}: config.${name}.enable`,
219
316
  );
220
317
  if (enable !== undefined) {
221
318
  extras.enable = enable;
222
319
  }
320
+ const rules = readRuleStates(
321
+ value.rules,
322
+ `${source}: config.${name}.rules`,
323
+ name,
324
+ );
325
+ if (rules !== undefined) {
326
+ extras.rules = rules;
327
+ }
223
328
  }
224
329
  if (name === "tsdoc-check") {
225
- const error = readStringArray(
226
- value.error,
227
- `${source}: config.${name}.error`,
228
- );
330
+ const error = readBoolean(value.error, `${source}: config.${name}.error`);
229
331
  if (error !== undefined) {
230
332
  extras.error = error;
231
333
  }
package/src/engines.ts CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  type EngineOptions,
6
6
  engineConfig,
7
7
  type QualityConfig,
8
+ RULE_VOCABULARY,
9
+ type RuleState,
8
10
  } from "./config";
9
11
 
10
12
  /**
@@ -194,12 +196,76 @@ function buildTypecheckCommand(
194
196
  }
195
197
 
196
198
  /**
197
- * opt-in ruleを有効にするコマンド引数へ展開する
199
+ * 設定fileが持つruleの指定
198
200
  */
199
- function enableArguments(
200
- options: { enable?: string[] } | null | undefined,
201
- ): string[] {
202
- return (options?.enable ?? []).flatMap((rule) => ["--enable", rule]);
201
+ interface RuleSelection {
202
+ enable?: boolean;
203
+ error?: boolean;
204
+ rules?: Record<string, RuleState>;
205
+ }
206
+
207
+ /**
208
+ * engineが公開するrule語彙
209
+ */
210
+ type RuleVocabulary = (typeof RULE_VOCABULARY)[EngineName];
211
+
212
+ /**
213
+ * 選んだruleを有効の一覧と違反の一覧へ反映する
214
+ *
215
+ * @param vocabulary - 反映先engineのrule語彙
216
+ * @param selected - 反映先のrule名の集合
217
+ * @param rule - 反映するrule名
218
+ * @param state - 反映する状態
219
+ */
220
+ function applyRuleState(
221
+ vocabulary: RuleVocabulary,
222
+ selected: { enable: Set<string>; error: Set<string> },
223
+ rule: string,
224
+ state: RuleState,
225
+ ): void {
226
+ if (state === "off") {
227
+ selected.enable.delete(rule);
228
+ selected.error.delete(rule);
229
+ return;
230
+ }
231
+ if (state === "on") {
232
+ selected.error.delete(rule);
233
+ } else {
234
+ selected.error.add(rule);
235
+ }
236
+ if (vocabulary.optIn.includes(rule)) {
237
+ selected.enable.add(rule);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * ruleの一括指定と個別指定をengineへ渡すruleの一覧へ展開する
243
+ *
244
+ * @param name - 展開するengine名
245
+ * @param options - engineへ渡す起動条件
246
+ * @returns --enableへ渡すrule名と--errorへ渡すrule名の組
247
+ */
248
+ function selectRules(
249
+ name: EngineName,
250
+ options: EngineOptions | null | undefined,
251
+ ): { enable: string[]; error: string[] } {
252
+ const selection = (options ?? {}) as RuleSelection;
253
+ const vocabulary = RULE_VOCABULARY[name];
254
+ const selected = { enable: new Set<string>(), error: new Set<string>() };
255
+ if (selection.enable === true) {
256
+ for (const rule of vocabulary.optIn) {
257
+ selected.enable.add(rule);
258
+ }
259
+ }
260
+ if (selection.error === true) {
261
+ for (const rule of vocabulary.all) {
262
+ applyRuleState(vocabulary, selected, rule, "error");
263
+ }
264
+ }
265
+ for (const [rule, state] of Object.entries(selection.rules ?? {})) {
266
+ applyRuleState(vocabulary, selected, rule, state);
267
+ }
268
+ return { enable: [...selected.enable], error: [...selected.error] };
203
269
  }
204
270
 
205
271
  /**
@@ -217,6 +283,12 @@ export function buildEngineCommand(
217
283
  ): string[] {
218
284
  const options: EngineOptions = engineConfig(context.config, name) ?? {};
219
285
  const limits = ENGINE_LIMITS[name];
286
+ const selection = selectRules(name, options);
287
+ const enableArguments = selection.enable.flatMap((rule) => [
288
+ "--enable",
289
+ rule,
290
+ ]);
291
+ const errorArguments = selection.error.flatMap((rule) => ["--error", rule]);
220
292
  const extra = options.args ?? [];
221
293
  const ignores =
222
294
  limits.ignore === undefined
@@ -228,8 +300,6 @@ export function buildEngineCommand(
228
300
  : (options.targets ?? ["."]);
229
301
  const targets = limits.targets === undefined ? requested : [];
230
302
  const ignoreArguments = ignores.flatMap((ignore) => ["--ignore", ignore]);
231
- const rules = engineConfig(context.config, "tsdoc-check")?.error ?? [];
232
- const errorArguments = rules.flatMap((rule) => ["--error", rule]);
233
303
  if (name === "biome") {
234
304
  return [
235
305
  executable,
@@ -251,7 +321,7 @@ export function buildEngineCommand(
251
321
  "--json",
252
322
  "--baseline",
253
323
  context.rawBaseline,
254
- ...enableArguments(engineConfig(context.config, "comment-check")),
324
+ ...enableArguments,
255
325
  ...targets,
256
326
  ...ignoreArguments,
257
327
  ...extra,
@@ -262,7 +332,7 @@ export function buildEngineCommand(
262
332
  executable,
263
333
  "lint",
264
334
  "--json",
265
- ...enableArguments(engineConfig(context.config, "document-style-check")),
335
+ ...enableArguments,
266
336
  ...targets,
267
337
  ...ignoreArguments,
268
338
  ...extra,
@@ -274,7 +344,7 @@ export function buildEngineCommand(
274
344
  return [
275
345
  executable,
276
346
  "--json",
277
- ...enableArguments(engineConfig(context.config, "tsdoc-check")),
347
+ ...enableArguments,
278
348
  ...errorArguments,
279
349
  ...targets,
280
350
  ...ignoreArguments,