diff --git a/lib/fast-path.ts b/lib/fast-path.ts index cfe181e77..74b9a5852 100644 --- a/lib/fast-path.ts +++ b/lib/fast-path.ts @@ -324,9 +324,6 @@ FPp.needsParens = function (assumeExpressionContext) { } const parent = this.getParentNode(); - if (!parent) { - return false; - } const name = this.getName(); @@ -347,205 +344,208 @@ FPp.needsParens = function (assumeExpressionContext) { return false; } - if ( - parent.type === "ParenthesizedExpression" || - (node.extra && node.extra.parenthesized) - ) { + if (parent && parent.type === "ParenthesizedExpression") { return false; } - switch (node.type) { - case "UnaryExpression": - case "SpreadElement": - case "SpreadProperty": - return ( - parent.type === "MemberExpression" && - name === "object" && - parent.object === node - ); - - case "BinaryExpression": - case "LogicalExpression": - switch (parent.type) { - case "CallExpression": - return name === "callee" && parent.callee === node; - - case "UnaryExpression": - case "SpreadElement": - case "SpreadProperty": - return true; + if (node.extra && node.extra.parenthesized) { + return true; + } - case "MemberExpression": - return name === "object" && parent.object === node; + if (parent) { + switch (node.type) { + case "UnaryExpression": + case "SpreadElement": + case "SpreadProperty": + return ( + parent.type === "MemberExpression" && + name === "object" && + parent.object === node + ); + + case "BinaryExpression": + case "LogicalExpression": + switch (parent.type) { + case "CallExpression": + return name === "callee" && parent.callee === node; + + case "UnaryExpression": + case "SpreadElement": + case "SpreadProperty": + return true; - case "BinaryExpression": - case "LogicalExpression": { - const po = parent.operator; - const pp = PRECEDENCE[po]; - const no = node.operator; - const np = PRECEDENCE[no]; + case "MemberExpression": + return name === "object" && parent.object === node; - if (pp > np) { - return true; - } + case "BinaryExpression": + case "LogicalExpression": { + const po = parent.operator; + const pp = PRECEDENCE[po]; + const no = node.operator; + const np = PRECEDENCE[no]; - if (pp === np && name === "right") { - assert.strictEqual(parent.right, node); - return true; + if (pp > np) { + return true; + } + + if (pp === np && name === "right") { + assert.strictEqual(parent.right, node); + return true; + } + + break; } - break; + default: + return false; } - default: - return false; - } - - break; + break; - case "SequenceExpression": - switch (parent.type) { - case "ReturnStatement": - return false; + case "SequenceExpression": + switch (parent.type) { + case "ReturnStatement": + return false; - case "ForStatement": - // Although parentheses wouldn't hurt around sequence expressions in - // the head of for loops, traditional style dictates that e.g. i++, - // j++ should not be wrapped with parentheses. - return false; + case "ForStatement": + // Although parentheses wouldn't hurt around sequence expressions in + // the head of for loops, traditional style dictates that e.g. i++, + // j++ should not be wrapped with parentheses. + return false; - case "ExpressionStatement": - return name !== "expression"; + case "ExpressionStatement": + return name !== "expression"; - default: - // Otherwise err on the side of overparenthesization, adding - // explicit exceptions above if this proves overzealous. - return true; - } + default: + // Otherwise err on the side of overparenthesization, adding + // explicit exceptions above if this proves overzealous. + return true; + } - case "OptionalIndexedAccessType": - return node.optional && parent.type === "IndexedAccessType"; - - case "IntersectionTypeAnnotation": - case "UnionTypeAnnotation": - return parent.type === "NullableTypeAnnotation"; - - case "Literal": - return ( - parent.type === "MemberExpression" && - isNumber.check(node.value) && - name === "object" && - parent.object === node - ); - - // Babel 6 Literal split - case "NumericLiteral": - return ( - parent.type === "MemberExpression" && - name === "object" && - parent.object === node - ); - - case "YieldExpression": - case "AwaitExpression": - case "AssignmentExpression": - case "ConditionalExpression": - switch (parent.type) { - case "UnaryExpression": - case "SpreadElement": - case "SpreadProperty": - case "BinaryExpression": - case "LogicalExpression": - return true; + case "OptionalIndexedAccessType": + return node.optional && parent.type === "IndexedAccessType"; + + case "IntersectionTypeAnnotation": + case "UnionTypeAnnotation": + return parent.type === "NullableTypeAnnotation"; + + case "Literal": + return ( + parent.type === "MemberExpression" && + isNumber.check(node.value) && + name === "object" && + parent.object === node + ); + + // Babel 6 Literal split + case "NumericLiteral": + return ( + parent.type === "MemberExpression" && + name === "object" && + parent.object === node + ); + + case "YieldExpression": + case "AwaitExpression": + case "AssignmentExpression": + case "ConditionalExpression": + switch (parent.type) { + case "UnaryExpression": + case "SpreadElement": + case "SpreadProperty": + case "BinaryExpression": + case "LogicalExpression": + return true; - case "CallExpression": - case "NewExpression": - return name === "callee" && parent.callee === node; + case "CallExpression": + case "NewExpression": + return name === "callee" && parent.callee === node; - case "ConditionalExpression": - return name === "test" && parent.test === node; + case "ConditionalExpression": + return name === "test" && parent.test === node; - case "MemberExpression": - return name === "object" && parent.object === node; + case "MemberExpression": + return name === "object" && parent.object === node; - default: - return false; - } + default: + return false; + } - case "ArrowFunctionExpression": - if ( - n.CallExpression.check(parent) && - name === "callee" && - parent.callee === node - ) { - return true; - } + case "ArrowFunctionExpression": + if ( + n.CallExpression.check(parent) && + name === "callee" && + parent.callee === node + ) { + return true; + } - if ( - n.MemberExpression.check(parent) && - name === "object" && - parent.object === node - ) { - return true; - } + if ( + n.MemberExpression.check(parent) && + name === "object" && + parent.object === node + ) { + return true; + } - if ( - n.TSAsExpression && - n.TSAsExpression.check(parent) && - name === "expression" && - parent.expression === node - ) { - return true; - } + if ( + n.TSAsExpression && + n.TSAsExpression.check(parent) && + name === "expression" && + parent.expression === node + ) { + return true; + } - return isBinary(parent); + return isBinary(parent); - case "ObjectExpression": - if ( - parent.type === "ArrowFunctionExpression" && - name === "body" && - parent.body === node - ) { - return true; - } + case "ObjectExpression": + if ( + parent.type === "ArrowFunctionExpression" && + name === "body" && + parent.body === node + ) { + return true; + } - break; + break; - case "TSAsExpression": - if ( - parent.type === "ArrowFunctionExpression" && - name === "body" && - parent.body === node && - node.expression.type === "ObjectExpression" - ) { - return true; - } - break; - - case "CallExpression": - if ( - name === "declaration" && - n.ExportDefaultDeclaration.check(parent) && - n.FunctionExpression.check(node.callee) - ) { - return true; - } - } + case "TSAsExpression": + if ( + parent.type === "ArrowFunctionExpression" && + name === "body" && + parent.body === node && + node.expression.type === "ObjectExpression" + ) { + return true; + } + break; + + case "CallExpression": + if ( + name === "declaration" && + n.ExportDefaultDeclaration.check(parent) && + n.FunctionExpression.check(node.callee) + ) { + return true; + } + } - if ( - parent.type === "NewExpression" && - name === "callee" && - parent.callee === node - ) { - return containsCallExpression(node); - } + if ( + parent.type === "NewExpression" && + name === "callee" && + parent.callee === node + ) { + return containsCallExpression(node); + } - if ( - assumeExpressionContext !== true && - !this.canBeFirstInStatement() && - this.firstInStatement() - ) { - return true; + if ( + assumeExpressionContext !== true && + !this.canBeFirstInStatement() && + this.firstInStatement() + ) { + return true; + } } return false; diff --git a/lib/printer.ts b/lib/printer.ts index 97bc56f24..a53f1e765 100644 --- a/lib/printer.ts +++ b/lib/printer.ts @@ -196,14 +196,14 @@ function genericPrint(path: any, config: any, options: any, printPath: any) { return linesWithoutParens; } - let shouldAddParens = node.extra ? node.extra.parenthesized : false; + let shouldAddParens = false; const decoratorsLines = printDecorators(path, printPath); if (decoratorsLines.isEmpty()) { // Nodes with decorators can't have parentheses, so we can avoid // computing path.needsParens() except in this case. if (!options.avoidRootParens) { - shouldAddParens = shouldAddParens || path.needsParens(); + shouldAddParens = path.needsParens(); } } else { parts.push(decoratorsLines); diff --git a/parsers/babel-ts.ts b/parsers/babel-ts.ts new file mode 100644 index 000000000..34c22c4c2 --- /dev/null +++ b/parsers/babel-ts.ts @@ -0,0 +1,10 @@ +import { parser } from "./babel"; +import getBabelOptions, { Overrides } from "./_babel_options"; + +export { parser }; + +export function parse(source: string, options?: Overrides) { + const babelOptions = getBabelOptions(options); + babelOptions.plugins.push("jsx", "typescript"); + return parser.parse(source, babelOptions); +} diff --git a/test/parens-babylon.ts b/test/parens-extra.ts similarity index 67% rename from test/parens-babylon.ts rename to test/parens-extra.ts index 0abdc8e3a..86c49553b 100644 --- a/test/parens-babylon.ts +++ b/test/parens-extra.ts @@ -1,7 +1,9 @@ import assert from "assert"; +// the babel parser denotes decorative parens with extra.parenthesized import * as babylon from "@babel/parser"; import { parse as recastParse } from "../lib/parser"; import { Printer } from "../lib/printer"; +import * as parser from "../parsers/babel-ts"; import * as types from "ast-types"; const printer = new Printer(); @@ -12,23 +14,20 @@ function parseExpression(expr: any) { return n.ExpressionStatement.check(ast) ? ast.expression : ast; } -const parse = (expr: string) => - recastParse(expr, { - parser: babylon, - }); +const parse = (expr: string) => recastParse(expr, { parser }); function check(expr: string) { const ast = parse(expr); const reprinted = printer.print(ast).code; - assert.strictEqual(reprinted, expr); + assert.strictEqual(expr, reprinted); const expressionAst = parseExpression(expr); const generic = printer.printGenerically(expressionAst).code; types.astNodesAreEquivalent.assert(expressionAst, parseExpression(generic)); } -describe("babylon parens", function () { +describe("parens from node.extra.parenthesized", function () { it("AwaitExpression", function () { check("async () => ({...(await obj)})"); check("(async function* () { yield await foo })"); @@ -55,4 +54,20 @@ describe("babylon parens", function () { assert.strictEqual(printer.print(ast).code, "(1).foo"); }); + + it("prints top level parens for an expression ast", function () { + check("(() => {})()"); + check("(function () {} ())"); + }); + + describe("reprinter", function () { + it("preserves necessary parens", function () { + const ast = parse("() => ({ prop: true })"); + const expr = ast.program.body[0].expression; + + expr.body.properties = []; + + assert.strictEqual(printer.print(ast).code, "() => ({})"); + }); + }); }); diff --git a/test/run.ts b/test/run.ts index 3dfebe48d..39534ed8d 100644 --- a/test/run.ts +++ b/test/run.ts @@ -5,6 +5,7 @@ import "./identity"; import "./jsx"; import "./lines"; import "./mapping"; +import "./parens-extra"; import "./parens"; import "./parser"; import "./patcher"; diff --git a/test/typescript.ts b/test/typescript.ts index 11ca02f7c..54b226685 100644 --- a/test/typescript.ts +++ b/test/typescript.ts @@ -5,10 +5,6 @@ import * as recast from "../main"; import * as types from "ast-types"; import { EOL as eol } from "os"; import * as parser from "../parsers/typescript"; -import { Printer } from "../lib/printer"; - -const { namedTypes: n } = types; -const printer = new Printer(); // Babel 7 no longer supports Node 4 or 5. const nodeMajorVersion = parseInt(process.versions.node, 10); @@ -311,34 +307,6 @@ const nodeMajorVersion = parseInt(process.versions.node, 10); "type Alpha = Color.a;", ]); }); - - it("parens", function () { - function parseExpression(expr: any) { - let ast: any = parser.parse(expr).program; - n.Program.assert(ast); - ast = ast.body[0]; - return n.ExpressionStatement.check(ast) ? ast.expression : ast; - } - - const parse = (expr: string) => recast.parse(expr, { parser }); - - function check(expr: string) { - const ast = parse(expr); - - const reprinted = recast.print(ast).code; - assert.strictEqual(reprinted, expr); - - const expressionAst = parseExpression(expr); - const generic = printer.printGenerically(expressionAst).code; - types.astNodesAreEquivalent.assert( - expressionAst, - parseExpression(generic), - ); - } - - check("(() => {}) as void"); - check("(function () {} as void)"); - }); }); testReprinting(