@nordcraft/search 1.0.45 → 1.0.46

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 (38) hide show
  1. package/dist/problems.worker.js +9 -3
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/logic/noStaticNodeCondition.js +29 -0
  4. package/dist/rules/logic/noStaticNodeCondition.js.map +1 -0
  5. package/dist/rules/logic/noStaticNodeCondition.test.js +274 -0
  6. package/dist/rules/logic/noStaticNodeCondition.test.js.map +1 -0
  7. package/dist/rules/logic/noUnnecessaryConditionFalsy.js +5 -1
  8. package/dist/rules/logic/noUnnecessaryConditionFalsy.js.map +1 -1
  9. package/dist/rules/logic/noUnnecessaryConditionTruthy.js +8 -4
  10. package/dist/rules/logic/noUnnecessaryConditionTruthy.js.map +1 -1
  11. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js +33 -4
  12. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js.map +1 -1
  13. package/dist/rules/noReferenceNodeRule.js +10 -8
  14. package/dist/rules/noReferenceNodeRule.js.map +1 -1
  15. package/dist/rules/noReferenceNodeRule.test.js +1 -1
  16. package/dist/rules/style/invalidStyleSyntaxRule.js +2 -2
  17. package/dist/rules/style/invalidStyleSyntaxRule.test.js +1 -1
  18. package/dist/util/contextlessEvaluateFormula.js +77 -0
  19. package/dist/util/contextlessEvaluateFormula.js.map +1 -0
  20. package/dist/util/contextlessEvaluateFormula.test.js +152 -0
  21. package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
  22. package/dist/util/removeUnused.fix.js +18 -1
  23. package/dist/util/removeUnused.fix.js.map +1 -1
  24. package/package.json +2 -2
  25. package/src/problems.worker.ts +16 -5
  26. package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
  27. package/src/rules/logic/noStaticNodeCondition.ts +43 -0
  28. package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
  29. package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
  30. package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
  31. package/src/rules/noReferenceNodeRule.test.ts +1 -1
  32. package/src/rules/noReferenceNodeRule.ts +17 -10
  33. package/src/rules/style/invalidStyleSyntaxRule.test.ts +1 -1
  34. package/src/rules/style/invalidStyleSyntaxRule.ts +3 -3
  35. package/src/types.d.ts +3 -0
  36. package/src/util/contextlessEvaluateFormula.test.ts +190 -0
  37. package/src/util/contextlessEvaluateFormula.ts +110 -0
  38. package/src/util/removeUnused.fix.ts +29 -1
