@geoql/oxlint-plugin-vue-doctor 1.0.0 → 1.2.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.ts CHANGED
@@ -1,62 +1,7 @@
1
- //#region src/rule-types.d.ts
2
- interface SourceLocation {
3
- start: {
4
- line: number;
5
- column: number;
6
- };
7
- end: {
8
- line: number;
9
- column: number;
10
- };
11
- }
12
- interface AstNode {
13
- type: string;
14
- loc?: SourceLocation;
15
- [key: string]: unknown;
16
- }
17
- interface Fix {
18
- range: [number, number];
19
- text: string;
20
- node?: AstNode;
21
- }
22
- interface Fixer {
23
- replaceText: (node: AstNode, text: string) => Fix;
24
- }
25
- type FixFn = (fixer: Fixer) => Fix;
26
- interface ReportDescriptor {
27
- node: AstNode;
28
- message: string;
29
- fix?: FixFn;
30
- }
31
- interface RuleContext {
32
- report: (descriptor: ReportDescriptor) => void;
33
- getFilename?: () => string | undefined;
34
- settings?: Record<string, unknown>;
35
- /** Capability tokens detected for the project (e.g. 'auto-imports:vue'). */
36
- capabilities?: Set<string>;
37
- }
38
- type RuleVisitor = (node: AstNode) => void;
39
- interface RuleMeta {
40
- name?: string;
41
- fixable?: 'code' | 'whitespace';
42
- }
43
- interface Rule {
44
- meta?: RuleMeta;
45
- create: (context: RuleContext) => Record<string, RuleVisitor>;
46
- fix?: (node: AstNode) => string | null;
47
- }
48
- interface Plugin {
49
- meta: {
50
- name: string;
51
- };
52
- rules: Record<string, Rule>;
53
- }
54
- //#endregion
1
+ import { Plugin, Rule, RuleContext, VUE_AUTO_IMPORTED } from "@geoql/doctor-rule-core";
2
+
55
3
  //#region src/plugin.d.ts
56
4
  declare const plugin: Plugin;
57
5
  //#endregion
58
- //#region src/shared/vue-auto-imported-symbols.d.ts
59
- declare const VUE_AUTO_IMPORTED: ReadonlySet<string>;
60
- //#endregion
61
6
  export { type Rule, type RuleContext, VUE_AUTO_IMPORTED, plugin as default, plugin };
