@nordcraft/search 1.0.45 → 1.0.47

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 (53) hide show
  1. package/dist/problems.worker.js +11 -3
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/attributes/unknownComponentAttributeRule.js +33 -0
  4. package/dist/rules/attributes/unknownComponentAttributeRule.js.map +1 -0
  5. package/dist/rules/attributes/unknownComponentAttributeRule.test.js +145 -0
  6. package/dist/rules/attributes/unknownComponentAttributeRule.test.js.map +1 -0
  7. package/dist/rules/logic/noStaticNodeCondition.js +29 -0
  8. package/dist/rules/logic/noStaticNodeCondition.js.map +1 -0
  9. package/dist/rules/logic/noStaticNodeCondition.test.js +274 -0
  10. package/dist/rules/logic/noStaticNodeCondition.test.js.map +1 -0
  11. package/dist/rules/logic/noUnnecessaryConditionFalsy.js +5 -1
  12. package/dist/rules/logic/noUnnecessaryConditionFalsy.js.map +1 -1
  13. package/dist/rules/logic/noUnnecessaryConditionTruthy.js +8 -4
  14. package/dist/rules/logic/noUnnecessaryConditionTruthy.js.map +1 -1
  15. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js +33 -4
  16. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js.map +1 -1
  17. package/dist/rules/noReferenceNodeRule.js +10 -8
  18. package/dist/rules/noReferenceNodeRule.js.map +1 -1
  19. package/dist/rules/noReferenceNodeRule.test.js +1 -1
  20. package/dist/rules/style/invalidStyleSyntaxRule.js +2 -2
  21. package/dist/rules/style/invalidStyleSyntaxRule.test.js +1 -1
  22. package/dist/rules/workflows/noPostNavigateAction.js +13 -2
  23. package/dist/rules/workflows/noPostNavigateAction.js.map +1 -1
  24. package/dist/rules/workflows/noPostNavigateAction.test.js +102 -1
  25. package/dist/rules/workflows/noPostNavigateAction.test.js.map +1 -1
  26. package/dist/util/contextlessEvaluateFormula.js +77 -0
  27. package/dist/util/contextlessEvaluateFormula.js.map +1 -0
  28. package/dist/util/contextlessEvaluateFormula.test.js +152 -0
  29. package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
  30. package/dist/util/helpers.test.js +1 -1
  31. package/dist/util/helpers.test.js.map +1 -1
  32. package/dist/util/removeUnused.fix.js +18 -1
  33. package/dist/util/removeUnused.fix.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/problems.worker.ts +18 -5
  36. package/src/rules/attributes/unknownComponentAttributeRule.test.ts +156 -0
  37. package/src/rules/attributes/unknownComponentAttributeRule.ts +40 -0
  38. package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
  39. package/src/rules/logic/noStaticNodeCondition.ts +43 -0
  40. package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
  41. package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
  42. package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
  43. package/src/rules/noReferenceNodeRule.test.ts +1 -1
  44. package/src/rules/noReferenceNodeRule.ts +17 -10
  45. package/src/rules/style/invalidStyleSyntaxRule.test.ts +1 -1
  46. package/src/rules/style/invalidStyleSyntaxRule.ts +3 -3
  47. package/src/rules/workflows/noPostNavigateAction.test.ts +111 -1
  48. package/src/rules/workflows/noPostNavigateAction.ts +20 -3
  49. package/src/types.d.ts +12 -0
  50. package/src/util/contextlessEvaluateFormula.test.ts +190 -0
  51. package/src/util/contextlessEvaluateFormula.ts +110 -0
  52. package/src/util/helpers.test.ts +1 -1
  53. package/src/util/removeUnused.fix.ts +29 -1
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.test.js","sourceRoot":"","sources":["../../src/util/helpers.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helpers.test.js","sourceRoot":"","sources":["../../src/util/helpers.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,20 @@
1
- import { omit } from '@nordcraft/core/dist/utils/collections';
1
+ import { get, omit, set } from '@nordcraft/core/dist/utils/collections';
2
2
  export const removeFromPathFix = ({ path, files }) => omit(files, path);
