@instructure/ui-codemods 8.17.1-snapshot.32 → 8.17.1-snapshot.78

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.
@@ -0,0 +1,551 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import {
26
+ Collection,
27
+ ImportDeclaration,
28
+ ImportSpecifier,
29
+ JSCodeshift,
30
+ JSXAttribute,
31
+ JSXElement,
32
+ JSXExpressionContainer,
33
+ JSXFragment,
34
+ JSXIdentifier,
35
+ JSXMemberExpression,
36
+ JSXSpreadAttribute,
37
+ JSXSpreadChild,
38
+ JSXText,
39
+ Literal
40
+ } from 'jscodeshift'
41
+ import type { LiteralKind } from 'ast-types/gen/kinds'
42
+ import fs from 'fs'
43
+
44
+ type JSXChild =
45
+ | JSXElement
46
+ | JSXExpressionContainer
47
+ | JSXSpreadChild
48
+ | JSXFragment
49
+ | LiteralKind
50
+
51
+ type Attribute = {
52
+ name: string
53
+ value?: string | string[]
54
+ }
55
+
56
+ const warningsMap: Record<string, boolean> = {}
57
+
58
+ /**
59
+ * Finds all the opening tag elements (JSXElement) given in tagName.
60
+ * You can supply an optional withAttributes argument,
61
+ * this will return only tags where the given prop with the given
62
+ * value (or at least one in the array) exists (every attribute needs to exist).
63
+ */
64
+ function findElements(
65
+ filePath: string,
66
+ j: JSCodeshift,
67
+ root: Collection,
68
+ tagName: string,
69
+ withAttributes?: Attribute | Attribute[]
70
+ ) {
71
+ let elements
72
+ if (tagName.includes('.')) {
73
+ const tagNames = tagName.split('.')
74
+ if (tagNames.length > 2) {
75
+ throw new Error(`This script cannot handle tab names with 2 or more "."
76
+ characters. Tag name: ${tagName}`)
77
+ }
78
+ elements = root.find(j.JSXElement, {
79
+ openingElement: {
80
+ // finds all <tagName.name>
81
+ name: {
82
+ type: 'JSXMemberExpression',
83
+ object: {
84
+ name: tagNames[0]
85
+ },
86
+ property: {
87
+ name: tagNames[1]
88
+ }
89
+ }
90
+ }
91
+ })
92
+ } else {
93
+ elements = root.find(j.JSXElement, {
94
+ openingElement: {
95
+ // finds all <tagName>
96
+ name: {
97
+ type: 'JSXIdentifier',
98
+ name: tagName
99
+ }
100
+ }
101
+ })
102
+ }
103
+ elements.find(j.JSXSpreadAttribute).forEach((path) => {
104
+ const line = filePath + '_' + path.value.loc?.start.line
105
+ if (!warningsMap[line]) {
106
+ // prevent displaying the same warning multiple times
107
+ warningsMap[line] = true
108
+ printWarning(
109
+ filePath,
110
+ path.value.loc?.start.line,
111
+ 'Spread attribute (`...`) detected. Please double check the ' +
112
+ 'result, I cannot see what is inside a spread object. '
113
+ )
114
+ }
115
+ })
116
+ return elements.filter((path) => {
117
+ return checkIfAttributeExist(
118
+ filePath,
119
+ path.value.openingElement.attributes,
120
+ withAttributes
121
+ )
122
+ })
123
+ }
124
+
125
+ function checkIfAttributeExist(
126
+ filePath: string,
127
+ attributes?: (JSXAttribute | JSXSpreadAttribute)[],
128
+ withAttributes?: Attribute | Attribute[] // if array has to match every name
129
+ ) {
130
+ if (
131
+ !withAttributes ||
132
+ (Array.isArray(withAttributes) && withAttributes.length === 0)
133
+ ) {
134
+ // no attribute name is given, treat this as a match
135
+ return true
136
+ }
137
+ if (!attributes) {
138
+ // attribute name is given, but element has no attributes
139
+ return false
140
+ }
141
+ const attribsToFind = Array.isArray(withAttributes)
142
+ ? withAttributes
143
+ : [withAttributes]
144
+ let numMatches = 0
145
+ for (const attr of attributes) {
146
+ for (const toFind of attribsToFind) {
147
+ if (isJSXAttribue(attr) && attr.name.name === toFind.name) {
148
+ if (!toFind.value) {
149
+ // attribute name matches, no values specified
150
+ numMatches++
151
+ } else if (isLiteral(attr.value)) {
152
+ if (typeof toFind.value === 'string') {
153
+ if (attr.value.value === toFind.value) {
154
+ // name and value match
155
+ numMatches++
156
+ }
157
+ } else if (toFind.value.includes(attr.value.value as string)) {
158
+ // name and one of the values match
159
+ numMatches++
160
+ }
161
+ } else {
162
+ printWarning(
163
+ filePath,
164
+ attr.loc?.start.line,
165
+ 'Was looking for a string attribute value, but found ' +
166
+ attr.value?.type
167
+ )
168
+ }
169
+ }
170
+ }
171
+ }
172
+ if (numMatches === attribsToFind.length) {
173
+ return true
174
+ }
175
+ return false
176
+ }
177
+
178
+ /**
179
+ * Returns all attributes from the given collection with the given attribute
180
+ * name. Optionally you can supply attribute value(s), this will return only
181
+ * attributes where these exist (at least one in the array has to exist).
182
+ */
183
+ function findAttribute(
184
+ filePath: string,
185
+ j: JSCodeshift,
186
+ root: Collection,
187
+ withAttrName: string,
188
+ withAttrValue?: string | string[]
189
+ ) {
190
+ return root
191
+ .find(j.JSXAttribute, {
192
+ name: {
193
+ name: withAttrName
194
+ }
195
+ })
196
+ .filter((path) => {
197
+ if (!withAttrValue || withAttrValue.length == 0) {
198
+ // no value(s) given, return everything
199
+ return true
200
+ }
201
+ if (!isLiteral(path.value.value) && withAttrValue) {
202
+ printWarning(
203
+ filePath,
204
+ path.value.loc?.start.line,
205
+ 'Attribute whose value is checked has non-literal value,' +
206
+ 'please check manually'
207
+ )
208
+ return false
209
+ }
210
+ const currentAttrValue = (path.value.value as Literal).value
211
+ if (typeof withAttrValue === 'string') {
212
+ if (currentAttrValue === withAttrValue) {
213
+ // single value to search for
214
+ return true
215
+ }
216
+ } else if (withAttrValue.includes(currentAttrValue as string)) {
217
+ return true
218
+ }
219
+ return false
220
+ })
221
+ }
222
+
223
+ /**
224
+ * Prunes every non-visible child. This is useful because jscodeshift parses
225
+ * newlines and spaces as children too, for example here:
226
+ * ```
227
+ * <Button>
228
+ * <aaa/>
229
+ * </Button>
230
+ * ```
231
+ * Button will have 3 children: a JSXText with value `" \n"`, aaa element, and
232
+ * again a JSXText with `" \n"`. This function removes the empty text nodes.
233
+ */
234
+ function getVisibleChildren(nodes?: JSXChild[]) {
235
+ const result: JSXChild[] = []
236
+ if (!nodes) {
237
+ return result
238
+ }
239
+ for (const child of nodes) {
240
+ if (!isJSXText(child) || child.value.trim().length > 0) {
241
+ result.push(child)
242
+ }
243
+ }
244
+ return result
245
+ }
246
+
247
+ /**
248
+ * Renames every element (=JSX tag). Modifies the input collection
249
+ */
250
+ function renameElements(
251
+ filePath: string,
252
+ root: Collection<JSXElement> | JSXElement['children'],
253
+ currentName: string,
254
+ newName: string
255
+ ) {
256
+ if (!root) {
257
+ return
258
+ }
259
+ if (Array.isArray(root)) {
260
+ for (const elem of root) {
261
+ if (isJSXElement(elem)) {
262
+ renameElement(elem, currentName, newName)
263
+ } else {
264
+ printWarning(
265
+ filePath,
266
+ elem.loc?.start.line,
267
+ "non element type encountered while renaming '" +
268
+ currentName +
269
+ "' please check."
270
+ )
271
+ }
272
+ }
273
+ } else {
274
+ root.forEach((node) => {
275
+ renameElement(node.node, currentName, newName)
276
+ })
277
+ }
278
+ }
279
+
280
+ function renameElement(node: JSXElement, currentName: string, newName: string) {
281
+ if (isJSXIdentifier(node.openingElement.name)) {
282
+ // Old and new name does not have a "." character
283
+ if (newName.indexOf('.') > -1) {
284
+ throw new Error(
285
+ 'Cannot perform a rename that adds a `.` character ' + ' to the name'
286
+ ) // actually possible, but we don't need it.
287
+ }
288
+ const openingElement = node.openingElement.name
289
+ if (openingElement.name === currentName) {
290
+ openingElement.name = newName
291
+ const closingElement = node.closingElement?.name as
292
+ | JSXIdentifier
293
+ | undefined
294
+ if (closingElement) {
295
+ closingElement.name = newName
296
+ }
297
+ }
298
+ } else if (isJSXMemberExpression(node.openingElement.name)) {
299
+ // Old and new name has a "." character
300
+ const newNameArr = newName.split('.')
301
+ if (newNameArr.length !== 2) {
302
+ throw new Error(
303
+ 'Cannot perform a rename that removes a `.` character ' +
304
+ ' from the name'
305
+ ) // actually possible, but we don't need it.
306
+ }
307
+ const openingElement = node.openingElement.name
308
+ ;(openingElement.object as JSXIdentifier).name = newNameArr[0]
309
+ ;(openingElement.property as JSXIdentifier).name = newNameArr[1]
310
+ const closingElement = node.closingElement?.name as
311
+ | JSXMemberExpression
312
+ | undefined
313
+ if (closingElement) {
314
+ ;(closingElement.object as JSXIdentifier).name = newNameArr[0]
315
+ ;(closingElement.property as JSXIdentifier).name = newNameArr[1]
316
+ }
317
+ } else {
318
+ throw new Error(
319
+ 'Cannot rename ' +
320
+ currentName +
321
+ ' this script cannot ' +
322
+ 'handle namespaced names (e.g. `List:Item`'
323
+ )
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Figures out if a certain component is imported in a AST tree, e.g.
329
+ * If it's imported and renamed (e.g. `import {Button as BTN} ...`) then it
330
+ * returns the renamed name of the import
331
+ * @param j the JSCodeshift API
332
+ * @param root the collection to check
333
+ * @param name imported name, e.g. Button
334
+ * @param path import path, or paths, e.g. @instructure/ui-buttons.
335
+ * @return the name its imported as, undefined if it's not imported
336
+ */
337
+ function findImport(
338
+ j: JSCodeshift,
339
+ root: Collection,
340
+ name: string,
341
+ path: string | string[]
342
+ ) {
343
+ let importedName: string | undefined
344
+ const importPaths = findImportPath(j, root, path)
345
+ // check import name
346
+ importPaths.forEach((path) => {
347
+ if (path.node.specifiers) {
348
+ path.node.specifiers.forEach((specifier) => {
349
+ if (isImportSpecifier(specifier) && specifier.imported.name === name) {
350
+ // is it imported via an alias? e.g. import { A as B } ..
351
+ if (
352
+ specifier.local &&
353
+ specifier.local.name &&
354
+ specifier.local.name != name
355
+ ) {
356
+ importedName = specifier.local.name
357
+ } else {
358
+ importedName = specifier.imported.name
359
+ }
360
+ return
361
+ }
362
+ })
363
+ }
364
+ })
365
+ return importedName
366
+ }
367
+
368
+ /**
369
+ * Adds a new import if needed (not imported yet). If its imported it returns
370
+ * the value under it's imported at (e.g. an alias).
371
+ * @param j the JSCodeshift API
372
+ * @param root the collection to check
373
+ * @param name imported name, e.g. Button
374
+ * @param pathToAdd import path, or paths. If it
375
+ * has multiple values it will search them all for this import, if it's not
376
+ * found it will use the first element of the array to add the import.
377
+ * @returns the name under it's imported at
378
+ */
379
+ function addImportIfNeeded(
380
+ j: JSCodeshift,
381
+ root: Collection,
382
+ name: string,
383
+ pathToAdd: string | string[]
384
+ ) {
385
+ // if its imported already return the import name
386
+ const importedName = findImport(j, root, name, pathToAdd)
387
+ if (importedName) {
388
+ return importedName
389
+ }
390
+ const paths: Collection<ImportDeclaration> = findImportPath(
391
+ j,
392
+ root,
393
+ pathToAdd
394
+ )
395
+ if (paths.length == 0) {
396
+ // not imported yet, just add a new line
397
+ const newPath = typeof pathToAdd === 'string' ? pathToAdd : pathToAdd[0]
398
+ root
399
+ .find(j.ImportDeclaration)
400
+ .at(-1)
401
+ .insertAfter(
402
+ j.importDeclaration(
403
+ [j.importSpecifier(j.identifier(name))],
404
+ j.literal(newPath)
405
+ )
406
+ )
407
+ } else {
408
+ paths.nodes()[0].specifiers!.push(j.importSpecifier(j.identifier(name)))
409
+ }
410
+ return name
411
+ }
412
+
413
+ /**
414
+ * Finds all lines that import from importPath, e.g.
415
+ * `findImportPath(j, root, ["@instructure/ui", "@instructure/ui-buttons"])`
416
+ * with the following root:
417
+ * ```
418
+ * import { a } from "@instructure/ui"
419
+ * import { b } from "@instructure/ui-buttons"
420
+ * import { c } from "react"
421
+ * ```
422
+ * will return lines 1 and 2
423
+ */
424
+ function findImportPath(
425
+ j: JSCodeshift,
426
+ root: Collection,
427
+ importPath: string | string[]
428
+ ) {
429
+ return (
430
+ root
431
+ .find(j.ImportDeclaration)
432
+ // check for import path
433
+ .filter((astPath) => {
434
+ const importSource = astPath.node.source.value // e.g. "@instructure/ui"
435
+ if (importSource && typeof importSource === 'string') {
436
+ if (typeof importPath === 'string') {
437
+ if (importSource.indexOf(importPath) > -1) {
438
+ return true
439
+ }
440
+ } else {
441
+ for (const anImport of importPath) {
442
+ if (importSource === anImport) {
443
+ return true
444
+ }
445
+ }
446
+ }
447
+ }
448
+ return false
449
+ })
450
+ )
451
+ }
452
+
453
+ /**
454
+ * Removes all children from the specified element and makes it
455
+ * self-closing.
456
+ */
457
+ function removeAllChildren(element: JSXElement) {
458
+ const elem = element
459
+ if (elem.children) {
460
+ while (elem.children.length > 0) {
461
+ elem.children.pop()
462
+ }
463
+ elem.openingElement.selfClosing = true
464
+ elem.closingElement = undefined
465
+ }
466
+ }
467
+
468
+ // type checkers
469
+ type astElem = { type: string }
470
+ function isImportSpecifier(elem?: astElem | null): elem is ImportSpecifier {
471
+ return elem !== null && elem !== undefined && elem.type === 'ImportSpecifier'
472
+ }
473
+
474
+ function isJSXAttribue(elem?: astElem | null): elem is JSXAttribute {
475
+ return elem !== null && elem !== undefined && elem.type === 'JSXAttribute'
476
+ }
477
+
478
+ function isJSXElement(elem?: astElem | null): elem is JSXElement {
479
+ return elem !== null && elem !== undefined && elem.type == 'JSXElement'
480
+ }
481
+
482
+ function isJSXText(elem?: astElem | null): elem is JSXText {
483
+ return elem !== null && elem !== undefined && elem.type == 'JSXText'
484
+ }
485
+
486
+ function isJSXIdentifier(elem?: astElem | null): elem is JSXIdentifier {
487
+ return elem !== null && elem !== undefined && elem.type == 'JSXIdentifier'
488
+ }
489
+
490
+ // Name of a tag that looks like "List.Item"
491
+ function isJSXMemberExpression(
492
+ elem?: astElem | null
493
+ ): elem is JSXMemberExpression {
494
+ return (
495
+ elem !== null && elem !== undefined && elem.type == 'JSXMemberExpression'
496
+ )
497
+ }
498
+
499
+ function isJSXExpressionContainer(
500
+ elem?: astElem | null
501
+ ): elem is JSXExpressionContainer {
502
+ return (
503
+ elem !== null && elem !== undefined && elem.type == 'JSXExpressionContainer'
504
+ )
505
+ }
506
+
507
+ function isLiteral(elem?: astElem | null): elem is Literal {
508
+ return elem !== null && elem !== undefined && elem.type === 'Literal'
509
+ }
510
+
511
+ const warnings: string[] = []
512
+ function printWarning(
513
+ filePath: string,
514
+ line: number | undefined,
515
+ message: string
516
+ ) {
517
+ const warning = filePath + ': ' + line + ': ' + message
518
+ warnings.push(warning)
519
+ console.warn(warning)
520
+ }
521
+
522
+ function writeWarningsToFile(fileName: string) {
523
+ if (warnings.length > 0) {
524
+ const sorted = warnings.sort()
525
+ fs.writeFileSync(fileName, sorted.join('\n') + '\n', {
526
+ encoding: 'utf-8',
527
+ flag: 'a'
528
+ })
529
+ warnings.length = 0
530
+ }
531
+ }
532
+
533
+ export {
534
+ findElements,
535
+ findAttribute,
536
+ findImport,
537
+ addImportIfNeeded,
538
+ renameElements,
539
+ getVisibleChildren,
540
+ removeAllChildren,
541
+ printWarning,
542
+ writeWarningsToFile,
543
+ // type checkers
544
+ isJSXAttribue,
545
+ isJSXElement,
546
+ isJSXText,
547
+ isJSXIdentifier,
548
+ isJSXMemberExpression,
549
+ isJSXExpressionContainer,
550
+ isLiteral
551
+ }
@@ -28,7 +28,7 @@ import formatSource from './utils/formatSource'
28
28
  import requireUncached from './utils/requireUncached'
29
29
  import replaceDeprecatedImports from './helpers/replaceDeprecatedImports'
30
30
  import type { API, FileInfo } from 'jscodeshift'
31
- import { ParsedImport } from './utils/parseImport'
31
+ import type { ParsedImport } from './helpers/replaceDeprecatedImports'
32
32
 
33
33
  /**
34
34
  * updates imports to the new per-package or the metapackage syntax.
@@ -29,7 +29,11 @@ import requireUncached from './utils/requireUncached'
29
29
  import replaceDeprecatedProps from './helpers/replaceDeprecatedProps'
30
30
  import { API, FileInfo } from 'jscodeshift'
31
31
 
32
- export default function (file: FileInfo, api: API, options: any) {
32
+ export default function updatePropNames(
33
+ file: FileInfo,
34
+ api: API,
35
+ options: any
36
+ ) {
33
37
  const j = api.jscodeshift
34
38
  const c = path.resolve(process.cwd(), options.config)
35
39
  const config: UpdatePropNamesOptions = fs.existsSync(c)
@@ -43,7 +47,8 @@ export default function (file: FileInfo, api: API, options: any) {
43
47
  const root = j(file.source)
44
48
  let hasModifications = false
45
49
 
46
- hasModifications = replaceDeprecatedProps(j, root, config) || hasModifications
50
+ hasModifications =
51
+ replaceDeprecatedProps(file.path, j, root, config) || hasModifications
47
52
 
48
53
  return hasModifications ? formatSource(root.toSource(), file.path) : null
49
54
  }
@@ -0,0 +1,101 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import type { API, Collection, FileInfo, JSCodeshift } from 'jscodeshift'
26
+ import formatSource from './utils/formatSource'
27
+ import { findImport, writeWarningsToFile } from './helpers/v7PropsUpdateHelpers'
28
+ import updateV7ButtonsMisc from './utils/updateV7ButtonsMisc'
29
+ import updateV7ButtonsWithText from './utils/updateV7ButtonsWithText'
30
+ import updateV7ButtonsIconCircle from './utils/updateV7ButtonsIconCircle'
31
+ import updateV7ButtonsClose from './utils/updateV7ButtonsClose'
32
+ import UpdateV7ButtonsLink from './utils/UpdateV7ButtonsLink'
33
+ import updateV7Heading from './utils/updateV7Heading'
34
+ import updateV7Lists from './utils/updateV7Lists'
35
+ import updateV7Pill from './utils/updateV7Pill'
36
+ import updateV7Popover from './utils/updateV7Popover'
37
+ import updateV7Tabs from './utils/updateV7Tabs'
38
+ import updateV7FocusableView from './utils/updateV7FocusableView'
39
+ import warnV7ComponentDeprecations from './utils/warnV7ComponentDeprecations'
40
+
41
+ /**
42
+ * Updates complex code from the InstUI v7 syntax to the v8 syntax.
43
+ * This script contains the codemods referenced in
44
+ * https://instructure.design/v7/#button-upgrade-guide and in
45
+ * https://instructure.design/v7/#v7-deprecated-props-and-components
46
+ * It can handle if a component and its attributes are defined in JSX; if its
47
+ * imported via an alias (`import {Button as BBB}`) is OK too.
48
+ * It cannot handle if props are added via the spread operator or programmatically.
49
+ * @param file Holds information about the currently processed file.
50
+ * @param api This object exposes the JSCodeshift library and helper functions
51
+ * from the runner.
52
+ * @param options pass a fileName parameter to write the warnings to a file.
53
+ * @returns {*|null} null if there was nothing to reformat
54
+ */
55
+ export default function updateV7Props(
56
+ file: FileInfo,
57
+ api: API,
58
+ options?: { fileName: string }
59
+ ) {
60
+ const j = api.jscodeshift
61
+ const root = j(file.source)
62
+ const hasModifications = updateProps(j, root, file.path)
63
+ if (options && options.fileName) {
64
+ writeWarningsToFile(options.fileName)
65
+ }
66
+ return hasModifications ? formatSource(root.toSource(), file.path) : null
67
+ }
68
+
69
+ function updateProps(j: JSCodeshift, root: Collection, filePath: string) {
70
+ const buttonImportName = findImport(j, root, 'Button', [
71
+ '@instructure/ui-buttons',
72
+ '@instructure/ui'
73
+ ])
74
+ if (buttonImportName) {
75
+ updateV7ButtonsMisc(j, root, buttonImportName, filePath)
76
+ updateV7ButtonsWithText(j, root, buttonImportName, filePath)
77
+ updateV7ButtonsIconCircle(j, root, buttonImportName, filePath)
78
+ UpdateV7ButtonsLink(j, root, buttonImportName, filePath)
79
+ }
80
+ const closeButtonUpdated = updateV7ButtonsClose(j, root, filePath)
81
+ const headingUpdated = updateV7Heading(j, root, filePath)
82
+ const listsUpdated = updateV7Lists(j, root, filePath)
83
+ const pillUpdated = updateV7Pill(j, root, filePath)
84
+ const popoverUpdated = updateV7Popover(j, root, filePath)
85
+ const tabsUpdated = updateV7Tabs(j, root, filePath)
86
+ const focusableViewUpdated = updateV7FocusableView(j, root, filePath)
87
+ warnV7ComponentDeprecations(j, root, filePath)
88
+ if (
89
+ buttonImportName ||
90
+ closeButtonUpdated ||
91
+ headingUpdated ||
92
+ listsUpdated ||
93
+ pillUpdated ||
94
+ popoverUpdated ||
95
+ tabsUpdated ||
96
+ focusableViewUpdated
97
+ ) {
98
+ return formatSource(root.toSource(), filePath)
99
+ }
100
+ return null
101
+ }