@nordcraft/search 1.0.45 → 1.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/problems.worker.js +9 -3
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/logic/noStaticNodeCondition.js +29 -0
  4. package/dist/rules/logic/noStaticNodeCondition.js.map +1 -0
  5. package/dist/rules/logic/noStaticNodeCondition.test.js +274 -0
  6. package/dist/rules/logic/noStaticNodeCondition.test.js.map +1 -0
  7. package/dist/rules/logic/noUnnecessaryConditionFalsy.js +5 -1
  8. package/dist/rules/logic/noUnnecessaryConditionFalsy.js.map +1 -1
  9. package/dist/rules/logic/noUnnecessaryConditionTruthy.js +8 -4
  10. package/dist/rules/logic/noUnnecessaryConditionTruthy.js.map +1 -1
  11. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js +33 -4
  12. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js.map +1 -1
  13. package/dist/rules/noReferenceNodeRule.js +10 -8
  14. package/dist/rules/noReferenceNodeRule.js.map +1 -1
  15. package/dist/rules/noReferenceNodeRule.test.js +1 -1
  16. package/dist/rules/style/invalidStyleSyntaxRule.js +2 -2
  17. package/dist/rules/style/invalidStyleSyntaxRule.test.js +1 -1
  18. package/dist/util/contextlessEvaluateFormula.js +77 -0
  19. package/dist/util/contextlessEvaluateFormula.js.map +1 -0
  20. package/dist/util/contextlessEvaluateFormula.test.js +152 -0
  21. package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
  22. package/dist/util/removeUnused.fix.js +18 -1
  23. package/dist/util/removeUnused.fix.js.map +1 -1
  24. package/package.json +2 -2
  25. package/src/problems.worker.ts +16 -5
  26. package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
  27. package/src/rules/logic/noStaticNodeCondition.ts +43 -0
  28. package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
  29. package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
  30. package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
  31. package/src/rules/noReferenceNodeRule.test.ts +1 -1
  32. package/src/rules/noReferenceNodeRule.ts +17 -10
  33. package/src/rules/style/invalidStyleSyntaxRule.test.ts +1 -1
  34. package/src/rules/style/invalidStyleSyntaxRule.ts +3 -3
  35. package/src/types.d.ts +3 -0
  36. package/src/util/contextlessEvaluateFormula.test.ts +190 -0
  37. package/src/util/contextlessEvaluateFormula.ts +110 -0
  38. package/src/util/removeUnused.fix.ts +29 -1
@@ -0,0 +1,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
  }