@@ -0,0 +1,77 @@
1
+ import {} from '@nordcraft/core/dist/formula/formula';
2
+ /**
3
+ * Static evaluation of a formula.
4
+ *
5
+ * Can be used by issues to determine if a formula or sub-formula can be reduced to a static value.
6
+ * When sophisticated enough, it can be used during compile-time to reduce all static subgraphs of a formula to static values, greatly reducing payload and improving runtime performance.
7
+ *
8
+ * @returns {
9
+ * isStatic: boolean; // Whether the formula is static (i.e., does not depend on any variables, context AND only use pure formulas (no Random, Date, etc.))
10
+ * result: unknown; // The evaluated value of the formula
11
+ * }
12
+ *
13
+ * TODO: Make this function more capable of evaluating pure core formulas.
14
+ * TODO: Memoize the results (using path or a fast hash) to avoid re-evaluating any similar sub-graphs multiple times.
15
+ * TODO: Add a complex test-suite to ensure it works and develops as expected.
16
+ */
17
+ export const contextlessEvaluateFormula = (formula) => {
18
+ // Very basic implementation, just to get started.
19
+ switch (formula.type) {
20
+ case 'value': {
21
+ return {
22
+ isStatic: true,
23
+ result: formula.value,
24
+ };
25
+ }
26
+ case 'array': {
27
+ const results = formula.arguments.map((arg) => contextlessEvaluateFormula(arg.formula));
28
+ return {
29
+ isStatic: results.every((res) => res.isStatic),
30
+ result: results.map((res) => res.result),
31
+ };
32
+ }
33
+ case 'record': {
34
+ const entries = Object.entries(formula.entries).map(([key, arg]) => [key, contextlessEvaluateFormula(arg.formula)]);
35
+ const results = entries.map(([, res]) => res);
36
+ return {
37
+ isStatic: results.every((res) => res.isStatic),
38
+ result: Object.fromEntries(entries.map(([key, res]) => [key, res.result])),
39
+ };
40
+ }
41
+ // Static if:
42
+ // - ALL conditions are static AND truthy
43
+ // - ANY condition is static and falsy
44
+ // - EMPTY argument list is always true
45
+ case 'and': {
46
+ const results = formula.arguments.map((arg) => contextlessEvaluateFormula(arg.formula));
47
+ const alwaysTrue = results.length === 0 ||
48
+ results.every((res) => res.isStatic && Boolean(res.result) === true);
49
+ const alwaysFalsy = results.some((res) => res.isStatic && Boolean(res.result) === false);
50
+ return {
51
+ isStatic: alwaysTrue || alwaysFalsy,
52
+ result: alwaysTrue ? true : alwaysFalsy ? false : undefined,
53
+ };
54
+ }
55
+ // Static if:
56
+ // - ANY condition is static AND truthy
57
+ // - ALL conditions are static AND falsy
58
+ // - EMPTY argument list is always false
59
+ case 'or': {
60
+ const results = formula.arguments.map((arg) => contextlessEvaluateFormula(arg.formula));
61
+ const alwaysFalsy = results.length === 0 ||
62
+ results.every((res) => res.isStatic && Boolean(res.result) === false);
63
+ const alwaysTrue = results.some((res) => res.isStatic && Boolean(res.result) === true);
64
+ return {
65
+ isStatic: alwaysTrue || alwaysFalsy,
66
+ result: alwaysFalsy ? false : alwaysTrue ? true : undefined,
67
+ };
68
+ }
69
+ default:
70
+ // For now, we assume that any other formula is not static.
71
+ return {
72
+ isStatic: false,
73
+ result: undefined,
74
+ };
75
+ }
76
+ };
77
+ //# sourceMappingURL=contextlessEvaluateFormula.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextlessEvaluateFormula.js","sourceRoot":"","sources":["../../src/util/contextlessEvaluateFormula.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { contextlessEvaluateFormula } from './contextlessEvaluateFormula';
3
+ describe('contextlessEvaluateFormula', () => {
4
+ test('should return `true` when the formula is a simple value formula', () => {
5
+ expect(contextlessEvaluateFormula({
6
+ type: 'value',
7
+ value: true,
8
+ })).toEqual({
9
+ isStatic: true,
10
+ result: true,
11
+ });
12
+ });
13
+ test('should not return a result and have `isStatic: false` when the formula uses a non-pure formula ("randomNumber", "Now")', () => {
14
+ expect(contextlessEvaluateFormula({
15
+ type: 'apply',
16
+ name: 'randomNumber',
17
+ arguments: [],
18
+ })).toEqual({
19
+ isStatic: false,
20
+ result: undefined,
21
+ });
22
+ expect(contextlessEvaluateFormula({
23
+ type: 'apply',
24
+ name: 'now',
25
+ arguments: [],
26
+ })).toEqual({
27
+ isStatic: false,
28
+ result: undefined,
29
+ });
30
+ });
31
+ test('should not return a result and have `isStatic: false` when the formula depends on a variable', () => {
32
+ expect(contextlessEvaluateFormula({
33
+ type: 'path',
34
+ path: ['Variables', 'myVariable'],
35
+ })).toEqual({
36
+ isStatic: false,
37
+ result: undefined,
38
+ });
39
+ });
40
+ test('should return a result and static for an array formula where all args are array or value types', () => {
41
+ expect(contextlessEvaluateFormula({
42
+ type: 'array',
43
+ arguments: [
44
+ { formula: { type: 'value', value: 1 } },
45
+ {
46
+ formula: {
47
+ type: 'array',
48
+ arguments: [{ formula: { type: 'value', value: 2 } }],
49
+ },
50
+ },
51
+ ],
52
+ })).toEqual({
53
+ isStatic: true,
54
+ result: [1, [2]],
55
+ });
56
+ });
57
+ test('should return `isStatic: false` for an array formula with a non-static argument', () => {
58
+ expect(contextlessEvaluateFormula({
59
+ type: 'array',
60
+ arguments: [
61
+ { formula: { type: 'value', value: 1 } },
62
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
63
+ ],
64
+ })).toEqual({
65
+ isStatic: false,
66
+ result: [
67
+ 1,
68
+ undefined, // The second argument was not static
69
+ ],
70
+ });
71
+ });
72
+ test('should be static true for `And` formulas with no arguments', () => {
73
+ expect(contextlessEvaluateFormula({
74
+ type: 'and',
75
+ arguments: [],
76
+ })).toEqual({
77
+ isStatic: true,
78
+ result: true,
79
+ });
80
+ });
81
+ test('should be static false for `Or` formulas with no arguments', () => {
82
+ expect(contextlessEvaluateFormula({
83
+ type: 'or',
84
+ arguments: [],
85
+ })).toEqual({
86
+ isStatic: true,
87
+ result: false,
88
+ });
89
+ });
90
+ test('should be static true for `And` formulas with all static truthy arguments', () => {
91
+ expect(contextlessEvaluateFormula({
92
+ type: 'and',
93
+ arguments: [
94
+ { formula: { type: 'value', value: true } },
95
+ { formula: { type: 'value', value: true } },
96
+ ],
97
+ })).toEqual({
98
+ isStatic: true,
99
+ result: true,
100
+ });
101
+ });
102
+ test('should not be static for `And` where any dynamic value exist', () => {
103
+ expect(contextlessEvaluateFormula({
104
+ type: 'and',
105
+ arguments: [
106
+ { formula: { type: 'value', value: true } },
107
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
108
+ ],
109
+ })).toEqual({
110
+ isStatic: false,
111
+ result: undefined,
112
+ });
113
+ });
114
+ test('should be static false for `And` when any argument is static false, even when dynamic arguments exist', () => {
115
+ expect(contextlessEvaluateFormula({
116
+ type: 'and',
117
+ arguments: [
118
+ { formula: { type: 'value', value: true } },
119
+ { formula: { type: 'path', path: ['Variables', 'myVariable'] } },
120
+ { formula: { type: 'value', value: false } },
121
+ ],
122
+ })).toEqual({
123
+ isStatic: true,
124
+ result: false,
125
+ });
126
+ });
127
+ test('should not be static for `Or` when any argument is dynamic and all static are falsy', () => {
128
+ expect(contextlessEvaluateFormula({
129
+ type: 'or',
130
+ arguments: [
131
+ { formula: { type: 'value', value: false } },
132
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
133
+ ],
134
+ })).toEqual({
135
+ isStatic: false,
136
+ result: undefined,
137
+ });
138
+ });
139
+ test('should be static true for `Or` when any argument is static true, even when dynamic arguments exist', () => {
140
+ expect(contextlessEvaluateFormula({
141
+ type: 'or',
142
+ arguments: [
143
+ { formula: { type: 'value', value: true } },
144
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
145
+ ],
146
+ })).toEqual({
147
+ isStatic: true,
148
+ result: true,
149
+ });
150
+ });
151
+ });
152
+ //# sourceMappingURL=contextlessEvaluateFormula.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextlessEvaluateFormula.test.js","sourceRoot":"","sources":["../../src/util/contextlessEvaluateFormula.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.46",
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.46"
23
23
  }
