@nordcraft/search 1.0.78 → 1.0.80

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 (167) hide show
  1. package/dist/rules/issues/actions/duplicateActionArgumentNameRule.test.js +3 -3
  2. package/dist/rules/issues/actions/duplicateActionArgumentNameRule.test.js.map +1 -1
  3. package/dist/rules/issues/actions/legacyActionRule.fix.js +1 -1
  4. package/dist/rules/issues/actions/legacyActionRule.fix.js.map +1 -1
  5. package/dist/rules/issues/actions/legacyActionRule.test.js +1 -1
  6. package/dist/rules/issues/actions/legacyActionRule.test.js.map +1 -1
  7. package/dist/rules/issues/actions/noReferenceProjectActionRule.test.js +1 -1
  8. package/dist/rules/issues/actions/noReferenceProjectActionRule.test.js.map +1 -1
  9. package/dist/rules/issues/actions/unknownActionArgumentRule.test.js +1 -1
  10. package/dist/rules/issues/actions/unknownActionArgumentRule.test.js.map +1 -1
  11. package/dist/rules/issues/actions/unknownActionEventRule.test.js +1 -1
  12. package/dist/rules/issues/actions/unknownActionEventRule.test.js.map +1 -1
  13. package/dist/rules/issues/actions/unknownProjectActionRule.test.js +1 -1
  14. package/dist/rules/issues/actions/unknownProjectActionRule.test.js.map +1 -1
  15. package/dist/rules/issues/apis/legacyApiRule.test.js +1 -1
  16. package/dist/rules/issues/apis/legacyApiRule.test.js.map +1 -1
  17. package/dist/rules/issues/apis/noReferenceApiRule.js +21 -1
  18. package/dist/rules/issues/apis/noReferenceApiRule.js.map +1 -1
  19. package/dist/rules/issues/apis/noReferenceApiRule.test.js +274 -0
  20. package/dist/rules/issues/apis/noReferenceApiRule.test.js.map +1 -1
  21. package/dist/rules/issues/components/componentRules.index.js +6 -1
  22. package/dist/rules/issues/components/componentRules.index.js.map +1 -1
  23. package/dist/rules/issues/components/invalidComponentStructureRule.js +132 -0
  24. package/dist/rules/issues/components/invalidComponentStructureRule.js.map +1 -0
  25. package/dist/rules/issues/components/invalidComponentStructureRule.test.js +38 -0
  26. package/dist/rules/issues/components/invalidComponentStructureRule.test.js.map +1 -0
  27. package/dist/rules/issues/context/noContextConsumersRule.test.js +3 -3
  28. package/dist/rules/issues/context/noContextConsumersRule.test.js.map +1 -1
  29. package/dist/rules/issues/context/unknownContextFormulaRule.test.js +1 -1
  30. package/dist/rules/issues/context/unknownContextFormulaRule.test.js.map +1 -1
  31. package/dist/rules/issues/context/unknownContextProviderFormulaRule.test.js +1 -1
  32. package/dist/rules/issues/context/unknownContextProviderFormulaRule.test.js.map +1 -1
  33. package/dist/rules/issues/context/unknownContextProviderRule.test.js +1 -1
  34. package/dist/rules/issues/context/unknownContextProviderRule.test.js.map +1 -1
  35. package/dist/rules/issues/dom/createRequiredDirectChildRule.test.js +1 -1
  36. package/dist/rules/issues/dom/createRequiredDirectChildRule.test.js.map +1 -1
  37. package/dist/rules/issues/dom/createRequiredDirectParentRule.test.js +1 -1
  38. package/dist/rules/issues/dom/createRequiredDirectParentRule.test.js.map +1 -1
  39. package/dist/rules/issues/dom/createRequiredElementAttributeRule.test.js +3 -3
  40. package/dist/rules/issues/dom/createRequiredElementAttributeRule.test.js.map +1 -1
  41. package/dist/rules/issues/dom/elementWithoutInteractiveContentRule.test.js +3 -3
  42. package/dist/rules/issues/dom/elementWithoutInteractiveContentRule.test.js.map +1 -1
  43. package/dist/rules/issues/dom/imageWithoutDimensionRule.test.js +4 -4
  44. package/dist/rules/issues/dom/imageWithoutDimensionRule.test.js.map +1 -1
  45. package/dist/rules/issues/dom/nonEmptyVoidElementRule.test.js +1 -1
  46. package/dist/rules/issues/dom/nonEmptyVoidElementRule.test.js.map +1 -1
  47. package/dist/rules/issues/events/duplicateEventTriggerRule.test.js +1 -1
  48. package/dist/rules/issues/events/duplicateEventTriggerRule.test.js.map +1 -1
  49. package/dist/rules/issues/formulas/duplicateFormulaArgumentNameRule.test.js +2 -2
  50. package/dist/rules/issues/formulas/duplicateFormulaArgumentNameRule.test.js.map +1 -1
  51. package/dist/rules/issues/formulas/legacyFormulaRule.fix.js +1 -1
  52. package/dist/rules/issues/formulas/legacyFormulaRule.fix.js.map +1 -1
  53. package/dist/rules/issues/formulas/noReferenceProjectFormulaRule.test.js +1 -1
  54. package/dist/rules/issues/formulas/noReferenceProjectFormulaRule.test.js.map +1 -1
  55. package/dist/rules/issues/formulas/unknownComponentFormulaInputRule.test.js +2 -2
  56. package/dist/rules/issues/formulas/unknownComponentFormulaInputRule.test.js.map +1 -1
  57. package/dist/rules/issues/formulas/unknownProjectFormulaInputRule.test.js +2 -2
  58. package/dist/rules/issues/formulas/unknownProjectFormulaInputRule.test.js.map +1 -1
  59. package/dist/rules/issues/formulas/unknownProjectFormulaRule.test.js +1 -1
  60. package/dist/rules/issues/formulas/unknownProjectFormulaRule.test.js.map +1 -1
  61. package/dist/rules/issues/formulas/unknownRepeatIndexFormulaRule.test.js +1 -1
  62. package/dist/rules/issues/formulas/unknownRepeatIndexFormulaRule.test.js.map +1 -1
  63. package/dist/rules/issues/formulas/unknownRepeatItemFormulaRule.test.js +1 -1
  64. package/dist/rules/issues/formulas/unknownRepeatItemFormulaRule.test.js.map +1 -1
  65. package/dist/rules/issues/logic/noStaticNodeCondition.js +5 -3
  66. package/dist/rules/issues/logic/noStaticNodeCondition.js.map +1 -1
  67. package/dist/rules/issues/logic/noStaticNodeCondition.test.js +1 -1
  68. package/dist/rules/issues/logic/noStaticNodeCondition.test.js.map +1 -1
  69. package/dist/rules/issues/logic/noUnnecessaryConditionFalsy.test.js +1 -1
  70. package/dist/rules/issues/logic/noUnnecessaryConditionFalsy.test.js.map +1 -1
  71. package/dist/rules/issues/logic/noUnnecessaryConditionTruthy.test.js +1 -1
  72. package/dist/rules/issues/logic/noUnnecessaryConditionTruthy.test.js.map +1 -1
  73. package/dist/rules/issues/miscellaneous/requireExtensionRule.test.js +1 -1
  74. package/dist/rules/issues/miscellaneous/requireExtensionRule.test.js.map +1 -1
  75. package/dist/rules/issues/miscellaneous/unknownCookieRule.test.js +1 -1
  76. package/dist/rules/issues/miscellaneous/unknownCookieRule.test.js.map +1 -1
  77. package/dist/rules/issues/routing/duplicateRouteRule.test.js +1 -1
  78. package/dist/rules/issues/routing/duplicateRouteRule.test.js.map +1 -1
  79. package/dist/rules/issues/routing/duplicateUrlParameterRule.test.js +1 -1
  80. package/dist/rules/issues/routing/duplicateUrlParameterRule.test.js.map +1 -1
  81. package/dist/rules/issues/style/noReferenceGlobalCSSVariable.js +32 -11
  82. package/dist/rules/issues/style/noReferenceGlobalCSSVariable.js.map +1 -1
  83. package/dist/rules/issues/style/noReferenceGlobalCSSVariable.test.js +55 -1
  84. package/dist/rules/issues/style/noReferenceGlobalCSSVariable.test.js.map +1 -1
  85. package/dist/rules/issues/style/styleRules.index.js +2 -0
  86. package/dist/rules/issues/style/styleRules.index.js.map +1 -1
  87. package/dist/rules/issues/style/unknownCSSVariable.js +78 -0
  88. package/dist/rules/issues/style/unknownCSSVariable.js.map +1 -0
  89. package/dist/rules/issues/style/unknownCSSVariable.test.js +337 -0
  90. package/dist/rules/issues/style/unknownCSSVariable.test.js.map +1 -0
  91. package/dist/rules/issues/workflows/duplicateWorkflowParameterRule.test.js +1 -1
  92. package/dist/rules/issues/workflows/duplicateWorkflowParameterRule.test.js.map +1 -1
  93. package/dist/rules/issues/workflows/noPostNavigateAction.js +3 -3
  94. package/dist/rules/issues/workflows/noPostNavigateAction.js.map +1 -1
  95. package/dist/rules/issues/workflows/noPostNavigateAction.test.js +1 -1
  96. package/dist/rules/issues/workflows/noPostNavigateAction.test.js.map +1 -1
  97. package/dist/rules/issues/workflows/unknownContextProviderWorkflowRule.test.js +1 -1
  98. package/dist/rules/issues/workflows/unknownContextProviderWorkflowRule.test.js.map +1 -1
  99. package/dist/rules/issues/workflows/unknownContextWorkflowRule.test.js +1 -1
  100. package/dist/rules/issues/workflows/unknownContextWorkflowRule.test.js.map +1 -1
  101. package/dist/rules/issues/workflows/unknownTriggerWorkflowParameterRule.test.js +3 -3
  102. package/dist/rules/issues/workflows/unknownTriggerWorkflowParameterRule.test.js.map +1 -1
  103. package/dist/rules/issues/workflows/unknownTriggerWorkflowRule.test.js +1 -1
  104. package/dist/rules/issues/workflows/unknownTriggerWorkflowRule.test.js.map +1 -1
  105. package/dist/rules/issues/workflows/unknownWorkflowParameterRule.test.js +1 -1
  106. package/dist/rules/issues/workflows/unknownWorkflowParameterRule.test.js.map +1 -1
  107. package/dist/searchProject.js +1 -1
  108. package/dist/searchProject.js.map +1 -1
  109. package/dist/util/removeUnused.fix.js +2 -2
  110. package/dist/util/removeUnused.fix.js.map +1 -1
  111. package/package.json +5 -4
  112. package/src/rules/issues/actions/duplicateActionArgumentNameRule.test.ts +3 -3
  113. package/src/rules/issues/actions/legacyActionRule.fix.ts +1 -1
  114. package/src/rules/issues/actions/legacyActionRule.test.ts +1 -1
  115. package/src/rules/issues/actions/noReferenceProjectActionRule.test.ts +1 -1
  116. package/src/rules/issues/actions/unknownActionArgumentRule.test.ts +1 -1
  117. package/src/rules/issues/actions/unknownActionEventRule.test.ts +1 -1
  118. package/src/rules/issues/actions/unknownProjectActionRule.test.ts +1 -1
  119. package/src/rules/issues/apis/legacyApiRule.test.ts +1 -1
  120. package/src/rules/issues/apis/noReferenceApiRule.test.ts +283 -0
  121. package/src/rules/issues/apis/noReferenceApiRule.ts +29 -1
  122. package/src/rules/issues/components/componentRules.index.ts +6 -1
  123. package/src/rules/issues/components/invalidComponentStructureRule.test.ts +41 -0
  124. package/src/rules/issues/components/invalidComponentStructureRule.ts +153 -0
  125. package/src/rules/issues/context/noContextConsumersRule.test.ts +3 -3
  126. package/src/rules/issues/context/unknownContextFormulaRule.test.ts +1 -1
  127. package/src/rules/issues/context/unknownContextProviderFormulaRule.test.ts +1 -1
  128. package/src/rules/issues/context/unknownContextProviderRule.test.ts +1 -1
  129. package/src/rules/issues/dom/createRequiredDirectChildRule.test.ts +1 -1
  130. package/src/rules/issues/dom/createRequiredDirectParentRule.test.ts +1 -1
  131. package/src/rules/issues/dom/createRequiredElementAttributeRule.test.ts +3 -3
  132. package/src/rules/issues/dom/elementWithoutInteractiveContentRule.test.ts +3 -3
  133. package/src/rules/issues/dom/imageWithoutDimensionRule.test.ts +4 -4
  134. package/src/rules/issues/dom/nonEmptyVoidElementRule.test.ts +1 -1
  135. package/src/rules/issues/events/duplicateEventTriggerRule.test.ts +1 -1
  136. package/src/rules/issues/formulas/duplicateFormulaArgumentNameRule.test.ts +2 -2
  137. package/src/rules/issues/formulas/legacyFormulaRule.fix.ts +1 -1
  138. package/src/rules/issues/formulas/noReferenceProjectFormulaRule.test.ts +1 -1
  139. package/src/rules/issues/formulas/unknownComponentFormulaInputRule.test.ts +2 -2
  140. package/src/rules/issues/formulas/unknownProjectFormulaInputRule.test.ts +2 -2
  141. package/src/rules/issues/formulas/unknownProjectFormulaRule.test.ts +1 -1
  142. package/src/rules/issues/formulas/unknownRepeatIndexFormulaRule.test.ts +1 -1
  143. package/src/rules/issues/formulas/unknownRepeatItemFormulaRule.test.ts +1 -1
  144. package/src/rules/issues/logic/noStaticNodeCondition.test.ts +1 -1
  145. package/src/rules/issues/logic/noStaticNodeCondition.ts +5 -3
  146. package/src/rules/issues/logic/noUnnecessaryConditionFalsy.test.ts +1 -1
  147. package/src/rules/issues/logic/noUnnecessaryConditionTruthy.test.ts +1 -1
  148. package/src/rules/issues/miscellaneous/requireExtensionRule.test.ts +1 -1
  149. package/src/rules/issues/miscellaneous/unknownCookieRule.test.ts +1 -1
  150. package/src/rules/issues/routing/duplicateRouteRule.test.ts +1 -1
  151. package/src/rules/issues/routing/duplicateUrlParameterRule.test.ts +1 -1
  152. package/src/rules/issues/style/noReferenceGlobalCSSVariable.test.ts +59 -1
  153. package/src/rules/issues/style/noReferenceGlobalCSSVariable.ts +38 -12
  154. package/src/rules/issues/style/styleRules.index.ts +2 -0
  155. package/src/rules/issues/style/unknownCSSVariable.test.ts +364 -0
  156. package/src/rules/issues/style/unknownCSSVariable.ts +101 -0
  157. package/src/rules/issues/workflows/duplicateWorkflowParameterRule.test.ts +1 -1
  158. package/src/rules/issues/workflows/noPostNavigateAction.test.ts +1 -1
  159. package/src/rules/issues/workflows/noPostNavigateAction.ts +6 -4
  160. package/src/rules/issues/workflows/unknownContextProviderWorkflowRule.test.ts +1 -1
  161. package/src/rules/issues/workflows/unknownContextWorkflowRule.test.ts +1 -1
  162. package/src/rules/issues/workflows/unknownTriggerWorkflowParameterRule.test.ts +3 -3
  163. package/src/rules/issues/workflows/unknownTriggerWorkflowRule.test.ts +1 -1
  164. package/src/rules/issues/workflows/unknownWorkflowParameterRule.test.ts +1 -1
  165. package/src/searchProject.ts +1 -2
  166. package/src/types.d.ts +12 -3
  167. package/src/util/removeUnused.fix.ts +4 -3
