@nordcraft/search 1.0.38 → 1.0.40

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 (41) hide show
  1. package/dist/fixProject.js +32 -0
  2. package/dist/fixProject.js.map +1 -0
  3. package/dist/problems.worker.js +45 -8
  4. package/dist/problems.worker.js.map +1 -1
  5. package/dist/rules/actions/legacyActionRule.js +144 -2
  6. package/dist/rules/actions/legacyActionRule.js.map +1 -1
  7. package/dist/rules/actions/legacyActionRule.test.js +235 -1
  8. package/dist/rules/actions/legacyActionRule.test.js.map +1 -1
  9. package/dist/rules/components/noReferenceComponentRule.js +31 -17
  10. package/dist/rules/components/noReferenceComponentRule.js.map +1 -1
  11. package/dist/rules/components/noReferenceComponentRule.test.js +86 -1
  12. package/dist/rules/components/noReferenceComponentRule.test.js.map +1 -1
  13. package/dist/rules/formulas/legacyFormulaRule.js +602 -6
  14. package/dist/rules/formulas/legacyFormulaRule.js.map +1 -1
  15. package/dist/rules/formulas/legacyFormulaRule.test.js +232 -1
  16. package/dist/rules/formulas/legacyFormulaRule.test.js.map +1 -1
  17. package/dist/rules/formulas/noReferenceProjectFormulaRule.js +73 -58
  18. package/dist/rules/formulas/noReferenceProjectFormulaRule.js.map +1 -1
  19. package/dist/rules/formulas/noReferenceProjectFormulaRule.test.js +34 -1
  20. package/dist/rules/formulas/noReferenceProjectFormulaRule.test.js.map +1 -1
  21. package/dist/searchProject.js +338 -217
  22. package/dist/searchProject.js.map +1 -1
  23. package/dist/util/helpers.js +31 -5
  24. package/dist/util/helpers.js.map +1 -1
  25. package/dist/util/helpers.test.js +58 -0
  26. package/dist/util/helpers.test.js.map +1 -0
  27. package/package.json +3 -2
  28. package/src/fixProject.ts +47 -0
  29. package/src/problems.worker.ts +90 -12
  30. package/src/rules/actions/legacyActionRule.test.ts +245 -1
  31. package/src/rules/actions/legacyActionRule.ts +166 -4
  32. package/src/rules/components/noReferenceComponentRule.test.ts +87 -1
  33. package/src/rules/components/noReferenceComponentRule.ts +38 -23
  34. package/src/rules/formulas/legacyFormulaRule.test.ts +242 -1
  35. package/src/rules/formulas/legacyFormulaRule.ts +697 -10
  36. package/src/rules/formulas/noReferenceProjectFormulaRule.test.ts +36 -1
  37. package/src/rules/formulas/noReferenceProjectFormulaRule.ts +78 -64
  38. package/src/searchProject.ts +217 -98
  39. package/src/types.d.ts +20 -3
  40. package/src/util/helpers.test.ts +80 -0
  41. package/src/util/helpers.ts +61 -9
@@ -5,8 +5,13 @@ import { ToddleFormula } from '@nordcraft/core/dist/formula/ToddleFormula'
5
5
  import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
6
6
  import { ToddleApiService } from '@nordcraft/ssr/dist/ToddleApiService'
7
7
  import { ToddleRoute } from '@nordcraft/ssr/dist/ToddleRoute'
8
- import type { ApplicationState, NodeType, Result, Rule } from './types'
9
- import { shouldSearchPath } from './util/helpers'
8
+ import type { ApplicationState, FixType, NodeType, Result, Rule } from './types'
9
+ import { shouldSearchExactPath, shouldVisitTree } from './util/helpers'
10
+
11
+ interface FixOptions {
12
+ mode: 'FIX'
13
+ fixType: FixType
14
+ }
10
15
 
11
16
  /**
12
17
  * Search a project by applying rules to all nodes in the project and returning reported results.
@@ -14,19 +19,40 @@ import { shouldSearchPath } from './util/helpers'
14
19
  * @param files All files to check against
15
20
  * @param rules All rules to check against
16
21
  * @param pathsToVisit Only visit specific paths. All subpaths are visited as well. For example, ['components', 'test'] would visit everything under the test component. Defaults is `[]` which means all paths are visited.
22
+ * @param state Optional parameter describing the current state of the application/editor
23
+ * @param options Optional parameter for fixing issues
17
24
  * @returns A generator that yields results as they are found
18
25
  */