@@ -37,6 +37,7 @@ import { unknownFormulaRule } from './rules/formulas/unknownFormulaRule'
37
37
  import { unknownProjectFormulaRule } from './rules/formulas/unknownProjectFormulaRule'
38
38
  import { unknownRepeatIndexFormulaRule } from './rules/formulas/unknownRepeatIndexFormulaRule'
39
39
  import { unknownRepeatItemFormulaRule } from './rules/formulas/unknownRepeatItemFormulaRule'
40
+ import { noStaticNodeCondition } from './rules/logic/noStaticNodeCondition'
40
41
  import { noUnnecessaryConditionFalsy } from './rules/logic/noUnnecessaryConditionFalsy'
41
42
  import { noUnnecessaryConditionTruthy } from './rules/logic/noUnnecessaryConditionTruthy'
42
43
  import { noReferenceNodeRule } from './rules/noReferenceNodeRule'
@@ -162,6 +163,7 @@ const RULES = [
162
163
  noReferenceProjectActionRule,
163
164
  noReferenceProjectFormulaRule,
164
165
  noReferenceVariableRule,
166
+ noStaticNodeCondition,
165
167
  noUnnecessaryConditionFalsy,
166
168
  noUnnecessaryConditionTruthy,
167
169
  requireExtensionRule,
@@ -196,21 +198,25 @@ const RULES = [
196
198
  ]
197
199
 
198
200
  interface FindProblemsArgs {
201
+ id: string
199
202
  files: ProjectFiles
200
203
  options?: Options
201
204
  }
202
205
 
203
206
  interface FixProblemsArgs {
207
+ id: string
204
208
  files: ProjectFiles
205
209
  options?: Options
206
210
  fixRule: Code
207
211
  fixType: FixType
208
- id: string
209
212
  }
210
213
 
211
214
  type Message = FindProblemsArgs | FixProblemsArgs
212
215
 
213
- type FindProblemsResponse = Result[]
216
+ interface FindProblemsResponse {
217
+ id: string
218
+ results: Result[]
219
+ }
214
220
 
215
221
  interface FixProblemsResponse {
216
222
  id: string
@@ -225,6 +231,11 @@ const respond = (data: Response) => postMessage(data)
225
231
 
226
232
  const findProblems = (data: FindProblemsArgs) => {
227
233
  const { files, options = {} } = data
234
+ const idRespond = (results: Result[]) =>
235
+ respond({
236
+ id: data.id,
237
+ results,
238
+ })
228
239
  const rules = RULES.filter(
229
240
  (rule) =>
230
241
  (!options.categories || options.categories.includes(rule.category)) &&
@@ -250,7 +261,7 @@ const findProblems = (data: FindProblemsArgs) => {
250
261
  case 'per-file': {
251
262
  if (fileType !== problem.path[0] || fileName !== problem.path[1]) {
252
263
  if (batch.length > 0) {
253
- respond(batch)
264
+ idRespond(batch)
254
265
  }
255
266
  batch = []
256
267
  fileType = problem.path[0]
@@ -264,7 +275,7 @@ const findProblems = (data: FindProblemsArgs) => {
264
275
  default: {
265
276
  batch.push(problem)
266
277
  if (batch.length >= (options.batchSize ?? 1)) {
267
- respond(batch)
278
+ idRespond(batch)
268
279
  batch = []
269
280
  }
270
281
  break
@@ -273,7 +284,7 @@ const findProblems = (data: FindProblemsArgs) => {
273
284
  }
274
285
 
275
286
  // Send the remaining results
276
- respond(batch)
287
+ idRespond(batch)
277
288
  }
278
289
 
279
290
  const fixProblems = (data: FixProblemsArgs) => {
@@ -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
+ })