tailwind-typescript-plugin 1.4.1-beta.1 → 1.4.1-beta.10

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +206 -34
  3. package/lib/core/interfaces.d.ts +2 -2
  4. package/lib/core/interfaces.d.ts.map +1 -1
  5. package/lib/core/types.d.ts +19 -1
  6. package/lib/core/types.d.ts.map +1 -1
  7. package/lib/extractors/BaseExtractor.d.ts +49 -2
  8. package/lib/extractors/BaseExtractor.d.ts.map +1 -1
  9. package/lib/extractors/BaseExtractor.js +184 -5
  10. package/lib/extractors/BaseExtractor.js.map +1 -1
  11. package/lib/extractors/CvaExtractor.js +1 -1
  12. package/lib/extractors/CvaExtractor.js.map +1 -1
  13. package/lib/extractors/ExpressionExtractor.d.ts.map +1 -1
  14. package/lib/extractors/ExpressionExtractor.js +35 -5
  15. package/lib/extractors/ExpressionExtractor.js.map +1 -1
  16. package/lib/extractors/JsxAttributeExtractor.js +1 -1
  17. package/lib/extractors/JsxAttributeExtractor.js.map +1 -1
  18. package/lib/extractors/TailwindVariantsExtractor.d.ts.map +1 -1
  19. package/lib/extractors/TailwindVariantsExtractor.js +1 -5
  20. package/lib/extractors/TailwindVariantsExtractor.js.map +1 -1
  21. package/lib/extractors/VariableReferenceExtractor.d.ts.map +1 -1
  22. package/lib/extractors/VariableReferenceExtractor.js +21 -0
  23. package/lib/extractors/VariableReferenceExtractor.js.map +1 -1
  24. package/lib/infrastructure/TailwindConflictDetector.d.ts +28 -18
  25. package/lib/infrastructure/TailwindConflictDetector.d.ts.map +1 -1
  26. package/lib/infrastructure/TailwindConflictDetector.js +216 -486
  27. package/lib/infrastructure/TailwindConflictDetector.js.map +1 -1
  28. package/lib/infrastructure/TailwindValidator.d.ts +15 -0
  29. package/lib/infrastructure/TailwindValidator.d.ts.map +1 -1
  30. package/lib/infrastructure/TailwindValidator.js +84 -5
  31. package/lib/infrastructure/TailwindValidator.js.map +1 -1
  32. package/lib/infrastructure/TailwindValidator.spec.js +194 -0
  33. package/lib/infrastructure/TailwindValidator.spec.js.map +1 -1
  34. package/lib/plugin/TailwindTypescriptPlugin.d.ts +26 -0
  35. package/lib/plugin/TailwindTypescriptPlugin.d.ts.map +1 -1
  36. package/lib/plugin/TailwindTypescriptPlugin.js +180 -1
  37. package/lib/plugin/TailwindTypescriptPlugin.js.map +1 -1
  38. package/lib/services/ClassNameExtractionService.d.ts +2 -2
  39. package/lib/services/ClassNameExtractionService.d.ts.map +1 -1
  40. package/lib/services/ClassNameExtractionService.js.map +1 -1
  41. package/lib/services/CompletionService.d.ts +98 -0
  42. package/lib/services/CompletionService.d.ts.map +1 -0
  43. package/lib/services/CompletionService.js +484 -0
  44. package/lib/services/CompletionService.js.map +1 -0
  45. package/lib/services/CompletionService.spec.d.ts +2 -0
  46. package/lib/services/CompletionService.spec.d.ts.map +1 -0
  47. package/lib/services/CompletionService.spec.js +334 -0
  48. package/lib/services/CompletionService.spec.js.map +1 -0
  49. package/lib/services/ConflictClassDetection.spec.js +24 -19
  50. package/lib/services/ConflictClassDetection.spec.js.map +1 -1
  51. package/lib/services/DuplicateClassDetection.spec.js +41 -60
  52. package/lib/services/DuplicateClassDetection.spec.js.map +1 -1
  53. package/lib/services/PluginConfigService.d.ts +2 -1
  54. package/lib/services/PluginConfigService.d.ts.map +1 -1
  55. package/lib/services/PluginConfigService.js +19 -12
  56. package/lib/services/PluginConfigService.js.map +1 -1
  57. package/lib/services/ValidationService.d.ts +4 -2
  58. package/lib/services/ValidationService.d.ts.map +1 -1
  59. package/lib/services/ValidationService.js +10 -7
  60. package/lib/services/ValidationService.js.map +1 -1
  61. package/package.json +4 -2
  62. package/lib/extractors/StringLiteralExtractor.d.ts +0 -12
  63. package/lib/extractors/StringLiteralExtractor.d.ts.map +0 -1
  64. package/lib/extractors/StringLiteralExtractor.js +0 -21
  65. package/lib/extractors/StringLiteralExtractor.js.map +0 -1
  66. package/lib/services/ClassNameExtractionService.original.d.ts +0 -20
  67. package/lib/services/ClassNameExtractionService.original.d.ts.map +0 -1
  68. package/lib/services/ClassNameExtractionService.original.js +0 -48
  69. package/lib/services/ClassNameExtractionService.original.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,70 @@
