diff --git a/packages/transforms/src/mdiIconToMdiPath/__tests__/mdiIconToMdiPath.test.ts b/packages/transforms/src/mdiIconToMdiPath/__tests__/mdiIconToMdiPath.test.ts
index 338807da..69c95fbd 100644
--- a/packages/transforms/src/mdiIconToMdiPath/__tests__/mdiIconToMdiPath.test.ts
+++ b/packages/transforms/src/mdiIconToMdiPath/__tests__/mdiIconToMdiPath.test.ts
@@ -62,4 +62,58 @@ testCodemod('mdiIconToMdiPath', mdiIconToMdiPath, [
`,
],
},
+ {
+ label: 'handles direct usage of mdi icons',
+ initialSource: `
+ import CloseIcon from 'mdi-react/CloseIcon'
+
+ export const Test = `,
+ expectedSource: `
+ import { mdiClose } from '@mdi/js'
+
+ import { Icon } from '@sourcegraph/wildcard'
+
+ export const Test =
+ `,
+ expectedManualChangeMessages: [
+ `
+ /test.tsx:3:20 - warning: component did not have accessibility attributes and has been hidden from screen readers automatically. Please review manually
+ >>>
+ `,
+ ],
+ },
+ {
+ label: 'handles direct usage of mdi icons with existing aria attributes',
+ initialSource: `
+ import CloseIcon from 'mdi-react/CloseIcon'
+
+ export const Test = `,
+ expectedSource: `
+ import { mdiClose } from '@mdi/js'
+
+ import { Icon } from '@sourcegraph/wildcard'
+
+ export const Test =
+ `,
+ },
+ {
+ label: 'handles direct usage of mdi icons with the size prop',
+ initialSource: `
+ import CloseIcon from 'mdi-react/CloseIcon'
+
+ export const Test =
+ export const Test2 = `,
+ expectedSource: `
+ import { mdiClose } from '@mdi/js'
+
+ import { Icon } from '@sourcegraph/wildcard'
+
+ export const Test = (
+
+ )
+ export const Test2 = (
+
+ )
+ `,
+ },
])
diff --git a/packages/transforms/src/mdiIconToMdiPath/mdiIconToMdiPath.ts b/packages/transforms/src/mdiIconToMdiPath/mdiIconToMdiPath.ts
index 6ab4a450..84126a64 100644
--- a/packages/transforms/src/mdiIconToMdiPath/mdiIconToMdiPath.ts
+++ b/packages/transforms/src/mdiIconToMdiPath/mdiIconToMdiPath.ts
@@ -1,3 +1,6 @@
+import { Node } from 'ts-morph'
+
+import { addOrUpdateSourcegraphWildcardImportIfNeeded } from '@sourcegraph/codemod-toolkit-packages'
import {
runTransform,
getParentUntilOrThrow,
@@ -9,7 +12,7 @@ import {
* Convert `` element to `` component.
*/
export const mdiIconToMdiPath = runTransform(context => {
- const { throwManualChangeError } = context
+ const { throwManualChangeError, addManualChangeLog } = context
const mdiIconPathsToImport = new Set()
@@ -19,6 +22,76 @@ export const mdiIconToMdiPath = runTransform(context => {
}
return {
+ /**
+ * Handles converting to
+ */
+ JsxSelfClosingElement(jsxElement) {
+ const tagElementName = jsxElement.getTagNameNode().getText()
+ const iconRegex = /(\w*.)Icon/
+
+ if (!tagElementName.match(iconRegex) || !isMdiReactToken(tagElementName)) {
+ // Not to (we handle correct import later)
+ jsxElement.set({
+ name: 'Icon',
+ })
+
+ // Add updated svgPath attribute
+ jsxElement.addAttribute({
+ name: 'svgPath',
+ initializer: `{${updatedValue}}`,
+ })
+
+ // Ensure `inline` is set to false to guarantee that we aren't introducing any new CSS with this change.
+ jsxElement.addAttribute({
+ name: 'inline',
+ initializer: '{false}',
+ })
+
+ // We need to set accessibility attributes on all icons
+ // If these aren't already set, we default to `aria-hidden={true}` and leave a message to review.
+ if (!jsxElement.getAttribute('aria-label') && !jsxElement.getAttribute('aria-hidden')) {
+ jsxElement.addAttribute({
+ name: 'aria-hidden',
+ initializer: '{true}',
+ })
+
+ addManualChangeLog({
+ node: jsxElement,
+ message:
+ ' component did not have accessibility attributes and has been hidden from screen readers automatically. Please review manually',
+ })
+ }
+
+ // Our previous icon library supported a `size` prop, which set height and width.
+ // We convert this to height and width to be explicit.
+ const sizeAttribute = jsxElement.getAttribute('size')
+ if (sizeAttribute && Node.isJsxAttribute(sizeAttribute)) {
+ jsxElement.addAttribute({
+ name: 'height',
+ initializer: sizeAttribute.getInitializer()?.getText(),
+ })
+
+ jsxElement.addAttribute({
+ name: 'width',
+ initializer: sizeAttribute.getInitializer()?.getText(),
+ })
+
+ // Remove the old attribute
+ jsxElement.getAttribute('size')?.remove()
+ }
+
+ // Store this value so we can import it once finished with this file.
+ mdiIconPathsToImport.add(updatedValue)
+ },
+ /**
+ * Handles converting to
+ */
JsxAttribute(jsxAttribute) {
const jsxTagElement = getParentUntilOrThrow(jsxAttribute, isJsxTagElement)
if (jsxTagElement.getTagNameNode().getText() !== 'Icon') {
@@ -75,9 +148,19 @@ export const mdiIconToMdiPath = runTransform(context => {
namedImports: [...mdiIconPathsToImport],
moduleSpecifier: '@mdi/js',
})
- }
- sourceFile.fixUnusedIdentifiers()
+ // If we're using the component for the first time,
+ // we need to add the import
+ addOrUpdateSourcegraphWildcardImportIfNeeded({
+ sourceFile,
+ importStructure: {
+ namedImports: ['Icon'],
+ },
+ })
+
+ // Clean up
+ sourceFile.fixUnusedIdentifiers()
+ }
},
}
})