@@ -130,7 +130,7 @@ describe('fix noReferenceNodeRule', () => {
130
130
  const fixedFiles = fixProject({
131
131
  files,
132
132
  rule: noReferenceNodeRule,
133
- fixType: 'delete orphan node',
133
+ fixType: 'delete-orphan-node',
134
134
  })
135
135
  expect(Object.keys(fixedFiles.components.test!.nodes)).toEqual([
136
136
  'root',
@@ -6,22 +6,29 @@ export const noReferenceNodeRule: Rule<{ node: string }> = {
6
6
  level: 'warning',
7
7
  category: 'No References',
8
8
  visit: (report, args) => {
9
- if (args.nodeType !== 'component') {
9
+ if (args.nodeType !== 'component-node') {
10
10
  return
11
11
  }
12
- const { path, value: component } = args
13
- const referencedNodes = new Set(
14
- Object.values(component.nodes).flatMap((node) => node.children ?? []),
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
+ ),
15
23
  )
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
- }
24
+
25
+ if (nodeId !== 'root' && !referencedNodesInComponent.has(nodeId)) {
26
+ report(path, { node: nodeId }, ['delete-orphan-node'])
20
27
  }
21
28
  },
22
29
  fixes: {
23
- 'delete orphan node': removeFromPathFix,
30
+ 'delete-orphan-node': removeFromPathFix,
24
31
  },
25
32
  }
26
33
 
27
- export type NoReferenceNodeRuleFix = 'delete orphan node'
34
+ export type NoReferenceNodeRuleFix = 'delete-orphan-node'
@@ -92,7 +92,7 @@ describe('fix invalidStyleSyntaxRule', () => {
92
92
  const fixedFiles = fixProject({
93
93
  files,
94
94
  rule: invalidStyleSyntaxRule,
95
- fixType: 'delete style property',
95
+ fixType: 'delete-style-property',
96
96
  })
97
97
  expect((fixedFiles.components.test!.nodes.root as ElementNodeModel).style)
98
98
  .toMatchInlineSnapshot(`
@@ -24,12 +24,12 @@ export const invalidStyleSyntaxRule: Rule<{
24
24
  },
25
25
  )
26
26
  if (!valid) {
27
- report(path, { property: value.styleProperty }, ['delete style property'])
27
+ report(path, { property: value.styleProperty }, ['delete-style-property'])
28
28
  }
29
29
  },
30
30
  fixes: {
31
- 'delete style property': removeFromPathFix,
31
+ 'delete-style-property': removeFromPathFix,
32
32
  },
33
33
  }
34
34
 
35
- export type InvalidStyleSyntaxRuleFix = 'delete style property'
35
+ export type InvalidStyleSyntaxRuleFix = 'delete-style-property'
package/src/types.d.ts CHANGED
@@ -30,6 +30,7 @@ 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'
33
34
  import type { NoReferenceNodeRuleFix } from './rules/noReferenceNodeRule'
34
35
  import type { InvalidStyleSyntaxRuleFix } from './rules/style/invalidStyleSyntaxRule'
35
36
 
@@ -59,6 +60,7 @@ type Code =
59
60
  | 'no-reference project action'
60
61
  | 'no-reference project formula'
61
62
  | 'no-reference variable'
63
+ | 'no-static-node-condition'
62
64
  | 'no-unnecessary-condition-falsy'
63
65
  | 'no-unnecessary-condition-truthy'
64
66
  | 'non-empty void element'
@@ -343,6 +345,7 @@ type FixType =
343
345
  | NoReferenceComponentFormulaRuleFix
344
346
  | NoReferenceNodeRuleFix
345
347
  | InvalidStyleSyntaxRuleFix
348
+ | NoStaticNodeConditionRuleFix
346
349
 
347
350
  export interface Rule<T = unknown, V = NodeType> {
348
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
+ })
@@ -0,0 +1,110 @@
1
+ import { type Formula } from '@nordcraft/core/dist/formula/formula'
2
+
3
+ /**
4
+ * Static evaluation of a formula.
5
+ *
6
+ * Can be used by issues to determine if a formula or sub-formula can be reduced to a static value.
7
+ * When sophisticated enough, it can be used during compile-time to reduce all static subgraphs of a formula to static values, greatly reducing payload and improving runtime performance.
8
+ *
9
+ * @returns {
10
+ * isStatic: boolean; // Whether the formula is static (i.e., does not depend on any variables, context AND only use pure formulas (no Random, Date, etc.))
11
+ * result: unknown; // The evaluated value of the formula
12
+ * }
13
+ *
14
+ * TODO: Make this function more capable of evaluating pure core formulas.
15
+ * TODO: Memoize the results (using path or a fast hash) to avoid re-evaluating any similar sub-graphs multiple times.
16
+ * TODO: Add a complex test-suite to ensure it works and develops as expected.
17
+ */
18
+ export const contextlessEvaluateFormula = (
19
+ formula: Formula,
20
+ ): {
21
+ isStatic: boolean
22
+ result: unknown
23
+ } => {
24
+ // Very basic implementation, just to get started.
25
+ switch (formula.type) {
26
+ case 'value': {
27
+ return {
28
+ isStatic: true,
29
+ result: formula.value,
30
+ }
31
+ }
32
+
33
+ case 'array': {
34
+ const results = formula.arguments.map((arg) =>
35
+ contextlessEvaluateFormula(arg.formula),
36
+ )
37
+
38
+ return {
39
+ isStatic: results.every((res) => res.isStatic),
40
+ result: results.map((res) => res.result),
41
+ }
42
+ }
43
+
44
+ case 'record': {
45
+ const entries = Object.entries(formula.entries).map(
46
+ ([key, arg]) => [key, contextlessEvaluateFormula(arg.formula)] as const,
47
+ )
48
+
49
+ const results = entries.map(([, res]) => res)
50
+
51
+ return {
52
+ isStatic: results.every((res) => res.isStatic),
53
+ result: Object.fromEntries(
54
+ entries.map(([key, res]) => [key, res.result]),
55
+ ),
56
+ }
57
+ }
58
+
59
+ // Static if:
60
+ // - ALL conditions are static AND truthy
61
+ // - ANY condition is static and falsy
62
+ // - EMPTY argument list is always true
63
+ case 'and': {
64
+ const results = formula.arguments.map((arg) =>
65
+ contextlessEvaluateFormula(arg.formula),
66
+ )
67
+
68
+ const alwaysTrue =
69
+ results.length === 0 ||
70
+ results.every((res) => res.isStatic && Boolean(res.result) === true)
71
+ const alwaysFalsy = results.some(
72
+ (res) => res.isStatic && Boolean(res.result) === false,
73
+ )
74
+
75
+ return {
76
+ isStatic: alwaysTrue || alwaysFalsy,
77
+ result: alwaysTrue ? true : alwaysFalsy ? false : undefined,
78
+ }
79
+ }
80
+
81
+ // Static if:
82
+ // - ANY condition is static AND truthy
83
+ // - ALL conditions are static AND falsy
84
+ // - EMPTY argument list is always false
85
+ case 'or': {
86
+ const results = formula.arguments.map((arg) =>
87
+ contextlessEvaluateFormula(arg.formula),
88
+ )
89
+
90
+ const alwaysFalsy =
91
+ results.length === 0 ||
92
+ results.every((res) => res.isStatic && Boolean(res.result) === false)
93
+ const alwaysTrue = results.some(
94
+ (res) => res.isStatic && Boolean(res.result) === true,
95
+ )
96
+
97
+ return {
98
+ isStatic: alwaysTrue || alwaysFalsy,
99
+ result: alwaysFalsy ? false : alwaysTrue ? true : undefined,
100
+ }
101
+ }
102
+
103
+ default:
104
+ // For now, we assume that any other formula is not static.
105
+ return {
106
+ isStatic: false,
107
+ result: undefined,
108
+ }
109
+ }
110
+ }
@@ -1,5 +1,33 @@
1
- import { omit } from '@nordcraft/core/dist/utils/collections'
1
+ import type { NodeModel } from '@nordcraft/core/dist/component/component.types'
2
+ import { get, omit, set } from '@nordcraft/core/dist/utils/collections'
2
3
  import type { FixFunction, NodeType } from '../types'
3
4
 
4
5
  export const removeFromPathFix: FixFunction<NodeType> = ({ path, files }) =>
5
6
  omit(files, path)
7
+
8
+ /**
9
+ * Same as removeFromPathFix, but also removes the node from its parent's children.
10
+ */
11
+ export const removeNodeFromPathFix: FixFunction<NodeType> = (data) => {
12
+ if (data.nodeType !== 'component-node') {
13
+ throw new Error('removeNodeFromPathFix can only be used on component nodes')
14
+ }
15
+
16
+ const componentNodesPath = data.path.slice(0, -1).map(String)
17
+ const filesWithoutNode = removeFromPathFix(data)
18
+ const nodes = get(filesWithoutNode, componentNodesPath) as Record<
19
+ string,
20
+ NodeModel
21
+ >
22
+ for (const key in nodes) {
23
+ if (
24
+ nodes[key].children?.includes(data.path[data.path.length - 1] as string)
25
+ ) {
26
+ nodes[key].children = nodes[key].children.filter(
27
+ (p) => p !== data.path[data.path.length - 1],
28
+ )
29
+ }
30
+ }
31
+
32
+ return set(filesWithoutNode, componentNodesPath, nodes)
33
+ }