@nordcraft/search 1.0.44 → 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 (59) hide show
  1. package/dist/problems.worker.js +13 -5
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/actions/legacyActionRule.test.js +3 -3
  4. package/dist/rules/actions/legacyActionRule.test.js.map +1 -1
  5. package/dist/rules/events/duplicateEventTriggerRule.js +2 -1
  6. package/dist/rules/events/duplicateEventTriggerRule.js.map +1 -1
  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 +24 -0
  18. package/dist/rules/noReferenceNodeRule.js.map +1 -0
  19. package/dist/rules/noReferenceNodeRule.test.js +131 -0
  20. package/dist/rules/noReferenceNodeRule.test.js.map +1 -0
  21. package/dist/rules/style/invalidStyleSyntaxRule.js +28 -0
  22. package/dist/rules/style/invalidStyleSyntaxRule.js.map +1 -0
  23. package/dist/rules/style/invalidStyleSyntaxRule.test.js +100 -0
  24. package/dist/rules/style/invalidStyleSyntaxRule.test.js.map +1 -0
  25. package/dist/searchProject.js +22 -1
  26. package/dist/searchProject.js.map +1 -1
  27. package/dist/util/contextlessEvaluateFormula.js +77 -0
  28. package/dist/util/contextlessEvaluateFormula.js.map +1 -0
  29. package/dist/util/contextlessEvaluateFormula.test.js +152 -0
  30. package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
  31. package/dist/util/removeUnused.fix.js +18 -1
  32. package/dist/util/removeUnused.fix.js.map +1 -1
  33. package/package.json +4 -3
  34. package/src/problems.worker.ts +20 -7
  35. package/src/rules/actions/legacyActionRule.test.ts +3 -3
  36. package/src/rules/events/duplicateEventTriggerRule.ts +2 -1
  37. package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
  38. package/src/rules/logic/noStaticNodeCondition.ts +43 -0
  39. package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
  40. package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
  41. package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
  42. package/src/rules/noReferenceNodeRule.test.ts +140 -0
  43. package/src/rules/noReferenceNodeRule.ts +34 -0
  44. package/src/rules/style/invalidStyleSyntaxRule.test.ts +106 -0
  45. package/src/rules/style/invalidStyleSyntaxRule.ts +35 -0
  46. package/src/searchProject.ts +25 -1
  47. package/src/types.d.ts +20 -2
  48. package/src/util/contextlessEvaluateFormula.test.ts +190 -0
  49. package/src/util/contextlessEvaluateFormula.ts +110 -0
  50. package/src/util/removeUnused.fix.ts +29 -1
  51. package/dist/memos/getAllCustomPropertiesBySyntax.js +0 -43
  52. package/dist/memos/getAllCustomPropertiesBySyntax.js.map +0 -1
  53. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js +0 -50
  54. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js.map +0 -1
  55. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js +0 -265
  56. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js.map +0 -1
  57. package/src/memos/getAllCustomPropertiesBySyntax.ts +0 -68
  58. package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.ts +0 -278
  59. package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.ts +0 -65
@@ -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,8 +5,9 @@
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/nordcraftengine/nordcraft",
7
7
  "dependencies": {
8
- "@nordcraft/ssr": "1.0.44",
9
- "jsondiffpatch": "0.7.3"
8
+ "@nordcraft/ssr": "1.0.46",
9
+ "jsondiffpatch": "0.7.3",
10
+ "postcss": "8.5.6"
10
11
  },
11
12
  "scripts": {
12
13
  "build": "tsc",
@@ -18,5 +19,5 @@
18
19
  "test:watch:only": "bun test --watch --only"
19
20
  },
20
21
  "files": ["dist", "src"],
21
- "version": "1.0.44"
22
+ "version": "1.0.46"
22
23
  }
@@ -37,12 +37,14 @@ 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'
43
+ import { noReferenceNodeRule } from './rules/noReferenceNodeRule'
42
44
  import { requireExtensionRule } from './rules/requireExtensionRule'
43
45
  import { unknownClassnameRule } from './rules/slots/unknownClassnameRule'
44
46
  import { unknownComponentSlotRule } from './rules/slots/unknownComponentSlotRule'
45
- import { ambiguousStyleVariableSyntaxRule } from './rules/style-variables/ambiguousStyleVariableSyntaxRule'
47
+ import { invalidStyleSyntaxRule } from './rules/style/invalidStyleSyntaxRule'
46
48
  import { unknownCookieRule } from './rules/unknownCookieRule'
47
49
  import { duplicateUrlParameterRule } from './rules/urlParameters/duplicateUrlParameterRule'
48
50
  import { unknownSetUrlParameterRule } from './rules/urlParameters/unknownSetUrlParameterRule'