@@ -1,9 +1,11 @@
1
1
  import { invalidStyleSyntaxRule } from './invalidStyleSyntaxRule'
2
2
  import { noReferenceGlobalCSSVariableRule } from './noReferenceGlobalCSSVariable'
3
3
  import { unknownClassnameRule } from './unknownClassnameRule'
4
+ import { unknownCSSVariableRule } from './unknownCSSVariable'
4
5
 
5
6
  export default [
6
7
  invalidStyleSyntaxRule,
7
8
  unknownClassnameRule,
9
+ unknownCSSVariableRule,
8
10
  noReferenceGlobalCSSVariableRule,
9
11
  ]
@@ -0,0 +1,364 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { searchProject } from '../../../searchProject'
3
+ import { unknownCSSVariableRule } from './unknownCSSVariable'
4
+
5
+ describe('unknownCSSVariableRule', () => {
6
+ test('should not report CSS variables that are defined in the global theme', () => {
7
+ const problems = Array.from(
8
+ searchProject({
9
+ files: {
10
+ themes: {
11
+ Default: {
12
+ fonts: [],
13
+ propertyDefinitions: {
14
+ '--global-color': {
15
+ syntax: { type: 'primitive', name: 'color' },
16
+ description: 'A global color',
17
+ inherits: true,
18
+ initialValue: 'blue',
19
+ values: {},
20
+ },
21
+ '--local-margin': {
22
+ syntax: { type: 'primitive', name: 'length-percentage' },
23
+ description: 'A local margin',
24
+ inherits: true,
25
+ initialValue: '10px',
26
+ values: {},
27
+ },
28
+ },
29
+ },
30
+ },
31
+ formulas: {},
32
+ components: {
33
+ test: {
34
+ name: 'test',
35
+ nodes: {
36
+ root: {
37
+ tag: 'div',
38
+ type: 'element',
39
+ attrs: {},
40
+ style: {
41
+ color: 'var(--global-color)',
42
+ margin: 'calc(var(--local-margin) + 5px)',
43
+ },
44
+ events: {},
45
+ classes: {},
46
+ children: [],
47
+ },
48
+ },
49
+ formulas: {},
50
+ apis: {},
51
+ attributes: {},
52
+ variables: {},
53
+ },
54
+ },
55
+ },
56
+ rules: [unknownCSSVariableRule],
57
+ }),
58
+ )
59
+
60
+ expect(problems).toBeEmpty()
61
+ })
62
+
63
+ test('should not report CSS variables that are defined in ancestors', () => {
64
+ const problems = Array.from(
65
+ searchProject({
66
+ files: {
67
+ themes: {
68
+ Default: {
69
+ fonts: [],
70
+ propertyDefinitions: {},
71
+ },
72
+ },
73
+ formulas: {},
74
+ components: {
75
+ test: {
76
+ name: 'test',
77
+ nodes: {
78
+ root: {
79
+ tag: 'div',
80
+ type: 'element',
81
+ attrs: {},
82
+ style: {},
83
+ events: {},
84
+ classes: {},
85
+ children: ['child'],
86
+ customProperties: {
87
+ '--local-color': {
88
+ formula: {
89
+ type: 'value',
90
+ value: 'blue',
91
+ },
92
+ },
93
+ },
94
+ },
95
+ child: {
96
+ tag: 'div',
97
+ type: 'element',
98
+ attrs: {},
99
+ customProperties: {
100
+ '--own-color': {
101
+ formula: {
102
+ type: 'value',
103
+ value: 'red',
104
+ },
105
+ },
106
+ },
107
+ style: {
108
+ margin: 'var(--local-color)',
109
+ color: 'var(--own-color)',
110
+ },
111
+ events: {},
112
+ classes: {},
113
+ children: [],
114
+ },
115
+ },
116
+ formulas: {},
117
+ apis: {},
118
+ attributes: {},
119
+ variables: {},
120
+ },
121
+ },
122
+ },
123
+ rules: [unknownCSSVariableRule],
124
+ }),
125
+ )
126
+
127
+ expect(problems).toBeEmpty()
128
+ })
129
+
130
+ test('should report CSS variables that are not defined globally or locally', () => {
131
+ const problems = Array.from(
132
+ searchProject({
133
+ files: {
134
+ themes: {
135
+ Default: {
136
+ fonts: [],
137
+ propertyDefinitions: {},
138
+ },
139
+ },
140
+ formulas: {},
141
+ components: {
142
+ test: {
143
+ name: 'test',
144
+ nodes: {
145
+ root: {
146
+ tag: 'div',
147
+ type: 'element',
148
+ attrs: {},
149
+ style: {
150
+ color: 'var(--undefined-global)',
151
+ margin: 'var(--undefined-local)',
152
+ },
153
+ events: {},
154
+ classes: {},
155
+ children: [],
156
+ },
157
+ },
158
+ formulas: {},
159
+ apis: {},
160
+ attributes: {},
161
+ variables: {},
162
+ },
163
+ },
164
+ },
165
+ rules: [unknownCSSVariableRule],
166
+ }),
167
+ )
168
+
169
+ expect(problems).toHaveLength(2)
170
+ expect(problems[0].details).toEqual({ name: '--undefined-global' })
171
+ expect(problems[1].details).toEqual({ name: '--undefined-local' })
172
+ })
173
+
174
+ test('should report problem if local variable is used in parent but only defined in child', () => {
175
+ const problems = Array.from(
176
+ searchProject({
177
+ files: {
178
+ themes: {
179
+ Default: {
180
+ fonts: [],
181
+ propertyDefinitions: {},
182
+ },
183
+ },
184
+ formulas: {},
185
+ components: {
186
+ test: {
187
+ name: 'test',
188
+ nodes: {
189
+ root: {
190
+ tag: 'div',
191
+ type: 'element',
192
+ attrs: {},
193
+ style: {
194
+ color: 'var(--child-only-variable)',
195
+ },
196
+ events: {},
197
+ classes: {},
198
+ children: ['child'],
199
+ },
200
+ child: {
201
+ tag: 'div',
202
+ type: 'element',
203
+ attrs: {},
204
+ customProperties: {
205
+ '--child-only-variable': {
206
+ formula: {
207
+ type: 'value',
208
+ value: 'red',
209
+ },
210
+ },
211
+ },
212
+ style: {},
213
+ events: {},
214
+ classes: {},
215
+ children: [],
216
+ },
217
+ },
218
+ formulas: {},
219
+ apis: {},
220
+ attributes: {},
221
+ variables: {},
222
+ },
223
+ },
224
+ },
225
+ rules: [unknownCSSVariableRule],
226
+ }),
227
+ )
228
+
229
+ expect(problems).toHaveLength(1)
230
+ })
231
+
232
+ test('should not report when legacy style variables are used', () => {
233
+ const problems = Array.from(
234
+ searchProject({
235
+ files: {
236
+ themes: {
237
+ Default: {
238
+ fonts: [],
239
+ propertyDefinitions: {},
240
+ },
241
+ },
242
+ formulas: {},
243
+ components: {
244
+ test: {
245
+ name: 'test',
246
+ nodes: {
247
+ root: {
248
+ tag: 'div',
249
+ type: 'element',
250
+ attrs: {},
251
+ 'style-variables': [
252
+ {
253
+ name: 'legacy-variable',
254
+ formula: {
255
+ type: 'value',
256
+ value: '10px',
257
+ },
258
+ category: 'spacing',
259
+ },
260
+ ],
261
+ style: {
262
+ margin: 'var(--legacy-variable)',
263
+ },
264
+ events: {},
265
+ classes: {},
266
+ children: [],
267
+ },
268
+ },
269
+ formulas: {},
270
+ apis: {},
271
+ attributes: {},
272
+ variables: {},
273
+ },
274
+ },
275
+ },
276
+ rules: [unknownCSSVariableRule],
277
+ }),
278
+ )
279
+
280
+ expect(problems).toBeEmpty()
281
+ })
282
+
283
+ test('should not report when declared in a style', () => {
284
+ const problems = Array.from(
285
+ searchProject({
286
+ files: {
287
+ themes: {
288
+ Default: {
289
+ fonts: [],
290
+ propertyDefinitions: {},
291
+ },
292
+ },
293
+ formulas: {},
294
+ components: {
295
+ test: {
296
+ name: 'test',
297
+ nodes: {
298
+ root: {
299
+ tag: 'div',
300
+ type: 'element',
301
+ attrs: {},
302
+ style: {
303
+ '--declared-in-style': '10px',
304
+ margin: 'var(--declared-in-style)',
305
+ },
306
+ events: {},
307
+ classes: {},
308
+ children: [],
309
+ },
310
+ },
311
+ formulas: {},
312
+ apis: {},
313
+ attributes: {},
314
+ variables: {},
315
+ },
316
+ },
317
+ },
318
+ rules: [unknownCSSVariableRule],
319
+ }),
320
+ )
321
+
322
+ expect(problems).toBeEmpty()
323
+ })
324
+
325
+ test('should not report when a legacy theme exists (no propertyDefinitions)', () => {
326
+ const problems = Array.from(
327
+ searchProject({
328
+ files: {
329
+ themes: {
330
+ Default: {
331
+ fonts: [],
332
+ },
333
+ },
334
+ formulas: {},
335
+ components: {
336
+ test: {
337
+ name: 'test',
338
+ nodes: {
339
+ root: {
340
+ tag: 'div',
341
+ type: 'element',
342
+ attrs: {},
343
+ style: {
344
+ color: 'var(--legacy-theme-variable)',
345
+ },
346
+ events: {},
347
+ classes: {},
348
+ children: [],
349
+ },
350
+ },
351
+ formulas: {},
352
+ apis: {},
353
+ attributes: {},
354
+ variables: {},
355
+ },
356
+ },
357
+ },
358
+ rules: [unknownCSSVariableRule],
359
+ }),
360
+ )
361
+
362
+ expect(problems).toBeEmpty()
363
+ })
364
+ })
@@ -0,0 +1,101 @@
1
+ import type { Rule } from '../../../types'
2
+
3
+ const REGEX = /var\(\s*(--[\w-]+)/g
4
+
5
+ export const unknownCSSVariableRule: Rule<{
6
+ name: string
7
+ }> = {
8
+ code: 'unknown css variable',
9
+ level: 'warning',
10
+ category: 'Unknown Reference',
11
+ visit: (report, { path, value, nodeType, files, memo }) => {
12
+ if (
13
+ nodeType !== 'style-declaration' ||
14
+ typeof value.styleValue !== 'string'
15
+ ) {
16
+ return
17
+ }
18
+
19
+ const theme = files.themes?.Default
20
+ // Issue rule only available for projects using v2 themes
21
+ if (theme?.propertyDefinitions === undefined) {
22
+ return
23
+ }
24
+
25
+ // Has a style property definition
26
+ const vars = [...value.styleValue.toString().matchAll(REGEX)].map(
27
+ ([_, varName]) => varName,
28
+ )
29
+ if (vars.length === 0) {
30
+ return
31
+ }
32
+
33
+ const themeCssVariables = theme.propertyDefinitions
34
+ const [_fileType, componentName, _nodes, nodeName] = path
35
+ const localCssVariables = memo(
36
+ `component-css-variables-${componentName}-${nodeName}`,
37
+ () => {
38
+ const vars = new Set<string>()
39
+ const component = files.components[componentName]
40
+ if (!component) {
41
+ return vars
42
+ }
43
+
44
+ const visitVars = (nodeName: string) => {
45
+ const node = component?.nodes?.[nodeName]
46
+ if (!node) {
47
+ return
48
+ }
49
+
50
+ if (node.type === 'component' || node.type === 'element') {
51
+ Object.keys(node.customProperties ?? {}).forEach((varName) => {
52
+ vars.add(varName)
53
+ })
54
+ Object.values(node.variants ?? {}).forEach((variant) => {
55
+ Object.keys(variant.customProperties ?? {}).forEach((varName) => {
56
+ vars.add(varName)
57
+ })
58
+ })
59
+
60
+ // Also add legacy style variables
61
+ if (node.type === 'element' && node['style-variables']) {
62
+ node['style-variables'].forEach((styleVar) => {
63
+ vars.add(`--${styleVar.name}`)
64
+ })
65
+ }
66
+
67
+ // Add if declared in any parent styles object
68
+ Object.keys(node.style ?? {}).forEach((styleKey) => {
69
+ if (styleKey.startsWith('--')) {
70
+ vars.add(styleKey)
71
+ }
72
+ })
73
+ Object.values(node.variants ?? {}).forEach((variant) => {
74
+ Object.keys(variant.style ?? {}).forEach((styleKey) => {
75
+ if (styleKey.startsWith('--')) {
76
+ vars.add(styleKey)
77
+ }
78
+ })
79
+ })
80
+ }
81
+
82
+ const parent = Object.entries(component.nodes ?? {}).find(([_, n]) =>
83
+ n.children?.includes(nodeName),
84
+ )
85
+ if (parent) {
86
+ visitVars(parent[0])
87
+ }
88
+ }
89
+
90
+ visitVars(nodeName.toString())
91
+ return vars
92
+ },
93
+ )
94
+
95
+ for (const varName of vars) {
96
+ if (!(varName in themeCssVariables) && !localCssVariables.has(varName)) {
97
+ report(path, { name: varName })
98
+ }
99
+ }
100
+ },
101
+ }
@@ -101,6 +101,6 @@ describe('duplicateWorkflowParameterRule', () => {
101
101
  }),
102
102
  )
