@instructure/ui-codemods 8.17.1-snapshot.31 → 8.17.1-snapshot.74
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.
- package/README.md +8 -0
- package/lib/helpers/replaceDeprecatedImports.ts +144 -3
- package/lib/helpers/replaceDeprecatedProps.ts +75 -7
- package/lib/helpers/v7PropsUpdateHelpers.ts +551 -0
- package/lib/updateImports.ts +1 -1
- package/lib/updatePropNames.ts +7 -2
- package/lib/updateV7Props.ts +101 -0
- package/lib/updateV8Props.ts +74 -0
- package/lib/utils/UpdateV7ButtonsLink.ts +139 -0
- package/lib/utils/updateV7ButtonsClose.ts +104 -0
- package/lib/utils/updateV7ButtonsIconCircle.ts +240 -0
- package/lib/utils/updateV7ButtonsMisc.ts +137 -0
- package/lib/utils/updateV7ButtonsWithText.ts +111 -0
- package/lib/utils/updateV7FocusableView.ts +105 -0
- package/lib/utils/updateV7Heading.ts +145 -0
- package/lib/utils/updateV7Lists.ts +113 -0
- package/lib/utils/updateV7Pill.ts +83 -0
- package/lib/utils/updateV7Popover.ts +137 -0
- package/lib/utils/updateV7Tabs.ts +129 -0
- package/lib/utils/warnV7ComponentDeprecations.ts +96 -0
- package/package.json +3 -3
- package/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils/createLiteral.ts +0 -48
- package/lib/utils/findDeprecatedProp.ts +0 -59
- package/lib/utils/findImportDeclaration.ts +0 -51
- package/lib/utils/findTransform.ts +0 -97
- package/lib/utils/parseImport.ts +0 -78
|
@@ -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
|
+
}
|
package/lib/updateImports.ts
CHANGED
|
@@ -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 './
|
|
31
|
+
import type { ParsedImport } from './helpers/replaceDeprecatedImports'
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* updates imports to the new per-package or the metapackage syntax.
|
package/lib/updatePropNames.ts
CHANGED
|
@@ -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 (
|
|
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 =
|
|
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
|
+
}
|