diff --git a/types/jscodeshift/index.d.ts b/types/jscodeshift/index.d.ts new file mode 100644 index 0000000000..e19423d928 --- /dev/null +++ b/types/jscodeshift/index.d.ts @@ -0,0 +1,9 @@ +// Type definitions for jscodeshift 0.6 +// Project: https://github.com/facebook/jscodeshift#readme +// Definitions by: Brie Bunge +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.9 + +import core = require("./src/core"); + +export = core; diff --git a/types/jscodeshift/package.json b/types/jscodeshift/package.json new file mode 100644 index 0000000000..a501c0f28d --- /dev/null +++ b/types/jscodeshift/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "dependencies": { + "ast-types": "0.12.1", + "recast": "0.17.2" + } +} diff --git a/types/jscodeshift/src/Collection.d.ts b/types/jscodeshift/src/Collection.d.ts new file mode 100644 index 0000000000..2cab87b153 --- /dev/null +++ b/types/jscodeshift/src/Collection.d.ts @@ -0,0 +1,131 @@ +import recast = require("recast"); +import JSXElement = require("./collections/JSXElement"); +import NodeCollection = require("./collections/Node"); +import VariableDeclarator = require("./collections/VariableDeclarator"); + +type ASTPath = recast.NodePath; + +export interface Collection + extends NodeCollection.TraversalMethods, + NodeCollection.MutationMethods, + VariableDeclarator.GlobalMethods, + VariableDeclarator.TransformMethods, + JSXElement.GlobalMethods, + JSXElement.TraversalMethods { + /** + * @param paths An array of AST paths + * @param parent A parent collection + * @param types An array of types all the paths in the collection + * have in common. If not passed, it will be inferred from the paths. + */ + new (paths: Array>, parent: Collection, types?: Array>): this; + + /** + * Returns a new collection containing the nodes for which the callback returns true. + */ + filter( + callback: (path: ASTPath, i: number, paths: Array>) => path is ASTPath + ): Collection; + filter( + callback: (path: ASTPath, i: number, paths: Array>) => boolean + ): Collection; + + /** + * Executes callback for each node/path in the collection. + */ + forEach(callback: (path: ASTPath, i: number, paths: Array>) => void): this; + + /** + * Tests whether at-least one path passes the test implemented by the provided callback. + */ + some(callback: (path: ASTPath, i: number, paths: Array>) => boolean): boolean; + + /** + * Tests whether all paths pass the test implemented by the provided callback. + */ + every(callback: (path: ASTPath, i: number, paths: Array>) => boolean): boolean; + + /** + * Executes the callback for every path in the collection and returns a new + * collection from the return values (which must be paths). + * + * The callback can return null to indicate to exclude the element from the + * new collection. + * + * If an array is returned, the array will be flattened into the result + * collection. + * + * @param callback + * @param type Force the new collection to be of a specific type + */ + map( + callback: ( + path: ASTPath, + i: number, + paths: Array> + ) => ASTPath | Array> | null | undefined, + type: recast.Type + ): Collection; + + /** Returns the number of elements in this collection. */ + size(): number; + + /** Returns the number of elements in this collection. */ + length: number; + + /** Returns an array of AST nodes in this collection. */ + nodes(): N[]; + + /** Returns an array of ASTPaths in this this collection. */ + paths(): Array>; + + getAST(): Array>; + + /** + * Converts the AST back to a string, using recast. + * @param options directly passed to recast's printer + */ + toSource(options?: recast.Options): string; + + /** + * Returns a new collection containing only the element at position index. + * In case of a negative index, the element is taken from the end: + * .at(0) - first element + * .at(-1) - last element + */ + at(index: number): Collection; + + /** Calls "get" on the first path (same as "collection.paths(0).get(...)"). */ + get(...fields: Array): any; + + /** + * Returns the type(s) of the collection. This is only used for unit tests, + * don't think other consumers would need it. + */ + getTypes(): string[]; + + /** + * Returns true if this collection has the type 'type'. + */ + isOfType(type: recast.Type): boolean; +} + +export function fromPaths(...args: any[]): any; + +export function fromNodes(...args: any[]): any; + +/** + * This function adds the provided methods to the prototype of the corresponding + * typed collection. If no type is passed, the methods are added to + * Collection.prototype and are available for all collections. + * + * @param methods Methods to add to the prototype + * @param type Optional type to add the methods to + */ +export function registerMethods(methods: object, type?: recast.Type): void; + +export function hasConflictingRegistration(...args: any[]): any; + +export function setDefaultCollectionType(...args: any[]): any; + +export {}; // shut off automatic exporting diff --git a/types/jscodeshift/src/collections/JSXElement.d.ts b/types/jscodeshift/src/collections/JSXElement.d.ts new file mode 100644 index 0000000000..0490e40f76 --- /dev/null +++ b/types/jscodeshift/src/collections/JSXElement.d.ts @@ -0,0 +1,71 @@ +import nodes = require("ast-types/gen/nodes"); +import recast = require("recast"); +import Collection = require("../Collection"); + +type ASTPath = recast.NodePath; +type JSXElement = nodes.JSXElement; + +export interface GlobalMethods { + /** + * Finds all JSXElements optionally filtered by name + */ + findJSXElements(name?: string): Collection.Collection; + + /** + * Finds all JSXElements by module name. Given + * + * var Bar = require('Foo'); + * + * + * findJSXElementsByModuleName('Foo') will find , without having to + * know the variable name. + */ + findJSXElementsByModuleName(moduleName: string): Collection.Collection; +} + +type Defined = T extends undefined ? never : T; +type JSXElementChild = Defined[0]; + +export interface TraversalMethods { + /** + * Returns all child nodes, including literals and expressions. + * This method only applies to JSXElement typed collections. + */ + childNodes(): Collection.Collection; + + /** + * Returns all children that are JSXElements. + * This method only applies to JSXElement typed collections. + */ + childElements(): Collection.Collection; +} + +interface Filter { + (path: ASTPath): boolean; +} + +export interface FilterMethods { + /** + * Filter method for attributes. + */ + hasAttributes(attributeFilter: { [attributeName: string]: any }): Filter; + + /** + * Filter elements which contain a specific child type + */ + hasChildren(name: string): Filter; +} + +export interface MappingMethods { + /** + * Given a JSXElement, returns its "root" name. E.g. it would return "Foo" for + * both and . + */ + getRootName(path: ASTPath): string; +} + +export function register(): void; +export const filters: FilterMethods; +export const mappings: MappingMethods; + +export {}; // shut off automatic exporting diff --git a/types/jscodeshift/src/collections/Node.d.ts b/types/jscodeshift/src/collections/Node.d.ts new file mode 100644 index 0000000000..d1e74f314d --- /dev/null +++ b/types/jscodeshift/src/collections/Node.d.ts @@ -0,0 +1,57 @@ +import nodes = require("ast-types/gen/nodes"); +import recast = require("recast"); +import Collection = require("../Collection"); + +type ASTPath = recast.NodePath; + +export interface TraversalMethods { + /** + * Find nodes of a specific type within the nodes of this collection. + */ + find(type: recast.Type, filter?: ((value: any) => boolean) | object): Collection.Collection; + + /** + * Returns a collection containing the paths that create the scope of the + * currently selected paths. Dedupes the paths. + */ + closestScope(): Collection.Collection; + + /** + * Traverse the AST up and finds the closest node of the provided type. + */ + closest(type: recast.Type, filter?: any): Collection.Collection; + + /** + * Finds the declaration for each selected path. Useful for member expressions + * or JSXElements. Expects a callback function that maps each path to the name + * to look for. + * + * If the callback returns a falsy value, the element is skipped. + */ + getVariableDeclarators(nameGetter: (...args: any[]) => any): Collection.Collection; +} + +export interface MutationMethods { + /** + * Simply replaces the selected nodes with the provided node. If a function + * is provided it is executed for every node and the node is replaced with the + * functions return value. + */ + replaceWith(nodes: T | T[] | ((path: ASTPath, i: number) => T)): Collection.Collection; + + /** + * Inserts a new node before the current one. + */ + insertBefore(insert: any): Collection.Collection; + + /** + * Inserts a new node after the current one. + */ + insertAfter(insert: any): Collection.Collection; + + remove(): Collection.Collection; +} + +export function register(): void; + +export {}; // shut off automatic exporting diff --git a/types/jscodeshift/src/collections/VariableDeclarator.d.ts b/types/jscodeshift/src/collections/VariableDeclarator.d.ts new file mode 100644 index 0000000000..eb7c8bcdfd --- /dev/null +++ b/types/jscodeshift/src/collections/VariableDeclarator.d.ts @@ -0,0 +1,39 @@ +import nodes = require("ast-types/gen/nodes"); +import recast = require("recast"); +import Collection = require("../Collection"); + +type ASTPath = recast.NodePath; + +export interface GlobalMethods { + /** + * Finds all variable declarators, optionally filtered by name. + */ + findVariableDeclarators(name?: string): Collection.Collection; +} + +export interface TransformMethods { + /** + * Renames a variable and all its occurrences. + * This method only applies to VariableDeclarator typed collections. + */ + renameTo(newName: string): Collection.Collection; +} + +interface Filter { + (path: ASTPath): boolean; +} + +export interface FilterMethods { + /** + * Returns a function that returns true if the provided path is a variable + * declarator and requires one of the specified module names. + * + * @param names A module name or an array of module names + */ + requiresModule(names: string | string[]): Filter; +} + +export function register(): void; +export const filters: FilterMethods; + +export {}; // shut off automatic exporting diff --git a/types/jscodeshift/src/core.d.ts b/types/jscodeshift/src/core.d.ts new file mode 100644 index 0000000000..9b9f48305d --- /dev/null +++ b/types/jscodeshift/src/core.d.ts @@ -0,0 +1,363 @@ +import nodes = require("ast-types/gen/nodes"); +import recast = require("recast"); +import Collection = require("./Collection"); +import template = require("./template"); +import VariableDeclarator = require("./collections/VariableDeclarator"); +import JSXElement = require("./collections/JSXElement"); + +declare namespace core { + type Parser = recast.Parser; + + interface Filters { + JSXElement: JSXElement.FilterMethods; + VariableDeclarator: VariableDeclarator.FilterMethods; + } + + interface Mappings { + JSXElement: JSXElement.MappingMethods; + } + + interface Plugin { + (core: Core): void; + } + + interface FileInfo { + /** The absolute path to the current file. */ + path: string; + /** The source code of the current file. */ + source: string; + } + + interface Stats { + /** + * Helper function to collect data during --dry runs. + * This function keeps a counter for how often it was called with a specific argument. + * The result is shown in the console. Useful for finding out how many files match a criterion. + */ + (name: string, quantity?: number): void; + } + + type ASTPath = recast.NodePath; + + interface Core { + (source: string, options?: Options): Collection.Collection; + (source: ASTNode | ASTNode[] | ASTPath | ASTPath[]): Collection.Collection; + + registerMethods: typeof Collection.registerMethods; + + types: typeof recast.default.types; + + match( + path: ASTNode | ASTPath, + filter: ((path: ASTNode) => boolean) | ASTNode + ): boolean; + + /** template, bound to default parser */ + template: template.Template; + + filters: Filters; + + mappings: Mappings; + + /** + * Utility function for registering plugins. + * + * Plugins are simple functions that are passed the core jscodeshift instance. + * They should extend jscodeshift by calling `registerMethods`, etc. + * This method guards against repeated registrations (the plugin callback will only be called once). + */ + use(plugin: Plugin): void; + + /** + * Returns a version of the core jscodeshift function "bound" to a specific + * parser. + */ + withParser(parser: string | Parser): JSCodeshift; + } + + type JSCodeshift = Core & recast.NamedTypes & recast.Builders; + + interface API { + j: JSCodeshift; + jscodeshift: JSCodeshift; + stats: Stats; + report: (msg: string) => void; + } + + interface Options { + [option: string]: any; + } + + interface Transform { + /** + * If a string is returned and it is different from passed source, the transform is considered to be successful. + * If a string is returned but it's the same as the source, the transform is considered to be unsuccessful. + * If nothing is returned, the file is not supposed to be transformed (which is ok). + */ + (file: FileInfo, api: API, options: Options): string | null | undefined | void; + } + + type ASTNode = nodes.ASTNode; + + type AnyTypeAnnotation = nodes.AnyTypeAnnotation; + type ArrayExpression = nodes.ArrayExpression; + type ArrayPattern = nodes.ArrayPattern; + type ArrayTypeAnnotation = nodes.ArrayTypeAnnotation; + type ArrowFunctionExpression = nodes.ArrowFunctionExpression; + type AssignmentExpression = nodes.AssignmentExpression; + type AssignmentPattern = nodes.AssignmentPattern; + type AwaitExpression = nodes.AwaitExpression; + type BigIntLiteral = nodes.BigIntLiteral; + type BinaryExpression = nodes.BinaryExpression; + type BindExpression = nodes.BindExpression; + type Block = nodes.Block; + type BlockStatement = nodes.BlockStatement; + type BooleanLiteral = nodes.BooleanLiteral; + type BooleanLiteralTypeAnnotation = nodes.BooleanLiteralTypeAnnotation; + type BooleanTypeAnnotation = nodes.BooleanTypeAnnotation; + type BreakStatement = nodes.BreakStatement; + type CallExpression = nodes.CallExpression; + type CatchClause = nodes.CatchClause; + type ClassBody = nodes.ClassBody; + type ClassDeclaration = nodes.ClassDeclaration; + type ClassExpression = nodes.ClassExpression; + type ClassImplements = nodes.ClassImplements; + type ClassMethod = nodes.ClassMethod; + type ClassPrivateMethod = nodes.ClassPrivateMethod; + type ClassPrivateProperty = nodes.ClassPrivateProperty; + type ClassProperty = nodes.ClassProperty; + type ClassPropertyDefinition = nodes.ClassPropertyDefinition; + type Comment = nodes.Comment; + type CommentBlock = nodes.CommentBlock; + type CommentLine = nodes.CommentLine; + type ComprehensionBlock = nodes.ComprehensionBlock; + type ComprehensionExpression = nodes.ComprehensionExpression; + type ConditionalExpression = nodes.ConditionalExpression; + type ContinueStatement = nodes.ContinueStatement; + type DebuggerStatement = nodes.DebuggerStatement; + type Declaration = nodes.Declaration; + type DeclareClass = nodes.DeclareClass; + type DeclaredPredicate = nodes.DeclaredPredicate; + type DeclareExportAllDeclaration = nodes.DeclareExportAllDeclaration; + type DeclareExportDeclaration = nodes.DeclareExportDeclaration; + type DeclareFunction = nodes.DeclareFunction; + type DeclareInterface = nodes.DeclareInterface; + type DeclareModule = nodes.DeclareModule; + type DeclareModuleExports = nodes.DeclareModuleExports; + type DeclareOpaqueType = nodes.DeclareOpaqueType; + type DeclareTypeAlias = nodes.DeclareTypeAlias; + type DeclareVariable = nodes.DeclareVariable; + type Decorator = nodes.Decorator; + type Directive = nodes.Directive; + type DirectiveLiteral = nodes.DirectiveLiteral; + type DoExpression = nodes.DoExpression; + type DoWhileStatement = nodes.DoWhileStatement; + type EmptyStatement = nodes.EmptyStatement; + type EmptyTypeAnnotation = nodes.EmptyTypeAnnotation; + type ExistentialTypeParam = nodes.ExistentialTypeParam; + type ExistsTypeAnnotation = nodes.ExistsTypeAnnotation; + type ExportAllDeclaration = nodes.ExportAllDeclaration; + type ExportBatchSpecifier = nodes.ExportBatchSpecifier; + type ExportDeclaration = nodes.ExportDeclaration; + type ExportDefaultDeclaration = nodes.ExportDefaultDeclaration; + type ExportDefaultSpecifier = nodes.ExportDefaultSpecifier; + type ExportNamedDeclaration = nodes.ExportNamedDeclaration; + type ExportNamespaceSpecifier = nodes.ExportNamespaceSpecifier; + type ExportSpecifier = nodes.ExportSpecifier; + type Expression = nodes.Expression; + type ExpressionStatement = nodes.ExpressionStatement; + type File = nodes.File; + type Flow = nodes.Flow; + type FlowPredicate = nodes.FlowPredicate; + type FlowType = nodes.FlowType; + type ForAwaitStatement = nodes.ForAwaitStatement; + type ForInStatement = nodes.ForInStatement; + type ForOfStatement = nodes.ForOfStatement; + type ForStatement = nodes.ForStatement; + type Function = nodes.Function; + type FunctionDeclaration = nodes.FunctionDeclaration; + type FunctionExpression = nodes.FunctionExpression; + type FunctionTypeAnnotation = nodes.FunctionTypeAnnotation; + type FunctionTypeParam = nodes.FunctionTypeParam; + type GeneratorExpression = nodes.GeneratorExpression; + type GenericTypeAnnotation = nodes.GenericTypeAnnotation; + type Identifier = nodes.Identifier; + type IfStatement = nodes.IfStatement; + type Import = nodes.Import; + type ImportDeclaration = nodes.ImportDeclaration; + type ImportDefaultSpecifier = nodes.ImportDefaultSpecifier; + type ImportNamespaceSpecifier = nodes.ImportNamespaceSpecifier; + type ImportSpecifier = nodes.ImportSpecifier; + type InferredPredicate = nodes.InferredPredicate; + type InterfaceDeclaration = nodes.InterfaceDeclaration; + type InterfaceExtends = nodes.InterfaceExtends; + type InterfaceTypeAnnotation = nodes.InterfaceTypeAnnotation; + type InterpreterDirective = nodes.InterpreterDirective; + type IntersectionTypeAnnotation = nodes.IntersectionTypeAnnotation; + type JSXAttribute = nodes.JSXAttribute; + type JSXClosingElement = nodes.JSXClosingElement; + type JSXClosingFragment = nodes.JSXClosingFragment; + type JSXElement = nodes.JSXElement; + type JSXEmptyExpression = nodes.JSXEmptyExpression; + type JSXExpressionContainer = nodes.JSXExpressionContainer; + type JSXFragment = nodes.JSXFragment; + type JSXIdentifier = nodes.JSXIdentifier; + type JSXMemberExpression = nodes.JSXMemberExpression; + type JSXNamespacedName = nodes.JSXNamespacedName; + type JSXOpeningElement = nodes.JSXOpeningElement; + type JSXOpeningFragment = nodes.JSXOpeningFragment; + type JSXSpreadAttribute = nodes.JSXSpreadAttribute; + type JSXSpreadChild = nodes.JSXSpreadChild; + type JSXText = nodes.JSXText; + type LabeledStatement = nodes.LabeledStatement; + type Line = nodes.Line; + type Literal = nodes.Literal; + type LogicalExpression = nodes.LogicalExpression; + type MemberExpression = nodes.MemberExpression; + type MemberTypeAnnotation = nodes.MemberTypeAnnotation; + type MetaProperty = nodes.MetaProperty; + type MethodDefinition = nodes.MethodDefinition; + type MixedTypeAnnotation = nodes.MixedTypeAnnotation; + type ModuleSpecifier = nodes.ModuleSpecifier; + type NewExpression = nodes.NewExpression; + type Node = nodes.Node; + type Noop = nodes.Noop; + type NullableTypeAnnotation = nodes.NullableTypeAnnotation; + type NullLiteral = nodes.NullLiteral; + type NullLiteralTypeAnnotation = nodes.NullLiteralTypeAnnotation; + type NullTypeAnnotation = nodes.NullTypeAnnotation; + type NumberLiteralTypeAnnotation = nodes.NumberLiteralTypeAnnotation; + type NumberTypeAnnotation = nodes.NumberTypeAnnotation; + type NumericLiteral = nodes.NumericLiteral; + type NumericLiteralTypeAnnotation = nodes.NumericLiteralTypeAnnotation; + type ObjectExpression = nodes.ObjectExpression; + type ObjectMethod = nodes.ObjectMethod; + type ObjectPattern = nodes.ObjectPattern; + type ObjectProperty = nodes.ObjectProperty; + type ObjectTypeAnnotation = nodes.ObjectTypeAnnotation; + type ObjectTypeCallProperty = nodes.ObjectTypeCallProperty; + type ObjectTypeIndexer = nodes.ObjectTypeIndexer; + type ObjectTypeInternalSlot = nodes.ObjectTypeInternalSlot; + type ObjectTypeProperty = nodes.ObjectTypeProperty; + type ObjectTypeSpreadProperty = nodes.ObjectTypeSpreadProperty; + type OpaqueType = nodes.OpaqueType; + type OptionalCallExpression = nodes.OptionalCallExpression; + type OptionalMemberExpression = nodes.OptionalMemberExpression; + type ParenthesizedExpression = nodes.ParenthesizedExpression; + type Pattern = nodes.Pattern; + type Position = nodes.Position; + type Printable = nodes.Printable; + type PrivateName = nodes.PrivateName; + type Program = nodes.Program; + type Property = nodes.Property; + type PropertyPattern = nodes.PropertyPattern; + type QualifiedTypeIdentifier = nodes.QualifiedTypeIdentifier; + type RegExpLiteral = nodes.RegExpLiteral; + type RestElement = nodes.RestElement; + type RestProperty = nodes.RestProperty; + type ReturnStatement = nodes.ReturnStatement; + type SequenceExpression = nodes.SequenceExpression; + type SourceLocation = nodes.SourceLocation; + type Specifier = nodes.Specifier; + type SpreadElement = nodes.SpreadElement; + type SpreadElementPattern = nodes.SpreadElementPattern; + type SpreadProperty = nodes.SpreadProperty; + type SpreadPropertyPattern = nodes.SpreadPropertyPattern; + type Statement = nodes.Statement; + type StringLiteral = nodes.StringLiteral; + type StringLiteralTypeAnnotation = nodes.StringLiteralTypeAnnotation; + type StringTypeAnnotation = nodes.StringTypeAnnotation; + type Super = nodes.Super; + type SwitchCase = nodes.SwitchCase; + type SwitchStatement = nodes.SwitchStatement; + type TaggedTemplateExpression = nodes.TaggedTemplateExpression; + type TemplateElement = nodes.TemplateElement; + type TemplateLiteral = nodes.TemplateLiteral; + type ThisExpression = nodes.ThisExpression; + type ThisTypeAnnotation = nodes.ThisTypeAnnotation; + type ThrowStatement = nodes.ThrowStatement; + type TryStatement = nodes.TryStatement; + type TSAnyKeyword = nodes.TSAnyKeyword; + type TSArrayType = nodes.TSArrayType; + type TSAsExpression = nodes.TSAsExpression; + type TSBooleanKeyword = nodes.TSBooleanKeyword; + type TSCallSignatureDeclaration = nodes.TSCallSignatureDeclaration; + type TSConditionalType = nodes.TSConditionalType; + type TSConstructorType = nodes.TSConstructorType; + type TSConstructSignatureDeclaration = nodes.TSConstructSignatureDeclaration; + type TSDeclareFunction = nodes.TSDeclareFunction; + type TSDeclareMethod = nodes.TSDeclareMethod; + type TSEnumDeclaration = nodes.TSEnumDeclaration; + type TSEnumMember = nodes.TSEnumMember; + type TSExportAssignment = nodes.TSExportAssignment; + type TSExpressionWithTypeArguments = nodes.TSExpressionWithTypeArguments; + type TSExternalModuleReference = nodes.TSExternalModuleReference; + type TSFunctionType = nodes.TSFunctionType; + type TSHasOptionalTypeAnnotation = nodes.TSHasOptionalTypeAnnotation; + type TSHasOptionalTypeParameters = nodes.TSHasOptionalTypeParameters; + type TSImportEqualsDeclaration = nodes.TSImportEqualsDeclaration; + type TSIndexedAccessType = nodes.TSIndexedAccessType; + type TSIndexSignature = nodes.TSIndexSignature; + type TSInferType = nodes.TSInferType; + type TSInterfaceBody = nodes.TSInterfaceBody; + type TSInterfaceDeclaration = nodes.TSInterfaceDeclaration; + type TSIntersectionType = nodes.TSIntersectionType; + type TSLiteralType = nodes.TSLiteralType; + type TSMappedType = nodes.TSMappedType; + type TSMethodSignature = nodes.TSMethodSignature; + type TSModuleBlock = nodes.TSModuleBlock; + type TSModuleDeclaration = nodes.TSModuleDeclaration; + type TSNamespaceExportDeclaration = nodes.TSNamespaceExportDeclaration; + type TSNeverKeyword = nodes.TSNeverKeyword; + type TSNonNullExpression = nodes.TSNonNullExpression; + type TSNullKeyword = nodes.TSNullKeyword; + type TSNumberKeyword = nodes.TSNumberKeyword; + type TSObjectKeyword = nodes.TSObjectKeyword; + type TSOptionalType = nodes.TSOptionalType; + type TSParameterProperty = nodes.TSParameterProperty; + type TSParenthesizedType = nodes.TSParenthesizedType; + type TSPropertySignature = nodes.TSPropertySignature; + type TSQualifiedName = nodes.TSQualifiedName; + type TSRestType = nodes.TSRestType; + type TSStringKeyword = nodes.TSStringKeyword; + type TSSymbolKeyword = nodes.TSSymbolKeyword; + type TSThisType = nodes.TSThisType; + type TSTupleType = nodes.TSTupleType; + type TSType = nodes.TSType; + type TSTypeAliasDeclaration = nodes.TSTypeAliasDeclaration; + type TSTypeAnnotation = nodes.TSTypeAnnotation; + type TSTypeAssertion = nodes.TSTypeAssertion; + type TSTypeLiteral = nodes.TSTypeLiteral; + type TSTypeOperator = nodes.TSTypeOperator; + type TSTypeParameter = nodes.TSTypeParameter; + type TSTypeParameterDeclaration = nodes.TSTypeParameterDeclaration; + type TSTypeParameterInstantiation = nodes.TSTypeParameterInstantiation; + type TSTypePredicate = nodes.TSTypePredicate; + type TSTypeQuery = nodes.TSTypeQuery; + type TSTypeReference = nodes.TSTypeReference; + type TSUndefinedKeyword = nodes.TSUndefinedKeyword; + type TSUnionType = nodes.TSUnionType; + type TSUnknownKeyword = nodes.TSUnknownKeyword; + type TSVoidKeyword = nodes.TSVoidKeyword; + type TupleTypeAnnotation = nodes.TupleTypeAnnotation; + type TypeAlias = nodes.TypeAlias; + type TypeAnnotation = nodes.TypeAnnotation; + type TypeCastExpression = nodes.TypeCastExpression; + type TypeofTypeAnnotation = nodes.TypeofTypeAnnotation; + type TypeParameter = nodes.TypeParameter; + type TypeParameterDeclaration = nodes.TypeParameterDeclaration; + type TypeParameterInstantiation = nodes.TypeParameterInstantiation; + type UnaryExpression = nodes.UnaryExpression; + type UnionTypeAnnotation = nodes.UnionTypeAnnotation; + type UpdateExpression = nodes.UpdateExpression; + type VariableDeclaration = nodes.VariableDeclaration; + type VariableDeclarator = nodes.VariableDeclarator; + type Variance = nodes.Variance; + type VoidTypeAnnotation = nodes.VoidTypeAnnotation; + type WhileStatement = nodes.WhileStatement; + type WithStatement = nodes.WithStatement; + type YieldExpression = nodes.YieldExpression; +} + +declare const core: core.JSCodeshift; +export = core; diff --git a/types/jscodeshift/src/template.d.ts b/types/jscodeshift/src/template.d.ts new file mode 100644 index 0000000000..cfc0a3c543 --- /dev/null +++ b/types/jscodeshift/src/template.d.ts @@ -0,0 +1,18 @@ +import recast = require("recast"); + +export = template; + +declare const template: typeof withParser; + +declare function withParser(parser: recast.Parser): template.Template; + +declare namespace template { + interface Template { + /** Tagged template function. Parses the string as source and returns an array of Statement AST nodes. */ + statements(...args: any[]): any; + /** Tagged template function. Parses the string as source and returns an Statement AST node. */ + statement(...args: any[]): any; + /** Tagged template function. Parses the string as source and returns an Expression AST node. */ + expression(...args: any[]): any; + } +} diff --git a/types/jscodeshift/test/js-transforms/README.md b/types/jscodeshift/test/js-transforms/README.md new file mode 100644 index 0000000000..f314daaa44 --- /dev/null +++ b/types/jscodeshift/test/js-transforms/README.md @@ -0,0 +1,2 @@ +Sample jscodeshift transforms taken from https://github.com/jhgg/js-transforms +and converted to TypeScript, for use as real-world test cases. diff --git a/types/jscodeshift/test/js-transforms/bind-this-to-bind-expression.ts b/types/jscodeshift/test/js-transforms/bind-this-to-bind-expression.ts new file mode 100644 index 0000000000..1c6d8cda56 --- /dev/null +++ b/types/jscodeshift/test/js-transforms/bind-this-to-bind-expression.ts @@ -0,0 +1,25 @@ +/** + * This transform converts stuff like: + * + * let x = this.foo.bind(this); + * + * to + * + * let x = ::this.foo; + * + */ +import j = require("jscodeshift"); + +const transform: j.Transform = (file, api) => { + const j = api.jscodeshift; + return j(file.source) + // Find stuff that looks like this.xyz.bind(this) + .find(j.CallExpression, {callee: {object: {object: j.ThisExpression}, property: {name: 'bind'}}}) + // Ensure that .bind() is being called with only one argument, and that argument is "this". + .filter(p => p.value.arguments.length === 1 && p.value.arguments[0].type === "ThisExpression") + // We can now replace it with ::this.xyz + .replaceWith(p => j.bindExpression(null, (p.value.callee as j.MemberExpression).object)) + .toSource(); +}; + +export = transform; diff --git a/types/jscodeshift/test/js-transforms/call-expression-bind-this-to-arrow-function-expression.ts b/types/jscodeshift/test/js-transforms/call-expression-bind-this-to-arrow-function-expression.ts new file mode 100644 index 0000000000..09cdf3f29e --- /dev/null +++ b/types/jscodeshift/test/js-transforms/call-expression-bind-this-to-arrow-function-expression.ts @@ -0,0 +1,60 @@ +/** + * Converts + * onClick(function(a, b) { + * return a + b; + * }.bind(this), + * function(b, c) { + * return 1; + * }.bind(this)); + * + * onClick(function(a) { + * var a = 1; + * return a; + * }.bind(this)); + * + * var a = function(c) { return c; }.bind(this); + * + * to + * onClick((a, b) => a + b, + * (b, c) => 1); + * + * onClick(a => { + * var a = 1; + * return a; + * }); + * + * var a = c => c; + * + */ +import { Transform, MemberExpression, FunctionExpression, ArrowFunctionExpression } from "jscodeshift"; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + + return j(file.source) + // We're looking for a CallExpression that's calling .bind() onto a FunctionExpression. + .find(j.CallExpression, {callee: {property: {name: 'bind'}, object: {type: 'FunctionExpression'}}}) + // Verify that .bind() is only being called with `this` as it's sole arguments. + .filter(p => p.value.arguments.length === 1 && p.value.arguments[0].type === "ThisExpression") + .replaceWith(p => { + const callee = p.value.callee as MemberExpression; + const object = callee.object as FunctionExpression; + // Grab the function body. Since we looked for the CallExpression originally, the "callee.object" would refer + // to the FunctionExpression that's being called .bind(this) on. We need the body of that function + // to transform into an ArrowFunctionExpression. + let body: ArrowFunctionExpression['body'] = object.body; + // We can get a bit clever here. If we have a function that consists of a single return statement in it's body, + // we can transform it to the more compact arrowFunctionExpression (a, b) => a + b, vs (a + b) => { return a + b } + let useExpression = false; + if (body.type === 'BlockStatement' && body.body.length === 1) { + const first = body.body[0]; + if (first.type === "ReturnStatement") { + useExpression = true; + body = first.argument!; + } + } + + return j.arrowFunctionExpression(object.params, body, useExpression); + }) + .toSource(); +}; diff --git a/types/jscodeshift/test/js-transforms/function-expression-to-arrow-function-expression.ts b/types/jscodeshift/test/js-transforms/function-expression-to-arrow-function-expression.ts new file mode 100644 index 0000000000..b960c920cd --- /dev/null +++ b/types/jscodeshift/test/js-transforms/function-expression-to-arrow-function-expression.ts @@ -0,0 +1,53 @@ +/** + * Converts a FunctionExpression to an ArrowFunctionExpression when safe to do so. + * + * var a = function(a, b) { + * return a + b; + * } + * + * var b = function(a, b) { + * var c = 0; + * return a + b + c; + * } + * + * var a = function(a, b) { + * return a + b + this.c; + * } + * + * var a = (a, b) => a + b + * + * var b = (a, b) => { + * var c = 0; + * return a + b + c; + * } + * + * var a = function(a, b) { + * return a + b + this.c; + * } + */ +import { Transform, ArrowFunctionExpression } from "jscodeshift"; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + + return j(file.source) + .find(j.FunctionExpression) + // We check for this expression, as if it's in a function expression, we don't want to re-bind "this" by + // using the arrowFunctionExpression. As that could potentially have some unintended consequences. + .filter(p => j(p).find(j.ThisExpression).size() === 0) + .replaceWith(p => { + let body: ArrowFunctionExpression['body'] = p.value.body; + // We can get a bit clever here. If we have a function that consists of a single return statement in it's body, + // we can transform it to the more compact arrowFunctionExpression (a, b) => a + b, vs (a + b) => { return a + b } + let useExpression = false; + if (body.type === 'BlockStatement' && body.body.length === 1) { + const first = body.body[0]; + if (first.type === "ReturnStatement") { + useExpression = true; + body = first.argument!; + } + } + return j.arrowFunctionExpression(p.value.params, body, useExpression); + }) + .toSource(); +}; diff --git a/types/jscodeshift/test/js-transforms/props-to-destructuring.ts b/types/jscodeshift/test/js-transforms/props-to-destructuring.ts new file mode 100644 index 0000000000..db4caff58c --- /dev/null +++ b/types/jscodeshift/test/js-transforms/props-to-destructuring.ts @@ -0,0 +1,142 @@ +/** + * Transforms: + * class C extends React.Component() { + * render() { + * return
+ * } + * } + * + * To: + * + * class C extends React.Component() { + * render() { + * const { + * foo, + * bar + * } = this.props; + * + * return
+ * } + * } + * + */ +import { Transform, VariableDeclarator, ObjectPattern, Identifier } from "jscodeshift"; + +// tslint:disable-next-line max-line-length +const keywordsStr = 'this function if return var else for new in typeof while case break try catch delete throw switch continue default instanceof do void finally with debugger implements interface package private protected public static class enum export extends import super true false null abstract boolean byte char const double final float goto int long native short synchronized throws transient volatile'; +const keywords = keywordsStr.split(' ').reduce<{ [key: string]: boolean }>((f, k) => { + f[k] = true; + return f; +}, {}); +const isKeyword = (k: string) => keywords.hasOwnProperty(k); + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const {statement} = j.template; + + return j(file.source) + .find(j.FunctionExpression) + .replaceWith(p => { + // console.log(p); + const root = j(p.value); + const variablesToReplace: { [name: string]: boolean } = {}; + + // Figure out if the variable was defined from props, so that we can re-use that definition. + const isFromProps = (name: string, resolvedScope: any) => { + return resolvedScope.getBindings()[name].every( + (p: any) => { + const decl = j(p).closest(j.VariableDeclarator); + // What happens when our VariableDeclarator is too high up the parent AST? + + if (!decl.size()) return false; + const node: VariableDeclarator = decl.nodes()[0]; + + if (!(node.init != null && + node.init.type === 'MemberExpression' && + node.init.object.type === 'ThisExpression' && + node.init.property.type === 'Identifier' && + node.init.property.name === 'props')) + return false; + + // Check for the case where it could be aliased (i.e.) { baz: foo } = this.props; + // In this case, we won't do a substitution. + if (p.parentPath.value.type === 'Property' && p.parentPath.value.key.name !== name) + return false; + + return true; + } + ); + }; + + // Transform "this.props.xyz" to "xyz", and record what we've transformed. + // Transform as long as we don't have "xyz" already defined in the scope. + root + .find(j.MemberExpression, { + object: { + type: 'MemberExpression', + object: {type: 'ThisExpression'}, + property: {name: 'props'} + } + }) + .filter(e => { + const resolvedScope = e.scope.lookup((e.value.property as Identifier).name); + // If the scope is null, that means that this property isn't defined in the scope yet, + // and we can use it. Otherwise, if it is defined, we should see if it was defined from `this.props` + // if none of these cases are true, we can't do substitution. + return resolvedScope === null || isFromProps((e.value.property as Identifier).name, resolvedScope); + }) + // Ensure that our substitution won't cause us to define a keyword, i.e. `this.props.while` won't + // get converted into `while`. + .filter(p => !isKeyword((p.value.property as Identifier).name)) + // Now, do the replacement, `this.props.xyz` => `xyz`. + .replaceWith(p => p.value.property as Identifier) + // Finally, mark the variable as something we will need to define earlier in the function, + // if it's not already defined. + .forEach(p => { + // Is this prop already defined somewhere else. + if (!p.scope.lookup(p.value.name)) + variablesToReplace[p.value.name] = true; + }); + + // Create property definitions for variables that we've replaced. + const properties = Object.keys(variablesToReplace) + .sort() + .map(k => { + const prop = j.property('init', j.identifier(k), j.identifier(k)); + prop.shorthand = true; + return prop; + }); + + // We have no properties to inject, so we can bail here. + if (!properties.length) + return p.value; + + // See if we already have a VariableDeclarator like { a, b, c } = this.props; + const propDefinitions = root + .find(j.VariableDeclarator, { + id: {type: 'ObjectPattern'}, + init: {type: 'MemberExpression', object: {type: 'ThisExpression'}, property: {name: 'props'}} + }); + + if (propDefinitions.size()) { + const nodePath = propDefinitions.paths()[0]; + const node = nodePath.value; + // TODO(brieb): support narrowing based on second argument to `find` + const newPattern = j.objectPattern((node.id as ObjectPattern).properties.concat(properties)); + nodePath.replace(j.variableDeclarator(newPattern, node.init)); + return p.value; + } + + // Otherwise, we'll have to create our own, as none were suitable for use. + // Create the variable definition `const { xyz } = this.props;` + const decl = statement`const { ${properties} } = this.props;`; + + // Add the variable definition to the top of the function expression body. + return j.functionExpression( + p.value.id, + p.value.params, + j.blockStatement([decl].concat(p.value.body.body)) + ); + } + ).toSource(); +}; diff --git a/types/jscodeshift/test/js-transforms/pure-to-composite-component.ts b/types/jscodeshift/test/js-transforms/pure-to-composite-component.ts new file mode 100644 index 0000000000..6d1a30c378 --- /dev/null +++ b/types/jscodeshift/test/js-transforms/pure-to-composite-component.ts @@ -0,0 +1,64 @@ +/** For when you've gone too pure and want to go back. */ +/** + * Converts + * let HistoryItem = (props) => { + * const { + * item + * } = props; + * return
  • {item}
  • ; + * }; + * + * let X = (props) =>
    foo
    ; + * + * to + * + * class HistoryItem extends Component { + * render() { + * const { + * item + * } = this.props; + * return
  • {item}
  • ; + * } + * } + * + * class X extends Component { + * render() { + * return
    foo
    ; + * } + * } + */ + +import { Transform, ASTNode } from "jscodeshift"; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const {statement} = j.template; + + function hasJSXElement(ast: ASTNode) { + return j(ast).find(j.JSXElement).size() > 0; + } + + return j(file.source) + .find(j.VariableDeclaration) + .filter(p => p.value.declarations.length === 1) + .replaceWith(p => { + const decl = p.value.declarations[0]; + if (decl.type === "VariableDeclarator" && decl.init != null) { + if (decl.init.type !== 'ArrowFunctionExpression' || + (!hasJSXElement(decl.init.body) && decl.init.body.type !== "JSXElement")) + return p.value; + + let body: any = decl.init.body; + body = body.type === "JSXElement" ? j.returnStatement(body) : body = body.body; + + j(body) + .find(j.Identifier, {name: 'props'}) + .replaceWith(p => j.memberExpression(j.thisExpression(), j.identifier('props'))); + + return statement`class ${decl.id} extends Component { + render() { ${body} } + }`; + } + }) + .toSource(); +}; diff --git a/types/jscodeshift/test/jscodeshift-tests.ts b/types/jscodeshift/test/jscodeshift-tests.ts new file mode 100644 index 0000000000..f426eb37b7 --- /dev/null +++ b/types/jscodeshift/test/jscodeshift-tests.ts @@ -0,0 +1,75 @@ +import { ASTNode, FileInfo, API, Transform, Parser } from "jscodeshift"; + +// Can define transform with `function`. +function replaceWithFooTransform(fileInfo: FileInfo, api: API) { + return api + .jscodeshift(fileInfo.source) + .findVariableDeclarators("foo") + .renameTo("bar") + .toSource(); +} + +// Can define transform with arrow function, using `Transform` type. +const reverseIdentifiersTransform: Transform = (file, api) => { + const j = api.jscodeshift; + + return j(file.source) + .find(j.Identifier) + .forEach(path => { + j(path).replaceWith( + j.identifier( + path.node.name + .split("") + .reverse() + .join("") + ) + ); + }) + .toSource(); +}; + +// Can define a custom parser. +const parser: Parser = { + parse(source, options) { + // return estree compatible AST + return { type: "root" }; + } +}; + +// Can pass options to recast +const transformWithRecastFormattingOptions: Transform = (file, { j }) => { + return j(file.source).toSource({ quote: "single" }); +}; + +const transformWithRecastParseOptions: Transform = (file, { j }) => { + return j(file.source, { + /* ...passes options to recast's parse method... */ + }).toSource(); +}; + +// `ASTNode` supports type narrowing. +{ + const node = ({} as any) as ASTNode; + if (node.type === "CatchClause") { + // $ExpectType CatchClause + node; + + if (node.param && node.param.type === "Identifier") { + // $ExpectType Identifier + node.param; + + if ( + node.param.typeAnnotation && + node.param.typeAnnotation.type === "TSTypeAnnotation" + ) { + // $ExpectType TSTypeAnnotation + node.param.typeAnnotation; + + if (node.param.typeAnnotation.typeAnnotation.type === "TSArrayType") { + // $ExpectType TSArrayType + node.param.typeAnnotation.typeAnnotation; + } + } + } + } +} diff --git a/types/jscodeshift/tsconfig.json b/types/jscodeshift/tsconfig.json new file mode 100644 index 0000000000..5937bd483a --- /dev/null +++ b/types/jscodeshift/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "src/core.d.ts", + "src/Collection.d.ts", + "src/collections/JSXElement.d.ts", + "src/collections/Node.d.ts", + "src/collections/VariableDeclarator.d.ts", + "src/template.d.ts", + "test/jscodeshift-tests.ts", + "test/js-transforms/bind-this-to-bind-expression.ts", + "test/js-transforms/call-expression-bind-this-to-arrow-function-expression.ts", + "test/js-transforms/function-expression-to-arrow-function-expression.ts", + "test/js-transforms/props-to-destructuring.ts", + "test/js-transforms/pure-to-composite-component.ts" + ] +} diff --git a/types/jscodeshift/tslint.json b/types/jscodeshift/tslint.json new file mode 100644 index 0000000000..f93cf8562a --- /dev/null +++ b/types/jscodeshift/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "dtslint/dt.json" +}