diff --git a/change/@fluentui-codemods-2020-08-15-12-38-06-implement-result.json b/change/@fluentui-codemods-2020-08-15-12-38-06-implement-result.json new file mode 100644 index 0000000000000..d48b7880ec526 --- /dev/null +++ b/change/@fluentui-codemods-2020-08-15-12-38-06-implement-result.json @@ -0,0 +1,8 @@ +{ + "type": "patch", + "comment": "Codemods: Implement result and prepare for logging changes", + "packageName": "@fluentui/codemods", + "email": "joschect@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-08-15T19:38:06.133Z" +} diff --git a/packages/codemods/src/codeMods/mods/componentToCompat/componentToCompat.mod.ts b/packages/codemods/src/codeMods/mods/componentToCompat/componentToCompat.mod.ts index 1b2f8f5b37a5f..46a130b90cf82 100644 --- a/packages/codemods/src/codeMods/mods/componentToCompat/componentToCompat.mod.ts +++ b/packages/codemods/src/codeMods/mods/componentToCompat/componentToCompat.mod.ts @@ -1,6 +1,7 @@ import { runComponentToCompat, buildCompatHash, RawCompat, ComponentToCompat, getNamedExports } from './compatHelpers'; import { CodeMod } from '../../types'; import { SourceFile } from 'ts-morph'; +import { Ok } from '../../../helpers/result'; // Not sure if this the best way to get all the things exported from button. It's dependent on version // And other things. Ideally we'd be able to get it from within ts-morph. @@ -40,7 +41,7 @@ export function createComponentToCompat(comp: RawCompat): ComponentToCompat { const ComponentToCompat: CodeMod = { run: (file: SourceFile) => { runComponentToCompat(file, buildCompatHash(exportMapping, createComponentToCompat), fabricindex); - return { success: true }; + return Ok({ logs: ['Moved imports to compat'] }); }, name: 'ComponentToCompat', version: '1.0.0', diff --git a/packages/codemods/src/codeMods/mods/configMod/configMod.ts b/packages/codemods/src/codeMods/mods/configMod/configMod.ts index d70a0121a6436..f376526010ef7 100644 --- a/packages/codemods/src/codeMods/mods/configMod/configMod.ts +++ b/packages/codemods/src/codeMods/mods/configMod/configMod.ts @@ -8,6 +8,7 @@ import { CodeModMapType, } from '../../types'; import { findJsxTag, renameProp, getImportsByPath, repathImport } from '../../utilities/index'; +import { Ok, Err } from '../../../helpers/result'; const jsonObj: UpgradeJSONType = require('../upgrades.json'); @@ -23,9 +24,9 @@ export function createCodeModFromJson(): CodeMod | undefined { func(); }); } catch (e) { - return { success: false }; + Err({ reason: 'Error' }); } - return { success: true }; + return Ok({ logs: ['Updated Successfully'] }); }, version: '100000', name: jsonObj.name, diff --git a/packages/codemods/src/codeMods/mods/officeToFluentImport/officeToFluentImport.mod.ts b/packages/codemods/src/codeMods/mods/officeToFluentImport/officeToFluentImport.mod.ts index 73f96447e1dee..89055001a48aa 100644 --- a/packages/codemods/src/codeMods/mods/officeToFluentImport/officeToFluentImport.mod.ts +++ b/packages/codemods/src/codeMods/mods/officeToFluentImport/officeToFluentImport.mod.ts @@ -1,6 +1,7 @@ import { SourceFile } from 'ts-morph'; import { CodeMod } from '../../types'; import { getImportsByPath, repathImport } from '../../utilities/index'; +import { Ok } from '../../../helpers/result'; const searchString = /^office\-ui\-fabric\-react/; const newString = '@fluentui/react'; @@ -11,7 +12,7 @@ const RepathOfficeToFluentImports: CodeMod = { imports.forEach(val => { repathImport(val, newString, searchString); }); - return { success: true }; + return Ok({ logs: ['Replaced office-ui-fabric-react imports with @fluentui'] }); }, name: 'RepathOfficeImportsToFluent', version: '1.0.0', diff --git a/packages/codemods/src/codeMods/mods/oldToNewButton/oldToNewButton.mod.ts b/packages/codemods/src/codeMods/mods/oldToNewButton/oldToNewButton.mod.ts index d40b7eafd4969..820e3748c6182 100644 --- a/packages/codemods/src/codeMods/mods/oldToNewButton/oldToNewButton.mod.ts +++ b/packages/codemods/src/codeMods/mods/oldToNewButton/oldToNewButton.mod.ts @@ -1,6 +1,7 @@ import { SourceFile } from 'ts-morph'; import { CodeMod } from '../../types'; import { renameProp, findJsxTag } from '../../utilities/index'; +import { Ok, Err } from '../../../helpers/result'; const oldToNewButton: CodeMod = { run: (file: SourceFile) => { @@ -8,9 +9,9 @@ const oldToNewButton: CodeMod = { const tags = findJsxTag(file, 'DefaultButton'); renameProp(tags, 'toggled', 'checked'); } catch (e) { - return { success: false }; + return Err({ reason: 'Error' }); } - return { success: true }; + return Ok({ logs: ['Upgrade completed'] }); }, version: '100000', name: 'oldToNewButton', diff --git a/packages/codemods/src/codeMods/mods/personaToAvatar/personaToAvatar.mod.ts b/packages/codemods/src/codeMods/mods/personaToAvatar/personaToAvatar.mod.ts index e533610574b50..b7a29d51b15c4 100644 --- a/packages/codemods/src/codeMods/mods/personaToAvatar/personaToAvatar.mod.ts +++ b/packages/codemods/src/codeMods/mods/personaToAvatar/personaToAvatar.mod.ts @@ -10,6 +10,7 @@ import { } from 'ts-morph'; import { findJsxTag, appendOrCreateNamedImport } from '../../utilities/index'; import { CodeMod } from '../../types'; +import { Ok, Err } from '../../../helpers/result'; const personaPath = 'office-ui-fabric-react/lib/Persona'; @@ -169,9 +170,9 @@ const PersonaToAvatarMod: CodeMod = { renamePrimaryTextProp(file); renameRenderCoin(file); } catch (e) { - return { success: false }; + return Err({ reason: 'Error', log: JSON.stringify(e) }); } - return { success: true }; + return Ok({ logs: ['Replaced Persona with Avatar'] }); }, version: '100000', name: 'PersonaToAvatar', diff --git a/packages/codemods/src/codeMods/types.ts b/packages/codemods/src/codeMods/types.ts index 3d59cb2c34b67..65400e029a979 100644 --- a/packages/codemods/src/codeMods/types.ts +++ b/packages/codemods/src/codeMods/types.ts @@ -1,9 +1,14 @@ import { SourceFile, JsxExpression, JsxOpeningElement, JsxSelfClosingElement } from 'ts-morph'; +import { Result } from '../helpers/result'; -export interface CodeModResult { - success?: boolean; +export interface ModResult { + logs: string[]; } - +export type NoOp = { + reason: string; + log?: string; +}; +export type CodeModResult = Result; export interface CodeMod { /** * Each type of codemod can have multiple versions which work on different versions of its targeted package. diff --git a/packages/codemods/src/modRunner/logger.ts b/packages/codemods/src/modRunner/logger.ts new file mode 100644 index 0000000000000..0f2e2ad48c004 --- /dev/null +++ b/packages/codemods/src/modRunner/logger.ts @@ -0,0 +1,10 @@ +export interface LogFunction { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (...args: any[]): void; +} + +export interface Logger { + log: LogFunction; + warn: LogFunction; + error: LogFunction; +} diff --git a/packages/codemods/src/modRunner/runnerUtilities.ts b/packages/codemods/src/modRunner/runnerUtilities.ts index b0763ccf579c8..7dcce00069751 100644 --- a/packages/codemods/src/modRunner/runnerUtilities.ts +++ b/packages/codemods/src/modRunner/runnerUtilities.ts @@ -1,6 +1,7 @@ import { CodeMod } from '../codeMods/types'; import { Glob } from 'glob'; import { Maybe, Nothing, Something } from '../helpers/maybe'; +import { Logger } from './logger'; // TODO ensure that async for all these utilities works export function runMods( @@ -71,12 +72,12 @@ export function loadMod(path: string, errorCallback: (e: Error) => void): Maybe< return Nothing(); } -export function getEnabledMods(getPaths = getModsPaths, loadM = loadMod) { +export function getEnabledMods(logger: Logger, getPaths = getModsPaths, loadM = loadMod) { return getPaths() .map(pth => { - console.log('fetching codeMod at ', pth); + logger.log('fetching codeMod at ', pth); return loadM(pth, e => { - console.error(e); + logger.error(e); }); }) .filter(modEnabled) diff --git a/packages/codemods/src/modRunner/tests/command.test.ts b/packages/codemods/src/modRunner/tests/command.test.ts index a481e2a88c225..1f73510f5dbe1 100644 --- a/packages/codemods/src/modRunner/tests/command.test.ts +++ b/packages/codemods/src/modRunner/tests/command.test.ts @@ -1,4 +1,5 @@ import { CommandParser, CommandParserResult, yargsParse } from '../../command'; +import { Ok } from '../../helpers/result'; describe('command parser', () => { describe('when called with a single argument', () => { @@ -39,7 +40,7 @@ describe('command parser', () => { result.modsFilter({ name: 'one', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(true); @@ -50,7 +51,7 @@ describe('command parser', () => { result.modsFilter({ name: 'one', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(false); diff --git a/packages/codemods/src/modRunner/tests/filters.test.ts b/packages/codemods/src/modRunner/tests/filters.test.ts index 5824acaed23e8..69270da1ea9c1 100644 --- a/packages/codemods/src/modRunner/tests/filters.test.ts +++ b/packages/codemods/src/modRunner/tests/filters.test.ts @@ -1,5 +1,6 @@ import { getModFilter, getRegexFilter, getStringFilter } from '../modFilter'; import { Maybe } from '../../helpers/maybe'; +import { Ok } from '../../helpers/result'; describe('modRunner tests', () => { it('gets a basic exact name match filter from string', () => { @@ -21,7 +22,7 @@ describe('modRunner tests', () => { modFilter({ name: 'ohi', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(true); @@ -29,7 +30,7 @@ describe('modRunner tests', () => { modFilter({ name: 'bar', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(true); @@ -37,7 +38,7 @@ describe('modRunner tests', () => { modFilter({ name: 'foo', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(true); @@ -49,7 +50,7 @@ describe('modRunner tests', () => { modFilter({ name: 'hi', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(true); @@ -57,7 +58,7 @@ describe('modRunner tests', () => { modFilter({ name: 'o zz o', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(true); @@ -65,7 +66,7 @@ describe('modRunner tests', () => { modFilter({ name: 'I wont be filtered!', run: () => { - return {}; + return Ok({ logs: [] }); }, }), ).toBe(false); diff --git a/packages/codemods/src/modRunner/tests/mocks/MockMods/CodeMod.mock.ts b/packages/codemods/src/modRunner/tests/mocks/MockMods/CodeMod.mock.ts index 66a6e0ed33ff8..3e1ad8663677d 100644 --- a/packages/codemods/src/modRunner/tests/mocks/MockMods/CodeMod.mock.ts +++ b/packages/codemods/src/modRunner/tests/mocks/MockMods/CodeMod.mock.ts @@ -1,7 +1,8 @@ import { CodeMod } from '../../../../codeMods/types'; +import { Err } from '../../../../helpers/result'; const CodeMod: CodeMod = { run: () => { - return {}; + return Err({ reason: 'No operation taken' }); }, version: '1.0.0', name: 'CodeMod', diff --git a/packages/codemods/src/modRunner/tests/modRunner.test.ts b/packages/codemods/src/modRunner/tests/modRunner.test.ts index 85512ad77e0d5..89b60b13164c3 100644 --- a/packages/codemods/src/modRunner/tests/modRunner.test.ts +++ b/packages/codemods/src/modRunner/tests/modRunner.test.ts @@ -8,6 +8,7 @@ import { } from '../runnerUtilities'; import { CodeMod, CodeModResult } from '../../codeMods/types'; import { Maybe, Nothing } from '../../helpers/maybe'; +import { Ok } from '../../helpers/result'; describe('modRunner tests', () => { it('gets the appropriate path to mods based on current dir', () => { @@ -40,7 +41,7 @@ describe('modRunner tests', () => { let runCount = 0; const runCallBack = (foo: string): CodeModResult => { runCount = runCount + 1; - return {}; + return Ok({ logs: [] }); }; const mods: CodeMod[] = [ { @@ -65,7 +66,7 @@ describe('modRunner tests', () => { it('filters enabled and nothing Mods', () => { const runcallBack = (foo: string): CodeModResult => { - return {}; + return Ok({ logs: [] }); }; // use a generator to simulate getting each mod back @@ -89,6 +90,7 @@ describe('modRunner tests', () => { const gen = modGen(); const filtered = getEnabledMods( + console, () => ['1', '2', '3', '4'], () => gen.next().value, ); diff --git a/packages/codemods/src/upgrade.ts b/packages/codemods/src/upgrade.ts index 53b07057ef68b..0bb61d25ecd10 100644 --- a/packages/codemods/src/upgrade.ts +++ b/packages/codemods/src/upgrade.ts @@ -1,12 +1,13 @@ import { runMods, getTsConfigs, getEnabledMods } from './modRunner/runnerUtilities'; import { CommandParserResult } from './command'; +import { Logger } from './modRunner/logger'; import { Project } from 'ts-morph'; - -// TODO actually do console logging, implement some nice callbacks. +// Injection point for logger so that it can easily be replaced. +const logger: Logger = console; export function upgrade(options: CommandParserResult) { - const mods = getEnabledMods().filter(options.modsFilter); + const mods = getEnabledMods(logger).filter(options.modsFilter); - console.log('getting configs'); + logger.log('getting configs'); const configs = getTsConfigs(); configs.forEach(configString => { @@ -17,14 +18,14 @@ export function upgrade(options: CommandParserResult) { const files = project.getSourceFiles(); runMods(mods, files, result => { if (result.error) { - console.error(`Error running mod ${result.mod.name} on file ${result.file.getBaseName()}`, result.error); + logger.error(`Error running mod ${result.mod.name} on file ${result.file.getBaseName()}`, result.error); error = true; } else { - console.log(`Upgraded file ${result.file.getBaseName()} with mod ${result.mod.name}`); + logger.log(`Upgraded file ${result.file.getBaseName()} with mod ${result.mod.name}`); } }); } catch (e) { - console.error(e); + logger.error(e); error = true; } if (!error) {