From e03e4d6d400dd311c0fe76f286a4f5c70aff4a9f Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Sun, 25 Mar 2018 18:35:32 +1100 Subject: [PATCH] Move needsParens to a language-specific file (#4201) --- src/common/fast-path.js | 552 ----------------------------- src/language-js/needs-parens.js | 560 ++++++++++++++++++++++++++++++ src/language-js/printer-estree.js | 11 +- 3 files changed, 566 insertions(+), 557 deletions(-) create mode 100644 src/language-js/needs-parens.js diff --git a/src/common/fast-path.js b/src/common/fast-path.js index 44a65133..eb678a9a 100644 --- a/src/common/fast-path.js +++ b/src/common/fast-path.js @@ -1,8 +1,6 @@ "use strict"; const assert = require("assert"); -const util = require("../common/util"); -const startsWithNoLookaheadToken = util.startsWithNoLookaheadToken; function FastPath(value) { assert.ok(this instanceof FastPath); @@ -131,554 +129,4 @@ FastPath.prototype.map = function map(callback /*, name1, name2, ... */) { return result; }; -FastPath.prototype.needsParens = function(options) { - const parent = this.getParentNode(); - if (!parent) { - return false; - } - - const name = this.getName(); - const node = this.getNode(); - - // If the value of this path is some child of a Node and not a Node - // itself, then it doesn't need parentheses. Only Node objects (in - // fact, only Expression nodes) need parentheses. - if (this.getValue() !== node) { - return false; - } - - // Only statements don't need parentheses. - if (isStatement(node)) { - return false; - } - - // Closure compiler requires that type casted expressions to be surrounded by - // parentheses. - if ( - util.hasClosureCompilerTypeCastComment( - options.originalText, - node, - options.locEnd - ) - ) { - return true; - } - - // Identifiers never need parentheses. - if (node.type === "Identifier") { - return false; - } - - if (parent.type === "ParenthesizedExpression") { - return false; - } - - // Add parens around the extends clause of a class. It is needed for almost - // all expressions. - if ( - (parent.type === "ClassDeclaration" || parent.type === "ClassExpression") && - parent.superClass === node && - (node.type === "ArrowFunctionExpression" || - node.type === "AssignmentExpression" || - node.type === "AwaitExpression" || - node.type === "BinaryExpression" || - node.type === "ConditionalExpression" || - node.type === "LogicalExpression" || - node.type === "NewExpression" || - node.type === "ObjectExpression" || - node.type === "ParenthesizedExpression" || - node.type === "SequenceExpression" || - node.type === "TaggedTemplateExpression" || - node.type === "UnaryExpression" || - node.type === "UpdateExpression" || - node.type === "YieldExpression") - ) { - return true; - } - - if ( - (parent.type === "ArrowFunctionExpression" && - parent.body === node && - node.type !== "SequenceExpression" && // these have parens added anyway - startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ false)) || - (parent.type === "ExpressionStatement" && - startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ true)) - ) { - return true; - } - - switch (node.type) { - case "CallExpression": { - let firstParentNotMemberExpression = parent; - let i = 0; - while ( - firstParentNotMemberExpression && - firstParentNotMemberExpression.type === "MemberExpression" - ) { - firstParentNotMemberExpression = this.getParentNode(++i); - } - - if ( - firstParentNotMemberExpression.type === "NewExpression" && - firstParentNotMemberExpression.callee === this.getParentNode(i - 1) - ) { - return true; - } - return false; - } - - case "SpreadElement": - case "SpreadProperty": - return ( - parent.type === "MemberExpression" && - name === "object" && - parent.object === node - ); - - case "UpdateExpression": - if (parent.type === "UnaryExpression") { - return ( - node.prefix && - ((node.operator === "++" && parent.operator === "+") || - (node.operator === "--" && parent.operator === "-")) - ); - } - // else fallthrough - case "UnaryExpression": - switch (parent.type) { - case "UnaryExpression": - return ( - node.operator === parent.operator && - (node.operator === "+" || node.operator === "-") - ); - - case "MemberExpression": - return name === "object" && parent.object === node; - - case "TaggedTemplateExpression": - return true; - - case "NewExpression": - case "CallExpression": - return name === "callee" && parent.callee === node; - - case "BinaryExpression": - return parent.operator === "**" && name === "left"; - - case "TSNonNullExpression": - return true; - - default: - return false; - } - - case "BinaryExpression": { - if (parent.type === "UpdateExpression") { - return true; - } - - const isLeftOfAForStatement = node => { - let i = 0; - while (node) { - const parent = this.getParentNode(i++); - if (!parent) { - return false; - } - if (parent.type === "ForStatement" && parent.init === node) { - return true; - } - node = parent; - } - return false; - }; - if (node.operator === "in" && isLeftOfAForStatement(node)) { - return true; - } - } - // fallthrough - case "TSTypeAssertionExpression": - case "TSAsExpression": - case "LogicalExpression": - switch (parent.type) { - case "ConditionalExpression": - return node.type === "TSAsExpression"; - - case "CallExpression": - case "NewExpression": - return name === "callee" && parent.callee === node; - - case "ClassDeclaration": - case "TSAbstractClassDeclaration": - return name === "superClass" && parent.superClass === node; - case "TSTypeAssertionExpression": - case "TaggedTemplateExpression": - case "UnaryExpression": - case "SpreadElement": - case "SpreadProperty": - case "ExperimentalSpreadProperty": - case "BindExpression": - case "AwaitExpression": - case "TSAsExpression": - case "TSNonNullExpression": - case "UpdateExpression": - return true; - - case "MemberExpression": - return name === "object" && parent.object === node; - - case "AssignmentExpression": - return ( - parent.left === node && - (node.type === "TSTypeAssertionExpression" || - node.type === "TSAsExpression") - ); - case "Decorator": - return ( - parent.expression === node && - (node.type === "TSTypeAssertionExpression" || - node.type === "TSAsExpression") - ); - - case "BinaryExpression": - case "LogicalExpression": { - if (!node.operator && node.type !== "TSTypeAssertionExpression") { - return true; - } - - const po = parent.operator; - const pp = util.getPrecedence(po); - const no = node.operator; - const np = util.getPrecedence(no); - - if (pp > np) { - return true; - } - - if ((po === "||" || po === "??") && no === "&&") { - return true; - } - - if (pp === np && name === "right") { - assert.strictEqual(parent.right, node); - return true; - } - - if (pp === np && !util.shouldFlatten(po, no)) { - return true; - } - - // Add parenthesis when working with binary operators - // It's not stricly needed but helps with code understanding - if (util.isBitwiseOperator(po)) { - return true; - } - - return false; - } - - default: - return false; - } - - case "TSParenthesizedType": { - const grandParent = this.getParentNode(1); - if ( - (parent.type === "TSTypeParameter" || - parent.type === "TypeParameter" || - parent.type === "VariableDeclarator" || - parent.type === "TSTypeAnnotation" || - parent.type === "GenericTypeAnnotation" || - parent.type === "TSTypeReference") && - (node.typeAnnotation.type === "TSTypeAnnotation" && - node.typeAnnotation.typeAnnotation.type !== "TSFunctionType" && - grandParent.type !== "TSTypeOperator") - ) { - return false; - } - // Delegate to inner TSParenthesizedType - if (node.typeAnnotation.type === "TSParenthesizedType") { - return false; - } - return true; - } - - 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 "ExpressionStatement": - return name !== "expression"; - - case "ArrowFunctionExpression": - // We do need parentheses, but SequenceExpressions are handled - // specially when printing bodies of arrow functions. - return name !== "body"; - - default: - // Otherwise err on the side of overparenthesization, adding - // explicit exceptions above if this proves overzealous. - return true; - } - - case "YieldExpression": - if ( - parent.type === "UnaryExpression" || - parent.type === "AwaitExpression" || - parent.type === "TSAsExpression" || - parent.type === "TSNonNullExpression" - ) { - return true; - } - // else fallthrough - case "AwaitExpression": - switch (parent.type) { - case "TaggedTemplateExpression": - case "BinaryExpression": - case "LogicalExpression": - case "SpreadElement": - case "SpreadProperty": - case "ExperimentalSpreadProperty": - case "TSAsExpression": - case "TSNonNullExpression": - return true; - - case "MemberExpression": - return parent.object === node; - - case "NewExpression": - case "CallExpression": - return parent.callee === node; - - case "ConditionalExpression": - return parent.test === node; - - default: - return false; - } - - case "ArrayTypeAnnotation": - return parent.type === "NullableTypeAnnotation"; - - case "IntersectionTypeAnnotation": - case "UnionTypeAnnotation": - return ( - parent.type === "ArrayTypeAnnotation" || - parent.type === "NullableTypeAnnotation" || - parent.type === "IntersectionTypeAnnotation" || - parent.type === "UnionTypeAnnotation" - ); - - case "NullableTypeAnnotation": - return parent.type === "ArrayTypeAnnotation"; - - case "FunctionTypeAnnotation": - return ( - parent.type === "UnionTypeAnnotation" || - parent.type === "IntersectionTypeAnnotation" || - parent.type === "ArrayTypeAnnotation" - ); - - case "StringLiteral": - case "NumericLiteral": - case "Literal": - if ( - typeof node.value === "string" && - parent.type === "ExpressionStatement" && - // TypeScript workaround for eslint/typescript-eslint-parser#267 - // See corresponding workaround in printer.js case: "Literal" - ((options.parser !== "typescript" && !parent.directive) || - (options.parser === "typescript" && - options.originalText.substr(options.locStart(node) - 1, 1) === "(")) - ) { - // To avoid becoming a directive - const grandParent = this.getParentNode(1); - - return ( - grandParent.type === "Program" || - grandParent.type === "BlockStatement" - ); - } - - return ( - parent.type === "MemberExpression" && - typeof node.value === "number" && - name === "object" && - parent.object === node - ); - - case "AssignmentExpression": { - const grandParent = this.getParentNode(1); - - if (parent.type === "ArrowFunctionExpression" && parent.body === node) { - return true; - } else if ( - parent.type === "ClassProperty" && - parent.key === node && - parent.computed - ) { - return false; - } else if ( - parent.type === "TSPropertySignature" && - parent.name === node - ) { - return false; - } else if ( - parent.type === "ForStatement" && - (parent.init === node || parent.update === node) - ) { - return false; - } else if (parent.type === "ExpressionStatement") { - return node.left.type === "ObjectPattern"; - } else if (parent.type === "TSPropertySignature" && parent.key === node) { - return false; - } else if (parent.type === "AssignmentExpression") { - return false; - } else if ( - parent.type === "SequenceExpression" && - grandParent && - grandParent.type === "ForStatement" && - (grandParent.init === parent || grandParent.update === parent) - ) { - return false; - } - return true; - } - case "ConditionalExpression": - switch (parent.type) { - case "TaggedTemplateExpression": - case "UnaryExpression": - case "SpreadElement": - case "SpreadProperty": - case "ExperimentalSpreadProperty": - case "BinaryExpression": - case "LogicalExpression": - case "ExportDefaultDeclaration": - case "AwaitExpression": - case "JSXSpreadAttribute": - case "TSTypeAssertionExpression": - case "TypeCastExpression": - case "TSAsExpression": - case "TSNonNullExpression": - return true; - - case "NewExpression": - case "CallExpression": - return name === "callee" && parent.callee === node; - - case "ConditionalExpression": - return name === "test" && parent.test === node; - - case "MemberExpression": - return name === "object" && parent.object === node; - - default: - return false; - } - - case "FunctionExpression": - switch (parent.type) { - case "CallExpression": - return name === "callee"; // Not strictly necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses. - case "TaggedTemplateExpression": - return true; // This is basically a kind of IIFE. - case "ExportDefaultDeclaration": - return true; - default: - return false; - } - - case "ArrowFunctionExpression": - switch (parent.type) { - case "CallExpression": - return name === "callee"; - - case "NewExpression": - return name === "callee"; - - case "MemberExpression": - return name === "object"; - - case "TSAsExpression": - case "BindExpression": - case "TaggedTemplateExpression": - case "UnaryExpression": - case "LogicalExpression": - case "BinaryExpression": - case "AwaitExpression": - case "TSTypeAssertionExpression": - return true; - - case "ConditionalExpression": - return name === "test"; - - default: - return false; - } - - case "ClassExpression": - return parent.type === "ExportDefaultDeclaration"; - } - - return false; -}; - -function isStatement(node) { - return ( - node.type === "BlockStatement" || - node.type === "BreakStatement" || - node.type === "ClassBody" || - node.type === "ClassDeclaration" || - node.type === "ClassMethod" || - node.type === "ClassProperty" || - node.type === "ClassPrivateProperty" || - node.type === "ContinueStatement" || - node.type === "DebuggerStatement" || - node.type === "DeclareClass" || - node.type === "DeclareExportAllDeclaration" || - node.type === "DeclareExportDeclaration" || - node.type === "DeclareFunction" || - node.type === "DeclareInterface" || - node.type === "DeclareModule" || - node.type === "DeclareModuleExports" || - node.type === "DeclareVariable" || - node.type === "DoWhileStatement" || - node.type === "ExportAllDeclaration" || - node.type === "ExportDefaultDeclaration" || - node.type === "ExportNamedDeclaration" || - node.type === "ExpressionStatement" || - node.type === "ForAwaitStatement" || - node.type === "ForInStatement" || - node.type === "ForOfStatement" || - node.type === "ForStatement" || - node.type === "FunctionDeclaration" || - node.type === "IfStatement" || - node.type === "ImportDeclaration" || - node.type === "InterfaceDeclaration" || - node.type === "LabeledStatement" || - node.type === "MethodDefinition" || - node.type === "ReturnStatement" || - node.type === "SwitchStatement" || - node.type === "ThrowStatement" || - node.type === "TryStatement" || - node.type === "TSAbstractClassDeclaration" || - node.type === "TSEnumDeclaration" || - node.type === "TSImportEqualsDeclaration" || - node.type === "TSInterfaceDeclaration" || - node.type === "TSModuleDeclaration" || - node.type === "TSNamespaceExportDeclaration" || - node.type === "TypeAlias" || - node.type === "VariableDeclaration" || - node.type === "WhileStatement" || - node.type === "WithStatement" - ); -} - module.exports = FastPath; diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js new file mode 100644 index 00000000..3c1b912f --- /dev/null +++ b/src/language-js/needs-parens.js @@ -0,0 +1,560 @@ +"use strict"; + +const assert = require("assert"); + +const util = require("../common/util"); + +function needsParens(path, options) { + const parent = path.getParentNode(); + if (!parent) { + return false; + } + + const name = path.getName(); + const node = path.getNode(); + + // If the value of this path is some child of a Node and not a Node + // itself, then it doesn't need parentheses. Only Node objects (in + // fact, only Expression nodes) need parentheses. + if (path.getValue() !== node) { + return false; + } + + // Only statements don't need parentheses. + if (isStatement(node)) { + return false; + } + + // Closure compiler requires that type casted expressions to be surrounded by + // parentheses. + if ( + util.hasClosureCompilerTypeCastComment( + options.originalText, + node, + options.locEnd + ) + ) { + return true; + } + + // Identifiers never need parentheses. + if (node.type === "Identifier") { + return false; + } + + if (parent.type === "ParenthesizedExpression") { + return false; + } + + // Add parens around the extends clause of a class. It is needed for almost + // all expressions. + if ( + (parent.type === "ClassDeclaration" || parent.type === "ClassExpression") && + parent.superClass === node && + (node.type === "ArrowFunctionExpression" || + node.type === "AssignmentExpression" || + node.type === "AwaitExpression" || + node.type === "BinaryExpression" || + node.type === "ConditionalExpression" || + node.type === "LogicalExpression" || + node.type === "NewExpression" || + node.type === "ObjectExpression" || + node.type === "ParenthesizedExpression" || + node.type === "SequenceExpression" || + node.type === "TaggedTemplateExpression" || + node.type === "UnaryExpression" || + node.type === "UpdateExpression" || + node.type === "YieldExpression") + ) { + return true; + } + + if ( + (parent.type === "ArrowFunctionExpression" && + parent.body === node && + node.type !== "SequenceExpression" && // these have parens added anyway + util.startsWithNoLookaheadToken( + node, + /* forbidFunctionAndClass */ false + )) || + (parent.type === "ExpressionStatement" && + util.startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ true)) + ) { + return true; + } + + switch (node.type) { + case "CallExpression": { + let firstParentNotMemberExpression = parent; + let i = 0; + while ( + firstParentNotMemberExpression && + firstParentNotMemberExpression.type === "MemberExpression" + ) { + firstParentNotMemberExpression = path.getParentNode(++i); + } + + if ( + firstParentNotMemberExpression.type === "NewExpression" && + firstParentNotMemberExpression.callee === path.getParentNode(i - 1) + ) { + return true; + } + return false; + } + + case "SpreadElement": + case "SpreadProperty": + return ( + parent.type === "MemberExpression" && + name === "object" && + parent.object === node + ); + + case "UpdateExpression": + if (parent.type === "UnaryExpression") { + return ( + node.prefix && + ((node.operator === "++" && parent.operator === "+") || + (node.operator === "--" && parent.operator === "-")) + ); + } + // else fallthrough + case "UnaryExpression": + switch (parent.type) { + case "UnaryExpression": + return ( + node.operator === parent.operator && + (node.operator === "+" || node.operator === "-") + ); + + case "MemberExpression": + return name === "object" && parent.object === node; + + case "TaggedTemplateExpression": + return true; + + case "NewExpression": + case "CallExpression": + return name === "callee" && parent.callee === node; + + case "BinaryExpression": + return parent.operator === "**" && name === "left"; + + case "TSNonNullExpression": + return true; + + default: + return false; + } + + case "BinaryExpression": { + if (parent.type === "UpdateExpression") { + return true; + } + + const isLeftOfAForStatement = node => { + let i = 0; + while (node) { + const parent = path.getParentNode(i++); + if (!parent) { + return false; + } + if (parent.type === "ForStatement" && parent.init === node) { + return true; + } + node = parent; + } + return false; + }; + if (node.operator === "in" && isLeftOfAForStatement(node)) { + return true; + } + } + // fallthrough + case "TSTypeAssertionExpression": + case "TSAsExpression": + case "LogicalExpression": + switch (parent.type) { + case "ConditionalExpression": + return node.type === "TSAsExpression"; + + case "CallExpression": + case "NewExpression": + return name === "callee" && parent.callee === node; + + case "ClassDeclaration": + case "TSAbstractClassDeclaration": + return name === "superClass" && parent.superClass === node; + case "TSTypeAssertionExpression": + case "TaggedTemplateExpression": + case "UnaryExpression": + case "SpreadElement": + case "SpreadProperty": + case "ExperimentalSpreadProperty": + case "BindExpression": + case "AwaitExpression": + case "TSAsExpression": + case "TSNonNullExpression": + case "UpdateExpression": + return true; + + case "MemberExpression": + return name === "object" && parent.object === node; + + case "AssignmentExpression": + return ( + parent.left === node && + (node.type === "TSTypeAssertionExpression" || + node.type === "TSAsExpression") + ); + case "Decorator": + return ( + parent.expression === node && + (node.type === "TSTypeAssertionExpression" || + node.type === "TSAsExpression") + ); + + case "BinaryExpression": + case "LogicalExpression": { + if (!node.operator && node.type !== "TSTypeAssertionExpression") { + return true; + } + + const po = parent.operator; + const pp = util.getPrecedence(po); + const no = node.operator; + const np = util.getPrecedence(no); + + if (pp > np) { + return true; + } + + if ((po === "||" || po === "??") && no === "&&") { + return true; + } + + if (pp === np && name === "right") { + assert.strictEqual(parent.right, node); + return true; + } + + if (pp === np && !util.shouldFlatten(po, no)) { + return true; + } + + // Add parenthesis when working with binary operators + // It's not stricly needed but helps with code understanding + if (util.isBitwiseOperator(po)) { + return true; + } + + return false; + } + + default: + return false; + } + + case "TSParenthesizedType": { + const grandParent = path.getParentNode(1); + if ( + (parent.type === "TSTypeParameter" || + parent.type === "TypeParameter" || + parent.type === "VariableDeclarator" || + parent.type === "TSTypeAnnotation" || + parent.type === "GenericTypeAnnotation" || + parent.type === "TSTypeReference") && + (node.typeAnnotation.type === "TSTypeAnnotation" && + node.typeAnnotation.typeAnnotation.type !== "TSFunctionType" && + grandParent.type !== "TSTypeOperator") + ) { + return false; + } + // Delegate to inner TSParenthesizedType + if (node.typeAnnotation.type === "TSParenthesizedType") { + return false; + } + return true; + } + + 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 "ExpressionStatement": + return name !== "expression"; + + case "ArrowFunctionExpression": + // We do need parentheses, but SequenceExpressions are handled + // specially when printing bodies of arrow functions. + return name !== "body"; + + default: + // Otherwise err on the side of overparenthesization, adding + // explicit exceptions above if this proves overzealous. + return true; + } + + case "YieldExpression": + if ( + parent.type === "UnaryExpression" || + parent.type === "AwaitExpression" || + parent.type === "TSAsExpression" || + parent.type === "TSNonNullExpression" + ) { + return true; + } + // else fallthrough + case "AwaitExpression": + switch (parent.type) { + case "TaggedTemplateExpression": + case "BinaryExpression": + case "LogicalExpression": + case "SpreadElement": + case "SpreadProperty": + case "ExperimentalSpreadProperty": + case "TSAsExpression": + case "TSNonNullExpression": + return true; + + case "MemberExpression": + return parent.object === node; + + case "NewExpression": + case "CallExpression": + return parent.callee === node; + + case "ConditionalExpression": + return parent.test === node; + + default: + return false; + } + + case "ArrayTypeAnnotation": + return parent.type === "NullableTypeAnnotation"; + + case "IntersectionTypeAnnotation": + case "UnionTypeAnnotation": + return ( + parent.type === "ArrayTypeAnnotation" || + parent.type === "NullableTypeAnnotation" || + parent.type === "IntersectionTypeAnnotation" || + parent.type === "UnionTypeAnnotation" + ); + + case "NullableTypeAnnotation": + return parent.type === "ArrayTypeAnnotation"; + + case "FunctionTypeAnnotation": + return ( + parent.type === "UnionTypeAnnotation" || + parent.type === "IntersectionTypeAnnotation" || + parent.type === "ArrayTypeAnnotation" + ); + + case "StringLiteral": + case "NumericLiteral": + case "Literal": + if ( + typeof node.value === "string" && + parent.type === "ExpressionStatement" && + // TypeScript workaround for eslint/typescript-eslint-parser#267 + // See corresponding workaround in printer.js case: "Literal" + ((options.parser !== "typescript" && !parent.directive) || + (options.parser === "typescript" && + options.originalText.substr(options.locStart(node) - 1, 1) === "(")) + ) { + // To avoid becoming a directive + const grandParent = path.getParentNode(1); + + return ( + grandParent.type === "Program" || + grandParent.type === "BlockStatement" + ); + } + + return ( + parent.type === "MemberExpression" && + typeof node.value === "number" && + name === "object" && + parent.object === node + ); + + case "AssignmentExpression": { + const grandParent = path.getParentNode(1); + + if (parent.type === "ArrowFunctionExpression" && parent.body === node) { + return true; + } else if ( + parent.type === "ClassProperty" && + parent.key === node && + parent.computed + ) { + return false; + } else if ( + parent.type === "TSPropertySignature" && + parent.name === node + ) { + return false; + } else if ( + parent.type === "ForStatement" && + (parent.init === node || parent.update === node) + ) { + return false; + } else if (parent.type === "ExpressionStatement") { + return node.left.type === "ObjectPattern"; + } else if (parent.type === "TSPropertySignature" && parent.key === node) { + return false; + } else if (parent.type === "AssignmentExpression") { + return false; + } else if ( + parent.type === "SequenceExpression" && + grandParent && + grandParent.type === "ForStatement" && + (grandParent.init === parent || grandParent.update === parent) + ) { + return false; + } + return true; + } + case "ConditionalExpression": + switch (parent.type) { + case "TaggedTemplateExpression": + case "UnaryExpression": + case "SpreadElement": + case "SpreadProperty": + case "ExperimentalSpreadProperty": + case "BinaryExpression": + case "LogicalExpression": + case "ExportDefaultDeclaration": + case "AwaitExpression": + case "JSXSpreadAttribute": + case "TSTypeAssertionExpression": + case "TypeCastExpression": + case "TSAsExpression": + case "TSNonNullExpression": + return true; + + case "NewExpression": + case "CallExpression": + return name === "callee" && parent.callee === node; + + case "ConditionalExpression": + return name === "test" && parent.test === node; + + case "MemberExpression": + return name === "object" && parent.object === node; + + default: + return false; + } + + case "FunctionExpression": + switch (parent.type) { + case "CallExpression": + return name === "callee"; // Not strictly necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses. + case "TaggedTemplateExpression": + return true; // This is basically a kind of IIFE. + case "ExportDefaultDeclaration": + return true; + default: + return false; + } + + case "ArrowFunctionExpression": + switch (parent.type) { + case "CallExpression": + return name === "callee"; + + case "NewExpression": + return name === "callee"; + + case "MemberExpression": + return name === "object"; + + case "TSAsExpression": + case "BindExpression": + case "TaggedTemplateExpression": + case "UnaryExpression": + case "LogicalExpression": + case "BinaryExpression": + case "AwaitExpression": + case "TSTypeAssertionExpression": + return true; + + case "ConditionalExpression": + return name === "test"; + + default: + return false; + } + + case "ClassExpression": + return parent.type === "ExportDefaultDeclaration"; + } + + return false; +} + +function isStatement(node) { + return ( + node.type === "BlockStatement" || + node.type === "BreakStatement" || + node.type === "ClassBody" || + node.type === "ClassDeclaration" || + node.type === "ClassMethod" || + node.type === "ClassProperty" || + node.type === "ClassPrivateProperty" || + node.type === "ContinueStatement" || + node.type === "DebuggerStatement" || + node.type === "DeclareClass" || + node.type === "DeclareExportAllDeclaration" || + node.type === "DeclareExportDeclaration" || + node.type === "DeclareFunction" || + node.type === "DeclareInterface" || + node.type === "DeclareModule" || + node.type === "DeclareModuleExports" || + node.type === "DeclareVariable" || + node.type === "DoWhileStatement" || + node.type === "ExportAllDeclaration" || + node.type === "ExportDefaultDeclaration" || + node.type === "ExportNamedDeclaration" || + node.type === "ExpressionStatement" || + node.type === "ForAwaitStatement" || + node.type === "ForInStatement" || + node.type === "ForOfStatement" || + node.type === "ForStatement" || + node.type === "FunctionDeclaration" || + node.type === "IfStatement" || + node.type === "ImportDeclaration" || + node.type === "InterfaceDeclaration" || + node.type === "LabeledStatement" || + node.type === "MethodDefinition" || + node.type === "ReturnStatement" || + node.type === "SwitchStatement" || + node.type === "ThrowStatement" || + node.type === "TryStatement" || + node.type === "TSAbstractClassDeclaration" || + node.type === "TSEnumDeclaration" || + node.type === "TSImportEqualsDeclaration" || + node.type === "TSInterfaceDeclaration" || + node.type === "TSModuleDeclaration" || + node.type === "TSNamespaceExportDeclaration" || + node.type === "TypeAlias" || + node.type === "VariableDeclaration" || + node.type === "WhileStatement" || + node.type === "WithStatement" + ); +} + +module.exports = needsParens; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 7d2fb08b..aaa9f845 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -10,6 +10,7 @@ const embed = require("./embed"); const clean = require("./clean"); const insertPragma = require("./pragma").insertPragma; const handleComments = require("./comments"); +const pathNeedsParens = require("./needs-parens"); const doc = require("../doc"); const docBuilders = doc.builders; @@ -119,8 +120,8 @@ function genericPrint(path, options, printPath, args) { ); } else { // Nodes with decorators can't have parentheses, so we can avoid - // computing path.needsParens() except in this case. - needsParens = path.needsParens(options); + // computing pathNeedsParens() except in this case. + needsParens = pathNeedsParens(path, options); } const parts = []; @@ -1332,7 +1333,7 @@ function printPathNoParens(path, options, print, args) { return "" + n.value; } // TypeScript workaround for eslint/typescript-eslint-parser#267 - // See corresponding workaround in fast-path.js needsParens() + // See corresponding workaround in needs-parens.js const grandParent = path.getParentNode(1); const isTypeScriptDirective = options.parser === "typescript" && @@ -2402,7 +2403,7 @@ function printPathNoParens(path, options, print, args) { (greatGreatGrandParent.type === "TSUnionType" || greatGreatGrandParent.type === "TSIntersectionType"); } else { - hasParens = path.needsParens(options); + hasParens = pathNeedsParens(path, options); } if (hasParens) { @@ -4975,7 +4976,7 @@ function exprNeedsASIProtection(path, options) { const node = path.getValue(); const maybeASIProblem = - path.needsParens(options) || + pathNeedsParens(path, options) || node.type === "ParenthesizedExpression" || node.type === "TypeCastExpression" || (node.type === "ArrowFunctionExpression" &&