Skip to content

Commit

Permalink
T dama/CodeMods - Add generic config-based codemod generator (#14496)
Browse files Browse the repository at this point in the history
* merge conflicts

* merge conflicts

* merge conflicts

* merge conflicts

* fix prop helper

* merge conflicts

* WIP improve logging

* merge conflicts

* merge conflicts

* merge conflicts

* merge conflicts

* WIP polishing

* remove logging

* add generic config mod

* Change files

* fix test

* add snapshot testing

* add snapshot

* improve logging

* new snaps

* fix tests

Co-authored-by: Trip Master <[email protected]>
  • Loading branch information
tmaster628 and Trip Master authored Aug 14, 2020
1 parent 2b472d6 commit d6a07da
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "minor",
"comment": "add generic config mod",
"packageName": "@fluentui/codemods",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2020-08-12T22:44:18.748Z"
}
3 changes: 2 additions & 1 deletion packages/codemods/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"just": "just-scripts",
"lint": "just-scripts lint",
"start-test": "just-scripts jest-watch",
"test": "just-scripts test"
"test": "just-scripts test",
"update-snapshots": "just-scripts jest -u"
},
"devDependencies": {
"@fluentui/eslint-plugin": "^0.53.4",
Expand Down
46 changes: 46 additions & 0 deletions packages/codemods/src/codeMods/mods/configMod/configMod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { SourceFile } from 'ts-morph';
import { CodeMod } from '../../types';
import { findJsxTag, renameProp } from '../../utilities/index';

const jsonObj = require('../upgrades.json');

/* Intermediate file that reads upgrades.json and returns
a codemod object to be run.
TODO: Add error checks for 'undefined' */
export function createCodeModFromJson(): CodeMod | undefined {
return {
run: (file: SourceFile) => {
try {
/* Codemod body, which can be added to */
const funcs = getCodeModUtilitiesFromJson(file);
funcs.forEach(func => {
func();
});
} catch (e) {
return { success: false };
}
return { success: true };
},
version: '100000',
name: jsonObj.name,
enabled: true,
};
}

/* Helper function that parses a json object for details about individual
codemods and formats each into a function. These functions are stored in
an array that is returned to the user. */
export function getCodeModUtilitiesFromJson(file: SourceFile): (() => void)[] {
const functions = [];
const modDetails = jsonObj.upgrades;
for (let i = 0; i < modDetails.length; i++) {
if (modDetails[i].type === 'renameProp') {
const func = function() {
const tags = findJsxTag(file, modDetails[i].options.from.importName);
renameProp(tags, modDetails[i].options.from.toRename, modDetails[i].options.to.replacementName);
};
functions.push(func);
}
}
return functions;
}
33 changes: 33 additions & 0 deletions packages/codemods/src/codeMods/mods/upgrades.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@fluentui.react",
"upgrades": [
{
"name": "Renaming 'isDisabled' to 'disabled' in Dropdown",
"type": "renameProp",
"options": {
"from": {
"importName": "Dropdown",
"paths": ["office-ui-fabric-react/lib"],
"toRename": "isDisabled"
},
"to": {
"replacementName": "disabled"
}
}
},
{
"name": "Renaming 'toggled' to 'checked' in CompoundButton WITH spread",
"type": "renameProp",
"options": {
"from": {
"importName": "CompoundButton",
"paths": ["office-ui-fabric-react/lib"],
"toRename": "toggled"
},
"to": {
"replacementName": "checked"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Tests a simple data-driven codeMod can rename props in CompoundButton and Dropdown 1`] = `
"import * as React from 'react';
import { CompoundButton } from 'office-ui-fabric-react/lib/Button';
export class RenderButton extends React.Component<LocalButtonProps> {
public render(): JSX.Element {
const { toggled, id, ...restProps } = this.props;
return (
<div>
<CompoundButton {...this.props} {...restProps} id={'d2'} checked={toggled}>
Button!
</CompoundButton>
</div>
);
}
}
export interface LocalButtonProps {
id: string;
description?: string;
imageSource: string;
toggled?: boolean;
}
"
`;
exports[`Tests a simple data-driven codeMod can rename props in CompoundButton and Dropdown 2`] = `
"import * as React from 'react';
import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const RenderDropdown = (props: any) => {
return (
<div>
<Dropdown options={[]} dropdownWidth={0} placeHolder={'placeholder!'} disabled={true} />
<Dropdown options={[]} dropdownWidth={5} placeHolder={'placeholder!'} disabled={false}>
{' '}
Woo Hoo!{' '}
</Dropdown>
</div>
);
};
"
`;
37 changes: 37 additions & 0 deletions packages/codemods/src/codeMods/tests/configMod/configMod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Project } from 'ts-morph';
import { createCodeModFromJson } from '../../mods/configMod/configMod';
import { Maybe } from '../../../helpers/maybe';
import { runMods } from '../../../modRunner/runnerUtilities';

const buttonPath = '/**/tests/mock/**/button/**/*.tsx';
const dropDownPath = '/**/tests/mock/**/dropdown/**/*.tsx';

describe('Tests a simple data-driven codeMod', () => {
let project: Project;

beforeEach(() => {
project = new Project();
project.addSourceFilesAtPaths(`${process.cwd()}${buttonPath}`);
project.addSourceFilesAtPaths(`${process.cwd()}${dropDownPath}`);
});

// TODO: Can you raise more errors in renameProp for better mod logging?
// TODO: Add value change examples && more mods in a later, larger PR.

it('can rename props in CompoundButton and Dropdown', () => {
const mod = Maybe(createCodeModFromJson());
if (mod.something) {
const mods = [];
mods.push(mod.value);
runMods(mods, project.getSourceFiles(), result => {
if (result.error) {
console.error(`Error running mod ${result.mod.name} on file ${result.file.getBaseName()}`, result.error);
} else {
console.log(`Upgraded file ${result.file.getBaseName()} with mod ${result.mod.name}`);
}
});
}
expect(project.getSourceFile('mCompoundButtonProps.tsx')?.getFullText()).toMatchSnapshot();
expect(project.getSourceFile('mDropdownProps.tsx')?.getFullText()).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ describe('Persona props mod tests', () => {
project.addSourceFilesAtPaths(`${process.cwd()}${dropDownPath}`);
});

it('can rename the toggled feature in Button', () => {
const file = project.getSourceFileOrThrow('mButtonProps.tsx');
const tags = findJsxTag(file, 'Button');
renameProp(tags, 'toggled', 'checked');
tags.forEach(val => {
expect(val.getAttribute('toggled')).not.toBeTruthy();
});
});

it('can work on the dropdown example', () => {
const file = project.getSourceFileOrThrow('mDropdownSpreadProps.tsx');
const tags = findJsxTag(file, 'Dropdown');
Expand Down

0 comments on commit d6a07da

Please sign in to comment.