@nordcraft/search 1.0.43 → 1.0.45
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 +4 -2
- package/dist/problems.worker.js.map +1 -1
- package/dist/rules/actions/legacyActionRule.fix.js +140 -0
- package/dist/rules/actions/legacyActionRule.fix.js.map +1 -0
- package/dist/rules/actions/legacyActionRule.js +3 -144
- package/dist/rules/actions/legacyActionRule.js.map +1 -1
- package/dist/rules/actions/legacyActionRule.test.js +4 -3
- package/dist/rules/actions/legacyActionRule.test.js.map +1 -1
- package/dist/rules/actions/noReferenceProjectActionRule.js +33 -40
- package/dist/rules/actions/noReferenceProjectActionRule.js.map +1 -1
- package/dist/rules/apis/noReferenceApiRule.js +7 -2
- package/dist/rules/apis/noReferenceApiRule.js.map +1 -1
- package/dist/rules/apis/noReferenceApiRule.test.js +65 -1
- package/dist/rules/apis/noReferenceApiRule.test.js.map +1 -1
- package/dist/rules/attributes/noReferenceAttributeRule.js +5 -1
- package/dist/rules/attributes/noReferenceAttributeRule.js.map +1 -1
- package/dist/rules/attributes/noReferenceAttributeRule.test.js +33 -1
- package/dist/rules/attributes/noReferenceAttributeRule.test.js.map +1 -1
- package/dist/rules/components/noReferenceComponentRule.js +18 -28
- package/dist/rules/components/noReferenceComponentRule.js.map +1 -1
- package/dist/rules/events/duplicateEventTriggerRule.js +2 -1
- package/dist/rules/events/duplicateEventTriggerRule.js.map +1 -1
- package/dist/rules/events/noReferenceEventRule.js +6 -2
- package/dist/rules/events/noReferenceEventRule.js.map +1 -1
- package/dist/rules/events/noReferenceEventRule.test.js +57 -1
- package/dist/rules/events/noReferenceEventRule.test.js.map +1 -1
- package/dist/rules/formulas/legacyFormulaRule.fix.js +574 -0
- package/dist/rules/formulas/legacyFormulaRule.fix.js.map +1 -0
- package/dist/rules/formulas/legacyFormulaRule.js +5 -580
- package/dist/rules/formulas/legacyFormulaRule.js.map +1 -1
- package/dist/rules/formulas/noReferenceComponentFormulaRule.js +8 -1
- package/dist/rules/formulas/noReferenceComponentFormulaRule.js.map +1 -1
- package/dist/rules/formulas/noReferenceProjectFormulaRule.js +63 -72
- package/dist/rules/formulas/noReferenceProjectFormulaRule.js.map +1 -1
- package/dist/rules/noReferenceNodeRule.js +22 -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 +57 -16
- package/dist/searchProject.js.map +1 -1
- package/dist/util/removeUnused.fix.js +3 -0
- package/dist/util/removeUnused.fix.js.map +1 -0
- package/package.json +4 -3
- package/src/problems.worker.ts +4 -2
- package/src/rules/actions/legacyActionRule.fix.ts +157 -0
- package/src/rules/actions/legacyActionRule.test.ts +4 -3
- package/src/rules/actions/legacyActionRule.ts +3 -159
- package/src/rules/actions/noReferenceProjectActionRule.ts +39 -47
- package/src/rules/apis/noReferenceApiRule.test.ts +67 -1
- package/src/rules/apis/noReferenceApiRule.ts +9 -2
- package/src/rules/attributes/noReferenceAttributeRule.test.ts +35 -1
- package/src/rules/attributes/noReferenceAttributeRule.ts +7 -2
- package/src/rules/components/noReferenceComponentRule.ts +23 -34
- package/src/rules/events/duplicateEventTriggerRule.ts +2 -1
- package/src/rules/events/noReferenceEventRule.test.ts +59 -1
- package/src/rules/events/noReferenceEventRule.ts +8 -3
- package/src/rules/formulas/legacyFormulaRule.fix.ts +661 -0
- package/src/rules/formulas/legacyFormulaRule.ts +9 -670
- package/src/rules/formulas/noReferenceComponentFormulaRule.ts +15 -3
- package/src/rules/formulas/noReferenceProjectFormulaRule.ts +70 -77
- package/src/rules/noReferenceNodeRule.test.ts +140 -0
- package/src/rules/noReferenceNodeRule.ts +27 -0
- package/src/rules/style/invalidStyleSyntaxRule.test.ts +106 -0
- package/src/rules/style/invalidStyleSyntaxRule.ts +35 -0
- package/src/searchProject.ts +66 -22
- package/src/types.d.ts +33 -10
- package/src/util/removeUnused.fix.ts +5 -0
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ToddleComponent } from '@nordcraft/core/dist/component/ToddleComponent'
|
|
2
2
|
import type { Rule } from '../../types'
|
|
3
|
+
import { removeFromPathFix } from '../../util/removeUnused.fix'
|
|
3
4
|
|
|
4
5
|
export const noReferenceComponentFormulaRule: Rule<{
|
|
5
6
|
name: string
|
|
@@ -38,7 +39,7 @@ export const noReferenceComponentFormulaRule: Rule<{
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
// It is possible that a formula is never used, but still has subscribers
|
|
41
|
-
const contextSubscribers = []
|
|
42
|
+
const contextSubscribers: string[] = []
|
|
42
43
|
if (value.exposeInContext) {
|
|
43
44
|
for (const _component of Object.values(files.components)) {
|
|
44
45
|
// Enforce that the component is not undefined since we're iterating
|
|
@@ -74,7 +75,18 @@ export const noReferenceComponentFormulaRule: Rule<{
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
report(
|
|
79
|
+
args.path,
|
|
80
|
+
{
|
|
81
|
+
contextSubscribers,
|
|
82
|
+
name: value.name,
|
|
83
|
+
},
|
|
84
|
+
['delete-component-formula'],
|
|
85
|
+
)
|
|
86
|
+
},
|
|
87
|
+
fixes: {
|
|
88
|
+
'delete-component-formula': removeFromPathFix,
|
|
79
89
|
},
|
|
80
90
|
}
|
|
91
|
+
|
|
92
|
+
export type NoReferenceComponentFormulaRuleFix = 'delete-component-formula'
|
|
@@ -1,106 +1,99 @@
|
|
|
1
1
|
import { ToddleComponent } from '@nordcraft/core/dist/component/ToddleComponent'
|
|
2
2
|
import type { Formula } from '@nordcraft/core/dist/formula/formula'
|
|
3
3
|
import { isToddleFormula } from '@nordcraft/core/dist/formula/formulaTypes'
|
|
4
|
-
import { omit } from '@nordcraft/core/dist/utils/collections'
|
|
5
4
|
import { ToddleApiService } from '@nordcraft/ssr/dist/ToddleApiService'
|
|
6
5
|
import { ToddleRoute } from '@nordcraft/ssr/dist/ToddleRoute'
|
|
7
|
-
import type {
|
|
6
|
+
import type { Rule } from '../../types'
|
|
7
|
+
import { removeFromPathFix } from '../../util/removeUnused.fix'
|
|
8
8
|
|
|
9
9
|
export const noReferenceProjectFormulaRule: Rule<void> = {
|
|
10
10
|
code: 'no-reference project formula',
|
|
11
11
|
level: 'warning',
|
|
12
12
|
category: 'No References',
|
|
13
|
-
visit: (report,
|
|
14
|
-
if (
|
|
15
|
-
|
|
13
|
+
visit: (report, { value, path, files, nodeType, memo }) => {
|
|
14
|
+
if (nodeType !== 'project-formula' || value.exported === true) {
|
|
15
|
+
return
|
|
16
16
|
}
|
|
17
|
-
},
|
|
18
|
-
fixes: {
|
|
19
|
-
'delete-project-formula': (data) => {
|
|
20
|
-
if (!hasReferences(data)) {
|
|
21
|
-
return omit(data.files, data.path)
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type NoReferenceProjectFormulaRuleFix = 'delete-project-formula'
|
|
28
|
-
|
|
29
|
-
const hasReferences = ({ value, files, nodeType, memo }: NodeType) => {
|
|
30
|
-
if (nodeType !== 'project-formula' || value.exported === true) {
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
33
17
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
18
|
+
// Check in all API services first, since that should be quick
|
|
19
|
+
for (const apiService of Object.values(files.services ?? {})) {
|
|
20
|
+
const service = new ToddleApiService({
|
|
21
|
+
service: apiService,
|
|
22
|
+
globalFormulas: { formulas: files.formulas, packages: files.packages },
|
|
23
|
+
})
|
|
24
|
+
const formulas = service.formulasInService()
|
|
25
|
+
for (const { path: _formulaPath, formula } of formulas) {
|
|
26
|
+
// Check if the formula is used in the formula
|
|
27
|
+
if (checkFormula(formula, value.name)) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
45
30
|
}
|
|
46
31
|
}
|
|
47
|
-
}
|
|
48
32
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
const formulas = route.formulasInRoute()
|
|
56
|
-
for (const { path: _formulaPath, formula } of formulas) {
|
|
57
|
-
// Check if the formula is used in the formula
|
|
58
|
-
if (checkFormula(formula, value.name)) {
|
|
59
|
-
return true
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const componentFormulaReferences = memo('componentFormulaReferences', () => {
|
|
65
|
-
const usedFormulas = new Set<string>()
|
|
66
|
-
for (const component of Object.values(files.components)) {
|
|
67
|
-
const c = new ToddleComponent({
|
|
68
|
-
// Enforce that the component is not undefined since we're iterating
|
|
69
|
-
component: component!,
|
|
70
|
-
getComponent: (name) => files.components[name],
|
|
71
|
-
packageName: undefined,
|
|
72
|
-
globalFormulas: {
|
|
73
|
-
formulas: files.formulas,
|
|
74
|
-
packages: files.packages,
|
|
75
|
-
},
|
|
33
|
+
// Check routes before components, since they should be quicker
|
|
34
|
+
for (const projectRoute of Object.values(files.routes ?? {})) {
|
|
35
|
+
const route = new ToddleRoute({
|
|
36
|
+
route: projectRoute,
|
|
37
|
+
globalFormulas: { formulas: files.formulas, packages: files.packages },
|
|
76
38
|
})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
39
|
+
const formulas = route.formulasInRoute()
|
|
40
|
+
for (const { path: _formulaPath, formula } of formulas) {
|
|
41
|
+
// Check if the formula is used in the formula
|
|
42
|
+
if (checkFormula(formula, value.name)) {
|
|
43
|
+
return
|
|
80
44
|
}
|
|
81
45
|
}
|
|
82
46
|
}
|
|
83
|
-
return usedFormulas
|
|
84
|
-
})
|
|
85
47
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
48
|
+
const componentFormulaReferences = memo(
|
|
49
|
+
'componentFormulaReferences',
|
|
50
|
+
() => {
|
|
51
|
+
const usedFormulas = new Set<string>()
|
|
52
|
+
for (const component of Object.values(files.components)) {
|
|
53
|
+
const c = new ToddleComponent({
|
|
54
|
+
// Enforce that the component is not undefined since we're iterating
|
|
55
|
+
component: component!,
|
|
56
|
+
getComponent: (name) => files.components[name],
|
|
57
|
+
packageName: undefined,
|
|
58
|
+
globalFormulas: {
|
|
59
|
+
formulas: files.formulas,
|
|
60
|
+
packages: files.packages,
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
for (const { formula } of c.formulasInComponent()) {
|
|
64
|
+
if (formula.type === 'function') {
|
|
65
|
+
usedFormulas.add(formula.name)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return usedFormulas
|
|
70
|
+
},
|
|
71
|
+
)
|
|
89
72
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (f.name === value.name) {
|
|
93
|
-
continue
|
|
73
|
+
if (componentFormulaReferences.has(value.name)) {
|
|
74
|
+
return
|
|
94
75
|
}
|
|
95
76
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
77
|
+
// TODO: Memoize similar to above. We need have a helper class `ToddleFormula` with `ToddleFormula.normalizeFormulas()`
|
|
78
|
+
for (const f of Object.values(files.formulas ?? {})) {
|
|
79
|
+
if (f.name === value.name) {
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if the formula is used in the formula
|
|
84
|
+
if (isToddleFormula(f) && checkFormula(f.formula, value.name)) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
99
87
|
}
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
report(path, undefined, ['delete-project-formula'])
|
|
89
|
+
},
|
|
90
|
+
fixes: {
|
|
91
|
+
'delete-project-formula': removeFromPathFix,
|
|
92
|
+
},
|
|
102
93
|
}
|
|
103
94
|
|
|
95
|
+
export type NoReferenceProjectFormulaRuleFix = 'delete-project-formula'
|
|
96
|
+
|
|
104
97
|
const checkArguments = (
|
|
105
98
|
args: {
|
|
106
99
|
formula: Formula
|
|
@@ -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,27 @@
|
|
|
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') {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
const { path, value: component } = args
|
|
13
|
+
const referencedNodes = new Set(
|
|
14
|
+
Object.values(component.nodes).flatMap((node) => node.children ?? []),
|
|
15
|
+
)
|
|
16
|
+
for (const key of Object.keys(component.nodes)) {
|
|
17
|
+
if (key !== 'root' && !referencedNodes.has(key)) {
|
|
18
|
+
report([...path, 'nodes', key], { node: key }, ['delete orphan node'])
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
fixes: {
|
|
23
|
+
'delete orphan node': removeFromPathFix,
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
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
|
@@ -200,6 +200,7 @@ function visitNode(args: {
|
|
|
200
200
|
useExactPaths: boolean
|
|
201
201
|
} & NodeType
|
|
202
202
|
state: ApplicationState | undefined
|
|
203
|
+
fixOptions: never
|
|
203
204
|
}): Generator<Result>
|
|
204
205
|
function visitNode(args: {
|
|
205
206
|
args: {
|
|
@@ -210,7 +211,7 @@ function visitNode(args: {
|
|
|
210
211
|
useExactPaths: boolean
|
|
211
212
|
} & NodeType
|
|
212
213
|
state: ApplicationState | undefined
|
|
213
|
-
fixOptions:
|
|
214
|
+
fixOptions: FixOptions
|
|
214
215
|
}): Generator<ProjectFiles | void>
|
|
215
216
|
function* visitNode({
|
|
216
217
|
args,
|
|
@@ -225,7 +226,7 @@ function* visitNode({
|
|
|
225
226
|
useExactPaths: boolean
|
|
226
227
|
} & NodeType
|
|
227
228
|
state: ApplicationState | undefined
|
|
228
|
-
fixOptions?:
|
|
229
|
+
fixOptions?: FixOptions
|
|
229
230
|
}): Generator<Result | ProjectFiles | void> {
|
|
230
231
|
const { rules, pathsToVisit, useExactPaths, ...data } = args
|
|
231
232
|
const { files, value, path, memo, nodeType } = data
|
|
@@ -243,22 +244,36 @@ function* visitNode({
|
|
|
243
244
|
!useExactPaths ||
|
|
244
245
|
shouldSearchExactPath({ path: data.path, pathsToVisit })
|
|
245
246
|
) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
247
|
+
const results: Result[] = []
|
|
248
|
+
let fixedFiles: ProjectFiles | undefined
|
|
249
|
+
for (const rule of rules) {
|
|
250
|
+
// eslint-disable-next-line no-console
|
|
251
|
+
console.timeStamp(`Visiting rule ${rule.code}`)
|
|
252
|
+
rule.visit(
|
|
253
|
+
// Report callback used to report issues
|
|
254
|
+
(path, details, fixes) => {
|
|
255
|
+
if (fixOptions) {
|
|
256
|
+
// We're in "fix mode"
|
|
257
|
+
if (
|
|
258
|
+
// We only overwrite fixedFiles once to avoid conflicting fixes
|
|
259
|
+
!fixedFiles &&
|
|
260
|
+
// The current fix must be one of the fixes reported
|
|
261
|
+
fixes?.includes(fixOptions.fixType) &&
|
|
262
|
+
// The rule must have an implementation for the fix
|
|
263
|
+
rule.fixes?.[fixOptions.fixType]
|
|
264
|
+
) {
|
|
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
|
+
)
|
|
271
|
+
if (ruleFixes) {
|
|
272
|
+
fixedFiles = ruleFixes
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
// We're in "report mode"
|
|
262
277
|
results.push({
|
|
263
278
|
code: rule.code,
|
|
264
279
|
category: rule.category,
|
|
@@ -267,12 +282,22 @@ function* visitNode({
|
|
|
267
282
|
details,
|
|
268
283
|
fixes,
|
|
269
284
|
})
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
data,
|
|
288
|
+
state,
|
|
289
|
+
)
|
|
290
|
+
if (fixedFiles) {
|
|
291
|
+
// We have applied a fix, stop processing more rules
|
|
292
|
+
break
|
|
274
293
|
}
|
|
294
|
+
}
|
|
275
295
|
|
|
296
|
+
if (fixOptions) {
|
|
297
|
+
if (fixedFiles) {
|
|
298
|
+
yield fixedFiles
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
276
301
|
for (const result of results) {
|
|
277
302
|
yield result
|
|
278
303
|
}
|
|
@@ -551,6 +576,25 @@ function* visitNode({
|
|
|
551
576
|
})
|
|
552
577
|
}
|
|
553
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
|
+
}
|
|
554
598
|
}
|
|
555
599
|
break
|
|
556
600
|
|