26
+ export function searchProject(args: {
27
+ files: Omit<ProjectFiles, 'config'> & Partial<Pick<ProjectFiles, 'config'>>
28
+ rules: Rule[]
29
+ pathsToVisit?: string[][]
30
+ useExactPaths?: boolean
31
+ state?: ApplicationState
32
+ }): Generator<Result>
33
+ export function searchProject(args: {
34
+ files: Omit<ProjectFiles, 'config'> & Partial<Pick<ProjectFiles, 'config'>>
35
+ rules: Rule[]
36
+ pathsToVisit?: string[][]
37
+ useExactPaths?: boolean
38
+ state?: ApplicationState
39
+ fixOptions: FixOptions
40
+ }): Generator<ProjectFiles | void>
19
41
  export function* searchProject({
20
42
  files,
21
43
  rules,
22
44
  pathsToVisit = [],
45
+ useExactPaths = false,
23
46
  state,
47
+ fixOptions,
24
48
  }: {
25
49
  files: Omit<ProjectFiles, 'config'> & Partial<Pick<ProjectFiles, 'config'>>
26
50
  rules: Rule[]
27
51
  pathsToVisit?: string[][]
52
+ useExactPaths?: boolean
28
53
  state?: ApplicationState
29
- }): Generator<Result> {
54
+ fixOptions?: FixOptions
55
+ }): Generator<Result | ProjectFiles | void> {
30
56
  const memos = new Map<string, any>()
31
57
  const memo = (key: string | string[], fn: () => any) => {
32
58
  const stringKey = Array.isArray(key) ? key.join('/') : key
@@ -42,87 +68,97 @@ export function* searchProject({
42
68
  for (const key in files.components) {
43
69
  const component = files.components[key]
44
70
  if (component) {
45
- yield* visitNode(
46
- {
71
+ yield* visitNode({
72
+ args: {
47
73
  nodeType: 'component',
48
74
  value: component,
49
75
  path: ['components', key],
50
76
  rules,
51
77
  files,
52
78
  pathsToVisit,
79
+ useExactPaths,
53
80
  memo,
54
81
  },
55
82
  state,
56
- )
83
+ fixOptions: fixOptions as any,
84
+ })
57
85
  }
58
86
  }
59
87
 
60
88
  for (const key in files.formulas) {
61
- yield* visitNode(
62
- {
89
+ yield* visitNode({
90
+ args: {
63
91
  nodeType: 'project-formula',
64
92
  value: files.formulas[key],
65
93
  path: ['formulas', key],
66
94
  rules,
67
95
  files,
68
96
  pathsToVisit,
97
+ useExactPaths,
69
98
  memo,
70
99
  },
71
100
  state,
72
- )
101
+ fixOptions: fixOptions as any,
102
+ })
73
103
  }
74
104
 
75
105
  for (const key in files.actions) {
76
- yield* visitNode(
77
- {
106
+ yield* visitNode({
107
+ args: {
78
108
  nodeType: 'project-action',
79
109
  value: files.actions[key],
80
110
  path: ['actions', key],
81
111
  rules,
82
112
  files,
83
113
  pathsToVisit,
114
+ useExactPaths,
84
115
  memo,
85
116
  },
86
117
  state,
87
- )
118
+ fixOptions: fixOptions as any,
119
+ })
88
120
  }
89
121
 
90
122
  for (const key in files.themes) {
91
- yield* visitNode(
92
- {
123
+ yield* visitNode({
124
+ args: {
93
125
  nodeType: 'project-theme',
94
126
  value: files.themes[key],
95
127
  path: ['themes', key],
96
128
  rules,
97
129
  files,
98
130
  pathsToVisit,
131
+ useExactPaths,
99
132
  memo,
100
133
  },
101
134
  state,
102
- )
135
+ fixOptions: fixOptions as any,
136
+ })
103
137
  }
104
138
 
105
139
  if (files.services) {
106
140
  for (const key in files.services) {
107
- yield* visitNode(
108
- {
141
+ yield* visitNode({
142
+ args: {
109
143
  nodeType: 'api-service',
110
144
  value: files.services[key],
111
145
  path: ['services', key],
112
146
  rules,
113
147
  files,
114
148
  pathsToVisit,
149
+ useExactPaths,
115
150
  memo,
116
151
  },
117
152
  state,
118
- )
153
+ fixOptions: fixOptions as any,
154
+ })
119
155
  }
120
156
  }
121
157
 
122
158
  if (files.routes) {
123
159
  for (const key in files.routes) {
124
- yield* visitNode(
125
- {
160
+ yield* visitNode({
161
+ args: {
126
162
  nodeType: 'project-route',
127
163
  value: files.routes[key],
128
164
  routeName: key,
@@ -130,63 +166,117 @@ export function* searchProject({
130
166
  rules,
131
167
  files,
132
168
  pathsToVisit,
169
+ useExactPaths,
133
170
  memo,
134
171
  },
135
172
  state,
136
- )
173
+ fixOptions: fixOptions as any,
174
+ })
137
175
  }
138
176
  }
139
177
 
140
- yield* visitNode(
141
- {
178
+ yield* visitNode({
179
+ args: {
142
180
  nodeType: 'project-config',
143
181
  value: files.config,
144
182
  path: ['config'],
145
183
  rules,
146
184
  files,
147
185
  pathsToVisit,
186
+ useExactPaths,
148
187
  memo,
149
188
  },
150
189
  state,
151
- )
190
+ fixOptions: fixOptions as any,
191
+ })
152
192
  }
153
193
 
154
- function* visitNode(
194
+ function visitNode(args: {
155
195
  args: {
156
196
  path: (string | number)[]
157
197
  rules: Rule[]
158
198
  files: Omit<ProjectFiles, 'config'> & Partial<Pick<ProjectFiles, 'config'>>
159
199
  pathsToVisit: string[][]
160
- } & NodeType,
161
- state: ApplicationState | undefined,
162
- ): Generator<Result> {
163
- performance.mark(`visitNode-${args.path.join('/')}`)
164
- const { rules, pathsToVisit, ...data } = args
200
+ useExactPaths: boolean
201
+ } & NodeType
202
+ state: ApplicationState | undefined
203
+ }): Generator<Result>
204
+ function visitNode(args: {
205
+ args: {
206
+ path: (string | number)[]
207
+ rules: Rule[]
208
+ files: Omit<ProjectFiles, 'config'> & Partial<Pick<ProjectFiles, 'config'>>
209
+ pathsToVisit: string[][]
210
+ useExactPaths: boolean
211
+ } & NodeType
212
+ state: ApplicationState | undefined
213
+ fixOptions: { mode: 'FIX'; fixType: FixType }
214
+ }): Generator<ProjectFiles | void>
215
+ function* visitNode({
216
+ args,
217
+ state,
218
+ fixOptions,
219
+ }: {
220
+ args: {
221
+ path: (string | number)[]
222
+ rules: Rule[]
223
+ files: Omit<ProjectFiles, 'config'> & Partial<Pick<ProjectFiles, 'config'>>
224
+ pathsToVisit: string[][]
225
+ useExactPaths: boolean
226
+ } & NodeType
227
+ state: ApplicationState | undefined
228
+ fixOptions?: { mode: 'FIX'; fixType: FixType }
229
+ }): Generator<Result | ProjectFiles | void> {
230
+ const { rules, pathsToVisit, useExactPaths, ...data } = args
165
231
  const { files, value, path, memo, nodeType } = data
166
- if (!shouldSearchPath(data.path, pathsToVisit)) {
232
+ if (
233
+ !shouldVisitTree({
234
+ path: data.path,
235
+ pathsToVisit,
236
+ })
237
+ ) {
238
+ // We don't need to search this path or any of its subpaths
167
239
  return
168
240
  }
169
241
 
170
- const results: Result[] = []
171
- for (const rule of rules) {
172
- performance.mark(`rule-${rule.code}`)
173
- rule.visit(
174
- (path, details) => {
175
- results.push({
176
- code: rule.code,
177
- category: rule.category,
178
- level: rule.level,
179
- path,
180
- details,
181
- })
182
- },
183
- data,
184
- state,
185
- )
186
- }
242
+ if (
243
+ !useExactPaths ||
244
+ shouldSearchExactPath({ path: data.path, pathsToVisit })
245
+ ) {
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) => {
262
+ results.push({
263
+ code: rule.code,
264
+ category: rule.category,
265
+ level: rule.level,
266
+ path,
267
+ details,
268
+ fixes,
269
+ })
270
+ },
271
+ data,
272
+ state,
273
+ )
274
+ }
187
275
 
188
- for (const result of results) {
189
- yield result
276
+ for (const result of results) {
277
+ yield result
278
+ }
279
+ }
190
280
  }
191
281
 
192
282
  switch (nodeType) {
@@ -202,41 +292,45 @@ function* visitNode(
202
292
  })
203
293
 
204
294
  for (const key in value.attributes) {
205
- yield* visitNode(
206
- {
295
+ yield* visitNode({
296
+ args: {
207
297
  nodeType: 'component-attribute',
208
298
  value: value.attributes[key],
209
299
  path: [...path, 'attributes', key],
210
300
  rules,
211
301
  files,
212
302
  pathsToVisit,
303
+ useExactPaths,
213
304
  memo,
214
305
  component,
215
306
  },
216
307
  state,
217
- )
308
+ fixOptions: fixOptions as any,
309
+ })
218
310
  }
219
311
 
220
312
  for (const key in value.variables) {
221
- yield* visitNode(
222
- {
313
+ yield* visitNode({
314
+ args: {
223
315
  nodeType: 'component-variable',
224
316
  value: value.variables[key],
225
317
  path: [...path, 'variables', key],
226
318
  rules,
227
319
  files,
228
320
  pathsToVisit,
321
+ useExactPaths,
229
322
  memo,
230
323
  component,
231
324
  },
232
325
  state,
233
- )
326
+ fixOptions: fixOptions as any,
327
+ })
234
328
  }
235
329
 
236
330
  for (const key in value.apis) {
237
331
  const api = value.apis[key]
238
- yield* visitNode(
239
- {
332
+ yield* visitNode({
333
+ args: {
240
334
  nodeType: 'component-api',
241
335
  value: api,
242
336
  component,
@@ -244,14 +338,16 @@ function* visitNode(
244
338
  rules,
245
339
  files,
246
340
  pathsToVisit,
341
+ useExactPaths,
247
342
  memo,
248
343
  },
249
344
  state,
250
- )
345
+ fixOptions: fixOptions as any,
346
+ })
251
347
  if (!isLegacyApi(api)) {
252
348
  for (const [inputKey, input] of Object.entries(api.inputs)) {
253
- yield* visitNode(
254
- {
349
+ yield* visitNode({
350
+ args: {
255
351
  nodeType: 'component-api-input',
256
352
  value: input,
257
353
  api,
@@ -260,128 +356,144 @@ function* visitNode(
260
356
  rules,
261
357
  files,
262
358
  pathsToVisit,
359
+ useExactPaths,
263
360
  memo,
264
361
  },
265
362
  state,
266
- )
363
+ fixOptions: fixOptions as any,
364
+ })
267
365
  }
268
366
  }
269
367
  }
270
368
 
271
369
  for (const key in value.formulas) {
272
- yield* visitNode(
273
- {
370
+ yield* visitNode({
371
+ args: {
274
372
  nodeType: 'component-formula',
275
373
  value: value.formulas[key],
276
374
  path: [...path, 'formulas', key],
277
375
  rules,
278
376
  files,
279
377
  pathsToVisit,
378
+ useExactPaths,
280
379
  memo,
281
380
  component,
282
381
  },
283
382
  state,
284
- )
383
+ fixOptions: fixOptions as any,
384
+ })
285
385
  }
286
386
 
287
387
  for (const key in value.workflows) {
288
- yield* visitNode(
289
- {
388
+ yield* visitNode({
389
+ args: {
290
390
  nodeType: 'component-workflow',
291
391
  value: value.workflows[key],
292
392
  path: [...path, 'workflows', key],
293
393
  rules,
294
394
  files,
295
395
  pathsToVisit,
396
+ useExactPaths,
296
397
  memo,
297
398
  component,
298
399
  },
299
400
  state,
300
- )
401
+ fixOptions: fixOptions as any,
402
+ })
301
403
  }
302
404
 
303
405
  for (let i = 0; i < (value.events ?? []).length; i++) {
304
406
  const event = value.events?.[i]
305
407
  if (event) {
306
- yield* visitNode(
307
- {
408
+ yield* visitNode({
409
+ args: {
308
410
  nodeType: 'component-event',
309
411
  path: [...path, 'events', i],
310
412
  rules,
311
413
  files,
312
414
  pathsToVisit,
415
+ useExactPaths,
313
416
  memo,
314
417
  value: { component, event },
315
418
  },
316
419
  state,
317
- )
420
+ fixOptions: fixOptions as any,
421
+ })
318
422
  }
319
423
  }
320
424
 
321
425
  for (const key in value.contexts) {
322
- yield* visitNode(
323
- {
426
+ yield* visitNode({
427
+ args: {
324
428
  nodeType: 'component-context',
325
429
  value: value.contexts[key],
326
430
  path: [...path, 'contexts', key],
327
431
  rules,
328
432
  files,
329
433
  pathsToVisit,
434
+ useExactPaths,
330
435
  memo,
331
436
  },
332
437
  state,
333
- )
438
+ fixOptions: fixOptions as any,
439
+ })
334
440
  }
335
441
 
336
442
  for (const key in value.nodes) {
337
- yield* visitNode(
338
- {
443
+ yield* visitNode({
444
+ args: {
339
445
  nodeType: 'component-node',
340
446
  value: value.nodes[key],
341
447
  path: [...path, 'nodes', key],
342
448
  rules,
343
449
  files,
344
450
  pathsToVisit,
451
+ useExactPaths,
345
452
  memo,
346
453
  component,
347
454
  },
348
455
  state,
349
- )
456
+ fixOptions: fixOptions as any,
457
+ })
350
458
  }
351
459
 
352
460
  for (const {
353
461
  path: formulaPath,
354
462
  formula,
355
463
  } of component.formulasInComponent()) {
356
- yield* visitNode(
357
- {
464
+ yield* visitNode({
465
+ args: {
358
466
  nodeType: 'formula',
359
467
  value: formula,
360
468
  path: [...path, ...formulaPath],
361
469
  rules,
362
470
  files,
363
471
  pathsToVisit,
472
+ useExactPaths,
364
473
  memo,
365
474
  component,
366
475
  },
367
476
  state,
368
- )
477
+ fixOptions: fixOptions as any,
478
+ })
369
479
  }
370
480
 
371
481
  for (const [actionPath, action] of component.actionModelsInComponent()) {
372
- yield* visitNode(
373
- {
482
+ yield* visitNode({
483
+ args: {
374
484
  nodeType: 'action-model',
375
485
  value: action,
376
486
  path: [...path, ...actionPath],
377
487
  rules,
378
488
  files,
379
489
  pathsToVisit,
490
+ useExactPaths,
380
491
  memo,
381
492
  component,
382
493
  },
383
494
  state,
384
- )
495
+ fixOptions: fixOptions as any,
496
+ })
385
497
  }
386
498
  break
387
499
  }
@@ -395,23 +507,24 @@ function* visitNode(
395
507
  packages: files.packages,
396
508
  },
397
509
  })
398
- formula.formulasInFormula()
399
510
  for (const {
400
511
  path: formulaPath,
401
512
  formula: f,
402
513
  } of formula.formulasInFormula()) {
403
- yield* visitNode(
404
- {
514
+ yield* visitNode({
515
+ args: {
405
516
  nodeType: 'formula',
406
517
  value: f,
407
- path: [...path, ...formulaPath],
518
+ path: [...path, 'formula', ...formulaPath],
408
519
  rules,
409
520
  files,
410
521
  pathsToVisit,
522
+ useExactPaths,
411
523
  memo,
412
524
  },
413
525
  state,
414
- )
526
+ fixOptions: fixOptions as any,
527
+ })
415
528
  }
416
529
  }
417
530
  break
@@ -422,18 +535,20 @@ function* visitNode(
422
535
  if (variants) {
423
536
  for (let i = 0; i < variants.length; i++) {
424
537
  const variant = variants[i]
425
- yield* visitNode(
426
- {
538
+ yield* visitNode({
539
+ args: {
427
540
  nodeType: 'style-variant',
428
541
  value: { variant, element: value },
429
542
  path: [...path, 'variants', i],
430
543
  rules,
431
544
  files,
432
545
  pathsToVisit,
546
+ useExactPaths,
433
547
  memo,
434
548
  },
435
549
  state,
436
- )
550
+ fixOptions: fixOptions as any,
551
+ })
437
552
  }
438
553
  }
439
554
  }
@@ -451,18 +566,20 @@ function* visitNode(
451
566
  path: formulaPath,
452
567
  formula,
453
568
  } of apiService.formulasInService()) {
454
- yield* visitNode(
455
- {
569
+ yield* visitNode({
570
+ args: {
456
571
  nodeType: 'formula',
457
572
  value: formula,
458
573
  path: [...path, ...formulaPath],
459
574
  rules,
460
575
  files,
461
576
  pathsToVisit,
577
+ useExactPaths,
462
578
  memo,
463
579
  },
464
580
  state,
465
- )
581
+ fixOptions: fixOptions as any,
582
+ })
466
583
  }
467
584
  break
468
585
  }
@@ -479,18 +596,20 @@ function* visitNode(
479
596
  path: formulaPath,
480
597
  formula,
481
598
  } of projectRoute.formulasInRoute()) {
482
- yield* visitNode(
483
- {
599
+ yield* visitNode({
600
+ args: {
484
601
  nodeType: 'formula',
485
602
  value: formula,
486
- path: [...path, ...formulaPath],
603
+ path: [...path, 'formula', ...formulaPath],
487
604
  rules,
488
605
  files,
489
606
  pathsToVisit,
607
+ useExactPaths,
490
608
  memo,
491
609
  },
492
610
  state,
493
- )
611
+ fixOptions: fixOptions as any,
612
+ })
494
613
  }
495
614
  break
496
615
  }