@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.
Files changed (80) hide show
  1. package/dist/problems.worker.js +4 -2
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/actions/legacyActionRule.fix.js +140 -0
  4. package/dist/rules/actions/legacyActionRule.fix.js.map +1 -0
  5. package/dist/rules/actions/legacyActionRule.js +3 -144
  6. package/dist/rules/actions/legacyActionRule.js.map +1 -1
  7. package/dist/rules/actions/legacyActionRule.test.js +4 -3
  8. package/dist/rules/actions/legacyActionRule.test.js.map +1 -1
  9. package/dist/rules/actions/noReferenceProjectActionRule.js +33 -40
  10. package/dist/rules/actions/noReferenceProjectActionRule.js.map +1 -1
  11. package/dist/rules/apis/noReferenceApiRule.js +7 -2
  12. package/dist/rules/apis/noReferenceApiRule.js.map +1 -1
  13. package/dist/rules/apis/noReferenceApiRule.test.js +65 -1
  14. package/dist/rules/apis/noReferenceApiRule.test.js.map +1 -1
  15. package/dist/rules/attributes/noReferenceAttributeRule.js +5 -1
  16. package/dist/rules/attributes/noReferenceAttributeRule.js.map +1 -1
  17. package/dist/rules/attributes/noReferenceAttributeRule.test.js +33 -1
  18. package/dist/rules/attributes/noReferenceAttributeRule.test.js.map +1 -1
  19. package/dist/rules/components/noReferenceComponentRule.js +18 -28
  20. package/dist/rules/components/noReferenceComponentRule.js.map +1 -1
  21. package/dist/rules/events/duplicateEventTriggerRule.js +2 -1
  22. package/dist/rules/events/duplicateEventTriggerRule.js.map +1 -1
  23. package/dist/rules/events/noReferenceEventRule.js +6 -2
  24. package/dist/rules/events/noReferenceEventRule.js.map +1 -1
  25. package/dist/rules/events/noReferenceEventRule.test.js +57 -1
  26. package/dist/rules/events/noReferenceEventRule.test.js.map +1 -1
  27. package/dist/rules/formulas/legacyFormulaRule.fix.js +574 -0
  28. package/dist/rules/formulas/legacyFormulaRule.fix.js.map +1 -0
  29. package/dist/rules/formulas/legacyFormulaRule.js +5 -580
  30. package/dist/rules/formulas/legacyFormulaRule.js.map +1 -1
  31. package/dist/rules/formulas/noReferenceComponentFormulaRule.js +8 -1
  32. package/dist/rules/formulas/noReferenceComponentFormulaRule.js.map +1 -1
  33. package/dist/rules/formulas/noReferenceProjectFormulaRule.js +63 -72
  34. package/dist/rules/formulas/noReferenceProjectFormulaRule.js.map +1 -1
  35. package/dist/rules/noReferenceNodeRule.js +22 -0
  36. package/dist/rules/noReferenceNodeRule.js.map +1 -0
  37. package/dist/rules/noReferenceNodeRule.test.js +131 -0
  38. package/dist/rules/noReferenceNodeRule.test.js.map +1 -0
  39. package/dist/rules/style/invalidStyleSyntaxRule.js +28 -0
  40. package/dist/rules/style/invalidStyleSyntaxRule.js.map +1 -0
  41. package/dist/rules/style/invalidStyleSyntaxRule.test.js +100 -0
  42. package/dist/rules/style/invalidStyleSyntaxRule.test.js.map +1 -0
  43. package/dist/searchProject.js +57 -16
  44. package/dist/searchProject.js.map +1 -1
  45. package/dist/util/removeUnused.fix.js +3 -0
  46. package/dist/util/removeUnused.fix.js.map +1 -0
  47. package/package.json +4 -3
  48. package/src/problems.worker.ts +4 -2
  49. package/src/rules/actions/legacyActionRule.fix.ts +157 -0
  50. package/src/rules/actions/legacyActionRule.test.ts +4 -3
  51. package/src/rules/actions/legacyActionRule.ts +3 -159
  52. package/src/rules/actions/noReferenceProjectActionRule.ts +39 -47
  53. package/src/rules/apis/noReferenceApiRule.test.ts +67 -1
  54. package/src/rules/apis/noReferenceApiRule.ts +9 -2
  55. package/src/rules/attributes/noReferenceAttributeRule.test.ts +35 -1
  56. package/src/rules/attributes/noReferenceAttributeRule.ts +7 -2
  57. package/src/rules/components/noReferenceComponentRule.ts +23 -34
  58. package/src/rules/events/duplicateEventTriggerRule.ts +2 -1
  59. package/src/rules/events/noReferenceEventRule.test.ts +59 -1
  60. package/src/rules/events/noReferenceEventRule.ts +8 -3
  61. package/src/rules/formulas/legacyFormulaRule.fix.ts +661 -0
  62. package/src/rules/formulas/legacyFormulaRule.ts +9 -670
  63. package/src/rules/formulas/noReferenceComponentFormulaRule.ts +15 -3
  64. package/src/rules/formulas/noReferenceProjectFormulaRule.ts +70 -77
  65. package/src/rules/noReferenceNodeRule.test.ts +140 -0
  66. package/src/rules/noReferenceNodeRule.ts +27 -0
  67. package/src/rules/style/invalidStyleSyntaxRule.test.ts +106 -0
  68. package/src/rules/style/invalidStyleSyntaxRule.ts +35 -0
  69. package/src/searchProject.ts +66 -22
  70. package/src/types.d.ts +33 -10
  71. package/src/util/removeUnused.fix.ts +5 -0
  72. package/dist/memos/getAllCustomPropertiesBySyntax.js +0 -43
  73. package/dist/memos/getAllCustomPropertiesBySyntax.js.map +0 -1
  74. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js +0 -50
  75. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js.map +0 -1
  76. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js +0 -265
  77. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js.map +0 -1
  78. package/src/memos/getAllCustomPropertiesBySyntax.ts +0 -68
  79. package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.ts +0 -278
  80. 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
