-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Wildcard
Input
codemod (#94)
- Loading branch information
1 parent
4cb14b1
commit 5c6a871
Showing
11 changed files
with
289 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Icon element to `<Icon />` Wildcard component codemod | ||
|
||
yarn transform --write -t ./packages/transforms/src/inputToComponent/inputToComponent.ts '/sourcegraph/client/!(wildcard)/src/\*_/_.{ts,tsx}' |
57 changes: 57 additions & 0 deletions
57
packages/transforms/src/inputToComponent/__tests__/inputToComponent.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { testCodemod } from '@sourcegraph/codemod-toolkit-ts' | ||
|
||
import { inputToComponent } from '../inputToComponent' | ||
|
||
testCodemod('inputToComponent', inputToComponent, [ | ||
{ | ||
label: 'case 1', | ||
initialSource: 'export const Test = <input className="hello form-control" type="text" {...rest} />', | ||
expectedSource: ` | ||
import { Input } from '@sourcegraph/wildcard' | ||
export const Test = <Input className="hello" {...rest} /> | ||
`, | ||
}, | ||
{ | ||
label: 'case 2', | ||
initialSource: 'export const Test = <input className="form-control form-control-sm hello" />', | ||
expectedSource: ` | ||
import { Input } from '@sourcegraph/wildcard' | ||
export const Test = <Input className="hello" size="sm" /> | ||
`, | ||
}, | ||
{ | ||
label: 'case 3', | ||
initialSource: ` | ||
import classNames from 'classnames' | ||
export const Test = <input className={classNames('form-control-sm hello', styles.coolInput)} />`, | ||
expectedSource: ` | ||
import classNames from 'classnames' | ||
import { Input } from '@sourcegraph/wildcard' | ||
export const Test = <Input className={classNames('hello', styles.coolInput)} size="sm" /> | ||
`, | ||
}, | ||
{ | ||
label: 'case 4', | ||
initialSource: ` | ||
import classNames from 'classnames' | ||
export const Test = <input aria-label="Console icon" className={classNames('form-control', styles.coolInput)} />`, | ||
expectedSource: ` | ||
import { Input } from '@sourcegraph/wildcard' | ||
export const Test = <Input aria-label="Console icon" className={styles.coolInput} /> | ||
`, | ||
}, | ||
{ | ||
label: 'case 5', | ||
initialSource: 'export const Test = <input className="hello" type="text" {...rest} />', | ||
expectedSource: ` | ||
import { Input } from '@sourcegraph/wildcard' | ||
export const Test = <Input className="hello" {...rest} /> | ||
`, | ||
}, | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './inputToComponent' | ||
export * from './validateCodemodTarget' |
31 changes: 31 additions & 0 deletions
31
packages/transforms/src/inputToComponent/inputClassNamesMapping.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { ts } from 'ts-morph' | ||
|
||
export const INPUT_SIZES = ['sm'] as const | ||
|
||
export interface ClassNameMapping { | ||
className: string | ||
props: { | ||
name: string | ||
value: ts.Node | ||
}[] | ||
} | ||
|
||
const sizeClassNamesMapping: ClassNameMapping[] = INPUT_SIZES.map(size => { | ||
return { | ||
className: `form-control-${size}`, | ||
props: [ | ||
{ | ||
name: 'size', | ||
value: ts.factory.createStringLiteral(size), | ||
}, | ||
], | ||
} | ||
}) | ||
|
||
export const inputClassNamesMapping: ClassNameMapping[] = [ | ||
...sizeClassNamesMapping, | ||
{ | ||
className: 'form-control', | ||
props: [], | ||
}, | ||
] |
95 changes: 95 additions & 0 deletions
95
packages/transforms/src/inputToComponent/inputToComponent.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { Node, printNode } from 'ts-morph' | ||
|
||
import { | ||
removeClassNameAndUpdateJsxElement, | ||
addOrUpdateSourcegraphWildcardImportIfNeeded, | ||
} from '@sourcegraph/codemod-toolkit-packages' | ||
import { | ||
runTransform, | ||
getParentUntilOrThrow, | ||
isJsxTagElement, | ||
getTagName, | ||
JsxTagElement, | ||
setOnJsxTagElement, | ||
getJsxAttributeStringValue, | ||
removeJsxAttribute, | ||
} from '@sourcegraph/codemod-toolkit-ts' | ||
|
||
import { validateCodemodTarget, validateCodemodTargetOrThrow } from './validateCodemodTarget' | ||
|
||
/** | ||
* Convert `<input class="form-control" />` element to the `<Input />` component. | ||
*/ | ||
export const inputToComponent = runTransform(context => { | ||
const { throwManualChangeError, addManualChangeLog } = context | ||
|
||
const jsxTagElementsToUpdate = new Set<JsxTagElement>() | ||
|
||
return { | ||
JsxSelfClosingElement(jsxTagElement) { | ||
if (validateCodemodTarget.JsxTagElement(jsxTagElement)) { | ||
jsxTagElementsToUpdate.add(jsxTagElement) | ||
} | ||
}, | ||
StringLiteral(stringLiteral) { | ||
const { classNameMappings } = validateCodemodTargetOrThrow.StringLiteral(stringLiteral) | ||
const jsxAttribute = getParentUntilOrThrow(stringLiteral, Node.isJsxAttribute) | ||
|
||
if (!/classname/i.test(jsxAttribute.getName())) { | ||
return | ||
} | ||
|
||
const jsxTagElement = getParentUntilOrThrow(jsxAttribute, isJsxTagElement) | ||
|
||
if (!validateCodemodTarget.JsxTagElement(jsxTagElement)) { | ||
throwManualChangeError({ | ||
node: jsxTagElement, | ||
message: `Class '${stringLiteral.getLiteralText()}' is used on the '${getTagName( | ||
jsxTagElement | ||
)}' element. Please update it manually.`, | ||
}) | ||
} | ||
|
||
for (const { className, props } of classNameMappings) { | ||
const { isRemoved, manualChangeLog } = removeClassNameAndUpdateJsxElement(stringLiteral, className) | ||
|
||
if (manualChangeLog) { | ||
addManualChangeLog(manualChangeLog) | ||
} | ||
|
||
if (isRemoved) { | ||
for (const { name, value } of props) { | ||
jsxTagElement.addAttribute({ | ||
name, | ||
initializer: printNode(value), | ||
}) | ||
} | ||
} | ||
} | ||
|
||
jsxTagElementsToUpdate.add(jsxTagElement) | ||
}, | ||
SourceFileExit(sourceFile) { | ||
if (jsxTagElementsToUpdate.size === 0) { | ||
return | ||
} | ||
|
||
for (const jsxTagElement of jsxTagElementsToUpdate) { | ||
if (getJsxAttributeStringValue(jsxTagElement, 'type') === 'text') { | ||
removeJsxAttribute(jsxTagElement, 'type') | ||
} | ||
|
||
setOnJsxTagElement(jsxTagElement, { name: 'Input' }) | ||
} | ||
|
||
addOrUpdateSourcegraphWildcardImportIfNeeded({ | ||
sourceFile, | ||
importStructure: { | ||
namedImports: ['Input'], | ||
}, | ||
}) | ||
|
||
sourceFile.fixUnusedIdentifiers() | ||
}, | ||
} | ||
}) |
49 changes: 49 additions & 0 deletions
49
packages/transforms/src/inputToComponent/validateCodemodTarget.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { StringLiteral } from 'ts-morph' | ||
|
||
import { throwFromMethodsIfUndefinedReturn } from '@sourcegraph/codemod-common' | ||
import { JsxTagElement } from '@sourcegraph/codemod-toolkit-ts' | ||
|
||
import { inputClassNamesMapping, ClassNameMapping } from './inputClassNamesMapping' | ||
|
||
interface StringLiteralValidatorResult { | ||
stringLiteral: StringLiteral | ||
classNameMappings: ClassNameMapping[] | ||
} | ||
|
||
interface JsxTagElementValidatorResult { | ||
jsxTagElement: JsxTagElement | ||
tagName: string | ||
} | ||
|
||
export const validateCodemodTarget = { | ||
/** | ||
* Returns `JsxTagElement`. | ||
*/ | ||
JsxTagElement(jsxTagElement: JsxTagElement, bannedTagName = 'input'): JsxTagElementValidatorResult | void { | ||
const tagName = jsxTagElement.getTagNameNode().getText() | ||
|
||
if (tagName === bannedTagName) { | ||
return { jsxTagElement, tagName } | ||
} | ||
}, | ||
|
||
/** | ||
* Returns non-void result if received `StringLiteral` has one of icon classes like `icon-inline`. | ||
*/ | ||
StringLiteral(stringLiteral: StringLiteral): StringLiteralValidatorResult | void { | ||
const classNameMappings = inputClassNamesMapping.filter(({ className }) => { | ||
return stringLiteral | ||
.getLiteralValue() | ||
.split(' ') | ||
.some(word => { | ||
return word === className | ||
}) | ||
}) | ||
|
||
if (classNameMappings.length !== 0) { | ||
return { classNameMappings, stringLiteral } | ||
} | ||
}, | ||
} | ||
|
||
export const validateCodemodTargetOrThrow = throwFromMethodsIfUndefinedReturn(validateCodemodTarget) |
Oops, something went wrong.