@weerachai06/tw-scanner 1.0.0 → 1.0.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/dist/extractor.js CHANGED
@@ -215,15 +215,14 @@ export function extractClassesFromFile(file) {
215
215
  if (!fs.existsSync(file))
216
216
  return [];
217
217
  const source = fs.readFileSync(file, 'utf8');
218
- const isTS = /\.(ts|tsx)$/.test(file);
218
+ const isJSX = /\.(tsx|jsx)$/.test(file);
219
219
  let ast;
220
220
  try {
221
221
  ast = parse(source, {
222
- jsx: true,
222
+ jsx: isJSX,
223
223
  loc: true,
224
224
  range: true,
225
225
  tolerant: true,
226
- ...(isTS ? {} : {}),
227
226
  });
228
227
  }
229
228
  catch (err) {
package/dist/validator.js CHANGED
@@ -18,20 +18,35 @@ export async function loadTailwindContext(cssFile) {
18
18
  compileCache.set(abs, result);
19
19
  return result;
20
20
  }
21
- // ─── Build escape for CSS selector matching ───────────────────────────────────
22
- function cssEscape(cls) {
23
- // Matches what Tailwind outputs in CSS selectors
24
- return cls
25
- .replace(/\//g, '\\/') // p-1/2 → p-1\/2
26
- .replace(/\./g, '\\.') // . → \.
27
- .replace(/:/g, '\\:') // md: → md\:
21
+ // ─── Build CSS selector for string matching ──────────────────────────────────
22
+ function cssSelector(cls) {
23
+ // Produces the escaped selector as it appears in Tailwind's CSS output
24
+ return '.' + cls
25
+ .replace(/\//g, '\\/')
26
+ .replace(/\./g, '\\.')
27
+ .replace(/:/g, '\\:')
28
28
  .replace(/\[/g, '\\[')
29
29
  .replace(/\]/g, '\\]')
30
30
  .replace(/\(/g, '\\(')
31
31
  .replace(/\)/g, '\\)')
32
+ .replace(/=/g, '\\=')
33
+ .replace(/>/g, '\\>')
34
+ .replace(/&/g, '\\&')
35
+ .replace(/~/g, '\\~')
36
+ .replace(/\+/g, '\\+')
32
37
  .replace(/#/g, '\\#')
33
38
  .replace(/%/g, '\\%')
34
- .replace(/!/g, '\\!');
39
+ .replace(/!/g, '\\!')
40
+ .replace(/,/g, '\\,')
41
+ .replace(/'/g, "\\'")
42
+ .replace(/"/g, '\\"');
43
+ }
44
+ function selectorInOutput(selector, output) {
45
+ const idx = output.indexOf(selector);
46
+ if (idx === -1)
47
+ return false;
48
+ const next = output[idx + selector.length];
49
+ return next === '{' || next === ' ' || next === ':' || next === '[' || next === ',';
35
50
  }
36
51
  // ─── Validation cache: per-context ───────────────────────────────────────────
37
52
  const validityCache = new Map();
@@ -43,11 +58,7 @@ export function isValidClass(cls, context) {
43
58
  return cache.get(cls);
44
59
  // Build CSS with this single candidate
45
60
  const output = context.build([cls]);
46
- // A valid class generates a selector in the utilities/components layer
47
- const escaped = cssEscape(cls);
48
- // Look for .classname{ or .classname { or .classname:hover{
49
- const pattern = new RegExp(`\\.${escaped.replace(/\\/g, '\\\\')}[\\s{\\[:]`);
50
- const valid = pattern.test(output);
61
+ const valid = selectorInOutput(cssSelector(cls), output);
51
62
  cache.set(cls, valid);
52
63
  return valid;
53
64
  }
@@ -72,9 +83,7 @@ export function validateBatch(classes, context) {
72
83
  // Build all candidates at once
73
84
  const output = context.build(toCheck);
74
85
  for (const cls of toCheck) {
75
- const escaped = cssEscape(cls);
76
- const pattern = new RegExp(`\\.${escaped.replace(/\\/g, '\\\\')}[\\s{\\[:]`);
77
- const valid = pattern.test(output);
86
+ const valid = selectorInOutput(cssSelector(cls), output);
78
87
  result.set(cls, valid);
79
88
  cache.set(cls, valid);
80
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weerachai06/tw-scanner",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AST-based Tailwind v4 class validator for React projects",
5
5
  "type": "module",
6
6
  "bin": {