oxlint-plugin-vize 0.435.0 → 0.439.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -10,6 +10,8 @@ interface PatinaSettings {
10
10
  preset?: PatinaPreset;
11
11
  typeAware?: boolean;
12
12
  corsaPath?: string;
13
+ /** Rules to evaluate together. Oxlint's own rules still control reporting. */
14
+ rules?: readonly string[] | Record<string, unknown>;
13
15
  }
14
16
  //#endregion
15
17
  //#region src/configs.d.ts
@@ -91,11 +93,11 @@ interface VizeLintConfigOptions {
91
93
  */
92
94
  rules?: OxlintRuleConfig;
93
95
  /**
94
- * Patina runtime settings forwarded through `settings.vize`. `preset` is
95
- * intentionally absent: it is derived from `options.preset`/`options.presets`
96
+ * Patina runtime settings forwarded through `settings.vize`. `preset` and
97
+ * `rules` are derived from the selected presets and final rule map,
96
98
  * so the rule map and the bridge's runtime gate can never disagree.
97
99
  */
98
- settings?: Omit<PatinaSettings, "preset">;
100
+ settings?: Omit<PatinaSettings, "preset" | "rules">;
99
101
  }
100
102
  interface VizeLintConfigSettings extends Record<string, unknown> {
101
103
  vize: PatinaSettings;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as isPatinaFile, c as compareLineColumn, d as getDiagnosticBlock, l as extractSfcBlocks, n as isLocationBridgeProgram, o as isScriptLikeFile, r as resolveWorkaroundSource, u as formatBlockLabel } from "./workaround-CcnA_oWy.mjs";
1
+ import { a as validateWorkaroundAuthority, c as formatBlockLabel, d as isPatinaFile, f as isScriptLikeFile, i as resolveWorkaroundSource, l as getDiagnosticBlock, o as compareLineColumn, r as readOriginalWorkaroundSource, s as extractSfcBlocks, t as isLocationBridgeProgram } from "./workaround-r91ttnV_.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { definePlugin, defineRule } from "@oxlint/plugins";
4
4
  import { spawnSync } from "node:child_process";
@@ -21,6 +21,66 @@ function hasOnlyKeys(value, keys) {
21
21
  return Object.keys(value).every((key) => keys.includes(key));
22
22
  }
23
23
  //#endregion
24
+ //#region src/rule-options.ts
25
+ function getRuleOptions(ruleName, options) {
26
+ const firstOption = options[0];
27
+ switch (ruleName) {
28
+ case "vue/component-name-in-template-casing":
29
+ if (isComponentNameInTemplateCasingOption(firstOption)) return { componentNameInTemplateCasing: firstOption };
30
+ break;
31
+ case "script/custom-event-name-casing":
32
+ if (isCustomEventNameCasingOption(firstOption)) return { customEventNameCasing: firstOption };
33
+ break;
34
+ case "vue/no-mutating-props":
35
+ if (isNoMutatingPropsOption(firstOption)) return { noMutatingProps: firstOption };
36
+ break;
37
+ case "vue/sfc-element-order":
38
+ if (isSfcElementOrderOption(firstOption)) return { sfcElementOrder: firstOption };
39
+ break;
40
+ case "vue/html-self-closing":
41
+ if (isHtmlSelfClosingOption(firstOption)) return { htmlSelfClosing: firstOption };
42
+ break;
43
+ case "vue/v-on-event-hyphenation":
44
+ if (isHyphenationStyle(firstOption)) return { vOnEventHyphenation: firstOption };
45
+ break;
46
+ case "vue/attribute-hyphenation":
47
+ if (isHyphenationStyle(firstOption)) return { attributeHyphenation: firstOption };
48
+ break;
49
+ }
50
+ }
51
+ function isComponentNameInTemplateCasingOption(value) {
52
+ return value === "PascalCase" || value === "kebab-case";
53
+ }
54
+ function isCustomEventNameCasingOption(value) {
55
+ return value === "camelCase" || value === "kebab-case";
56
+ }
57
+ function isNoMutatingPropsOption(value) {
58
+ if (!isRecord$1(value)) return false;
59
+ return hasOnlyKeys(value, ["shallowOnly"]) && optionalBooleanField(value.shallowOnly);
60
+ }
61
+ function isSfcElementOrderOption(value) {
62
+ if (!isRecord$1(value)) return false;
63
+ return hasOnlyKeys(value, ["order"]) && (value.order === void 0 || Array.isArray(value.order) && value.order.every(isSfcElementOrderGroup));
64
+ }
65
+ function isSfcElementOrderGroup(value) {
66
+ return typeof value === "string" || Array.isArray(value) && value.every(isString);
67
+ }
68
+ function isHtmlSelfClosingOption(value) {
69
+ if (!isRecord$1(value)) return false;
70
+ return hasOnlyKeys(value, [
71
+ "html",
72
+ "svg",
73
+ "math"
74
+ ]) && optionField(value.svg) && optionField(value.math) && (value.html === void 0 || isRecord$1(value.html) && hasOnlyKeys(value.html, [
75
+ "void",
76
+ "normal",
77
+ "component"
78
+ ]) && optionField(value.html.void) && optionField(value.html.normal) && optionField(value.html.component));
79
+ }
80
+ function isHyphenationStyle(value) {
81
+ return value === "always" || value === "never";
82
+ }
83
+ //#endregion
24
84
  //#region src/native.ts
