@weerachai06/tw-scanner 1.1.0 → 1.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/README.md +28 -2
- package/dist/validator.js +27 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,10 @@ npx @weerachai06/tw-scanner --src ./src --css ./src/globals.css --json | jq '.in
|
|
|
47
47
|
|---|---|---|
|
|
48
48
|
| ❌ Invalid class | `bg-old-token-500` | **Error** — invalid after migration |
|
|
49
49
|
| ❌ Invalid in cva variants | `danger: 'bg-red-danger'` | **Error** — detected via AST |
|
|
50
|
+
| ❌ Invalid `@apply` class | `@apply bg-fake-999;` in `.css` | **Error** — validated against your config |
|
|
51
|
+
| ❌ Missing CSS Module class | `styles.nonExistent` | **Error** — class not defined in `.module.css` |
|
|
50
52
|
| ⚠️ Dynamic expression | `` `text-${color}` `` | **Warning** — cannot validate statically |
|
|
53
|
+
| ⚠️ Dynamic module access | `styles[variable]` | **Warning** — skipped, cannot resolve statically |
|
|
51
54
|
|
|
52
55
|
## How it works
|
|
53
56
|
|
|
@@ -60,13 +63,36 @@ Uses `@typescript-eslint/typescript-estree` to parse `.ts/.tsx/.js/.jsx` and tra
|
|
|
60
63
|
- `ConditionalExpression` and `LogicalExpression` inside class utilities
|
|
61
64
|
- `TemplateLiteral` — static parts extracted, dynamic parts flagged as warnings
|
|
62
65
|
|
|
63
|
-
### 2.
|
|
66
|
+
### 2. CSS file scanning
|
|
67
|
+
|
|
68
|
+
`.css` and `.module.css` files are also scanned. Classes inside `@apply` directives are extracted and validated against your Tailwind config.
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.btn {
|
|
72
|
+
@apply bg-blue-500 text-white px-4; /* ✓ valid */
|
|
73
|
+
@apply bg-fake-999; /* ✗ error */
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. CSS Modules validation
|
|
78
|
+
|
|
79
|
+
When a JS/TS file imports a `.module.css` file, all `styles.xxx` and `styles['xxx']` usages are checked against the class names actually defined in that file.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import styles from './button.module.css'
|
|
83
|
+
|
|
84
|
+
<button className={styles.btn}>...</button> // ✓ defined
|
|
85
|
+
<button className={styles.nonExistent}>...</button> // ✗ error
|
|
86
|
+
<button className={styles[variant]}>...</button> // ⚠ skipped (dynamic)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 4. Tailwind v4 Validation
|
|
64
90
|
|
|
65
91
|
Uses `@tailwindcss/node`'s `compile()` API to load your actual CSS entry file (including all `@theme` tokens and plugins), then calls `build([candidate])` for each class. If the class generates no CSS rule in the output, it's invalid.
|
|
66
92
|
|
|
67
93
|
Validation is **100% accurate against your real config** — custom tokens, plugins, and all.
|
|
68
94
|
|
|
69
|
-
###
|
|
95
|
+
### 5. Batch validation
|
|
70
96
|
|
|
71
97
|
All unique class values are validated in a single `build()` call per batch for performance.
|
|
72
98
|
|
package/dist/validator.js
CHANGED
|
@@ -11,10 +11,33 @@ export async function loadTailwindContext(cssFile) {
|
|
|
11
11
|
throw new Error(`Tailwind CSS file not found: ${abs}`);
|
|
12
12
|
}
|
|
13
13
|
const css = fs.readFileSync(abs, 'utf8');
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
let result;
|
|
15
|
+
try {
|
|
16
|
+
result = await compile(css, {
|
|
17
|
+
base: path.dirname(abs),
|
|
18
|
+
onDependency: () => { },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const msg = err.message ?? '';
|
|
23
|
+
const match = msg.match(/Cannot apply unknown utility class [`']?([^\s`']+)[`']?/);
|
|
24
|
+
if (match) {
|
|
25
|
+
// Strip all @apply lines from the entry CSS and retry so scanning can continue.
|
|
26
|
+
// Unknown utilities are likely defined via PostCSS plugins or external font systems
|
|
27
|
+
// that @tailwindcss/node's compile() doesn't have access to.
|
|
28
|
+
const strippedCss = css.replace(/^\s*@apply\b[^;]+;/gm, '');
|
|
29
|
+
console.warn(`⚠ Skipping @apply in "${path.relative(process.cwd(), abs)}" — ` +
|
|
30
|
+
`unknown utility: "${match[1]}". ` +
|
|
31
|
+
`Add "@utility ${match[1]} {}" to register it if you want it validated.`);
|
|
32
|
+
result = await compile(strippedCss, {
|
|
33
|
+
base: path.dirname(abs),
|
|
34
|
+
onDependency: () => { },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
18
41
|
compileCache.set(abs, result);
|
|
19
42
|
return result;
|
|
20
43
|
}
|