@instructure/ui-codemods 10.20.2-snapshot-11 → 10.20.2-snapshot-13

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/CHANGELOG.md +1 -1
  2. package/README.md +5 -87
  3. package/lib/__node_tests__/codemodHelpers.test.tsx +455 -0
  4. package/lib/__node_tests__/{updateV10Breaking.test.ts → codemods.test.ts} +3 -3
  5. package/lib/__node_tests__/{testHelpers.ts → runTest.ts} +1 -3
  6. package/lib/index.ts +1 -16
  7. package/lib/{utils → instui-codemods}/updateToV10Colors.ts +6 -7
  8. package/lib/updateV10Breaking.ts +12 -26
  9. package/lib/{helpers → utils}/codemodHelpers.ts +153 -164
  10. package/lib/utils/codemodTypeCheckers.ts +221 -0
  11. package/lib/utils/instUICodemodExecutor.ts +89 -0
  12. package/package.json +2 -2
  13. package/tsconfig.build.tsbuildinfo +1 -1
  14. package/lib/helpers/replaceDeprecatedImports.ts +0 -546
  15. package/lib/helpers/replaceDeprecatedProps.ts +0 -230
  16. package/lib/updateImports.ts +0 -86
  17. package/lib/updatePropNames.ts +0 -66
  18. package/lib/updateV7Props.ts +0 -101
  19. package/lib/updateV8Breaking.ts +0 -49
  20. package/lib/updateV8ReactDOM.ts +0 -61
  21. package/lib/updateV9Breaking.ts +0 -54
  22. package/lib/utils/UpdateV7ButtonsLink.ts +0 -139
  23. package/lib/utils/requireUncached.ts +0 -28
  24. package/lib/utils/updateToV8Theming.ts +0 -84
  25. package/lib/utils/updateToV9Theming.ts +0 -70
  26. package/lib/utils/updateV7ButtonsClose.ts +0 -104
  27. package/lib/utils/updateV7ButtonsIconCircle.ts +0 -240
  28. package/lib/utils/updateV7ButtonsMisc.ts +0 -137
  29. package/lib/utils/updateV7ButtonsWithText.ts +0 -111
  30. package/lib/utils/updateV7FocusableView.ts +0 -105
  31. package/lib/utils/updateV7Heading.ts +0 -145
  32. package/lib/utils/updateV7Lists.ts +0 -113
  33. package/lib/utils/updateV7Pill.ts +0 -83
  34. package/lib/utils/updateV7Popover.ts +0 -133
  35. package/lib/utils/updateV7Tabs.ts +0 -129
  36. package/lib/utils/updateV8RenderProp.ts +0 -142
  37. package/lib/utils/updateV8ThemeProp.ts +0 -51
  38. package/lib/utils/warnV7ComponentDeprecations.ts +0 -96
@@ -22,33 +22,19 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import type { API, Collection, FileInfo, JSCodeshift } from 'jscodeshift'
26
- import { writeWarningsToFile } from './helpers/codemodHelpers'
27
- import formatSource from './utils/formatSource'
28
- import { updateToV10Colors } from './utils/updateToV10Colors'
25
+ import type { Transform } from 'jscodeshift'
26
+ import { updateToV10Colors } from './instui-codemods/updateToV10Colors'
27
+ import instUICodemodExecutor from './utils/instUICodemodExecutor'
29
28
 
