@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.
- package/dist/problems.worker.js +13 -5
- package/dist/problems.worker.js.map +1 -1
- package/dist/rules/actions/legacyActionRule.test.js +3 -3
- package/dist/rules/actions/legacyActionRule.test.js.map +1 -1
- package/dist/rules/events/duplicateEventTriggerRule.js +2 -1
- package/dist/rules/events/duplicateEventTriggerRule.js.map +1 -1
- package/dist/rules/logic/noStaticNodeCondition.js +29 -0
- package/dist/rules/logic/noStaticNodeCondition.js.map +1 -0
- package/dist/rules/logic/noStaticNodeCondition.test.js +274 -0
- package/dist/rules/logic/noStaticNodeCondition.test.js.map +1 -0
- package/dist/rules/logic/noUnnecessaryConditionFalsy.js +5 -1
- package/dist/rules/logic/noUnnecessaryConditionFalsy.js.map +1 -1
- package/dist/rules/logic/noUnnecessaryConditionTruthy.js +8 -4
- package/dist/rules/logic/noUnnecessaryConditionTruthy.js.map +1 -1
- package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js +33 -4
- package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js.map +1 -1
- package/dist/rules/noReferenceNodeRule.js +24 -0
- package/dist/rules/noReferenceNodeRule.js.map +1 -0
- package/dist/rules/noReferenceNodeRule.test.js +131 -0
- package/dist/rules/noReferenceNodeRule.test.js.map +1 -0
- package/dist/rules/style/invalidStyleSyntaxRule.js +28 -0
- package/dist/rules/style/invalidStyleSyntaxRule.js.map +1 -0
- package/dist/rules/style/invalidStyleSyntaxRule.test.js +100 -0
- package/dist/rules/style/invalidStyleSyntaxRule.test.js.map +1 -0
- package/dist/searchProject.js +22 -1
- package/dist/searchProject.js.map +1 -1
- package/dist/util/contextlessEvaluateFormula.js +77 -0
- package/dist/util/contextlessEvaluateFormula.js.map +1 -0
- package/dist/util/contextlessEvaluateFormula.test.js +152 -0
- package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
- package/dist/util/removeUnused.fix.js +18 -1
- package/dist/util/removeUnused.fix.js.map +1 -1
- package/package.json +4 -3
- package/src/problems.worker.ts +20 -7
- package/src/rules/actions/legacyActionRule.test.ts +3 -3
- package/src/rules/events/duplicateEventTriggerRule.ts +2 -1
- package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
- package/src/rules/logic/noStaticNodeCondition.ts +43 -0
- package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
- package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
- package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
- package/src/rules/noReferenceNodeRule.test.ts +140 -0
- package/src/rules/noReferenceNodeRule.ts +34 -0
- package/src/rules/style/invalidStyleSyntaxRule.test.ts +106 -0
- package/src/rules/style/invalidStyleSyntaxRule.ts +35 -0
- package/src/searchProject.ts +25 -1
- package/src/types.d.ts +20 -2
- package/src/util/contextlessEvaluateFormula.test.ts +190 -0
- package/src/util/contextlessEvaluateFormula.ts +110 -0
- package/src/util/removeUnused.fix.ts +29 -1
- package/dist/memos/getAllCustomPropertiesBySyntax.js +0 -43
- package/dist/memos/getAllCustomPropertiesBySyntax.js.map +0 -1
- package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js +0 -50
- package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js.map +0 -1
- package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js +0 -265
- package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js.map +0 -1
- package/src/memos/getAllCustomPropertiesBySyntax.ts +0 -68
- package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.ts +0 -278
- package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.ts +0 -65
|
@@ -0,0 +1,140 @@
|
|
|
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 { noReferenceNodeRule } from './noReferenceNodeRule'
|
|
6
|
+
|
|
7
|
+
describe('find noReferenceNodeRule', () => {
|
|
8
|
+
test('should detect nodes with no references', () => {
|
|
9
|
+
const problems = Array.from(
|
|
10
|
+
searchProject({
|
|
11
|
+
files: {
|
|
12
|
+
formulas: {},
|
|
13
|
+
components: {
|
|
14
|
+
test: {
|
|
15
|
+
name: 'test',
|
|
16
|
+
nodes: {
|
|
17
|
+
root: {
|
|
18
|
+
id: 'root',
|
|
19
|
+
type: 'element',
|
|
20
|
+
tag: 'div',
|
|
21
|
+
children: [],
|
|
22
|
+
attrs: {},
|
|
23
|
+
style: {},
|
|
24
|
+
events: {},
|
|
25
|
+
classes: {},
|
|
26
|
+
},
|
|
27
|
+
'1LisbD0eCjsuccoUwajn1': {
|
|
28
|
+
id: 'XdhwPGsdFNI4s8A0oMwre',
|
|
29
|
+
type: 'text',
|
|
30
|
+
value: { type: 'value', value: 'Clone the project' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
formulas: {},
|
|
34
|
+
apis: {},
|
|
35
|
+
attributes: {},
|
|
36
|
+
variables: {},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
rules: [noReferenceNodeRule],
|
|
41
|
+
}),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
expect(problems).toHaveLength(1)
|
|
45
|
+
expect(problems[0].details).toEqual({ node: '1LisbD0eCjsuccoUwajn1' })
|
|
46
|
+
expect(problems[0].path).toEqual([
|
|
47
|
+
'components',
|
|
48
|
+
'test',
|
|
49
|
+
'nodes',
|
|
50
|
+
'1LisbD0eCjsuccoUwajn1',
|
|
51
|
+
])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('should not detect nodes with references', () => {
|
|
55
|
+
const problems = Array.from(
|
|
56
|
+
searchProject({
|
|
57
|
+
files: {
|
|
58
|
+
formulas: {},
|
|
59
|
+
components: {
|
|
60
|
+
test: {
|
|
61
|
+
name: 'test',
|
|
62
|
+
nodes: {
|
|
63
|
+
root: {
|
|
64
|
+
id: 'root',
|
|
65
|
+
type: 'element',
|
|
66
|
+
tag: 'div',
|
|
67
|
+
children: ['1LisbD0eCjsuccoUwajn1'],
|
|
68
|
+
attrs: {},
|
|
69
|
+
style: {},
|
|
70
|
+
events: {},
|
|
71
|
+
classes: {},
|
|
72
|
+
},
|
|
73
|
+
'1LisbD0eCjsuccoUwajn1': {
|
|
74
|
+
id: 'XdhwPGsdFNI4s8A0oMwre',
|
|
75
|
+
type: 'text',
|
|
76
|
+
value: { type: 'value', value: 'Clone the project' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
formulas: {},
|
|
80
|
+
apis: {},
|
|
81
|
+
attributes: {},
|
|
82
|
+
variables: {},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
rules: [noReferenceNodeRule],
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
expect(problems).toEqual([])
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('fix noReferenceNodeRule', () => {
|
|
95
|
+
test('should delete nodes with no references', () => {
|
|
96
|
+
const files: ProjectFiles = {
|
|
97
|
+
formulas: {},
|
|
98
|
+
components: {
|
|
99
|
+
test: {
|
|
100
|
+
name: 'test',
|
|
101
|
+
nodes: {
|
|
102
|
+
root: {
|
|
103
|
+
id: 'root',
|
|
104
|
+
type: 'element',
|
|
105
|
+
tag: 'div',
|
|
106
|
+
children: ['used'],
|
|
107
|
+
attrs: {},
|
|
108
|
+
style: {},
|
|
109
|
+
events: {},
|
|
110
|
+
classes: {},
|
|
111
|
+
},
|
|
112
|
+
'1LisbD0eCjsuccoUwajn1': {
|
|
113
|
+
id: 'XdhwPGsdFNI4s8A0oMwre',
|
|
114
|
+
type: 'text',
|
|
115
|
+
value: { type: 'value', value: 'Clone the project' },
|
|
116
|
+
},
|
|
117
|
+
used: {
|
|
118
|
+
id: 'used',
|
|
119
|
+
type: 'text',
|
|
120
|
+
value: { type: 'value', value: 'I am used' },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
formulas: {},
|
|
124
|
+
apis: {},
|
|
125
|
+
attributes: {},
|
|
126
|
+
variables: {},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
const fixedFiles = fixProject({
|
|
131
|
+
files,
|
|
132
|
+
rule: noReferenceNodeRule,
|
|
133
|
+
fixType: 'delete-orphan-node',
|
|
134
|
+
})
|
|
135
|
+
expect(Object.keys(fixedFiles.components.test!.nodes)).toEqual([
|
|
136
|
+
'root',
|
|
137
|
+
'used',
|
|
138
|
+
])
|
|
139
|
+
})
|
|
140
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Rule } from '../types'
|
|
2
|
+
import { removeFromPathFix } from '../util/removeUnused.fix'
|
|
3
|
+
|
|
4
|
+
export const noReferenceNodeRule: Rule<{ node: string }> = {
|
|
5
|
+
code: 'no-reference node',
|
|
6
|
+
level: 'warning',
|
|
7
|
+
category: 'No References',
|
|
8
|
+
visit: (report, args) => {
|
|
9
|
+
if (args.nodeType !== 'component-node') {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
const { path, component } = args
|
|
13
|
+
const nodeId = path.at(-1)
|
|
14
|
+
if (typeof nodeId !== 'string') {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
const referencedNodesInComponent = args.memo(
|
|
18
|
+
`node-references-${component.name}`,
|
|
19
|
+
() =>
|
|
20
|
+
new Set(
|
|
21
|
+
Object.values(component.nodes).flatMap((node) => node.children ?? []),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if (nodeId !== 'root' && !referencedNodesInComponent.has(nodeId)) {
|
|
26
|
+
report(path, { node: nodeId }, ['delete-orphan-node'])
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
fixes: {
|
|
30
|
+
'delete-orphan-node': removeFromPathFix,
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type NoReferenceNodeRuleFix = 'delete-orphan-node'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ElementNodeModel } from '@nordcraft/core/dist/component/component.types'
|
|
2
|
+
import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
|
|
3
|
+
import { describe, expect, test } from 'bun:test'
|
|
4
|
+
import { fixProject } from '../../fixProject'
|
|
5
|
+
import { searchProject } from '../../searchProject'
|
|
6
|
+
import { invalidStyleSyntaxRule } from './invalidStyleSyntaxRule'
|
|
7
|
+
|
|
8
|
+
describe('find invalidStyleSyntaxRule', () => {
|
|
9
|
+
test('should find invalid style syntax', () => {
|
|
10
|
+
const problems = Array.from(
|
|
11
|
+
searchProject({
|
|
12
|
+
files: {
|
|
13
|
+
formulas: {},
|
|
14
|
+
components: {
|
|
15
|
+
test: {
|
|
16
|
+
name: 'test',
|
|
17
|
+
nodes: {
|
|
18
|
+
root: {
|
|
19
|
+
tag: 'ul',
|
|
20
|
+
type: 'element',
|
|
21
|
+
attrs: {},
|
|
22
|
+
style: {
|
|
23
|
+
gap: '8px',
|
|
24
|
+
width: '100%',
|
|
25
|
+
'max-width': 'calc(NOT VALID',
|
|
26
|
+
height: '/* 100px */ 22px',
|
|
27
|
+
'{': '100px',
|
|
28
|
+
'border-width': '--my-var',
|
|
29
|
+
},
|
|
30
|
+
events: {},
|
|
31
|
+
classes: {},
|
|
32
|
+
children: [],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
formulas: {},
|
|
36
|
+
apis: {},
|
|
37
|
+
attributes: {},
|
|
38
|
+
variables: {},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
rules: [invalidStyleSyntaxRule],
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(problems).toHaveLength(2)
|
|
47
|
+
expect(problems[0].code).toBe('invalid style syntax')
|
|
48
|
+
expect(problems[0].path).toEqual([
|
|
49
|
+
'components',
|
|
50
|
+
'test',
|
|
51
|
+
'nodes',
|
|
52
|
+
'root',
|
|
53
|
+
'style',
|
|
54
|
+
'max-width',
|
|
55
|
+
])
|
|
56
|
+
expect(problems[0].details.property).toBe('max-width')
|
|
57
|
+
expect(problems[1].details.property).toBe('{')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('fix invalidStyleSyntaxRule', () => {
|
|
62
|
+
test('should remove an invalid style property', () => {
|
|
63
|
+
const files: ProjectFiles = {
|
|
64
|
+
formulas: {},
|
|
65
|
+
components: {
|
|
66
|
+
test: {
|
|
67
|
+
name: 'test',
|
|
68
|
+
nodes: {
|
|
69
|
+
root: {
|
|
70
|
+
tag: 'ul',
|
|
71
|
+
type: 'element',
|
|
72
|
+
attrs: {},
|
|
73
|
+
style: {
|
|
74
|
+
gap: '8px',
|
|
75
|
+
width: '100%',
|
|
76
|
+
'max-width': 'calc(NOT VALID',
|
|
77
|
+
height: '/* 100px */ 22px',
|
|
78
|
+
'{': '100px',
|
|
79
|
+
},
|
|
80
|
+
events: {},
|
|
81
|
+
classes: {},
|
|
82
|
+
children: [],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
formulas: {},
|
|
86
|
+
apis: {},
|
|
87
|
+
attributes: {},
|
|
88
|
+
variables: {},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
const fixedFiles = fixProject({
|
|
93
|
+
files,
|
|
94
|
+
rule: invalidStyleSyntaxRule,
|
|
95
|
+
fixType: 'delete-style-property',
|
|
96
|
+
})
|
|
97
|
+
expect((fixedFiles.components.test!.nodes.root as ElementNodeModel).style)
|
|
98
|
+
.toMatchInlineSnapshot(`
|
|
99
|
+
{
|
|
100
|
+
"gap": "8px",
|
|
101
|
+
"height": "/* 100px */ 22px",
|
|
102
|
+
"width": "100%",
|
|
103
|
+
}
|
|
104
|
+
`)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { parse } from 'postcss'
|
|
2
|
+
import type { Rule } from '../../types'
|
|
3
|
+
import { removeFromPathFix } from '../../util/removeUnused.fix'
|
|
4
|
+
|
|
5
|
+
export const invalidStyleSyntaxRule: Rule<{
|
|
6
|
+
property: string
|
|
7
|
+
}> = {
|
|
8
|
+
code: 'invalid style syntax',
|
|
9
|
+
level: 'error',
|
|
10
|
+
category: 'Quality',
|
|
11
|
+
visit: (report, { nodeType, value, path, memo }) => {
|
|
12
|
+
if (nodeType !== 'style-declaration') {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
const valid = memo(
|
|
16
|
+
`valid-style-${value.styleProperty}:${value.styleValue}`,
|
|
17
|
+
() => {
|
|
18
|
+
try {
|
|
19
|
+
parse(`${value.styleProperty}: ${value.styleValue}`)
|
|
20
|
+
return true
|
|
21
|
+
} catch {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
if (!valid) {
|
|
27
|
+
report(path, { property: value.styleProperty }, ['delete-style-property'])
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
fixes: {
|
|
31
|
+
'delete-style-property': removeFromPathFix,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type InvalidStyleSyntaxRuleFix = 'delete-style-property'
|
package/src/searchProject.ts
CHANGED
|
@@ -262,7 +262,12 @@ function* visitNode({
|
|
|
262
262
|
// The rule must have an implementation for the fix
|
|
263
263
|
rule.fixes?.[fixOptions.fixType]
|
|
264
264
|
) {
|
|
265
|
-
const ruleFixes = rule.fixes[fixOptions.fixType]?.(
|
|
265
|
+
const ruleFixes = rule.fixes[fixOptions.fixType]?.(
|
|
266
|
+
// We must use the path from the report, not the original path
|
|
267
|
+
// because the report might be for a subpath
|
|
268
|
+
{ ...data, path },
|
|
269
|
+
state,
|
|
270
|
+
)
|
|
266
271
|
if (ruleFixes) {
|
|
267
272
|
fixedFiles = ruleFixes
|
|
268
273
|
}
|
|
@@ -571,6 +576,25 @@ function* visitNode({
|
|
|
571
576
|
})
|
|
572
577
|
}
|
|
573
578
|
}
|
|
579
|
+
for (const [styleKey, styleValue] of Object.entries(
|
|
580
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
581
|
+
value.style ?? {},
|
|
582
|
+
)) {
|
|
583
|
+
yield* visitNode({
|
|
584
|
+
args: {
|
|
585
|
+
nodeType: 'style-declaration',
|
|
586
|
+
value: { styleProperty: styleKey, styleValue, element: value },
|
|
587
|
+
path: [...path, 'style', styleKey],
|
|
588
|
+
rules,
|
|
589
|
+
files,
|
|
590
|
+
pathsToVisit,
|
|
591
|
+
useExactPaths,
|
|
592
|
+
memo,
|
|
593
|
+
},
|
|
594
|
+
state,
|
|
595
|
+
fixOptions: fixOptions as any,
|
|
596
|
+
})
|
|
597
|
+
}
|
|
574
598
|
}
|
|
575
599
|
break
|
|
576
600
|
|
package/src/types.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ import type { ToddleComponent } from '@nordcraft/core/dist/component/ToddleCompo
|
|
|
14
14
|
import type { Formula } from '@nordcraft/core/dist/formula/formula'
|
|
15
15
|
import type { PluginFormula } from '@nordcraft/core/dist/formula/formulaTypes'
|
|
16
16
|
import type { Theme } from '@nordcraft/core/dist/styling/theme'
|
|
17
|
+
import type { PluginAction } from '@nordcraft/core/dist/types'
|
|
17
18
|
import type {
|
|
18
19
|
ApiService,
|
|
19
|
-
PluginAction,
|
|
20
20
|
ProjectFiles,
|
|
21
21
|
Route,
|
|
22
22
|
ToddleProject,
|
|
@@ -30,9 +30,11 @@ import type { NoReferenceEventRuleFix } from './rules/events/noReferenceEventRul
|
|
|
30
30
|
import type { LegacyFormulaRuleFix } from './rules/formulas/legacyFormulaRule'
|
|
31
31
|
import type { NoReferenceComponentFormulaRuleFix } from './rules/formulas/noReferenceComponentFormulaRule'
|
|
32
32
|
import type { NoReferenceProjectFormulaRuleFix } from './rules/formulas/noReferenceProjectFormulaRule'
|
|
33
|
+
import type { NoStaticNodeConditionRuleFix } from './rules/logic/noStaticNodeConditionRule'
|
|
34
|
+
import type { NoReferenceNodeRuleFix } from './rules/noReferenceNodeRule'
|
|
35
|
+
import type { InvalidStyleSyntaxRuleFix } from './rules/style/invalidStyleSyntaxRule'
|
|
33
36
|
|
|
34
37
|
type Code =
|
|
35
|
-
| 'ambiguous style variable syntax'
|
|
36
38
|
| 'duplicate event trigger'
|
|
37
39
|
| 'duplicate url parameter'
|
|
38
40
|
| 'duplicate workflow parameter'
|
|
@@ -40,6 +42,7 @@ type Code =
|
|
|
40
42
|
| 'invalid api parser mode'
|
|
41
43
|
| 'invalid api proxy body setting'
|
|
42
44
|
| 'invalid element child'
|
|
45
|
+
| 'invalid style syntax'
|
|
43
46
|
| 'legacy action'
|
|
44
47
|
| 'legacy api'
|
|
45
48
|
| 'legacy formula'
|
|
@@ -53,9 +56,11 @@ type Code =
|
|
|
53
56
|
| 'no-reference component workflow'
|
|
54
57
|
| 'no-reference component'
|
|
55
58
|
| 'no-reference event'
|
|
59
|
+
| 'no-reference node'
|
|
56
60
|
| 'no-reference project action'
|
|
57
61
|
| 'no-reference project formula'
|
|
58
62
|
| 'no-reference variable'
|
|
63
|
+
| 'no-static-node-condition'
|
|
59
64
|
| 'no-unnecessary-condition-falsy'
|
|
60
65
|
| 'no-unnecessary-condition-truthy'
|
|
61
66
|
| 'non-empty void element'
|
|
@@ -297,6 +302,15 @@ type StyleVariantNode = {
|
|
|
297
302
|
value: { variant: StyleVariant; element: ElementNodeModel }
|
|
298
303
|
} & Base
|
|
299
304
|
|
|
305
|
+
type StyleNode = {
|
|
306
|
+
nodeType: 'style-declaration'
|
|
307
|
+
value: {
|
|
308
|
+
styleProperty: string
|
|
309
|
+
styleValue: string
|
|
310
|
+
element: ElementNodeModel
|
|
311
|
+
}
|
|
312
|
+
} & Base
|
|
313
|
+
|
|
300
314
|
export type NodeType =
|
|
301
315
|
| ActionModelNode
|
|
302
316
|
| ComponentAPIInputNode
|
|
@@ -316,6 +330,7 @@ export type NodeType =
|
|
|
316
330
|
| ProjectFormulaNode
|
|
317
331
|
| ProjectThemeNode
|
|
318
332
|
| ProjectRoute
|
|
333
|
+
| StyleNode
|
|
319
334
|
| StyleVariantNode
|
|
320
335
|
|
|
321
336
|
type FixType =
|
|
@@ -328,6 +343,9 @@ type FixType =
|
|
|
328
343
|
| NoReferenceAttributeRuleFix
|
|
329
344
|
| NoReferenceEventRuleFix
|
|
330
345
|
| NoReferenceComponentFormulaRuleFix
|
|
346
|
+
| NoReferenceNodeRuleFix
|
|
347
|
+
| InvalidStyleSyntaxRuleFix
|
|
348
|
+
| NoStaticNodeConditionRuleFix
|
|
331
349
|
|
|
332
350
|
export interface Rule<T = unknown, V = NodeType> {
|
|
333
351
|
category: Category
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { contextlessEvaluateFormula } from './contextlessEvaluateFormula'
|
|
3
|
+
|
|
4
|
+
describe('contextlessEvaluateFormula', () => {
|
|
5
|
+
test('should return `true` when the formula is a simple value formula', () => {
|
|
6
|
+
expect(
|
|
7
|
+
contextlessEvaluateFormula({
|
|
8
|
+
type: 'value',
|
|
9
|
+
value: true,
|
|
10
|
+
}),
|
|
11
|
+
).toEqual({
|
|
12
|
+
isStatic: true,
|
|
13
|
+
result: true,
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should not return a result and have `isStatic: false` when the formula uses a non-pure formula ("randomNumber", "Now")', () => {
|
|
18
|
+
expect(
|
|
19
|
+
contextlessEvaluateFormula({
|
|
20
|
+
type: 'apply',
|
|
21
|
+
name: 'randomNumber',
|
|
22
|
+
arguments: [],
|
|
23
|
+
}),
|
|
24
|
+
).toEqual({
|
|
25
|
+
isStatic: false,
|
|
26
|
+
result: undefined,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(
|
|
30
|
+
contextlessEvaluateFormula({
|
|
31
|
+
type: 'apply',
|
|
32
|
+
name: 'now',
|
|
33
|
+
arguments: [],
|
|
34
|
+
}),
|
|
35
|
+
).toEqual({
|
|
36
|
+
isStatic: false,
|
|
37
|
+
result: undefined,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('should not return a result and have `isStatic: false` when the formula depends on a variable', () => {
|
|
42
|
+
expect(
|
|
43
|
+
contextlessEvaluateFormula({
|
|
44
|
+
type: 'path',
|
|
45
|
+
path: ['Variables', 'myVariable'],
|
|
46
|
+
}),
|
|
47
|
+
).toEqual({
|
|
48
|
+
isStatic: false,
|
|
49
|
+
result: undefined,
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('should return a result and static for an array formula where all args are array or value types', () => {
|
|
54
|
+
expect(
|
|
55
|
+
contextlessEvaluateFormula({
|
|
56
|
+
type: 'array',
|
|
57
|
+
arguments: [
|
|
58
|
+
{ formula: { type: 'value', value: 1 } },
|
|
59
|
+
{
|
|
60
|
+
formula: {
|
|
61
|
+
type: 'array',
|
|
62
|
+
arguments: [{ formula: { type: 'value', value: 2 } }],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}),
|
|
67
|
+
).toEqual({
|
|
68
|
+
isStatic: true,
|
|
69
|
+
result: [1, [2]],
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('should return `isStatic: false` for an array formula with a non-static argument', () => {
|
|
74
|
+
expect(
|
|
75
|
+
contextlessEvaluateFormula({
|
|
76
|
+
type: 'array',
|
|
77
|
+
arguments: [
|
|
78
|
+
{ formula: { type: 'value', value: 1 } },
|
|
79
|
+
{ formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
|
|
80
|
+
],
|
|
81
|
+
}),
|
|
82
|
+
).toEqual({
|
|
83
|
+
isStatic: false,
|
|
84
|
+
result: [
|
|
85
|
+
1,
|
|
86
|
+
undefined, // The second argument was not static
|
|
87
|
+
],
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('should be static true for `And` formulas with no arguments', () => {
|
|
92
|
+
expect(
|
|
93
|
+
contextlessEvaluateFormula({
|
|
94
|
+
type: 'and',
|
|
95
|
+
arguments: [],
|
|
96
|
+
}),
|
|
97
|
+
).toEqual({
|
|
98
|
+
isStatic: true,
|
|
99
|
+
result: true,
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('should be static false for `Or` formulas with no arguments', () => {
|
|
104
|
+
expect(
|
|
105
|
+
contextlessEvaluateFormula({
|
|
106
|
+
type: 'or',
|
|
107
|
+
arguments: [],
|
|
108
|
+
}),
|
|
109
|
+
).toEqual({
|
|
110
|
+
isStatic: true,
|
|
111
|
+
result: false,
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('should be static true for `And` formulas with all static truthy arguments', () => {
|
|
116
|
+
expect(
|
|
117
|
+
contextlessEvaluateFormula({
|
|
118
|
+
type: 'and',
|
|
119
|
+
arguments: [
|
|
120
|
+
{ formula: { type: 'value', value: true } },
|
|
121
|
+
{ formula: { type: 'value', value: true } },
|
|
122
|
+
],
|
|
123
|
+
}),
|
|
124
|
+
).toEqual({
|
|
125
|
+
isStatic: true,
|
|
126
|
+
result: true,
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('should not be static for `And` where any dynamic value exist', () => {
|
|
131
|
+
expect(
|
|
132
|
+
contextlessEvaluateFormula({
|
|
133
|
+
type: 'and',
|
|
134
|
+
arguments: [
|
|
135
|
+
{ formula: { type: 'value', value: true } },
|
|
136
|
+
{ formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
).toEqual({
|
|
140
|
+
isStatic: false,
|
|
141
|
+
result: undefined,
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('should be static false for `And` when any argument is static false, even when dynamic arguments exist', () => {
|
|
146
|
+
expect(
|
|
147
|
+
contextlessEvaluateFormula({
|
|
148
|
+
type: 'and',
|
|
149
|
+
arguments: [
|
|
150
|
+
{ formula: { type: 'value', value: true } },
|
|
151
|
+
{ formula: { type: 'path', path: ['Variables', 'myVariable'] } },
|
|
152
|
+
{ formula: { type: 'value', value: false } },
|
|
153
|
+
],
|
|
154
|
+
}),
|
|
155
|
+
).toEqual({
|
|
156
|
+
isStatic: true,
|
|
157
|
+
result: false,
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('should not be static for `Or` when any argument is dynamic and all static are falsy', () => {
|
|
162
|
+
expect(
|
|
163
|
+
contextlessEvaluateFormula({
|
|
164
|
+
type: 'or',
|
|
165
|
+
arguments: [
|
|
166
|
+
{ formula: { type: 'value', value: false } },
|
|
167
|
+
{ formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
|
|
168
|
+
],
|
|
169
|
+
}),
|
|
170
|
+
).toEqual({
|
|
171
|
+
isStatic: false,
|
|
172
|
+
result: undefined,
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('should be static true for `Or` when any argument is static true, even when dynamic arguments exist', () => {
|
|
177
|
+
expect(
|
|
178
|
+
contextlessEvaluateFormula({
|
|
179
|
+
type: 'or',
|
|
180
|
+
arguments: [
|
|
181
|
+
{ formula: { type: 'value', value: true } },
|
|
182
|
+
{ formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
|
|
183
|
+
],
|
|
184
|
+
}),
|
|
185
|
+
).toEqual({
|
|
186
|
+
isStatic: true,
|
|
187
|
+
result: true,
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
})
|