@geoql/doctor-rule-core 1.3.0 → 1.4.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 CHANGED
@@ -133,7 +133,7 @@ const noExplicitImportsOfAutoImported = defineRule({ create(context) {
133
133
  } });
134
134
  //#endregion
135
135
  //#region src/rules/nuxt/ai-slop/no-fetch-in-setup.ts
136
- const MESSAGE$31 = `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`;
136
+ const MESSAGE$32 = `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
137
  function isFetchCall(node) {
138
138
  if (!node || node.type !== "CallExpression") return false;
139
139
  const callee = node.callee;
@@ -170,7 +170,7 @@ const noFetchInSetup = defineRule({ create(context) {
170
170
  if (node["parent"]?.type === "AwaitExpression") return;
171
171
  context.report({
172
172
  node,
173
- message: MESSAGE$31
173
+ message: MESSAGE$32
174
174
  });
175
175
  },
176
176
  AwaitExpression(node) {
@@ -178,7 +178,7 @@ const noFetchInSetup = defineRule({ create(context) {
178
178
  if (!isFetchCall(node.argument)) return;
179
179
  context.report({
180
180
  node,
181
- message: MESSAGE$31
181
+ message: MESSAGE$32
182
182
  });
183
183
  }
184
184
  };
@@ -190,7 +190,7 @@ const LEGACY_PROPS = /* @__PURE__ */ new Set([
190
190
  "server",
191
191
  "browser"
192
192
  ]);
193
- const MESSAGE$30 = `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`;
193
+ const MESSAGE$31 = `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
194
  const noProcessClientServer = defineRule({
195
195
  create(context) {
196
196
  return { MemberExpression(node) {
@@ -202,7 +202,7 @@ const noProcessClientServer = defineRule({
202
202
  if (!LEGACY_PROPS.has(property.name)) return;
203
203
  context.report({
204
204
  node,
205
- message: MESSAGE$30
205
+ message: MESSAGE$31
206
206
  });
207
207
  } };
208
208
  },
@@ -219,7 +219,7 @@ const noProcessClientServer = defineRule({
219
219
  });
220
220
  //#endregion
221
221
  //#region src/rules/nuxt/ai-slop/no-useState-for-server-data.ts
222
- const MESSAGE$29 = `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`;
222
+ const MESSAGE$30 = `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
223
  function containsFetchOrAwait(node, visited) {
224
224
  if (!node || visited.has(node)) return false;
225
225
  visited.add(node);
@@ -256,13 +256,13 @@ const noUseStateForServerData = defineRule({ create(context) {
256
256
  if (!containsFetchOrAwait(initFn.body, /* @__PURE__ */ new Set())) return;
257
257
  context.report({
258
258
  node,
259
- message: MESSAGE$29
259
+ message: MESSAGE$30
260
260
  });
261
261
  } };
262
262
  } });
263
263
  //#endregion
264
264
  //#region src/rules/nuxt/data-fetching/useAsyncData-key-required-in-loop.ts
265
- const MESSAGE$28 = `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`;
265
+ const MESSAGE$29 = `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
266
  const DATA_FETCHERS = /* @__PURE__ */ new Set(["useAsyncData", "useFetch"]);
267
267
  function isMapCall(node) {
268
268
  const callee = node.callee;
@@ -315,7 +315,7 @@ const useAsyncDataKeyRequiredInLoop = defineRule({ create(context) {
315
315
  if (firstArg.type === "Literal" && typeof firstArg.value === "string") return;
316
316
  context.report({
317
317
  node,
318
- message: MESSAGE$28
318
+ message: MESSAGE$29
319
319
  });
320
320
  },
321
321
  "CallExpression:exit"(node) {
@@ -325,7 +325,7 @@ const useAsyncDataKeyRequiredInLoop = defineRule({ create(context) {
325
325
  } });
326
326
  //#endregion
327
327
  //#region src/rules/nuxt/security/no-user-input-in-fetch-url.ts
328
- const MESSAGE$27 = `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`;
328
+ const MESSAGE$28 = `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
329
  const FETCH_CALLEES = /* @__PURE__ */ new Set([
330
330
  "useFetch",
331
331
  "useLazyFetch",
@@ -363,13 +363,13 @@ const noUserInputInFetchUrl = defineRule({ create(context) {
363
363
  const urlArg = node.arguments[0];
364
364
  if (urlArgIsTainted(urlArg)) context.report({
365
365
  node,
366
- message: MESSAGE$27
366
+ message: MESSAGE$28
367
367
  });
368
368
  } };
369
369
  } });
370
370
  //#endregion
371
371
  //#region src/rules/nuxt/hydration/clientOnly-for-browser-apis.ts
372
- const MESSAGE$26 = `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`;
372
+ const MESSAGE$27 = `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
373
  const BROWSER_GLOBALS$1 = /* @__PURE__ */ new Set([
374
374
  "window",
375
375
  "document",
@@ -440,14 +440,14 @@ const clientOnlyForBrowserApis = defineRule({ create(context) {
440
440
  if (!BROWSER_GLOBALS$1.has(obj.name)) return;
441
441
  context.report({
442
442
  node,
443
- message: MESSAGE$26
443
+ message: MESSAGE$27
444
444
  });
445
445
  }
446
446
  };
447
447
  } });
448
448
  //#endregion
449
449
  //#region src/rules/nuxt/hydration/no-browser-global-in-computed.ts
450
- const MESSAGE$25 = `Reading a browser global inside a computed getter runs during SSR (computeds evaluate on the server render) and throws or hydration-mismatches. Guard with import.meta.client or derive the value in onMounted. See https://nuxt.com/docs/4.x/getting-started/data-fetching`;
450
+ const MESSAGE$26 = `Reading a browser global inside a computed getter runs during SSR (computeds evaluate on the server render) and throws or hydration-mismatches. Guard with import.meta.client or derive the value in onMounted. See https://nuxt.com/docs/4.x/getting-started/data-fetching`;
451
451
  const BROWSER_GLOBALS = /* @__PURE__ */ new Set([
452
452
  "window",
453
453
  "document",
@@ -501,14 +501,14 @@ const noBrowserGlobalInComputed = defineRule({ create(context) {
501
501
  if (!BROWSER_GLOBALS.has(object.name)) return;
502
502
  context.report({
503
503
  node,
504
- message: MESSAGE$25
504
+ message: MESSAGE$26
505
505
  });
506
506
  }
507
507
  };
508
508
  } });
509
509
  //#endregion
510
510
  //#region src/rules/nuxt/hydration/no-document-in-setup.ts
511
- const MESSAGE$24 = `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`;
511
+ const MESSAGE$25 = `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`;
512
512
  const SSR_UNSAFE_GLOBALS = /* @__PURE__ */ new Set([
513
513
  "document",
514
514
  "window",
@@ -588,14 +588,14 @@ const noDocumentInSetup = defineRule({ create(context) {
588
588
  if (isGuardedByImportMetaClient(node)) return;
589
589
  context.report({
590
590
  node,
591
- message: MESSAGE$24
591
+ message: MESSAGE$25
592
592
  });
593
593
  }
594
594
  };
595
595
  } });
596
596
  //#endregion
597
597
  //#region src/rules/nuxt/server-routes/createError-on-failure.ts
598
- const MESSAGE$23 = `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`;
598
+ const MESSAGE$24 = `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`;
599
599
  const createErrorOnFailure = defineRule({ create(context) {
600
600
  let handlerDepth = 0;
601
601
  return {
@@ -620,14 +620,14 @@ const createErrorOnFailure = defineRule({ create(context) {
620
620
  if (ctor.name !== "Error") return;
621
621
  context.report({
622
622
  node,
623
- message: MESSAGE$23
623
+ message: MESSAGE$24
624
624
  });
625
625
  }
626
626
  };
627
627
  } });
628
628
  //#endregion
629
629
  //#region src/rules/nuxt/server-routes/defineEventHandler-typed.ts
630
- const MESSAGE$22 = `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`;
630
+ const MESSAGE$23 = `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`;
631
631
  function isFunction$2(node) {
632
632
  return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
633
633
  }
@@ -646,13 +646,13 @@ const defineEventHandlerTyped = defineRule({ create(context) {
646
646
  if (params[0].typeAnnotation) return;
647
647
  context.report({
648
648
  node,
649
- message: MESSAGE$22
649
+ message: MESSAGE$23
650
650
  });
651
651
  } };
652
652
  } });
653
653
  //#endregion
654
654
  //#region src/rules/nuxt/server-routes/validate-body-with-h3-v2.ts
655
- const MESSAGE$21 = `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`;
655
+ const MESSAGE$22 = `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`;
656
656
  const validateBodyWithH3V2 = defineRule({ create(context) {
657
657
  return { CallExpression(node) {
658
658
  const callee = node.callee;
@@ -660,7 +660,7 @@ const validateBodyWithH3V2 = defineRule({ create(context) {
660
660
  if (callee.name !== "readBody") return;
661
661
  context.report({
662
662
  node,
663
- message: MESSAGE$21
663
+ message: MESSAGE$22
664
664
  });
665
665
  } };
666
666
  } });
@@ -691,7 +691,7 @@ const NUXT_RULES = [
691
691
  ];
692
692
  //#endregion
693
693
  //#region src/rules/vue/ai-slop/no-destructure-props-without-toRefs.ts
694
- const MESSAGE$20 = `Destructuring props loses reactivity. Wrap with toRefs(...) to keep refs. See https://vuejs.org/guide/components/props.html#reactive-props-destructure`;
694
+ const MESSAGE$21 = `Destructuring props loses reactivity. Wrap with toRefs(...) to keep refs. See https://vuejs.org/guide/components/props.html#reactive-props-destructure`;
695
695
  function calleeName$6(node) {
696
696
  const callee = node.callee;
697
697
  if (callee?.type === "Identifier") return callee.name;
@@ -747,7 +747,7 @@ const noDestructurePropsWithoutToRefs = defineRule({ create(context) {
747
747
  if (isToRefsCall$1(init)) return;
748
748
  if ((isDefinePropsCall(init) || init.type === "Identifier" && propsSources.has(init.name)) && isOneTimeContext()) context.report({
749
749
  node,
750
- message: MESSAGE$20
750
+ message: MESSAGE$21
751
751
  });
752
752
  },
753
753
  Property(node) {
@@ -762,7 +762,7 @@ const noDestructurePropsWithoutToRefs = defineRule({ create(context) {
762
762
  } });
763
763
  //#endregion
764
764
  //#region src/rules/vue/ai-slop/no-destructure-reactive-without-toRefs.ts
765
- const MESSAGE$19 = `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`;
765
+ const MESSAGE$20 = `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`;
766
766
  const REACTIVE_FACTORIES = /* @__PURE__ */ new Set(["reactive", "shallowReactive"]);
767
767
  function calleeName$5(node) {
768
768
  const callee = node.callee;
@@ -794,7 +794,7 @@ const noDestructureReactiveWithoutToRefs = defineRule({ create(context) {
794
794
  if (isToRefsCall(init)) return;
795
795
  if (isReactiveCall(init) || init.type === "Identifier" && reactiveSources.has(init.name)) context.report({
796
796
  node,
797
- message: MESSAGE$19
797
+ message: MESSAGE$20
798
798
  });
799
799
  } };
800
800
  } });
@@ -863,7 +863,7 @@ const noImportsFromVueWhenAutoImported = defineRule({ create(context) {
863
863
  } });
864
864
  //#endregion
865
865
  //#region src/rules/vue/ai-slop/no-non-null-assertion-on-ref-value.ts
866
- const MESSAGE$18 = `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`;
866
+ const MESSAGE$19 = `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`;
867
867
  const REF_FACTORIES = /* @__PURE__ */ new Set([
868
868
  "ref",
869
869
  "shallowRef",
@@ -900,14 +900,14 @@ const noNonNullAssertionOnRefValue = defineRule({ create(context) {
900
900
  if (!refSources.has(object.name)) return;
901
901
  context.report({
902
902
  node,
903
- message: MESSAGE$18
903
+ message: MESSAGE$19
904
904
  });
905
905
  }
906
906
  };
907
907
  } });
908
908
  //#endregion
909
909
  //#region src/rules/vue/composition/defineProps-typed.ts
910
- const MESSAGE$17 = `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`;
910
+ const MESSAGE$18 = `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`;
911
911
  function calleeName$3(node) {
912
912
  const callee = node.callee;
913
913
  if (callee?.type === "Identifier") return callee.name;
@@ -917,13 +917,13 @@ const definePropsTyped = defineRule({ create(context) {
917
917
  if (calleeName$3(node) !== "defineProps") return;
918
918
  if (node.arguments[0]?.type === "ObjectExpression") context.report({
919
919
  node,
920
- message: MESSAGE$17
920
+ message: MESSAGE$18
921
921
  });
922
922
  } };
923
923
  } });
924
924
  //#endregion
925
925
  //#region src/rules/vue/composition/no-pinia-store-in-setup.ts
926
- const MESSAGE$16 = `This calls defineStore() inside a function (setup, a composable, or a component body), so a brand-new store definition is created on every call instead of one shared singleton. Call defineStore() once at module scope and call the returned useXxxStore() inside setup. See https://pinia.vuejs.org/core-concepts/#defining-a-store`;
926
+ const MESSAGE$17 = `This calls defineStore() inside a function (setup, a composable, or a component body), so a brand-new store definition is created on every call instead of one shared singleton. Call defineStore() once at module scope and call the returned useXxxStore() inside setup. See https://pinia.vuejs.org/core-concepts/#defining-a-store`;
927
927
  const noPiniaStoreInSetup = defineRule({ create(context) {
928
928
  let functionDepth = 0;
929
929
  const enter = () => {
@@ -938,7 +938,7 @@ const noPiniaStoreInSetup = defineRule({ create(context) {
938
938
  if (callee?.type !== "Identifier" || callee.name !== "defineStore") return;
939
939
  if (functionDepth > 0) context.report({
940
940
  node,
941
- message: MESSAGE$16
941
+ message: MESSAGE$17
942
942
  });
943
943
  },
944
944
  FunctionDeclaration: enter,
@@ -951,7 +951,7 @@ const noPiniaStoreInSetup = defineRule({ create(context) {
951
951
  } });
952
952
  //#endregion
953
953
  //#region src/rules/vue/composition/no-prop-callback-in-setup.ts
954
- const MESSAGE$15 = `Calling a parent callback prop during setup or inside a computed getter runs a side effect on the render path — it fires on every re-evaluation and during SSR. Move the call into an event handler, watcher, or lifecycle hook. See https://vuejs.org/guide/components/events.html`;
954
+ const MESSAGE$16 = `Calling a parent callback prop during setup or inside a computed getter runs a side effect on the render path — it fires on every re-evaluation and during SSR. Move the call into an event handler, watcher, or lifecycle hook. See https://vuejs.org/guide/components/events.html`;
955
955
  const CALLBACK_NAME = /^on[A-Z]/;
956
956
  function calleeIdentifierName$2(node) {
957
957
  const callee = node.callee;
@@ -1004,14 +1004,14 @@ const noPropCallbackInSetup = defineRule({ create(context) {
1004
1004
  const enclosing = functionStack[functionStack.length - 1];
1005
1005
  if (enclosing === void 0 || enclosing.isComputedGetter) context.report({
1006
1006
  node,
1007
- message: MESSAGE$15
1007
+ message: MESSAGE$16
1008
1008
  });
1009
1009
  }
1010
1010
  };
1011
1011
  } });
1012
1012
  //#endregion
1013
1013
  //#region src/rules/vue/composition/prefer-script-setup-for-new-files.ts
1014
- const MESSAGE$14 = `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`;
1014
+ const MESSAGE$15 = `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`;
1015
1015
  function isFunctionValue(node) {
1016
1016
  return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
1017
1017
  }
@@ -1028,13 +1028,13 @@ const preferScriptSetupForNewFiles = defineRule({ create(context) {
1028
1028
  if (declaration?.type !== "ObjectExpression") return;
1029
1029
  if (declaration.properties.some(isSetupProperty)) context.report({
1030
1030
  node,
1031
- message: MESSAGE$14
1031
+ message: MESSAGE$15
1032
1032
  });
1033
1033
  } };
1034
1034
  } });
1035
1035
  //#endregion
1036
1036
  //#region src/rules/vue/performance/prefer-defineAsyncComponent-on-route.ts
1037
- const MESSAGE$13 = `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`;
1037
+ const MESSAGE$14 = `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`;
1038
1038
  function keyName(key) {
1039
1039
  return key.type === "Identifier" ? key.name : key.value;
1040
1040
  }
@@ -1044,13 +1044,13 @@ const preferDefineAsyncComponentOnRoute = defineRule({ create(context) {
1044
1044
  if (keyName(node.key) !== "component") return;
1045
1045
  if (node.value.type === "Identifier") context.report({
1046
1046
  node,
1047
- message: MESSAGE$13
1047
+ message: MESSAGE$14
1048
1048
  });
1049
1049
  } };
1050
1050
  } });
1051
1051
  //#endregion
1052
1052
  //#region src/rules/vue/performance/prefer-module-scope-pure-function.ts
1053
- const MESSAGE$12 = `This helper closes over nothing reactive (only its parameters, imports, and globals), yet it is re-created on every call of the enclosing function. Hoist it to module scope so a single function reference is shared across all component instances. See https://vuejs.org/guide/best-practices/performance.html`;
1053
+ const MESSAGE$13 = `This helper closes over nothing reactive (only its parameters, imports, and globals), yet it is re-created on every call of the enclosing function. Hoist it to module scope so a single function reference is shared across all component instances. See https://vuejs.org/guide/best-practices/performance.html`;
1054
1054
  const GLOBALS = /* @__PURE__ */ new Set([
1055
1055
  "console",
1056
1056
  "Math",
@@ -1178,7 +1178,7 @@ const preferModuleScopePureFunction = defineRule({ create(context) {
1178
1178
  if (functionDepth < 1) return;
1179
1179
  if (isHoistable(fn, imports)) context.report({
1180
1180
  node: fn,
1181
- message: MESSAGE$12
1181
+ message: MESSAGE$13
1182
1182
  });
1183
1183
  };
1184
1184
  return {
@@ -1196,7 +1196,7 @@ const preferModuleScopePureFunction = defineRule({ create(context) {
1196
1196
  if (init?.type === "ArrowFunctionExpression" || init?.type === "FunctionExpression") {
1197
1197
  if (isHoistable(init, imports)) context.report({
1198
1198
  node: init,
1199
- message: MESSAGE$12
1199
+ message: MESSAGE$13
1200
1200
  });
1201
1201
  }
1202
1202
  }
@@ -1216,7 +1216,7 @@ const preferModuleScopePureFunction = defineRule({ create(context) {
1216
1216
  } });
1217
1217
  //#endregion
1218
1218
  //#region src/rules/vue/performance/prefer-module-scope-static-value.ts
1219
- const MESSAGE$11 = `This static array or object literal is declared inside a function, so a fresh copy is allocated on every call (including every component instantiation). Hoist it to module scope so the single frozen reference is shared. See https://vuejs.org/guide/best-practices/performance.html`;
1219
+ const MESSAGE$12 = `This static array or object literal is declared inside a function, so a fresh copy is allocated on every call (including every component instantiation). Hoist it to module scope so the single frozen reference is shared. See https://vuejs.org/guide/best-practices/performance.html`;
1220
1220
  const LITERAL_TYPES = /* @__PURE__ */ new Set([
1221
1221
  "Literal",
1222
1222
  "StringLiteral",
@@ -1281,14 +1281,14 @@ const preferModuleScopeStaticValue = defineRule({ create(context) {
1281
1281
  if (!isStaticLiteral(init)) return;
1282
1282
  context.report({
1283
1283
  node: init,
1284
- message: MESSAGE$11
1284
+ message: MESSAGE$12
1285
1285
  });
1286
1286
  }
1287
1287
  };
1288
1288
  } });
1289
1289
  //#endregion
1290
1290
  //#region src/rules/vue/performance/prefer-stable-empty-fallback.ts
1291
- const MESSAGE$10 = `This computed falls back to a fresh empty array or object literal, so it returns a brand-new reference whenever the source is nullish. Downstream watchers, child props, and v-memo see an identity change every recompute. Hoist a single module-scope EMPTY constant and fall back to that. See https://vuejs.org/guide/best-practices/performance.html`;
1291
+ const MESSAGE$11 = `This computed falls back to a fresh empty array or object literal, so it returns a brand-new reference whenever the source is nullish. Downstream watchers, child props, and v-memo see an identity change every recompute. Hoist a single module-scope EMPTY constant and fall back to that. See https://vuejs.org/guide/best-practices/performance.html`;
1292
1292
  function isEmptyLiteral(node) {
1293
1293
  if (node?.type === "ArrayExpression") return node.elements.length === 0;
1294
1294
  if (node?.type === "ObjectExpression") return node.properties.length === 0;
@@ -1318,13 +1318,13 @@ const preferStableEmptyFallback = defineRule({ create(context) {
1318
1318
  const returned = getterBody(node);
1319
1319
  if (returned && fallsBackToEmptyLiteral(returned)) context.report({
1320
1320
  node: returned,
1321
- message: MESSAGE$10
1321
+ message: MESSAGE$11
1322
1322
  });
1323
1323
  } };
1324
1324
  } });
1325
1325
  //#endregion
1326
1326
  //#region src/rules/vue/reactivity/effect-listener-cleanup-mismatch.ts
1327
- const MESSAGE$9 = `This cleanup cannot detach the listener it pairs with: removeEventListener only removes when the handler reference AND capture flag match addEventListener exactly. Use the same named handler (never an inline arrow) and the same capture flag. See https://vuejs.org/guide/essentials/watchers.html#side-effect-cleanup`;
1327
+ const MESSAGE$10 = `This cleanup cannot detach the listener it pairs with: removeEventListener only removes when the handler reference AND capture flag match addEventListener exactly. Use the same named handler (never an inline arrow) and the same capture flag. See https://vuejs.org/guide/essentials/watchers.html#side-effect-cleanup`;
1328
1328
  const EFFECT_CALLS = /* @__PURE__ */ new Set([
1329
1329
  "watch",
1330
1330
  "watchEffect",
@@ -1381,7 +1381,7 @@ function reportMismatches(context, frame) {
1381
1381
  if (pairable.length === 0) continue;
1382
1382
  if (!pairable.some((a) => a.handlerKey !== void 0 && a.handlerKey === removed.handlerKey && a.capture === removed.capture)) context.report({
1383
1383
  node: removed.node,
1384
- message: MESSAGE$9
1384
+ message: MESSAGE$10
1385
1385
  });
1386
1386
  }
1387
1387
  }
@@ -1425,7 +1425,7 @@ const effectListenerCleanupMismatch = defineRule({ create(context) {
1425
1425
  } });
1426
1426
  //#endregion
1427
1427
  //#region src/rules/vue/reactivity/no-fresh-deps-in-watch.ts
1428
- const MESSAGE$8 = `This watch getter returns a freshly-constructed array or object literal, so Vue compares it by reference (Object.is) and the watcher re-fires on every reactive tick — even when nothing changed. Use the multi-source array form watch([a, b], cb) for several sources, or watch a primitive/ref directly. See https://vuejs.org/guide/essentials/watchers.html#watch-source-types`;
1428
+ const MESSAGE$9 = `This watch getter returns a freshly-constructed array or object literal, so Vue compares it by reference (Object.is) and the watcher re-fires on every reactive tick — even when nothing changed. Use the multi-source array form watch([a, b], cb) for several sources, or watch a primitive/ref directly. See https://vuejs.org/guide/essentials/watchers.html#watch-source-types`;
1429
1429
  function isFunction$1(node) {
1430
1430
  return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
1431
1431
  }
@@ -1452,13 +1452,13 @@ const noFreshDepsInWatch = defineRule({ create(context) {
1452
1452
  if (!source || !isFunction$1(source)) return;
1453
1453
  if (getterReturnsFreshLiteral(source)) context.report({
1454
1454
  node: source,
1455
- message: MESSAGE$8
1455
+ message: MESSAGE$9
1456
1456
  });
1457
1457
  } };
1458
1458
  } });
1459
1459
  //#endregion
1460
1460
  //#region src/rules/vue/reactivity/no-stale-timer-ref.ts
1461
- const MESSAGE$7 = `clearTimeout/clearInterval on a ref leaves the stale timer id in .value, so any "timer pending" check on that ref keeps passing after the timer is gone. Reset the ref (e.g. timer.value = null) after clearing. See https://vuejs.org/guide/essentials/reactivity-fundamentals`;
1461
+ const MESSAGE$8 = `clearTimeout/clearInterval on a ref leaves the stale timer id in .value, so any "timer pending" check on that ref keeps passing after the timer is gone. Reset the ref (e.g. timer.value = null) after clearing. See https://vuejs.org/guide/essentials/reactivity-fundamentals`;
1462
1462
  const CLEAR_CALLS = /* @__PURE__ */ new Set(["clearTimeout", "clearInterval"]);
1463
1463
  /** Matches `<identifier>.value` and returns the identifier name. */
1464
1464
  function refValueTarget(node) {
@@ -1472,7 +1472,7 @@ function refValueTarget(node) {
1472
1472
  function flush(context, frame) {
1473
1473
  for (const item of frame.pending) context.report({
1474
1474
  node: item.node,
1475
- message: MESSAGE$7
1475
+ message: MESSAGE$8
1476
1476
  });
1477
1477
  }
1478
1478
  const noStaleTimerRef = defineRule({ create(context) {
@@ -1521,7 +1521,7 @@ const noStaleTimerRef = defineRule({ create(context) {
1521
1521
  } });
1522
1522
  //#endregion
1523
1523
  //#region src/rules/vue/reactivity/prefer-readonly-for-injected.ts
1524
- 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`;
1524
+ const MESSAGE$7 = `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`;
1525
1525
  const MUTATING_METHODS = /* @__PURE__ */ new Set([
1526
1526
  "push",
1527
1527
  "pop",
@@ -1553,7 +1553,7 @@ const preferReadonlyForInjected = defineRule({ create(context) {
1553
1553
  const name = memberObjectName(node.left);
1554
1554
  if (name !== void 0 && injectedSources.has(name)) context.report({
1555
1555
  node,
1556
- message: MESSAGE$6
1556
+ message: MESSAGE$7
1557
1557
  });
1558
1558
  },
1559
1559
  CallExpression(node) {
@@ -1564,14 +1564,14 @@ const preferReadonlyForInjected = defineRule({ create(context) {
1564
1564
  const property = callee.property;
1565
1565
  if (MUTATING_METHODS.has(property.name)) context.report({
1566
1566
  node,
1567
- message: MESSAGE$6
1567
+ message: MESSAGE$7
1568
1568
  });
1569
1569
  }
1570
1570
  };
1571
1571
  } });
1572
1572
  //#endregion
1573
1573
  //#region src/rules/vue/reactivity/prefer-shallowRef-for-large-data.ts
1574
- 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`;
1574
+ const MESSAGE$6 = `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`;
1575
1575
  const ARRAY_LIMIT = 100;
1576
1576
  const OBJECT_LIMIT = 50;
1577
1577
  const FETCH_IDENTIFIERS = /* @__PURE__ */ new Set(["$fetch", "useFetch"]);
@@ -1605,13 +1605,13 @@ const preferShallowRefForLargeData = defineRule({ create(context) {
1605
1605
  const init = node.arguments[0];
1606
1606
  if (isLargeData(init)) context.report({
1607
1607
  node,
1608
- message: MESSAGE$5
1608
+ message: MESSAGE$6
1609
1609
  });
1610
1610
  } };
1611
1611
  } });
1612
1612
  //#endregion
1613
1613
  //#region src/rules/vue/reactivity/watch-without-cleanup.ts
1614
- 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`;
1614
+ const MESSAGE$5 = `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`;
1615
1615
  const REGISTER_CALLS = /* @__PURE__ */ new Set([
1616
1616
  "addEventListener",
1617
1617
  "setInterval",
@@ -1679,6 +1679,81 @@ const watchWithoutCleanup = defineRule({ create(context) {
1679
1679
  watchStack.pop();
1680
1680
  if (top.hasRegistration && !top.hasCleanup) context.report({
1681
1681
  node,
1682
+ message: MESSAGE$5
1683
+ });
1684
+ }
1685
+ };
1686
+ } });
1687
+ //#endregion
1688
+ //#region src/rules/vue/security/markdown-it-unsanitized-html.ts
1689
+ const MESSAGE$4 = `markdown-it is configured with \`html: true\` but no sanitizer is connected. Raw HTML in user-authored markdown is an XSS vector. Pipe md.render output through DOMPurify/sanitize-html, or add a markdown-it sanitizer plugin. See https://github.com/markdown-it/markdown-it/blob/master/docs/security.md`;
1690
+ const SANITIZER_MODULES = /* @__PURE__ */ new Set([
1691
+ "dompurify",
1692
+ "isomorphic-dompurify",
1693
+ "sanitize-html",
1694
+ "xss",
1695
+ "markdown-it-sanitizer",
1696
+ "insane"
1697
+ ]);
1698
+ function importSource(node) {
1699
+ return String(node.source.value);
1700
+ }
1701
+ function optsEnableHtml(node) {
1702
+ if (!node || node.type !== "ObjectExpression") return false;
1703
+ for (const prop of node.properties) {
1704
+ if (prop.type !== "Property") continue;
1705
+ const key = prop.key;
1706
+ if ((key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0) !== "html") continue;
1707
+ const value = prop.value;
1708
+ return value.type === "Literal" && value.value === true;
1709
+ }
1710
+ return false;
1711
+ }
1712
+ function isMarkdownItCallee(callee, local) {
1713
+ return callee?.type === "Identifier" && callee.name === local;
1714
+ }
1715
+ const markdownItUnsanitizedHtml = defineRule({ create(context) {
1716
+ let markdownItLocal;
1717
+ let sanitized = false;
1718
+ const unsafeSites = [];
1719
+ const markdownInstances = /* @__PURE__ */ new Set();
1720
+ const recordConstruction = (node, calleeArgs, assignedTo) => {
1721
+ if (assignedTo) markdownInstances.add(assignedTo);
1722
+ if (optsEnableHtml(calleeArgs?.[0])) unsafeSites.push(node);
1723
+ };
1724
+ return {
1725
+ ImportDeclaration(node) {
1726
+ const source = importSource(node);
1727
+ if (SANITIZER_MODULES.has(source)) sanitized = true;
1728
+ if (source === "markdown-it") {
1729
+ for (const spec of node.specifiers) if (spec.type === "ImportDefaultSpecifier") markdownItLocal = spec.local.name;
1730
+ }
1731
+ },
1732
+ VariableDeclarator(node) {
1733
+ if (markdownItLocal === void 0) return;
1734
+ const init = node.init;
1735
+ const id = node.id;
1736
+ const assignedTo = id.type === "Identifier" ? id.name : void 0;
1737
+ if (init?.type === "NewExpression" && isMarkdownItCallee(init.callee, markdownItLocal)) recordConstruction(init, init.arguments, assignedTo);
1738
+ else if (init?.type === "CallExpression" && isMarkdownItCallee(init.callee, markdownItLocal)) recordConstruction(init, init.arguments, assignedTo);
1739
+ },
1740
+ CallExpression(node) {
1741
+ const callee = node.callee;
1742
+ if (callee?.type !== "MemberExpression") return;
1743
+ const prop = callee.property;
1744
+ const propName = prop.type === "Identifier" ? prop.name : void 0;
1745
+ if (propName === "use") {
1746
+ sanitized = true;
1747
+ return;
1748
+ }
1749
+ if (propName !== "set") return;
1750
+ const object = callee.object;
1751
+ if (object.type === "Identifier" && markdownInstances.has(object.name) && optsEnableHtml(node.arguments?.[0])) unsafeSites.push(node);
1752
+ },
1753
+ "Program:exit"() {
1754
+ if (sanitized) return;
1755
+ for (const site of unsafeSites) context.report({
1756
+ node: site,
1682
1757
  message: MESSAGE$4
1683
1758
  });
1684
1759
  }
@@ -1873,7 +1948,8 @@ const VUE_RULES = [
1873
1948
  coreRule("security/no-inner-html", "security", "error", true, defineRule(noInnerHtml)),
1874
1949
  coreRule("security/no-eval-like", "security", "error", true, defineRule(noEvalLike)),
1875
1950
  coreRule("security/no-auth-token-in-web-storage", "security", "warn", true, defineRule(noAuthTokenInWebStorage)),
1876
- coreRule("security/no-secrets-in-source", "security", "warn", true, defineRule(noSecretsInSource))
1951
+ coreRule("security/no-secrets-in-source", "security", "warn", true, defineRule(noSecretsInSource)),
1952
+ coreRule("security/markdown-it-unsanitized-html", "security", "warn", true, defineRule(markdownItUnsanitizedHtml))
1877
1953
  ];
1878
1954
  //#endregion
1879
1955
  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 };