@geoql/oxlint-plugin-nuxt-doctor 0.1.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,7 +31,7 @@ Nuxt 4 only (the `app/` directory layout with `compatibilityVersion: 4`). Severi
31
31
 
32
32
  ## Architecture
33
33
 
34
- See [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md) for how the JS plugin co-loads with oxlint's native `vue` plugin and why script, template, and cross-file rules live in different passes.
34
+ See [`docs/SPEC.md`](../../docs/SPEC.md) §10 for how the JS plugin co-loads with oxlint's native `vue` plugin and why script, template, and cross-file rules live in different passes.
35
35
 
36
36
  ## License
37
37
 
package/dist/index.d.ts CHANGED
@@ -1,46 +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 ReportDescriptor {
18
- node: AstNode;
19
- message: string;
20
- }
21
- interface RuleContext {
22
- report: (descriptor: ReportDescriptor) => void;
23
- getFilename?: () => string | undefined;
24
- settings?: Record<string, unknown>;
25
- /** Capability tokens detected for the project (e.g. 'auto-imports:vue'). */
26
- capabilities?: Set<string>;
27
- }
28
- type RuleVisitor = (node: AstNode) => void;
29
- interface Rule {
30
- create: (context: RuleContext) => Record<string, RuleVisitor>;
31
- }
32
- interface Plugin {
33
- meta: {
34
- name: string;
35
- };
36
- rules: Record<string, Rule>;
37
- }
38
- //#endregion
1
+ import { NUXT_AUTO_IMPORTED, Plugin, Rule, RuleContext } from "@geoql/doctor-rule-core";
2
+
39
3
  //#region src/plugin.d.ts
40
4
  declare const plugin: Plugin;
41
5
  //#endregion
42
- //#region src/shared/nuxt-auto-imported-symbols.d.ts
43
- declare const NUXT_AUTO_IMPORTED: ReadonlySet<string>;
44
- //#endregion
45
6
  export { NUXT_AUTO_IMPORTED, type Rule, type RuleContext, plugin as default, plugin };
