Skip to content

Commit

Permalink
Merge pull request #112 from edsrzf/plugin-validate
Browse files Browse the repository at this point in the history
Allow plugins to validate their options
  • Loading branch information
edsrzf authored May 7, 2021
2 parents 010762b + 6319866 commit 45933b8
Show file tree
Hide file tree
Showing 20 changed files with 225 additions and 17 deletions.
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

0 comments on commit 45933b8

Please sign in to comment.