62
7
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,506 +1,8 @@
1
- //#region src/define-rule.ts
2
- function defineRule(rule) {
3
- const userFix = rule.fix;
4
- if (!userFix) return rule;
5
- return {
6
- ...rule,
7
- meta: {
8
- ...rule.meta,
9
- fixable: "code"
10
- },
11
- create(context) {
12
- const wrapped = {
13
- ...context,
14
- report(descriptor) {
15
- const replacement = userFix(descriptor.node);
16
- if (replacement === null) {
17
- context.report(descriptor);
18
- return;
19
- }
20
- context.report({
21
- ...descriptor,
22
- fix: (fixer) => fixer.replaceText(descriptor.node, replacement)
23
- });
24
- }
25
- };
26
- return rule.create(wrapped);
27
- }
28
- };
29
- }
30
- //#endregion
31
- //#region src/rules/composition/defineProps-typed.ts
32
- const MESSAGE$8 = `This defineProps() call declares props with a runtime object, which discards static type information. Use the type-only generic form (defineProps<{ ... }>()) so props are fully typed. See https://vuejs.org/guide/typescript/composition-api.html#typing-component-props`;
33
- function calleeName$5(node) {
34
- const callee = node.callee;
35
- if (callee?.type === "Identifier") return callee.name;
36
- }
37
- const definePropsTyped = defineRule({ create(context) {
38
- return { CallExpression(node) {
39
- if (calleeName$5(node) !== "defineProps") return;
40
- if (node.arguments[0]?.type === "ObjectExpression") context.report({
41
- node,
42
- message: MESSAGE$8
43
- });
44
- } };
45
- } });
46
- //#endregion
47
- //#region src/rules/composition/prefer-script-setup-for-new-files.ts
48
- const MESSAGE$7 = `This component nests composition-API logic inside an options-object setup(). Use a <script setup> block instead — it removes the setup() boilerplate and exposes bindings to the template directly. See https://vuejs.org/api/sfc-script-setup.html`;
49
- function isFunctionValue(node) {
50
- return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
51
- }
52
- function isSetupProperty(prop) {
53
- if (prop.type !== "Property" || prop.computed === true) return false;
54
- if (prop.shorthand === true) return false;
55
- const key = prop.key;
56
- if (key.type !== "Identifier" || key.name !== "setup") return false;
57
- return isFunctionValue(prop.value);
58
- }
59
- const preferScriptSetupForNewFiles = defineRule({ create(context) {
60
- return { ExportDefaultDeclaration(node) {
61
- const declaration = node.declaration;
62
- if (declaration?.type !== "ObjectExpression") return;
63
- if (declaration.properties.some(isSetupProperty)) context.report({
64
- node,
65
- message: MESSAGE$7
66
- });
67
- } };
68
- } });
69
- //#endregion
70
- //#region src/rules/ai-slop/no-destructure-props-without-toRefs.ts
71
- const MESSAGE$6 = `Destructuring props loses reactivity. Wrap with toRefs(...) to keep refs. See https://vuejs.org/guide/components/props.html#reactive-props-destructure`;
72
- function calleeName$4(node) {
73
- const callee = node.callee;
74
- if (callee?.type === "Identifier") return callee.name;
75
- }
76
- function isDefinePropsCall(node) {
77
- if (!node || node.type !== "CallExpression") return false;
78
- const name = calleeName$4(node);
79
- if (name === "defineProps") return true;
80
- if (name === "withDefaults") {
81
- const args = node.arguments;
82
- return isDefinePropsCall(args?.[0]);
83
- }
84
- return false;
85
- }
86
- function isToRefsCall$1(node) {
87
- return node?.type === "CallExpression" && calleeName$4(node) === "toRefs";
88
- }
89
- const noDestructurePropsWithoutToRefs = defineRule({ create(context) {
90
- const propsSources = /* @__PURE__ */ new Set();
91
- function registerSetupParam(fn) {
92
- const first = (fn?.params)?.[0];
93
- if (first?.type === "Identifier") propsSources.add(first.name);
94
- }
95
- return {
96
- VariableDeclarator(node) {
97
- const id = node.id;
98
- const init = node.init;
99
- if (id.type === "Identifier" && isDefinePropsCall(init)) {
100
- propsSources.add(id.name);
101
- return;
102
- }
103
- if (id.type !== "ObjectPattern" || !init) return;
104
- if (isToRefsCall$1(init)) return;
105
- if (isDefinePropsCall(init) || init.type === "Identifier" && propsSources.has(init.name)) context.report({
106
- node,
107
- message: MESSAGE$6
108
- });
109
- },
110
- Property(node) {
111
- const key = node.key;
112
- if (key?.type === "Identifier" && key.name === "setup") registerSetupParam(node.value);
113
- }
114
- };
115
- } });
116
- //#endregion
117
- //#region src/rules/ai-slop/no-destructure-reactive-without-toRefs.ts
118
- const MESSAGE$5 = `Destructuring reactive state loses reactivity. Wrap with toRefs(...) or read single keys via toRef(state, 'key'). See https://vuejs.org/guide/essentials/reactivity-fundamentals.html#destructuring-reactive-state`;
119
- const REACTIVE_FACTORIES = new Set(["reactive", "shallowReactive"]);
120
- function calleeName$3(node) {
121
- const callee = node.callee;
122
- if (callee?.type === "Identifier") return callee.name;
123
- }
124
- function isReactiveCall(node) {
125
- if (!node || node.type !== "CallExpression") return false;
126
- const name = calleeName$3(node);
127
- if (name && REACTIVE_FACTORIES.has(name)) return true;
128
- if (name === "readonly") {
129
- const args = node.arguments;
130
- return isReactiveCall(args?.[0]);
131
- }
132
- return false;
133
- }
134
- function isToRefsCall(node) {
135
- return node?.type === "CallExpression" && calleeName$3(node) === "toRefs";
136
- }
137
- const noDestructureReactiveWithoutToRefs = defineRule({ create(context) {
138
- const reactiveSources = /* @__PURE__ */ new Set();
139
- return { VariableDeclarator(node) {
140
- const id = node.id;
141
- const init = node.init;
142
- if (id.type === "Identifier" && isReactiveCall(init)) {
143
- reactiveSources.add(id.name);
144
- return;
145
- }
146
- if (id.type !== "ObjectPattern" || !init) return;
147
- if (isToRefsCall(init)) return;
148
- if (isReactiveCall(init) || init.type === "Identifier" && reactiveSources.has(init.name)) context.report({
149
- node,
150
- message: MESSAGE$5
151
- });
152
- } };
153
- } });
154
- //#endregion
155
- //#region src/rules/ai-slop/no-em-dash-in-string.ts
156
- const EM_DASH = "—";
157
- const noEmDashInString = defineRule({
158
- create(context) {
159
- return { Literal(node) {
160
- const literal = node;
161
- if (typeof literal.value !== "string") return;
162
- if (!literal.value.includes(EM_DASH)) return;
163
- context.report({
164
- node,
165
- message: "Em dash in string literal reads as AI-generated output; use comma, colon, or parentheses."
166
- });
167
- } };
168
- },
169
- fix(node) {
170
- const literal = node;
171
- if (typeof literal.value !== "string") return null;
172
- const raw = literal.raw;
173
- if (typeof raw !== "string") return null;
174
- if (!raw.includes(EM_DASH)) return null;
175
- return raw.split(EM_DASH).join("-");
176
- }
177
- });
178
- //#endregion
179
- //#region src/shared/vue-auto-imported-symbols.ts
180
- const VUE_AUTO_IMPORTED = new Set([
181
- "ref",
182
- "shallowRef",
183
- "computed",
184
- "reactive",
185
- "shallowReactive",
186
- "readonly",
187
- "shallowReadonly",
188
- "toRef",
189
- "toRefs",
190
- "isRef",
191
- "isReactive",
192
- "isReadonly",
193
- "isProxy",
194
- "unref",
195
- "triggerRef",
196
- "customRef",
197
- "markRaw",
198
- "toRaw",
199
- "watch",
200
- "watchEffect",
201
- "watchPostEffect",
202
- "watchSyncEffect",
203
- "effectScope",
204
- "getCurrentScope",
205
- "onScopeDispose",
206
- "onMounted",
207
- "onUpdated",
208
- "onUnmounted",
209
- "onBeforeMount",
210
- "onBeforeUpdate",
211
- "onBeforeUnmount",
212
- "onErrorCaptured",
213
- "onRenderTracked",
214
- "onRenderTriggered",
215
- "onActivated",
216
- "onDeactivated",
217
- "onServerPrefetch",
218
- "provide",
219
- "inject",
220
- "hasInjectionContext",
221
- "getCurrentInstance",
222
- "nextTick",
223
- "defineComponent",
224
- "defineAsyncComponent",
225
- "useTemplateRef",
226
- "useId",
227
- "useModel"
228
- ]);
229
- //#endregion
230
- //#region src/rules/ai-slop/no-imports-from-vue-when-auto-imported.ts
231
- const DOCS_URL = "https://nuxt.com/docs/4.x/guide/concepts/auto-imports";
232
- const WHOLE_MESSAGE = `The entire import from 'vue' can be removed — these names are auto-imported in this project. See ${DOCS_URL}`;
233
- const SPECIFIER_MESSAGE = `This symbol is auto-imported in your project. Remove it from the 'vue' import — it is dead weight in the diff. See ${DOCS_URL}`;
234
- function isAutoImportedValueSpecifier(spec) {
235
- if (spec.importKind === "type") return false;
236
- const imported = spec.imported;
237
- return VUE_AUTO_IMPORTED.has(imported.name);
238
- }
239
- const noImportsFromVueWhenAutoImported = defineRule({ create(context) {
240
- let gated = false;
241
- return {
242
- Program() {
243
- gated = context.capabilities?.has("auto-imports:vue") !== true;
244
- },
245
- ImportDeclaration(node) {
246
- if (gated) return;
247
- if (node.importKind === "type") return;
248
- if (node.source.value !== "vue") return;
249
- const specifiers = node.specifiers;
250
- const named = specifiers.filter((s) => s.type === "ImportSpecifier");
251
- if (named.length === 0) return;
252
- const offending = named.filter(isAutoImportedValueSpecifier);
253
- if (offending.length === 0) return;
254
- if (offending.length === named.length && specifiers.length === named.length) {
255
- context.report({
256
- node,
257
- message: WHOLE_MESSAGE
258
- });
259
- return;
260
- }
261
- for (const spec of offending) context.report({
262
- node: spec,
263
- message: SPECIFIER_MESSAGE
264
- });
265
- }
266
- };
267
- } });
268
- //#endregion
269
- //#region src/rules/ai-slop/no-non-null-assertion-on-ref-value.ts
270
- const MESSAGE$4 = `Non-null assertion on a ref's .value hides nullability. Add an explicit guard ('if (!r.value) return') or use optional chaining ('r.value?.x'). See https://vuejs.org/guide/typescript/composition-api.html#typing-ref`;
271
- const REF_FACTORIES = new Set([
272
- "ref",
273
- "shallowRef",
274
- "customRef",
275
- "computed",
276
- "useTemplateRef"
277
- ]);
278
- function calleeName$2(node) {
279
- const callee = node.callee;
280
- if (callee?.type === "Identifier") return callee.name;
281
- }
282
- function isRefFactoryCall(node) {
283
- if (!node || node.type !== "CallExpression") return false;
284
- const name = calleeName$2(node);
285
- return name !== void 0 && REF_FACTORIES.has(name);
286
- }
287
- function isDotValue(node) {
288
- if (!node || node.type !== "MemberExpression") return false;
289
- const property = node.property;
290
- return node.computed !== true && property?.type === "Identifier" && property.name === "value";
291
- }
292
- const noNonNullAssertionOnRefValue = defineRule({ create(context) {
293
- const refSources = /* @__PURE__ */ new Set();
294
- return {
295
- VariableDeclarator(node) {
296
- const id = node.id;
297
- if (id.type === "Identifier" && isRefFactoryCall(node.init)) refSources.add(id.name);
298
- },
299
- TSNonNullExpression(node) {
300
- const arg = node.expression;
301
- if (!isDotValue(arg)) return;
302
- const object = arg.object;
303
- if (object?.type !== "Identifier") return;
304
- if (!refSources.has(object.name)) return;
305
- context.report({
306
- node,
307
- message: MESSAGE$4
308
- });
309
- }
310
- };
311
- } });
312
- //#endregion
313
- //#region src/rules/reactivity/prefer-readonly-for-injected.ts
314
- const MESSAGE$3 = `Mutating an injected value from a consumer breaks one-way data flow and makes the source of changes hard to trace. Wrap the provided value in readonly() at the provide site and expose a dedicated mutator instead. See https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity`;
315
- const MUTATING_METHODS = new Set([
316
- "push",
317
- "pop",
318
- "splice",
319
- "shift",
320
- "unshift",
321
- "sort",
322
- "reverse"
323
- ]);
324
- function calleeName$1(node) {
325
- const callee = node.callee;
326
- if (callee?.type === "Identifier") return callee.name;
327
- }
328
- function memberObjectName(node) {
329
- if (!node || node.type !== "MemberExpression") return void 0;
330
- const object = node.object;
331
- if (object.type !== "Identifier") return void 0;
332
- return object.name;
333
- }
334
- const preferReadonlyForInjected = defineRule({ create(context) {
335
- const injectedSources = /* @__PURE__ */ new Set();
336
- return {
337
- VariableDeclarator(node) {
338
- const id = node.id;
339
- const init = node.init;
340
- if (id.type === "Identifier" && init?.type === "CallExpression" && calleeName$1(init) === "inject") injectedSources.add(id.name);
341
- },
342
- AssignmentExpression(node) {
343
- const name = memberObjectName(node.left);
344
- if (name !== void 0 && injectedSources.has(name)) context.report({
345
- node,
346
- message: MESSAGE$3
347
- });
348
- },
349
- CallExpression(node) {
350
- const callee = node.callee;
351
- if (callee?.type !== "MemberExpression" || callee.computed === true) return;
352
- const name = memberObjectName(callee);
353
- if (name === void 0 || !injectedSources.has(name)) return;
354
- const property = callee.property;
355
- if (MUTATING_METHODS.has(property.name)) context.report({
356
- node,
357
- message: MESSAGE$3
358
- });
359
- }
360
- };
361
- } });
362
- //#endregion
363
- //#region src/rules/reactivity/prefer-shallowRef-for-large-data.ts
364
- const MESSAGE$2 = `This ref holds large or fetched data, so deep reactivity tracks every nested property and wastes memory and CPU. Use shallowRef() (and triggerRef on replacement) for large arrays, big objects, or server payloads. See https://vuejs.org/api/reactivity-advanced.html#shallowref`;
365
- const ARRAY_LIMIT = 100;
366
- const OBJECT_LIMIT = 50;
367
- const FETCH_IDENTIFIERS = new Set(["$fetch", "useFetch"]);
368
- function calleeName(node) {
369
- const callee = node.callee;
370
- if (callee?.type === "Identifier") return callee.name;
371
- }
372
- function isAxiosGet(node) {
373
- const callee = node.callee;
374
- if (callee?.type !== "MemberExpression" || callee.computed === true) return false;
375
- const object = callee.object;
376
- const property = callee.property;
377
- return object.type === "Identifier" && object.name === "axios" && property.name === "get";
378
- }
379
- function isFetchSource(node) {
380
- if (node.type !== "CallExpression") return false;
381
- const name = calleeName(node);
382
- if (name !== void 0 && FETCH_IDENTIFIERS.has(name)) return true;
383
- return isAxiosGet(node);
384
- }
385
- function isLargeData(node) {
386
- if (!node) return false;
387
- const value = node.type === "AwaitExpression" ? node.argument : node;
388
- if (value.type === "ArrayExpression") return value.elements.length > ARRAY_LIMIT;
389
- if (value.type === "ObjectExpression") return value.properties.length > OBJECT_LIMIT;
390
- return isFetchSource(value);
391
- }
392
- const preferShallowRefForLargeData = defineRule({ create(context) {
393
- return { CallExpression(node) {
394
- if (calleeName(node) !== "ref") return;
395
- const init = node.arguments[0];
396
- if (isLargeData(init)) context.report({
397
- node,
398
- message: MESSAGE$2
399
- });
400
- } };
401
- } });
402
- //#endregion
403
- //#region src/rules/reactivity/watch-without-cleanup.ts
404
- const MESSAGE$1 = `This watcher registers a listener, timer, or observer but never cleans it up, so it leaks across re-runs and hot reloads. Return a cleanup function (or call onWatcherCleanup) inside the watcher. See https://vuejs.org/guide/essentials/watchers.html#side-effect-cleanup`;
405
- const REGISTER_CALLS = new Set([
406
- "addEventListener",
407
- "setInterval",
408
- "setTimeout"
409
- ]);
410
- const OBSERVERS = new Set([
411
- "IntersectionObserver",
412
- "MutationObserver",
413
- "ResizeObserver"
414
- ]);
415
- const CLEANUP_CALLS = new Set(["onCleanup", "onWatcherCleanup"]);
416
- function calleeIdentifierName(node) {
417
- const callee = node.callee;
418
- if (callee?.type === "Identifier") return callee.name;
419
- }
420
- function calledMethodName(node) {
421
- const callee = node.callee;
422
- if (callee?.type === "Identifier") return callee.name;
423
- if (callee?.type === "MemberExpression" && callee.computed !== true) return callee.property.name;
424
- }
425
- function isFunction(node) {
426
- return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
427
- }
428
- const watchWithoutCleanup = defineRule({ create(context) {
429
- const watchStack = [];
430
- return {
431
- CallExpression(node) {
432
- const name = calleeIdentifierName(node);
433
- if (name === "watch" || name === "watchEffect") {
434
- const isEffect = name === "watchEffect";
435
- const callback = node.arguments[isEffect ? 0 : 1];
436
- if (!isFunction(callback)) return;
437
- watchStack.push({
438
- sourceCall: node,
439
- isEffect,
440
- hasRegistration: false,
441
- hasCleanup: false
442
- });
443
- return;
444
- }
445
- if (watchStack.length === 0) return;
446
- const top = watchStack[watchStack.length - 1];
447
- const method = calledMethodName(node);
448
- if (method !== void 0 && REGISTER_CALLS.has(method)) top.hasRegistration = true;
449
- if (method !== void 0 && CLEANUP_CALLS.has(method) && top.isEffect) top.hasCleanup = true;
450
- },
451
- NewExpression(node) {
452
- if (watchStack.length === 0) return;
453
- const callee = node.callee;
454
- if (callee?.type === "Identifier" && OBSERVERS.has(callee.name)) watchStack[watchStack.length - 1].hasRegistration = true;
455
- },
456
- ReturnStatement(node) {
457
- if (watchStack.length === 0) return;
458
- if (isFunction(node.argument)) watchStack[watchStack.length - 1].hasCleanup = true;
459
- },
460
- "CallExpression:exit"(node) {
461
- if (watchStack.length === 0) return;
462
- const top = watchStack[watchStack.length - 1];
463
- if (top.sourceCall !== node) return;
464
- watchStack.pop();
465
- if (top.hasRegistration && !top.hasCleanup) context.report({
466
- node,
467
- message: MESSAGE$1
468
- });
469
- }
470
- };
471
- } });
472
- //#endregion
473
- //#region src/rules/performance/prefer-defineAsyncComponent-on-route.ts
474
- const MESSAGE = `This route record's \`component:\` field references the imported component directly, which forces a synchronous import of the route bundle. Use \`() => import('./Foo.vue')\` or \`defineAsyncComponent(() => import('./Foo.vue'))\` to lazy-load the route. See https://vuejs.org/guide/components/async.html`;
475
- function keyName(key) {
476
- return key.type === "Identifier" ? key.name : key.value;
477
- }
478
- //#endregion
1
+ import { VUE_AUTO_IMPORTED, VUE_RULES } from "@geoql/doctor-rule-core";
479
2
  //#region src/plugin.ts
