@geoql/oxlint-plugin-nuxt-doctor 1.0.0 → 1.1.1

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 { NUXT_AUTO_IMPORTED, Plugin, Rule, RuleContext } 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/nuxt-auto-imported-symbols.d.ts
59
- declare const NUXT_AUTO_IMPORTED: ReadonlySet<string>;
60
- //#endregion
61
6
  export { NUXT_AUTO_IMPORTED, type Rule, type RuleContext, plugin as default, plugin };
62
7
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,529 +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/ai-slop/no-process-client-server.ts
32
- const LEGACY_PROPS = new Set([
33
- "client",
34
- "server",
35
- "browser"
36
- ]);
37
- const MESSAGE$8 = `Use import.meta.client / import.meta.server / import.meta.browser instead of process.client / process.server / process.browser (Nuxt 4). See https://nuxt.com/docs/4.x/guide/concepts/auto-imports`;
38
- const noProcessClientServer = defineRule({
39
- create(context) {
40
- return { MemberExpression(node) {
41
- const object = node.object;
42
- if (object?.type !== "Identifier") return;
43
- if (object.name !== "process") return;
44
- const property = node.property;
45
- if (property?.type !== "Identifier") return;
46
- if (!LEGACY_PROPS.has(property.name)) return;
47
- context.report({
48
- node,
49
- message: MESSAGE$8
50
- });
51
- } };
52
- },
53
- fix(node) {
54
- const object = node.object;
55
- if (object?.type !== "Identifier") return null;
56
- if (object.name !== "process") return null;
57
- const property = node.property;
58
- if (property?.type !== "Identifier") return null;
59
- const name = property.name;
60
- if (!LEGACY_PROPS.has(name)) return null;
61
- return `import.meta.${name}`;
62
- }
63
- });
64
- //#endregion
65
- //#region src/shared/nuxt-auto-imported-symbols.ts
66
- const NUXT_AUTO_IMPORTED = new Set([
67
- "ref",
68
- "shallowRef",
69
- "computed",
70
- "reactive",
71
- "shallowReactive",
72
- "readonly",
73
- "shallowReadonly",
74
- "toRef",
75
- "toRefs",
76
- "isRef",
77
- "isReactive",
78
- "isReadonly",
79
- "isProxy",
80
- "unref",
81
- "triggerRef",
82
- "customRef",
83
- "markRaw",
84
- "toRaw",
85
- "watch",
86
- "watchEffect",
87
- "watchPostEffect",
88
- "watchSyncEffect",
89
- "effectScope",
90
- "getCurrentScope",
91
- "onScopeDispose",
92
- "onMounted",
93
- "onUpdated",
94
- "onUnmounted",
95
- "onBeforeMount",
96
- "onBeforeUpdate",
97
- "onBeforeUnmount",
98
- "onErrorCaptured",
99
- "onRenderTracked",
100
- "onRenderTriggered",
101
- "onActivated",
102
- "onDeactivated",
103
- "onServerPrefetch",
104
- "provide",
105
- "inject",
106
- "hasInjectionContext",
107
- "getCurrentInstance",
108
- "nextTick",
109
- "defineComponent",
110
- "defineAsyncComponent",
111
- "useTemplateRef",
112
- "useId",
113
- "useModel",
114
- "useRoute",
115
- "useRouter",
116
- "useFetch",
117
- "useAsyncData",
118
- "useState",
119
- "useRuntimeConfig",
120
- "useNuxtApp",
121
- "navigateTo",
122
- "definePageMeta",
123
- "useSeoMeta",
124
- "useHead"
125
- ]);
126
- //#endregion
127
- //#region src/rules/ai-slop/no-explicit-imports-of-auto-imported.ts
128
- const DOCS_URL = "https://nuxt.com/docs/4.x/guide/concepts/auto-imports";
129
- const WHOLE_MESSAGE = `These symbols are auto-imported in Nuxt projects. Remove the import entirely. See ${DOCS_URL}`;
130
- const SPECIFIER_MESSAGE = `This symbol is auto-imported in Nuxt projects. Remove it from the import — it is dead weight. See ${DOCS_URL}`;
131
- const AUTO_IMPORT_SOURCES = new Set([
132
- "vue",
133
- "#imports",
134
- "vue-router",
135
- "#app"
136
- ]);
137
- function isAutoImportedValueSpecifier(spec) {
138
- if (spec.importKind === "type") return false;
139
- const imported = spec.imported;
140
- return NUXT_AUTO_IMPORTED.has(imported.name);
141
- }
142
- const noExplicitImportsOfAutoImported = defineRule({ create(context) {
143
- return { ImportDeclaration(node) {
144
- if (node.importKind === "type") return;
145
- const source = node.source;
146
- if (!AUTO_IMPORT_SOURCES.has(source.value)) return;
147
- const specifiers = node.specifiers;
148
- const named = specifiers.filter((s) => s.type === "ImportSpecifier");
149
- if (named.length === 0) return;
150
- const offending = named.filter(isAutoImportedValueSpecifier);
151
- if (offending.length === 0) return;
152
- if (offending.length === named.length && specifiers.length === named.length) {
153
- context.report({
154
- node,
155
- message: WHOLE_MESSAGE
156
- });
157
- return;
158
- }
159
- for (const spec of offending) context.report({
160
- node: spec,
161
- message: SPECIFIER_MESSAGE
162
- });
163
- } };
164
- } });
165
- //#endregion
166
- //#region src/rules/ai-slop/no-useState-for-server-data.ts
167
- const MESSAGE$7 = `useState with a fetch/await initializer suggests useFetch or useAsyncData for automatic SSR hydration and request deduplication. See https://nuxt.com/docs/4.x/guide/data-fetching`;
168
- function containsFetchOrAwait(node, visited) {
169
- if (!node || visited.has(node)) return false;
170
- visited.add(node);
171
- if (node.type === "AwaitExpression") return true;
172
- if (node.type === "CallExpression") {
173
- const callee = node.callee;
174
- if (callee?.type === "Identifier") {
175
- const name = callee.name;
176
- if (name === "$fetch" || name === "fetch") return true;
177
- }
178
- }
179
- for (const key of Object.keys(node)) {
180
- if (key === "type" || key === "loc" || key === "range") continue;
181
- const value = node[key];
182
- if (Array.isArray(value)) {
183
- for (const child of value) if (child && typeof child === "object" && "type" in child) {
184
- if (containsFetchOrAwait(child, visited)) return true;
185
- }
186
- } else if (value && typeof value === "object" && "type" in value) {
187
- if (containsFetchOrAwait(value, visited)) return true;
188
- }
189
- }
190
- return false;
191
- }
192
- const noUseStateForServerData = defineRule({ create(context) {
193
- return { CallExpression(node) {
194
- const callee = node.callee;
195
- if (callee?.type !== "Identifier") return;
196
- if (callee.name !== "useState") return;
197
- const args = node.arguments;
198
- if (args.length < 2) return;
199
- const initFn = args[1];
200
- if (initFn.type !== "ArrowFunctionExpression" && initFn.type !== "FunctionExpression") return;
201
- if (!containsFetchOrAwait(initFn.body, /* @__PURE__ */ new Set())) return;
202
- context.report({
203
- node,
204
- message: MESSAGE$7
205
- });
206
- } };
207
- } });
208
- //#endregion
209
- //#region src/rules/ai-slop/no-fetch-in-setup.ts
210
- const MESSAGE$6 = `Bare $fetch/fetch at the top level of <script setup> runs on both server and client without SSR deduplication. Use useFetch for automatic request deduplication and SSR hydration. See https://nuxt.com/docs/4.x/guide/data-fetching`;
211
- function isFetchCall(node) {
212
- if (!node || node.type !== "CallExpression") return false;
213
- const callee = node.callee;
214
- if (callee?.type === "Identifier") {
215
- const name = callee.name;
216
- return name === "$fetch" || name === "fetch";
217
- }
218
- return false;
219
- }
220
- const noFetchInSetup = defineRule({ create(context) {
221
- const functionDepthStack = [];
222
- return {
223
- ArrowFunctionExpression() {
224
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
225
- },
226
- "ArrowFunctionExpression:exit"() {
227
- functionDepthStack.pop();
228
- },
229
- FunctionExpression() {
230
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
231
- },
232
- "FunctionExpression:exit"() {
233
- functionDepthStack.pop();
234
- },
235
- FunctionDeclaration() {
236
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
237
- },
238
- "FunctionDeclaration:exit"() {
239
- functionDepthStack.pop();
240
- },
241
- CallExpression(node) {
242
- if (functionDepthStack.length > 0) return;
243
- if (!isFetchCall(node)) return;
244
- if (node["parent"]?.type === "AwaitExpression") return;
245
- context.report({
246
- node,
247
- message: MESSAGE$6
248
- });
249
- },
250
- AwaitExpression(node) {
251
- if (functionDepthStack.length > 0) return;
252
- if (!isFetchCall(node.argument)) return;
253
- context.report({
254
- node,
255
- message: MESSAGE$6
256
- });
257
- }
258
- };
259
- } });
260
- //#endregion
261
- //#region src/rules/data-fetching/useAsyncData-key-required-in-loop.ts
262
- const MESSAGE$5 = `useAsyncData/useFetch without an explicit string key inside a loop causes duplicate requests and cache fragmentation. Pass a unique string key as the first argument. See https://nuxt.com/docs/4.x/guide/data-fetching`;
263
- const DATA_FETCHERS = new Set(["useAsyncData", "useFetch"]);
264
- function isMapCall(node) {
265
- const callee = node.callee;
266
- if (callee?.type === "Identifier") return callee.name === "map";
267
- if (callee?.type === "MemberExpression") {
268
- const prop = callee.property;
269
- return prop?.type === "Identifier" && prop.name === "map";
270
- }
271
- return false;
272
- }
273
- const useAsyncDataKeyRequiredInLoop = defineRule({ create(context) {
274
- let loopDepth = 0;
275
- return {
276
- ForStatement() {
277
- loopDepth++;
278
- },
279
- "ForStatement:exit"() {
280
- loopDepth--;
281
- },
282
- ForOfStatement() {
283
- loopDepth++;
284
- },
285
- "ForOfStatement:exit"() {
286
- loopDepth--;
287
- },
288
- ForInStatement() {
289
- loopDepth++;
290
- },
291
- "ForInStatement:exit"() {
292
- loopDepth--;
293
- },
294
- WhileStatement() {
295
- loopDepth++;
296
- },
297
- "WhileStatement:exit"() {
298
- loopDepth--;
299
- },
300
- CallExpression(node) {
301
- if (isMapCall(node)) {
302
- loopDepth++;
303
- return;
304
- }
305
- if (loopDepth === 0) return;
306
- const callee = node.callee;
307
- if (callee?.type !== "Identifier") return;
308
- if (!DATA_FETCHERS.has(callee.name)) return;
309
- const args = node.arguments;
310
- if (args.length === 0) return;
311
- const firstArg = args[0];
312
- if (firstArg.type === "Literal" && typeof firstArg.value === "string") return;
313
- context.report({
314
- node,
315
- message: MESSAGE$5
316
- });
317
- },
318
- "CallExpression:exit"(node) {
319
- if (isMapCall(node)) loopDepth--;
320
- }
321
- };
322
- } });
323
- //#endregion
324
- //#region src/rules/server-routes/defineEventHandler-typed.ts
325
- const MESSAGE$4 = `defineEventHandler handler param event lacks a type annotation. Add a generic: defineEventHandler<H3Event>(...) or type the event parameter explicitly. See https://h3.unjs.io/guide/typed`;
326
- function isFunction(node) {
327
- return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
328
- }
329
- const defineEventHandlerTyped = defineRule({ create(context) {
330
- return { CallExpression(node) {
331
- const callee = node.callee;
332
- if (callee?.type !== "Identifier") return;
333
- if (callee.name !== "defineEventHandler") return;
334
- if (node.typeArguments) return;
335
- const args = node.arguments;
336
- if (args.length === 0) return;
337
- const handler = args[0];
338
- if (!isFunction(handler)) return;
339
- const params = handler.params;
340
- if (!params || params.length === 0) return;
341
- if (params[0].typeAnnotation) return;
342
- context.report({
343
- node,
344
- message: MESSAGE$4
345
- });
346
- } };
347
- } });
348
- //#endregion
349
- //#region src/rules/server-routes/validate-body-with-h3-v2.ts
350
- const MESSAGE$3 = `readBody() is the legacy H3 reader. In Nuxt 4 with h3 v2, use readValidatedBody to parse and validate the request body in one step. See https://nuxt.com/docs/4.x/guide/data-fetching`;
351
- const validateBodyWithH3V2 = defineRule({ create(context) {
352
- return { CallExpression(node) {
353
- const callee = node.callee;
354
- if (callee?.type !== "Identifier") return;
355
- if (callee.name !== "readBody") return;
356
- context.report({
357
- node,
358
- message: MESSAGE$3
359
- });
360
- } };
361
- } });
362
- //#endregion
363
- //#region src/rules/server-routes/createError-on-failure.ts
364
- const MESSAGE$2 = `throw new Error() leaks the call stack and exposes internal details. Use throw createError() from h3 for proper HTTP errors with no stack leak. See https://h3.unjs.io/guide/throw`;
365
- const createErrorOnFailure = defineRule({ create(context) {
366
- let handlerDepth = 0;
367
- return {
368
- CallExpression(node) {
369
- const callee = node.callee;
370
- if (callee?.type !== "Identifier") return;
371
- if (callee.name !== "defineEventHandler") return;
372
- handlerDepth++;
373
- },
374
- "CallExpression:exit"(node) {
375
- const callee = node.callee;
376
- if (callee?.type !== "Identifier") return;
377
- if (callee.name !== "defineEventHandler") return;
378
- handlerDepth--;
379
- },
380
- ThrowStatement(node) {
381
- if (handlerDepth === 0) return;
382
- const arg = node.argument;
383
- if (!arg || arg.type !== "NewExpression") return;
384
- const ctor = arg.callee;
385
- if (ctor?.type !== "Identifier") return;
386
- if (ctor.name !== "Error") return;
387
- context.report({
388
- node,
389
- message: MESSAGE$2
390
- });
391
- }
392
- };
393
- } });
394
- //#endregion
395
- //#region src/rules/hydration/no-document-in-setup.ts
396
- const MESSAGE$1 = `Accessing document/window/navigator/localStorage at the top level of <script setup> causes a server-side crash (SSR). These browser globals are undefined on the server. Move access inside onMounted or guard with import.meta.client. See https://nuxt.com/docs/4.x/guide/components`;
397
- const SSR_UNSAFE_GLOBALS = new Set([
398
- "document",
399
- "window",
400
- "navigator",
401
- "localStorage",
402
- "sessionStorage"
403
- ]);
404
- const noDocumentInSetup = defineRule({ create(context) {
405
- const functionDepthStack = [];
406
- return {
407
- ArrowFunctionExpression() {
408
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
409
- },
410
- "ArrowFunctionExpression:exit"() {
411
- functionDepthStack.pop();
412
- },
413
- FunctionExpression() {
414
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
415
- },
416
- "FunctionExpression:exit"() {
417
- functionDepthStack.pop();
418
- },
419
- FunctionDeclaration() {
420
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
421
- },
422
- "FunctionDeclaration:exit"() {
423
- functionDepthStack.pop();
424
- },
425
- Identifier(node) {
426
- if (functionDepthStack.length > 0) return;
427
- if (SSR_UNSAFE_GLOBALS.has(node.name)) context.report({
428
- node,
429
- message: MESSAGE$1
430
- });
431
- }
432
- };
433
- } });
434
- //#endregion
435
- //#region src/rules/hydration/clientOnly-for-browser-apis.ts
436
- const MESSAGE = `Accessing window./document./navigator./localStorage./sessionStorage. without an import.meta.client or process.client guard causes SSR failures. Wrap with if (import.meta.client) { ... } or move to onMounted. See https://nuxt.com/docs/4.x/guide/components`;
437
- const BROWSER_GLOBALS = new Set([
438
- "window",
439
- "document",
440
- "navigator",
441
- "localStorage",
442
- "sessionStorage"
443
- ]);
444
- function isImportMetaClientGuard(node) {
445
- if (!node || node.type !== "MemberExpression") return false;
446
- const obj = node.object;
447
- if (obj?.type !== "MetaProperty") return false;
448
- const meta = obj.meta;
449
- const prop = obj.property;
450
- if (meta?.type !== "Identifier") return false;
451
- if (meta.name !== "import") return false;
452
- if (prop?.type !== "Identifier") return false;
453
- if (prop.name !== "meta") return false;
454
- const property = node.property;
455
- if (property?.type !== "Identifier") return false;
456
- return property.name === "client";
457
- }
458
- function isProcessClientGuard(node) {
459
- if (!node || node.type !== "MemberExpression") return false;
460
- const obj = node.object;
461
- if (obj?.type !== "Identifier") return false;
462
- if (obj.name !== "process") return false;
463
- const property = node.property;
464
- if (property?.type !== "Identifier") return false;
465
- return property.name === "client";
466
- }
467
- //#endregion
1
+ import { NUXT_AUTO_IMPORTED, NUXT_RULES } from "@geoql/doctor-rule-core";
468
2
  //#region src/plugin.ts
