@nordcraft/search 1.0.46 → 1.0.48

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 (35) hide show
  1. package/dist/problems.worker.js +2 -0
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/attributes/unknownComponentAttributeRule.js +29 -0
  4. package/dist/rules/attributes/unknownComponentAttributeRule.js.map +1 -0
  5. package/dist/rules/attributes/unknownComponentAttributeRule.test.js +148 -0
  6. package/dist/rules/attributes/unknownComponentAttributeRule.test.js.map +1 -0
  7. package/dist/rules/logic/noStaticNodeCondition.test.js +20 -2
  8. package/dist/rules/logic/noStaticNodeCondition.test.js.map +1 -1
  9. package/dist/rules/noReferenceNodeRule.js +2 -2
  10. package/dist/rules/noReferenceNodeRule.js.map +1 -1
  11. package/dist/rules/noReferenceNodeRule.test.js +87 -0
  12. package/dist/rules/noReferenceNodeRule.test.js.map +1 -1
  13. package/dist/rules/workflows/noPostNavigateAction.js +13 -2
  14. package/dist/rules/workflows/noPostNavigateAction.js.map +1 -1
  15. package/dist/rules/workflows/noPostNavigateAction.test.js +102 -1
  16. package/dist/rules/workflows/noPostNavigateAction.test.js.map +1 -1
  17. package/dist/searchProject.js +22 -0
  18. package/dist/searchProject.js.map +1 -1
  19. package/dist/util/helpers.test.js +1 -1
  20. package/dist/util/helpers.test.js.map +1 -1
  21. package/dist/util/removeUnused.fix.js +22 -7
  22. package/dist/util/removeUnused.fix.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/problems.worker.ts +2 -0
  25. package/src/rules/attributes/unknownComponentAttributeRule.test.ts +159 -0
  26. package/src/rules/attributes/unknownComponentAttributeRule.ts +37 -0
  27. package/src/rules/logic/noStaticNodeCondition.test.ts +20 -2
  28. package/src/rules/noReferenceNodeRule.test.ts +87 -0
  29. package/src/rules/noReferenceNodeRule.ts +2 -2
  30. package/src/rules/workflows/noPostNavigateAction.test.ts +111 -1
  31. package/src/rules/workflows/noPostNavigateAction.ts +20 -3
  32. package/src/searchProject.ts +24 -0
  33. package/src/types.d.ts +19 -2
  34. package/src/util/helpers.test.ts +1 -1
  35. package/src/util/removeUnused.fix.ts +27 -14
@@ -1,8 +1,11 @@
1
+ import type { ElementNodeModel } from '@nordcraft/core/dist/component/component.types'
2
+ import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
1
3
  import { describe, expect, test } from 'bun:test'
4
+ import { fixProject } from '../../fixProject'
2
5
  import { searchProject } from '../../searchProject'
3
6
  import { noPostNavigateAction } from './noPostNavigateAction'
4
7
 
