Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upGitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
| import jscodeshift, { Identifier, TSTypeAnnotation } from 'jscodeshift'; | |
| import { Collection } from 'jscodeshift/src/Collection'; | |
| import tsp from 'typescript/lib/protocol'; | |
| import { Plugin } from 'ts-migrate-server'; | |
| import { isDiagnosticWithLinePosition } from '../utils/type-guards'; | |
| type Options = { anyAlias?: string }; | |
| const explicitAnyPlugin: Plugin<Options> = { | |
| name: 'explicit-any', | |
| async run({ options, text, getDiagnostics }) { | |
| const { semanticDiagnostics } = await getDiagnostics(); | |
| const diagnostics = semanticDiagnostics | |
| .filter(isDiagnosticWithLinePosition) | |
| .filter((d) => d.category === 'error'); | |
| return withExplicitAny(text, diagnostics, options.anyAlias); | |
| }, | |
| }; | |
| export default explicitAnyPlugin; | |
| const j = jscodeshift.withParser('tsx'); | |
| function withExplicitAny( | |
| text: string, | |
| diagnostics: tsp.DiagnosticWithLinePosition[], | |
| anyAlias?: string, | |
| ): string { | |
| const root = j(text); | |
| const anyType = anyAlias != null ? j.tsTypeReference(j.identifier(anyAlias)) : j.tsAnyKeyword(); | |
| const typeAnnotation = j.tsTypeAnnotation(anyType); | |
| replaceTS7006( | |
| root, | |
| diagnostics.filter((diagnostic) => diagnostic.code === 7006), | |
| typeAnnotation, | |
| ); | |
| replaceTS7031( | |
| root, | |
| diagnostics.filter((diagnostic) => diagnostic.code === 7031), | |
| typeAnnotation, | |
| ); | |
| replaceTS7034( | |
| root, | |
| diagnostics.filter((diagnostic) => diagnostic.code === 7034), | |
| typeAnnotation, | |
| ); | |
| replaceTS2459( | |
| root, | |
| diagnostics.filter((diagnostic) => diagnostic.code === 2459), | |
| typeAnnotation, | |
| ); | |
| replaceTS2525( | |
| root, | |
| diagnostics.filter((diagnostic) => diagnostic.code === 2525), | |
| typeAnnotation, | |
| ); | |
| return root.toSource(); | |
| } | |
| // TS7006: "Parameter '{0}' implicitly has an '{1}' type." | |
| function replaceTS7006( | |
| root: Collection<any>, | |
| diagnostics: tsp.DiagnosticWithLinePosition[], | |
| typeAnnotation: TSTypeAnnotation, | |
| ) { | |
| diagnostics.forEach((diagnostic) => { | |
| root | |
| .find( | |
| j.Identifier, | |
| (node: any) => | |
| node.start === diagnostic.start && | |
| node.end === diagnostic.start + diagnostic.length && | |
| node.typeAnnotation == null, | |
| ) | |
| .forEach((path) => { | |
| // Special case for single-parameter arrow surrounding parens issue. | |
| if ( | |
| j.ArrowFunctionExpression.check(path.parent.node) && | |
| path.parent.node.params.length === 1 | |
| ) { | |
| const paramIndex = path.parent.node.params.indexOf(path.node); | |
| path.parent.replace( | |
| j.arrowFunctionExpression.from({ | |
| ...path.parent.node, | |
| params: [ | |
| ...path.parent.node.params.slice(0, paramIndex), | |
| j.identifier.from({ | |
| ...(path.parent.node.params[paramIndex] as Identifier), | |
| typeAnnotation, | |
| }), | |
| ...path.parent.node.params.slice(paramIndex + 1), | |
| ], | |
| }), | |
| ); | |
| } else { | |
| path.get('typeAnnotation').replace(typeAnnotation); | |
| } | |
| }); | |
| }); | |
| } | |
| // TS7031: "Binding element '{0}' implicitly has an '{1}' type." | |
| function replaceTS7031( | |
| root: Collection<any>, | |
| diagnostics: tsp.DiagnosticWithLinePosition[], | |
| typeAnnotation: TSTypeAnnotation, | |
| ) { | |
| const getParentObjectPattern = (path: any) => { | |
| let res = path; | |
| while ( | |
| res.parent && | |
| j.ObjectProperty.check(res.parent.value) && | |
| res.parent.parent && | |
| j.ObjectPattern.check(res.parent.parent.value) | |
| ) { | |
| res = res.parent.parent; | |
| } | |
| return res; | |
| }; | |
| diagnostics.forEach((diagnostic) => { | |
| root.find(j.ObjectPattern).forEach((path) => { | |
| if (path.node.typeAnnotation == null) { | |
| const propertyIndex = path.node.properties.findIndex( | |
| (property: any) => | |
| property.start === diagnostic.start && | |
| property.end === diagnostic.start + diagnostic.length && | |
| j.ObjectProperty.check(property), | |
| ); | |
| if (propertyIndex !== -1) { | |
| const objectPattern = getParentObjectPattern(path); | |
| if (objectPattern.node.typeAnnotation == null) { | |
| objectPattern.get('typeAnnotation').replace(typeAnnotation); | |
| } | |
| } | |
| } | |
| }); | |
| }); | |
| } | |
| // TS7034: Variable '{0}' implicitly has type '{1}' in some locations where its type cannot be determined. | |
| function replaceTS7034( | |
| root: Collection<any>, | |
| diagnostics: tsp.DiagnosticWithLinePosition[], | |
| typeAnnotation: TSTypeAnnotation, | |
| ) { | |
| diagnostics.forEach((diagnostic) => { | |
| root | |
| .find(j.Identifier) | |
| .filter( | |
| (path: any) => | |
| path.node.start === diagnostic.start && | |
| path.node.end === diagnostic.start + diagnostic.length && | |
| path.node.typeAnnotation == null, | |
| ) | |
| .forEach((path) => { | |
| if ( | |
| !j.ImportSpecifier.check(path.parent.node) && | |
| !j.ImportDefaultSpecifier.check(path.parent.node) && | |
| !j.ImportNamespaceSpecifier.check(path.parent.node) | |
| ) { | |
| path.get('typeAnnotation').replace(typeAnnotation); | |
| } | |
| }); | |
| }); | |
| } | |
| // TS2459: Type '{0}' has no property '{1}' and no string index signature. | |
| function replaceTS2459( | |
| root: Collection<any>, | |
| diagnostics: tsp.DiagnosticWithLinePosition[], | |
| typeAnnotation: TSTypeAnnotation, | |
| ) { | |
| diagnostics.forEach((diagnostic) => { | |
| root | |
| .find(j.Identifier) | |
| .filter( | |
| (path: any) => | |
| path.node.start === diagnostic.start && | |
| path.node.end === diagnostic.start + diagnostic.length && | |
| path.node.typeAnnotation == null, | |
| ) | |
| .forEach((path) => { | |
| let newNode = path.parentPath; | |
| // The error will only provide the location of the left hand side identifier | |
| // so we have to find the variable declarator by traveling back up | |
| while (newNode.parentPath && !j.VariableDeclarator.check(newNode.node)) { | |
| newNode = newNode.parentPath; | |
| } | |
| if (newNode.get('init')) { | |
| // init returns the right hand side identifier | |
| const rightHandSideNodePath = newNode.get('init'); | |
| const name = rightHandSideNodePath.getValueProperty('name'); | |
| let { scope } = rightHandSideNodePath; | |
| // we check if the current scope declares the identifier | |
| // if not we move up to the parent scope | |
| while (scope && scope.parent && !scope.declares(name)) { | |
| scope = scope.parent; | |
| } | |
| if (scope && scope.getBindings()[name]) { | |
| const binding = scope.getBindings()[name][0]; | |
| if ( | |
| j.AssignmentPattern.check(binding.parentPath.node) && | |
| j.ObjectExpression.check(binding.parentPath.node.right) && | |
| binding.parentPath.node.right.properties.length === 0 && | |
| binding.node.typeAnnotation == null | |
| ) { | |
| binding.get('typeAnnotation').replace(typeAnnotation); | |
| } | |
| } | |
| } | |
| }); | |
| }); | |
| } | |
| // TS2525: Initializer provides no value for this binding element and the binding element has no default value. | |
| function replaceTS2525( | |
| root: Collection<any>, | |
| diagnostics: tsp.DiagnosticWithLinePosition[], | |
| typeAnnotation: TSTypeAnnotation, | |
| ) { | |
| diagnostics.forEach((diagnostic) => { | |
| root | |
| .find(j.Identifier) | |
| .filter( | |
| (path: any) => | |
| path.node.start === diagnostic.start && | |
| path.node.end === diagnostic.start + diagnostic.length && | |
| path.node.typeAnnotation == null, | |
| ) | |
| .forEach((path) => { | |
| const potentialObjDestructionNode = path.parentPath.parentPath.parentPath; | |
| if ( | |
| j.ObjectPattern.check(potentialObjDestructionNode.node) && | |
| (j.AssignmentPattern.check(potentialObjDestructionNode.parentPath.node) || | |
| j.VariableDeclarator.check(potentialObjDestructionNode.parentPath.node)) && | |
| // to prevent adding a type to the obj destruction inside of the destruction | |
| !j.ObjectProperty.check(potentialObjDestructionNode.parentPath.parentPath.node) && | |
| potentialObjDestructionNode.node.typeAnnotation == null | |
| ) { | |
| potentialObjDestructionNode.get('typeAnnotation').replace(typeAnnotation); | |
| } | |
| }); | |
| }); | |
| } |