From 17c8595115965054a9b57670f164d8a4bfe8a9e5 Mon Sep 17 00:00:00 2001 From: Brie <723547+brieb@users.noreply.github.com> Date: Wed, 9 Jan 2019 17:09:54 -0800 Subject: [PATCH] [jscodeshift] Fix compiler errors in test/js-transforms --- types/jscodeshift/index.d.ts | 6 ++-- .../jscodeshift/test/js-transforms/README.md | 2 ++ .../bind-this-to-bind-expression.ts | 5 +-- ...-bind-this-to-arrow-function-expression.ts | 19 +++++++--- ...expression-to-arrow-function-expression.ts | 13 +++++-- .../js-transforms/props-to-destructuring.ts | 36 ++++++++++--------- .../pure-to-composite-component.ts | 30 ++++++++-------- 7 files changed, 69 insertions(+), 42 deletions(-) create mode 100644 types/jscodeshift/test/js-transforms/README.md mode change 100755 => 100644 types/jscodeshift/test/js-transforms/bind-this-to-bind-expression.ts diff --git a/types/jscodeshift/index.d.ts b/types/jscodeshift/index.d.ts index b9b15be825..95a26cd0a4 100644 --- a/types/jscodeshift/index.d.ts +++ b/types/jscodeshift/index.d.ts @@ -250,7 +250,9 @@ declare module "jscodeshift/src/Collection" { declare module "jscodeshift/src/collections/Node" { import { Collection } from "jscodeshift/src/Collection"; - import { ASTNode, Type } from "recast"; + import { ASTNode, Type, NodePath } from "recast"; + + type ASTPath = NodePath; export interface TraversalMethods { /** @@ -285,7 +287,7 @@ declare module "jscodeshift/src/collections/Node" { * is provided it is executed for every node and the node is replaced with the * functions return value. */ - replaceWith(nodes: T | T[] | ((path: any, i: number) => T)): this; + replaceWith(nodes: T | T[] | ((path: ASTPath, i: number) => T)): Collection; /** * Inserts a new node before the current one. 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 old mode 100755 new mode 100644 index 97248a7e35..65161f8957 --- a/types/jscodeshift/test/js-transforms/bind-this-to-bind-expression.ts +++ b/types/jscodeshift/test/js-transforms/bind-this-to-bind-expression.ts @@ -8,8 +8,9 @@ * let x = ::this.foo; * */ +import { Transform, MemberExpression } from "jscodeshift"; -module.exports = function (file, api) { +const transform: Transform = function (file, api) { const j = api.jscodeshift; return j(file.source) // Find stuff that looks like this.xyz.bind(this) @@ -17,6 +18,6 @@ module.exports = function (file, api) { // 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.object)) + .replaceWith(p => j.bindExpression(null, (p.value.callee as MemberExpression).object)) .toSource(); }; \ No newline at end of file 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 index 727f2e950d..08bf424ae8 100755 --- 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 @@ -25,8 +25,9 @@ * var a = c => c; * */ +import { Transform, MemberExpression, FunctionExpression } from "jscodeshift"; -module.exports = function(file, api) { +const transform: Transform = function(file, api) { const j = api.jscodeshift; return j(file.source) @@ -35,16 +36,24 @@ module.exports = function(file, api) { // 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 => { + var callee = p.value.callee as MemberExpression; + var 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. - var body = p.value.callee.object.body; + var 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 } - var useExpression = body.type == 'BlockStatement' && body.body.length == 1 && body.body[0].type == "ReturnStatement"; - body = useExpression ? body.body[0].argument : body; + var 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.callee.object.params, body, useExpression); + return j.arrowFunctionExpression(object.params, body, useExpression); }) .toSource(); }; \ No newline at end of file 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 index fdee1a994b..ed633bc462 100755 --- 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 @@ -25,8 +25,9 @@ * return a + b + this.c; * } */ +import { Transform } from "jscodeshift"; -module.exports = function (file, api) { +const transform: Transform = function (file, api) { const j = api.jscodeshift; return j(file.source) @@ -38,8 +39,14 @@ module.exports = function (file, api) { var 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 } - var useExpression = body.type == 'BlockStatement' && body.body.length == 1 && body.body[0].type == "ReturnStatement"; - body = useExpression ? body.body[0].argument : body; + var 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 index 87def91d52..81f58c5bb7 100755 --- a/types/jscodeshift/test/js-transforms/props-to-destructuring.ts +++ b/types/jscodeshift/test/js-transforms/props-to-destructuring.ts @@ -19,37 +19,40 @@ * } * */ +import { Transform, VariableDeclarator, ObjectPattern, Identifier, BlockStatement } from "jscodeshift"; -let keywords = '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'; -keywords = keywords.split(' ').reduce((f, k) => { +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'; +let keywords = keywordsStr.split(' ').reduce<{ [key: string]: boolean }>((f, k) => { f[k] = true; return f; }, {}); -const isKeyword = k => keywords.hasOwnProperty(k); +const isKeyword = (k: string) => keywords.hasOwnProperty(k); -module.exports = function (file, api) { +const transform: Transform = function (file, api) { const j = api.jscodeshift; const {statement} = j.template; return j(file.source) .find(j.FunctionExpression) .replaceWith(p => { - console.log(p); + // console.log(p); const root = j(p.value); - const variablesToReplace = {}; + 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, resolvedScope) => { + const isFromProps = (name: string, resolvedScope: any) => { return resolvedScope.getBindings()[name].every( - p => { + (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 = decl.nodes()[0]; + const node: VariableDeclarator = decl.nodes()[0]; - if (!(node.init.type == 'MemberExpression' && + 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; @@ -74,17 +77,17 @@ module.exports = function (file, api) { } }) .filter(e => { - const resolvedScope = e.scope.lookup(e.value.property.name); + 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.name, resolvedScope); + 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.name)) + .filter(p => !isKeyword((p.value.property as Identifier).name)) // Now, do the replacement, `this.props.xyz` => `xyz`. - .replaceWith(p => p.value.property) + .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 => { @@ -117,7 +120,8 @@ module.exports = function (file, api) { if (propDefinitions.size()) { const nodePath = propDefinitions.paths()[0]; const node = nodePath.value; - const newPattern = j.objectPattern(node.id.properties.concat(properties)); + // 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; } @@ -130,7 +134,7 @@ module.exports = function (file, api) { return j.functionExpression( p.value.id, p.value.params, - j.blockStatement([decl].concat(p.value.body.body)) + j.blockStatement([decl].concat((p.value.body as BlockStatement).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 index dd4b3bf5c2..a0ea249746 100755 --- a/types/jscodeshift/test/js-transforms/pure-to-composite-component.ts +++ b/types/jscodeshift/test/js-transforms/pure-to-composite-component.ts @@ -27,12 +27,13 @@ * } */ +import { Transform, ASTNode } from "jscodeshift"; -module.exports = function (file, api) { +const transform: Transform = function (file, api) { const j = api.jscodeshift; const {statement} = j.template; - function hasJSXElement(ast) { + function hasJSXElement(ast: ASTNode) { return j(ast).find(j.JSXElement).size() > 0; } @@ -41,21 +42,22 @@ module.exports = function (file, api) { .filter(p => p.value.declarations.length == 1) .replaceWith(p => { const decl = p.value.declarations[0]; - if (decl.init.type !== 'ArrowFunctionExpression' || - (!hasJSXElement(decl.init.body) && decl.init.body.type !== "JSXElement")) - return p.value; + 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 = decl.init.body; - body = body.type == "JSXElement" ? j.returnStatement(body) : body = body.body; + 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} } - }`; + 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(); };