3
+ /**
4
+ * Same as removeFromPathFix, but also removes the node from its parent's children.
5
+ */
6
+ export const removeNodeFromPathFix = (data) => {
7
+ if (data.nodeType !== 'component-node') {
8
+ throw new Error('removeNodeFromPathFix can only be used on component nodes');
9
+ }
10
+ const componentNodesPath = data.path.slice(0, -1).map(String);
11
+ const filesWithoutNode = removeFromPathFix(data);
12
+ const nodes = get(filesWithoutNode, componentNodesPath);
13
+ for (const key in nodes) {
14
+ if (nodes[key].children?.includes(data.path[data.path.length - 1])) {
15
+ nodes[key].children = nodes[key].children.filter((p) => p !== data.path[data.path.length - 1]);
16
+ }
17
+ }
18
+ return set(filesWithoutNode, componentNodesPath, nodes);
19
+ };
3
20
  //# sourceMappingURL=removeUnused.fix.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"removeUnused.fix.js","sourceRoot":"","sources":["../../src/util/removeUnused.fix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wCAAwC,CAAA;AAG7D,MAAM,CAAC,MAAM,iBAAiB,GAA0B,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAC1E,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA"}
1
+ {"version":3,"file":"removeUnused.fix.js","sourceRoot":"","sources":["../../src/util/removeUnused.fix.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,wCAAwC,CAAA;AAGvE,MAAM,CAAC,MAAM,iBAAiB,GAA0B,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAC1E,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;AAEnB;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA0B,CAAC,IAAI,EAAE,EAAE;IACnE,IAAI,IAAI,CAAC,QAAQ,KAAK,gBAAgB,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAA;IAC9E,CAAC;IAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC7D,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IAChD,MAAM,KAAK,GAAG,GAAG,CAAC,gBAAgB,EAAE,kBAAkB,CAGrD,CAAA;IACD,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IACE,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC,EACxE,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAC7C,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAA;AACzD,CAAC,CAAA"}
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/nordcraftengine/nordcraft",
7
7
  "dependencies": {
8
- "@nordcraft/ssr": "1.0.45",
8
+ "@nordcraft/ssr": "1.0.47",
9
9
  "jsondiffpatch": "0.7.3",
10
10
  "postcss": "8.5.6"
11
11
  },
@@ -19,5 +19,5 @@
19
19
  "test:watch:only": "bun test --watch --only"
20
20
  },
21
21
  "files": ["dist", "src"],
22
- "version": "1.0.45"
22
+ "version": "1.0.47"
23
23
  }
@@ -12,6 +12,7 @@ import { unknownApiInputRule } from './rules/apis/unknownApiInputRule'
12
12
  import { unknownApiRule } from './rules/apis/unknownApiRule'
13
13
  import { noReferenceAttributeRule } from './rules/attributes/noReferenceAttributeRule'
14
14
  import { unknownAttributeRule } from './rules/attributes/unknownAttributeRule'
15
+ import { unknownComponentAttributeRule } from './rules/attributes/unknownComponentAttributeRule'
15
16
  import { noReferenceComponentRule } from './rules/components/noReferenceComponentRule'
16
17
  import { unknownComponentRule } from './rules/components/unknownComponentRule'
17
18
  import { noContextConsumersRule } from './rules/context/noContextConsumersRule'
@@ -37,6 +38,7 @@ import { unknownFormulaRule } from './rules/formulas/unknownFormulaRule'
37
38
  import { unknownProjectFormulaRule } from './rules/formulas/unknownProjectFormulaRule'
38
39
  import { unknownRepeatIndexFormulaRule } from './rules/formulas/unknownRepeatIndexFormulaRule'
39
40
  import { unknownRepeatItemFormulaRule } from './rules/formulas/unknownRepeatItemFormulaRule'
41
+ import { noStaticNodeCondition } from './rules/logic/noStaticNodeCondition'
40
42
  import { noUnnecessaryConditionFalsy } from './rules/logic/noUnnecessaryConditionFalsy'
41
43
  import { noUnnecessaryConditionTruthy } from './rules/logic/noUnnecessaryConditionTruthy'
42
44
  import { noReferenceNodeRule } from './rules/noReferenceNodeRule'