103
103
 
104
- expect(problems).toHaveLength(0)
104
+ expect(problems).toBeEmpty()
105
105
  })
106
106
  })
@@ -457,7 +457,7 @@ describe('find noPostNavigateAction', () => {
457
457
  }),
458
458
  )
459
459
 
460
- expect(problems).toHaveLength(0)
460
+ expect(problems).toBeEmpty()
461
461
  })
462
462
  })
463
463
 
@@ -1,5 +1,5 @@
1
1
  import { get, set } from '@nordcraft/core/dist/utils/collections'
2
- import type { ActionModelNode, Rule } from '../../../types'
2
+ import type { ActionModelNode, FixFunctionArgs, Rule } from '../../../types'
3
3
 
4
4
  export const noPostNavigateAction: Rule<{ parameter: string }> = {
5
5
  code: 'no post navigate action',
@@ -13,7 +13,7 @@ export const noPostNavigateAction: Rule<{ parameter: string }> = {
13
13
  ) {
14
14
  return
15
15
  }
16
- const actionsArrayPath = path.slice(0, -1).map((p) => String(p))
16
+ const actionsArrayPath = path.slice(0, -1)
17
17
  const actions = get(files, actionsArrayPath)
18
18
  if (!Array.isArray(actions)) {
19
19
  return
@@ -29,8 +29,10 @@ export const noPostNavigateAction: Rule<{ parameter: string }> = {
29
29
  }
30
30
  },
31
31
  fixes: {
32
- 'delete-following-actions': ({ path, files }: ActionModelNode) => {
33
- const actionsArrayPath = path.slice(0, -1).map((p) => String(p))
32
+ 'delete-following-actions': ({
33
+ data: { path, files },
34
+ }: FixFunctionArgs<ActionModelNode>) => {
35
+ const actionsArrayPath = path.slice(0, -1)
34
36
  const actions = get(files, actionsArrayPath)
35
37
  const actionIndex = path.at(-1)
36
38
  if (actionIndex === undefined || !Array.isArray(actions)) {
@@ -142,6 +142,6 @@ describe('unknownContextProviderWorkflowRule', () => {
142
142
  }),
143
143
  )
144
144
 
145
- expect(problems).toHaveLength(0)
145
+ expect(problems).toBeEmpty()
146
146
  })
147
147
  })