469
3
  const plugin = {
470
4
  meta: { name: "nuxt-doctor" },
471
- rules: {
472
- "ai-slop/no-process-client-server": noProcessClientServer,
473
- "ai-slop/no-explicit-imports-of-auto-imported": noExplicitImportsOfAutoImported,
474
- "ai-slop/no-useState-for-server-data": noUseStateForServerData,
475
- "ai-slop/no-fetch-in-setup": noFetchInSetup,
476
- "data-fetching/useAsyncData-key-required-in-loop": useAsyncDataKeyRequiredInLoop,
477
- "server-routes/defineEventHandler-typed": defineEventHandlerTyped,
478
- "server-routes/validate-body-with-h3-v2": validateBodyWithH3V2,
479
- "server-routes/createError-on-failure": createErrorOnFailure,
480
- "hydration/no-document-in-setup": noDocumentInSetup,
481
- "hydration/clientOnly-for-browser-apis": defineRule({ create(context) {
482
- const functionDepthStack = [];
483
- let isGuarded = false;
484
- return {
485
- ArrowFunctionExpression() {
486
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
487
- },
488
- "ArrowFunctionExpression:exit"() {
489
- functionDepthStack.pop();
490
- },
491
- FunctionExpression() {
492
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
493
- },
494
- "FunctionExpression:exit"() {
495
- functionDepthStack.pop();
496
- },
497
- FunctionDeclaration() {
498
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
499
- },
500
- "FunctionDeclaration:exit"() {
501
- functionDepthStack.pop();
502
- },
503
- IfStatement(node) {
504
- if (functionDepthStack.length > 0) return;
505
- const test = node.test;
506
- if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) isGuarded = true;
507
- },
508
- "IfStatement:exit"(node) {
509
- if (functionDepthStack.length > 0) return;
510
- const test = node.test;
511
- if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) isGuarded = false;
512
- },
513
- MemberExpression(node) {
514
- if (functionDepthStack.length > 0) return;
515
- if (isGuarded) return;
516
- const obj = node.object;
517
- if (obj?.type !== "Identifier") return;
518
- if (!BROWSER_GLOBALS.has(obj.name)) return;
519
- context.report({
520
- node,
521
- message: MESSAGE
522
- });
523
- }
524
- };
525
- } })
526
- }
5
+ rules: Object.fromEntries(NUXT_RULES.map((rule) => [rule.id, rule]))
527
6
  };
