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

Allow plugins to validate their options #112

Merged
merged 5 commits into from
May 7, 2021
Merged
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
2 changes: 2 additions & 0 deletions packages/ts-migrate-plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@
"dependencies": {
"eslint": "^7.14.0",
"jscodeshift": "^0.12.0",
"json-schema": "^0.3.0",
"ts-migrate-server": "^0.1.18",
"typescript": "4.2.4"
},
"gitHead": "7acf6067f15c9bb367cda9c47fcfb4203dcc54f3",
"devDependencies": {
"@ts-morph/bootstrap": "^0.9.1",
"@types/json-schema": "^7.0.7",
"jest": "26.6.3"
}
}
8 changes: 5 additions & 3 deletions packages/ts-migrate-plugins/src/plugins/add-conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import ts from 'typescript';
import { Plugin } from 'ts-migrate-server';
import { isDiagnosticWithLinePosition } from '../utils/type-guards';
import getTokenAtPosition from './utils/token-pos';
import { AnyAliasOptions, validateAnyAliasOptions } from '../utils/validateOptions';

type Options = {
anyAlias?: string;
};
type Options = AnyAliasOptions;

const supportedDiagnostics = new Set([
// TS2339: Property '{0}' does not exist on type '{1}'.
Expand All @@ -16,6 +15,7 @@ const supportedDiagnostics = new Set([

const addConversionsPlugin: Plugin<Options> = {
name: 'add-conversions',

run({ fileName, sourceFile, text, options, getLanguageService }) {
// Filter out diagnostics we care about.
const diags = getLanguageService()
Expand All @@ -31,6 +31,8 @@ const addConversionsPlugin: Plugin<Options> = {
const printer = ts.createPrinter();
return printer.printFile(newSourceFile);
},

validate: validateAnyAliasOptions,
};

export default addConversionsPlugin;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import jscodeshift, { ASTPath, ClassBody } from 'jscodeshift';
import { Plugin } from 'ts-migrate-server';
import { isDiagnosticWithLinePosition } from '../utils/type-guards';
import { AnyAliasOptions, validateAnyAliasOptions } from '../utils/validateOptions';

type Options = { anyAlias?: string };
type Options = AnyAliasOptions;

const j = jscodeshift.withParser('tsx');

const declareMissingClassPropertiesPlugin: Plugin<Options> = {
name: 'declare-missing-class-properties',

async run({ text, fileName, getLanguageService, options }) {
const diagnostics = getLanguageService()
.getSemanticDiagnostics(fileName)
Expand Down Expand Up @@ -82,6 +84,8 @@ const declareMissingClassPropertiesPlugin: Plugin<Options> = {

return root.toSource();
},

validate: validateAnyAliasOptions,
};

export default declareMissingClassPropertiesPlugin;
Expand Down
6 changes: 5 additions & 1 deletion packages/ts-migrate-plugins/src/plugins/explicit-any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import { Collection } from 'jscodeshift/src/Collection';
import ts from 'typescript';
import { Plugin } from 'ts-migrate-server';
import { isDiagnosticWithLinePosition } from '../utils/type-guards';
import { AnyAliasOptions, validateAnyAliasOptions } from '../utils/validateOptions';

type Options = { anyAlias?: string };
type Options = AnyAliasOptions;

const explicitAnyPlugin: Plugin<Options> = {
name: 'explicit-any',

run({ options, fileName, text, getLanguageService }) {
const semanticDiagnostics = getLanguageService().getSemanticDiagnostics(fileName);
const diagnostics = semanticDiagnostics
.filter(isDiagnosticWithLinePosition)
.filter((d) => d.category === ts.DiagnosticCategory.Error);
return withExplicitAny(text, diagnostics, options.anyAlias);
},

validate: validateAnyAliasOptions,
};

export default explicitAnyPlugin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import {
collectIdentifierNodes,
KnownDefinitionMap,
} from './utils/identifiers';
import { AnyAliasOptions, validateAnyAliasOptions } from '../utils/validateOptions';

type Options = {
anyAlias?: string;
};
type Options = AnyAliasOptions;

const hoistClassStaticsPlugin: Plugin<Options> = {
name: 'hoist-class-statics',

run({ sourceFile, text, options }) {
return hoistStaticClassProperties(sourceFile, text, options);
},

validate: validateAnyAliasOptions,
};

export default hoistClassStaticsPlugin;
Expand Down
25 changes: 24 additions & 1 deletion packages/ts-migrate-plugins/src/plugins/jsdoc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
/* eslint-disable no-bitwise */
import ts from 'typescript';
import { Plugin } from 'ts-migrate-server';
import {
AnyAliasOptions,
Properties,
anyAliasProperty,
createValidate,
} from '../utils/validateOptions';

type TypeMap = Record<string, TypeOptions>;

Expand Down Expand Up @@ -39,12 +45,27 @@ const defaultTypeMap: TypeMap = {

type Options = {
annotateReturns?: boolean;
anyAlias?: string;
typeMap?: TypeMap;
} & AnyAliasOptions;

const optionProperties: Properties = {
...anyAliasProperty,
annotateReturns: { type: 'boolean' },
typeMap: {
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: { tsName: { type: 'string' }, acceptsTypeParameters: { type: 'boolean' } },
additionalProperties: false,
},
],
},
};

const jsDocPlugin: Plugin<Options> = {
name: 'jsdoc',

run({ sourceFile, text, options }) {
const result = ts.transform(sourceFile, [jsDocTransformerFactory(options)]);
const newSourceFile = result.transformed[0];
Expand All @@ -54,6 +75,8 @@ const jsDocPlugin: Plugin<Options> = {
const printer = ts.createPrinter();
return printer.printFile(newSourceFile);
},

validate: createValidate(optionProperties),
};

export default jsDocPlugin;
Expand Down
39 changes: 37 additions & 2 deletions packages/ts-migrate-plugins/src/plugins/member-accessibility.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
/* eslint-disable no-bitwise */
import ts from 'typescript';
import { Plugin } from 'ts-migrate-server';
import { Plugin, PluginOptionsError } from 'ts-migrate-server';

import { Properties, validateOptions } from '../utils/validateOptions';

const accessibility = ['private' as const, 'protected' as const, 'public' as const];

type Options = {
defaultAccessibility?: 'private' | 'protected' | 'public';
defaultAccessibility?: typeof accessibility[number];
privateRegex?: string;
protectedRegex?: string;
publicRegex?: string;
};

const optionProperties: Properties = {
defaultAccessibility: { enum: accessibility },
privateRegex: { type: 'string' },
protectedRegex: { type: 'string' },
publicRegex: { type: 'string' },
};

const memberAccessibilityPlugin: Plugin<Options> = {
name: 'member-accessibility',

run({ sourceFile, text, options }) {
const result = ts.transform(sourceFile, [memberAccessibilityTransformerFactory(options)]);
const newSourceFile = result.transformed[0];
Expand All @@ -20,6 +32,29 @@ const memberAccessibilityPlugin: Plugin<Options> = {
const printer = ts.createPrinter();
return printer.printFile(newSourceFile);
},

validate(options: unknown): options is Options {
const valid = validateOptions(options, optionProperties);

if (valid) {
// Validate regex property syntax.
// This can't be covered by JSON schema.
const validOptions = options as Options;
accessibility.forEach((accessibility) => {
const key = `${accessibility}Regex` as const;
const value = validOptions[key];
if (value) {
try {
RegExp(value);
} catch (e) {
throw new PluginOptionsError(`${key}: ${e.message}`);
}
}
});
}

return true;
},
};

export default memberAccessibilityPlugin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@ import ts from 'typescript';
import { Plugin } from 'ts-migrate-server';
import { getReactComponentHeritageType, isReactClassComponent } from './utils/react';
import updateSourceText, { SourceTextUpdate } from '../utils/updateSourceText';
import { createValidate, Properties } from '../utils/validateOptions';

type Options = { force?: boolean };

const optionProperties: Properties = {
force: { type: 'boolean' },
};

const reactClassLifecycleMethodsPlugin: Plugin<Options> = {
name: 'react-class-lifecycle-methods',

run({ fileName, sourceFile, text, options }) {
return /\.tsx$/.test(fileName)
? annotateReactComponentLifecycleMethods(sourceFile, text, options.force)
: undefined;
},

validate: createValidate(optionProperties),
};

export default reactClassLifecycleMethodsPlugin;
Expand Down
6 changes: 5 additions & 1 deletion packages/ts-migrate-plugins/src/plugins/react-class-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
} from './utils/react';
import { collectIdentifiers } from './utils/identifiers';
import updateSourceText, { SourceTextUpdate } from '../utils/updateSourceText';
import { AnyAliasOptions, validateAnyAliasOptions } from '../utils/validateOptions';

type Options = { anyAlias?: string };
type Options = AnyAliasOptions;

const reactClassStatePlugin: Plugin<Options> = {
name: 'react-class-state',

async run({ fileName, sourceFile, options }) {
if (!fileName.endsWith('.tsx')) return undefined;

Expand Down Expand Up @@ -93,6 +95,8 @@ const reactClassStatePlugin: Plugin<Options> = {

return updateSourceText(sourceFile.text, updates);
},

validate: validateAnyAliasOptions,
};

export default reactClassStatePlugin;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import ts from 'typescript';
import { Plugin } from 'ts-migrate-server';
import updateSourceText, { SourceTextUpdate } from '../utils/updateSourceText';
import { createValidate, Properties } from '../utils/validateOptions';

type Options = {
useDefaultPropsHelper?: boolean;
};

const optionProperties: Properties = {
useDefaultPropsHelper: { type: 'boolean' },
};

/**
* At first, we are going to check is there any
* - `CompName.defaultProps = defaultPropsName;`
Expand All @@ -16,6 +21,7 @@ const WITH_DEFAULT_PROPS_HELPER = `WithDefaultProps`;

const reactDefaultPropsPlugin: Plugin<Options> = {
name: 'react-default-props',

run({ sourceFile, text, options }) {
const importDeclarations = sourceFile.statements.filter(ts.isImportDeclaration);
const expressionStatements = sourceFile.statements.filter(ts.isExpressionStatement);
Expand Down Expand Up @@ -344,6 +350,8 @@ const reactDefaultPropsPlugin: Plugin<Options> = {

return updateSourceText(text, updates);
},

validate: createValidate(optionProperties),
};

// the target project might not have this as an internal dependency in project.json
Expand Down
21 changes: 19 additions & 2 deletions packages/ts-migrate-plugins/src/plugins/react-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,33 @@ import updateSourceText, { SourceTextUpdate } from '../utils/updateSourceText';
import getTypeFromPropTypesObjectLiteral, { createPropsTypeNameGetter } from './utils/react-props';
import { getTextPreservingWhitespace } from './utils/text';
import { updateImports, DefaultImport, NamedImport } from './utils/imports';
import {
AnyAliasOptions,
AnyFunctionAliasOptions,
Properties,
anyAliasProperty,
anyFunctionAliasProperty,
createValidate,
} from '../utils/validateOptions';

type Options = {
anyAlias?: string;
anyFunctionAlias?: string;
shouldUpdateAirbnbImports?: boolean;
shouldKeepPropTypes?: boolean;
} & AnyAliasOptions &
AnyFunctionAliasOptions;

const optionProperties: Properties = {
...anyAliasProperty,
...anyFunctionAliasProperty,
shouldUpdateAirbnbImports: { type: 'boolean' },
shouldKeepPropTypes: { type: 'boolean' },
};

export type PropTypesIdentifierMap = { [property: string]: string };

const reactPropsPlugin: Plugin<Options> = {
name: 'react-props',

run({ fileName, sourceFile, options }) {
if (!fileName.endsWith('.tsx')) return undefined;

Expand Down Expand Up @@ -79,6 +94,8 @@ const reactPropsPlugin: Plugin<Options> = {
: [];
return updateSourceText(updatedSourceText, importUpdates);
},

validate: createValidate(optionProperties),
};

export default reactPropsPlugin;
Expand Down
15 changes: 14 additions & 1 deletion packages/ts-migrate-plugins/src/plugins/react-shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import path from 'path';
import { Plugin } from 'ts-migrate-server';
import getTypeFromPropTypesObjectLiteral from './utils/react-props';
import updateSourceText, { SourceTextUpdate } from '../utils/updateSourceText';
import {
AnyAliasOptions,
AnyFunctionAliasOptions,
anyAliasProperty,
anyFunctionAliasProperty,
createValidate,
} from '../utils/validateOptions';

type Options = { anyAlias?: string; anyFunctionAlias?: string };
type Options = AnyAliasOptions & AnyFunctionAliasOptions;

/**
* first we are checking if we have imports of `prop-types` or `react-validators`
* only if we have them - this file might have shapes
*/
const reactShapePlugin: Plugin<Options> = {
name: 'react-shape',

run({ fileName, sourceFile, options, text }) {
const baseName = path.basename(fileName);
const importDeclarations = sourceFile.statements.filter(ts.isImportDeclaration);
Expand Down Expand Up @@ -218,6 +226,11 @@ const reactShapePlugin: Plugin<Options> = {

return updateSourceText(text, updates);
},

validate: createValidate({
...anyAliasProperty,
...anyFunctionAliasProperty,
}),
};

function getTypeForTheShape(
Expand Down
Loading