30
- export default function updateV10Breaking(
31
- file: FileInfo,
32
- api: API,
29
+ /**
30
+ * Updates theme color syntax from InstUI v9 to v10
31
+ */
32
+ const updateV10Breaking: Transform = (
33
+ file,
34
+ api,
33
35
  options?: { fileName?: string; usePrettier?: boolean }
34
- ) {
35
- const j = api.jscodeshift
36
- const root = j(file.source)
37
- const hasModifications = doUpdate(j, root, file.path)
38
- if (options && options.fileName) {
39
- writeWarningsToFile(options.fileName)
40
- }
41
-
42
- if (hasModifications) {
43
- const shouldUsePrettier = options?.usePrettier !== false
44
- return shouldUsePrettier
45
- ? formatSource(root.toSource(), file.path)
46
- : root.toSource()
47
- } else {
48
- return null
49
- }
36
+ ) => {
37
+ return instUICodemodExecutor(updateToV10Colors, file, api, options)
50
38
  }
51
39
 
52
- function doUpdate(j: JSCodeshift, root: Collection, filePath: string) {
53
- return updateToV10Colors(j, root, filePath)
54
- }
40
+ export default updateV10Breaking
@@ -22,33 +22,30 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import {
26
- CallExpression,
25
+ import type {
27
26
  Collection,
28
- Identifier,
29
27
  ImportDeclaration,
30
- ImportDefaultSpecifier,
31
- ImportSpecifier,
32
28
  JSCodeshift,
33
29
  JSXAttribute,
34
30
  JSXElement,
35
- JSXExpressionContainer,
36
- JSXFragment,
37
31
  JSXIdentifier,
38
32
  JSXMemberExpression,
39
33
  JSXSpreadAttribute,
40
- JSXText,
41
- Literal,
42
- MemberExpression,
43
- SpreadElement
34
+ Literal
44
35
  } from 'jscodeshift'
36
+ import {
37
+ isImportSpecifier,
38
+ isImportDefaultSpecifier,
39
+ isJSXAttribute,
40
+ isJSXElement,
41
+ isJSXText,
42
+ isJSXIdentifier,
43
+ isJSXMemberExpression,
44
+ isLiteral,
45
+ isTSTypeParameter
46
+ } from './codemodTypeCheckers'
45
47
  import fs from 'fs'
46
48
 
47
- type GetArrayType<T extends any[] | undefined> = T extends (infer U)[]
48
- ? U
49
- : never
50
- export type JSXChild = GetArrayType<JSXElement['children']>
51
-
52
49
  type Attribute = {
53
50
  name: string
54
51
  value?: string | string[]
@@ -57,10 +54,24 @@ type Attribute = {
57
54
  const warningsMap: Record<string, boolean> = {}
58
55
 
59
56
  /**
60
- * Finds all the opening tag elements (JSXElement) given in tagName.
61
- * You can supply an optional withAttributes argument,
62
- * this will return only tags where the given prop with the given
63
- * value (or at least one in the array) exists (every attribute needs to exist).
57
+ * Finds all the opening tag elements (`JSXElement`) which equal `tagName`.
58
+ *
59
+ * Using the optional `withAttributes` argument will return only tags where the
60
+ * given prop with the given `value` (at least one in the array) exists
61
+ * (every attribute needs to exist).
62
+ *
63
+ * @example
64
+ * This code finds all `ApplyTheme` tags that are either imported
65
+ * from `@instructure/ui` or from `@instructure/ui-themeable`:
66
+ * ```
67
+ * const importedName = findImport('', j, root, 'ApplyTheme', [
68
+ * '@instructure/ui',
69
+ * '@instructure/ui-themeable'
70
+ * ])
71
+ * if (importedName) {
72
+ * const found = findElements(filePath, j, root, importedName)
73
+ * ...
74
+ * ```
64
75
  */
65
76
  function findElements(
66
77
  filePath: string,
@@ -73,7 +84,7 @@ function findElements(
73
84
  if (tagName.includes('.')) {
74
85
  const tagNames = tagName.split('.')
75
86
  if (tagNames.length > 2) {
76
- throw new Error(`This script cannot handle tab names with 2 or more "."
87
+ throw new Error(`This script cannot handle tag names with 2 or more "."
77
88
  characters. Tag name: ${tagName}`)
78
89
  }
79
90
  elements = root.find(j.JSXElement, {
@@ -145,7 +156,7 @@ function checkIfAttributeExist(
145
156
  let numMatches = 0
146
157
  for (const attr of attributes) {
147
158
  for (const toFind of attribsToFind) {
148
- if (isJSXAttribue(attr) && attr.name.name === toFind.name) {
159
+ if (isJSXAttribute(attr) && attr.name.name === toFind.name) {
149
160
  if (!toFind.value) {
150
161
  // attribute name matches, no values specified
151
162
  numMatches++
@@ -178,8 +189,20 @@ function checkIfAttributeExist(
178
189
 
179
190
  /**
180
191
  * Returns all attributes from the given collection with the given attribute
181
- * name. Optionally you can supply attribute value(s), this will return only
192
+ * name.
193
+ *
194
+ * Optionally you can supply attribute value(s), this will return only
182
195
  * attributes where these exist (at least one in the array has to exist).
196
+ * @example
197
+ * const data = j(
198
+ * `<>
199
+ * <button id='1234' aloneAttr>a</button>
200
+ * <button test={false}>button</button>
201
+ * <test id={some.obj}>c</test>
202
+ * <div {...{id:5}} />
203
+ * </>`)
204
+ * const r = findAttribute('', j, data, 'id')
205
+ * //r equals ["id='1234'", 'id={some.obj}']
183
206
  */
184
207
  function findAttribute(
185
208
  filePath: string,
@@ -203,8 +226,10 @@ function findAttribute(
203
226
  printWarning(
204
227
  filePath,
205
228
  path.value.loc?.start.line,
206
- 'Attribute whose value is checked has non-literal value,' +
207
- 'please check manually'
229
+ 'Attribute with value ' +
230
+ j(path.value).toSource() +
231
+ ' has non-literal value,' +
232
+ ' please check manually'
208
233
  )
209
234
  return false
210
235
  }
@@ -223,17 +248,19 @@ function findAttribute(
223
248
 
224
249
  /**
225
250
  * Prunes every non-visible child. This is useful because jscodeshift parses
226
- * newlines and spaces as children too, for example here:
251
+ * newlines and spaces as children too
252
+ * @example
227
253
  * ```
228
254
  * <Button>
229
255
  * <aaa/>
230
256
  * </Button>
231
257
  * ```
232
- * Button will have 3 children: a JSXText with value `" \n"`, aaa element, and
233
- * again a JSXText with `" \n"`. This function removes the empty text nodes.
258
+ * Button will have 3 children: a `JSXText` with value `" \n"`, `aaa` element, and
259
+ * again a `JSXText` with `" \n"`. This function removes the empty text nodes.
260
+ * This does not modify the input array.
234
261
  */
235
- function getVisibleChildren(nodes?: JSXChild[]) {
236
- const result: JSXChild[] = []
262
+ function getVisibleChildren(nodes?: JSXElement['children']) {
263
+ const result: JSXElement['children'] = []
237
264
  if (!nodes) {
238
265
  return result
239
266
  }
@@ -246,7 +273,26 @@ function getVisibleChildren(nodes?: JSXChild[]) {
246
273
  }
247
274
 
248
275
  /**
249
- * Renames every element (=JSX tag). Modifies the input collection
276
+ * Renames every element (=JSX tag) with the given name. Modifies the input
277
+ * collection.
278
+ * @example this code renames `<EmotionThemeProvider>` -> `<InstUISettingsProvider>`
279
+ * ```
280
+ * const importedName = findImport(j, root, 'EmotionThemeProvider', [
281
+ * '@instructure/emotion'
282
+ * ])
283
+ * if (importedName) {
284
+ * const emotionThemeProviderElements = findElements( filePath, j,
285
+ * root,
286
+ * importedName
287
+ * )
288
+ * renameElements(
289
+ * filePath,
290
+ * emotionThemeProviderElements,
291
+ * importedName,
292
+ * 'InstUISettingsProvider'
293
+ * )
294
+ * }
295
+ * ```
250
296
  */
251
297
  function renameElements(
252
298
  filePath: string,
@@ -326,16 +372,17 @@ function renameElement(node: JSXElement, currentName: string, newName: string) {
326
372
  }
327
373
 
328
374
  /**
329
- * Figures out if a certain component is imported in a AST tree.
330
- * If it's imported and renamed (e.g. `import {Button as BTN} ...`) then it
331
- * returns the renamed name of the import
332
- * @param j the JSCodeshift API
333
- * @param root the collection to check
334
- * @param name imported name, e.g. Button. If its a default import its ignored
335
- * @param path import path, or paths, e.g. `@instructure/ui-buttons`. Uses
336
- * `string.indexOf()` to search for matches, so substring matches are returned
337
- * too.
338
- * @return the name its imported as, undefined if it's not imported
375
+ * Checks if a certain import exist in the given collection and whether its
376
+ * renamed.
377
+ * @param j The jscodeshift API
378
+ * @param root The collection to check
379
+ * @param name Imported name, e.g. `Button`. If it's a default import its ignored
380
+ * @param path Import path, or paths, e.g.
381
+ * `['@instructure/ui-buttons', '@instructure/ui']`. Uses strict equality
382
+ * comparison (`===`) for matches.
383
+ * @return The name it's imported as, `undefined` if it's not imported. If it's
384
+ * imported and renamed (e.g. `import {Button as BTN} ...`) then it
385
+ * returns the renamed name of the import (`BTN` in this case)
339
386
  */
340
387
  function findImport(
341
388
  j: JSCodeshift,
@@ -343,20 +390,25 @@ function findImport(
343
390
  name: string,
344
391
  path: string | string[]
345
392
  ) {
346
- let importedName
393
+ let importedName: string | undefined
347
394
  const importPaths = findImportPath(j, root, path)
348
395
  // check import name
349
396
  importPaths.forEach((path) => {
350
397
  if (path.node.specifiers) {
351
398
  path.node.specifiers.forEach((specifier) => {
352
- if (isImportDefaultSpecifier(specifier) && specifier.local) {
399
+ if (
400
+ isImportDefaultSpecifier(specifier) &&
401
+ specifier.local &&
402
+ !isTSTypeParameter(specifier.local)
403
+ ) {
353
404
  importedName = specifier.local.name
354
- return
405
+ return undefined
355
406
  }
356
407
  if (isImportSpecifier(specifier) && specifier.imported.name === name) {
357
408
  // is it imported via an alias? e.g. import { A as B } ..
358
409
  if (
359
410
  specifier.local &&
411
+ !isTSTypeParameter(specifier.local) &&
360
412
  specifier.local.name &&
361
413
  specifier.local.name != name
362
414
  ) {
@@ -364,7 +416,7 @@ function findImport(
364
416
  } else {
365
417
  importedName = specifier.imported.name
366
418
  }
367
- return
419
+ return undefined
368
420
  }
369
421
  })
370
422
  }
@@ -374,10 +426,10 @@ function findImport(
374
426
 
375
427
  /**
376
428
  * Finds every imported component from the given path in the given collection.
377
- * If an import is renamed it returns the renamed name. If `exactMatch` is true
429
+ * If an import is renamed it returns the renamed name. If `exactMatch` is `false`
378
430
  * importPath is searched via `string.indexOf()` so it can be a substring.
379
- * For example calling it with `@instructure/ui` on a collection with
380
- * `exactMatch = true` with this collection:
431
+ * @example Calling it with `@instructure/ui` on a collection with
432
+ * `exactMatch = false` with this collection:
381
433
  * ```
382
434
  * import { a } from "@instructure/ui"
383
435
  * import { b } from "@instructure/ui-buttons"
@@ -391,12 +443,12 @@ function findEveryImport(
391
443
  importPath: string,
392
444
  exactMatch = true
393
445
  ) {
394
- const imports: (ImportSpecifier['local'] | string)[] = []
446
+ const imports: string[] = []
395
447
  const everyInstUIImport = findImportPath(j, root, importPath, exactMatch)
396
448
  everyInstUIImport.forEach((path) => {
397
449
  if (path.node.specifiers) {
398
450
  path.node.specifiers.forEach((specifier) => {
399
- if (specifier.local) {
451
+ if (specifier.local && !isTSTypeParameter(specifier.local)) {
400
452
  imports.push(specifier.local.name)
401
453
  }
402
454
  })
@@ -408,6 +460,7 @@ function findEveryImport(
408
460
  /**
409
461
  * Adds a new import if needed (not imported yet). If its imported it returns
410
462
  * the value under it's imported at (e.g. an alias).
463
+ * Modifies the input collection.
411
464
  * @param j the JSCodeshift API
412
465
  * @param root the collection to check
413
466
  * @param name imported name, e.g. Button
@@ -450,22 +503,38 @@ function addImportIfNeeded(
450
503
  return name
451
504
  }
452
505
 
506
+ /**
507
+ * Replaces an import. Modifies the input collection.
508
+ * @example
509
+ * ```
510
+ * replaceImport(j, root,
511
+ * 'OldImport',
512
+ * 'NewImport',
513
+ * '@instructure/emotion'
514
+ * )
515
+ * ```
516
+ * replaces
517
+ * ```
518
+ * import { OldImport } from "@instructure/emotion"
519
+ * ```
520
+ * with
521
+ * ```
522
+ * import { NewImport } from "@instructure/emotion"
523
+ * ```
524
+ * @returns the `newName` input parameter
525
+ */
453
526
  function replaceImport(
454
527
  j: JSCodeshift,
455
528
  root: Collection,
456
529
  oldName: string,
457
- name: string,
458
- pathToAdd: string | string[],
530
+ newName: string,
531
+ importPath: string | string[],
459
532
  isDefaultImport = false
460
533
  ) {
461
- const paths: Collection<ImportDeclaration> = findImportPath(
462
- j,
463
- root,
464
- pathToAdd
465
- )
534
+ const paths = findImportPath(j, root, importPath)
466
535
  const importSpecifier = isDefaultImport
467
- ? j.importDefaultSpecifier(j.identifier(name))
468
- : j.importSpecifier(j.identifier(name))
536
+ ? j.importDefaultSpecifier(j.identifier(newName))
537
+ : j.importSpecifier(j.identifier(newName))
469
538
  if (paths.length > 0) {
470
539
  // going through all specifiers and replacing the old import name with the new one
471
540
  paths.nodes()[0].specifiers = paths
@@ -474,12 +543,17 @@ function replaceImport(
474
543
  return specifier.local?.name !== oldName ? specifier : importSpecifier
475
544
  })
476
545
  }
477
- return name
546
+ return newName
478
547
  }
479
548
 
480
549
  /**
481
- * Finds all lines that import from `importPath`. For example with the
482
- * following root:
550
+ * Finds all lines that import from `importPath`.
551
+ *
552
+ * If `exactMatch` is `true` (default) it uses strict
553
+ * equality comparison (`===`), if its `false` it uses `string.indexOf()` so
554
+ * substring matches are returned too.
555
+ *
556
+ * @example With the following root:
483
557
  * ```
484
558
  * import { a } from "@instructure/ui"
485
559
  * import { b } from "@instructure/ui-buttons"
@@ -487,8 +561,6 @@ function replaceImport(
487
561
  * ```
488
562
  * `findImportPath(j, root, "@instructure/ui-buttons")`
489
563
  * will return line 2.
490
- * If exactMatch is `false` It uses `string.indexOf()` to find results, so
491
- * it returns substring matches too.
492
564
  */
493
565
  function findImportPath(
494
566
  j: JSCodeshift,
@@ -526,6 +598,7 @@ function findImportPath(
526
598
  /**
527
599
  * Removes all children from the specified element and makes it
528
600
  * self-closing.
601
+ * Modifies the collection in place.
529
602
  */
530
603
  function removeAllChildren(element: JSXElement) {
531
604
  const elem = element
@@ -538,91 +611,14 @@ function removeAllChildren(element: JSXElement) {
538
611
  }
539
612
  }
540
613
 
541
- // type checkers
542
- type astElem = { type: string }
543
-
544
- function isSpreadElement(elem?: astElem | null): elem is SpreadElement {
545
- return elem !== null && elem !== undefined && elem.type === 'SpreadElement'
546
- }
547
-
548
- function isImportSpecifier(elem?: astElem | null): elem is ImportSpecifier {
549
- return elem !== null && elem !== undefined && elem.type === 'ImportSpecifier'
550
- }
551
-
552
- function isImportDefaultSpecifier(
553
- elem?: astElem | null
554
- ): elem is ImportDefaultSpecifier {
555
- return (
556
- elem !== null &&
557
- elem !== undefined &&
558
- elem.type === 'ImportDefaultSpecifier'
559
- )
560
- }
561
-
562
- function isLiteral(elem?: astElem | null): elem is Literal {
563
- return elem !== null && elem !== undefined && elem.type === 'Literal'
564
- }
565
-
566
- function isIdentifier(elem?: astElem | null): elem is Identifier {
567
- return elem !== null && elem !== undefined && elem.type === 'Identifier'
568
- }
569
-
570
- function isMemberExpression(elem?: astElem | null): elem is MemberExpression {
571
- return elem !== null && elem !== undefined && elem.type == 'MemberExpression'
572
- }
573
-
574
- function isCallExpression(elem?: astElem | null): elem is CallExpression {
575
- return elem !== null && elem !== undefined && elem.type == 'CallExpression'
576
- }
577
-
578
- function isJSXAttribue(elem?: astElem | null): elem is JSXAttribute {
579
- return elem !== null && elem !== undefined && elem.type === 'JSXAttribute'
580
- }
581
-
582
- function isJSXElement(elem?: astElem | astElem[] | null): elem is JSXElement {
583
- return (
584
- elem !== null &&
585
- elem !== undefined &&
586
- !Array.isArray(elem) &&
587
- elem.type == 'JSXElement'
588
- )
589
- }
590
-
591
- function isJSXText(elem?: astElem | astElem[] | null): elem is JSXText {
592
- return (
593
- elem !== null &&
594
- elem !== undefined &&
595
- !Array.isArray(elem) &&
596
- elem.type == 'JSXText'
597
- )
598
- }
599
-
600
- function isJSXIdentifier(elem?: astElem | null): elem is JSXIdentifier {
601
- return elem !== null && elem !== undefined && elem.type == 'JSXIdentifier'
602
- }
603
-
604
- function isJSXFragment(elem?: astElem | null): elem is JSXFragment {
605
- return elem !== null && elem !== undefined && elem.type == 'JSXFragment'
606
- }
607
-
608
- // Name of a tag that looks like "List.Item"
609
- function isJSXMemberExpression(
610
- elem?: astElem | null
611
- ): elem is JSXMemberExpression {
612
- return (
613
- elem !== null && elem !== undefined && elem.type == 'JSXMemberExpression'
614
- )
615
- }
616
-
617
- function isJSXExpressionContainer(
618
- elem?: astElem | null
619
- ): elem is JSXExpressionContainer {
620
- return (
621
- elem !== null && elem !== undefined && elem.type == 'JSXExpressionContainer'
622
- )
623
- }
624
-
625
614
  const warnings: string[] = []
615
+ /**
616
+ * Prints the given warning with the file path and line to the console
617
+ * and adds it to an array in the background
618
+ * @param filePath
619
+ * @param line
620
+ * @param message
621
+ */
626
622
  function printWarning(
627
623
  filePath: string,
628
624
  line: number | undefined,
@@ -633,6 +629,10 @@ function printWarning(
633
629
  console.warn(warning)
634
630
  }
635
631
 
632
+ /**
633
+ * Writes all warnings printed with `printWarning` to the given file
634
+ * @param fileName
635
+ */
636
636
  function writeWarningsToFile(fileName: string) {
637
637
  if (warnings.length > 0) {
638
638
  const sorted = warnings.sort()
@@ -645,29 +645,18 @@ function writeWarningsToFile(fileName: string) {
645
645
  }
646
646
 
647
647
  export {
648
+ // lookup
649
+ getVisibleChildren,
648
650
  findElements,
649
651
  findAttribute,
650
652
  findImport,
651
653
  findEveryImport,
654
+ // editing
652
655
  replaceImport,
653
656
  addImportIfNeeded,
654
657
  renameElements,
655
- getVisibleChildren,
656
658
  removeAllChildren,
659
+ // logging
657
660
  printWarning,
658
- writeWarningsToFile,
659
- // type checkers
660
- isSpreadElement,
661
- isIdentifier,
662
- isImportSpecifier,
663
- isMemberExpression,
664
- isCallExpression,
665
- isJSXAttribue,
666
- isJSXElement,
667
- isJSXText,
668
- isJSXIdentifier,
669
- isJSXFragment,
670
- isJSXMemberExpression,
671
- isJSXExpressionContainer,
672
- isLiteral
661
+ writeWarningsToFile
673
662
  }