5
- describe('noPostNavigateAction', () => {
8
+ describe('find noPostNavigateAction', () => {
6
9
  test('should detect actions after a url navigate action', () => {
7
10
  const problems = Array.from(
8
11
  searchProject({
@@ -457,3 +460,110 @@ describe('noPostNavigateAction', () => {
457
460
  expect(problems).toHaveLength(0)
458
461
  })
459
462
  })
463
+
464
+ describe('fix noPostNavigateAction', () => {
465
+ test('should remove actions after a url navigate action', () => {
466
+ const files: ProjectFiles = {
467
+ formulas: {},
468
+ components: {
469
+ nav: {
470
+ attributes: {},
471
+ events: [],
472
+ formulas: {},
473
+ nodes: {
474
+ root: {
475
+ type: 'element',
476
+ tag: 'div',
477
+ attrs: {},
478
+ classes: {},
479
+ events: {},
480
+ children: ['wkPXF99C3qdRgZmUcnHO_'],
481
+ style: {},
482
+ },
483
+ wkPXF99C3qdRgZmUcnHO_: {
484
+ tag: 'button',
485
+ type: 'element',
486
+ attrs: {},
487
+ style: {
488
+ color: 'var(--grey-200, #E5E5E5)',
489
+ 'border-radius': '6px',
490
+ 'background-color': 'var(--blue-600, #2563EB)',
491
+ 'padding-left': '8px',
492
+ 'padding-right': '8px',
493
+ 'padding-top': '8px',
494
+ 'padding-bottom': '8px',
495
+ width: 'fit-content',
496
+ cursor: 'pointer',
497
+ },
498
+ events: {
499
+ click: {
500
+ trigger: 'click',
501
+ actions: [
502
+ {
503
+ name: '@toddle/gotToURL',
504
+ arguments: [
505
+ {
506
+ name: 'URL',
507
+ formula: {
508
+ type: 'value',
509
+ value: 'https://example.com',
510
+ },
511
+ },
512
+ ],
513
+ label: 'Go to URL',
514
+ },
515
+ {
516
+ name: '@toddle/logToConsole',
517
+ arguments: [
518
+ {
519
+ name: 'Label',
520
+ formula: {
521
+ type: 'value',
522
+ value: '',
523
+ },
524
+ },
525
+ {
526
+ name: 'Data',
527
+ formula: {
528
+ type: 'value',
529
+ value: '<Data>',
530
+ },
531
+ },
532
+ ],
533
+ label: 'Log to console',
534
+ },
535
+ ],
536
+ },
537
+ },
538
+ classes: {},
539
+ children: ['zFhmZR6YnLy089HmlK-Yn'],
540
+ variants: [],
541
+ },
542
+ 'zFhmZR6YnLy089HmlK-Yn': {
543
+ type: 'text',
544
+ value: {
545
+ type: 'value',
546
+ value: 'Button',
547
+ },
548
+ },
549
+ },
550
+ variables: {},
551
+ apis: {},
552
+ name: 'nav',
553
+ },
554
+ },
555
+ }
556
+ const fixedProject = fixProject({
557
+ files,
558
+ rule: noPostNavigateAction,
559
+ fixType: 'delete-following-actions',
560
+ })
561
+ expect(
562
+ (
563
+ fixedProject.components.nav?.nodes[
564
+ 'wkPXF99C3qdRgZmUcnHO_'
565
+ ] as ElementNodeModel
566
+ )?.events?.click?.actions,
567
+ ).toHaveLength(1)
568
+ })
569
+ })
@@ -1,5 +1,5 @@
1
- import { get } from '@nordcraft/core/dist/utils/collections'
2
- import type { Rule } from '../../types'
1
+ import { get, set } from '@nordcraft/core/dist/utils/collections'
2
+ import type { ActionModelNode, Rule } from '../../types'
3
3
 
4
4
  export const noPostNavigateAction: Rule<{ parameter: string }> = {
5
5
  code: 'no post navigate action',
@@ -25,7 +25,24 @@ export const noPostNavigateAction: Rule<{ parameter: string }> = {
25
25
  const actionIndex = Number(_actionIndex)
26
26
  if (actionIndex < actions.length - 1) {
27
27
  // If the action is not the last one in the array, report it
28
- report(path)
28
+ report(path, undefined, ['delete-following-actions'])
29
29
  }
30
30
  },
31
+ fixes: {
32
+ 'delete-following-actions': ({ path, files }: ActionModelNode) => {
33
+ const actionsArrayPath = path.slice(0, -1).map((p) => String(p))
34
+ const actions = get(files, actionsArrayPath)
35
+ const actionIndex = path.at(-1)
36
+ if (actionIndex === undefined || !Array.isArray(actions)) {
37
+ return
38
+ }
39
+ return set(
40
+ files,
41
+ actionsArrayPath,
42
+ actions.slice(0, Number(actionIndex) + 1),
43
+ )
44
+ },
45
+ },
31
46
  }
47
+
48
+ export type NoPostNavigateActionRuleFix = 'delete-following-actions'
@@ -480,6 +480,30 @@ function* visitNode({
480
480
  state,
481
481
  fixOptions: fixOptions as any,
482
482
  })
483
+ if (
484
+ value.nodes[key].type === 'component' ||
485
+ value.nodes[key].type === 'element'
486
+ ) {
487
+ // Visit attributes on component and element nodes
488
+ for (const attrKey in value.nodes[key].attrs) {
489
+ const attr = value.nodes[key].attrs[attrKey]
490
+ yield* visitNode({
491
+ args: {
492
+ nodeType: 'component-node-attribute',
493
+ value: { key: attrKey, value: attr },
494
+ path: [...path, 'nodes', key, 'attrs', attrKey],
495
+ rules,
496
+ files,
497
+ pathsToVisit,
498
+ useExactPaths,
499
+ memo,
500
+ node: value.nodes[key],
501
+ },
502
+ state,
503
+ fixOptions: fixOptions as any,
504
+ })
505
+ }
506
+ }
483
507
  }
484
508
 
485
509
  for (const {
package/src/types.d.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  ComponentEvent as _ComponentEvent,
7
7
  ActionModel,
8
8
  Component,
9
+ ComponentNodeModel,
9
10
  ElementNodeModel,
10
11
  NodeModel,
11
12
  StyleVariant,
@@ -25,14 +26,16 @@ import type { LegacyActionRuleFix } from './rules/actions/legacyActionRule'
25
26
  import type { NoReferenceProjectActionRuleFix } from './rules/actions/noReferenceProjectActionRule'
26
27
  import type { NoReferenceApiRuleFix } from './rules/apis/noReferenceApiRule'
27
28
  import type { NoReferenceAttributeRuleFix } from './rules/attributes/noReferenceAttributeRule'
29
+ import type { UnknownComponentAttributeRuleFix } from './rules/attributes/unknownComponentAttributeRule'
28
30
  import type { NoReferenceComponentRuleFix } from './rules/components/noReferenceComponentRule'
29
31
  import type { NoReferenceEventRuleFix } from './rules/events/noReferenceEventRule'
30
32
  import type { LegacyFormulaRuleFix } from './rules/formulas/legacyFormulaRule'
31
33
  import type { NoReferenceComponentFormulaRuleFix } from './rules/formulas/noReferenceComponentFormulaRule'
32
34
  import type { NoReferenceProjectFormulaRuleFix } from './rules/formulas/noReferenceProjectFormulaRule'
33
- import type { NoStaticNodeConditionRuleFix } from './rules/logic/noStaticNodeConditionRule'
35
+ import type { NoStaticNodeConditionRuleFix } from './rules/logic/noStaticNodeCondition'
34
36
  import type { NoReferenceNodeRuleFix } from './rules/noReferenceNodeRule'
35
37
  import type { InvalidStyleSyntaxRuleFix } from './rules/style/invalidStyleSyntaxRule'
38
+ import type { NoPostNavigateActionRuleFix } from './rules/workflows/noPostNavigateAction'
36
39
 
37
40
  type Code =
38
41
  | 'duplicate event trigger'
@@ -75,6 +78,7 @@ type Code =
75
78
  | 'unknown api'
76
79
  | 'unknown attribute'
77
80
  | 'unknown classname'
81
+ | 'unknown component attribute'
78
82
  | 'unknown component formula input'
79
83
  | 'unknown component slot'
80
84
  | 'unknown context formula'
@@ -219,6 +223,10 @@ type ComponentWorkflowNode = {
219
223
  testValue: any
220
224
  }[]
221
225
  | null
226
+ callbacks?: {
227
+ name: string
228
+ testValue: any
229
+ }[]
222
230
  actions: ActionModel[]
223
231
  exposeInContext?: boolean | undefined
224
232
  }
@@ -248,6 +256,12 @@ type ComponentVariableNode = {
248
256
  component: ToddleComponent<Function>
249
257
  } & Base
250
258
 
259
+ type ComponentNodeAttributeNode = {
260
+ nodeType: 'component-node-attribute'
261
+ value: { key: string; value?: Formula }
262
+ node: ComponentNodeModel | ElementNodeModel
263
+ } & Base
264
+
251
265
  type ComponentAttributeNode = {
252
266
  nodeType: 'component-attribute'
253
267
  value: Component['attributes'][0]
@@ -320,6 +334,7 @@ export type NodeType =
320
334
  | ComponentEvent
321
335
  | ComponentFormulaNode
322
336
  | ComponentNode
337
+ | ComponentNodeAttributeNode
323
338
  | ComponentNodeNode
324
339
  | ComponentVariableNode
325
340
  | ComponentWorkflowNode
@@ -328,8 +343,8 @@ export type NodeType =
328
343
  | ProjectApiService
329
344
  | ProjectConfigNode
330
345
  | ProjectFormulaNode
331
- | ProjectThemeNode
332
346
  | ProjectRoute
347
+ | ProjectThemeNode
333
348
  | StyleNode
334
349
  | StyleVariantNode
335
350
 
@@ -346,6 +361,8 @@ type FixType =
346
361
  | NoReferenceNodeRuleFix
347
362
  | InvalidStyleSyntaxRuleFix
348
363
  | NoStaticNodeConditionRuleFix
364
+ | NoPostNavigateActionRuleFix
365
+ | UnknownComponentAttributeRuleFix
349
366
 
350
367
  export interface Rule<T = unknown, V = NodeType> {
351
368
  category: Category
@@ -53,7 +53,7 @@ describe('shouldSearchPath', () => {
53
53
  })
54
54
 
55
55
  describe('shouldSearchExactPath', () => {
56
- test.only('should only return true for exact matches', () => {
56
+ test('should only return true for exact matches', () => {
57
57
  const pathsToVisit = [
58
58
  ['components', 'Button'],
59
59
  ['components', 'Input'],
@@ -6,28 +6,41 @@ export const removeFromPathFix: FixFunction<NodeType> = ({ path, files }) =>
6
6
  omit(files, path)
7
7
 
8
8
  /**
9
- * Same as removeFromPathFix, but also removes the node from its parent's children.
9
+ * Same as removeFromPathFix, but also cleans up any references to the removed node.
10
10
  */
11
11
  export const removeNodeFromPathFix: FixFunction<NodeType> = (data) => {
12
12
  if (data.nodeType !== 'component-node') {
13
13
  throw new Error('removeNodeFromPathFix can only be used on component nodes')
14
14
  }
15
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
- >
16
+ const componentNodesPath = data.path.map(String)
17
+ const nodeId = componentNodesPath.pop()!
18
+ const nodes = get(data.files, componentNodesPath) as Record<string, NodeModel>
19
+
20
+ // Clean parent reference
22
21
  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
- )
22
+ if (nodes[key].children?.includes(nodeId)) {
23
+ nodes[key].children = nodes[key].children.filter((p) => p !== nodeId)
24
+ }
25
+ }
26
+
27
+ // Clean children recursively
28
+ const removeNodeAndChildren = (nodeId: string) => {
29
+ const node = nodes[nodeId] as NodeModel | undefined
30
+ if (!node) {
31
+ return
29
32
  }
33
+
34
+ if (node.children) {
35
+ for (const childId of node.children) {
36
+ removeNodeAndChildren(childId)
37
+ }
38
+ }
39
+
40
+ delete nodes[nodeId]
30
41
  }
31
42
 
32
- return set(filesWithoutNode, componentNodesPath, nodes)
43
+ removeNodeAndChildren(nodeId)
44
+
45
+ return set(data.files, componentNodesPath, nodes)
33
46
  }