46
7
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,492 +1,8 @@
1
- //#region src/define-rule.ts
2
- function defineRule(rule) {
3
- return rule;
4
- }
5
- //#endregion
6
- //#region src/rules/ai-slop/no-process-client-server.ts
7
- const LEGACY_PROPS = new Set([
8
- "client",
9
- "server",
10
- "browser"
11
- ]);
12
- 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`;
13
- const noProcessClientServer = defineRule({ create(context) {
14
- return { MemberExpression(node) {
15
- const object = node.object;
16
- if (object?.type !== "Identifier") return;
17
- if (object.name !== "process") return;
18
- const property = node.property;
19
- if (property?.type !== "Identifier") return;
20
- if (!LEGACY_PROPS.has(property.name)) return;
21
- context.report({
22
- node,
23
- message: MESSAGE$8
24
- });
25
- } };
26
- } });
27
- //#endregion
28
- //#region src/shared/nuxt-auto-imported-symbols.ts
29
- const NUXT_AUTO_IMPORTED = new Set([
30
- "ref",
31
- "shallowRef",
32
- "computed",
33
- "reactive",
34
- "shallowReactive",
35
- "readonly",
36
- "shallowReadonly",
37
- "toRef",
38
- "toRefs",
39
- "isRef",
40
- "isReactive",
41
- "isReadonly",
42
- "isProxy",
43
- "unref",
44
- "triggerRef",
45
- "customRef",
46
- "markRaw",
47
- "toRaw",
48
- "watch",
49
- "watchEffect",
50
- "watchPostEffect",
51
- "watchSyncEffect",
52
- "effectScope",
53
- "getCurrentScope",
54
- "onScopeDispose",
55
- "onMounted",
56
- "onUpdated",
57
- "onUnmounted",
58
- "onBeforeMount",
59
- "onBeforeUpdate",
60
- "onBeforeUnmount",
61
- "onErrorCaptured",
62
- "onRenderTracked",
63
- "onRenderTriggered",
64
- "onActivated",
65
- "onDeactivated",
66
- "onServerPrefetch",
67
- "provide",
68
- "inject",
69
- "hasInjectionContext",
70
- "getCurrentInstance",
71
- "nextTick",
72
- "defineComponent",
73
- "defineAsyncComponent",
74
- "useTemplateRef",
75
- "useId",
76
- "useModel",
77
- "useRoute",
78
- "useRouter",
79
- "useFetch",
80
- "useAsyncData",
81
- "useState",
82
- "useRuntimeConfig",
83
- "useNuxtApp",
84
- "navigateTo",
85
- "definePageMeta",
86
- "useSeoMeta",
87
- "useHead"
88
- ]);
89
- //#endregion
90
- //#region src/rules/ai-slop/no-explicit-imports-of-auto-imported.ts
91
- const DOCS_URL = "https://nuxt.com/docs/4.x/guide/concepts/auto-imports";
92
- const WHOLE_MESSAGE = `These symbols are auto-imported in Nuxt projects. Remove the import entirely. See ${DOCS_URL}`;
93
- const SPECIFIER_MESSAGE = `This symbol is auto-imported in Nuxt projects. Remove it from the import — it is dead weight. See ${DOCS_URL}`;
94
- const AUTO_IMPORT_SOURCES = new Set([
95
- "vue",
96
- "#imports",
97
- "vue-router",
98
- "#app"
99
- ]);
100
- function isAutoImportedValueSpecifier(spec) {
101
- if (spec.importKind === "type") return false;
102
- const imported = spec.imported;
103
- return NUXT_AUTO_IMPORTED.has(imported.name);
104
- }
105
- const noExplicitImportsOfAutoImported = defineRule({ create(context) {
106
- return { ImportDeclaration(node) {
107
- if (node.importKind === "type") return;
108
- const source = node.source;
109
- if (!AUTO_IMPORT_SOURCES.has(source.value)) return;
110
- const specifiers = node.specifiers;
111
- const named = specifiers.filter((s) => s.type === "ImportSpecifier");
112
- if (named.length === 0) return;
113
- const offending = named.filter(isAutoImportedValueSpecifier);
114
- if (offending.length === 0) return;
115
- if (offending.length === named.length && specifiers.length === named.length) {
116
- context.report({
117
- node,
118
- message: WHOLE_MESSAGE
119
- });
120
- return;
121
- }
122
- for (const spec of offending) context.report({
123
- node: spec,
124
- message: SPECIFIER_MESSAGE
125
- });
126
- } };
127
- } });
128
- //#endregion
129
- //#region src/rules/ai-slop/no-useState-for-server-data.ts
130
- 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`;
131
- function containsFetchOrAwait(node, visited) {
132
- if (!node || visited.has(node)) return false;
133
- visited.add(node);
134
- if (node.type === "AwaitExpression") return true;
135
- if (node.type === "CallExpression") {
136
- const callee = node.callee;
137
- if (callee?.type === "Identifier") {
138
- const name = callee.name;
139
- if (name === "$fetch" || name === "fetch") return true;
140
- }
141
- }
142
- for (const key of Object.keys(node)) {
143
- if (key === "type" || key === "loc" || key === "range") continue;
144
- const value = node[key];
145
- if (Array.isArray(value)) {
146
- for (const child of value) if (child && typeof child === "object" && "type" in child) {
147
- if (containsFetchOrAwait(child, visited)) return true;
148
- }
149
- } else if (value && typeof value === "object" && "type" in value) {
150
- if (containsFetchOrAwait(value, visited)) return true;
151
- }
152
- }
153
- return false;
154
- }
155
- const noUseStateForServerData = defineRule({ create(context) {
156
- return { CallExpression(node) {
157
- const callee = node.callee;
158
- if (callee?.type !== "Identifier") return;
159
- if (callee.name !== "useState") return;
160
- const args = node.arguments;
161
- if (args.length < 2) return;
162
- const initFn = args[1];
163
- if (initFn.type !== "ArrowFunctionExpression" && initFn.type !== "FunctionExpression") return;
164
- if (!containsFetchOrAwait(initFn.body, /* @__PURE__ */ new Set())) return;
165
- context.report({
166
- node,
167
- message: MESSAGE$7
168
- });
169
- } };
170
- } });
171
- //#endregion
172
- //#region src/rules/ai-slop/no-fetch-in-setup.ts
173
- 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`;
174
- function isFetchCall(node) {
175
- if (!node || node.type !== "CallExpression") return false;
176
- const callee = node.callee;
177
- if (callee?.type === "Identifier") {
178
- const name = callee.name;
179
- return name === "$fetch" || name === "fetch";
180
- }
181
- return false;
182
- }
183
- const noFetchInSetup = defineRule({ create(context) {
184
- const functionDepthStack = [];
185
- return {
186
- ArrowFunctionExpression() {
187
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
188
- },
189
- "ArrowFunctionExpression:exit"() {
190
- functionDepthStack.pop();
191
- },
192
- FunctionExpression() {
193
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
194
- },
195
- "FunctionExpression:exit"() {
196
- functionDepthStack.pop();
197
- },
198
- FunctionDeclaration() {
199
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
200
- },
201
- "FunctionDeclaration:exit"() {
202
- functionDepthStack.pop();
203
- },
204
- CallExpression(node) {
205
- if (functionDepthStack.length > 0) return;
206
- if (!isFetchCall(node)) return;
207
- if (node["parent"]?.type === "AwaitExpression") return;
208
- context.report({
209
- node,
210
- message: MESSAGE$6
211
- });
212
- },
213
- AwaitExpression(node) {
214
- if (functionDepthStack.length > 0) return;
215
- if (!isFetchCall(node.argument)) return;
216
- context.report({
217
- node,
218
- message: MESSAGE$6
219
- });
220
- }
221
- };
222
- } });
223
- //#endregion
224
- //#region src/rules/data-fetching/useAsyncData-key-required-in-loop.ts
225
- 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`;
226
- const DATA_FETCHERS = new Set(["useAsyncData", "useFetch"]);
227
- function isMapCall(node) {
228
- const callee = node.callee;
229
- if (callee?.type === "Identifier") return callee.name === "map";
230
- if (callee?.type === "MemberExpression") {
231
- const prop = callee.property;
232
- return prop?.type === "Identifier" && prop.name === "map";
233
- }
234
- return false;
235
- }
236
- const useAsyncDataKeyRequiredInLoop = defineRule({ create(context) {
237
- let loopDepth = 0;
238
- return {
239
- ForStatement() {
240
- loopDepth++;
241
- },
242
- "ForStatement:exit"() {
243
- loopDepth--;
244
- },
245
- ForOfStatement() {
246
- loopDepth++;
247
- },
248
- "ForOfStatement:exit"() {
249
- loopDepth--;
250
- },
251
- ForInStatement() {
252
- loopDepth++;
253
- },
254
- "ForInStatement:exit"() {
255
- loopDepth--;
256
- },
257
- WhileStatement() {
258
- loopDepth++;
259
- },
260
- "WhileStatement:exit"() {
261
- loopDepth--;
262
- },
263
- CallExpression(node) {
264
- if (isMapCall(node)) {
265
- loopDepth++;
266
- return;
267
- }
268
- if (loopDepth === 0) return;
269
- const callee = node.callee;
270
- if (callee?.type !== "Identifier") return;
271
- if (!DATA_FETCHERS.has(callee.name)) return;
272
- const args = node.arguments;
273
- if (args.length === 0) return;
274
- const firstArg = args[0];
275
- if (firstArg.type === "Literal" && typeof firstArg.value === "string") return;
276
- context.report({
277
- node,
278
- message: MESSAGE$5
279
- });
280
- },
281
- "CallExpression:exit"(node) {
282
- if (isMapCall(node)) loopDepth--;
283
- }
284
- };
285
- } });
286
- //#endregion
287
- //#region src/rules/server-routes/defineEventHandler-typed.ts
288
- 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`;
289
- function isFunction(node) {
290
- return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
291
- }
292
- const defineEventHandlerTyped = defineRule({ create(context) {
293
- return { CallExpression(node) {
294
- const callee = node.callee;
295
- if (callee?.type !== "Identifier") return;
296
- if (callee.name !== "defineEventHandler") return;
297
- if (node.typeArguments) return;
298
- const args = node.arguments;
299
- if (args.length === 0) return;
300
- const handler = args[0];
301
- if (!isFunction(handler)) return;
302
- const params = handler.params;
303
- if (!params || params.length === 0) return;
304
- if (params[0].typeAnnotation) return;
305
- context.report({
306
- node,
307
- message: MESSAGE$4
308
- });
309
- } };
310
- } });
311
- //#endregion
312
- //#region src/rules/server-routes/validate-body-with-h3-v2.ts
313
- 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`;
314
- const validateBodyWithH3V2 = defineRule({ create(context) {
315
- return { CallExpression(node) {
316
- const callee = node.callee;
317
- if (callee?.type !== "Identifier") return;
318
- if (callee.name !== "readBody") return;
319
- context.report({
320
- node,
321
- message: MESSAGE$3
322
- });
323
- } };
324
- } });
325
- //#endregion
326
- //#region src/rules/server-routes/createError-on-failure.ts
327
- 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`;
328
- const createErrorOnFailure = defineRule({ create(context) {
329
- let handlerDepth = 0;
330
- return {
331
- CallExpression(node) {
332
- const callee = node.callee;
333
- if (callee?.type !== "Identifier") return;
334
- if (callee.name !== "defineEventHandler") return;
335
- handlerDepth++;
336
- },
337
- "CallExpression:exit"(node) {
338
- const callee = node.callee;
339
- if (callee?.type !== "Identifier") return;
340
- if (callee.name !== "defineEventHandler") return;
341
- handlerDepth--;
342
- },
343
- ThrowStatement(node) {
344
- if (handlerDepth === 0) return;
345
- const arg = node.argument;
346
- if (!arg || arg.type !== "NewExpression") return;
347
- const ctor = arg.callee;
348
- if (ctor?.type !== "Identifier") return;
349
- if (ctor.name !== "Error") return;
350
- context.report({
351
- node,
352
- message: MESSAGE$2
353
- });
354
- }
355
- };
356
- } });
357
- //#endregion
358
- //#region src/rules/hydration/no-document-in-setup.ts
359
- 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`;
360
- const SSR_UNSAFE_GLOBALS = new Set([
361
- "document",
362
- "window",
363
- "navigator",
364
- "localStorage",
365
- "sessionStorage"
366
- ]);
367
- const noDocumentInSetup = defineRule({ create(context) {
368
- const functionDepthStack = [];
369
- return {
370
- ArrowFunctionExpression() {
371
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
372
- },
373
- "ArrowFunctionExpression:exit"() {
374
- functionDepthStack.pop();
375
- },
376
- FunctionExpression() {
377
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
378
- },
379
- "FunctionExpression:exit"() {
380
- functionDepthStack.pop();
381
- },
382
- FunctionDeclaration() {
383
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
384
- },
385
- "FunctionDeclaration:exit"() {
386
- functionDepthStack.pop();
387
- },
388
- Identifier(node) {
389
- if (functionDepthStack.length > 0) return;
390
- if (SSR_UNSAFE_GLOBALS.has(node.name)) context.report({
391
- node,
392
- message: MESSAGE$1
393
- });
394
- }
395
- };
396
- } });
397
- //#endregion
398
- //#region src/rules/hydration/clientOnly-for-browser-apis.ts
399
- 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`;
400
- const BROWSER_GLOBALS = new Set([
401
- "window",
402
- "document",
403
- "navigator",
404
- "localStorage",
405
- "sessionStorage"
406
- ]);
407
- function isImportMetaClientGuard(node) {
408
- if (!node || node.type !== "MemberExpression") return false;
409
- const obj = node.object;
410
- if (obj?.type !== "MetaProperty") return false;
411
- const meta = obj.meta;
412
- const prop = obj.property;
413
- if (meta?.type !== "Identifier") return false;
414
- if (meta.name !== "import") return false;
415
- if (prop?.type !== "Identifier") return false;
416
- if (prop.name !== "meta") return false;
417
- const property = node.property;
418
- if (property?.type !== "Identifier") return false;
419
- return property.name === "client";
420
- }
421
- function isProcessClientGuard(node) {
422
- if (!node || node.type !== "MemberExpression") return false;
423
- const obj = node.object;
424
- if (obj?.type !== "Identifier") return false;
425
- if (obj.name !== "process") return false;
426
- const property = node.property;
427
- if (property?.type !== "Identifier") return false;
428
- return property.name === "client";
429
- }
430
- //#endregion
1
+ import { NUXT_AUTO_IMPORTED, NUXT_RULES } from "@geoql/doctor-rule-core";
431
2
  //#region src/plugin.ts
432
3
  const plugin = {
433
4
  meta: { name: "nuxt-doctor" },
434
- rules: {
435
- "ai-slop/no-process-client-server": noProcessClientServer,
436
- "ai-slop/no-explicit-imports-of-auto-imported": noExplicitImportsOfAutoImported,
437
- "ai-slop/no-useState-for-server-data": noUseStateForServerData,
438
- "ai-slop/no-fetch-in-setup": noFetchInSetup,
439
- "data-fetching/useAsyncData-key-required-in-loop": useAsyncDataKeyRequiredInLoop,
440
- "server-routes/defineEventHandler-typed": defineEventHandlerTyped,
441
- "server-routes/validate-body-with-h3-v2": validateBodyWithH3V2,
442
- "server-routes/createError-on-failure": createErrorOnFailure,
443
- "hydration/no-document-in-setup": noDocumentInSetup,
444
- "hydration/clientOnly-for-browser-apis": defineRule({ create(context) {
445
- const functionDepthStack = [];
446
- let isGuarded = false;
447
- return {
448
- ArrowFunctionExpression() {
449
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
450
- },
451
- "ArrowFunctionExpression:exit"() {
452
- functionDepthStack.pop();
453
- },
454
- FunctionExpression() {
455
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
456
- },
457
- "FunctionExpression:exit"() {
458
- functionDepthStack.pop();
459
- },
460
- FunctionDeclaration() {
461
- functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
462
- },
463
- "FunctionDeclaration:exit"() {
464
- functionDepthStack.pop();
465
- },
466
- IfStatement(node) {
467
- if (functionDepthStack.length > 0) return;
468
- const test = node.test;
469
- if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) isGuarded = true;
470
- },
471
- "IfStatement:exit"(node) {
472
- if (functionDepthStack.length > 0) return;
473
- const test = node.test;
474
- if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) isGuarded = false;
475
- },
476
- MemberExpression(node) {
477
- if (functionDepthStack.length > 0) return;
478
- if (isGuarded) return;
479
- const obj = node.object;
480
- if (obj?.type !== "Identifier") return;
481
- if (!BROWSER_GLOBALS.has(obj.name)) return;
482
- context.report({
483
- node,
484
- message: MESSAGE
485
- });
486
- }
487
- };
488
- } })
489
- }
5
+ rules: Object.fromEntries(NUXT_RULES.map((rule) => [rule.id, rule]))
490
6
  };
491
7
  //#endregion
492
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 } from './rule-types.js';\n\nexport function defineRule(rule: Rule): Rule {\n return rule;\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});\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": "0.1.1",
3
+ "version": "1.1.0",
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.1.0"
46
+ },
44
47
  "devDependencies": {
45
- "@types/node": "^25.9.1",
48
+ "@types/node": "^25.9.3",
46
49
  "oxc-parser": "0.133.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",