oxlint-plugin-vize 0.443.0 → 0.454.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 +35 -0
- package/dist/cli.mjs +240 -1
- package/dist/configs-BuOTHAnm.d.mts +40 -0
- package/dist/configs-DY0BrbH2.mjs +853 -0
- package/dist/eslint.d.mts +7 -0
- package/dist/eslint.mjs +153 -0
- package/dist/index.d.mts +2 -39
- package/dist/index.mjs +1 -852
- package/package.json +15 -11
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import { a as validateWorkaroundAuthority, c as formatBlockLabel, d as isPatinaFile, f as isScriptLikeFile, i as resolveWorkaroundSource, l as getDiagnosticBlock, m as loadBinding, o as compareLineColumn, r as readOriginalWorkaroundSource, s as extractSfcBlocks, t as isLocationBridgeProgram } from "./workaround-CiQUFJw0.mjs";
|
|
2
|
+
import { definePlugin, defineRule } from "@oxlint/plugins";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
//#region src/plugin-option-guards.ts
|
|
5
|
+
function optionField(value) {
|
|
6
|
+
return value === void 0 || value === "always" || value === "never" || value === "any";
|
|
7
|
+
}
|
|
8
|
+
function optionalBooleanField(value) {
|
|
9
|
+
return value === void 0 || typeof value === "boolean";
|
|
10
|
+
}
|
|
11
|
+
function isString(value) {
|
|
12
|
+
return typeof value === "string";
|
|
13
|
+
}
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function hasOnlyKeys(value, keys) {
|
|
18
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/rule-options.ts
|
|
22
|
+
function getRuleOptions(ruleName, options) {
|
|
23
|
+
const firstOption = options[0];
|
|
24
|
+
switch (ruleName) {
|
|
25
|
+
case "vue/component-name-in-template-casing":
|
|
26
|
+
if (isComponentNameInTemplateCasingOption(firstOption)) return { componentNameInTemplateCasing: firstOption };
|
|
27
|
+
break;
|
|
28
|
+
case "script/custom-event-name-casing":
|
|
29
|
+
if (isCustomEventNameCasingOption(firstOption)) return { customEventNameCasing: firstOption };
|
|
30
|
+
break;
|
|
31
|
+
case "vue/no-mutating-props":
|
|
32
|
+
if (isNoMutatingPropsOption(firstOption)) return { noMutatingProps: firstOption };
|
|
33
|
+
break;
|
|
34
|
+
case "vue/sfc-element-order":
|
|
35
|
+
if (isSfcElementOrderOption(firstOption)) return { sfcElementOrder: firstOption };
|
|
36
|
+
break;
|
|
37
|
+
case "vue/html-self-closing":
|
|
38
|
+
if (isHtmlSelfClosingOption(firstOption)) return { htmlSelfClosing: firstOption };
|
|
39
|
+
break;
|
|
40
|
+
case "vue/v-on-event-hyphenation":
|
|
41
|
+
if (isHyphenationStyle(firstOption)) return { vOnEventHyphenation: firstOption };
|
|
42
|
+
break;
|
|
43
|
+
case "vue/attribute-hyphenation":
|
|
44
|
+
if (isHyphenationStyle(firstOption)) return { attributeHyphenation: firstOption };
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function isComponentNameInTemplateCasingOption(value) {
|
|
49
|
+
return value === "PascalCase" || value === "kebab-case";
|
|
50
|
+
}
|
|
51
|
+
function isCustomEventNameCasingOption(value) {
|
|
52
|
+
return value === "camelCase" || value === "kebab-case";
|
|
53
|
+
}
|
|
54
|
+
function isNoMutatingPropsOption(value) {
|
|
55
|
+
if (!isRecord(value)) return false;
|
|
56
|
+
return hasOnlyKeys(value, ["shallowOnly"]) && optionalBooleanField(value.shallowOnly);
|
|
57
|
+
}
|
|
58
|
+
function isSfcElementOrderOption(value) {
|
|
59
|
+
if (!isRecord(value)) return false;
|
|
60
|
+
return hasOnlyKeys(value, ["order"]) && (value.order === void 0 || Array.isArray(value.order) && value.order.every(isSfcElementOrderGroup));
|
|
61
|
+
}
|
|
62
|
+
function isSfcElementOrderGroup(value) {
|
|
63
|
+
return typeof value === "string" || Array.isArray(value) && value.every(isString);
|
|
64
|
+
}
|
|
65
|
+
function isHtmlSelfClosingOption(value) {
|
|
66
|
+
if (!isRecord(value)) return false;
|
|
67
|
+
return hasOnlyKeys(value, [
|
|
68
|
+
"html",
|
|
69
|
+
"svg",
|
|
70
|
+
"math"
|
|
71
|
+
]) && optionField(value.svg) && optionField(value.math) && (value.html === void 0 || isRecord(value.html) && hasOnlyKeys(value.html, [
|
|
72
|
+
"void",
|
|
73
|
+
"normal",
|
|
74
|
+
"component"
|
|
75
|
+
]) && optionField(value.html.void) && optionField(value.html.normal) && optionField(value.html.component));
|
|
76
|
+
}
|
|
77
|
+
function isHyphenationStyle(value) {
|
|
78
|
+
return value === "always" || value === "never";
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/rule-selection.ts
|
|
82
|
+
const selectionCache = /* @__PURE__ */ new WeakMap();
|
|
83
|
+
/** A selection is a batching hint, never a second reporting/severity gate. */
|
|
84
|
+
function parseRuleSelection(rules) {
|
|
85
|
+
if (rules === void 0) return;
|
|
86
|
+
const revision = JSON.stringify(rules);
|
|
87
|
+
const cached = selectionCache.get(rules);
|
|
88
|
+
if (cached?.revision === revision) return cached.selection;
|
|
89
|
+
const optionsByRule = /* @__PURE__ */ new Map();
|
|
90
|
+
if (Array.isArray(rules)) {
|
|
91
|
+
for (const name of rules) if (typeof name === "string" && name.length > 0) optionsByRule.set(normalizeRuleName(name), void 0);
|
|
92
|
+
} else if (typeof rules === "object" && rules !== null) {
|
|
93
|
+
const usesPluginIds = Object.keys(rules).some((id) => id.startsWith("vize/"));
|
|
94
|
+
for (const [id, entry] of Object.entries(rules)) {
|
|
95
|
+
if (!id.startsWith("vize/") && (usesPluginIds || !/^(vue|script|style|css|type|nuxt|ecosystem)\//u.test(id))) continue;
|
|
96
|
+
const severity = Array.isArray(entry) ? entry[0] : entry;
|
|
97
|
+
if (severity !== "error" && severity !== "warn" && severity !== 1 && severity !== 2) continue;
|
|
98
|
+
const name = normalizeRuleName(id);
|
|
99
|
+
const options = Array.isArray(entry) ? getRuleOptions(name, entry.slice(1)) : void 0;
|
|
100
|
+
optionsByRule.set(name, options);
|
|
101
|
+
}
|
|
102
|
+
} else return;
|
|
103
|
+
const selection = createRuleSelection(optionsByRule);
|
|
104
|
+
selectionCache.set(rules, {
|
|
105
|
+
revision,
|
|
106
|
+
selection
|
|
107
|
+
});
|
|
108
|
+
return selection;
|
|
109
|
+
}
|
|
110
|
+
function createRuleSelection(optionsByRule) {
|
|
111
|
+
const names = [...optionsByRule.keys()].sort();
|
|
112
|
+
const options = {};
|
|
113
|
+
for (const name of names) Object.assign(options, optionsByRule.get(name));
|
|
114
|
+
return {
|
|
115
|
+
names,
|
|
116
|
+
options,
|
|
117
|
+
optionsByRule,
|
|
118
|
+
cacheKey: JSON.stringify([names, ruleOptionsKey(options)])
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function normalizeRuleName(name) {
|
|
122
|
+
return name.startsWith("vize/") ? name.slice(5) : name;
|
|
123
|
+
}
|
|
124
|
+
function ruleOptionsKey(options) {
|
|
125
|
+
if (options === void 0) return "{}";
|
|
126
|
+
return JSON.stringify(options ?? {}, (_key, value) => {
|
|
127
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
|
|
128
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function selectVizeRuleConfig(rules) {
|
|
132
|
+
return Object.fromEntries(Object.entries(rules).filter(([id]) => id.startsWith("vize/")));
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/settings.ts
|
|
136
|
+
const HELP_LEVELS = new Set([
|
|
137
|
+
"none",
|
|
138
|
+
"short",
|
|
139
|
+
"full"
|
|
140
|
+
]);
|
|
141
|
+
const TYPE_AWARE_RULE_PREFIX$1 = "type/";
|
|
142
|
+
const PRESET_ALIASES = new Map([
|
|
143
|
+
["generalrecommended", "general-recommended"],
|
|
144
|
+
["happypath", "general-recommended"],
|
|
145
|
+
["happy", "general-recommended"],
|
|
146
|
+
["default", "general-recommended"],
|
|
147
|
+
["recommended", "general-recommended"],
|
|
148
|
+
["essential", "essential"],
|
|
149
|
+
["ecosystem", "ecosystem"],
|
|
150
|
+
["eco", "ecosystem"],
|
|
151
|
+
["incremental", "incremental"],
|
|
152
|
+
["all", "incremental"],
|
|
153
|
+
["opinionated", "opinionated"],
|
|
154
|
+
["opnionated", "opinionated"],
|
|
155
|
+
["strict", "opinionated"],
|
|
156
|
+
["nuxt", "nuxt"]
|
|
157
|
+
]);
|
|
158
|
+
function getVizeSettings(context) {
|
|
159
|
+
const settings = context.settings;
|
|
160
|
+
return parseVizeSettings(settings.vize ?? settings.patina);
|
|
161
|
+
}
|
|
162
|
+
function parseVizeSettings(vize) {
|
|
163
|
+
if (typeof vize !== "object" || vize === null || Array.isArray(vize)) return {};
|
|
164
|
+
const vizeRecord = vize;
|
|
165
|
+
const locale = vizeRecord.locale;
|
|
166
|
+
const helpLevel = vizeRecord.helpLevel;
|
|
167
|
+
const preset = vizeRecord.preset;
|
|
168
|
+
const showHelp = vizeRecord.showHelp;
|
|
169
|
+
const typeAware = vizeRecord.typeAware;
|
|
170
|
+
const corsaPath = vizeRecord.corsaPath;
|
|
171
|
+
const resolved = {};
|
|
172
|
+
if (Array.isArray(vizeRecord.rules) || typeof vizeRecord.rules === "object" && vizeRecord.rules !== null) resolved.rules = vizeRecord.rules;
|
|
173
|
+
if (typeof locale === "string") resolved.locale = locale;
|
|
174
|
+
if (typeof preset === "string") {
|
|
175
|
+
const normalizedPreset = normalizePreset(preset);
|
|
176
|
+
if (normalizedPreset) resolved.preset = normalizedPreset;
|
|
177
|
+
}
|
|
178
|
+
if (typeof typeAware === "boolean") resolved.typeAware = typeAware;
|
|
179
|
+
if (typeof corsaPath === "string") resolved.corsaPath = corsaPath;
|
|
180
|
+
if (typeof helpLevel === "string" && HELP_LEVELS.has(helpLevel)) {
|
|
181
|
+
resolved.helpLevel = helpLevel;
|
|
182
|
+
return resolved;
|
|
183
|
+
}
|
|
184
|
+
if (typeof showHelp === "boolean") resolved.helpLevel = showHelp ? "full" : "none";
|
|
185
|
+
return resolved;
|
|
186
|
+
}
|
|
187
|
+
function getActivePreset(settings) {
|
|
188
|
+
return settings.preset ?? "incremental";
|
|
189
|
+
}
|
|
190
|
+
function isIncrementalPreset(settings) {
|
|
191
|
+
return getActivePreset(settings) === "incremental";
|
|
192
|
+
}
|
|
193
|
+
function getCacheKey(filename, settings) {
|
|
194
|
+
const selection = parseRuleSelection(settings.rules);
|
|
195
|
+
return JSON.stringify([
|
|
196
|
+
filename,
|
|
197
|
+
settings.locale ?? "",
|
|
198
|
+
settings.helpLevel ?? "",
|
|
199
|
+
getActivePreset(settings),
|
|
200
|
+
settings.typeAware ? "type-aware" : "",
|
|
201
|
+
settings.corsaPath ?? "",
|
|
202
|
+
selection?.cacheKey
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
function isTypeAwareRuleName(ruleName) {
|
|
206
|
+
return ruleName.startsWith(TYPE_AWARE_RULE_PREFIX$1);
|
|
207
|
+
}
|
|
208
|
+
function normalizePreset(value) {
|
|
209
|
+
return PRESET_ALIASES.get(value.replaceAll(/[-_\s]/gu, "").toLowerCase());
|
|
210
|
+
}
|
|
211
|
+
if (import.meta.vitest) {
|
|
212
|
+
const { describe, expect, it } = import.meta.vitest;
|
|
213
|
+
describe("parseVizeSettings", () => {
|
|
214
|
+
it("reads locale, help level, preset, and type-aware settings", () => {
|
|
215
|
+
expect(parseVizeSettings({
|
|
216
|
+
locale: "ja",
|
|
217
|
+
helpLevel: "short",
|
|
218
|
+
preset: "essential",
|
|
219
|
+
typeAware: true,
|
|
220
|
+
corsaPath: "./node_modules/@typescript/typescript-linux-x64/lib/tsc"
|
|
221
|
+
})).toEqual({
|
|
222
|
+
locale: "ja",
|
|
223
|
+
helpLevel: "short",
|
|
224
|
+
preset: "essential",
|
|
225
|
+
typeAware: true,
|
|
226
|
+
corsaPath: "./node_modules/@typescript/typescript-linux-x64/lib/tsc"
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
it("normalizes preset aliases", () => {
|
|
230
|
+
expect(parseVizeSettings({ preset: "recommended" })).toEqual({ preset: "general-recommended" });
|
|
231
|
+
expect(parseVizeSettings({ preset: "incremental" })).toEqual({ preset: "incremental" });
|
|
232
|
+
expect(parseVizeSettings({ preset: "all" })).toEqual({ preset: "incremental" });
|
|
233
|
+
expect(parseVizeSettings({ preset: "ecosystem" })).toEqual({ preset: "ecosystem" });
|
|
234
|
+
expect(parseVizeSettings({ preset: "strict" })).toEqual({ preset: "opinionated" });
|
|
235
|
+
expect(parseVizeSettings({ preset: "Opnionated" })).toEqual({ preset: "opinionated" });
|
|
236
|
+
});
|
|
237
|
+
it("falls back to showHelp for compatibility", () => {
|
|
238
|
+
expect(parseVizeSettings({
|
|
239
|
+
locale: "ja",
|
|
240
|
+
showHelp: false,
|
|
241
|
+
preset: "Nuxt"
|
|
242
|
+
})).toEqual({
|
|
243
|
+
locale: "ja",
|
|
244
|
+
helpLevel: "none",
|
|
245
|
+
preset: "nuxt"
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
it("ignores invalid values", () => {
|
|
249
|
+
expect(parseVizeSettings({
|
|
250
|
+
locale: 1,
|
|
251
|
+
showHelp: "no",
|
|
252
|
+
helpLevel: "verbose",
|
|
253
|
+
preset: "wide",
|
|
254
|
+
typeAware: "yes",
|
|
255
|
+
corsaPath: false
|
|
256
|
+
})).toEqual({});
|
|
257
|
+
});
|
|
258
|
+
it("ignores non-object vize settings", () => {
|
|
259
|
+
expect(parseVizeSettings(null)).toEqual({});
|
|
260
|
+
expect(parseVizeSettings("ja")).toEqual({});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region src/binding.ts
|
|
266
|
+
function lintPatina(source, filename, settings, enabledRules, ruleOptions) {
|
|
267
|
+
return loadBinding().lintPatinaSfc(source, {
|
|
268
|
+
filename,
|
|
269
|
+
locale: settings.locale,
|
|
270
|
+
helpLevel: settings.helpLevel,
|
|
271
|
+
preset: getActivePreset(settings),
|
|
272
|
+
enabledRules: enabledRules ? [...enabledRules] : void 0,
|
|
273
|
+
typeAware: settings.typeAware,
|
|
274
|
+
corsaPath: settings.corsaPath,
|
|
275
|
+
...ruleOptions
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function getPatinaRules() {
|
|
279
|
+
return loadBinding().getPatinaRules();
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/script-map.ts
|
|
283
|
+
function createSingleScriptMap(source, extractedScript, sfcBlocks = extractSfcBlocks(source)) {
|
|
284
|
+
if (!extractedScript) return null;
|
|
285
|
+
const blocks = sfcBlocks.filter((block) => block.kind === "script" || block.kind === "script-setup");
|
|
286
|
+
let match = null;
|
|
287
|
+
for (const block of blocks) {
|
|
288
|
+
const skipped = countSkippedPrefix(block.content, extractedScript);
|
|
289
|
+
if (skipped === null) continue;
|
|
290
|
+
if (match !== null) return null;
|
|
291
|
+
match = {
|
|
292
|
+
block,
|
|
293
|
+
scriptStart: advancePosition(block.contentStart, block.content.slice(0, skipped))
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return match;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Oxlint trims the leading newline (and any indentation on that line) from a
|
|
300
|
+
* `.vue` script block before handing the program to JS plugins, so the
|
|
301
|
+
* extracted text starts partway into `block.content`. Returns how many
|
|
302
|
+
* characters were dropped, or `null` when the extracted text is not this
|
|
303
|
+
* block's body.
|
|
304
|
+
*/
|
|
305
|
+
function countSkippedPrefix(content, extractedScript) {
|
|
306
|
+
const skipped = content.length - extractedScript.length;
|
|
307
|
+
if (skipped < 0 || !content.endsWith(extractedScript)) return null;
|
|
308
|
+
return /^\s*$/u.test(content.slice(0, skipped)) ? skipped : null;
|
|
309
|
+
}
|
|
310
|
+
function advancePosition(from, text) {
|
|
311
|
+
let { line, column } = from;
|
|
312
|
+
for (const character of text) {
|
|
313
|
+
if (character === "\n") {
|
|
314
|
+
line += 1;
|
|
315
|
+
column = 1;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (character !== "\r") column += 1;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
line,
|
|
322
|
+
column
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function mapToScriptLoc(diagnostic, scriptMap) {
|
|
326
|
+
if (!scriptMap) return null;
|
|
327
|
+
const { block, scriptStart } = scriptMap;
|
|
328
|
+
if (compareLineColumn(diagnostic.location.start, scriptStart) < 0 || compareLineColumn(diagnostic.location.end, block.contentEnd) > 0) return null;
|
|
329
|
+
return {
|
|
330
|
+
start: toScriptPosition(diagnostic.location.start, scriptStart),
|
|
331
|
+
end: toScriptPosition(diagnostic.location.end, scriptStart)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Patina reports 1-based SFC line/column; Oxlint expects 1-based lines and
|
|
336
|
+
* 0-based columns relative to the extracted program.
|
|
337
|
+
*/
|
|
338
|
+
function toScriptPosition(position, scriptStart) {
|
|
339
|
+
if (position.line === scriptStart.line) return {
|
|
340
|
+
line: 1,
|
|
341
|
+
column: position.column - scriptStart.column
|
|
342
|
+
};
|
|
343
|
+
return {
|
|
344
|
+
line: position.line - scriptStart.line + 1,
|
|
345
|
+
column: position.column - 1
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/file-state.ts
|
|
350
|
+
const FILE_STATE_CACHE_CAPACITY = 128;
|
|
351
|
+
const fileStateCache = /* @__PURE__ */ new Map();
|
|
352
|
+
const EMPTY_DIAGNOSTICS = [];
|
|
353
|
+
const TYPE_AWARE_RUNTIME_RULE = "type/corsa-runtime";
|
|
354
|
+
let fileStateCacheClock = 0;
|
|
355
|
+
function getFileState(context) {
|
|
356
|
+
const settings = getVizeSettings(context);
|
|
357
|
+
const physicalSource = fs.readFileSync(context.physicalFilename, "utf8");
|
|
358
|
+
const cacheKey = getCacheKey(context.physicalFilename, settings);
|
|
359
|
+
const cached = fileStateCache.get(cacheKey);
|
|
360
|
+
if (cached && cached.state.revision.physicalSource === physicalSource) {
|
|
361
|
+
validateWorkaroundAuthority(cached.state, context.physicalFilename);
|
|
362
|
+
if (cached.state.extractedScript !== context.sourceCode.text) {
|
|
363
|
+
cached.state.extractedScript = context.sourceCode.text;
|
|
364
|
+
cached.state.scriptMap = void 0;
|
|
365
|
+
}
|
|
366
|
+
cached.lastUsed = nextFileStateCacheClock();
|
|
367
|
+
return cached.state;
|
|
368
|
+
}
|
|
369
|
+
const resolvedSource = resolveWorkaroundSource(physicalSource, context.physicalFilename, readOriginalWorkaroundSource);
|
|
370
|
+
const state = {
|
|
371
|
+
revision: { physicalSource },
|
|
372
|
+
filename: resolvedSource.filename,
|
|
373
|
+
source: resolvedSource.source,
|
|
374
|
+
extractedScript: context.sourceCode.text,
|
|
375
|
+
usesOriginalLocations: resolvedSource.usesOriginalLocations || isScriptLikeFile(resolvedSource.filename),
|
|
376
|
+
reportedDiagnostics: /* @__PURE__ */ new Set(),
|
|
377
|
+
sfcBlocks: void 0,
|
|
378
|
+
scriptMap: void 0,
|
|
379
|
+
allDiagnosticsByRule: null,
|
|
380
|
+
allDiagnosticsIncludesTypeAware: false,
|
|
381
|
+
partialDiagnosticsByRule: /* @__PURE__ */ new Map(),
|
|
382
|
+
requestedRules: /* @__PURE__ */ new Set(),
|
|
383
|
+
reportedTypeAwareRuntimeDiagnostic: false,
|
|
384
|
+
ruleSelection: parseRuleSelection(settings.rules),
|
|
385
|
+
selectedDiagnosticsByRule: null
|
|
386
|
+
};
|
|
387
|
+
fileStateCache.set(cacheKey, {
|
|
388
|
+
state,
|
|
389
|
+
lastUsed: nextFileStateCacheClock()
|
|
390
|
+
});
|
|
391
|
+
evictLeastRecentlyUsedFileState();
|
|
392
|
+
return state;
|
|
393
|
+
}
|
|
394
|
+
function getScriptMap(state) {
|
|
395
|
+
if (state.scriptMap !== void 0) return state.scriptMap;
|
|
396
|
+
state.scriptMap = createSingleScriptMap(state.source, state.extractedScript, getSfcBlocks(state));
|
|
397
|
+
return state.scriptMap;
|
|
398
|
+
}
|
|
399
|
+
function getSfcBlocks(state) {
|
|
400
|
+
if (state.sfcBlocks !== void 0) return state.sfcBlocks;
|
|
401
|
+
state.sfcBlocks = extractSfcBlocks(state.source);
|
|
402
|
+
return state.sfcBlocks;
|
|
403
|
+
}
|
|
404
|
+
function markDiagnosticAsReported(state, diagnostic) {
|
|
405
|
+
const key = diagnosticIdentity(diagnostic);
|
|
406
|
+
if (state.reportedDiagnostics.has(key)) return false;
|
|
407
|
+
state.reportedDiagnostics.add(key);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
function diagnosticIdentity(diagnostic) {
|
|
411
|
+
const { start, end } = diagnostic.location;
|
|
412
|
+
return [
|
|
413
|
+
diagnostic.rule,
|
|
414
|
+
diagnostic.severity,
|
|
415
|
+
diagnostic.message,
|
|
416
|
+
start.line,
|
|
417
|
+
start.column,
|
|
418
|
+
start.offset,
|
|
419
|
+
end.line,
|
|
420
|
+
end.column,
|
|
421
|
+
end.offset
|
|
422
|
+
].join("\0");
|
|
423
|
+
}
|
|
424
|
+
function nextFileStateCacheClock() {
|
|
425
|
+
fileStateCacheClock += 1;
|
|
426
|
+
return fileStateCacheClock;
|
|
427
|
+
}
|
|
428
|
+
function evictLeastRecentlyUsedFileState() {
|
|
429
|
+
if (fileStateCache.size <= FILE_STATE_CACHE_CAPACITY) return;
|
|
430
|
+
let oldestKey;
|
|
431
|
+
let oldestUse = Number.POSITIVE_INFINITY;
|
|
432
|
+
for (const [key, cached] of fileStateCache) if (cached.lastUsed < oldestUse) {
|
|
433
|
+
oldestKey = key;
|
|
434
|
+
oldestUse = cached.lastUsed;
|
|
435
|
+
}
|
|
436
|
+
if (oldestKey !== void 0) fileStateCache.delete(oldestKey);
|
|
437
|
+
}
|
|
438
|
+
function indexDiagnosticsByRule(diagnostics) {
|
|
439
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
440
|
+
for (const diagnostic of diagnostics) {
|
|
441
|
+
const existing = grouped.get(diagnostic.rule);
|
|
442
|
+
if (existing) {
|
|
443
|
+
existing.push(diagnostic);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
grouped.set(diagnostic.rule, [diagnostic]);
|
|
447
|
+
}
|
|
448
|
+
return grouped;
|
|
449
|
+
}
|
|
450
|
+
function diagnosticsForRule(state, diagnosticsByRule, ruleName) {
|
|
451
|
+
const directDiagnostics = diagnosticsByRule.get(ruleName);
|
|
452
|
+
if (directDiagnostics && directDiagnostics.length > 0) return directDiagnostics;
|
|
453
|
+
if (!isTypeAwareRuleName(ruleName) || state.reportedTypeAwareRuntimeDiagnostic) return EMPTY_DIAGNOSTICS;
|
|
454
|
+
const runtimeDiagnostics = diagnosticsByRule.get(TYPE_AWARE_RUNTIME_RULE);
|
|
455
|
+
if (!runtimeDiagnostics || runtimeDiagnostics.length === 0) return EMPTY_DIAGNOSTICS;
|
|
456
|
+
state.reportedTypeAwareRuntimeDiagnostic = true;
|
|
457
|
+
return runtimeDiagnostics;
|
|
458
|
+
}
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/active-rule-collection.ts
|
|
461
|
+
const programs = /* @__PURE__ */ new WeakMap();
|
|
462
|
+
const results = /* @__PURE__ */ new WeakMap();
|
|
463
|
+
function collectActiveRule(context, program, ruleName, options) {
|
|
464
|
+
const state = getFileState(context);
|
|
465
|
+
let collection = programs.get(program);
|
|
466
|
+
if (!collection || collection.selection) {
|
|
467
|
+
collection = {
|
|
468
|
+
state,
|
|
469
|
+
optionsByRule: /* @__PURE__ */ new Map()
|
|
470
|
+
};
|
|
471
|
+
programs.set(program, collection);
|
|
472
|
+
}
|
|
473
|
+
if (collection.state !== state) {
|
|
474
|
+
programs.delete(program);
|
|
475
|
+
throw new Error("Vize source or configuration changed during rule collection");
|
|
476
|
+
}
|
|
477
|
+
collection.optionsByRule.set(ruleName, options === void 0 ? void 0 : structuredClone(options));
|
|
478
|
+
}
|
|
479
|
+
function getActiveRuleDiagnostics(context, program, ruleName) {
|
|
480
|
+
const collection = programs.get(program);
|
|
481
|
+
if (!collection?.optionsByRule.has(ruleName)) return void 0;
|
|
482
|
+
if (getFileState(context) !== collection.state) {
|
|
483
|
+
programs.delete(program);
|
|
484
|
+
throw new Error("Vize source or configuration changed before rule execution");
|
|
485
|
+
}
|
|
486
|
+
if (!collection.selection) {
|
|
487
|
+
collection.selection = createRuleSelection(collection.optionsByRule);
|
|
488
|
+
collection.remaining = new Set(collection.selection.names);
|
|
489
|
+
}
|
|
490
|
+
const { state, selection } = collection;
|
|
491
|
+
let cached = results.get(state);
|
|
492
|
+
if (cached?.key !== selection.cacheKey) {
|
|
493
|
+
const settings = getVizeSettings(context);
|
|
494
|
+
const diagnostics = indexDiagnosticsByRule(lintPatina(state.source, state.filename, {
|
|
495
|
+
...settings,
|
|
496
|
+
typeAware: settings.typeAware === true || selection.names.some(isTypeAwareRuleName)
|
|
497
|
+
}, selection.names, selection.options).diagnostics);
|
|
498
|
+
if (cached) {
|
|
499
|
+
state.reportedDiagnostics.clear();
|
|
500
|
+
state.reportedTypeAwareRuntimeDiagnostic = false;
|
|
501
|
+
}
|
|
502
|
+
cached = {
|
|
503
|
+
key: selection.cacheKey,
|
|
504
|
+
diagnostics
|
|
505
|
+
};
|
|
506
|
+
results.set(state, cached);
|
|
507
|
+
}
|
|
508
|
+
const diagnostics = diagnosticsForRule(state, cached.diagnostics, ruleName);
|
|
509
|
+
collection.remaining.delete(ruleName);
|
|
510
|
+
if (collection.remaining.size === 0) programs.delete(program);
|
|
511
|
+
return {
|
|
512
|
+
state,
|
|
513
|
+
diagnostics
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/format.ts
|
|
518
|
+
const helpTextCache = /* @__PURE__ */ new Map();
|
|
519
|
+
function formatPatinaMessage(diagnostic, options) {
|
|
520
|
+
const sections = [];
|
|
521
|
+
const summaryLine = createSummaryLine(diagnostic.message);
|
|
522
|
+
const details = createDetailsBody(diagnostic.message, summaryLine);
|
|
523
|
+
const summary = options.hasMappedLocation ? summaryLine : `${summaryLine} (${formatInlineLocation(options.blockLabel, diagnostic)})`;
|
|
524
|
+
if (details) sections.push(formatSection("Details", details));
|
|
525
|
+
const helpText = resolveHelpText(diagnostic.help, options.helpLevel);
|
|
526
|
+
if (helpText) sections.push(formatSection("Help", helpText));
|
|
527
|
+
if (sections.length === 0) return summary;
|
|
528
|
+
return `${summary}\n${sections.join("\n")}`;
|
|
529
|
+
}
|
|
530
|
+
function createDetailsBody(message, summary) {
|
|
531
|
+
if (message === summary) return null;
|
|
532
|
+
if (message.startsWith(summary)) return message.slice(summary.length).trim() || null;
|
|
533
|
+
return message;
|
|
534
|
+
}
|
|
535
|
+
function formatSection(title, body) {
|
|
536
|
+
return ` ${title}:\n${indentBlock(body, " ")}`;
|
|
537
|
+
}
|
|
538
|
+
function formatInlineLocation(blockLabel, diagnostic) {
|
|
539
|
+
const { line, column } = diagnostic.location.start;
|
|
540
|
+
return `at ${blockLabel}:${line}:${column}`;
|
|
541
|
+
}
|
|
542
|
+
function resolveHelpText(help, helpLevel) {
|
|
543
|
+
if (help == null || helpLevel === "none") return null;
|
|
544
|
+
const plainText = formatHelpText(help);
|
|
545
|
+
if (helpLevel === "short") return firstMeaningfulLine(plainText);
|
|
546
|
+
return plainText;
|
|
547
|
+
}
|
|
548
|
+
function formatHelpText(help) {
|
|
549
|
+
const cached = helpTextCache.get(help);
|
|
550
|
+
if (cached) return cached;
|
|
551
|
+
const plainText = help.replace(/\r\n?/gu, "\n").replace(/^\s*```[^\n]*$/gmu, "").replace(/\*\*(.*?)\*\*/gu, "$1").replace(/__(.*?)__/gu, "$1").replace(/`([^`\n]+)`/gu, "$1").replace(/^\s*[-*]\s+/gmu, "- ").replace(/\n{3,}/gu, "\n\n").trim();
|
|
552
|
+
helpTextCache.set(help, plainText);
|
|
553
|
+
return plainText;
|
|
554
|
+
}
|
|
555
|
+
function firstMeaningfulLine(text) {
|
|
556
|
+
for (const line of text.split("\n")) {
|
|
557
|
+
const trimmed = line.trim();
|
|
558
|
+
if (trimmed) return trimmed;
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
function indentBlock(text, indent) {
|
|
563
|
+
return text.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
564
|
+
}
|
|
565
|
+
function createSummaryLine(message) {
|
|
566
|
+
const firstSentenceEnd = message.indexOf(". ");
|
|
567
|
+
if (firstSentenceEnd === -1) return message;
|
|
568
|
+
return message.slice(0, firstSentenceEnd + 1);
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/plugin.ts
|
|
572
|
+
function createOxlintDiagnostic(diagnostic, state, scriptMap, helpLevel, program) {
|
|
573
|
+
const loc = state.usesOriginalLocations ? createOriginalSfcLoc(diagnostic) : mapToScriptLoc(diagnostic, scriptMap);
|
|
574
|
+
const block = loc === null ? getDiagnosticBlock(diagnostic, getSfcBlocks(state)) : null;
|
|
575
|
+
const message = formatPatinaMessage(diagnostic, {
|
|
576
|
+
hasMappedLocation: loc !== null,
|
|
577
|
+
blockLabel: formatBlockLabel(block),
|
|
578
|
+
helpLevel
|
|
579
|
+
});
|
|
580
|
+
if (loc == null && state.extractedScript.length === 0) return {
|
|
581
|
+
node: program,
|
|
582
|
+
message
|
|
583
|
+
};
|
|
584
|
+
const fallbackColumn = state.extractedScript.length === 0 ? 0 : 1;
|
|
585
|
+
return {
|
|
586
|
+
loc: loc ?? {
|
|
587
|
+
start: {
|
|
588
|
+
line: 1,
|
|
589
|
+
column: fallbackColumn
|
|
590
|
+
},
|
|
591
|
+
end: {
|
|
592
|
+
line: 1,
|
|
593
|
+
column: fallbackColumn
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
message
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function shouldReportForCurrentProgram(diagnostic, state, scriptMap) {
|
|
600
|
+
if (state.usesOriginalLocations) return isScriptLikeFile(state.filename) || isLocationBridgeProgram(state.extractedScript);
|
|
601
|
+
if (scriptMap == null) return true;
|
|
602
|
+
const block = getDiagnosticBlock(diagnostic, getSfcBlocks(state));
|
|
603
|
+
return !isScriptBlock(block) || block === scriptMap.block;
|
|
604
|
+
}
|
|
605
|
+
function isScriptBlock(block) {
|
|
606
|
+
return block?.kind === "script" || block?.kind === "script-setup";
|
|
607
|
+
}
|
|
608
|
+
function createOriginalSfcLoc(diagnostic) {
|
|
609
|
+
return {
|
|
610
|
+
start: {
|
|
611
|
+
line: diagnostic.location.start.line,
|
|
612
|
+
column: Math.max(0, diagnostic.location.start.column - 1)
|
|
613
|
+
},
|
|
614
|
+
end: {
|
|
615
|
+
line: diagnostic.location.end.line,
|
|
616
|
+
column: Math.max(0, diagnostic.location.end.column - 1)
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function createPatinaRule(ruleMeta) {
|
|
621
|
+
return defineRule({
|
|
622
|
+
meta: {
|
|
623
|
+
type: ruleMeta.defaultSeverity === "error" ? "problem" : "suggestion",
|
|
624
|
+
docs: { description: ruleMeta.description },
|
|
625
|
+
schema: ruleOptionsSchema(ruleMeta.name)
|
|
626
|
+
},
|
|
627
|
+
createOnce(context) {
|
|
628
|
+
return {
|
|
629
|
+
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
|
+
collectActiveRule(context, program, ruleMeta.name, getRuleOptions(ruleMeta.name, contextOptions(context)));
|
|
635
|
+
},
|
|
636
|
+
"Program:exit"(program) {
|
|
637
|
+
const collected = getActiveRuleDiagnostics(context, program, ruleMeta.name);
|
|
638
|
+
if (!collected) return;
|
|
639
|
+
const { state } = collected;
|
|
640
|
+
const helpLevel = getVizeSettings(context).helpLevel ?? "full";
|
|
641
|
+
const scriptMap = getScriptMap(state);
|
|
642
|
+
const diagnostics = collected.diagnostics.filter((diagnostic) => shouldReportForCurrentProgram(diagnostic, state, scriptMap));
|
|
643
|
+
if (diagnostics.length === 0) return;
|
|
644
|
+
for (const diagnostic of diagnostics) {
|
|
645
|
+
if (!markDiagnosticAsReported(state, diagnostic)) continue;
|
|
646
|
+
context.report(createOxlintDiagnostic(diagnostic, state, scriptMap, helpLevel, program));
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
function ruleOptionsSchema(ruleName) {
|
|
654
|
+
switch (ruleName) {
|
|
655
|
+
case "vue/component-name-in-template-casing": return [{ enum: ["PascalCase", "kebab-case"] }];
|
|
656
|
+
case "script/custom-event-name-casing": return [{ enum: ["camelCase", "kebab-case"] }];
|
|
657
|
+
case "vue/no-mutating-props": return [noMutatingPropsSchema()];
|
|
658
|
+
case "vue/sfc-element-order": return [sfcElementOrderSchema()];
|
|
659
|
+
case "vue/html-self-closing": return [htmlSelfClosingSchema()];
|
|
660
|
+
case "vue/v-on-event-hyphenation":
|
|
661
|
+
case "vue/attribute-hyphenation": return [{ enum: ["always", "never"] }];
|
|
662
|
+
default: return [];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function noMutatingPropsSchema() {
|
|
666
|
+
return {
|
|
667
|
+
type: "object",
|
|
668
|
+
properties: { shallowOnly: { type: "boolean" } },
|
|
669
|
+
additionalProperties: false
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function sfcElementOrderSchema() {
|
|
673
|
+
const selector = { type: "string" };
|
|
674
|
+
return {
|
|
675
|
+
type: "object",
|
|
676
|
+
properties: { order: {
|
|
677
|
+
type: "array",
|
|
678
|
+
items: { oneOf: [selector, {
|
|
679
|
+
type: "array",
|
|
680
|
+
items: selector
|
|
681
|
+
}] }
|
|
682
|
+
} },
|
|
683
|
+
additionalProperties: false
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function htmlSelfClosingSchema() {
|
|
687
|
+
const style = { enum: [
|
|
688
|
+
"always",
|
|
689
|
+
"never",
|
|
690
|
+
"any"
|
|
691
|
+
] };
|
|
692
|
+
return {
|
|
693
|
+
type: "object",
|
|
694
|
+
properties: {
|
|
695
|
+
html: {
|
|
696
|
+
type: "object",
|
|
697
|
+
properties: {
|
|
698
|
+
void: style,
|
|
699
|
+
normal: style,
|
|
700
|
+
component: style
|
|
701
|
+
},
|
|
702
|
+
additionalProperties: false
|
|
703
|
+
},
|
|
704
|
+
svg: style,
|
|
705
|
+
math: style
|
|
706
|
+
},
|
|
707
|
+
additionalProperties: false
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function contextOptions(context) {
|
|
711
|
+
const options = context.options;
|
|
712
|
+
return Array.isArray(options) ? options : [];
|
|
713
|
+
}
|
|
714
|
+
var plugin_default = definePlugin({
|
|
715
|
+
meta: { name: "vize" },
|
|
716
|
+
rules: Object.fromEntries(getPatinaRules().map((ruleMeta) => [ruleMeta.name, createPatinaRule(ruleMeta)]))
|
|
717
|
+
});
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/configs.ts
|
|
720
|
+
const TYPE_AWARE_RULE_PREFIX = "type/";
|
|
721
|
+
function createVizeRuleConfig(options = {}) {
|
|
722
|
+
const presets = resolveRuleConfigPresets(options);
|
|
723
|
+
const includeTypeAware = options.includeTypeAware ?? false;
|
|
724
|
+
const rules = {};
|
|
725
|
+
for (const ruleMeta of getPatinaRules()) {
|
|
726
|
+
if (!matchesPresets(ruleMeta, presets)) continue;
|
|
727
|
+
if (!includeTypeAware && isTypeAwareRule(ruleMeta)) continue;
|
|
728
|
+
rules[`vize/${ruleMeta.name}`] = toOxlintSeverity(ruleMeta.defaultSeverity);
|
|
729
|
+
}
|
|
730
|
+
return rules;
|
|
731
|
+
}
|
|
732
|
+
const configs = {
|
|
733
|
+
all: createVizeRuleConfig({ preset: "all" }),
|
|
734
|
+
ecosystem: createVizeRuleConfig({ preset: "ecosystem" }),
|
|
735
|
+
ecosystemWithTypeAware: createVizeRuleConfig({
|
|
736
|
+
includeTypeAware: true,
|
|
737
|
+
preset: "ecosystem"
|
|
738
|
+
}),
|
|
739
|
+
essential: createVizeRuleConfig({ preset: "essential" }),
|
|
740
|
+
happyPath: createVizeRuleConfig({ preset: "happy-path" }),
|
|
741
|
+
happyPathWithTypeAware: createVizeRuleConfig({
|
|
742
|
+
includeTypeAware: true,
|
|
743
|
+
preset: "happy-path"
|
|
744
|
+
}),
|
|
745
|
+
nuxt: createVizeRuleConfig({ preset: "nuxt" }),
|
|
746
|
+
opinionated: createVizeRuleConfig({ preset: "opinionated" }),
|
|
747
|
+
opinionatedWithTypeAware: createVizeRuleConfig({
|
|
748
|
+
includeTypeAware: true,
|
|
749
|
+
preset: "opinionated"
|
|
750
|
+
}),
|
|
751
|
+
recommended: createVizeRuleConfig({ preset: "general-recommended" }),
|
|
752
|
+
recommendedWithTypeAware: createVizeRuleConfig({
|
|
753
|
+
includeTypeAware: true,
|
|
754
|
+
preset: "general-recommended"
|
|
755
|
+
})
|
|
756
|
+
};
|
|
757
|
+
function normalizeRuleConfigPreset(preset) {
|
|
758
|
+
return preset === "happy-path" ? "general-recommended" : preset;
|
|
759
|
+
}
|
|
760
|
+
function resolveRuleConfigPresets(options) {
|
|
761
|
+
const requestedPresets = [...toPresetArray((options.preset === void 0 && options.presets === void 0 ? "general-recommended" : options.preset) ?? []), ...options.presets ?? []].map(normalizeRuleConfigPreset);
|
|
762
|
+
return [...new Set(requestedPresets)];
|
|
763
|
+
}
|
|
764
|
+
function toPresetArray(preset) {
|
|
765
|
+
return Array.isArray(preset) ? preset : [preset];
|
|
766
|
+
}
|
|
767
|
+
function matchesPresets(ruleMeta, presets) {
|
|
768
|
+
return presets.includes("all") || presets.some((preset) => preset !== "all" && ruleMeta.presets.includes(preset));
|
|
769
|
+
}
|
|
770
|
+
function isTypeAwareRule(ruleMeta) {
|
|
771
|
+
return ruleMeta.name.startsWith(TYPE_AWARE_RULE_PREFIX);
|
|
772
|
+
}
|
|
773
|
+
function toOxlintSeverity(severity) {
|
|
774
|
+
return severity === "warning" ? "warn" : severity;
|
|
775
|
+
}
|
|
776
|
+
if (import.meta.vitest) {
|
|
777
|
+
const { describe, expect, it } = import.meta.vitest;
|
|
778
|
+
describe("createVizeRuleConfig", () => {
|
|
779
|
+
it("normalizes Vize warning severity to Oxlint's warn", () => {
|
|
780
|
+
expect(configs.recommended["vize/vue/no-multi-spaces"]).toBe("warn");
|
|
781
|
+
});
|
|
782
|
+
it("accepts the CLI happy-path preset alias", () => {
|
|
783
|
+
expect(createVizeRuleConfig({ preset: "happy-path" })).toEqual(configs.recommended);
|
|
784
|
+
expect(configs.happyPath).toEqual(configs.recommended);
|
|
785
|
+
});
|
|
786
|
+
it("unions multiple presets", () => {
|
|
787
|
+
const config = createVizeRuleConfig({ presets: ["happy-path", "ecosystem"] });
|
|
788
|
+
expect(config["vize/script/valid-define-props"]).toBe("error");
|
|
789
|
+
expect(config["vize/ecosystem/router-link-require-to"]).toBe("error");
|
|
790
|
+
expect(config["vize/script/no-options-api"]).toBeUndefined();
|
|
791
|
+
});
|
|
792
|
+
it("accepts multiple presets through the preset option", () => {
|
|
793
|
+
expect(createVizeRuleConfig({ preset: ["happy-path", "ecosystem"] })).toEqual(createVizeRuleConfig({ presets: ["happy-path", "ecosystem"] }));
|
|
794
|
+
});
|
|
795
|
+
it("filters rules by preset", () => {
|
|
796
|
+
expect(configs.essential["vize/vue/require-v-for-key"]).toBe("error");
|
|
797
|
+
expect(configs.essential["vize/vue/require-scoped-style"]).toBeUndefined();
|
|
798
|
+
});
|
|
799
|
+
it("enables script correctness rules in essential and happy-path", () => {
|
|
800
|
+
expect(configs.essential["vize/script/valid-define-props"]).toBe("error");
|
|
801
|
+
expect(configs.essential["vize/script/no-import-compiler-macros"]).toBe("error");
|
|
802
|
+
expect(configs.recommended["vize/script/valid-define-props"]).toBe("error");
|
|
803
|
+
expect(configs.recommended["vize/script/no-import-compiler-macros"]).toBe("error");
|
|
804
|
+
});
|
|
805
|
+
it("keeps low-noise happy-path script warnings out of essential", () => {
|
|
806
|
+
expect(configs.recommended["vize/script/no-duplicate-attr-inheritance"]).toBe("warn");
|
|
807
|
+
expect(configs.recommended["vize/script/no-reactive-destructure"]).toBeUndefined();
|
|
808
|
+
expect(configs.essential["vize/script/no-duplicate-attr-inheritance"]).toBeUndefined();
|
|
809
|
+
expect(configs.recommended["vize/script/no-unused-emit-declarations"]).toBeUndefined();
|
|
810
|
+
expect(configs.essential["vize/script/no-unused-emit-declarations"]).toBeUndefined();
|
|
811
|
+
});
|
|
812
|
+
it("keeps opinionated script style out of happy-path", () => {
|
|
813
|
+
expect(configs.recommended["vize/script/no-options-api"]).toBeUndefined();
|
|
814
|
+
expect(configs.recommended["vize/script/define-props-declaration"]).toBeUndefined();
|
|
815
|
+
expect(configs.opinionated["vize/script/no-options-api"]).toBe("error");
|
|
816
|
+
});
|
|
817
|
+
it("skips unstable type-aware rules by default", () => {
|
|
818
|
+
expect(configs.opinionated["vize/type/require-typed-props"]).toBeUndefined();
|
|
819
|
+
expect(configs.opinionatedWithTypeAware["vize/type/require-typed-props"]).toBe("warn");
|
|
820
|
+
});
|
|
821
|
+
it("keeps Options API allowed in the Nuxt preset", () => {
|
|
822
|
+
expect(configs.nuxt["vize/script/no-options-api"]).toBeUndefined();
|
|
823
|
+
expect(configs.opinionated["vize/script/no-options-api"]).toBe("error");
|
|
824
|
+
});
|
|
825
|
+
it("enables Nuxt framework rules only in the Nuxt and all presets", () => {
|
|
826
|
+
for (const ruleName of [
|
|
827
|
+
"prefer-import-meta",
|
|
828
|
+
"no-page-meta-runtime-values",
|
|
829
|
+
"no-nuxt-config-test-key",
|
|
830
|
+
"nuxt-config-keys-order"
|
|
831
|
+
]) {
|
|
832
|
+
const ruleId = `vize/nuxt/${ruleName}`;
|
|
833
|
+
expect(configs.nuxt[ruleId]).toBe("error");
|
|
834
|
+
expect(configs.all[ruleId]).toBe("error");
|
|
835
|
+
expect(configs.ecosystem[ruleId]).toBeUndefined();
|
|
836
|
+
expect(configs.opinionated[ruleId]).toBeUndefined();
|
|
837
|
+
expect(configs.recommended[ruleId]).toBeUndefined();
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
it("keeps ecosystem rules out of non-ecosystem presets until explicitly selected", () => {
|
|
841
|
+
expect(configs.recommended["vize/ecosystem/router-link-require-to"]).toBeUndefined();
|
|
842
|
+
expect(configs.nuxt["vize/ecosystem/nuxt-prefer-nuxt-link"]).toBe("warn");
|
|
843
|
+
expect(configs.ecosystem["vize/ecosystem/nuxt-prefer-nuxt-link"]).toBeUndefined();
|
|
844
|
+
expect(configs.all["vize/ecosystem/nuxt-prefer-nuxt-link"]).toBe("warn");
|
|
845
|
+
expect(configs.ecosystem["vize/ecosystem/router-link-require-to"]).toBe("error");
|
|
846
|
+
expect(configs.ecosystem["vize/ecosystem/vue-i18n-no-missing-key"]).toBe("warn");
|
|
847
|
+
expect(configs.ecosystem["vize/ecosystem/void-link-require-href"]).toBe("error");
|
|
848
|
+
expect(configs.all["vize/ecosystem/router-link-require-to"]).toBe("error");
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
853
|
+
export { getPatinaRules as a, isTypeAwareRuleName as c, getRuleOptions as d, formatPatinaMessage as i, createRuleSelection as l, createVizeRuleConfig as n, getActivePreset as o, plugin_default as r, getVizeSettings as s, configs as t, selectVizeRuleConfig as u };
|