@@ -144,6 +146,7 @@ const RULES = [
144
146
  duplicateWorkflowParameterRule,
145
147
  elementWithoutInteractiveContentRule,
146
148
  imageWithoutDimensionRule,
149
+ invalidStyleSyntaxRule,
147
150
  legacyActionRule,
148
151
  legacyApiRule,
149
152
  legacyFormulaRule,
@@ -156,9 +159,11 @@ const RULES = [
156
159
  noReferenceComponentRule,
157
160
  noReferenceComponentWorkflowRule,
158
161
  noReferenceEventRule,
162
+ noReferenceNodeRule,
159
163
  noReferenceProjectActionRule,
160
164
  noReferenceProjectFormulaRule,
161
165
  noReferenceVariableRule,
166
+ noStaticNodeCondition,
162
167
  noUnnecessaryConditionFalsy,
163
168
  noUnnecessaryConditionTruthy,
164
169
  requireExtensionRule,
@@ -190,25 +195,28 @@ const RULES = [
190
195
  unknownVariableRule,
191
196
  unknownVariableSetterRule,
192
197
  unknownWorkflowParameterRule,
193
- ambiguousStyleVariableSyntaxRule,
194
198
  ]
195
199
 
196
200
  interface FindProblemsArgs {
201
+ id: string
197
202
  files: ProjectFiles
198
203
  options?: Options
199
204
  }
200
205
 
201
206
  interface FixProblemsArgs {
207
+ id: string
202
208
  files: ProjectFiles
203
209
  options?: Options
204
210
  fixRule: Code
205
211
  fixType: FixType
206
- id: string
207
212
  }
208
213
 
209
214
  type Message = FindProblemsArgs | FixProblemsArgs
210
215
 
211
- type FindProblemsResponse = Result[]
216
+ interface FindProblemsResponse {
217
+ id: string
218
+ results: Result[]
219
+ }
212
220
 
213
221
  interface FixProblemsResponse {
214
222
  id: string
@@ -223,6 +231,11 @@ const respond = (data: Response) => postMessage(data)
223
231
 
224
232
  const findProblems = (data: FindProblemsArgs) => {
225
233
  const { files, options = {} } = data
234
+ const idRespond = (results: Result[]) =>
235
+ respond({
236
+ id: data.id,
237
+ results,
238
+ })
226
239
  const rules = RULES.filter(
227
240
  (rule) =>
228
241
  (!options.categories || options.categories.includes(rule.category)) &&
@@ -248,7 +261,7 @@ const findProblems = (data: FindProblemsArgs) => {
248
261
  case 'per-file': {
249
262
  if (fileType !== problem.path[0] || fileName !== problem.path[1]) {
250
263
  if (batch.length > 0) {
251
- respond(batch)
264
+ idRespond(batch)
252
265
  }
253
266
  batch = []
254
267
  fileType = problem.path[0]
@@ -262,7 +275,7 @@ const findProblems = (data: FindProblemsArgs) => {
262
275
  default: {
263
276
  batch.push(problem)
264
277
  if (batch.length >= (options.batchSize ?? 1)) {
265
- respond(batch)
278
+ idRespond(batch)
266
279
  batch = []
267
280
  }
268
281
  break
@@ -271,7 +284,7 @@ const findProblems = (data: FindProblemsArgs) => {
271
284
  }
272
285
 
273
286
  // Send the remaining results
274
- respond(batch)
287
+ idRespond(batch)
275
288
  }
276
289
 
277
290
  const fixProblems = (data: FixProblemsArgs) => {
@@ -197,7 +197,7 @@ describe('fix legacyActions', () => {
197
197
  })
198
198
  const fixedAction = (
199
199
  fixedProject.components['apiComponent']?.nodes['root'] as ElementNodeModel
200
- ).events['click'].actions[0]
200
+ ).events['click']?.actions[0]
201
201
  expect(fixedAction).toMatchInlineSnapshot(`
202
202
  {
203
203
  "cases": [
@@ -321,7 +321,7 @@ describe('fix legacyActions', () => {
321
321
  })
322
322
  const fixedAction = (
323
323
  fixedProject.components['apiComponent']?.nodes['root'] as ElementNodeModel
324
- ).events['click'].actions[0]
324
+ ).events['click']?.actions[0]
325
325
  expect(fixedAction).toMatchInlineSnapshot(`
326
326
  {
327
327
  "data": {
@@ -378,7 +378,7 @@ describe('fix legacyActions', () => {
378
378
  })
379
379
  const fixedAction = (
380
380
  fixedProject.components['apiComponent']?.nodes['root'] as ElementNodeModel
381
- ).events['click'].actions[0]
381
+ ).events['click']?.actions[0]
382
382
  expect(fixedAction).toMatchInlineSnapshot(`
383
383
  {
384
384
  "arguments": [
@@ -10,8 +10,9 @@ export const duplicateEventTriggerRule: Rule<{ trigger: string }> = {
10
10
  }
11
11
  const eventTriggers = new Set<string>()
12
12
 
13
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
13
14
  Object.entries(value.events ?? {}).forEach(([key, event]) => {
14
- if (typeof event.trigger !== 'string') {
15
+ if (typeof event?.trigger !== 'string') {
15
16
  return
16
17
  }
17
18
  if (eventTriggers.has(event.trigger)) {
@@ -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'
@@ -1,4 +1,5 @@
1
1
  import type { Rule } from '../../types'
2
+ import { contextlessEvaluateFormula } from '../../util/contextlessEvaluateFormula'
2
3
 
3
4
  export const noUnnecessaryConditionFalsy: Rule = {
4
5
  code: 'no-unnecessary-condition-falsy',
@@ -10,10 +11,10 @@ export const noUnnecessaryConditionFalsy: Rule = {
10
11
  }
11
12
 
12
13
  if (
13
- value.arguments.some(
14
- (arg) =>
15
- arg.formula.type === 'value' && Boolean(arg.formula.value) === false,
16
- )
14
+ value.arguments.some((arg) => {
15
+ const { result, isStatic } = contextlessEvaluateFormula(arg.formula)
16
+ return isStatic && Boolean(result) === false
17
+ })
17
18
  ) {
18
19
  report(path)
19
20
  }
@@ -32,7 +32,41 @@ describe('noUnnecessaryConditionTruthy', () => {
32
32
  },
33
33
  ],
34
34
  },
35
- test2: {
35
+ },
36
+ classes: {},
37
+ events: {},
38
+ tag: 'div',
39
+ children: [],
40
+ style: {},
41
+ },
42
+ },
43
+ formulas: {},
44
+ apis: {},
45
+ attributes: {},
46
+ variables: {},
47
+ },
48
+ },
49
+ },
50
+ rules: [noUnnecessaryConditionTruthy],
51
+ }),
52
+ )
53
+
54
+ expect(problems).toHaveLength(1)
55
+ expect(problems[0].code).toBe('no-unnecessary-condition-truthy')
56
+ })
57
+
58
+ test('should report unnecessary truthy conditions when a value is of type object or array as they are always consider truthy', () => {
59
+ const problems = Array.from(
60
+ searchProject({
61
+ files: {
62
+ components: {
63
+ test: {
64
+ name: 'test',
65
+ nodes: {
66
+ root: {
67
+ type: 'element',
68
+ attrs: {
69
+ test1: {
36
70
  type: 'or',
37
71
  arguments: [
38
72
  {
@@ -43,7 +77,7 @@ describe('noUnnecessaryConditionTruthy', () => {
43
77
  },
44
78
  ],
45
79
  },
46
- test3: {
80
+ test2: {
47
81
  type: 'or',
48
82
  arguments: [
49
83
  {
@@ -73,10 +107,9 @@ describe('noUnnecessaryConditionTruthy', () => {
73
107
  }),
74
108
  )
75
109
 
76
- expect(problems).toHaveLength(3)
110
+ expect(problems).toHaveLength(2)
77
111
  expect(problems[0].code).toBe('no-unnecessary-condition-truthy')
78
112
  expect(problems[1].code).toBe('no-unnecessary-condition-truthy')
79
- expect(problems[2].code).toBe('no-unnecessary-condition-truthy')
80
113
  })
81
114
 
82
115
  test('should not report necessary truthy conditions', () => {
@@ -1,4 +1,5 @@
1
1
  import type { Rule } from '../../types'
2
+ import { contextlessEvaluateFormula } from '../../util/contextlessEvaluateFormula'
2
3
 
3
4
  export const noUnnecessaryConditionTruthy: Rule = {
4
5
  code: 'no-unnecessary-condition-truthy',
@@ -10,14 +11,15 @@ export const noUnnecessaryConditionTruthy: Rule = {
10
11
  }
11
12
 
12
13
  if (
13
- value.arguments.some(
14
- (arg) =>
15
- (arg.formula.type === 'value' &&
16
- Boolean(arg.formula.value) === true) ||
17
- // Objects and arrays, even empty ones, are always truthy
18
- arg.formula.type === 'object' ||
19
- arg.formula.type === 'array',
20
- )
14
+ value.arguments.some((arg) => {
15
+ // Objects and arrays, even empty ones, are always truthy
16
+ if (arg.formula.type === 'object' || arg.formula.type === 'array') {
17
+ return true
18
+ }
19
+
20
+ const { result, isStatic } = contextlessEvaluateFormula(arg.formula)
21
+ return isStatic && Boolean(result) === true
22
+ })
21
23
  ) {
22
24
  report(path)
23
25
  }