Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: JSON configuration file to control plugins #74

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 10 additions & 87 deletions packages/ts-migrate/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,13 @@

/* eslint-disable no-await-in-loop, no-restricted-syntax */
import path from 'path';
import log from 'updatable-log';
import yargs from 'yargs';

import {
declareMissingClassPropertiesPlugin,
eslintFixPlugin,
explicitAnyPlugin,
hoistClassStaticsPlugin,
jsDocPlugin,
memberAccessibilityPlugin,
reactClassLifecycleMethodsPlugin,
reactClassStatePlugin,
reactDefaultPropsPlugin,
reactPropsPlugin,
reactShapePlugin,
stripTSIgnorePlugin,
tsIgnorePlugin,
Plugin,
} from 'ts-migrate-plugins';
import { eslintFixPlugin, stripTSIgnorePlugin, tsIgnorePlugin, Plugin } from 'ts-migrate-plugins';
import { migrate, MigrateConfig } from 'ts-migrate-server';
import init from './commands/init';
import rename from './commands/rename';
import { defaultMigrateConfig, migrateConfigFromFile, singlePluginConfig } from './config';

// eslint-disable-next-line no-unused-expressions
yargs
Expand Down Expand Up @@ -76,6 +61,8 @@ yargs
(cmd) =>
cmd
.positional('folder', { type: 'string' })
.string('config')
.alias('config', 'c')
.choices('defaultAccessibility', ['private', 'protected', 'public'] as const)
.string('plugin')
.string('privateRegex')
Expand All @@ -84,6 +71,7 @@ yargs
.string('sources')
.alias('sources', 's')
.describe('sources', 'Path to a subset of your project to rename (globs are ok).')
.string('typeMap')
.example('migrate /frontend/foo', 'Migrate all the files in /frontend/foo')
.example(
'$0 migrate /frontend/foo -s "bar/**/*" -s "node_modules/**/*.d.ts"',
Expand All @@ -94,77 +82,12 @@ yargs
const rootDir = path.resolve(process.cwd(), args.folder);
const { sources } = args;
let config: MigrateConfig;

if (args.plugin) {
const availablePlugins = [
declareMissingClassPropertiesPlugin,
eslintFixPlugin,
explicitAnyPlugin,
hoistClassStaticsPlugin,
jsDocPlugin,
memberAccessibilityPlugin,
reactClassLifecycleMethodsPlugin,
reactClassStatePlugin,
reactDefaultPropsPlugin,
reactPropsPlugin,
reactShapePlugin,
stripTSIgnorePlugin,
tsIgnorePlugin,
];
const plugin = availablePlugins.find((cur) => cur.name === args.plugin);
if (!plugin) {
log.error(`Could not find a plugin named ${args.plugin}.`);
process.exit(1);
return;
}
if (plugin === jsDocPlugin) {
const anyAlias = args.aliases === 'tsfixme' ? '$TSFixMe' : undefined;
const typeMap = typeof args.typeMap === 'string' ? JSON.parse(args.typeMap) : undefined;
config = new MigrateConfig().addPlugin(jsDocPlugin, { anyAlias, typeMap });
} else {
config = new MigrateConfig().addPlugin(plugin, {});
}
if (args.config) {
config = migrateConfigFromFile(args.config);
} else if (args.plugin) {
config = singlePluginConfig(args.plugin, args);
} else {
const airbnbAnyAlias = '$TSFixMe';
const airbnbAnyFunctionAlias = '$TSFixMeFunction';
// by default, we're not going to use any aliases in ts-migrate
const anyAlias = args.aliases === 'tsfixme' ? airbnbAnyAlias : undefined;
const anyFunctionAlias = args.aliases === 'tsfixme' ? airbnbAnyFunctionAlias : undefined;
const useDefaultPropsHelper = args.useDefaultPropsHelper === 'true';

const { defaultAccessibility, privateRegex, protectedRegex, publicRegex } = args;

config = new MigrateConfig()
.addPlugin(stripTSIgnorePlugin, {})
.addPlugin(hoistClassStaticsPlugin, { anyAlias })
.addPlugin(reactPropsPlugin, {
anyAlias,
anyFunctionAlias,
shouldUpdateAirbnbImports: true,
})
.addPlugin(reactClassStatePlugin, { anyAlias })
.addPlugin(reactClassLifecycleMethodsPlugin, { force: true })
.addPlugin(reactDefaultPropsPlugin, {
useDefaultPropsHelper,
})
.addPlugin(reactShapePlugin, {
anyAlias,
anyFunctionAlias,
})
.addPlugin(declareMissingClassPropertiesPlugin, { anyAlias })
.addPlugin(memberAccessibilityPlugin, {
defaultAccessibility,
privateRegex,
protectedRegex,
publicRegex,
})
.addPlugin(explicitAnyPlugin, { anyAlias })
// We need to run eslint-fix before ts-ignore because formatting may affect where
// the errors are that need to get ignored.
.addPlugin(eslintFixPlugin, {})
.addPlugin(tsIgnorePlugin, {})
// We need to run eslint-fix again after ts-ignore to fix up formatting.
.addPlugin(eslintFixPlugin, {});
config = defaultMigrateConfig(args);
}

const exitCode = await migrate({ rootDir, config, sources });
Expand Down
133 changes: 133 additions & 0 deletions packages/ts-migrate/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import fs from 'fs';
import log from 'updatable-log';
import { MigrateConfig } from 'ts-migrate-server';

import {
declareMissingClassPropertiesPlugin,
eslintFixPlugin,
explicitAnyPlugin,
hoistClassStaticsPlugin,
jsDocPlugin,
memberAccessibilityPlugin,
reactClassLifecycleMethodsPlugin,
reactClassStatePlugin,
reactDefaultPropsPlugin,
reactPropsPlugin,
reactShapePlugin,
stripTSIgnorePlugin,
tsIgnorePlugin,
} from 'ts-migrate-plugins';

const availablePlugins = [
declareMissingClassPropertiesPlugin,
eslintFixPlugin,
explicitAnyPlugin,
hoistClassStaticsPlugin,
jsDocPlugin,
memberAccessibilityPlugin,
reactClassLifecycleMethodsPlugin,
reactClassStatePlugin,
reactDefaultPropsPlugin,
reactPropsPlugin,
reactShapePlugin,
stripTSIgnorePlugin,
tsIgnorePlugin,
];

interface MigrateConfigJson {
globalOptions?: Record<string, unknown>;
plugins: PluginConfigJson[];
}

interface PluginConfigJson {
name: string;
options?: Record<string, unknown>;
}

function migrateConfigFromJson(json: MigrateConfigJson): MigrateConfig {
const config = new MigrateConfig();
json.plugins.forEach((pluginJson) => {
const plugin = availablePlugins.find((cur) => cur.name === pluginJson.name);
if (!plugin) {
log.error(`Could not find a plugin named ${pluginJson.name}.`);
process.exit(1);
}
config.addPlugin(plugin, { ...json.globalOptions, ...pluginJson.options });
});
return config;
}

export function migrateConfigFromFile(path: string): MigrateConfig {
return migrateConfigFromJson(JSON.parse(fs.readFileSync(path).toString()));
}

interface Args {
aliases?: string;
defaultAccessibility?: 'private' | 'protected' | 'public';
privateRegex?: string;
protectedRegex?: string;
publicRegex?: string;
typeMap?: string;
useDefaultPropsHelper?: string;
}

export function singlePluginConfig(pluginName: string, args: Args): MigrateConfig {
return migrateConfigFromJson({
globalOptions: {
anyAlias: args.aliases === 'tsfixme' ? '$TSFixMe' : undefined,
},
plugins: [
{
name: pluginName,
options: {
typeMap: pluginName === 'jsdoc' && args.typeMap ? JSON.parse(args.typeMap) : undefined,
},
},
],
});
}

export function defaultMigrateConfig(args: Args): MigrateConfig {
const airbnbAnyAlias = '$TSFixMe';
const airbnbAnyFunctionAlias = '$TSFixMeFunction';
// by default, we're not going to use any aliases in ts-migrate
const anyAlias = args.aliases === 'tsfixme' ? airbnbAnyAlias : undefined;
const anyFunctionAlias = args.aliases === 'tsfixme' ? airbnbAnyFunctionAlias : undefined;
const useDefaultPropsHelper = args.useDefaultPropsHelper === 'true';

const { defaultAccessibility, privateRegex, protectedRegex, publicRegex } = args;

return (
new MigrateConfig()
.addPlugin(stripTSIgnorePlugin, {})
.addPlugin(hoistClassStaticsPlugin, { anyAlias })
.addPlugin(reactPropsPlugin, {
anyAlias,
anyFunctionAlias,
shouldUpdateAirbnbImports: true,
})
.addPlugin(reactClassStatePlugin, { anyAlias })
.addPlugin(reactClassLifecycleMethodsPlugin, { force: true })
.addPlugin(reactDefaultPropsPlugin, {
useDefaultPropsHelper,
})
.addPlugin(reactShapePlugin, {
anyAlias,
anyFunctionAlias,
})
.addPlugin(declareMissingClassPropertiesPlugin, { anyAlias })
.addPlugin(memberAccessibilityPlugin, {
defaultAccessibility,
privateRegex,
protectedRegex,
publicRegex,
})
.addPlugin(explicitAnyPlugin, { anyAlias })
// We need to run eslint-fix before ts-ignore because formatting may affect where
// the errors are that need to get ignored.
.addPlugin(eslintFixPlugin, {})
.addPlugin(tsIgnorePlugin, {})
// We need to run eslint-fix again after ts-ignore to fix up formatting.
.addPlugin(eslintFixPlugin, {})
);
}