@geoql/doctor-rule-core 1.0.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.js ADDED
@@ -0,0 +1,1176 @@
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/shared/auto-imported-symbols.ts
32
+ const VUE_AUTO_IMPORTED = new Set([
33
+ "ref",
34
+ "shallowRef",
35
+ "computed",
36
+ "reactive",
37
+ "shallowReactive",
38
+ "readonly",
39
+ "shallowReadonly",
40
+ "toRef",
41
+ "toRefs",
42
+ "isRef",
43
+ "isReactive",
44
+ "isReadonly",
45
+ "isProxy",
46
+ "unref",
47
+ "triggerRef",
48
+ "customRef",
49
+ "markRaw",
50
+ "toRaw",
51
+ "watch",
52
+ "watchEffect",
53
+ "watchPostEffect",
54
+ "watchSyncEffect",
55
+ "effectScope",
56
+ "getCurrentScope",
57
+ "onScopeDispose",
58
+ "onMounted",
59
+ "onUpdated",
60
+ "onUnmounted",
61
+ "onBeforeMount",
62
+ "onBeforeUpdate",
63
+ "onBeforeUnmount",
64
+ "onErrorCaptured",
65
+ "onRenderTracked",
66
+ "onRenderTriggered",
67
+ "onActivated",
68
+ "onDeactivated",
69
+ "onServerPrefetch",
70
+ "provide",
71
+ "inject",
72
+ "hasInjectionContext",
73
+ "getCurrentInstance",
74
+ "nextTick",
75
+ "defineComponent",
76
+ "defineAsyncComponent",
77
+ "useTemplateRef",
78
+ "useId",
79
+ "useModel"
80
+ ]);
81
+ const NUXT_AUTO_IMPORTED = new Set([
82
+ ...VUE_AUTO_IMPORTED,
83
+ "useRoute",
84
+ "useRouter",
85
+ "useFetch",
86
+ "useAsyncData",
87
+ "useState",
88
+ "useRuntimeConfig",
89
+ "useNuxtApp",
90
+ "navigateTo",
91
+ "definePageMeta",
92
+ "useSeoMeta",
93
+ "useHead"
94
+ ]);
95
+ //#endregion
96
+ //#region src/rules/nuxt/ai-slop/no-explicit-imports-of-auto-imported.ts
97
+ const DOCS_URL$1 = "https://nuxt.com/docs/4.x/guide/concepts/auto-imports";
98
+ const WHOLE_MESSAGE$1 = `These symbols are auto-imported in Nuxt projects. Remove the import entirely. See ${DOCS_URL$1}`;
99
+ const SPECIFIER_MESSAGE$1 = `This symbol is auto-imported in Nuxt projects. Remove it from the import — it is dead weight. See ${DOCS_URL$1}`;
100
+ const AUTO_IMPORT_SOURCES = new Set([
101
+ "vue",
102
+ "#imports",
103
+ "vue-router",
104
+ "#app"
105
+ ]);
106
+ function isAutoImportedValueSpecifier$1(spec) {
107
+ if (spec.importKind === "type") return false;
108
+ const imported = spec.imported;
109
+ return NUXT_AUTO_IMPORTED.has(imported.name);
110
+ }
111
+ const noExplicitImportsOfAutoImported = defineRule({ create(context) {
112
+ return { ImportDeclaration(node) {
113
+ if (node.importKind === "type") return;
114
+ const source = node.source;
115
+ if (!AUTO_IMPORT_SOURCES.has(source.value)) return;
116
+ const specifiers = node.specifiers;
117
+ const named = specifiers.filter((s) => s.type === "ImportSpecifier");
118
+ if (named.length === 0) return;
119
+ const offending = named.filter(isAutoImportedValueSpecifier$1);
120
+ if (offending.length === 0) return;
121
+ if (offending.length === named.length && specifiers.length === named.length) {
122
+ context.report({
123
+ node,
124
+ message: WHOLE_MESSAGE$1
125
+ });
126
+ return;
127
+ }
128
+ for (const spec of offending) context.report({
129
+ node: spec,
130
+ message: SPECIFIER_MESSAGE$1
131
+ });
132
+ } };
133
+ } });
134
+ //#endregion
135
+ //#region src/rules/nuxt/ai-slop/no-fetch-in-setup.ts
136
+ const MESSAGE$22 = `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`;
137
+ function isFetchCall(node) {
138
+ if (!node || node.type !== "CallExpression") return false;
139
+ const callee = node.callee;
140
+ if (callee?.type === "Identifier") {
141
+ const name = callee.name;
142
+ return name === "$fetch" || name === "fetch";
143
+ }
144
+ return false;
145
+ }
146
+ const noFetchInSetup = defineRule({ create(context) {
147
+ const functionDepthStack = [];
148
+ return {
149
+ ArrowFunctionExpression() {
150
+ functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
151
+ },
152
+ "ArrowFunctionExpression:exit"() {
153
+ functionDepthStack.pop();
154
+ },
155
+ FunctionExpression() {
156
+ functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
157
+ },
158
+ "FunctionExpression:exit"() {
159
+ functionDepthStack.pop();
160
+ },
161
+ FunctionDeclaration() {
162
+ functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
163
+ },
164
+ "FunctionDeclaration:exit"() {
165
+ functionDepthStack.pop();
166
+ },
167
+ CallExpression(node) {
168
+ if (functionDepthStack.length > 0) return;
169
+ if (!isFetchCall(node)) return;
170
+ if (node["parent"]?.type === "AwaitExpression") return;
171
+ context.report({
172
+ node,
173
+ message: MESSAGE$22
174
+ });
175
+ },
176
+ AwaitExpression(node) {
177
+ if (functionDepthStack.length > 0) return;
178
+ if (!isFetchCall(node.argument)) return;
179
+ context.report({
180
+ node,
181
+ message: MESSAGE$22
182
+ });
183
+ }
184
+ };
185
+ } });
186
+ //#endregion
187
+ //#region src/rules/nuxt/ai-slop/no-process-client-server.ts
188
+ const LEGACY_PROPS = new Set([
189
+ "client",
190
+ "server",
191
+ "browser"
192
+ ]);
193
+ const MESSAGE$21 = `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`;
194
+ const noProcessClientServer = defineRule({
195
+ create(context) {
196
+ return { MemberExpression(node) {
197
+ const object = node.object;
198
+ if (object?.type !== "Identifier") return;
199
+ if (object.name !== "process") return;
200
+ const property = node.property;
201
+ if (property?.type !== "Identifier") return;
202
+ if (!LEGACY_PROPS.has(property.name)) return;
203
+ context.report({
204
+ node,
205
+ message: MESSAGE$21
206
+ });
207
+ } };
208
+ },
209
+ fix(node) {
210
+ const object = node.object;
211
+ if (object?.type !== "Identifier") return null;
212
+ if (object.name !== "process") return null;
213
+ const property = node.property;
214
+ if (property?.type !== "Identifier") return null;
215
+ const name = property.name;
216
+ if (!LEGACY_PROPS.has(name)) return null;
217
+ return `import.meta.${name}`;
218
+ }
219
+ });
220
+ //#endregion
221
+ //#region src/rules/nuxt/ai-slop/no-useState-for-server-data.ts
222
+ const MESSAGE$20 = `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`;
223
+ function containsFetchOrAwait(node, visited) {
224
+ if (!node || visited.has(node)) return false;
225
+ visited.add(node);
226
+ if (node.type === "AwaitExpression") return true;
227
+ if (node.type === "CallExpression") {
228
+ const callee = node.callee;
229
+ if (callee?.type === "Identifier") {
230
+ const name = callee.name;
231
+ if (name === "$fetch" || name === "fetch") return true;
232
+ }
233
+ }
234
+ for (const key of Object.keys(node)) {
235
+ if (key === "type" || key === "loc" || key === "range") continue;
236
+ const value = node[key];
237
+ if (Array.isArray(value)) {
238
+ for (const child of value) if (child && typeof child === "object" && "type" in child) {
239
+ if (containsFetchOrAwait(child, visited)) return true;
240
+ }
241
+ } else if (value && typeof value === "object" && "type" in value) {
242
+ if (containsFetchOrAwait(value, visited)) return true;
243
+ }
244
+ }
245
+ return false;
246
+ }
247
+ const noUseStateForServerData = defineRule({ create(context) {
248
+ return { CallExpression(node) {
249
+ const callee = node.callee;
250
+ if (callee?.type !== "Identifier") return;
251
+ if (callee.name !== "useState") return;
252
+ const args = node.arguments;
253
+ if (args.length < 2) return;
254
+ const initFn = args[1];
255
+ if (initFn.type !== "ArrowFunctionExpression" && initFn.type !== "FunctionExpression") return;
256
+ if (!containsFetchOrAwait(initFn.body, /* @__PURE__ */ new Set())) return;
257
+ context.report({
258
+ node,
259
+ message: MESSAGE$20
260
+ });
261
+ } };
262
+ } });
263
+ //#endregion
264
+ //#region src/rules/nuxt/data-fetching/useAsyncData-key-required-in-loop.ts
265
+ const MESSAGE$19 = `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`;
266
+ const DATA_FETCHERS = new Set(["useAsyncData", "useFetch"]);
267
+ function isMapCall(node) {
268
+ const callee = node.callee;
269
+ if (callee?.type === "Identifier") return callee.name === "map";
270
+ if (callee?.type === "MemberExpression") {
271
+ const prop = callee.property;
272
+ return prop?.type === "Identifier" && prop.name === "map";
273
+ }
274
+ return false;
275
+ }
276
+ const useAsyncDataKeyRequiredInLoop = defineRule({ create(context) {
277
+ let loopDepth = 0;
278
+ return {
279
+ ForStatement() {
280
+ loopDepth++;
281
+ },
282
+ "ForStatement:exit"() {
283
+ loopDepth--;
284
+ },
285
+ ForOfStatement() {
286
+ loopDepth++;
287
+ },
288
+ "ForOfStatement:exit"() {
289
+ loopDepth--;
290
+ },
291
+ ForInStatement() {
292
+ loopDepth++;
293
+ },
294
+ "ForInStatement:exit"() {
295
+ loopDepth--;
296
+ },
297
+ WhileStatement() {
298
+ loopDepth++;
299
+ },
300
+ "WhileStatement:exit"() {
301
+ loopDepth--;
302
+ },
303
+ CallExpression(node) {
304
+ if (isMapCall(node)) {
305
+ loopDepth++;
306
+ return;
307
+ }
308
+ if (loopDepth === 0) return;
309
+ const callee = node.callee;
310
+ if (callee?.type !== "Identifier") return;
311
+ if (!DATA_FETCHERS.has(callee.name)) return;
312
+ const args = node.arguments;
313
+ if (args.length === 0) return;
314
+ const firstArg = args[0];
315
+ if (firstArg.type === "Literal" && typeof firstArg.value === "string") return;
316
+ context.report({
317
+ node,
318
+ message: MESSAGE$19
319
+ });
320
+ },
321
+ "CallExpression:exit"(node) {
322
+ if (isMapCall(node)) loopDepth--;
323
+ }
324
+ };
325
+ } });
326
+ //#endregion
327
+ //#region src/rules/nuxt/security/no-user-input-in-fetch-url.ts
328
+ const MESSAGE$18 = `Building a useFetch/$fetch URL from user input (route.query/route.params) lets an attacker control the request target — an SSRF risk during SSR. Use a fixed path and pass user input via the query/body options instead. See https://owasp.org/www-community/attacks/Server_Side_Request_Forgery`;
329
+ const FETCH_CALLEES = new Set([
330
+ "useFetch",
331
+ "useLazyFetch",
332
+ "useAsyncData",
333
+ "useLazyAsyncData",
334
+ "$fetch"
335
+ ]);
336
+ const USER_INPUT_ROOTS = new Set(["route", "useRoute"]);
337
+ const USER_INPUT_SEGMENTS = new Set(["query", "params"]);
338
+ function calleeName$7(node) {
339
+ const callee = node.callee;
340
+ if (callee?.type === "Identifier") return callee.name;
341
+ }
342
+ /** True when the expression reads from route.query.* or route.params.*. */
343
+ function readsUserInput(node) {
344
+ if (node.type !== "MemberExpression") return false;
345
+ const object = node.object;
346
+ const property = node.property;
347
+ if (object.type === "Identifier" && USER_INPUT_ROOTS.has(object.name) && property.type === "Identifier" && USER_INPUT_SEGMENTS.has(property.name)) return true;
348
+ return readsUserInput(object);
349
+ }
350
+ function templateReadsUserInput(node) {
351
+ return node.expressions.some((expr) => readsUserInput(expr));
352
+ }
353
+ function urlArgIsTainted(arg) {
354
+ if (!arg) return false;
355
+ if (arg.type === "TemplateLiteral") return templateReadsUserInput(arg);
356
+ if (arg.type === "MemberExpression") return readsUserInput(arg);
357
+ return false;
358
+ }
359
+ const noUserInputInFetchUrl = defineRule({ create(context) {
360
+ return { CallExpression(node) {
361
+ const name = calleeName$7(node);
362
+ if (name === void 0 || !FETCH_CALLEES.has(name)) return;
363
+ const urlArg = node.arguments[0];
364
+ if (urlArgIsTainted(urlArg)) context.report({
365
+ node,
366
+ message: MESSAGE$18
367
+ });
368
+ } };
369
+ } });
370
+ //#endregion
371
+ //#region src/rules/nuxt/hydration/clientOnly-for-browser-apis.ts
372
+ const MESSAGE$17 = `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`;
373
+ const BROWSER_GLOBALS = new Set([
374
+ "window",
375
+ "document",
376
+ "navigator",
377
+ "localStorage",
378
+ "sessionStorage"
379
+ ]);
380
+ function isImportMetaClientGuard(node) {
381
+ if (!node || node.type !== "MemberExpression") return false;
382
+ const obj = node.object;
383
+ if (obj?.type !== "MetaProperty") return false;
384
+ const meta = obj.meta;
385
+ const prop = obj.property;
386
+ if (meta?.type !== "Identifier") return false;
387
+ if (meta.name !== "import") return false;
388
+ if (prop?.type !== "Identifier") return false;
389
+ if (prop.name !== "meta") return false;
390
+ const property = node.property;
391
+ if (property?.type !== "Identifier") return false;
392
+ return property.name === "client";
393
+ }
394
+ function isProcessClientGuard(node) {
395
+ if (!node || node.type !== "MemberExpression") return false;
396
+ const obj = node.object;
397
+ if (obj?.type !== "Identifier") return false;
398
+ if (obj.name !== "process") return false;
399
+ const property = node.property;
400
+ if (property?.type !== "Identifier") return false;
401
+ return property.name === "client";
402
+ }
403
+ const clientOnlyForBrowserApis = defineRule({ create(context) {
404
+ const functionDepthStack = [];
405
+ let isGuarded = false;
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
+ IfStatement(node) {
426
+ if (functionDepthStack.length > 0) return;
427
+ const test = node.test;
428
+ if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) isGuarded = true;
429
+ },
430
+ "IfStatement:exit"(node) {
431
+ if (functionDepthStack.length > 0) return;
432
+ const test = node.test;
433
+ if (isImportMetaClientGuard(test) || isProcessClientGuard(test)) isGuarded = false;
434
+ },
435
+ MemberExpression(node) {
436
+ if (functionDepthStack.length > 0) return;
437
+ if (isGuarded) return;
438
+ const obj = node.object;
439
+ if (obj?.type !== "Identifier") return;
440
+ if (!BROWSER_GLOBALS.has(obj.name)) return;
441
+ context.report({
442
+ node,
443
+ message: MESSAGE$17
444
+ });
445
+ }
446
+ };
447
+ } });
448
+ //#endregion
449
+ //#region src/rules/nuxt/hydration/no-document-in-setup.ts
450
+ const MESSAGE$16 = `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`;
451
+ const SSR_UNSAFE_GLOBALS = new Set([
452
+ "document",
453
+ "window",
454
+ "navigator",
455
+ "localStorage",
456
+ "sessionStorage"
457
+ ]);
458
+ const noDocumentInSetup = defineRule({ create(context) {
459
+ const functionDepthStack = [];
460
+ return {
461
+ ArrowFunctionExpression() {
462
+ functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
463
+ },
464
+ "ArrowFunctionExpression:exit"() {
465
+ functionDepthStack.pop();
466
+ },
467
+ FunctionExpression() {
468
+ functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
469
+ },
470
+ "FunctionExpression:exit"() {
471
+ functionDepthStack.pop();
472
+ },
473
+ FunctionDeclaration() {
474
+ functionDepthStack.push(functionDepthStack.length > 0 ? functionDepthStack[functionDepthStack.length - 1] + 1 : 1);
475
+ },
476
+ "FunctionDeclaration:exit"() {
477
+ functionDepthStack.pop();
478
+ },
479
+ Identifier(node) {
480
+ if (functionDepthStack.length > 0) return;
481
+ if (SSR_UNSAFE_GLOBALS.has(node.name)) context.report({
482
+ node,
483
+ message: MESSAGE$16
484
+ });
485
+ }
486
+ };
487
+ } });
488
+ //#endregion
489
+ //#region src/rules/nuxt/server-routes/createError-on-failure.ts
490
+ const MESSAGE$15 = `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`;
491
+ const createErrorOnFailure = defineRule({ create(context) {
492
+ let handlerDepth = 0;
493
+ return {
494
+ CallExpression(node) {
495
+ const callee = node.callee;
496
+ if (callee?.type !== "Identifier") return;
497
+ if (callee.name !== "defineEventHandler") return;
498
+ handlerDepth++;
499
+ },
500
+ "CallExpression:exit"(node) {
501
+ const callee = node.callee;
502
+ if (callee?.type !== "Identifier") return;
503
+ if (callee.name !== "defineEventHandler") return;
504
+ handlerDepth--;
505
+ },
506
+ ThrowStatement(node) {
507
+ if (handlerDepth === 0) return;
508
+ const arg = node.argument;
509
+ if (!arg || arg.type !== "NewExpression") return;
510
+ const ctor = arg.callee;
511
+ if (ctor?.type !== "Identifier") return;
512
+ if (ctor.name !== "Error") return;
513
+ context.report({
514
+ node,
515
+ message: MESSAGE$15
516
+ });
517
+ }
518
+ };
519
+ } });
520
+ //#endregion
521
+ //#region src/rules/nuxt/server-routes/defineEventHandler-typed.ts
522
+ const MESSAGE$14 = `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`;
523
+ function isFunction$1(node) {
524
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
525
+ }
526
+ const defineEventHandlerTyped = defineRule({ create(context) {
527
+ return { CallExpression(node) {
528
+ const callee = node.callee;
529
+ if (callee?.type !== "Identifier") return;
530
+ if (callee.name !== "defineEventHandler") return;
531
+ if (node.typeArguments) return;
532
+ const args = node.arguments;
533
+ if (args.length === 0) return;
534
+ const handler = args[0];
535
+ if (!isFunction$1(handler)) return;
536
+ const params = handler.params;
537
+ if (!params || params.length === 0) return;
538
+ if (params[0].typeAnnotation) return;
539
+ context.report({
540
+ node,
541
+ message: MESSAGE$14
542
+ });
543
+ } };
544
+ } });
545
+ //#endregion
546
+ //#region src/rules/nuxt/server-routes/validate-body-with-h3-v2.ts
547
+ const MESSAGE$13 = `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`;
548
+ const validateBodyWithH3V2 = defineRule({ create(context) {
549
+ return { CallExpression(node) {
550
+ const callee = node.callee;
551
+ if (callee?.type !== "Identifier") return;
552
+ if (callee.name !== "readBody") return;
553
+ context.report({
554
+ node,
555
+ message: MESSAGE$13
556
+ });
557
+ } };
558
+ } });
559
+ //#endregion
560
+ //#region src/rules/nuxt/index.ts
561
+ function coreRule$1(id, category, severity, recommended, rule) {
562
+ return {
563
+ id,
564
+ category,
565
+ severity,
566
+ recommended,
567
+ ...rule
568
+ };
569
+ }
570
+ const NUXT_RULES = [
571
+ coreRule$1("ai-slop/no-process-client-server", "ai-slop", "error", true, defineRule(noProcessClientServer)),
572
+ coreRule$1("ai-slop/no-explicit-imports-of-auto-imported", "ai-slop", "warn", true, defineRule(noExplicitImportsOfAutoImported)),
573
+ coreRule$1("ai-slop/no-useState-for-server-data", "ai-slop", "warn", true, defineRule(noUseStateForServerData)),
574
+ coreRule$1("ai-slop/no-fetch-in-setup", "ai-slop", "warn", true, defineRule(noFetchInSetup)),
575
+ coreRule$1("data-fetching/useAsyncData-key-required-in-loop", "data-fetching", "error", true, defineRule(useAsyncDataKeyRequiredInLoop)),
576
+ coreRule$1("server-routes/defineEventHandler-typed", "server-routes", "warn", true, defineRule(defineEventHandlerTyped)),
577
+ coreRule$1("server-routes/validate-body-with-h3-v2", "server-routes", "warn", true, defineRule(validateBodyWithH3V2)),
578
+ coreRule$1("server-routes/createError-on-failure", "server-routes", "warn", true, defineRule(createErrorOnFailure)),
579
+ coreRule$1("hydration/no-document-in-setup", "hydration", "error", true, defineRule(noDocumentInSetup)),
580
+ coreRule$1("hydration/clientOnly-for-browser-apis", "hydration", "error", true, defineRule(clientOnlyForBrowserApis)),
581
+ coreRule$1("security/no-user-input-in-fetch-url", "security", "warn", true, defineRule(noUserInputInFetchUrl))
582
+ ];
583
+ //#endregion
584
+ //#region src/rules/vue/ai-slop/no-destructure-props-without-toRefs.ts
585
+ const MESSAGE$12 = `Destructuring props loses reactivity. Wrap with toRefs(...) to keep refs. See https://vuejs.org/guide/components/props.html#reactive-props-destructure`;
586
+ function calleeName$6(node) {
587
+ const callee = node.callee;
588
+ if (callee?.type === "Identifier") return callee.name;
589
+ }
590
+ function isDefinePropsCall(node) {
591
+ if (!node || node.type !== "CallExpression") return false;
592
+ const name = calleeName$6(node);
593
+ if (name === "defineProps") return true;
594
+ if (name === "withDefaults") {
595
+ const args = node.arguments;
596
+ return isDefinePropsCall(args?.[0]);
597
+ }
598
+ return false;
599
+ }
600
+ function isToRefsCall$1(node) {
601
+ return node?.type === "CallExpression" && calleeName$6(node) === "toRefs";
602
+ }
603
+ const noDestructurePropsWithoutToRefs = defineRule({ create(context) {
604
+ const propsSources = /* @__PURE__ */ new Set();
605
+ function registerSetupParam(fn) {
606
+ const first = (fn?.params)?.[0];
607
+ if (first?.type === "Identifier") propsSources.add(first.name);
608
+ }
609
+ return {
610
+ VariableDeclarator(node) {
611
+ const id = node.id;
612
+ const init = node.init;
613
+ if (id.type === "Identifier" && isDefinePropsCall(init)) {
614
+ propsSources.add(id.name);
615
+ return;
616
+ }
617
+ if (id.type !== "ObjectPattern" || !init) return;
618
+ if (isToRefsCall$1(init)) return;
619
+ if (isDefinePropsCall(init) || init.type === "Identifier" && propsSources.has(init.name)) context.report({
620
+ node,
621
+ message: MESSAGE$12
622
+ });
623
+ },
624
+ Property(node) {
625
+ const key = node.key;
626
+ if (key?.type === "Identifier" && key.name === "setup") registerSetupParam(node.value);
627
+ }
628
+ };
629
+ } });
630
+ //#endregion
631
+ //#region src/rules/vue/ai-slop/no-destructure-reactive-without-toRefs.ts
632
+ const MESSAGE$11 = `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`;
633
+ const REACTIVE_FACTORIES = new Set(["reactive", "shallowReactive"]);
634
+ function calleeName$5(node) {
635
+ const callee = node.callee;
636
+ if (callee?.type === "Identifier") return callee.name;
637
+ }
638
+ function isReactiveCall(node) {
639
+ if (!node || node.type !== "CallExpression") return false;
640
+ const name = calleeName$5(node);
641
+ if (name && REACTIVE_FACTORIES.has(name)) return true;
642
+ if (name === "readonly") {
643
+ const args = node.arguments;
644
+ return isReactiveCall(args?.[0]);
645
+ }
646
+ return false;
647
+ }
648
+ function isToRefsCall(node) {
649
+ return node?.type === "CallExpression" && calleeName$5(node) === "toRefs";
650
+ }
651
+ const noDestructureReactiveWithoutToRefs = defineRule({ create(context) {
652
+ const reactiveSources = /* @__PURE__ */ new Set();
653
+ return { VariableDeclarator(node) {
654
+ const id = node.id;
655
+ const init = node.init;
656
+ if (id.type === "Identifier" && isReactiveCall(init)) {
657
+ reactiveSources.add(id.name);
658
+ return;
659
+ }
660
+ if (id.type !== "ObjectPattern" || !init) return;
661
+ if (isToRefsCall(init)) return;
662
+ if (isReactiveCall(init) || init.type === "Identifier" && reactiveSources.has(init.name)) context.report({
663
+ node,
664
+ message: MESSAGE$11
665
+ });
666
+ } };
667
+ } });
668
+ //#endregion
669
+ //#region src/rules/vue/ai-slop/no-em-dash-in-string.ts
670
+ const EM_DASH = "—";
671
+ const noEmDashInString = defineRule({
672
+ create(context) {
673
+ return { Literal(node) {
674
+ const literal = node;
675
+ if (typeof literal.value !== "string") return;
676
+ if (!literal.value.includes(EM_DASH)) return;
677
+ context.report({
678
+ node,
679
+ message: "Em dash in string literal reads as AI-generated output; use comma, colon, or parentheses."
680
+ });
681
+ } };
682
+ },
683
+ fix(node) {
684
+ const literal = node;
685
+ if (typeof literal.value !== "string") return null;
686
+ const raw = literal.raw;
687
+ if (typeof raw !== "string") return null;
688
+ if (!raw.includes(EM_DASH)) return null;
689
+ return raw.split(EM_DASH).join("-");
690
+ }
691
+ });
692
+ //#endregion
693
+ //#region src/rules/vue/ai-slop/no-imports-from-vue-when-auto-imported.ts
694
+ const DOCS_URL = "https://nuxt.com/docs/4.x/guide/concepts/auto-imports";
695
+ const WHOLE_MESSAGE = `The entire import from 'vue' can be removed — these names are auto-imported in this project. See ${DOCS_URL}`;
696
+ 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}`;
697
+ function isAutoImportedValueSpecifier(spec) {
698
+ if (spec.importKind === "type") return false;
699
+ const imported = spec.imported;
700
+ return VUE_AUTO_IMPORTED.has(imported.name);
701
+ }
702
+ const noImportsFromVueWhenAutoImported = defineRule({ create(context) {
703
+ let gated = false;
704
+ return {
705
+ Program() {
706
+ gated = context.capabilities?.has("auto-imports:vue") !== true;
707
+ },
708
+ ImportDeclaration(node) {
709
+ if (gated) return;
710
+ if (node.importKind === "type") return;
711
+ if (node.source.value !== "vue") return;
712
+ const specifiers = node.specifiers;
713
+ const named = specifiers.filter((s) => s.type === "ImportSpecifier");
714
+ if (named.length === 0) return;
715
+ const offending = named.filter(isAutoImportedValueSpecifier);
716
+ if (offending.length === 0) return;
717
+ if (offending.length === named.length && specifiers.length === named.length) {
718
+ context.report({
719
+ node,
720
+ message: WHOLE_MESSAGE
721
+ });
722
+ return;
723
+ }
724
+ for (const spec of offending) context.report({
725
+ node: spec,
726
+ message: SPECIFIER_MESSAGE
727
+ });
728
+ }
729
+ };
730
+ } });
731
+ //#endregion
732
+ //#region src/rules/vue/ai-slop/no-non-null-assertion-on-ref-value.ts
733
+ const MESSAGE$10 = `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`;
734
+ const REF_FACTORIES = new Set([
735
+ "ref",
736
+ "shallowRef",
737
+ "customRef",
738
+ "computed",
739
+ "useTemplateRef"
740
+ ]);
741
+ function calleeName$4(node) {
742
+ const callee = node.callee;
743
+ if (callee?.type === "Identifier") return callee.name;
744
+ }
745
+ function isRefFactoryCall(node) {
746
+ if (!node || node.type !== "CallExpression") return false;
747
+ const name = calleeName$4(node);
748
+ return name !== void 0 && REF_FACTORIES.has(name);
749
+ }
750
+ function isDotValue(node) {
751
+ if (!node || node.type !== "MemberExpression") return false;
752
+ const property = node.property;
753
+ return node.computed !== true && property?.type === "Identifier" && property.name === "value";
754
+ }
755
+ const noNonNullAssertionOnRefValue = defineRule({ create(context) {
756
+ const refSources = /* @__PURE__ */ new Set();
757
+ return {
758
+ VariableDeclarator(node) {
759
+ const id = node.id;
760
+ if (id.type === "Identifier" && isRefFactoryCall(node.init)) refSources.add(id.name);
761
+ },
762
+ TSNonNullExpression(node) {
763
+ const arg = node.expression;
764
+ if (!isDotValue(arg)) return;
765
+ const object = arg.object;
766
+ if (object?.type !== "Identifier") return;
767
+ if (!refSources.has(object.name)) return;
768
+ context.report({
769
+ node,
770
+ message: MESSAGE$10
771
+ });
772
+ }
773
+ };
774
+ } });
775
+ //#endregion
776
+ //#region src/rules/vue/composition/defineProps-typed.ts
777
+ const MESSAGE$9 = `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`;
778
+ function calleeName$3(node) {
779
+ const callee = node.callee;
780
+ if (callee?.type === "Identifier") return callee.name;
781
+ }
782
+ const definePropsTyped = defineRule({ create(context) {
783
+ return { CallExpression(node) {
784
+ if (calleeName$3(node) !== "defineProps") return;
785
+ if (node.arguments[0]?.type === "ObjectExpression") context.report({
786
+ node,
787
+ message: MESSAGE$9
788
+ });
789
+ } };
790
+ } });
791
+ //#endregion
792
+ //#region src/rules/vue/composition/prefer-script-setup-for-new-files.ts
793
+ const MESSAGE$8 = `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`;
794
+ function isFunctionValue(node) {
795
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
796
+ }
797
+ function isSetupProperty(prop) {
798
+ if (prop.type !== "Property" || prop.computed === true) return false;
799
+ if (prop.shorthand === true) return false;
800
+ const key = prop.key;
801
+ if (key.type !== "Identifier" || key.name !== "setup") return false;
802
+ return isFunctionValue(prop.value);
803
+ }
804
+ const preferScriptSetupForNewFiles = defineRule({ create(context) {
805
+ return { ExportDefaultDeclaration(node) {
806
+ const declaration = node.declaration;
807
+ if (declaration?.type !== "ObjectExpression") return;
808
+ if (declaration.properties.some(isSetupProperty)) context.report({
809
+ node,
810
+ message: MESSAGE$8
811
+ });
812
+ } };
813
+ } });
814
+ //#endregion
815
+ //#region src/rules/vue/performance/prefer-defineAsyncComponent-on-route.ts
816
+ const MESSAGE$7 = `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`;
817
+ function keyName(key) {
818
+ return key.type === "Identifier" ? key.name : key.value;
819
+ }
820
+ const preferDefineAsyncComponentOnRoute = defineRule({ create(context) {
821
+ return { Property(node) {
822
+ if (node.computed === true || node.shorthand === true) return;
823
+ if (keyName(node.key) !== "component") return;
824
+ if (node.value.type === "Identifier") context.report({
825
+ node,
826
+ message: MESSAGE$7
827
+ });
828
+ } };
829
+ } });
830
+ //#endregion
831
+ //#region src/rules/vue/reactivity/prefer-readonly-for-injected.ts
832
+ const MESSAGE$6 = `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`;
833
+ const MUTATING_METHODS = new Set([
834
+ "push",
835
+ "pop",
836
+ "splice",
837
+ "shift",
838
+ "unshift",
839
+ "sort",
840
+ "reverse"
841
+ ]);
842
+ function calleeName$2(node) {
843
+ const callee = node.callee;
844
+ if (callee?.type === "Identifier") return callee.name;
845
+ }
846
+ function memberObjectName(node) {
847
+ if (!node || node.type !== "MemberExpression") return void 0;
848
+ const object = node.object;
849
+ if (object.type !== "Identifier") return void 0;
850
+ return object.name;
851
+ }
852
+ const preferReadonlyForInjected = defineRule({ create(context) {
853
+ const injectedSources = /* @__PURE__ */ new Set();
854
+ return {
855
+ VariableDeclarator(node) {
856
+ const id = node.id;
857
+ const init = node.init;
858
+ if (id.type === "Identifier" && init?.type === "CallExpression" && calleeName$2(init) === "inject") injectedSources.add(id.name);
859
+ },
860
+ AssignmentExpression(node) {
861
+ const name = memberObjectName(node.left);
862
+ if (name !== void 0 && injectedSources.has(name)) context.report({
863
+ node,
864
+ message: MESSAGE$6
865
+ });
866
+ },
867
+ CallExpression(node) {
868
+ const callee = node.callee;
869
+ if (callee?.type !== "MemberExpression" || callee.computed === true) return;
870
+ const name = memberObjectName(callee);
871
+ if (name === void 0 || !injectedSources.has(name)) return;
872
+ const property = callee.property;
873
+ if (MUTATING_METHODS.has(property.name)) context.report({
874
+ node,
875
+ message: MESSAGE$6
876
+ });
877
+ }
878
+ };
879
+ } });
880
+ //#endregion
881
+ //#region src/rules/vue/reactivity/prefer-shallowRef-for-large-data.ts
882
+ const MESSAGE$5 = `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`;
883
+ const ARRAY_LIMIT = 100;
884
+ const OBJECT_LIMIT = 50;
885
+ const FETCH_IDENTIFIERS = new Set(["$fetch", "useFetch"]);
886
+ function calleeName$1(node) {
887
+ const callee = node.callee;
888
+ if (callee?.type === "Identifier") return callee.name;
889
+ }
890
+ function isAxiosGet(node) {
891
+ const callee = node.callee;
892
+ if (callee?.type !== "MemberExpression" || callee.computed === true) return false;
893
+ const object = callee.object;
894
+ const property = callee.property;
895
+ return object.type === "Identifier" && object.name === "axios" && property.name === "get";
896
+ }
897
+ function isFetchSource(node) {
898
+ if (node.type !== "CallExpression") return false;
899
+ const name = calleeName$1(node);
900
+ if (name !== void 0 && FETCH_IDENTIFIERS.has(name)) return true;
901
+ return isAxiosGet(node);
902
+ }
903
+ function isLargeData(node) {
904
+ if (!node) return false;
905
+ const value = node.type === "AwaitExpression" ? node.argument : node;
906
+ if (value.type === "ArrayExpression") return value.elements.length > ARRAY_LIMIT;
907
+ if (value.type === "ObjectExpression") return value.properties.length > OBJECT_LIMIT;
908
+ return isFetchSource(value);
909
+ }
910
+ const preferShallowRefForLargeData = defineRule({ create(context) {
911
+ return { CallExpression(node) {
912
+ if (calleeName$1(node) !== "ref") return;
913
+ const init = node.arguments[0];
914
+ if (isLargeData(init)) context.report({
915
+ node,
916
+ message: MESSAGE$5
917
+ });
918
+ } };
919
+ } });
920
+ //#endregion
921
+ //#region src/rules/vue/reactivity/watch-without-cleanup.ts
922
+ const MESSAGE$4 = `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`;
923
+ const REGISTER_CALLS = new Set([
924
+ "addEventListener",
925
+ "setInterval",
926
+ "setTimeout"
927
+ ]);
928
+ const OBSERVERS = new Set([
929
+ "IntersectionObserver",
930
+ "MutationObserver",
931
+ "ResizeObserver"
932
+ ]);
933
+ const CLEANUP_CALLS = new Set(["onCleanup", "onWatcherCleanup"]);
934
+ function calleeIdentifierName(node) {
935
+ const callee = node.callee;
936
+ if (callee?.type === "Identifier") return callee.name;
937
+ }
938
+ function calledMethodName(node) {
939
+ const callee = node.callee;
940
+ if (callee?.type === "Identifier") return callee.name;
941
+ if (callee?.type === "MemberExpression" && callee.computed !== true) return callee.property.name;
942
+ }
943
+ function isFunction(node) {
944
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
945
+ }
946
+ const watchWithoutCleanup = defineRule({ create(context) {
947
+ const watchStack = [];
948
+ return {
949
+ CallExpression(node) {
950
+ const name = calleeIdentifierName(node);
951
+ if (name === "watch" || name === "watchEffect") {
952
+ const isEffect = name === "watchEffect";
953
+ const callback = node.arguments[isEffect ? 0 : 1];
954
+ if (!isFunction(callback)) return;
955
+ watchStack.push({
956
+ sourceCall: node,
957
+ isEffect,
958
+ hasRegistration: false,
959
+ hasCleanup: false
960
+ });
961
+ return;
962
+ }
963
+ if (watchStack.length === 0) return;
964
+ const top = watchStack[watchStack.length - 1];
965
+ const method = calledMethodName(node);
966
+ if (method !== void 0 && REGISTER_CALLS.has(method)) top.hasRegistration = true;
967
+ if (method !== void 0 && CLEANUP_CALLS.has(method) && top.isEffect) top.hasCleanup = true;
968
+ },
969
+ NewExpression(node) {
970
+ if (watchStack.length === 0) return;
971
+ const callee = node.callee;
972
+ if (callee?.type === "Identifier" && OBSERVERS.has(callee.name)) watchStack[watchStack.length - 1].hasRegistration = true;
973
+ },
974
+ ReturnStatement(node) {
975
+ if (watchStack.length === 0) return;
976
+ if (isFunction(node.argument)) watchStack[watchStack.length - 1].hasCleanup = true;
977
+ },
978
+ "CallExpression:exit"(node) {
979
+ if (watchStack.length === 0) return;
980
+ const top = watchStack[watchStack.length - 1];
981
+ if (top.sourceCall !== node) return;
982
+ watchStack.pop();
983
+ if (top.hasRegistration && !top.hasCleanup) context.report({
984
+ node,
985
+ message: MESSAGE$4
986
+ });
987
+ }
988
+ };
989
+ } });
990
+ //#endregion
991
+ //#region src/rules/vue/security/no-auth-token-in-web-storage.ts
992
+ const MESSAGE$3 = `Persisting auth tokens/secrets in localStorage or sessionStorage exposes them to any XSS payload. Store them in an HttpOnly, Secure, SameSite cookie set by the server instead. See https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage`;
993
+ const STORAGES = new Set(["localStorage", "sessionStorage"]);
994
+ const SENSITIVE_KEY = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key|auth/i;
995
+ function stringValue$1(node) {
996
+ if (node?.type === "Literal" && typeof node.value === "string") return node.value;
997
+ }
998
+ function isStorageObject(node) {
999
+ return node?.type === "Identifier" && STORAGES.has(node.name);
1000
+ }
1001
+ function memberKey(node) {
1002
+ const property = node.property;
1003
+ if (node.computed === true) return stringValue$1(property);
1004
+ return property.name;
1005
+ }
1006
+ const noAuthTokenInWebStorage = defineRule({ create(context) {
1007
+ return {
1008
+ CallExpression(node) {
1009
+ const callee = node.callee;
1010
+ if (callee.type !== "MemberExpression") return;
1011
+ if (callee.computed === true) return;
1012
+ if (!isStorageObject(callee.object)) return;
1013
+ if (callee.property.name !== "setItem") return;
1014
+ const key = stringValue$1(node.arguments[0]);
1015
+ if (key !== void 0 && SENSITIVE_KEY.test(key)) context.report({
1016
+ node,
1017
+ message: MESSAGE$3
1018
+ });
1019
+ },
1020
+ AssignmentExpression(node) {
1021
+ const left = node.left;
1022
+ if (left.type !== "MemberExpression") return;
1023
+ if (!isStorageObject(left.object)) return;
1024
+ const key = memberKey(left);
1025
+ if (key !== void 0 && SENSITIVE_KEY.test(key)) context.report({
1026
+ node,
1027
+ message: MESSAGE$3
1028
+ });
1029
+ }
1030
+ };
1031
+ } });
1032
+ //#endregion
1033
+ //#region src/rules/vue/security/no-eval-like.ts
1034
+ const MESSAGE$2 = `eval, new Function, and string-argument setTimeout/setInterval execute arbitrary code and are XSS/RCE vectors. Replace with explicit logic or JSON.parse for data. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!`;
1035
+ const STRING_TIMER_CALLEES = new Set(["setTimeout", "setInterval"]);
1036
+ function calleeName(node) {
1037
+ const callee = node.callee;
1038
+ if (callee?.type === "Identifier") return callee.name;
1039
+ }
1040
+ function isStringArg(node) {
1041
+ if (!node) return false;
1042
+ if (node.type === "Literal") return typeof node.value === "string";
1043
+ return node.type === "TemplateLiteral";
1044
+ }
1045
+ const noEvalLike = defineRule({ create(context) {
1046
+ return {
1047
+ CallExpression(node) {
1048
+ const name = calleeName(node);
1049
+ if (name === void 0) return;
1050
+ if (name === "eval") {
1051
+ context.report({
1052
+ node,
1053
+ message: MESSAGE$2
1054
+ });
1055
+ return;
1056
+ }
1057
+ if (STRING_TIMER_CALLEES.has(name)) {
1058
+ const args = node.arguments;
1059
+ if (isStringArg(args?.[0])) context.report({
1060
+ node,
1061
+ message: MESSAGE$2
1062
+ });
1063
+ }
1064
+ },
1065
+ NewExpression(node) {
1066
+ const callee = node.callee;
1067
+ if (callee?.type === "Identifier" && callee.name === "Function") context.report({
1068
+ node,
1069
+ message: MESSAGE$2
1070
+ });
1071
+ }
1072
+ };
1073
+ } });
1074
+ //#endregion
1075
+ //#region src/rules/vue/security/no-inner-html.ts
1076
+ const MESSAGE$1 = `Assigning to innerHTML/outerHTML injects unsanitized markup and is a DOM-based XSS sink. Use textContent for text, or sanitize with DOMPurify before assigning. See https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations`;
1077
+ const HTML_SINKS = new Set(["innerHTML", "outerHTML"]);
1078
+ function isHtmlSinkTarget(node) {
1079
+ if (node.type !== "MemberExpression") return false;
1080
+ if (node.computed === true) return false;
1081
+ const property = node.property;
1082
+ return property.type === "Identifier" && HTML_SINKS.has(property.name);
1083
+ }
1084
+ const noInnerHtml = defineRule({ create(context) {
1085
+ return { AssignmentExpression(node) {
1086
+ if (!isHtmlSinkTarget(node.left)) return;
1087
+ context.report({
1088
+ node,
1089
+ message: MESSAGE$1
1090
+ });
1091
+ } };
1092
+ } });
1093
+ //#endregion
1094
+ //#region src/rules/vue/security/no-secrets-in-source.ts
1095
+ const MESSAGE = `Hardcoded secret in source. Committed secrets leak in the client bundle and git history. Move it to an environment variable or a server-only runtime config. See https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html`;
1096
+ const SECRET_VALUE_PATTERNS = [
1097
+ /\bsk-[A-Za-z0-9-]{10,}\b/,
1098
+ /\bghp_[A-Za-z0-9]{20,}\b/,
1099
+ /\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
1100
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
1101
+ /\bAKIA[0-9A-Z]{16}\b/,
1102
+ /\bAIza[0-9A-Za-z_-]{20,}\b/
1103
+ ];
1104
+ const SECRET_NAME = /^(api[-_]?secret|api[-_]?key|secret[-_]?key|private[-_]?key|access[-_]?token|auth[-_]?token|client[-_]?secret|password|passwd|jwt[-_]?secret)$/i;
1105
+ const MIN_NAMED_SECRET_LENGTH = 8;
1106
+ function stringValue(node) {
1107
+ if (node?.type === "Literal" && typeof node.value === "string") return node.value;
1108
+ }
1109
+ function isHighSignalSecret(value) {
1110
+ return SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value));
1111
+ }
1112
+ const noSecretsInSource = defineRule({ create(context) {
1113
+ const reported = /* @__PURE__ */ new Set();
1114
+ function report(node) {
1115
+ if (reported.has(node)) return;
1116
+ reported.add(node);
1117
+ context.report({
1118
+ node,
1119
+ message: MESSAGE
1120
+ });
1121
+ }
1122
+ function checkNamedAssignment(name, valueNode) {
1123
+ if (name === void 0 || !SECRET_NAME.test(name)) return;
1124
+ if (!valueNode) return;
1125
+ const value = stringValue(valueNode);
1126
+ if (value === void 0 || value.length < MIN_NAMED_SECRET_LENGTH) return;
1127
+ report(valueNode);
1128
+ }
1129
+ return {
1130
+ Literal(node) {
1131
+ if (typeof node.value !== "string") return;
1132
+ if (isHighSignalSecret(node.value)) report(node);
1133
+ },
1134
+ VariableDeclarator(node) {
1135
+ const id = node.id;
1136
+ if (id.type !== "Identifier") return;
1137
+ checkNamedAssignment(id.name, node.init);
1138
+ },
1139
+ Property(node) {
1140
+ const key = node.key;
1141
+ checkNamedAssignment(key.type === "Identifier" ? key.name : stringValue(key), node.value);
1142
+ }
1143
+ };
1144
+ } });
1145
+ //#endregion
1146
+ //#region src/rules/vue/index.ts
1147
+ function coreRule(id, category, severity, recommended, rule) {
1148
+ return {
1149
+ id,
1150
+ category,
1151
+ severity,
1152
+ recommended,
1153
+ ...rule
1154
+ };
1155
+ }
1156
+ const VUE_RULES = [
1157
+ coreRule("no-em-dash-in-string", "ai-slop", "warn", true, defineRule(noEmDashInString)),
1158
+ coreRule("no-destructure-props-without-to-refs", "ai-slop", "error", true, defineRule(noDestructurePropsWithoutToRefs)),
1159
+ coreRule("no-destructure-reactive-without-to-refs", "ai-slop", "error", true, defineRule(noDestructureReactiveWithoutToRefs)),
1160
+ coreRule("no-non-null-assertion-on-ref-value", "ai-slop", "warn", true, defineRule(noNonNullAssertionOnRefValue)),
1161
+ coreRule("no-imports-from-vue-when-auto-imported", "ai-slop", "warn", true, defineRule(noImportsFromVueWhenAutoImported)),
1162
+ coreRule("reactivity/watch-without-cleanup", "reactivity", "warn", true, defineRule(watchWithoutCleanup)),
1163
+ coreRule("reactivity/prefer-shallowRef-for-large-data", "reactivity", "info", false, defineRule(preferShallowRefForLargeData)),
1164
+ coreRule("reactivity/prefer-readonly-for-injected", "reactivity", "info", false, defineRule(preferReadonlyForInjected)),
1165
+ coreRule("composition/prefer-script-setup-for-new-files", "composition", "warn", true, defineRule(preferScriptSetupForNewFiles)),
1166
+ coreRule("composition/defineProps-typed", "composition", "warn", true, defineRule(definePropsTyped)),
1167
+ coreRule("performance/prefer-defineAsyncComponent-on-route", "performance", "info", false, defineRule(preferDefineAsyncComponentOnRoute)),
1168
+ coreRule("security/no-inner-html", "security", "error", true, defineRule(noInnerHtml)),
1169
+ coreRule("security/no-eval-like", "security", "error", true, defineRule(noEvalLike)),
1170
+ coreRule("security/no-auth-token-in-web-storage", "security", "warn", true, defineRule(noAuthTokenInWebStorage)),
1171
+ coreRule("security/no-secrets-in-source", "security", "warn", true, defineRule(noSecretsInSource))
1172
+ ];
1173
+ //#endregion
1174
+ export { NUXT_AUTO_IMPORTED, NUXT_RULES, VUE_AUTO_IMPORTED, VUE_RULES, clientOnlyForBrowserApis, createErrorOnFailure, defineEventHandlerTyped, definePropsTyped, defineRule, noAuthTokenInWebStorage, noDestructurePropsWithoutToRefs, noDestructureReactiveWithoutToRefs, noDocumentInSetup, noEmDashInString, noEvalLike, noExplicitImportsOfAutoImported, noFetchInSetup, noImportsFromVueWhenAutoImported, noInnerHtml, noNonNullAssertionOnRefValue, noProcessClientServer, noSecretsInSource, noUseStateForServerData, noUserInputInFetchUrl, preferDefineAsyncComponentOnRoute, preferReadonlyForInjected, preferScriptSetupForNewFiles, preferShallowRefForLargeData, useAsyncDataKeyRequiredInLoop, validateBodyWithH3V2, watchWithoutCleanup };
1175
+
1176
+ //# sourceMappingURL=index.js.map