1
+ ## [1.4.0-beta.10](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.9...v1.4.0-beta.10) (2025-11-29)
2
+
3
+ ### ⚡ Performance
4
+
5
+ * optimize test execution with sharding and separated test commands ([0a89bdd](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/0a89bddcd77ec96eba6a2c0ff579b1c8b4d46f2d))
6
+
7
+ ## [1.4.0-beta.9](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.8...v1.4.0-beta.9) (2025-11-29)
8
+
9
+ ### ♻️ Code Refactoring
10
+
11
+ * restructure test cases into folder-based organization with JSDoc comments ([13cc790](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/13cc7900545d35b215e12618c8e7b003cfc67d02))
12
+
13
+ ## [1.4.0-beta.8](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.7...v1.4.0-beta.8) (2025-11-28)
14
+
15
+ ### ✨ Features
16
+
17
+ * add import verification for utility functions ([9d34a12](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/9d34a129cdb05d5e74cba1510d89b9b6c6afeab6))
18
+ * add namespace import verification for member expressions ([eaacf9a](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/eaacf9a808c7f63e1d2fa5a284c24e1426821aa6))
19
+
20
+ ### ♻️ Code Refactoring
21
+
22
+ * add imports to test files and update default utility functions ([698fdc2](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/698fdc2afcfbb59dcd1cf6b17c754ef4a0b9a542))
23
+
24
+ ## [1.4.0-beta.7](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.6...v1.4.0-beta.7) (2025-11-28)
25
+
26
+ ### ✨ Features
27
+
28
+ * add wildcard pattern support for allowedClasses ([a490b68](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/a490b68800b54bfde6eadef42ab97aaee51550fc))
29
+
30
+ ## [1.4.0-beta.6](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.5...v1.4.0-beta.6) (2025-11-28)
31
+
32
+ ### ✨ Features
33
+
34
+ * add spread operator support for class name validation ([a4c4ad1](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/a4c4ad1fc2ef42e7d4d87351af4fe464384c44b3))
35
+
36
+ ### 🔧 Chores
37
+
38
+ * add utility libraries and cn helper ([2ac664d](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/2ac664dc33ba4a5c52e3059ee5ec5c9257da1c78))
39
+
40
+ ## [1.4.0-beta.5](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.4...v1.4.0-beta.5) (2025-11-27)
41
+
42
+ ### ♻️ Code Refactoring
43
+
44
+ * split test cases into folder structure for better organization ([fbe6774](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/fbe67742147857fb7c01e6e925142ba26d9fe7bf))
45
+
46
+ ## [1.4.0-beta.4](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.3...v1.4.0-beta.4) (2025-11-27)
47
+
48
+ ### ✨ Features
49
+
50
+ * warn on all duplicate class occurrences ([d01d878](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/d01d87844da17e15d5608ddddb1e3fdc033c8d38))
51
+
52
+ ### 🐛 Bug Fixes
53
+
54
+ * dispose plugin in benchmark to close file watchers ([9efdc6a](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/9efdc6a8e24de4dd3000c44fc075bb765ddba1bb))
55
+
56
+ ## [1.4.0-beta.3](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.2...v1.4.0-beta.3) (2025-11-26)
57
+
58
+ ### ✨ Features
59
+
60
+ * add hot reloading for global.css changes ([c215ae6](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/c215ae68b71741b1909460df0d22606dbfd5c0f1)), closes [#ff0000](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/issues/ff0000)
61
+
62
+ ## [1.4.0-beta.2](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.4.0-beta.1...v1.4.0-beta.2) (2025-11-26)
63
+
64
+ ### ✨ Features
65
+
66
+ * add conflicting class detection ([a31c01a](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/commit/a31c01aa6e747e2949109273f7eec3f1b7ac4876))
67
+
1
68
  ## [1.4.0-beta.1](https://github.com/IvanRodriCalleja/tailwind-typescript-plugin/compare/v1.3.0...v1.4.0-beta.1) (2025-11-26)
2
69
 
3
70
  ### ✨ Features
package/README.md CHANGED
@@ -82,15 +82,27 @@ Add the plugin to the `compilerOptions.plugins` array in your `tsconfig.json`:
82
82
 
83
83
  - `globalCss` (required): Path to your global CSS file that imports Tailwind CSS. This can be relative to your project root.
84
84
 
85
- - `allowedClasses` (optional): Array of custom class names that should be treated as valid alongside Tailwind classes. Useful for project-specific or third-party utility classes that aren't part of Tailwind.
85
+ - `allowedClasses` (optional): Array of custom class names or wildcard patterns that should be treated as valid alongside Tailwind classes. Useful for project-specific or third-party utility classes that aren't part of Tailwind.
86
86
  - **Default**: `[]` (no custom classes allowed)
87
+ - **Supports wildcard patterns**:
88
+ | Pattern | Description | Example | Matches |
89
+ |---------|-------------|---------|---------|
90
+ | `prefix-*` | Matches classes starting with prefix | `custom-*` | `custom-button`, `custom-card` |
91
+ | `*-suffix` | Matches classes ending with suffix | `*-icon` | `arrow-icon`, `close-icon` |
92
+ | `*-contains-*` | Matches classes containing the string | `*-component-*` | `app-component-header` |
93
+ | `exact` | Exact match (no wildcards) | `my-class` | Only `my-class` |
87
94
  - **Example**:
88
95
  ```json
89
96
  {
90
- "allowedClasses": ["custom-button", "app-header", "project-card"]
97
+ "allowedClasses": [
98
+ "custom-*",
99
+ "*-icon",
100
+ "*-component-*",
101
+ "exact-class"
102
+ ]
91
103
  }
92
104
  ```
93
- - Classes in this list will be considered valid and won't trigger validation errors
105
+ - Classes matching any pattern will be considered valid and won't trigger validation errors
94
106
  - Works with all extraction patterns (literals, expressions, functions, arrays, etc.)
95
107
  - Combines with Tailwind classes - both are validated independently
96
108
 
@@ -128,34 +140,99 @@ Add the plugin to the `compilerOptions.plugins` array in your `tsconfig.json`:
128
140
  ```
129
141
  - **Performance impact**: Disabling unused extractors skips TypeChecker operations and symbol resolution for that library, providing faster validation
130
142
 
131
- - `utilityFunctions` (optional): Array of additional function names to validate. These will be **merged with the defaults**, so you don't lose the common ones.
132
- - **Defaults (always included)**: `['clsx', 'cn', 'classnames', 'classNames', 'cx', 'cva', 'twMerge', 'tv']`
133
- - **Add your own**: Provide custom function names that will be added to the defaults
134
- - **Example config**:
135
- ```json
136
- {
137
- "utilityFunctions": ["myCustomFn", "buildClasses"]
138
- }
139
- ```
140
- This will validate: `clsx`, `cn`, `classnames`, `classNames`, `cx`, `cva`, `twMerge`, `tv`, **`myCustomFn`**, **`buildClasses`**
143
+ - `utilityFunctions` (optional): Array of utility functions to validate. These will be **merged with the defaults**, so you don't lose the common ones. Supports two formats:
141
144
 
142
- - **Supported patterns**:
143
- ```typescript
144
- // Simple calls (validated by default):
145
- className={clsx('flex', 'items-center')}
146
- className={cn('flex', 'items-center')}
145
+ **Simple string format** (matches by function name only):
146
+ ```json
147
+ {
148
+ "utilityFunctions": ["myCustomFn", "buildClasses"]
149
+ }
150
+ ```
151
+
152
+ **Object format with import verification** (matches by name AND verifies import source):
153
+ ```json
154
+ {
155
+ "utilityFunctions": [
156
+ { "name": "merge", "from": "@/lib/utils" },
157
+ { "name": "cx", "from": "my-styling-package" }
158
+ ]
159
+ }
160
+ ```
161
+
162
+ **Mixed format** (combine both):
163
+ ```json
164
+ {
165
+ "utilityFunctions": [
166
+ "anyFunctionNamedThis",
167
+ { "name": "merge", "from": "@/lib/utils" }
168
+ ]
169
+ }
170
+ ```
147
171
 
148
- // Member expressions (nested property access):
149
- className={utils.cn('flex', 'items-center')}
150
- className={lib.clsx('flex', 'items-center')}
172
+ - **Defaults (always included)**: `clsx` (from 'clsx'), `cn` (name-only), `classnames`/`classNames`/`cx` (from 'classnames'), `twMerge` (from 'tailwind-merge'). Note: `cva` and `tv` are variant functions handled by dedicated extractors.
173
+ - **Import verification**: When using the object format with `from`, the plugin verifies the function is actually imported from that package before validating. This prevents false positives when a function with the same name exists but isn't a className utility.
174
+ - **Subpath matching**: `{ "name": "fn", "from": "my-pkg" }` also matches `import { fn } from 'my-pkg/utils'`
151
175
 
152
- // Custom functions (add via config):
153
- className={myCustomFn('flex', 'items-center')}
154
- className={buildClasses('flex', 'items-center')}
176
+ **Example configurations**:
177
+ ```json
178
+ // Simple: just add function names
179
+ {
180
+ "utilityFunctions": ["myCustomFn", "buildClasses"]
181
+ }
155
182
 
156
- // Dynamic calls (ignored, won't throw errors):
157
- className={functions['cn']('flex', 'items-center')}
158
- ```
183
+ // Precise: verify import sources
184
+ {
185
+ "utilityFunctions": [
186
+ { "name": "cn", "from": "@/lib/utils" },
187
+ { "name": "merge", "from": "tailwind-merge" }
188
+ ]
189
+ }
190
+
191
+ // Mixed: some with import verification, some without
192
+ {
193
+ "utilityFunctions": [
194
+ "anyFn",
195
+ { "name": "preciseFn", "from": "@/utils" }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ **Supported import patterns**:
201
+ ```typescript
202
+ // Named import
203
+ import { merge } from '@/lib/utils';
204
+ className={merge('flex', 'items-center')} // ✅ Validated
205
+
206
+ // Default import
207
+ import merge from '@/lib/utils';
208
+ className={merge('flex', 'items-center')} // ✅ Validated
209
+
210
+ // Aliased import
211
+ import { something as merge } from '@/lib/utils';
212
+ className={merge('flex', 'items-center')} // ✅ Validated
213
+
214
+ // Wrong import source (not validated when using object format)
215
+ import { merge } from 'different-package';
216
+ className={merge('flex', 'invalid-class')} // ⏭️ Skipped (wrong import)
217
+ ```
218
+
219
+ **Supported call patterns**:
220
+ ```typescript
221
+ // Simple calls (validated by default):
222
+ className={clsx('flex', 'items-center')}
223
+ className={cn('flex', 'items-center')}
224
+
225
+ // Member expressions (nested property access):
226
+ className={utils.cn('flex', 'items-center')}
227
+ className={lib.clsx('flex', 'items-center')}
228
+
229
+ // Custom functions (add via config):
230
+ className={myCustomFn('flex', 'items-center')}
231
+ className={buildClasses('flex', 'items-center')}
232
+
233
+ // Dynamic calls (ignored, won't throw errors):
234
+ className={functions['cn']('flex', 'items-center')}
235
+ ```
159
236
 
160
237
  ### 2. Ensure your CSS file imports Tailwind
161
238
 
@@ -245,6 +322,18 @@ const validClass = 'flex items-center';
245
322
  const baseClass = 'flex';
246
323
  <div className={[baseClass, 'items-center']}>Array with variable</div>
247
324
 
325
+ // ✅ Spread operator in arrays
326
+ const baseClasses = ['flex', 'items-center'];
327
+ <div className={[...baseClasses, 'p-4']}>Spread in array</div>
328
+
329
+ // ✅ Spread operator in function calls
330
+ <div className={cn(...baseClasses, 'p-4')}>Spread in function</div>
331
+
332
+ // ✅ Multiple spreads
333
+ const layoutClasses = ['flex', 'items-center'];
334
+ const spacingClasses = ['p-4', 'm-2'];
335
+ <div className={cn(...layoutClasses, ...spacingClasses)}>Multiple spreads</div>
336
+
248
337
  // ✅ Variables in tv() and cva()
249
338
  const buttonBase = 'font-semibold text-white';
250
339
  const button = tv({ base: buttonBase });
@@ -275,6 +364,14 @@ const badClass = 'invalid-array-class';
275
364
  // Error: The class "invalid-array-class" is not a valid Tailwind class.
276
365
  // This value is used as className via variable "badClass" on line 8
277
366
  <div className={['flex', badClass]}>Array with invalid variable</div>
367
+
368
+ // ❌ Invalid class in spread operator
369
+ const invalidClasses = ['flex', 'invalid-spread-class'];
370
+ // Error: The class "invalid-spread-class" is not a valid Tailwind class.
371
+ <div className={[...invalidClasses, 'items-center']}>Spread with invalid</div>
372
+
373
+ // ❌ Invalid class in spread within function call
374
+ <div className={cn(...invalidClasses, 'p-4')}>Function spread with invalid</div>
278
375
  ```
279
376
 
280
377
  **Custom allowed classes**:
@@ -326,6 +423,18 @@ The plugin detects duplicate classes within the same `className` attribute and s
326
423
  <div className={cn(['flex', 'flex', 'items-center'])}>In array</div>
327
424
  // Warning: Duplicate class "flex"
328
425
 
426
+ // ⚠️ Warning: Duplicates with spread operators
427
+ const baseClasses = ['flex', 'items-center'];
428
+ <div className={cn(...baseClasses, 'flex', 'items-center', 'p-4')}>Spread duplicates</div>
429
+ // Warning: Duplicate class "flex"
430
+ // Warning: Duplicate class "items-center"
431
+
432
+ // ⚠️ Warning: Duplicates between multiple spreads
433
+ const layoutClasses = ['flex', 'items-center'];
434
+ const containerClasses = ['flex', 'justify-center'];
435
+ <div className={cn(...layoutClasses, ...containerClasses)}>Multiple spread duplicates</div>
436
+ // Warning: Duplicate class "flex"
437
+
329
438
  // ✅ Valid: Same class in DIFFERENT elements (not duplicates)
330
439
  <div className="flex items-center">
331
440
  <span className="flex justify-center">Different elements</span>
@@ -479,6 +588,22 @@ The plugin intelligently handles ternary expressions - conflicts are NOT flagged
479
588
  // Warning: Both classes affect text-align
480
589
  ```
481
590
 
591
+ **Conflicts with spread operators**:
592
+
593
+ ```tsx
594
+ // ⚠️ Warning: Conflicts with spread operator
595
+ const baseClasses = ['flex', 'p-4'];
596
+ <div className={cn(...baseClasses, 'p-2', 'items-center')}>Spread conflict</div>
597
+ // Warning: Class "p-4" conflicts with "p-2". Both affect the padding property.
598
+
599
+ // ⚠️ Warning: Conflicts between multiple spreads
600
+ const smallText = ['text-sm', 'font-medium'];
601
+ const largeText = ['text-lg', 'font-bold'];
602
+ <div className={cn(...smallText, ...largeText)}>Multiple spread conflicts</div>
603
+ // Warning: Class "text-sm" conflicts with "text-lg". Both affect the font-size property.
604
+ // Warning: Class "font-medium" conflicts with "font-bold". Both affect the font-weight property.
605
+ ```
606
+
482
607
  **Conflicts in tv() and cva()**:
483
608
 
484
609
  The plugin detects conflicts within `tv()` and `cva()` base properties, but intelligently skips conflicts between base and variants since variants are designed to override base styles:
@@ -834,6 +959,15 @@ const card2 = tv({ base: 'flex justify-center' });
834
959
  Validates variables used in computed object property keys
835
960
  Example: `const myClass = 'invalid-class'; <div className={{ [myClass]: true }}>Object</div>`
836
961
 
962
+ - [X] **Spread Operator** → [`test-spread-operator/`](./example/src/test-spread-operator/)
963
+ Validates spread operators in arrays and function calls
964
+ Example: `const base = ['flex', 'invalid']; <div className={[...base, 'p-4']}>Spread</div>`
965
+ - Spread in arrays: `className={[...baseClasses, 'p-4']}`
966
+ - Spread in function calls: `className={cn(...baseClasses, 'p-4')}`
967
+ - Multiple spreads: `className={cn(...layoutClasses, ...spacingClasses)}`
968
+ - Nested spreads: Variables containing arrays with spreads are resolved
969
+ - Detects invalid classes, duplicates, and conflicts within spread expressions
970
+
837
971
  - [X] **TV Variable** → [`tv-variable.tsx`](./example/src/tv-variable.tsx)
838
972
  Validates variables in tailwind-variants tv() definitions
839
973
  Example: `const baseClasses = 'invalid-class'; const button = tv({ base: baseClasses })`
@@ -842,19 +976,19 @@ const card2 = tv({ base: 'flex justify-center' });
842
976
  Validates variables in class-variance-authority cva() definitions
843
977
  Example: `const baseClasses = 'invalid-class'; const button = cva(baseClasses)`
844
978
 
845
- - [X] **Duplicate Classes** → [`duplicate-classes.tsx`](./example/src/duplicate-classes.tsx)
979
+ - [X] **Duplicate Classes** → [`duplicate-classes/`](./example/src/duplicate-classes/)
846
980
  Detects duplicate classes within the same className attribute
847
- Example: `className="flex flex items-center"` shows warning on second `flex`
981
+ Example: `className="flex flex items-center"` shows warning on both `flex` occurrences
848
982
 
849
- - [X] **Duplicate Classes in Ternary** → [`duplicate-classes.tsx`](./example/src/duplicate-classes.tsx)
983
+ - [X] **Duplicate Classes in Ternary** → [`duplicate-classes/`](./example/src/duplicate-classes/)
850
984
  Smart detection of duplicates in ternary expressions:
851
- - Root + branch duplicate: `clsx('flex', isActive ? 'flex' : 'flex')` → Warning (true duplicate)
985
+ - Root + branch duplicate: `clsx('flex', isActive ? 'flex' : 'flex')` → Warning on all occurrences
852
986
  - Both branches same class: `clsx('mt-4', isActive ? 'flex' : 'flex')` → Warning (consider extracting)
853
987
  - Single branch only: `clsx('mt-4', isActive ? 'flex' : '')` → No warning (valid pattern)
854
988
 
855
- - [X] **Duplicate Classes in Variables with Conditionals** → [`duplicate-classes.tsx`](./example/src/duplicate-classes.tsx)
989
+ - [X] **Duplicate Classes in Variables with Conditionals** → [`duplicate-classes/`](./example/src/duplicate-classes/)
856
990
  Resolves variables containing ternary expressions and detects duplicates:
857
- - `const x = isActive ? 'flex' : 'flex'; clsx('flex', x)` → Warning (root + variable duplicate)
991
+ - `const x = isActive ? 'flex' : 'flex'; clsx('flex', x)` → Warning on all occurrences
858
992
  - `const x = isActive ? 'flex' : 'flex'; clsx('mt-4', x)` → Warning (consider extracting from variable)
859
993
  - `const x = isActive ? 'flex' : ''; clsx('mt-4', x)` → No warning (single branch)
860
994
 
@@ -879,6 +1013,16 @@ const card2 = tv({ base: 'flex justify-center' });
879
1013
  - Works with clsx, cn, tv(), cva() and other utility functions
880
1014
  - tv()/cva() base vs variant: NO conflict (variants are designed to override base)
881
1015
 
1016
+ - [X] **Utility Function Import Verification** → [`utility-function-imports/`](./example/src/utility-function-imports/)
1017
+ Validates custom utility functions with optional import source verification
1018
+ Example: `{ "name": "merge", "from": "@/lib/utils" }` only validates `merge()` if imported from `@/lib/utils`
1019
+ - Supports simple string format (name-only matching) and object format (with import verification)
1020
+ - Named imports: `import { merge } from '@/lib/utils'`
1021
+ - Default imports: `import merge from '@/lib/utils'`
1022
+ - Aliased imports: `import { something as merge } from '@/lib/utils'`
1023
+ - Subpath matching: `{ "from": "my-pkg" }` matches `import from 'my-pkg/utils'`
1024
+ - Functions from wrong import sources are skipped (prevents false positives)
1025
+
882
1026
  ## How It Works
883
1027
 
884
1028
  The plugin hooks into the TypeScript Language Service and:
@@ -902,6 +1046,34 @@ The plugin is designed for minimal performance impact:
902
1046
 
903
1047
  **Typical overhead**: <1ms per file for most files, ~2-3ms for files with many tv()/cva() calls
904
1048
 
1049
+ ## Hot Reloading
1050
+
1051
+ The plugin automatically watches your global CSS file for changes. When you modify your Tailwind configuration or add custom classes in your CSS file, the plugin will:
1052
+
1053
+ 1. **Detect changes** - Watches the `globalCss` file specified in your `tsconfig.json`
1054
+ 2. **Reload the design system** - Automatically re-parses your Tailwind configuration
1055
+ 3. **Clear caches** - Invalidates all diagnostic caches
1056
+ 4. **Refresh diagnostics** - Requests TypeScript to revalidate all open files
1057
+
1058
+ This means you can add new custom classes or modify your Tailwind theme, and the plugin will immediately recognize them without restarting your editor or TypeScript server.
1059
+
1060
+ ```css
1061
+ /* global.css */
1062
+ @import "tailwindcss";
1063
+
1064
+ /* Add a custom utility - plugin will recognize it after save */
1065
+ @utility custom-gradient {
1066
+ background: linear-gradient(to right, #ff7e5f, #feb47b);
1067
+ }
1068
+ ```
1069
+
1070
+ ```tsx
1071
+ // This will be valid immediately after saving global.css
1072
+ <div className="custom-gradient">Hot reloaded!</div>
1073
+ ```
1074
+
1075
+ **Note**: The file watcher uses debouncing (300ms) to avoid excessive reloads when making rapid changes.
1076
+
905
1077
  ## Development
906
1078
 
907
1079
  This is a monorepo using Yarn workspaces:
@@ -944,7 +1116,7 @@ Commit to main → Auto-publishes 1.0.33-beta.3
944
1116
 
945
1117
  Users can install beta versions:
946
1118
  ```bash
947
- npm install typescript-custom-plugin@beta
1119
+ npm install tailwind-typescript-plugin@beta
948
1120
  ```
949
1121
 
950
1122
  ### Stable Releases (Manual)
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript/lib/tsserverlibrary';
2
- import { ClassNameInfo, ExtractionContext } from './types';
2
+ import { ClassNameInfo, ExtractionContext, UtilityFunction } from './types';
3
3
  /**
4
4
  * Base interface for class name extractors
5
5
  * Follows the Strategy pattern for extensibility
@@ -34,7 +34,7 @@ export interface IVariantsConfig {
34
34
  */
35
35
  export interface IPluginConfig {
36
36
  globalCss?: string;
37
- utilityFunctions?: string[];
37
+ utilityFunctions?: UtilityFunction[];
38
38
  variants?: IVariantsConfig;
39
39
  allowedClasses?: string[];
40
40
  enableLogging?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/core/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE3D;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IACnC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAE9D;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,aAAa,EAAE,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,aAAa,IAAI,OAAO,CAAC;IACzB,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,gBAAgB,CAAC,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC;CACrF"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/core/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5E;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IACnC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAE9D;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,aAAa,EAAE,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,aAAa,IAAI,OAAO,CAAC;IACzB,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,gBAAgB,CAAC,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC;CACrF"}
@@ -1,4 +1,22 @@
1
1
  import * as ts from 'typescript/lib/tsserverlibrary';
2
+ /**
3
+ * Represents a utility function configuration with optional import source
4
+ * When 'from' is specified, the import will be verified before matching
5
+ *
6
+ * Examples:
7
+ * - { name: 'clsx', from: 'clsx' } - matches `import { clsx } from 'clsx'` or `import clsx from 'clsx'`
8
+ * - { name: 'cn', from: '@/lib/utils' } - matches `import { cn } from '@/lib/utils'`
9
+ */
10
+ export interface UtilityFunctionConfig {
11
+ name: string;
12
+ from: string;
13
+ }
14
+ /**
15
+ * A utility function can be either:
16
+ * - A simple string (matches by function name only, backwards compatible)
17
+ * - A UtilityFunctionConfig object (matches by name AND verifies import source)
18
+ */
19
+ export type UtilityFunction = string | UtilityFunctionConfig;
2
20
  /**
3
21
  * Represents information about a class name found in source code
4
22
  */
@@ -45,7 +63,7 @@ export interface ClassNameInfo {
45
63
  export interface ExtractionContext {
46
64
  readonly typescript: typeof ts;
47
65
  readonly sourceFile: ts.SourceFile;
48
- readonly utilityFunctions: string[];
66
+ readonly utilityFunctions: UtilityFunction[];
49
67
  readonly typeChecker?: ts.TypeChecker;
50
68
  }
51
69
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,aAAa,CAAC,EAAE;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;IACF;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,UAAU,EAAE,aAAa,EAAE,CAAC;CAC5B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,qBAAqB,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,aAAa,CAAC,EAAE;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;IACF;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,UAAU,EAAE,aAAa,EAAE,CAAC;CAC5B"}
@@ -1,11 +1,21 @@
1
1
  import * as ts from 'typescript/lib/tsserverlibrary';
2
2
  import { IClassNameExtractor } from '../core/interfaces';
3
- import { ClassNameInfo, ExtractionContext } from '../core/types';
3
+ import { ClassNameInfo, ExtractionContext, UtilityFunction } from '../core/types';
4
4
  /**
5
5
  * Abstract base class for all extractors
6
6
  * Provides common functionality and enforces the contract
7
7
  */
8
8
  export declare abstract class BaseExtractor implements IClassNameExtractor {
9
+ /**
10
+ * Cache for import mappings per file
11
+ * Maps filename -> (local identifier name -> module specifier)
12
+ */
13
+ private importCache;
14
+ /**
15
+ * Cache for namespace import mappings per file
16
+ * Maps filename -> (namespace identifier -> module specifier)
17
+ */
18
+ private namespaceImportCache;
9
19
  abstract canHandle(node: ts.Node, context: ExtractionContext): boolean;
10
20
  abstract extract(node: ts.Node, context: ExtractionContext): ClassNameInfo[];
11
21
  /**
@@ -15,7 +25,44 @@ export declare abstract class BaseExtractor implements IClassNameExtractor {
15
25
  protected extractFromStringLiteral(literal: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral, context: ExtractionContext): ClassNameInfo[];
16
26
  /**
17
27
  * Helper method to check if a function call should be validated
28
+ * Supports both simple string matching and precise import verification
29
+ */
30
+ protected shouldValidateFunctionCall(callExpression: ts.CallExpression, utilityFunctions: UtilityFunction[], context?: ExtractionContext): boolean;
31
+ /**
32
+ * Check if a function name is imported from a specific module
33
+ * Supports:
34
+ * - Named imports: import { clsx } from 'clsx'
35
+ * - Aliased imports: import { clsx as cx } from 'clsx'
36
+ * - Default imports: import clsx from 'clsx'
37
+ */
38
+ protected isImportedFrom(functionName: string, expectedModule: string, context: ExtractionContext): boolean;
39
+ /**
40
+ * Check if an object name is a namespace import from a specific module
41
+ * For: import * as utils from 'clsx' -> utils.clsx('flex')
42
+ */
43
+ protected isNamespaceImportedFrom(objectName: string, expectedModule: string, context: ExtractionContext): boolean;
44
+ /**
45
+ * Get the import map for the current file
46
+ * Caches the result for performance
47
+ */
48
+ private getImportMap;
49
+ /**
50
+ * Get the namespace import map for the current file
51
+ * Caches the result for performance
52
+ */
53
+ private getNamespaceImportMap;
54
+ /**
55
+ * Build both import maps (regular and namespace) for the current file
56
+ */
57
+ private buildImportMaps;
58
+ /**
59
+ * Clear the import cache (useful for testing or when files change)
60
+ */
61
+ clearImportCache(): void;
62
+ /**
63
+ * Check if a function name matches any utility function (by name only)
64
+ * Used for quick exclusion checks without import verification
18
65
  */
19
- protected shouldValidateFunctionCall(callExpression: ts.CallExpression, utilityFunctions: string[]): boolean;
66
+ protected isUtilityFunctionName(functionName: string, utilityFunctions: UtilityFunction[]): boolean;
20
67
  }
21
68
  //# sourceMappingURL=BaseExtractor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BaseExtractor.d.ts","sourceRoot":"","sources":["../../src/extractors/BaseExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAErD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEjE;;;GAGG;AACH,8BAAsB,aAAc,YAAW,mBAAmB;IACjE,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO;IACtE,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,aAAa,EAAE;IAE5E;;;OAGG;IACH,SAAS,CAAC,wBAAwB,CACjC,OAAO,EAAE,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,6BAA6B,EAC5D,OAAO,EAAE,iBAAiB,GACxB,aAAa,EAAE;IA0BlB;;OAEG;IACH,SAAS,CAAC,0BAA0B,CACnC,cAAc,EAAE,EAAE,CAAC,cAAc,EACjC,gBAAgB,EAAE,MAAM,EAAE,GACxB,OAAO;CAgBV"}
1
+ {"version":3,"file":"BaseExtractor.d.ts","sourceRoot":"","sources":["../../src/extractors/BaseExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAErD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAclF;;;GAGG;AACH,8BAAsB,aAAc,YAAW,mBAAmB;IACjE;;;OAGG;IACH,OAAO,CAAC,WAAW,CAAgC;IAEnD;;;OAGG;IACH,OAAO,CAAC,oBAAoB,CAAyC;IAErE,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO;IACtE,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,aAAa,EAAE;IAE5E;;;OAGG;IACH,SAAS,CAAC,wBAAwB,CACjC,OAAO,EAAE,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,6BAA6B,EAC5D,OAAO,EAAE,iBAAiB,GACxB,aAAa,EAAE;IA0BlB;;;OAGG;IACH,SAAS,CAAC,0BAA0B,CACnC,cAAc,EAAE,EAAE,CAAC,cAAc,EACjC,gBAAgB,EAAE,eAAe,EAAE,EACnC,OAAO,CAAC,EAAE,iBAAiB,GACzB,OAAO;IAyDV;;;;;;OAMG;IACH,SAAS,CAAC,cAAc,CACvB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,iBAAiB,GACxB,OAAO;IAYV;;;OAGG;IACH,SAAS,CAAC,uBAAuB,CAChC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,iBAAiB,GACxB,OAAO;IAYV;;;OAGG;IACH,OAAO,CAAC,YAAY;IAcpB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,eAAe;IAsDvB;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAKxB;;;OAGG;IACH,SAAS,CAAC,qBAAqB,CAC9B,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,eAAe,EAAE,GACjC,OAAO;CASV"}