25
85
  const require = createRequire(import.meta.url);
26
86
  const FALLBACK_BINDING_PACKAGE = "@vizejs/native";
@@ -111,88 +171,58 @@ if (import.meta.vitest) {
111
171
  });
112
172
  }
113
173
  //#endregion
114
- //#region src/binding.ts
115
- function lintPatina(source, filename, settings, enabledRules, ruleOptions) {
116
- return loadBinding().lintPatinaSfc(source, {
117
- filename,
118
- locale: settings.locale,
119
- helpLevel: settings.helpLevel,
120
- preset: settings.preset,
121
- enabledRules: enabledRules ? [...enabledRules] : void 0,
122
- typeAware: settings.typeAware,
123
- corsaPath: settings.corsaPath,
124
- ...ruleOptions
174
+ //#region src/rule-selection.ts
175
+ const selectionCache = /* @__PURE__ */ new WeakMap();
176
+ /** A selection is a batching hint, never a second reporting/severity gate. */
177
+ function parseRuleSelection(rules) {
178
+ if (rules === void 0) return;
179
+ const revision = JSON.stringify(rules);
180
+ const cached = selectionCache.get(rules);
181
+ if (cached?.revision === revision) return cached.selection;
182
+ const optionsByRule = /* @__PURE__ */ new Map();
183
+ if (Array.isArray(rules)) {
184
+ for (const name of rules) if (typeof name === "string" && name.length > 0) optionsByRule.set(normalizeRuleName(name), void 0);
185
+ } else if (typeof rules === "object" && rules !== null) {
186
+ const usesPluginIds = Object.keys(rules).some((id) => id.startsWith("vize/"));
187
+ for (const [id, entry] of Object.entries(rules)) {
188
+ if (!id.startsWith("vize/") && (usesPluginIds || !/^(vue|script|style|css|type|nuxt|ecosystem)\//u.test(id))) continue;
189
+ const severity = Array.isArray(entry) ? entry[0] : entry;
190
+ if (severity !== "error" && severity !== "warn" && severity !== 1 && severity !== 2) continue;
191
+ const name = normalizeRuleName(id);
192
+ const options = Array.isArray(entry) ? getRuleOptions(name, entry.slice(1)) : void 0;
193
+ optionsByRule.set(name, options);
194
+ }
195
+ } else return;
196
+ const selection = createRuleSelection(optionsByRule);
197
+ selectionCache.set(rules, {
198
+ revision,
199
+ selection
125
200
  });
201
+ return selection;
126
202
  }
127
- function getPatinaRules() {
128
- return loadBinding().getPatinaRules();
129
- }
130
- //#endregion
131
- //#region src/script-map.ts
132
- function createSingleScriptMap(source, extractedScript, sfcBlocks = extractSfcBlocks(source)) {
133
- if (!extractedScript) return null;
134
- const blocks = sfcBlocks.filter((block) => block.kind === "script" || block.kind === "script-setup");
135
- let match = null;
136
- for (const block of blocks) {
137
- const skipped = countSkippedPrefix(block.content, extractedScript);
138
- if (skipped === null) continue;
139
- if (match !== null) return null;
140
- match = {
141
- block,
142
- scriptStart: advancePosition(block.contentStart, block.content.slice(0, skipped))
143
- };
144
- }
145
- return match;
146
- }
147
- /**
148
- * Oxlint trims the leading newline (and any indentation on that line) from a
149
- * `.vue` script block before handing the program to JS plugins, so the
150
- * extracted text starts partway into `block.content`. Returns how many
151
- * characters were dropped, or `null` when the extracted text is not this
152
- * block's body.
153
- */
154
- function countSkippedPrefix(content, extractedScript) {
155
- const skipped = content.length - extractedScript.length;
156
- if (skipped < 0 || !content.endsWith(extractedScript)) return null;
157
- return /^\s*$/u.test(content.slice(0, skipped)) ? skipped : null;
158
- }
159
- function advancePosition(from, text) {
160
- let { line, column } = from;
161
- for (const character of text) {
162
- if (character === "\n") {
163
- line += 1;
164
- column = 1;
165
- continue;
166
- }
167
- if (character !== "\r") column += 1;
168
- }
203
+ function createRuleSelection(optionsByRule) {
204
+ const names = [...optionsByRule.keys()].sort();
205
+ const options = {};
206
+ for (const name of names) Object.assign(options, optionsByRule.get(name));
169
207
  return {
170
- line,
171
- column
208
+ names,
209
+ options,
210
+ optionsByRule,
211
+ cacheKey: JSON.stringify([names, ruleOptionsKey(options)])
172
212
  };
173
213
  }
174
- function mapToScriptLoc(diagnostic, scriptMap) {
175
- if (!scriptMap) return null;
176
- const { block, scriptStart } = scriptMap;
177
- if (compareLineColumn(diagnostic.location.start, scriptStart) < 0 || compareLineColumn(diagnostic.location.end, block.contentEnd) > 0) return null;
178
- return {
179
- start: toScriptPosition(diagnostic.location.start, scriptStart),
180
- end: toScriptPosition(diagnostic.location.end, scriptStart)
181
- };
214
+ function normalizeRuleName(name) {
215
+ return name.startsWith("vize/") ? name.slice(5) : name;
182
216
  }
183
- /**
184
- * Patina reports 1-based SFC line/column; Oxlint expects 1-based lines and
185
- * 0-based columns relative to the extracted program.
186
- */
187
- function toScriptPosition(position, scriptStart) {
188
- if (position.line === scriptStart.line) return {
189
- line: 1,
190
- column: position.column - scriptStart.column
191
- };
192
- return {
193
- line: position.line - scriptStart.line + 1,
194
- column: position.column - 1
195
- };
217
+ function ruleOptionsKey(options) {
218
+ if (options === void 0) return "{}";
219
+ return JSON.stringify(options ?? {}, (_key, value) => {
220
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
221
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
222
+ });
223
+ }
224
+ function selectVizeRuleConfig(rules) {
225
+ return Object.fromEntries(Object.entries(rules).filter(([id]) => id.startsWith("vize/")));
196
226
  }
197
227
  //#endregion
198
228
  //#region src/settings.ts
@@ -232,6 +262,7 @@ function parseVizeSettings(vize) {
232
262
  const typeAware = vizeRecord.typeAware;
233
263
  const corsaPath = vizeRecord.corsaPath;
234
264
  const resolved = {};
265
+ if (Array.isArray(vizeRecord.rules) || typeof vizeRecord.rules === "object" && vizeRecord.rules !== null) resolved.rules = vizeRecord.rules;
235
266
  if (typeof locale === "string") resolved.locale = locale;
236
267
  if (typeof preset === "string") {
237
268
  const normalizedPreset = normalizePreset(preset);
@@ -247,20 +278,22 @@ function parseVizeSettings(vize) {
247
278
  return resolved;
248
279
  }
249
280
  function getActivePreset(settings) {
250
- return settings.preset ?? "general-recommended";
281
+ return settings.preset ?? "incremental";
251
282
  }
252
283
  function isIncrementalPreset(settings) {
253
284
  return getActivePreset(settings) === "incremental";
254
285
  }
255
286
  function getCacheKey(filename, settings) {
256
- return [
287
+ const selection = parseRuleSelection(settings.rules);
288
+ return JSON.stringify([
257
289
  filename,
258
290
  settings.locale ?? "",
259
291
  settings.helpLevel ?? "",
260
292
  getActivePreset(settings),
261
293
  settings.typeAware ? "type-aware" : "",
262
- settings.corsaPath ?? ""
263
- ].join("::");
294
+ settings.corsaPath ?? "",
295
+ selection?.cacheKey
296
+ ]);
264
297
  }
265
298
  function isTypeAwareRuleName(ruleName) {
266
299
  return ruleName.startsWith(TYPE_AWARE_RULE_PREFIX$1);
@@ -322,6 +355,90 @@ if (import.meta.vitest) {
322
355
  });
323
356
  }
324
357
  //#endregion
358
+ //#region src/binding.ts
359
+ function lintPatina(source, filename, settings, enabledRules, ruleOptions) {
360
+ return loadBinding().lintPatinaSfc(source, {
361
+ filename,
362
+ locale: settings.locale,
363
+ helpLevel: settings.helpLevel,
364
+ preset: getActivePreset(settings),
365
+ enabledRules: enabledRules ? [...enabledRules] : void 0,
366
+ typeAware: settings.typeAware,
367
+ corsaPath: settings.corsaPath,
368
+ ...ruleOptions
369
+ });
370
+ }
371
+ function getPatinaRules() {
372
+ return loadBinding().getPatinaRules();
373
+ }
374
+ //#endregion
375
+ //#region src/script-map.ts
376
+ function createSingleScriptMap(source, extractedScript, sfcBlocks = extractSfcBlocks(source)) {
377
+ if (!extractedScript) return null;
378
+ const blocks = sfcBlocks.filter((block) => block.kind === "script" || block.kind === "script-setup");
379
+ let match = null;
380
+ for (const block of blocks) {
381
+ const skipped = countSkippedPrefix(block.content, extractedScript);
382
+ if (skipped === null) continue;
383
+ if (match !== null) return null;
384
+ match = {
385
+ block,
386
+ scriptStart: advancePosition(block.contentStart, block.content.slice(0, skipped))
387
+ };
388
+ }
389
+ return match;
390
+ }
391
+ /**
392
+ * Oxlint trims the leading newline (and any indentation on that line) from a
393
+ * `.vue` script block before handing the program to JS plugins, so the
394
+ * extracted text starts partway into `block.content`. Returns how many
395
+ * characters were dropped, or `null` when the extracted text is not this
396
+ * block's body.
397
+ */
398
+ function countSkippedPrefix(content, extractedScript) {
399
+ const skipped = content.length - extractedScript.length;
400
+ if (skipped < 0 || !content.endsWith(extractedScript)) return null;
401
+ return /^\s*$/u.test(content.slice(0, skipped)) ? skipped : null;
402
+ }
403
+ function advancePosition(from, text) {
404
+ let { line, column } = from;
405
+ for (const character of text) {
406
+ if (character === "\n") {
407
+ line += 1;
408
+ column = 1;
409
+ continue;
410
+ }
411
+ if (character !== "\r") column += 1;
412
+ }
413
+ return {
414
+ line,
415
+ column
416
+ };
417
+ }
418
+ function mapToScriptLoc(diagnostic, scriptMap) {
419
+ if (!scriptMap) return null;
420
+ const { block, scriptStart } = scriptMap;
421
+ if (compareLineColumn(diagnostic.location.start, scriptStart) < 0 || compareLineColumn(diagnostic.location.end, block.contentEnd) > 0) return null;
422
+ return {
423
+ start: toScriptPosition(diagnostic.location.start, scriptStart),
424
+ end: toScriptPosition(diagnostic.location.end, scriptStart)
425
+ };
426
+ }
427
+ /**
428
+ * Patina reports 1-based SFC line/column; Oxlint expects 1-based lines and
429
+ * 0-based columns relative to the extracted program.
430
+ */
431
+ function toScriptPosition(position, scriptStart) {
432
+ if (position.line === scriptStart.line) return {
433
+ line: 1,
434
+ column: position.column - scriptStart.column
435
+ };
436
+ return {
437
+ line: position.line - scriptStart.line + 1,
438
+ column: position.column - 1
439
+ };
440
+ }
441
+ //#endregion
325
442
  //#region src/file-state.ts
326
443
  const FILE_STATE_CACHE_CAPACITY = 128;
327
444
  const fileStateCache = /* @__PURE__ */ new Map();
@@ -331,10 +448,10 @@ let fileStateCacheClock = 0;
331
448
  function getFileState(context) {
332
449
  const settings = getVizeSettings(context);
333
450
  const physicalSource = fs.readFileSync(context.physicalFilename, "utf8");
334
- const resolvedSource = resolveWorkaroundSource(physicalSource, context.physicalFilename);
335
- const cacheKey = getCacheKey(resolvedSource.filename, settings);
451
+ const cacheKey = getCacheKey(context.physicalFilename, settings);
336
452
  const cached = fileStateCache.get(cacheKey);
337
453
  if (cached && cached.state.revision.physicalSource === physicalSource) {
454
+ validateWorkaroundAuthority(cached.state, context.physicalFilename);
338
455
  if (cached.state.extractedScript !== context.sourceCode.text) {
339
456
  cached.state.extractedScript = context.sourceCode.text;
340
457
  cached.state.scriptMap = void 0;
@@ -342,6 +459,7 @@ function getFileState(context) {
342
459
  cached.lastUsed = nextFileStateCacheClock();
343
460
  return cached.state;
344
461
  }
462
+ const resolvedSource = resolveWorkaroundSource(physicalSource, context.physicalFilename, readOriginalWorkaroundSource);
345
463
  const state = {
346
464
  revision: { physicalSource },
347
465
  filename: resolvedSource.filename,
@@ -355,7 +473,9 @@ function getFileState(context) {
355
473
  allDiagnosticsIncludesTypeAware: false,
356
474
  partialDiagnosticsByRule: /* @__PURE__ */ new Map(),
357
475
  requestedRules: /* @__PURE__ */ new Set(),
358
- reportedTypeAwareRuntimeDiagnostic: false
476
+ reportedTypeAwareRuntimeDiagnostic: false,
477
+ ruleSelection: parseRuleSelection(settings.rules),
478
+ selectedDiagnosticsByRule: null
359
479
  };
360
480
  fileStateCache.set(cacheKey, {
361
481
  state,
@@ -364,91 +484,6 @@ function getFileState(context) {
364
484
  evictLeastRecentlyUsedFileState();
365
485
  return state;
366
486
  }
367
- function getDiagnosticsForRule(context, state, ruleName, ruleOptions) {
368
- if (ruleOptions != null) return getConfiguredRuleDiagnostics(context, state, ruleName, ruleOptions);
369
- if (state.allDiagnosticsByRule) {
370
- if (isTypeAwareRuleName(ruleName) && !state.allDiagnosticsIncludesTypeAware) {
371
- const settings = getVizeSettings(context);
372
- state.allDiagnosticsByRule = indexDiagnosticsByRule(lintPatina(state.source, state.filename, {
373
- ...settings,
374
- typeAware: true
375
- }).diagnostics);
376
- state.allDiagnosticsIncludesTypeAware = true;
377
- }
378
- return diagnosticsForRule(state, state.allDiagnosticsByRule, ruleName);
379
- }
380
- const cached = state.partialDiagnosticsByRule.get(ruleName);
381
- if (cached) return cached;
382
- const settings = getVizeSettings(context);
383
- const ruleSettings = isTypeAwareRuleName(ruleName) ? {
384
- ...settings,
385
- typeAware: true
386
- } : settings;
387
- if (isIncrementalPreset(settings)) {
388
- const diagnostics = lintPatina(state.source, state.filename, ruleSettings, [ruleName]).diagnostics;
389
- const ruleDiagnostics = diagnosticsForRule(state, indexDiagnosticsByRule(diagnostics), ruleName);
390
- state.partialDiagnosticsByRule.set(ruleName, ruleDiagnostics);
391
- return ruleDiagnostics;
392
- }
393
- if (state.requestedRules.size === 0) {
394
- state.requestedRules.add(ruleName);
395
- const diagnostics = lintPatina(state.source, state.filename, ruleSettings, [ruleName]).diagnostics;
396
- const ruleDiagnostics = diagnosticsForRule(state, indexDiagnosticsByRule(diagnostics), ruleName);
397
- state.partialDiagnosticsByRule.set(ruleName, ruleDiagnostics);
398
- return ruleDiagnostics;
399
- }
400
- state.requestedRules.add(ruleName);
401
- const fullPassTypeAware = settings.typeAware === true || Array.from(state.requestedRules).some(isTypeAwareRuleName);
402
- const allDiagnostics = lintPatina(state.source, state.filename, {
403
- ...settings,
404
- typeAware: fullPassTypeAware
405
- }).diagnostics;
406
- state.allDiagnosticsByRule = indexDiagnosticsByRule(allDiagnostics);
407
- state.allDiagnosticsIncludesTypeAware = fullPassTypeAware;
408
- state.partialDiagnosticsByRule.clear();
409
- return diagnosticsForRule(state, state.allDiagnosticsByRule, ruleName);
410
- }
411
- function getConfiguredRuleDiagnostics(context, state, ruleName, ruleOptions) {
412
- const cacheKey = configuredRuleCacheKey(ruleName, ruleOptions);
413
- const cached = state.partialDiagnosticsByRule.get(cacheKey);
414
- if (cached) return cached;
415
- const settings = getVizeSettings(context);
416
- const ruleSettings = isTypeAwareRuleName(ruleName) ? {
417
- ...settings,
418
- typeAware: true
419
- } : settings;
420
- const diagnostics = lintPatina(state.source, state.filename, ruleSettings, [ruleName], ruleOptions).diagnostics;
421
- const ruleDiagnostics = diagnosticsForRule(state, indexDiagnosticsByRule(diagnostics), ruleName);
422
- state.partialDiagnosticsByRule.set(cacheKey, ruleDiagnostics);
423
- return ruleDiagnostics;
424
- }
425
- function configuredRuleCacheKey(ruleName, ruleOptions) {
426
- return [
427
- ruleName,
428
- ruleOptions.componentNameInTemplateCasing ?? "",
429
- ruleOptions.customEventNameCasing ?? "",
430
- ruleOptions.noMutatingProps == null ? "" : noMutatingPropsCacheKey(ruleOptions.noMutatingProps),
431
- ruleOptions.sfcElementOrder == null ? "" : sfcElementOrderCacheKey(ruleOptions.sfcElementOrder),
432
- ruleOptions.htmlSelfClosing == null ? "" : htmlSelfClosingCacheKey(ruleOptions.htmlSelfClosing),
433
- ruleOptions.vOnEventHyphenation ?? "",
434
- ruleOptions.attributeHyphenation ?? ""
435
- ].join("\0");
436
- }
437
- function noMutatingPropsCacheKey(options) {
438
- return options.shallowOnly === true ? "1" : "0";
439
- }
440
- function sfcElementOrderCacheKey(options) {
441
- return (options.order ?? []).map((group) => Array.isArray(group) ? group.join("") : group).join("");
442
- }
443
- function htmlSelfClosingCacheKey(options) {
444
- return [
445
- options.html?.void ?? "",
446
- options.html?.normal ?? "",
447
- options.html?.component ?? "",
448
- options.svg ?? "",
449
- options.math ?? ""
450
- ].join("/");
451
- }
452
487
  function getScriptMap(state) {
453
488
  if (state.scriptMap !== void 0) return state.scriptMap;
454
489
  state.scriptMap = createSingleScriptMap(state.source, state.extractedScript, getSfcBlocks(state));
@@ -515,6 +550,63 @@ function diagnosticsForRule(state, diagnosticsByRule, ruleName) {
515
550
  return runtimeDiagnostics;
516
551
  }
517
552
  //#endregion
553
+ //#region src/active-rule-collection.ts
554
+ const programs = /* @__PURE__ */ new WeakMap();
555
+ const results = /* @__PURE__ */ new WeakMap();
556
+ function collectActiveRule(context, program, ruleName, options) {
557
+ const state = getFileState(context);
558
+ let collection = programs.get(program);
559
+ if (!collection || collection.selection) {
560
+ collection = {
561
+ state,
562
+ optionsByRule: /* @__PURE__ */ new Map()
563
+ };
564
+ programs.set(program, collection);
565
+ }
566
+ if (collection.state !== state) {
567
+ programs.delete(program);
568
+ throw new Error("Vize source or configuration changed during rule collection");
569
+ }
570
+ collection.optionsByRule.set(ruleName, options === void 0 ? void 0 : structuredClone(options));
571
+ }
572
+ function getActiveRuleDiagnostics(context, program, ruleName) {
573
+ const collection = programs.get(program);
574
+ if (!collection?.optionsByRule.has(ruleName)) return void 0;
575
+ if (getFileState(context) !== collection.state) {
576
+ programs.delete(program);
577
+ throw new Error("Vize source or configuration changed before rule execution");
578
+ }
579
+ if (!collection.selection) {
580
+ collection.selection = createRuleSelection(collection.optionsByRule);
581
+ collection.remaining = new Set(collection.selection.names);
582
+ }
583
+ const { state, selection } = collection;
584
+ let cached = results.get(state);
585
+ if (cached?.key !== selection.cacheKey) {
586
+ const settings = getVizeSettings(context);
587
+ const diagnostics = indexDiagnosticsByRule(lintPatina(state.source, state.filename, {
588
+ ...settings,
589
+ typeAware: settings.typeAware === true || selection.names.some(isTypeAwareRuleName)
590
+ }, selection.names, selection.options).diagnostics);
591
+ if (cached) {
592
+ state.reportedDiagnostics.clear();
593
+ state.reportedTypeAwareRuntimeDiagnostic = false;
594
+ }
595
+ cached = {
596
+ key: selection.cacheKey,
597
+ diagnostics
598
+ };
599
+ results.set(state, cached);
600
+ }
601
+ const diagnostics = diagnosticsForRule(state, cached.diagnostics, ruleName);
602
+ collection.remaining.delete(ruleName);
603
+ if (collection.remaining.size === 0) programs.delete(program);
604
+ return {
605
+ state,
606
+ diagnostics
607
+ };
608
+ }
609
+ //#endregion
518
610
  //#region src/format.ts
519
611
  const helpTextCache = /* @__PURE__ */ new Map();
520
612
  function formatPatinaMessage(diagnostic, options) {
@@ -626,22 +718,28 @@ function createPatinaRule(ruleMeta) {
626
718
  schema: ruleOptionsSchema(ruleMeta.name)
627
719
  },
628
720
  createOnce(context) {
629
- return { Program(program) {
630
- if (!isPatinaFile(context.filename)) return;
631
- const settings = getVizeSettings(context);
632
- const activePreset = getActivePreset(settings);
633
- if (ruleMeta.presets.length > 0 && !isIncrementalPreset(settings) && !ruleMeta.presets.includes(activePreset)) return;
634
- const helpLevel = settings.helpLevel ?? "full";
635
- const state = getFileState(context);
636
- const scriptMap = getScriptMap(state);
637
- const ruleOptions = getRuleOptions(ruleMeta.name, contextOptions(context));
638
- const diagnostics = getDiagnosticsForRule(context, state, ruleMeta.name, ruleOptions).filter((diagnostic) => shouldReportForCurrentProgram(diagnostic, state, scriptMap));
639
- if (diagnostics.length === 0) return;
640
- for (const diagnostic of diagnostics) {
641
- if (!markDiagnosticAsReported(state, diagnostic)) continue;
642
- context.report(createOxlintDiagnostic(diagnostic, state, scriptMap, helpLevel, program));
721
+ return {
722
+ Program(program) {
723
+ if (!isPatinaFile(context.filename)) return;
724
+ const settings = getVizeSettings(context);
725
+ const activePreset = getActivePreset(settings);
726
+ if (ruleMeta.presets.length > 0 && !isIncrementalPreset(settings) && !ruleMeta.presets.includes(activePreset)) return;
727
+ collectActiveRule(context, program, ruleMeta.name, getRuleOptions(ruleMeta.name, contextOptions(context)));
728
+ },
729
+ "Program:exit"(program) {
730
+ const collected = getActiveRuleDiagnostics(context, program, ruleMeta.name);
731
+ if (!collected) return;
732
+ const { state } = collected;
733
+ const helpLevel = getVizeSettings(context).helpLevel ?? "full";
734
+ const scriptMap = getScriptMap(state);
735
+ const diagnostics = collected.diagnostics.filter((diagnostic) => shouldReportForCurrentProgram(diagnostic, state, scriptMap));
736
+ if (diagnostics.length === 0) return;
737
+ for (const diagnostic of diagnostics) {
738
+ if (!markDiagnosticAsReported(state, diagnostic)) continue;
739
+ context.report(createOxlintDiagnostic(diagnostic, state, scriptMap, helpLevel, program));
740
+ }
643
741
  }
644
- } };
742
+ };
645
743
  }
646
744
  });
647
745
  }
@@ -706,64 +804,6 @@ function contextOptions(context) {
706
804
  const options = context.options;
707
805
  return Array.isArray(options) ? options : [];
708
806
  }
709
- function getRuleOptions(ruleName, options) {
710
- const firstOption = options[0];
711
- switch (ruleName) {
712
- case "vue/component-name-in-template-casing":
713
- if (isComponentNameInTemplateCasingOption(firstOption)) return { componentNameInTemplateCasing: firstOption };
714
- break;
715
- case "script/custom-event-name-casing":
716
- if (isCustomEventNameCasingOption(firstOption)) return { customEventNameCasing: firstOption };
717
- break;
718
- case "vue/no-mutating-props":
719
- if (isNoMutatingPropsOption(firstOption)) return { noMutatingProps: firstOption };
720
- break;
721
- case "vue/sfc-element-order":
722
- if (isSfcElementOrderOption(firstOption)) return { sfcElementOrder: firstOption };
723
- break;
724
- case "vue/html-self-closing":
725
- if (isHtmlSelfClosingOption(firstOption)) return { htmlSelfClosing: firstOption };
726
- break;
727
- case "vue/v-on-event-hyphenation":
728
- if (isHyphenationStyle(firstOption)) return { vOnEventHyphenation: firstOption };
729
- break;
730
- case "vue/attribute-hyphenation":
731
- if (isHyphenationStyle(firstOption)) return { attributeHyphenation: firstOption };
732
- break;
733
- }
734
- }
735
- function isComponentNameInTemplateCasingOption(value) {
736
- return value === "PascalCase" || value === "kebab-case";
737
- }
738
- function isCustomEventNameCasingOption(value) {
739
- return value === "camelCase" || value === "kebab-case";
740
- }
741
- function isNoMutatingPropsOption(value) {
742
- if (!isRecord$1(value)) return false;
743
- return hasOnlyKeys(value, ["shallowOnly"]) && optionalBooleanField(value.shallowOnly);
744
- }
745
- function isSfcElementOrderOption(value) {
746
- if (!isRecord$1(value)) return false;
747
- return hasOnlyKeys(value, ["order"]) && (value.order === void 0 || Array.isArray(value.order) && value.order.every(isSfcElementOrderGroup));
748
- }
749
- function isSfcElementOrderGroup(value) {
750
- return typeof value === "string" || Array.isArray(value) && value.every(isString);
751
- }
752
- function isHtmlSelfClosingOption(value) {
753
- if (!isRecord$1(value)) return false;
754
- return hasOnlyKeys(value, [
755
- "html",
756
- "svg",
757
- "math"
758
- ]) && optionField(value.svg) && optionField(value.math) && (value.html === void 0 || isRecord$1(value.html) && hasOnlyKeys(value.html, [
759
- "void",
760
- "normal",
761
- "component"
762
- ]) && optionField(value.html.void) && optionField(value.html.normal) && optionField(value.html.component));
763
- }
764
- function isHyphenationStyle(value) {
765
- return value === "always" || value === "never";
766
- }
767
807
  var plugin_default = definePlugin({
768
808
  meta: { name: "vize" },
769
809
  rules: Object.fromEntries(getPatinaRules().map((ruleMeta) => [ruleMeta.name, createPatinaRule(ruleMeta)]))
@@ -955,16 +995,18 @@ function buildVizeLintConfig(options, buildOptions) {
955
995
  const runtimePreset = buildOptions.forceIncrementalRuntime ? "incremental" : toRuntimePreset(presets);
956
996
  const extraRules = options.rules ?? {};
957
997
  assertUsableVizeRules(extraRules, presets, runtimePreset);
998
+ const rules = {
999
+ ...createPresetRules(presets, options.includeTypeAware),
1000
+ ...extraRules
1001
+ };
958
1002
  return {
959
1003
  jsPlugins: [VIZE_JS_PLUGIN_SPECIFIER],
960
1004
  plugins: [...new Set(["vue", ...options.plugins ?? []])],
961
- rules: {
962
- ...createPresetRules(presets, options.includeTypeAware),
963
- ...extraRules
964
- },
1005
+ rules,
965
1006
  settings: { vize: {
966
1007
  ...options.settings,
967
- preset: runtimePreset
1008
+ preset: runtimePreset,
1009
+ rules: selectVizeRuleConfig(rules)
968
1010
  } }
969
1011
  };
970
1012
  }
@@ -1083,6 +1125,7 @@ function defineVizeLintConfig(...entries) {
1083
1125
  if (uniquePresets.length === 1) config.settings.vize.preset = uniquePresets[0];
1084
1126
  else if (uniquePresets.length > 1) config.settings.vize.preset = "incremental";
1085
1127
  }
1128
+ if (config.settings.vize.rules !== void 0 || Object.keys(config.rules).some((id) => id.startsWith("vize/"))) config.settings.vize.rules = selectVizeRuleConfig(config.rules);
1086
1129
  return config;
1087
1130
  }
1088
1131
  function mergeFlatConfigEntry(target, runtimePresets, entry) {