@@ -162,6 +164,7 @@ const RULES = [
162
164
  noReferenceProjectActionRule,
163
165
  noReferenceProjectFormulaRule,
164
166
  noReferenceVariableRule,
167
+ noStaticNodeCondition,
165
168
  noUnnecessaryConditionFalsy,
166
169
  noUnnecessaryConditionTruthy,
167
170
  requireExtensionRule,
@@ -178,6 +181,7 @@ const RULES = [
178
181
  unknownContextProviderWorkflowRule,
179
182
  unknownContextWorkflowRule,
180
183
  unknownCookieRule,
184
+ unknownComponentAttributeRule,
181
185
  unknownEventRule,
182
186
  unknownFormulaRule,
183
187
  unknownProjectActionRule,
@@ -196,21 +200,25 @@ const RULES = [
196
200
  ]
197
201
 
198
202
  interface FindProblemsArgs {
203
+ id: string
199
204
  files: ProjectFiles
200
205
  options?: Options
201
206
  }
202
207
 
203
208
  interface FixProblemsArgs {
209
+ id: string
204
210
  files: ProjectFiles
205
211
  options?: Options
206
212
  fixRule: Code
207
213
  fixType: FixType
208
- id: string
209
214
  }
210
215
 
211
216
  type Message = FindProblemsArgs | FixProblemsArgs
212
217
 
213
- type FindProblemsResponse = Result[]
218
+ interface FindProblemsResponse {
219
+ id: string
220
+ results: Result[]
221
+ }
214
222
 
215
223
  interface FixProblemsResponse {
216
224
  id: string
@@ -225,6 +233,11 @@ const respond = (data: Response) => postMessage(data)
225
233
 
226
234
  const findProblems = (data: FindProblemsArgs) => {
227
235
  const { files, options = {} } = data
236
+ const idRespond = (results: Result[]) =>
237
+ respond({
238
+ id: data.id,
239
+ results,
240
+ })
228
241
  const rules = RULES.filter(
229
242
  (rule) =>
230
243
  (!options.categories || options.categories.includes(rule.category)) &&
@@ -250,7 +263,7 @@ const findProblems = (data: FindProblemsArgs) => {
250
263
  case 'per-file': {
251
264
  if (fileType !== problem.path[0] || fileName !== problem.path[1]) {
252
265
  if (batch.length > 0) {
253
- respond(batch)
266
+ idRespond(batch)
254
267
  }
255
268
  batch = []
256
269
  fileType = problem.path[0]
@@ -264,7 +277,7 @@ const findProblems = (data: FindProblemsArgs) => {
264
277
  default: {
265
278
  batch.push(problem)
266
279
  if (batch.length >= (options.batchSize ?? 1)) {
267
- respond(batch)
280
+ idRespond(batch)
268
281
  batch = []
269
282
  }
270
283
  break
@@ -273,7 +286,7 @@ const findProblems = (data: FindProblemsArgs) => {
273
286
  }
274
287
 
275
288
  // Send the remaining results
276
- respond(batch)
289
+ idRespond(batch)
277
290
  }
278
291
 
279
292
  const fixProblems = (data: FixProblemsArgs) => {
@@ -0,0 +1,156 @@
1
+ import type { ComponentNodeModel } from '@nordcraft/core/dist/component/component.types'
2
+ import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils'
3
+ import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
4
+ import { describe, expect, test } from 'bun:test'
5
+ import { fixProject } from '../../fixProject'
6
+ import { searchProject } from '../../searchProject'
7
+ import { unknownComponentAttributeRule } from './unknownComponentAttributeRule'
8
+
9
+ describe('find unknownComponentAttribute', () => {
10
+ test('should report unknown component attributes', () => {
11
+ const problems = Array.from(
12
+ searchProject({
13
+ files: {
14
+ components: {
15
+ referenced: {
16
+ name: 'referenced',
17
+ nodes: {},
18
+ attributes: {
19
+ first: {
20
+ name: 'first',
21
+ testValue: valueFormula(null),
22
+ },
23
+ second: {
24
+ name: 'second',
25
+ testValue: valueFormula(null),
26
+ },
27
+ },
28
+ apis: {},
29
+ formulas: {},
30
+ variables: {},
31
+ },
32
+ test: {
33
+ name: 'test',
34
+ nodes: {
35
+ root: {
36
+ type: 'component',
37
+ name: 'referenced',
38
+ events: {},
39
+ children: [],
40
+ style: {},
41
+ attrs: {
42
+ first: valueFormula('exists'),
43
+ third: valueFormula('does not exist'),
44
+ },
45
+ },
46
+ },
47
+ formulas: {},
48
+ apis: {},
49
+ attributes: {},
50
+ variables: {},
51
+ },
52
+ },
53
+ },
54
+ rules: [unknownComponentAttributeRule],
55
+ }),
56
+ )
57
+
58
+ expect(problems).toHaveLength(1)
59
+ expect(problems[0].code).toBe('unknown component attribute')
60
+ expect(problems[0].details).toEqual({ name: 'third' })
61
+ })
62
+
63
+ test('should not report component attributes that exist', () => {
64
+ const problems = Array.from(
65
+ searchProject({
66
+ files: {
67
+ components: {
68
+ test: {
69
+ name: 'test',
70
+ nodes: {
71
+ root: {
72
+ type: 'element',
73
+ attrs: {
74
+ known: valueFormula('exists'),
75
+ },
76
+ classes: {},
77
+ events: {},
78
+ tag: 'div',
79
+ children: [],
80
+ style: {},
81
+ },
82
+ },
83
+ formulas: {},
84
+ apis: {},
85
+ variables: {},
86
+ attributes: {
87
+ known: {
88
+ name: 'known',
89
+ testValue: { type: 'value', value: null },
90
+ },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ rules: [unknownComponentAttributeRule],
96
+ }),
97
+ )
98
+ expect(problems).toEqual([])
99
+ })
100
+ })
101
+
102
+ describe('fix unknownComponentAttribute', () => {
103
+ test('should remove unknown component attributes', () => {
104
+ const files: ProjectFiles = {
105
+ components: {
106
+ referenced: {
107
+ name: 'referenced',
108
+ nodes: {},
109
+ attributes: {
110
+ first: {
111
+ name: 'first',
112
+ testValue: valueFormula(null),
113
+ },
114
+ second: {
115
+ name: 'second',
116
+ testValue: valueFormula(null),
117
+ },
118
+ },
119
+ apis: {},
120
+ formulas: {},
121
+ variables: {},
122
+ },
123
+ test: {
124
+ name: 'test',
125
+ nodes: {
126
+ root: {
127
+ type: 'component',
128
+ name: 'referenced',
129
+ events: {},
130
+ children: [],
131
+ style: {},
132
+ attrs: {
133
+ first: valueFormula('exists'),
134
+ third: valueFormula('does not exist'),
135
+ },
136
+ },
137
+ },
138
+ formulas: {},
139
+ apis: {},
140
+ attributes: {},
141
+ variables: {},
142
+ },
143
+ },
144
+ }
145
+ const fixedProject = fixProject({
146
+ files,
147
+ rule: unknownComponentAttributeRule,
148
+ fixType: 'delete-component-attribute',
149
+ })
150
+ expect(
151
+ (fixedProject.components.test!.nodes.root as ComponentNodeModel).attrs,
152
+ ).toEqual({
153
+ first: valueFormula('exists'),
154
+ })
155
+ })
156
+ })
@@ -0,0 +1,40 @@
1
+ import { isDefined } from '@nordcraft/core/dist/utils/util'
2
+ import type { Rule } from '../../types'
3
+ import { removeFromPathFix } from '../../util/removeUnused.fix'
4
+
5
+ export const unknownComponentAttributeRule: Rule<{
6
+ name: string
7
+ }> = {
8
+ code: 'unknown component attribute',
9
+ level: 'error',
10
+ category: 'Unknown Reference',
11
+ visit: (report, { path, files, value, nodeType }) => {
12
+ if (
13
+ nodeType !== 'component-node' ||
14
+ value.type !== 'component' ||
15
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
16
+ Object.keys(value.attrs ?? {}).length === 0
17
+ ) {
18
+ return
19
+ }
20
+ const component = value.package
21
+ ? files.packages?.[value.package].components?.[value.name]
22
+ : files.components[value.name]
23
+ if (!component) {
24
+ return
25
+ }
26
+ for (const attrKey of Object.keys(value.attrs)) {
27
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
28
+ if (!isDefined(component.attributes?.[attrKey])) {
29
+ report([...path, 'attrs', attrKey], { name: attrKey }, [
30
+ 'delete-component-attribute',
31
+ ])
32
+ }
33
+ }
34
+ },
35
+ fixes: {
36
+ 'delete-component-attribute': removeFromPathFix,
37
+ },
38
+ }
39
+
40
+ export type UnknownComponentAttributeRuleFix = 'delete-component-attribute'
@@ -0,0 +1,290 @@
1
+ import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
2
+ import { describe, expect, test } from 'bun:test'
3
+ import { fixProject } from '../../fixProject'
4
+ import { searchProject } from '../../searchProject'
5
+ import { noStaticNodeCondition } from './noStaticNodeCondition'
6
+
7
+ describe('noStaticNodeCondition', () => {
8
+ test('should report node condition that is always truthy', () => {
9
+ const problems = Array.from(
10
+ searchProject({
11
+ files: {
12
+ components: {
13
+ test: {
14
+ name: 'test',
15
+ nodes: {
16
+ root: {
17
+ type: 'element',
18
+ attrs: {},
19
+ classes: {},
20
+ events: {},
21
+ tag: 'div',
22
+ children: [],
23
+ style: {},
24
+ condition: {
25
+ type: 'value',
26
+ value: true,
27
+ },
28
+ },
29
+ },
30
+ formulas: {},
31
+ apis: {},
32
+ attributes: {},
33
+ variables: {},
34
+ },
35
+ },
36
+ },
37
+ rules: [noStaticNodeCondition],
38
+ }),
39
+ )
40
+
41
+ expect(problems).toHaveLength(1)
42
+ expect(problems[0].code).toBe('no-static-node-condition')
43
+ expect(problems[0].details).toEqual({
44
+ result: true,
45
+ })
46
+ expect(problems[0].fixes).toEqual(['remove-condition'])
47
+ expect(problems[0].path).toEqual([
48
+ 'components',
49
+ 'test',
50
+ 'nodes',
51
+ 'root',
52
+ 'condition',
53
+ ])
54
+ })
55
+
56
+ test('should report node condition that is always falsy', () => {
57
+ const problems = Array.from(
58
+ searchProject({
59
+ files: {
60
+ components: {
61
+ test: {
62
+ name: 'test',
63
+ nodes: {
64
+ root: {
65
+ type: 'element',
66
+ attrs: {},
67
+ classes: {},
68
+ events: {},
69
+ tag: 'div',
70
+ children: [],
71
+ style: {},
72
+ condition: {
73
+ type: 'value',
74
+ value: false,
75
+ },
76
+ },
77
+ },
78
+ formulas: {},
79
+ apis: {},
80
+ attributes: {},
81
+ variables: {},
82
+ },
83
+ },
84
+ },
85
+ rules: [noStaticNodeCondition],
86
+ }),
87
+ )
88
+
89
+ expect(problems).toHaveLength(1)
90
+ expect(problems[0].code).toBe('no-static-node-condition')
91
+ expect(problems[0].details).toEqual({
92
+ result: false,
93
+ })
94
+ })
95
+
96
+ test('should not report node condition that is dynamic', () => {
97
+ const problems = Array.from(
98
+ searchProject({
99
+ files: {
100
+ components: {
101
+ test: {
102
+ name: 'test',
103
+ nodes: {
104
+ root: {
105
+ type: 'element',
106
+ attrs: {},
107
+ classes: {},
108
+ events: {},
109
+ tag: 'div',
110
+ children: [],
111
+ style: {},
112
+ condition: {
113
+ type: 'apply',
114
+ name: 'randomNumber',
115
+ arguments: [],
116
+ },
117
+ },
118
+ },
119
+ formulas: {},
120
+ apis: {},
121
+ attributes: {},
122
+ variables: {},
123
+ },
124
+ },
125
+ },
126
+ rules: [noStaticNodeCondition],
127
+ }),
128
+ )
129
+
130
+ expect(problems).toHaveLength(0)
131
+ })
132
+
133
+ test('should fix static truthy condition by removing the condition', () => {
134
+ const files: ProjectFiles = {
135
+ components: {
136
+ test: {
137
+ name: 'test',
138
+ nodes: {
139
+ root: {
140
+ type: 'element',
141
+ attrs: {},
142
+ classes: {},
143
+ events: {},
144
+ tag: 'div',
145
+ children: [],
146
+ style: {},
147
+ condition: {
148
+ type: 'value',
149
+ value: true,
150
+ },
151
+ },
152
+ },
153
+ formulas: {},
154
+ apis: {},
155
+ attributes: {},
156
+ variables: {},
157
+ },
158
+ },
159
+ }
160
+ const fixedFiles = fixProject({
161
+ files,
162
+ rule: noStaticNodeCondition,
163
+ fixType: 'remove-condition',
164
+ })
165
+ expect(fixedFiles).toMatchInlineSnapshot(`
166
+ {
167
+ "components": {
168
+ "test": {
169
+ "apis": {},
170
+ "attributes": {},
171
+ "formulas": {},
172
+ "name": "test",
173
+ "nodes": {
174
+ "root": {
175
+ "attrs": {},
176
+ "children": [],
177
+ "classes": {},
178
+ "events": {},
179
+ "style": {},
180
+ "tag": "div",
181
+ "type": "element",
182
+ },
183
+ },
184
+ "variables": {},
185
+ },
186
+ },
187
+ }
188
+ `)
189
+ })
190
+
191
+ test("should fix static falsy condition by removing the node and any parents' references to it", () => {
192
+ const files: ProjectFiles = {
193
+ components: {
194
+ test: {
195
+ name: 'test',
196
+ nodes: {
197
+ root: {
198
+ type: 'element',
199
+ attrs: {},
200
+ classes: {},
201
+ events: {},
202
+ tag: 'div',
203
+ children: ['alwaysHiddenElement', 'sometimesVisibleElement'],
204
+ style: {},
205
+ },
206
+ alwaysHiddenElement: {
207
+ type: 'element',
208
+ attrs: {},
209
+ classes: {},
210
+ events: {},
211
+ tag: 'div',
212
+ children: [],
213
+ style: {},
214
+ condition: {
215
+ type: 'value',
216
+ value: false,
217
+ },
218
+ },
219
+ sometimesVisibleElement: {
220
+ type: 'element',
221
+ attrs: {},
222
+ classes: {},
223
+ events: {},
224
+ tag: 'div',
225
+ children: [],
226
+ style: {},
227
+ condition: {
228
+ type: 'path',
229
+ path: ['Variables', 'maybe'],
230
+ },
231
+ },
232
+ },
233
+ formulas: {},
234
+ apis: {},
235
+ attributes: {},
236
+ variables: {},
237
+ },
238
+ },
239
+ }
240
+
241
+ const fixedFiles = fixProject({
242
+ files,
243
+ rule: noStaticNodeCondition,
244
+ fixType: 'remove-node',
245
+ })
246
+
247
+ expect(fixedFiles).toMatchInlineSnapshot(`
248
+ {
249
+ "components": {
250
+ "test": {
251
+ "apis": {},
252
+ "attributes": {},
253
+ "formulas": {},
254
+ "name": "test",
255
+ "nodes": {
256
+ "root": {
257
+ "attrs": {},
258
+ "children": [
259
+ "sometimesVisibleElement",
260
+ ],
261
+ "classes": {},
262
+ "events": {},
263
+ "style": {},
264
+ "tag": "div",
265
+ "type": "element",
266
+ },
267
+ "sometimesVisibleElement": {
268
+ "attrs": {},
269
+ "children": [],
270
+ "classes": {},
271
+ "condition": {
272
+ "path": [
273
+ "Variables",
274
+ "maybe",
275
+ ],
276
+ "type": "path",
277
+ },
278
+ "events": {},
279
+ "style": {},
280
+ "tag": "div",
281
+ "type": "element",
282
+ },
283
+ },
284
+ "variables": {},
285
+ },
286
+ },
287
+ }
288
+ `)
289
+ })
290
+ })
@@ -0,0 +1,43 @@
1
+ import type { FixFunction, NodeType, Rule } from '../../types'
2
+ import { contextlessEvaluateFormula } from '../../util/contextlessEvaluateFormula'
3
+ import {
4
+ removeFromPathFix,
5
+ removeNodeFromPathFix,
6
+ } from '../../util/removeUnused.fix'
7
+
8
+ export const noStaticNodeCondition: Rule<{
9
+ result: ReturnType<typeof contextlessEvaluateFormula>['result']
10
+ }> = {
11
+ code: 'no-static-node-condition',
12
+ level: 'warning',
13
+ category: 'Quality',
14
+ visit: (report, { path: nodePath, value, nodeType }) => {
15
+ if (nodeType !== 'component-node' || !value.condition) {
16
+ return
17
+ }
18
+
19
+ const { isStatic, result } = contextlessEvaluateFormula(value.condition)
20
+ if (isStatic) {
21
+ const conditionPath = [...nodePath, 'condition']
22
+ // - if truthy: "Condition is always true, you can safely remove the condition as it will always be rendered."
23
+ // - if falsy: "Condition is always false, you can safely remove the entire node as it will never be rendered."
24
+ report(
25
+ conditionPath,
26
+ {
27
+ result,
28
+ },
29
+ Boolean(result) === true ? ['remove-condition'] : ['remove-node'],
30
+ )
31
+ }
32
+ },
33
+ fixes: {
34
+ 'remove-condition': removeFromPathFix,
35
+ 'remove-node': (({ path, ...data }) =>
36
+ removeNodeFromPathFix({
37
+ path: path.slice(0, -1),
38
+ ...data,
39
+ })) as FixFunction<NodeType>,
40
+ },
41
+ }
42
+
43
+ export type NoStaticNodeConditionRuleFix = 'remove-condition' | 'remove-node'