480
3
  const plugin = {
481
4
  meta: { name: "vue-doctor" },
482
- rules: {
483
- "no-em-dash-in-string": noEmDashInString,
484
- "no-destructure-props-without-to-refs": noDestructurePropsWithoutToRefs,
485
- "no-destructure-reactive-without-to-refs": noDestructureReactiveWithoutToRefs,
486
- "no-non-null-assertion-on-ref-value": noNonNullAssertionOnRefValue,
487
- "no-imports-from-vue-when-auto-imported": noImportsFromVueWhenAutoImported,
488
- "reactivity/watch-without-cleanup": watchWithoutCleanup,
489
- "reactivity/prefer-shallowRef-for-large-data": preferShallowRefForLargeData,
490
- "reactivity/prefer-readonly-for-injected": preferReadonlyForInjected,
491
- "composition/prefer-script-setup-for-new-files": preferScriptSetupForNewFiles,
492
- "composition/defineProps-typed": definePropsTyped,
493
- "performance/prefer-defineAsyncComponent-on-route": defineRule({ create(context) {
494
- return { Property(node) {
495
- if (node.computed === true || node.shorthand === true) return;
496
- if (keyName(node.key) !== "component") return;
497
- if (node.value.type === "Identifier") context.report({
498
- node,
499
- message: MESSAGE
500
- });
501
- } };
502
- } })
503
- }
5
+ rules: Object.fromEntries(VUE_RULES.map((rule) => [rule.id, rule]))
504
6
  };
505
7
  //#endregion