- report(path, { contextSubscribers, name: value.name })
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 { NodeType, Rule } from '../../types'
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, details) => {
14
- if (!hasReferences(details)) {
15
- report(details.path, undefined, ['delete-project-formula'])
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
- // Check in all API services first, since that should be quick
35
- for (const apiService of Object.values(files.services ?? {})) {
36
- const service = new ToddleApiService({
37
- service: apiService,
38
- globalFormulas: { formulas: files.formulas, packages: files.packages },
39
- })
40
- const formulas = service.formulasInService()
41
- for (const { path: _formulaPath, formula } of formulas) {
42
- // Check if the formula is used in the formula
43
- if (checkFormula(formula, value.name)) {
44
- return true
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
- // Check routes before components, since they should be quicker
50
- for (const projectRoute of Object.values(files.routes ?? {})) {
51
- const route = new ToddleRoute({
52
- route: projectRoute,
53
- globalFormulas: { formulas: files.formulas, packages: files.packages },
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
- for (const { formula } of c.formulasInComponent()) {
78
- if (formula.type === 'function') {
79
- usedFormulas.add(formula.name)
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
- if (componentFormulaReferences.has(value.name)) {
87
- return true
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
- // TODO: Memoize similar to above. We need have a helper class `ToddleFormula` with `ToddleFormula.normalizeFormulas()`
91
- for (const f of Object.values(files.formulas ?? {})) {
92
- if (f.name === value.name) {
93
- continue
73
+ if (componentFormulaReferences.has(value.name)) {
74
+ return
94
75
  }
95
76
 
96
- // Check if the formula is used in the formula
97
- if (isToddleFormula(f) && checkFormula(f.formula, value.name)) {
98
- return true
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
- return false
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'
@@ -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: { mode: 'FIX'; fixType: FixType }
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?: { mode: 'FIX'; fixType: FixType }
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
- if (fixOptions) {
247
- // We're fixing issues
248
- for (const rule of rules) {
249
- const fixedFiles = rule.fixes?.[fixOptions.fixType]?.(data, state)
250
- if (fixedFiles) {
251
- yield fixedFiles
252
- }
253
- }
254
- } else {
255
- // We're looking for issues
256
- const results: Result[] = []
257
- for (const rule of rules) {
258
- // eslint-disable-next-line no-console
259
- console.timeStamp(`Visiting rule ${rule.code}`)
260
- rule.visit(
261
- (path, details, fixes) => {
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
- data,
272
- state,
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