@@ -88,6 +88,6 @@ describe('unknownContextWorkflowRule', () => {
88
88
  }),
89
89
  )
90
90
 
91
- expect(problems).toHaveLength(0)
91
+ expect(problems).toBeEmpty()
92
92
  })
93
93
  })
@@ -126,7 +126,7 @@ describe('unknownTriggerWorkflowParameterRule', () => {
126
126
  }),
127
127
  )
128
128
 
129
- expect(problems).toHaveLength(0)
129
+ expect(problems).toBeEmpty()
130
130
  })
131
131
  test('should detect unknown workflow parameters in context workflows', () => {
132
132
  const problems = Array.from(
@@ -300,7 +300,7 @@ describe('unknownTriggerWorkflowParameterRule', () => {
300
300
  }),
301
301
  )
302
302
 
303
- expect(problems).toHaveLength(0)
303
+ expect(problems).toBeEmpty()
304
304
  })
305
305
  test('should detect unknown workflow parameters in context workflows from packages', () => {
306
306
  const problems = Array.from(
@@ -480,6 +480,6 @@ describe('unknownTriggerWorkflowParameterRule', () => {
480
480
  }),
481
481
  )
482
482
 
483
- expect(problems).toHaveLength(0)
483
+ expect(problems).toBeEmpty()
484
484
  })