506
8
  //#region src/index.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["MESSAGE","calleeName","MESSAGE","MESSAGE","calleeName","isToRefsCall","MESSAGE","calleeName","MESSAGE","calleeName","MESSAGE","calleeName","MESSAGE","MESSAGE"],"sources":["../src/define-rule.ts","../src/rules/composition/defineProps-typed.ts","../src/rules/composition/prefer-script-setup-for-new-files.ts","../src/rules/ai-slop/no-destructure-props-without-toRefs.ts","../src/rules/ai-slop/no-destructure-reactive-without-toRefs.ts","../src/rules/ai-slop/no-em-dash-in-string.ts","../src/shared/vue-auto-imported-symbols.ts","../src/rules/ai-slop/no-imports-from-vue-when-auto-imported.ts","../src/rules/ai-slop/no-non-null-assertion-on-ref-value.ts","../src/rules/reactivity/prefer-readonly-for-injected.ts","../src/rules/reactivity/prefer-shallowRef-for-large-data.ts","../src/rules/reactivity/watch-without-cleanup.ts","../src/rules/performance/prefer-defineAsyncComponent-on-route.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["import type { Rule, RuleContext } from './rule-types.js';\n\nexport function defineRule(rule: Rule): Rule {\n const userFix = rule.fix;\n if (!userFix) return rule;\n return {\n ...rule,\n meta: { ...rule.meta, fixable: 'code' },\n create(context: RuleContext) {\n const wrapped: RuleContext = {\n ...context,\n report(descriptor) {\n const replacement = userFix(descriptor.node);\n if (replacement === null) {\n context.report(descriptor);\n return;\n }\n context.report({\n ...descriptor,\n fix: (fixer) => fixer.replaceText(descriptor.node, replacement),\n });\n },\n };\n return rule.create(wrapped);\n },\n };\n}\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL =\n 'https://vuejs.org/guide/typescript/composition-api.html#typing-component-props';\nconst MESSAGE = `This defineProps() call declares props with a runtime object, which discards static type information. Use the type-only generic form (defineProps<{ ... }>()) so props are fully typed. See ${DOCS_URL}`;\n\nfunction calleeName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nexport const definePropsTyped = defineRule({\n create(context: RuleContext) {\n return {\n CallExpression(node: AstNode) {\n if (calleeName(node) !== 'defineProps') return;\n const firstArg = (node.arguments as AstNode[])[0];\n if (firstArg?.type === 'ObjectExpression') {\n context.report({ node, message: MESSAGE });\n }\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL = 'https://vuejs.org/api/sfc-script-setup.html';\nconst MESSAGE = `This component nests composition-API logic inside an options-object setup(). Use a <script setup> block instead — it removes the setup() boilerplate and exposes bindings to the template directly. See ${DOCS_URL}`;\n\nfunction isFunctionValue(node: AstNode | undefined): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nfunction isSetupProperty(prop: AstNode): boolean {\n if (prop.type !== 'Property' || prop.computed === true) return false;\n if (prop.shorthand === true) return false;\n const key = prop.key as AstNode;\n if (key.type !== 'Identifier' || key.name !== 'setup') return false;\n return isFunctionValue(prop.value as AstNode | undefined);\n}\n\nexport const preferScriptSetupForNewFiles = defineRule({\n create(context: RuleContext) {\n return {\n ExportDefaultDeclaration(node: AstNode) {\n const declaration = node.declaration as AstNode | undefined;\n if (declaration?.type !== 'ObjectExpression') return;\n const properties = declaration.properties as AstNode[];\n if (properties.some(isSetupProperty)) {\n context.report({ node, message: MESSAGE });\n }\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL =\n 'https://vuejs.org/guide/components/props.html#reactive-props-destructure';\nconst MESSAGE = `Destructuring props loses reactivity. Wrap with toRefs(...) to keep refs. See ${DOCS_URL}`;\n\nfunction calleeName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nfunction isDefinePropsCall(node: AstNode | undefined): boolean {\n if (!node || node.type !== 'CallExpression') return false;\n const name = calleeName(node);\n if (name === 'defineProps') return true;\n if (name === 'withDefaults') {\n const args = node.arguments as AstNode[] | undefined;\n return isDefinePropsCall(args?.[0]);\n }\n return false;\n}\n\nfunction isToRefsCall(node: AstNode | undefined): boolean {\n return node?.type === 'CallExpression' && calleeName(node) === 'toRefs';\n}\n\nexport const noDestructurePropsWithoutToRefs = defineRule({\n create(context: RuleContext) {\n const propsSources = new Set<string>();\n\n function registerSetupParam(fn: AstNode | undefined): void {\n const params = fn?.params as AstNode[] | undefined;\n const first = params?.[0];\n if (first?.type === 'Identifier') propsSources.add(first.name as string);\n }\n\n return {\n VariableDeclarator(node: AstNode) {\n const id = node.id as AstNode;\n const init = node.init as AstNode | undefined;\n if (id.type === 'Identifier' && isDefinePropsCall(init)) {\n propsSources.add(id.name as string);\n return;\n }\n if (id.type !== 'ObjectPattern' || !init) return;\n if (isToRefsCall(init)) return;\n const fromProps =\n isDefinePropsCall(init) ||\n (init.type === 'Identifier' && propsSources.has(init.name as string));\n if (fromProps) context.report({ node, message: MESSAGE });\n },\n Property(node: AstNode) {\n const key = node.key as AstNode | undefined;\n if (key?.type === 'Identifier' && key.name === 'setup') {\n registerSetupParam(node.value as AstNode);\n }\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL =\n 'https://vuejs.org/guide/essentials/reactivity-fundamentals.html#destructuring-reactive-state';\nconst MESSAGE = `Destructuring reactive state loses reactivity. Wrap with toRefs(...) or read single keys via toRef(state, 'key'). See ${DOCS_URL}`;\n\nconst REACTIVE_FACTORIES = new Set(['reactive', 'shallowReactive']);\n\nfunction calleeName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nfunction isReactiveCall(node: AstNode | undefined): boolean {\n if (!node || node.type !== 'CallExpression') return false;\n const name = calleeName(node);\n if (name && REACTIVE_FACTORIES.has(name)) return true;\n if (name === 'readonly') {\n const args = node.arguments as AstNode[] | undefined;\n return isReactiveCall(args?.[0]);\n }\n return false;\n}\n\nfunction isToRefsCall(node: AstNode | undefined): boolean {\n return node?.type === 'CallExpression' && calleeName(node) === 'toRefs';\n}\n\nexport const noDestructureReactiveWithoutToRefs = defineRule({\n create(context: RuleContext) {\n const reactiveSources = new Set<string>();\n return {\n VariableDeclarator(node: AstNode) {\n const id = node.id as AstNode;\n const init = node.init as AstNode | undefined;\n if (id.type === 'Identifier' && isReactiveCall(init)) {\n reactiveSources.add(id.name as string);\n return;\n }\n if (id.type !== 'ObjectPattern' || !init) return;\n if (isToRefsCall(init)) return;\n const fromReactive =\n isReactiveCall(init) ||\n (init.type === 'Identifier' &&\n reactiveSources.has(init.name as string));\n if (fromReactive) context.report({ node, message: MESSAGE });\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst EM_DASH = '\\u2014';\n\ninterface LiteralNode extends AstNode {\n type: 'Literal';\n value: unknown;\n}\n\nexport const noEmDashInString = defineRule({\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n const literal = node as LiteralNode;\n if (typeof literal.value !== 'string') return;\n if (!literal.value.includes(EM_DASH)) return;\n context.report({\n node,\n message:\n 'Em dash in string literal reads as AI-generated output; use comma, colon, or parentheses.',\n });\n },\n };\n },\n fix(node: AstNode) {\n const literal = node as LiteralNode;\n if (typeof literal.value !== 'string') return null;\n const raw = literal.raw;\n if (typeof raw !== 'string') return null;\n if (!raw.includes(EM_DASH)) return null;\n return raw.split(EM_DASH).join('-');\n },\n});\n","export const VUE_AUTO_IMPORTED: ReadonlySet<string> = new Set([\n 'ref',\n 'shallowRef',\n 'computed',\n 'reactive',\n 'shallowReactive',\n 'readonly',\n 'shallowReadonly',\n 'toRef',\n 'toRefs',\n 'isRef',\n 'isReactive',\n 'isReadonly',\n 'isProxy',\n 'unref',\n 'triggerRef',\n 'customRef',\n 'markRaw',\n 'toRaw',\n 'watch',\n 'watchEffect',\n 'watchPostEffect',\n 'watchSyncEffect',\n 'effectScope',\n 'getCurrentScope',\n 'onScopeDispose',\n 'onMounted',\n 'onUpdated',\n 'onUnmounted',\n 'onBeforeMount',\n 'onBeforeUpdate',\n 'onBeforeUnmount',\n 'onErrorCaptured',\n 'onRenderTracked',\n 'onRenderTriggered',\n 'onActivated',\n 'onDeactivated',\n 'onServerPrefetch',\n 'provide',\n 'inject',\n 'hasInjectionContext',\n 'getCurrentInstance',\n 'nextTick',\n 'defineComponent',\n 'defineAsyncComponent',\n 'useTemplateRef',\n 'useId',\n 'useModel',\n]);\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\nimport { VUE_AUTO_IMPORTED } from '../../shared/vue-auto-imported-symbols.js';\n\nconst DOCS_URL = 'https://nuxt.com/docs/4.x/guide/concepts/auto-imports';\nconst WHOLE_MESSAGE = `The entire import from 'vue' can be removed — these names are auto-imported in this project. See ${DOCS_URL}`;\nconst SPECIFIER_MESSAGE = `This symbol is auto-imported in your project. Remove it from the 'vue' import — it is dead weight in the diff. See ${DOCS_URL}`;\n\nfunction isAutoImportedValueSpecifier(spec: AstNode): boolean {\n if (spec.importKind === 'type') return false;\n const imported = spec.imported as AstNode;\n return VUE_AUTO_IMPORTED.has(imported.name as string);\n}\n\nexport const noImportsFromVueWhenAutoImported = defineRule({\n create(context: RuleContext) {\n let gated = false;\n return {\n Program() {\n gated = context.capabilities?.has('auto-imports:vue') !== true;\n },\n ImportDeclaration(node: AstNode) {\n if (gated) return;\n if (node.importKind === 'type') return;\n const source = node.source as AstNode;\n if (source.value !== 'vue') return;\n const specifiers = node.specifiers as AstNode[];\n const named = specifiers.filter((s) => s.type === 'ImportSpecifier');\n if (named.length === 0) return;\n const offending = named.filter(isAutoImportedValueSpecifier);\n if (offending.length === 0) return;\n if (\n offending.length === named.length &&\n specifiers.length === named.length\n ) {\n context.report({ node, message: WHOLE_MESSAGE });\n return;\n }\n for (const spec of offending) {\n context.report({ node: spec, message: SPECIFIER_MESSAGE });\n }\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL =\n 'https://vuejs.org/guide/typescript/composition-api.html#typing-ref';\nconst MESSAGE = `Non-null assertion on a ref's .value hides nullability. Add an explicit guard ('if (!r.value) return') or use optional chaining ('r.value?.x'). See ${DOCS_URL}`;\n\nconst REF_FACTORIES = new Set([\n 'ref',\n 'shallowRef',\n 'customRef',\n 'computed',\n 'useTemplateRef',\n]);\n\nfunction calleeName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nfunction isRefFactoryCall(node: AstNode | undefined): boolean {\n if (!node || node.type !== 'CallExpression') return false;\n const name = calleeName(node);\n return name !== undefined && REF_FACTORIES.has(name);\n}\n\nfunction isDotValue(node: AstNode | undefined): node is AstNode {\n if (!node || node.type !== 'MemberExpression') return false;\n const property = node.property as AstNode | undefined;\n return (\n node.computed !== true &&\n property?.type === 'Identifier' &&\n property.name === 'value'\n );\n}\n\nexport const noNonNullAssertionOnRefValue = defineRule({\n create(context: RuleContext) {\n const refSources = new Set<string>();\n return {\n VariableDeclarator(node: AstNode) {\n const id = node.id as AstNode;\n if (\n id.type === 'Identifier' &&\n isRefFactoryCall(node.init as AstNode)\n ) {\n refSources.add(id.name as string);\n }\n },\n TSNonNullExpression(node: AstNode) {\n const arg = node.expression as AstNode | undefined;\n if (!isDotValue(arg)) return;\n const object = arg.object as AstNode | undefined;\n if (object?.type !== 'Identifier') return;\n if (!refSources.has(object.name as string)) return;\n context.report({ node, message: MESSAGE });\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL =\n 'https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity';\nconst MESSAGE = `Mutating an injected value from a consumer breaks one-way data flow and makes the source of changes hard to trace. Wrap the provided value in readonly() at the provide site and expose a dedicated mutator instead. See ${DOCS_URL}`;\n\nconst MUTATING_METHODS = new Set([\n 'push',\n 'pop',\n 'splice',\n 'shift',\n 'unshift',\n 'sort',\n 'reverse',\n]);\n\nfunction calleeName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nfunction memberObjectName(node: AstNode | undefined): string | undefined {\n if (!node || node.type !== 'MemberExpression') return undefined;\n const object = node.object as AstNode;\n if (object.type !== 'Identifier') return undefined;\n return object.name as string;\n}\n\nexport const preferReadonlyForInjected = defineRule({\n create(context: RuleContext) {\n const injectedSources = new Set<string>();\n return {\n VariableDeclarator(node: AstNode) {\n const id = node.id as AstNode;\n const init = node.init as AstNode | undefined;\n if (\n id.type === 'Identifier' &&\n init?.type === 'CallExpression' &&\n calleeName(init) === 'inject'\n ) {\n injectedSources.add(id.name as string);\n }\n },\n AssignmentExpression(node: AstNode) {\n const name = memberObjectName(node.left as AstNode);\n if (name !== undefined && injectedSources.has(name)) {\n context.report({ node, message: MESSAGE });\n }\n },\n CallExpression(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'MemberExpression' || callee.computed === true) {\n return;\n }\n const name = memberObjectName(callee);\n if (name === undefined || !injectedSources.has(name)) return;\n const property = callee.property as AstNode;\n if (MUTATING_METHODS.has(property.name as string)) {\n context.report({ node, message: MESSAGE });\n }\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL = 'https://vuejs.org/api/reactivity-advanced.html#shallowref';\nconst MESSAGE = `This ref holds large or fetched data, so deep reactivity tracks every nested property and wastes memory and CPU. Use shallowRef() (and triggerRef on replacement) for large arrays, big objects, or server payloads. See ${DOCS_URL}`;\n\nconst ARRAY_LIMIT = 100;\nconst OBJECT_LIMIT = 50;\nconst FETCH_IDENTIFIERS = new Set(['$fetch', 'useFetch']);\n\nfunction calleeName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nfunction isAxiosGet(node: AstNode): boolean {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'MemberExpression' || callee.computed === true) {\n return false;\n }\n const object = callee.object as AstNode;\n const property = callee.property as AstNode;\n return (\n object.type === 'Identifier' &&\n object.name === 'axios' &&\n property.name === 'get'\n );\n}\n\nfunction isFetchSource(node: AstNode): boolean {\n if (node.type !== 'CallExpression') return false;\n const name = calleeName(node);\n if (name !== undefined && FETCH_IDENTIFIERS.has(name)) return true;\n return isAxiosGet(node);\n}\n\nfunction isLargeData(node: AstNode | undefined): boolean {\n if (!node) return false;\n const value =\n node.type === 'AwaitExpression' ? (node.argument as AstNode) : node;\n if (value.type === 'ArrayExpression') {\n return (value.elements as AstNode[]).length > ARRAY_LIMIT;\n }\n if (value.type === 'ObjectExpression') {\n return (value.properties as AstNode[]).length > OBJECT_LIMIT;\n }\n return isFetchSource(value);\n}\n\nexport const preferShallowRefForLargeData = defineRule({\n create(context: RuleContext) {\n return {\n CallExpression(node: AstNode) {\n if (calleeName(node) !== 'ref') return;\n const init = (node.arguments as AstNode[])[0];\n if (isLargeData(init)) context.report({ node, message: MESSAGE });\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL =\n 'https://vuejs.org/guide/essentials/watchers.html#side-effect-cleanup';\nconst MESSAGE = `This watcher registers a listener, timer, or observer but never cleans it up, so it leaks across re-runs and hot reloads. Return a cleanup function (or call onWatcherCleanup) inside the watcher. See ${DOCS_URL}`;\n\nconst REGISTER_CALLS = new Set([\n 'addEventListener',\n 'setInterval',\n 'setTimeout',\n]);\nconst OBSERVERS = new Set([\n 'IntersectionObserver',\n 'MutationObserver',\n 'ResizeObserver',\n]);\nconst CLEANUP_CALLS = new Set(['onCleanup', 'onWatcherCleanup']);\n\ninterface WatchFrame {\n sourceCall: AstNode;\n isEffect: boolean;\n hasRegistration: boolean;\n hasCleanup: boolean;\n}\n\nfunction calleeIdentifierName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n return undefined;\n}\n\nfunction calledMethodName(node: AstNode): string | undefined {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name as string;\n if (callee?.type === 'MemberExpression' && callee.computed !== true) {\n const property = callee.property as AstNode;\n return property.name as string;\n }\n return undefined;\n}\n\nfunction isFunction(node: AstNode | undefined): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nexport const watchWithoutCleanup = defineRule({\n create(context: RuleContext) {\n const watchStack: WatchFrame[] = [];\n\n return {\n CallExpression(node: AstNode) {\n const name = calleeIdentifierName(node);\n if (name === 'watch' || name === 'watchEffect') {\n const isEffect = name === 'watchEffect';\n const args = node.arguments as AstNode[];\n const callback = args[isEffect ? 0 : 1];\n if (!isFunction(callback)) return;\n watchStack.push({\n sourceCall: node,\n isEffect,\n hasRegistration: false,\n hasCleanup: false,\n });\n return;\n }\n if (watchStack.length === 0) return;\n const top = watchStack[watchStack.length - 1]!;\n const method = calledMethodName(node);\n if (method !== undefined && REGISTER_CALLS.has(method)) {\n top.hasRegistration = true;\n }\n if (method !== undefined && CLEANUP_CALLS.has(method) && top.isEffect) {\n top.hasCleanup = true;\n }\n },\n NewExpression(node: AstNode) {\n if (watchStack.length === 0) return;\n const callee = node.callee as AstNode | undefined;\n if (\n callee?.type === 'Identifier' &&\n OBSERVERS.has(callee.name as string)\n ) {\n watchStack[watchStack.length - 1]!.hasRegistration = true;\n }\n },\n ReturnStatement(node: AstNode) {\n if (watchStack.length === 0) return;\n if (isFunction(node.argument as AstNode | undefined)) {\n watchStack[watchStack.length - 1]!.hasCleanup = true;\n }\n },\n 'CallExpression:exit'(node: AstNode) {\n if (watchStack.length === 0) return;\n const top = watchStack[watchStack.length - 1]!;\n if (top.sourceCall !== node) return;\n watchStack.pop();\n if (top.hasRegistration && !top.hasCleanup) {\n context.report({ node, message: MESSAGE });\n }\n },\n };\n },\n});\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\n\nconst DOCS_URL = 'https://vuejs.org/guide/components/async.html';\nconst MESSAGE = `This route record's \\`component:\\` field references the imported component directly, which forces a synchronous import of the route bundle. Use \\`() => import('./Foo.vue')\\` or \\`defineAsyncComponent(() => import('./Foo.vue'))\\` to lazy-load the route. See ${DOCS_URL}`;\n\nfunction keyName(key: AstNode): unknown {\n return key.type === 'Identifier' ? key.name : key.value;\n}\n\nexport const preferDefineAsyncComponentOnRoute = defineRule({\n create(context: RuleContext) {\n return {\n Property(node: AstNode) {\n if (node.computed === true || node.shorthand === true) return;\n if (keyName(node.key as AstNode) !== 'component') return;\n const value = node.value as AstNode;\n if (value.type === 'Identifier') {\n context.report({ node, message: MESSAGE });\n }\n },\n };\n },\n});\n","import type { Plugin } from './rule-types.js';\nimport { definePropsTyped } from './rules/composition/defineProps-typed.js';\nimport { preferScriptSetupForNewFiles } from './rules/composition/prefer-script-setup-for-new-files.js';\nimport { noDestructurePropsWithoutToRefs } from './rules/ai-slop/no-destructure-props-without-toRefs.js';\nimport { noDestructureReactiveWithoutToRefs } from './rules/ai-slop/no-destructure-reactive-without-toRefs.js';\nimport { noEmDashInString } from './rules/ai-slop/no-em-dash-in-string.js';\nimport { noImportsFromVueWhenAutoImported } from './rules/ai-slop/no-imports-from-vue-when-auto-imported.js';\nimport { noNonNullAssertionOnRefValue } from './rules/ai-slop/no-non-null-assertion-on-ref-value.js';\nimport { preferReadonlyForInjected } from './rules/reactivity/prefer-readonly-for-injected.js';\nimport { preferShallowRefForLargeData } from './rules/reactivity/prefer-shallowRef-for-large-data.js';\nimport { watchWithoutCleanup } from './rules/reactivity/watch-without-cleanup.js';\nimport { preferDefineAsyncComponentOnRoute } from './rules/performance/prefer-defineAsyncComponent-on-route.js';\n\nexport const plugin: Plugin = {\n meta: { name: 'vue-doctor' },\n rules: {\n 'no-em-dash-in-string': noEmDashInString,\n 'no-destructure-props-without-to-refs': noDestructurePropsWithoutToRefs,\n 'no-destructure-reactive-without-to-refs':\n noDestructureReactiveWithoutToRefs,\n 'no-non-null-assertion-on-ref-value': noNonNullAssertionOnRefValue,\n 'no-imports-from-vue-when-auto-imported': noImportsFromVueWhenAutoImported,\n 'reactivity/watch-without-cleanup': watchWithoutCleanup,\n 'reactivity/prefer-shallowRef-for-large-data': preferShallowRefForLargeData,\n 'reactivity/prefer-readonly-for-injected': preferReadonlyForInjected,\n 'composition/prefer-script-setup-for-new-files':\n preferScriptSetupForNewFiles,\n 'composition/defineProps-typed': definePropsTyped,\n 'performance/prefer-defineAsyncComponent-on-route':\n preferDefineAsyncComponentOnRoute,\n },\n};\n","import { plugin } from './plugin.js';\n\nexport default plugin;\nexport { plugin };\nexport { VUE_AUTO_IMPORTED } from './shared/vue-auto-imported-symbols.js';\nexport type { Rule, RuleContext } from './rule-types.js';\n"],"mappings":";AAEA,SAAgB,WAAW,MAAkB;CAC3C,MAAM,UAAU,KAAK;CACrB,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EACL,GAAG;EACH,MAAM;GAAE,GAAG,KAAK;GAAM,SAAS;GAAQ;EACvC,OAAO,SAAsB;GAC3B,MAAM,UAAuB;IAC3B,GAAG;IACH,OAAO,YAAY;KACjB,MAAM,cAAc,QAAQ,WAAW,KAAK;KAC5C,IAAI,gBAAgB,MAAM;MACxB,QAAQ,OAAO,WAAW;MAC1B;;KAEF,QAAQ,OAAO;MACb,GAAG;MACH,MAAM,UAAU,MAAM,YAAY,WAAW,MAAM,YAAY;MAChE,CAAC;;IAEL;GACD,OAAO,KAAK,OAAO,QAAQ;;EAE9B;;;;ACpBH,MAAMA,YAAU;AAEhB,SAASC,aAAW,MAAmC;CACrD,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,MAAa,mBAAmB,WAAW,EACzC,OAAO,SAAsB;CAC3B,OAAO,EACL,eAAe,MAAe;EAC5B,IAAIA,aAAW,KAAK,KAAK,eAAe;EAExC,IADkB,KAAK,UAAwB,IACjC,SAAS,oBACrB,QAAQ,OAAO;GAAE;GAAM,SAASD;GAAS,CAAC;IAG/C;GAEJ,CAAC;;;ACrBF,MAAME,YAAU;AAEhB,SAAS,gBAAgB,MAAoC;CAC3D,OACE,MAAM,SAAS,6BACf,MAAM,SAAS;;AAInB,SAAS,gBAAgB,MAAwB;CAC/C,IAAI,KAAK,SAAS,cAAc,KAAK,aAAa,MAAM,OAAO;CAC/D,IAAI,KAAK,cAAc,MAAM,OAAO;CACpC,MAAM,MAAM,KAAK;CACjB,IAAI,IAAI,SAAS,gBAAgB,IAAI,SAAS,SAAS,OAAO;CAC9D,OAAO,gBAAgB,KAAK,MAA6B;;AAG3D,MAAa,+BAA+B,WAAW,EACrD,OAAO,SAAsB;CAC3B,OAAO,EACL,yBAAyB,MAAe;EACtC,MAAM,cAAc,KAAK;EACzB,IAAI,aAAa,SAAS,oBAAoB;EAE9C,IADmB,YAAY,WAChB,KAAK,gBAAgB,EAClC,QAAQ,OAAO;GAAE;GAAM,SAASA;GAAS,CAAC;IAG/C;GAEJ,CAAC;;;AC7BF,MAAMC,YAAU;AAEhB,SAASC,aAAW,MAAmC;CACrD,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,SAAS,kBAAkB,MAAoC;CAC7D,IAAI,CAAC,QAAQ,KAAK,SAAS,kBAAkB,OAAO;CACpD,MAAM,OAAOA,aAAW,KAAK;CAC7B,IAAI,SAAS,eAAe,OAAO;CACnC,IAAI,SAAS,gBAAgB;EAC3B,MAAM,OAAO,KAAK;EAClB,OAAO,kBAAkB,OAAO,GAAG;;CAErC,OAAO;;AAGT,SAASC,eAAa,MAAoC;CACxD,OAAO,MAAM,SAAS,oBAAoBD,aAAW,KAAK,KAAK;;AAGjE,MAAa,kCAAkC,WAAW,EACxD,OAAO,SAAsB;CAC3B,MAAM,+BAAe,IAAI,KAAa;CAEtC,SAAS,mBAAmB,IAA+B;EAEzD,MAAM,SADS,IAAI,UACI;EACvB,IAAI,OAAO,SAAS,cAAc,aAAa,IAAI,MAAM,KAAe;;CAG1E,OAAO;EACL,mBAAmB,MAAe;GAChC,MAAM,KAAK,KAAK;GAChB,MAAM,OAAO,KAAK;GAClB,IAAI,GAAG,SAAS,gBAAgB,kBAAkB,KAAK,EAAE;IACvD,aAAa,IAAI,GAAG,KAAe;IACnC;;GAEF,IAAI,GAAG,SAAS,mBAAmB,CAAC,MAAM;GAC1C,IAAIC,eAAa,KAAK,EAAE;GAIxB,IAFE,kBAAkB,KAAK,IACtB,KAAK,SAAS,gBAAgB,aAAa,IAAI,KAAK,KAAe,EACvD,QAAQ,OAAO;IAAE;IAAM,SAASF;IAAS,CAAC;;EAE3D,SAAS,MAAe;GACtB,MAAM,MAAM,KAAK;GACjB,IAAI,KAAK,SAAS,gBAAgB,IAAI,SAAS,SAC7C,mBAAmB,KAAK,MAAiB;;EAG9C;GAEJ,CAAC;;;ACxDF,MAAMG,YAAU;AAEhB,MAAM,qBAAqB,IAAI,IAAI,CAAC,YAAY,kBAAkB,CAAC;AAEnE,SAASC,aAAW,MAAmC;CACrD,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,SAAS,eAAe,MAAoC;CAC1D,IAAI,CAAC,QAAQ,KAAK,SAAS,kBAAkB,OAAO;CACpD,MAAM,OAAOA,aAAW,KAAK;CAC7B,IAAI,QAAQ,mBAAmB,IAAI,KAAK,EAAE,OAAO;CACjD,IAAI,SAAS,YAAY;EACvB,MAAM,OAAO,KAAK;EAClB,OAAO,eAAe,OAAO,GAAG;;CAElC,OAAO;;AAGT,SAAS,aAAa,MAAoC;CACxD,OAAO,MAAM,SAAS,oBAAoBA,aAAW,KAAK,KAAK;;AAGjE,MAAa,qCAAqC,WAAW,EAC3D,OAAO,SAAsB;CAC3B,MAAM,kCAAkB,IAAI,KAAa;CACzC,OAAO,EACL,mBAAmB,MAAe;EAChC,MAAM,KAAK,KAAK;EAChB,MAAM,OAAO,KAAK;EAClB,IAAI,GAAG,SAAS,gBAAgB,eAAe,KAAK,EAAE;GACpD,gBAAgB,IAAI,GAAG,KAAe;GACtC;;EAEF,IAAI,GAAG,SAAS,mBAAmB,CAAC,MAAM;EAC1C,IAAI,aAAa,KAAK,EAAE;EAKxB,IAHE,eAAe,KAAK,IACnB,KAAK,SAAS,gBACb,gBAAgB,IAAI,KAAK,KAAe,EAC1B,QAAQ,OAAO;GAAE;GAAM,SAASD;GAAS,CAAC;IAE/D;GAEJ,CAAC;;;AChDF,MAAM,UAAU;AAOhB,MAAa,mBAAmB,WAAW;CACzC,OAAO,SAAsB;EAC3B,OAAO,EACL,QAAQ,MAAe;GACrB,MAAM,UAAU;GAChB,IAAI,OAAO,QAAQ,UAAU,UAAU;GACvC,IAAI,CAAC,QAAQ,MAAM,SAAS,QAAQ,EAAE;GACtC,QAAQ,OAAO;IACb;IACA,SACE;IACH,CAAC;KAEL;;CAEH,IAAI,MAAe;EACjB,MAAM,UAAU;EAChB,IAAI,OAAO,QAAQ,UAAU,UAAU,OAAO;EAC9C,MAAM,MAAM,QAAQ;EACpB,IAAI,OAAO,QAAQ,UAAU,OAAO;EACpC,IAAI,CAAC,IAAI,SAAS,QAAQ,EAAE,OAAO;EACnC,OAAO,IAAI,MAAM,QAAQ,CAAC,KAAK,IAAI;;CAEtC,CAAC;;;ACjCF,MAAa,oBAAyC,IAAI,IAAI;CAC5D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;AC5CF,MAAM,WAAW;AACjB,MAAM,gBAAgB,oGAAoG;AAC1H,MAAM,oBAAoB,sHAAsH;AAEhJ,SAAS,6BAA6B,MAAwB;CAC5D,IAAI,KAAK,eAAe,QAAQ,OAAO;CACvC,MAAM,WAAW,KAAK;CACtB,OAAO,kBAAkB,IAAI,SAAS,KAAe;;AAGvD,MAAa,mCAAmC,WAAW,EACzD,OAAO,SAAsB;CAC3B,IAAI,QAAQ;CACZ,OAAO;EACL,UAAU;GACR,QAAQ,QAAQ,cAAc,IAAI,mBAAmB,KAAK;;EAE5D,kBAAkB,MAAe;GAC/B,IAAI,OAAO;GACX,IAAI,KAAK,eAAe,QAAQ;GAEhC,IADe,KAAK,OACT,UAAU,OAAO;GAC5B,MAAM,aAAa,KAAK;GACxB,MAAM,QAAQ,WAAW,QAAQ,MAAM,EAAE,SAAS,kBAAkB;GACpE,IAAI,MAAM,WAAW,GAAG;GACxB,MAAM,YAAY,MAAM,OAAO,6BAA6B;GAC5D,IAAI,UAAU,WAAW,GAAG;GAC5B,IACE,UAAU,WAAW,MAAM,UAC3B,WAAW,WAAW,MAAM,QAC5B;IACA,QAAQ,OAAO;KAAE;KAAM,SAAS;KAAe,CAAC;IAChD;;GAEF,KAAK,MAAM,QAAQ,WACjB,QAAQ,OAAO;IAAE,MAAM;IAAM,SAAS;IAAmB,CAAC;;EAG/D;GAEJ,CAAC;;;ACvCF,MAAME,YAAU;AAEhB,MAAM,gBAAgB,IAAI,IAAI;CAC5B;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAASC,aAAW,MAAmC;CACrD,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,SAAS,iBAAiB,MAAoC;CAC5D,IAAI,CAAC,QAAQ,KAAK,SAAS,kBAAkB,OAAO;CACpD,MAAM,OAAOA,aAAW,KAAK;CAC7B,OAAO,SAAS,KAAA,KAAa,cAAc,IAAI,KAAK;;AAGtD,SAAS,WAAW,MAA4C;CAC9D,IAAI,CAAC,QAAQ,KAAK,SAAS,oBAAoB,OAAO;CACtD,MAAM,WAAW,KAAK;CACtB,OACE,KAAK,aAAa,QAClB,UAAU,SAAS,gBACnB,SAAS,SAAS;;AAItB,MAAa,+BAA+B,WAAW,EACrD,OAAO,SAAsB;CAC3B,MAAM,6BAAa,IAAI,KAAa;CACpC,OAAO;EACL,mBAAmB,MAAe;GAChC,MAAM,KAAK,KAAK;GAChB,IACE,GAAG,SAAS,gBACZ,iBAAiB,KAAK,KAAgB,EAEtC,WAAW,IAAI,GAAG,KAAe;;EAGrC,oBAAoB,MAAe;GACjC,MAAM,MAAM,KAAK;GACjB,IAAI,CAAC,WAAW,IAAI,EAAE;GACtB,MAAM,SAAS,IAAI;GACnB,IAAI,QAAQ,SAAS,cAAc;GACnC,IAAI,CAAC,WAAW,IAAI,OAAO,KAAe,EAAE;GAC5C,QAAQ,OAAO;IAAE;IAAM,SAASD;IAAS,CAAC;;EAE7C;GAEJ,CAAC;;;ACvDF,MAAME,YAAU;AAEhB,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAASC,aAAW,MAAmC;CACrD,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,SAAS,iBAAiB,MAA+C;CACvE,IAAI,CAAC,QAAQ,KAAK,SAAS,oBAAoB,OAAO,KAAA;CACtD,MAAM,SAAS,KAAK;CACpB,IAAI,OAAO,SAAS,cAAc,OAAO,KAAA;CACzC,OAAO,OAAO;;AAGhB,MAAa,4BAA4B,WAAW,EAClD,OAAO,SAAsB;CAC3B,MAAM,kCAAkB,IAAI,KAAa;CACzC,OAAO;EACL,mBAAmB,MAAe;GAChC,MAAM,KAAK,KAAK;GAChB,MAAM,OAAO,KAAK;GAClB,IACE,GAAG,SAAS,gBACZ,MAAM,SAAS,oBACfA,aAAW,KAAK,KAAK,UAErB,gBAAgB,IAAI,GAAG,KAAe;;EAG1C,qBAAqB,MAAe;GAClC,MAAM,OAAO,iBAAiB,KAAK,KAAgB;GACnD,IAAI,SAAS,KAAA,KAAa,gBAAgB,IAAI,KAAK,EACjD,QAAQ,OAAO;IAAE;IAAM,SAASD;IAAS,CAAC;;EAG9C,eAAe,MAAe;GAC5B,MAAM,SAAS,KAAK;GACpB,IAAI,QAAQ,SAAS,sBAAsB,OAAO,aAAa,MAC7D;GAEF,MAAM,OAAO,iBAAiB,OAAO;GACrC,IAAI,SAAS,KAAA,KAAa,CAAC,gBAAgB,IAAI,KAAK,EAAE;GACtD,MAAM,WAAW,OAAO;GACxB,IAAI,iBAAiB,IAAI,SAAS,KAAe,EAC/C,QAAQ,OAAO;IAAE;IAAM,SAASA;IAAS,CAAC;;EAG/C;GAEJ,CAAC;;;AC7DF,MAAME,YAAU;AAEhB,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,oBAAoB,IAAI,IAAI,CAAC,UAAU,WAAW,CAAC;AAEzD,SAAS,WAAW,MAAmC;CACrD,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,SAAS,WAAW,MAAwB;CAC1C,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,sBAAsB,OAAO,aAAa,MAC7D,OAAO;CAET,MAAM,SAAS,OAAO;CACtB,MAAM,WAAW,OAAO;CACxB,OACE,OAAO,SAAS,gBAChB,OAAO,SAAS,WAChB,SAAS,SAAS;;AAItB,SAAS,cAAc,MAAwB;CAC7C,IAAI,KAAK,SAAS,kBAAkB,OAAO;CAC3C,MAAM,OAAO,WAAW,KAAK;CAC7B,IAAI,SAAS,KAAA,KAAa,kBAAkB,IAAI,KAAK,EAAE,OAAO;CAC9D,OAAO,WAAW,KAAK;;AAGzB,SAAS,YAAY,MAAoC;CACvD,IAAI,CAAC,MAAM,OAAO;CAClB,MAAM,QACJ,KAAK,SAAS,oBAAqB,KAAK,WAAuB;CACjE,IAAI,MAAM,SAAS,mBACjB,OAAQ,MAAM,SAAuB,SAAS;CAEhD,IAAI,MAAM,SAAS,oBACjB,OAAQ,MAAM,WAAyB,SAAS;CAElD,OAAO,cAAc,MAAM;;AAG7B,MAAa,+BAA+B,WAAW,EACrD,OAAO,SAAsB;CAC3B,OAAO,EACL,eAAe,MAAe;EAC5B,IAAI,WAAW,KAAK,KAAK,OAAO;EAChC,MAAM,OAAQ,KAAK,UAAwB;EAC3C,IAAI,YAAY,KAAK,EAAE,QAAQ,OAAO;GAAE;GAAM,SAASA;GAAS,CAAC;IAEpE;GAEJ,CAAC;;;ACvDF,MAAMC,YAAU;AAEhB,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACD,CAAC;AACF,MAAM,YAAY,IAAI,IAAI;CACxB;CACA;CACA;CACD,CAAC;AACF,MAAM,gBAAgB,IAAI,IAAI,CAAC,aAAa,mBAAmB,CAAC;AAShE,SAAS,qBAAqB,MAAmC;CAC/D,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;;AAInD,SAAS,iBAAiB,MAAmC;CAC3D,MAAM,SAAS,KAAK;CACpB,IAAI,QAAQ,SAAS,cAAc,OAAO,OAAO;CACjD,IAAI,QAAQ,SAAS,sBAAsB,OAAO,aAAa,MAE7D,OADiB,OAAO,SACR;;AAKpB,SAAS,WAAW,MAAoC;CACtD,OACE,MAAM,SAAS,6BACf,MAAM,SAAS;;AAInB,MAAa,sBAAsB,WAAW,EAC5C,OAAO,SAAsB;CAC3B,MAAM,aAA2B,EAAE;CAEnC,OAAO;EACL,eAAe,MAAe;GAC5B,MAAM,OAAO,qBAAqB,KAAK;GACvC,IAAI,SAAS,WAAW,SAAS,eAAe;IAC9C,MAAM,WAAW,SAAS;IAE1B,MAAM,WADO,KAAK,UACI,WAAW,IAAI;IACrC,IAAI,CAAC,WAAW,SAAS,EAAE;IAC3B,WAAW,KAAK;KACd,YAAY;KACZ;KACA,iBAAiB;KACjB,YAAY;KACb,CAAC;IACF;;GAEF,IAAI,WAAW,WAAW,GAAG;GAC7B,MAAM,MAAM,WAAW,WAAW,SAAS;GAC3C,MAAM,SAAS,iBAAiB,KAAK;GACrC,IAAI,WAAW,KAAA,KAAa,eAAe,IAAI,OAAO,EACpD,IAAI,kBAAkB;GAExB,IAAI,WAAW,KAAA,KAAa,cAAc,IAAI,OAAO,IAAI,IAAI,UAC3D,IAAI,aAAa;;EAGrB,cAAc,MAAe;GAC3B,IAAI,WAAW,WAAW,GAAG;GAC7B,MAAM,SAAS,KAAK;GACpB,IACE,QAAQ,SAAS,gBACjB,UAAU,IAAI,OAAO,KAAe,EAEpC,WAAW,WAAW,SAAS,GAAI,kBAAkB;;EAGzD,gBAAgB,MAAe;GAC7B,IAAI,WAAW,WAAW,GAAG;GAC7B,IAAI,WAAW,KAAK,SAAgC,EAClD,WAAW,WAAW,SAAS,GAAI,aAAa;;EAGpD,sBAAsB,MAAe;GACnC,IAAI,WAAW,WAAW,GAAG;GAC7B,MAAM,MAAM,WAAW,WAAW,SAAS;GAC3C,IAAI,IAAI,eAAe,MAAM;GAC7B,WAAW,KAAK;GAChB,IAAI,IAAI,mBAAmB,CAAC,IAAI,YAC9B,QAAQ,OAAO;IAAE;IAAM,SAASA;IAAS,CAAC;;EAG/C;GAEJ,CAAC;;;ACtGF,MAAM,UAAU;AAEhB,SAAS,QAAQ,KAAuB;CACtC,OAAO,IAAI,SAAS,eAAe,IAAI,OAAO,IAAI;;;;ACMpD,MAAa,SAAiB;CAC5B,MAAM,EAAE,MAAM,cAAc;CAC5B,OAAO;EACL,wBAAwB;EACxB,wCAAwC;EACxC,2CACE;EACF,sCAAsC;EACtC,0CAA0C;EAC1C,oCAAoC;EACpC,+CAA+C;EAC/C,2CAA2C;EAC3C,iDACE;EACF,iCAAiC;EACjC,oDDlB6C,WAAW,EAC1D,OAAO,SAAsB;GAC3B,OAAO,EACL,SAAS,MAAe;IACtB,IAAI,KAAK,aAAa,QAAQ,KAAK,cAAc,MAAM;IACvD,IAAI,QAAQ,KAAK,IAAe,KAAK,aAAa;IAElD,IADc,KAAK,MACT,SAAS,cACjB,QAAQ,OAAO;KAAE;KAAM,SAAS;KAAS,CAAC;MAG/C;KAEJ,CCMK;EACH;CACF;;;AC7BD,IAAA,cAAe"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/plugin.ts","../src/index.ts"],"sourcesContent":["import type { Plugin } from './rule-types.js';\nimport { VUE_RULES } from '@geoql/doctor-rule-core';\n\nexport const plugin: Plugin = {\n meta: { name: 'vue-doctor' },\n rules: Object.fromEntries(VUE_RULES.map((rule) => [rule.id, rule])),\n};\n","import { plugin } from './plugin.js';\n\nexport default plugin;\nexport { plugin };\nexport { VUE_AUTO_IMPORTED } from '@geoql/doctor-rule-core';\nexport type { Rule, RuleContext } from './rule-types.js';\n"],"mappings":";;AAGA,MAAa,SAAiB;CAC5B,MAAM,EAAE,MAAM,aAAa;CAC3B,OAAO,OAAO,YAAY,UAAU,KAAK,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AACpE;;;ACJA,IAAA,cAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoql/oxlint-plugin-vue-doctor",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "description": "oxlint JS plugin for Vue 3 anti-patterns and AI-slop detection. Pairs with oxlint's built-in vue plugin via jsPlugins. TypeScript, ESM.",
6
6
  "keywords": [
@@ -41,15 +41,21 @@
41
41
  "publishConfig": {
42
42
  "access": "public"
43
43
  },
44
+ "dependencies": {
45
+ "@geoql/doctor-rule-core": "1.2.0"
46
+ },
44
47
  "devDependencies": {
45
- "@types/node": "^25.9.1",
46
- "oxc-parser": "0.133.0",
48
+ "@types/node": "^25.9.4",
49
+ "oxc-parser": "^0.137.0",
47
50
  "typescript": "^6.0.3",
48
- "vitest": "^4.1.7"
51
+ "vitest": "^4.1.9"
49
52
  },
50
53
  "peerDependencies": {
51
54
  "oxlint": "^1.66.0"
52
55
  },
56
+ "engines": {
57
+ "node": ">=24"
58
+ },
53
59
  "scripts": {
54
60
  "lint": "vp lint src",
55
61
  "lint:fix": "vp lint src --fix",