528
7
  //#endregion
529
8
  //#region src/index.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["MESSAGE","MESSAGE","MESSAGE","MESSAGE","MESSAGE","MESSAGE","MESSAGE","MESSAGE"],"sources":["../src/define-rule.ts","../src/rules/ai-slop/no-process-client-server.ts","../src/shared/nuxt-auto-imported-symbols.ts","../src/rules/ai-slop/no-explicit-imports-of-auto-imported.ts","../src/rules/ai-slop/no-useState-for-server-data.ts","../src/rules/ai-slop/no-fetch-in-setup.ts","../src/rules/data-fetching/useAsyncData-key-required-in-loop.ts","../src/rules/server-routes/defineEventHandler-typed.ts","../src/rules/server-routes/validate-body-with-h3-v2.ts","../src/rules/server-routes/createError-on-failure.ts","../src/rules/hydration/no-document-in-setup.ts","../src/rules/hydration/clientOnly-for-browser-apis.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 LEGACY_PROPS = new Set(['client', 'server', 'browser']);\nconst DOCS_URL = 'https://nuxt.com/docs/4.x/guide/concepts/auto-imports';\nconst MESSAGE = `Use import.meta.client / import.meta.server / import.meta.browser instead of process.client / process.server / process.browser (Nuxt 4). See ${DOCS_URL}`;\n\nexport const noProcessClientServer = defineRule({\n create(context: RuleContext) {\n return {\n MemberExpression(node: AstNode) {\n const object = node.object as AstNode | undefined;\n if (object?.type !== 'Identifier') return;\n if ((object as AstNode & { name: string }).name !== 'process') return;\n const property = node.property as AstNode | undefined;\n if (property?.type !== 'Identifier') return;\n if (!LEGACY_PROPS.has(property.name as string)) return;\n context.report({ node, message: MESSAGE });\n },\n };\n },\n fix(node: AstNode) {\n const object = node.object as AstNode | undefined;\n if (object?.type !== 'Identifier') return null;\n if ((object as AstNode & { name: string }).name !== 'process') return null;\n const property = node.property as AstNode | undefined;\n if (property?.type !== 'Identifier') return null;\n const name = property.name as string;\n if (!LEGACY_PROPS.has(name)) return null;\n return `import.meta.${name}`;\n },\n});\n","export const NUXT_AUTO_IMPORTED: ReadonlySet<string> = new Set([\n // Vue reactivity auto-imported in Nuxt\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 // Nuxt auto-imports\n 'useRoute',\n 'useRouter',\n 'useFetch',\n 'useAsyncData',\n 'useState',\n 'useRuntimeConfig',\n 'useNuxtApp',\n 'navigateTo',\n 'definePageMeta',\n 'useSeoMeta',\n 'useHead',\n]);\n","import { defineRule } from '../../define-rule.js';\nimport type { AstNode, RuleContext } from '../../rule-types.js';\nimport { NUXT_AUTO_IMPORTED } from '../../shared/nuxt-auto-imported-symbols.js';\n\nconst DOCS_URL = 'https://nuxt.com/docs/4.x/guide/concepts/auto-imports';\nconst WHOLE_MESSAGE = `These symbols are auto-imported in Nuxt projects. Remove the import entirely. See ${DOCS_URL}`;\nconst SPECIFIER_MESSAGE = `This symbol is auto-imported in Nuxt projects. Remove it from the import — it is dead weight. See ${DOCS_URL}`;\n\nconst AUTO_IMPORT_SOURCES = new Set(['vue', '#imports', 'vue-router', '#app']);\n\nfunction isAutoImportedValueSpecifier(spec: AstNode): boolean {\n if (spec.importKind === 'type') return false;\n const imported = spec.imported as AstNode;\n return NUXT_AUTO_IMPORTED.has(imported.name as string);\n}\n\nexport const noExplicitImportsOfAutoImported = defineRule({\n create(context: RuleContext) {\n return {\n ImportDeclaration(node: AstNode) {\n if (node.importKind === 'type') return;\n const source = node.source as AstNode;\n if (!AUTO_IMPORT_SOURCES.has(source.value as string)) 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 = 'https://nuxt.com/docs/4.x/guide/data-fetching';\nconst MESSAGE = `useState with a fetch/await initializer suggests useFetch or useAsyncData for automatic SSR hydration and request deduplication. See ${DOCS_URL}`;\n\nfunction containsFetchOrAwait(\n node: AstNode | undefined,\n visited: Set<unknown>,\n): boolean {\n if (!node || visited.has(node)) return false;\n visited.add(node);\n if (node.type === 'AwaitExpression') return true;\n if (node.type === 'CallExpression') {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') {\n const name = callee.name as string;\n if (name === '$fetch' || name === 'fetch') return true;\n }\n }\n for (const key of Object.keys(node)) {\n if (key === 'type' || key === 'loc' || key === 'range') continue;\n const value = (node as Record<string, unknown>)[key];\n if (Array.isArray(value)) {\n for (const child of value) {\n if (child && typeof child === 'object' && 'type' in child) {\n if (containsFetchOrAwait(child as AstNode, visited)) return true;\n }\n }\n } else if (value && typeof value === 'object' && 'type' in value) {\n if (containsFetchOrAwait(value as AstNode, visited)) return true;\n }\n }\n return false;\n}\n\nexport const noUseStateForServerData = defineRule({\n create(context: RuleContext) {\n return {\n CallExpression(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'Identifier') return;\n if ((callee as AstNode & { name: string }).name !== 'useState') return;\n const args = node.arguments as AstNode[];\n if (args.length < 2) return;\n const initFn = args[1];\n if (\n initFn.type !== 'ArrowFunctionExpression' &&\n initFn.type !== 'FunctionExpression'\n )\n return;\n if (!containsFetchOrAwait(initFn.body as AstNode, new Set())) 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 = 'https://nuxt.com/docs/4.x/guide/data-fetching';\nconst MESSAGE = `Bare $fetch/fetch at the top level of <script setup> runs on both server and client without SSR deduplication. Use useFetch for automatic request deduplication and SSR hydration. See ${DOCS_URL}`;\n\nfunction isFetchCall(node: AstNode | undefined): boolean {\n if (!node || node.type !== 'CallExpression') return false;\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') {\n const name = callee.name as string;\n return name === '$fetch' || name === 'fetch';\n }\n return false;\n}\n\nexport const noFetchInSetup = defineRule({\n create(context: RuleContext) {\n const functionDepthStack: number[] = [];\n\n return {\n ArrowFunctionExpression() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'ArrowFunctionExpression:exit'() {\n functionDepthStack.pop();\n },\n FunctionExpression() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'FunctionExpression:exit'() {\n functionDepthStack.pop();\n },\n FunctionDeclaration() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'FunctionDeclaration:exit'() {\n functionDepthStack.pop();\n },\n CallExpression(node: AstNode) {\n if (functionDepthStack.length > 0) return;\n if (!isFetchCall(node)) return;\n const parent = (node as Record<string, unknown>)['parent'] as\n | AstNode\n | undefined;\n if (parent?.type === 'AwaitExpression') return;\n context.report({ node, message: MESSAGE });\n },\n AwaitExpression(node: AstNode) {\n if (functionDepthStack.length > 0) return;\n if (!isFetchCall(node.argument as AstNode | undefined)) 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 = 'https://nuxt.com/docs/4.x/guide/data-fetching';\nconst MESSAGE = `useAsyncData/useFetch without an explicit string key inside a loop causes duplicate requests and cache fragmentation. Pass a unique string key as the first argument. See ${DOCS_URL}`;\n\nconst DATA_FETCHERS = new Set(['useAsyncData', 'useFetch']);\n\nfunction isMapCall(node: AstNode): boolean {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type === 'Identifier') return callee.name === 'map';\n if (callee?.type === 'MemberExpression') {\n const prop = (callee as AstNode & { property?: AstNode }).property as\n | AstNode\n | undefined;\n return (\n prop?.type === 'Identifier' &&\n (prop as AstNode & { name: string }).name === 'map'\n );\n }\n return false;\n}\n\nexport const useAsyncDataKeyRequiredInLoop = defineRule({\n create(context: RuleContext) {\n let loopDepth = 0;\n\n return {\n ForStatement() {\n loopDepth++;\n },\n 'ForStatement:exit'() {\n loopDepth--;\n },\n ForOfStatement() {\n loopDepth++;\n },\n 'ForOfStatement:exit'() {\n loopDepth--;\n },\n ForInStatement() {\n loopDepth++;\n },\n 'ForInStatement:exit'() {\n loopDepth--;\n },\n WhileStatement() {\n loopDepth++;\n },\n 'WhileStatement:exit'() {\n loopDepth--;\n },\n CallExpression(node: AstNode) {\n if (isMapCall(node)) {\n loopDepth++;\n return;\n }\n if (loopDepth === 0) return;\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'Identifier') return;\n if (!DATA_FETCHERS.has(callee.name as string)) return;\n const args = node.arguments as AstNode[];\n if (args.length === 0) return;\n const firstArg = args[0];\n if (firstArg.type === 'Literal' && typeof firstArg.value === 'string')\n return;\n context.report({ node, message: MESSAGE });\n },\n 'CallExpression:exit'(node: AstNode) {\n if (isMapCall(node)) {\n loopDepth--;\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://h3.unjs.io/guide/typed';\nconst MESSAGE = `defineEventHandler handler param event lacks a type annotation. Add a generic: defineEventHandler<H3Event>(...) or type the event parameter explicitly. See ${DOCS_URL}`;\n\nfunction isFunction(node: AstNode | undefined): boolean {\n return (\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression'\n );\n}\n\nexport const defineEventHandlerTyped = defineRule({\n create(context: RuleContext) {\n return {\n CallExpression(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'Identifier') return;\n if (\n (callee as AstNode & { name: string }).name !== 'defineEventHandler'\n )\n return;\n if (node.typeArguments) return;\n const args = node.arguments as AstNode[];\n if (args.length === 0) return;\n const handler = args[0];\n if (!isFunction(handler)) return;\n const params = (handler as AstNode & { params: AstNode[] }).params;\n if (!params || params.length === 0) return;\n const eventParam = params[0] as AstNode;\n if (eventParam.typeAnnotation) 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 = 'https://nuxt.com/docs/4.x/guide/data-fetching';\nconst MESSAGE = `readBody() is the legacy H3 reader. In Nuxt 4 with h3 v2, use readValidatedBody to parse and validate the request body in one step. See ${DOCS_URL}`;\n\nexport const validateBodyWithH3V2 = defineRule({\n create(context: RuleContext) {\n return {\n CallExpression(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'Identifier') return;\n if ((callee as AstNode & { name: string }).name !== 'readBody') 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 = 'https://h3.unjs.io/guide/throw';\nconst MESSAGE = `throw new Error() leaks the call stack and exposes internal details. Use throw createError() from h3 for proper HTTP errors with no stack leak. See ${DOCS_URL}`;\n\nexport const createErrorOnFailure = defineRule({\n create(context: RuleContext) {\n let handlerDepth = 0;\n\n return {\n CallExpression(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'Identifier') return;\n if (\n (callee as AstNode & { name: string }).name !== 'defineEventHandler'\n )\n return;\n handlerDepth++;\n },\n 'CallExpression:exit'(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee?.type !== 'Identifier') return;\n if (\n (callee as AstNode & { name: string }).name !== 'defineEventHandler'\n )\n return;\n handlerDepth--;\n },\n ThrowStatement(node: AstNode) {\n if (handlerDepth === 0) return;\n const arg = node.argument as AstNode | undefined;\n if (!arg || arg.type !== 'NewExpression') return;\n const ctor = arg.callee as AstNode | undefined;\n if (ctor?.type !== 'Identifier') return;\n if ((ctor as AstNode & { name: string }).name !== 'Error') 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 = 'https://nuxt.com/docs/4.x/guide/components';\nconst MESSAGE = `Accessing document/window/navigator/localStorage at the top level of <script setup> causes a server-side crash (SSR). These browser globals are undefined on the server. Move access inside onMounted or guard with import.meta.client. See ${DOCS_URL}`;\n\nconst SSR_UNSAFE_GLOBALS = new Set([\n 'document',\n 'window',\n 'navigator',\n 'localStorage',\n 'sessionStorage',\n]);\n\nexport const noDocumentInSetup = defineRule({\n create(context: RuleContext) {\n const functionDepthStack: number[] = [];\n\n return {\n ArrowFunctionExpression() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'ArrowFunctionExpression:exit'() {\n functionDepthStack.pop();\n },\n FunctionExpression() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'FunctionExpression:exit'() {\n functionDepthStack.pop();\n },\n FunctionDeclaration() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'FunctionDeclaration:exit'() {\n functionDepthStack.pop();\n },\n Identifier(node: AstNode) {\n if (functionDepthStack.length > 0) return;\n if (SSR_UNSAFE_GLOBALS.has(node.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://nuxt.com/docs/4.x/guide/components';\nconst MESSAGE = `Accessing window./document./navigator./localStorage./sessionStorage. without an import.meta.client or process.client guard causes SSR failures. Wrap with if (import.meta.client) { ... } or move to onMounted. See ${DOCS_URL}`;\n\nconst BROWSER_GLOBALS = new Set([\n 'window',\n 'document',\n 'navigator',\n 'localStorage',\n 'sessionStorage',\n]);\n\nfunction isImportMetaClientGuard(node: AstNode | undefined): boolean {\n if (!node || node.type !== 'MemberExpression') return false;\n const obj = node.object as AstNode | undefined;\n if (obj?.type !== 'MetaProperty') return false;\n const meta = obj.meta as AstNode | undefined;\n const prop = obj.property as AstNode | undefined;\n if (meta?.type !== 'Identifier') return false;\n if ((meta as AstNode & { name: string }).name !== 'import') return false;\n if (prop?.type !== 'Identifier') return false;\n if ((prop as AstNode & { name: string }).name !== 'meta') return false;\n const property = node.property as AstNode | undefined;\n if (property?.type !== 'Identifier') return false;\n return (property as AstNode & { name: string }).name === 'client';\n}\n\nfunction isProcessClientGuard(node: AstNode | undefined): boolean {\n if (!node || node.type !== 'MemberExpression') return false;\n const obj = node.object as AstNode | undefined;\n if (obj?.type !== 'Identifier') return false;\n if ((obj as AstNode & { name: string }).name !== 'process') return false;\n const property = node.property as AstNode | undefined;\n if (property?.type !== 'Identifier') return false;\n return (property as AstNode & { name: string }).name === 'client';\n}\n\nexport const clientOnlyForBrowserApis = defineRule({\n create(context: RuleContext) {\n const functionDepthStack: number[] = [];\n let isGuarded = false;\n\n return {\n ArrowFunctionExpression() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'ArrowFunctionExpression:exit'() {\n functionDepthStack.pop();\n },\n FunctionExpression() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'FunctionExpression:exit'() {\n functionDepthStack.pop();\n },\n FunctionDeclaration() {\n functionDepthStack.push(\n functionDepthStack.length > 0\n ? functionDepthStack[functionDepthStack.length - 1]! + 1\n : 1,\n );\n },\n 'FunctionDeclaration:exit'() {\n functionDepthStack.pop();\n },\n IfStatement(node: AstNode) {\n if (functionDepthStack.length > 0) return;\n const test = node.test as AstNode | undefined;\n if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) {\n isGuarded = true;\n }\n },\n 'IfStatement:exit'(node: AstNode) {\n if (functionDepthStack.length > 0) return;\n const test = node.test as AstNode | undefined;\n if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) {\n isGuarded = false;\n }\n },\n MemberExpression(node: AstNode) {\n if (functionDepthStack.length > 0) return;\n if (isGuarded) return;\n const obj = node.object as AstNode | undefined;\n if (obj?.type !== 'Identifier') return;\n if (!BROWSER_GLOBALS.has(obj.name as string)) return;\n context.report({ node, message: MESSAGE });\n },\n };\n },\n});\n","import type { Plugin } from './rule-types.js';\nimport { noProcessClientServer } from './rules/ai-slop/no-process-client-server.js';\nimport { noExplicitImportsOfAutoImported } from './rules/ai-slop/no-explicit-imports-of-auto-imported.js';\nimport { noUseStateForServerData } from './rules/ai-slop/no-useState-for-server-data.js';\nimport { noFetchInSetup } from './rules/ai-slop/no-fetch-in-setup.js';\nimport { useAsyncDataKeyRequiredInLoop } from './rules/data-fetching/useAsyncData-key-required-in-loop.js';\nimport { defineEventHandlerTyped } from './rules/server-routes/defineEventHandler-typed.js';\nimport { validateBodyWithH3V2 } from './rules/server-routes/validate-body-with-h3-v2.js';\nimport { createErrorOnFailure } from './rules/server-routes/createError-on-failure.js';\nimport { noDocumentInSetup } from './rules/hydration/no-document-in-setup.js';\nimport { clientOnlyForBrowserApis } from './rules/hydration/clientOnly-for-browser-apis.js';\n\nexport const plugin: Plugin = {\n meta: { name: 'nuxt-doctor' },\n rules: {\n 'ai-slop/no-process-client-server': noProcessClientServer,\n 'ai-slop/no-explicit-imports-of-auto-imported':\n noExplicitImportsOfAutoImported,\n 'ai-slop/no-useState-for-server-data': noUseStateForServerData,\n 'ai-slop/no-fetch-in-setup': noFetchInSetup,\n 'data-fetching/useAsyncData-key-required-in-loop':\n useAsyncDataKeyRequiredInLoop,\n 'server-routes/defineEventHandler-typed': defineEventHandlerTyped,\n 'server-routes/validate-body-with-h3-v2': validateBodyWithH3V2,\n 'server-routes/createError-on-failure': createErrorOnFailure,\n 'hydration/no-document-in-setup': noDocumentInSetup,\n 'hydration/clientOnly-for-browser-apis': clientOnlyForBrowserApis,\n },\n};\n","import { plugin } from './plugin.js';\n\nexport default plugin;\nexport { plugin };\nexport { NUXT_AUTO_IMPORTED } from './shared/nuxt-auto-imported-symbols.js';\nexport type { Rule, RuleContext } from './rule-types.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/plugin.ts","../src/index.ts"],"sourcesContent":["import type { Plugin } from './rule-types.js';\nimport { NUXT_RULES } from '@geoql/doctor-rule-core';\n\nexport const plugin: Plugin = {\n meta: { name: 'nuxt-doctor' },\n rules: Object.fromEntries(NUXT_RULES.map((rule) => [rule.id, rule])),\n};\n","import { plugin } from './plugin.js';\n\nexport default plugin;\nexport { plugin };\nexport { NUXT_AUTO_IMPORTED } from '@geoql/doctor-rule-core';\nexport type { Rule, RuleContext } from './rule-types.js';\n"],"mappings":";;AAGA,MAAa,SAAiB;CAC5B,MAAM,EAAE,MAAM,cAAc;CAC5B,OAAO,OAAO,YAAY,WAAW,KAAK,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AACrE;;;ACJA,IAAA,cAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoql/oxlint-plugin-nuxt-doctor",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "private": false,
5
5
  "description": "oxlint JS plugin for Nuxt 4 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",