485
485
  })
@@ -100,6 +100,6 @@ describe('unknownTriggerWorkflowRule', () => {
100
100
  }),
101
101
  )
102
102
 
103
- expect(problems).toHaveLength(0)
103
+ expect(problems).toBeEmpty()
104
104
  })
105
105
  })
@@ -111,6 +111,6 @@ describe('unknownWorkflowParameterRule', () => {
111
111
  }),
112
112
  )
113
113
 
114
- expect(problems).toHaveLength(0)
114
+ expect(problems).toBeEmpty()
115
115
  })
116
116
  })
@@ -284,8 +284,7 @@ function* visitNode({
284
284
  const ruleFixes = rule.fixes[fixOptions.fixType]?.(
285
285
  // We must use the path from the report, not the original path
286
286
  // because the report might be for a subpath
287
- { ...data, path },
288
- state,
287
+ { data: { ...data, path }, details, state },
289
288
  )
290
289
  if (ruleFixes) {
291
290
  fixedFiles = ruleFixes
package/src/types.d.ts CHANGED
@@ -34,6 +34,7 @@ import type { NoReferenceApiServiceRuleFix } from './rules/issues/apis/noReferen
34
34
  import type { UnknownApiServiceRuleFix } from './rules/issues/apis/unknownApiServiceRule'
35
35
  import type { NoReferenceAttributeRuleFix } from './rules/issues/attributes/noReferenceAttributeRule'
36
36
  import type { UnknownComponentAttributeRuleFix } from './rules/issues/attributes/unknownComponentAttributeRule'
37
+ import type { ChangeDataTypeFix } from './rules/issues/components/invalidComponentStructureRule'
37
38
  import type { NoReferenceComponentRuleFix } from './rules/issues/components/noReferenceComponentRule'
38
39
  import type { NoReferenceEventRuleFix } from './rules/issues/events/noReferenceEventRule'
39
40
  import type { LegacyFormulaRuleFix } from './rules/issues/formulas/legacyFormulaRule'
@@ -57,6 +58,7 @@ type Code =
57
58
  | 'invalid api proxy cookie setting'
58
59
  | 'invalid element child'
59
60
  | 'invalid style syntax'
61
+ | 'invalid component structure'
60
62
  | 'legacy action'
61
63
  | 'legacy api'
62
64
  | 'legacy formula'
@@ -118,6 +120,7 @@ type Code =
118
120
  | 'unknown url parameter'
119
121
  | 'unknown variable setter'
120
122
  | 'unknown variable'
123
+ | 'unknown css variable'
121
124
  | 'unknown trigger workflow parameter'
122
125
  | 'unknown workflow parameter'
123
126
 
@@ -401,6 +404,7 @@ export type NodeType =
401
404
  | StyleVariantNode
402
405
 
403
406
  type FixType =
407
+ | ChangeDataTypeFix
404
408
  | InvalidStyleSyntaxRuleFix
405
409
  | LegacyActionRuleFix
406
410
  | LegacyFormulaRuleFix
@@ -433,7 +437,12 @@ export interface Rule<T = unknown, V = NodeType> {
433
437
  fixes?: Partial<Record<FixType, FixFunction>>
434
438
  }
435
439
 
436
- export type FixFunction<T extends NodeType> = (
437
- data: T,
438
- state?: ApplicationState,
440
+ interface FixFunctionArgs<Data extends NodeType, Details = unknown> {
441
+ data: Data
442
+ details?: Details
443
+ state?: ApplicationState
444
+ }
445
+
446
+ export type FixFunction<Data extends NodeType, Details = unknown> = (
447
+ args: FixFunctionArgs<Data, Details>,
439
448
  ) => ProjectFiles | void
@@ -2,13 +2,14 @@ import type { NodeModel } from '@nordcraft/core/dist/component/component.types'
2
2
  import { get, omit, set } from '@nordcraft/core/dist/utils/collections'
3
3
  import type { FixFunction, NodeType } from '../types'
4
4
 
5
- export const removeFromPathFix: FixFunction<NodeType> = ({ path, files }) =>
6
- omit(files, path)
5
+ export const removeFromPathFix: FixFunction<NodeType> = ({
6
+ data: { path, files },
7
+ }) => omit(files, path)
7
8
 
8
9
  /**
9
10
  * Same as removeFromPathFix, but also cleans up any references to the removed node.
10
11
  */
11
- export const removeNodeFromPathFix: FixFunction<NodeType> = (data) => {
12
+ export const removeNodeFromPathFix: FixFunction<NodeType> = ({ data }) => {
12
13
  const nodeId = String(data.path[data.path.length - 1])
13
14
  const componentNodesPath = data.path.slice(0, -1).map(String)
14
15
  const nodes = structuredClone(get(data.files, componentNodesPath)) as Record<