shadscan-vue 0.1.0 → 0.1.2
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/CHANGELOG.md +15 -0
- package/README.md +2 -0
- package/dist/index.js +28 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.2](https://github.com/vinayakkulkarni/shadscan-vue/compare/v0.1.1...v0.1.2) (2026-07-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **rules:** detect the destructured useMagicKeys hotkey binding ([175fd5e](https://github.com/vinayakkulkarni/shadscan-vue/commit/175fd5e89549e2c91677b6a48974bc20c8b168d5))
|
|
9
|
+
* **rules:** exempt icon-module icons and clarify the command-menu message ([904f76d](https://github.com/vinayakkulkarni/shadscan-vue/commit/904f76d3dfd7cc270cec65d6945c9a82cccb3455))
|
|
10
|
+
|
|
11
|
+
## [0.1.1](https://github.com/vinayakkulkarni/shadscan-vue/compare/v0.1.0...v0.1.1) (2026-07-25)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
* point the package and readmes at the live site ([72c9546](https://github.com/vinayakkulkarni/shadscan-vue/commit/72c9546592d4d95474c4266b1e62f654f4f21320))
|
|
17
|
+
|
|
3
18
|
## 0.1.0 (2026-07-25)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Deterministic checks. Evidence. Agent-ready fixes. Static audit — no AI required.
|
|
6
6
|
|
|
7
|
+
**[shadscan-vue.geoql.in](https://shadscan-vue.geoql.in)** — rule catalog, docs, and changelog.
|
|
8
|
+
|
|
7
9
|
`shadscan-vue` audits **shadcn-vue** (Vue 3 + Vite) and **shadcn-nuxt** (Nuxt 4)
|
|
8
10
|
applications for the fundamentals that are easy to skip and expensive to miss:
|
|
9
11
|
theme wiring, command menus, route and error states, accessible controls, form
|
package/dist/index.js
CHANGED
|
@@ -1777,7 +1777,7 @@ const K_KEY_PATTERN = /\.key\s*===\s*['"]k['"]|key\s*===\s*['"]k['"]/iu;
|
|
|
1777
1777
|
const PREVENT_DEFAULT_PATTERN = /preventDefault\s*\(/u;
|
|
1778
1778
|
const TOGGLE_PATTERN = /\.value\s*=|=\s*!|toggle|open\s*=|\bset[A-Z]/u;
|
|
1779
1779
|
const KEYDOWN_PATTERN = /keydown/u;
|
|
1780
|
-
const MAGIC_KEYS_CMD_K_PATTERN = /useMagicKeys[\s\S]*?(?:Meta|Cmd|Ctrl|Control)\s*\+\s*
|
|
1780
|
+
const MAGIC_KEYS_CMD_K_PATTERN = /useMagicKeys[\s\S]*?(?:(?:Meta|Cmd|Ctrl|Control)\s*\+\s*K|\b(?:meta|cmd|ctrl|control)_k\b)/iu;
|
|
1781
1781
|
const WATCH_PATTERN = /\bwatch\s*\(/u;
|
|
1782
1782
|
const isSourceFile$2 = (file) => file.kind === "vue" || file.kind === "ts" || file.kind === "js";
|
|
1783
1783
|
const detectKeydownFlow = (text) => KEYDOWN_PATTERN.test(text) && MODIFIER_PATTERN$1.test(text) && K_KEY_PATTERN.test(text) && PREVENT_DEFAULT_PATTERN.test(text) && TOGGLE_PATTERN.test(text);
|
|
@@ -1852,9 +1852,9 @@ const commandMenuPresent = {
|
|
|
1852
1852
|
}]);
|
|
1853
1853
|
if (files.find((file) => templateHasAllParts(file)) !== void 0) return result.pass();
|
|
1854
1854
|
return result.fail([{
|
|
1855
|
-
message: "
|
|
1855
|
+
message: "The command module is installed but no template assembles it into a command menu.",
|
|
1856
1856
|
evidence: [],
|
|
1857
|
-
remediation: "Ensure a single template renders CommandDialog, CommandInput, CommandEmpty, and at least one CommandItem together."
|
|
1857
|
+
remediation: "Ensure a single template renders CommandDialog, CommandInput, CommandEmpty, and at least one CommandItem together. A hand-rolled palette misses the keyboard and labelling behaviour these parts provide."
|
|
1858
1858
|
}]);
|
|
1859
1859
|
}
|
|
1860
1860
|
};
|
|
@@ -2803,9 +2803,19 @@ const baseIdentifier = (collection) => {
|
|
|
2803
2803
|
return /([A-Za-z_$][\w$]*)\s*$/u.exec(collection)?.[1];
|
|
2804
2804
|
};
|
|
2805
2805
|
/** True when the file's script region references the identifier. */
|
|
2806
|
+
const scriptSource = (file) => file.sfc?.descriptor.scriptSetup?.content ?? file.sfc?.descriptor.script?.content ?? "";
|
|
2806
2807
|
const scriptDefines = (file, identifier) => {
|
|
2807
|
-
|
|
2808
|
-
|
|
2808
|
+
return new RegExp(`\\b${identifier}\\b`, "u").test(scriptSource(file));
|
|
2809
|
+
};
|
|
2810
|
+
/**
|
|
2811
|
+
* A collection assigned a non-empty array literal at declaration cannot be
|
|
2812
|
+
* empty at runtime, so demanding an empty state for it reports a failure the
|
|
2813
|
+
* author cannot act on. Matches `const tabs = [` and typed forms such as
|
|
2814
|
+
* `const tabs: Tab[] = [`, then confirms the literal has at least one entry.
|
|
2815
|
+
*/
|
|
2816
|
+
const isStaticNonEmptyLiteral = (file, identifier) => {
|
|
2817
|
+
const declaration = new RegExp(`\\b(?:const|let|var)\\s+${identifier}\\b[^=]*=\\s*\\[\\s*([^\\]])`, "su").exec(scriptSource(file));
|
|
2818
|
+
return declaration !== null && declaration[1] !== void 0;
|
|
2809
2819
|
};
|
|
2810
2820
|
/** True when the file contains an empty-branch guard for the collection. */
|
|
2811
2821
|
const hasEmptyBranch = (fileText, collection, identifier) => {
|
|
@@ -2847,7 +2857,7 @@ const emptyStatePresent = {
|
|
|
2847
2857
|
if (collection === void 0) continue;
|
|
2848
2858
|
const identifier = baseIdentifier(collection);
|
|
2849
2859
|
if (identifier === void 0) continue;
|
|
2850
|
-
if (!scriptDefines(file, identifier)) continue;
|
|
2860
|
+
if (!scriptDefines(file, identifier) || isStaticNonEmptyLiteral(file, identifier)) continue;
|
|
2851
2861
|
if (hasEmptyBranch(file.text, collection, identifier)) continue;
|
|
2852
2862
|
failures.push({
|
|
2853
2863
|
message: `<${element.tag}> iterates \`${collection}\` with no empty-state branch in this file.`,
|
|
@@ -3185,6 +3195,14 @@ const BUTTON_TAGS = /* @__PURE__ */ new Set([
|
|
|
3185
3195
|
"RouterLink",
|
|
3186
3196
|
"router-link"
|
|
3187
3197
|
]);
|
|
3198
|
+
/**
|
|
3199
|
+
* @nuxt/icon applies `{ "aria-hidden": true }` to every icon it renders (its
|
|
3200
|
+
* documented `attrs` default), so its `<Icon>` and `<NuxtIcon>` components are
|
|
3201
|
+
* already decorative without the author writing the attribute. Auditing them
|
|
3202
|
+
* reports a failure that cannot be acted on.
|
|
3203
|
+
*/
|
|
3204
|
+
const NUXT_ICON_MODULE = "@nuxt/icon";
|
|
3205
|
+
const NUXT_ICON_TAGS = /* @__PURE__ */ new Set(["icon", "nuxt-icon"]);
|
|
3188
3206
|
const buttonIconsHaveDataIcon = {
|
|
3189
3207
|
id: "button-icons-have-data-icon",
|
|
3190
3208
|
title: "Icons inside controls are marked decorative",
|
|
@@ -3198,12 +3216,14 @@ const buttonIconsHaveDataIcon = {
|
|
|
3198
3216
|
"vite-vue",
|
|
3199
3217
|
"generic-vue"
|
|
3200
3218
|
],
|
|
3201
|
-
run: async ({ sources, result }) => {
|
|
3219
|
+
run: async ({ sources, discovery, result }) => {
|
|
3202
3220
|
const files = await sources();
|
|
3203
3221
|
const findings = [];
|
|
3204
3222
|
let evaluated = 0;
|
|
3223
|
+
const iconModuleHidesIcons = discovery.dependencies[NUXT_ICON_MODULE] !== void 0;
|
|
3205
3224
|
forEachElement(files, (element, file, ancestors) => {
|
|
3206
3225
|
if (!isIconTag(element.tag)) return;
|
|
3226
|
+
if (iconModuleHidesIcons && NUXT_ICON_TAGS.has(pascalToKebab(element.tag))) return;
|
|
3207
3227
|
if (!ancestors.some((ancestor) => BUTTON_TAGS.has(ancestor.tag))) return;
|
|
3208
3228
|
evaluated += 1;
|
|
3209
3229
|
const ariaHidden = findAttribute(element, "aria-hidden");
|
|
@@ -3225,7 +3245,7 @@ const buttonIconsHaveDataIcon = {
|
|
|
3225
3245
|
//#region src/rules/production-polish/metadata-title-description-complete.ts
|
|
3226
3246
|
const TITLE_PATTERN = /\b(?:title|titleTemplate)\s*:/u;
|
|
3227
3247
|
const DESCRIPTION_PATTERN = /\bdescription\s*:/u;
|
|
3228
|
-
const HEAD_CALL_PATTERN = /\b(?:useSeoMeta|useHead
|
|
3248
|
+
const HEAD_CALL_PATTERN = /\b(?:useSeoMeta|useHead)\s*\(/u;
|
|
3229
3249
|
const metadataTitleDescriptionComplete = {
|
|
3230
3250
|
id: "metadata-title-description-complete",
|
|
3231
3251
|
title: "Routable pages declare a title and description",
|