diff --git a/types/jscodeshift/index.d.ts b/types/jscodeshift/index.d.ts index 7d1fe12f72..3e91d382ba 100644 --- a/types/jscodeshift/index.d.ts +++ b/types/jscodeshift/index.d.ts @@ -1,39 +1,429 @@ // Type definitions for jscodeshift 0.6 // Project: https://github.com/facebook/jscodeshift#readme -// Definitions by: My Self +// Definitions by: Brie Bunge // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.9 -/*~ If this module is a UMD module that exposes a global variable 'myLib' when - *~ loaded outside a module loader environment, declare that global here. - *~ Otherwise, delete this declaration. - */ -export as namespace myLib; +declare module "jscodeshift" { + import { Collection, registerMethods } from "jscodeshift/src/Collection"; + import * as JSXElement from "jscodeshift/src/collections/JSXElement"; + import * as VariableDeclarator from "jscodeshift/src/collections/VariableDeclarator"; + import { Template } from "jscodeshift/src/template"; + import recast, { Builders, NamedTypes, NodePath, Options, Parser } from "recast"; + import { ASTNode } from "ast-types/gen/nodes"; -/*~ If this module has methods, declare them as functions like so. - */ -export function myMethod(a: string): string; -export function myOtherMethod(a: number): number; + export type ASTPath = NodePath; -/*~ You can declare types that are available via importing the module */ -export interface someType { - name: string; - length: number; - extras?: string[]; + export interface Filters { + JSXElement: JSXElement.FilterMethods; + VariableDeclarator: VariableDeclarator.FilterMethods; + } + + export interface Mappings { + JSXElement: JSXElement.MappingMethods; + } + + export interface Plugin { + (core: Core): void; + } + + export interface FileInfo { + /** The absolute path to the current file. */ + path: string; + /** The source code of the current file. */ + source: string; + } + + export 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; + } + + interface Core { + (source: string, options?: Options): Collection; + (source: ASTNode | ASTNode[] | ASTPath | ASTPath[]): Collection; + + registerMethods: typeof registerMethods; + + types: typeof recast.types; + + match(path: ASTNode | ASTPath, filter: ((path: ASTNode) => boolean) | ASTNode): boolean; + + /** template, bound to default parser */ + 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 & NamedTypes & Builders; + + const core: JSCodeshift; + export default core; + + export interface API { + j: JSCodeshift; + jscodeshift: JSCodeshift; + stats: Stats; + report: (msg: string) => void; + } + + export interface Options { + [option: string]: any; + } + + export 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; + } + + export * from "ast-types/gen/nodes"; + export { Collection, Parser }; } -/*~ You can declare properties of the module using const, let, or var */ -export const myField: number; +declare module "jscodeshift/src/template" { + import { Parser } from "recast"; -/*~ If there are types, properties, or methods inside dotted names - *~ of the module, declare them inside a 'namespace'. - */ -export namespace subProp { - /*~ For example, given this definition, someone could write: - *~ import { subProp } from 'yourModule'; - *~ subProp.foo(); - *~ or - *~ import * as yourMod from 'yourModule'; - *~ yourMod.subProp.foo(); + export 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; + } + + export default function withParser(parser: Parser): Template; + + export {}; // force module +} + +declare module "jscodeshift/src/Collection" { + import * as JSXElement from "jscodeshift/src/collections/JSXElement"; + import * as NodeCollection from "jscodeshift/src/collections/Node"; + import * as VariableDeclarator from "jscodeshift/src/collections/VariableDeclarator"; + import recast, { ASTNode, NodePath, Options } from "recast"; + + type Type = typeof recast.types.Type; + type ASTPath = NodePath; + + 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: ASTPath[], parent: Collection, types?: Type[]): this; + + /** + * Returns a new collection containing the nodes for which the callback returns true. + */ + filter( + callback: (path: ASTPath, i: number, paths: ASTPath[]) => path is ASTPath + ): Collection; + filter( + callback: (path: ASTPath, i: number, paths: ASTPath[]) => boolean + ): Collection; + + /** + * Executes callback for each node/path in the collection. + */ + forEach(callback: (path: ASTPath, i: number, paths: ASTPath[]) => void): this; + + /** + * Tests whether at-least one path passes the test implemented by the provided callback. + */ + some(callback: (path: ASTPath, i: number, paths: ASTPath[]) => boolean): boolean; + + /** + * Tests whether all paths pass the test implemented by the provided callback. + */ + every(callback: (path: ASTPath, i: number, paths: ASTPath[]) => 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: ASTPath[] + ) => ASTPath | ASTPath[] | null | undefined, + type: 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(): ASTPath[]; + + getAST(): ASTPath[]; + + /** + * Converts the AST back to a string, using recast. + * @param options directly passed to recast's printer + */ + toSource(options?: 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: (string | number)[]): T; + + /** + * 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: Type): boolean; + } + + /** + * 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 foo(): void; -} \ No newline at end of file + export function registerMethods(methods: object, type?: Type): void; +} + +declare module "jscodeshift/src/collections/Node" { + import { Collection } from "jscodeshift/src/Collection"; + import { ASTNode, Type } from "recast"; + + export interface TraversalMethods { + /** + * Find nodes of a specific type within the nodes of this collection. + */ + find(type: Type): Collection; + find(type: Type, filter: (value: any) => boolean): Collection; + find(type: Type, filter: object): Collection; + + /** + * Returns a collection containing the paths that create the scope of the + * currently selected paths. Dedupes the paths. + */ + closestScope(): Collection; + + /** + * Traverse the AST up and finds the closest node of the provided type. + */ + closest(type: Type, filter?: any): 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 falsey value, the element is skipped. + */ + getVariableDeclarators(nameGetter: Function): 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. + * + * @param {Node|Array|function} nodes + */ + replaceWith(nodes: T | T[] | ((path: any, i: number) => T)): this; + + /** + * Inserts a new node before the current one. + * + * @param {Node|Array|function} insert + */ + insertBefore(insert: any): Collection; + + /** + * Inserts a new node after the current one. + * + * @param {Node|Array|function} insert + */ + insertAfter(insert: any): Collection; + + remove(): Collection; + } + + export function register(): void; + + export {}; // force module +} + +declare module "jscodeshift/src/collections/VariableDeclarator" { + import { VariableDeclarator } from "ast-types/gen/nodes"; + import { Collection } from "jscodeshift/src/Collection"; + import recast, { NodePath } from "recast"; + + type Node = typeof recast.types.namedTypes.Node; + type ASTPath = NodePath; + + export interface GlobalMethods { + /** + * Finds all variable declarators, optionally filtered by name. + */ + findVariableDeclarators(name?: string): Collection; + } + + export interface TransformMethods { + /** + * Renames a variable and all its occurrences. + * This method only applies to VariableDeclarator typed collections. + */ + renameTo(newName: string): 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 {}; // force module +} + +declare module "jscodeshift/src/collections/JSXElement" { + import { JSXElement } from "ast-types/gen/nodes"; + import { Collection } from "jscodeshift/src/Collection"; + import { NodePath } from "recast"; + + type ASTPath = NodePath; + + export interface GlobalMethods { + /** + * Finds all JSXElements optionally filtered by name + */ + findJSXElements(name?: string): 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; + } + + 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; + + /** + * Returns all children that are JSXElements. + * This method only applies to JSXElement typed collections. + */ + childElements(): 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 {}; // force module +} diff --git a/types/jscodeshift/jscodeshift-tests.ts b/types/jscodeshift/jscodeshift-tests.ts index e69de29bb2..8631e400c4 100644 --- a/types/jscodeshift/jscodeshift-tests.ts +++ b/types/jscodeshift/jscodeshift-tests.ts @@ -0,0 +1,50 @@ +import { ASTNode, FileInfo, API, Transform } 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(); +}; + +// `ASTNode` supports type narrowing. +{ + const node = ({} as any) as ASTNode; + if (node.type === "CatchClause") { + // `node` is narrowed to `CatchClause` here + if (node.param && node.param.type === "Identifier") { + // `node.param` is narrowed to `Identifier` here + if ( + node.param.typeAnnotation && + node.param.typeAnnotation.type === "TSTypeAnnotation" + ) { + // `node.param.typeAnnotation` is narrowed to `TSTypeAnnotation` here + if (node.param.typeAnnotation.typeAnnotation.type === "TSArrayType") { + // `node.param.typeAnnotation.typeAnnotation` is narrowed to `TSArrayType` here + node.param.typeAnnotation.typeAnnotation.elementType; + } + } + } + } +} diff --git a/types/jscodeshift/package.json b/types/jscodeshift/package.json new file mode 100644 index 0000000000..65d03a5a7f --- /dev/null +++ b/types/jscodeshift/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "dependencies": { + "ast-types": "^0.12.0", + "recast": "^0.17.0" + } +} diff --git a/types/jscodeshift/tsconfig.json b/types/jscodeshift/tsconfig.json index 726b5b889f..f1ad7409d3 100644 --- a/types/jscodeshift/tsconfig.json +++ b/types/jscodeshift/tsconfig.json @@ -7,6 +7,7 @@ "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, + "strictFunctionTypes": true, "baseUrl": "../", "typeRoots": [ "../"