diff --git a/src/language-js/print/block.js b/src/language-js/print/block.js new file mode 100644 index 000000000000..b4a7f5051809 --- /dev/null +++ b/src/language-js/print/block.js @@ -0,0 +1,76 @@ +"use strict"; + +const { printDanglingComments } = require("../../main/comments"); +const { isNextLineEmpty } = require("../../common/util"); +const { + builders: { concat, hardline, indent }, +} = require("../../document"); +const { hasDanglingComments } = require("../utils"); +const { locEnd } = require("../loc"); + +const { printStatementSequence } = require("./statement"); + +/** @typedef {import("../../document").Doc} Doc */ + +function printBlock(path, options, print) { + const n = path.getValue(); + const parts = []; + const semi = options.semi ? ";" : ""; + const naked = path.call((bodyPath) => { + return printStatementSequence(bodyPath, options, print); + }, "body"); + + if (n.type === "StaticBlock") { + parts.push("static "); + } + + const hasContent = n.body.some((node) => node.type !== "EmptyStatement"); + const hasDirectives = n.directives && n.directives.length > 0; + + const parent = path.getParentNode(); + const parentParent = path.getParentNode(1); + if ( + !hasContent && + !hasDirectives && + !hasDanglingComments(n) && + (parent.type === "ArrowFunctionExpression" || + parent.type === "FunctionExpression" || + parent.type === "FunctionDeclaration" || + parent.type === "ObjectMethod" || + parent.type === "ClassMethod" || + parent.type === "ClassPrivateMethod" || + parent.type === "ForStatement" || + parent.type === "WhileStatement" || + parent.type === "DoWhileStatement" || + parent.type === "DoExpression" || + (parent.type === "CatchClause" && !parentParent.finalizer) || + parent.type === "TSModuleDeclaration" || + parent.type === "TSDeclareFunction" || + n.type === "StaticBlock") + ) { + return concat([...parts, "{}"]); + } + + parts.push("{"); + + // Babel 6 + if (hasDirectives) { + path.each((childPath) => { + parts.push(indent(concat([hardline, print(childPath), semi]))); + if (isNextLineEmpty(options.originalText, childPath.getValue(), locEnd)) { + parts.push(hardline); + } + }, "directives"); + } + + if (hasContent) { + parts.push(indent(concat([hardline, naked]))); + } + + parts.push(printDanglingComments(path, options)); + parts.push(hardline, "}"); + + return concat(parts); +} + +module.exports = { printBlock }; diff --git a/src/language-js/print/member-chain.js b/src/language-js/print/member-chain.js index 9bb2333dda36..16247fdb30c1 100644 --- a/src/language-js/print/member-chain.js +++ b/src/language-js/print/member-chain.js @@ -35,10 +35,10 @@ const { utils: { willBreak }, } = require("../../document"); const printCallArguments = require("./call-arguments"); +const { printMemberLookup } = require("./member"); const { printOptionalToken, printFunctionTypeParameters, - printMemberLookup, printBindExpressionCallee, } = require("./misc"); diff --git a/src/language-js/print/member.js b/src/language-js/print/member.js new file mode 100644 index 000000000000..78201b27fcc4 --- /dev/null +++ b/src/language-js/print/member.js @@ -0,0 +1,67 @@ +"use strict"; + +const { + builders: { concat, softline, group, indent }, +} = require("../../document"); +const { isNumericLiteral } = require("../utils"); +const { printOptionalToken } = require("./misc"); + +function printMemberExpression(path, options, print) { + const n = path.getValue(); + + const parent = path.getParentNode(); + let firstNonMemberParent; + let i = 0; + do { + firstNonMemberParent = path.getParentNode(i); + i++; + } while ( + firstNonMemberParent && + (firstNonMemberParent.type === "MemberExpression" || + firstNonMemberParent.type === "OptionalMemberExpression" || + firstNonMemberParent.type === "TSNonNullExpression") + ); + + const shouldInline = + (firstNonMemberParent && + (firstNonMemberParent.type === "NewExpression" || + firstNonMemberParent.type === "BindExpression" || + (firstNonMemberParent.type === "VariableDeclarator" && + firstNonMemberParent.id.type !== "Identifier") || + (firstNonMemberParent.type === "AssignmentExpression" && + firstNonMemberParent.left.type !== "Identifier"))) || + n.computed || + (n.object.type === "Identifier" && + n.property.type === "Identifier" && + parent.type !== "MemberExpression" && + parent.type !== "OptionalMemberExpression"); + + return concat([ + path.call(print, "object"), + shouldInline + ? printMemberLookup(path, options, print) + : group( + indent(concat([softline, printMemberLookup(path, options, print)])) + ), + ]); +} + +function printMemberLookup(path, options, print) { + const property = path.call(print, "property"); + const n = path.getValue(); + const optional = printOptionalToken(path); + + if (!n.computed) { + return concat([optional, ".", property]); + } + + if (!n.property || isNumericLiteral(n.property)) { + return concat([optional, "[", property, "]"]); + } + + return group( + concat([optional, "[", indent(concat([softline, property])), softline, "]"]) + ); +} + +module.exports = { printMemberExpression, printMemberLookup }; diff --git a/src/language-js/print/misc.js b/src/language-js/print/misc.js index 2a8ccbdfe4ad..4d30c8889215 100644 --- a/src/language-js/print/misc.js +++ b/src/language-js/print/misc.js @@ -3,11 +3,10 @@ /** @type {import("assert")} */ const assert = require("assert"); const { - builders: { concat, softline, group, indent, join, line, hardline }, + builders: { concat, group, indent, join, line, hardline }, } = require("../../document"); const { hasNewlineBetweenOrAfterDecorators, - isNumericLiteral, getParentExportDeclaration, } = require("../utils"); @@ -41,24 +40,6 @@ function printFunctionTypeParameters(path, options, print) { return ""; } -function printMemberLookup(path, options, print) { - const property = path.call(print, "property"); - const n = path.getValue(); - const optional = printOptionalToken(path); - - if (!n.computed) { - return concat([optional, ".", property]); - } - - if (!n.property || isNumericLiteral(n.property)) { - return concat([optional, "[", property, "]"]); - } - - return group( - concat([optional, "[", indent(concat([softline, property])), softline, "]"]) - ); -} - function printBindExpressionCallee(path, options, print) { return concat(["::", path.call(print, "callee")]); } @@ -95,12 +76,24 @@ function printFlowDeclaration(path, printed) { return concat(["declare ", printed]); } +function adjustClause(node, clause, forceSpace) { + if (node.type === "EmptyStatement") { + return ";"; + } + + if (node.type === "BlockStatement" || forceSpace) { + return concat([" ", clause]); + } + + return indent(concat([line, clause])); +} + module.exports = { printOptionalToken, printFunctionTypeParameters, - printMemberLookup, printBindExpressionCallee, printTypeScriptModifiers, printDecorators, printFlowDeclaration, + adjustClause, }; diff --git a/src/language-js/print/statement.js b/src/language-js/print/statement.js new file mode 100644 index 000000000000..a4a95c9091be --- /dev/null +++ b/src/language-js/print/statement.js @@ -0,0 +1,151 @@ +"use strict"; + +const { isNextLineEmpty } = require("../../common/util"); +const { + builders: { concat, join, hardline }, +} = require("../../document"); +const pathNeedsParens = require("../needs-parens"); +const { + classChildNeedsASIProtection, + classPropMayCauseASIProblems, + getLeftSidePathName, + hasNakedLeftSide, + isJSXNode, + isLastStatement, + isTheOnlyJSXElementInMarkdown, +} = require("../utils"); +const { locEnd } = require("../loc"); +const { shouldPrintParamsWithoutParens } = require("./function"); + +/** @typedef {import("../../document").Doc} Doc */ + +function printStatement({ path, index, bodyNode, isClass }, options, print) { + const node = path.getValue(); + + // Just in case the AST has been modified to contain falsy + // "statements," it's safer simply to skip them. + /* istanbul ignore if */ + if (!node) { + return; + } + + // Skip printing EmptyStatement nodes to avoid leaving stray + // semicolons lying around. + if (node.type === "EmptyStatement") { + return; + } + + const printed = print(path); + const text = options.originalText; + const parts = []; + + // in no-semi mode, prepend statement with semicolon if it might break ASI + // don't prepend the only JSX element in a program with semicolon + if ( + !options.semi && + !isClass && + !isTheOnlyJSXElementInMarkdown(options, path) && + statementNeedsASIProtection(path, options) + ) { + if (node.comments && node.comments.some((comment) => comment.leading)) { + parts.push(print(path, { needsSemi: true })); + } else { + parts.push(";", printed); + } + } else { + parts.push(printed); + } + + if (!options.semi && isClass) { + if (classPropMayCauseASIProblems(path)) { + parts.push(";"); + } else if ( + node.type === "ClassProperty" || + node.type === "FieldDefinition" + ) { + const nextChild = bodyNode.body[index + 1]; + if (classChildNeedsASIProtection(nextChild)) { + parts.push(";"); + } + } + } + + if (isNextLineEmpty(text, node, locEnd) && !isLastStatement(path)) { + parts.push(hardline); + } + + return concat(parts); +} + +function printStatementSequence(path, options, print) { + const bodyNode = path.getNode(); + const isClass = bodyNode.type === "ClassBody"; + + const printed = path + .map((statementPath, index) => + printStatement( + { + path, + index, + bodyNode, + isClass, + }, + options, + print + ) + ) + .filter(Boolean); + + return join(hardline, printed); +} + +function statementNeedsASIProtection(path, options) { + const node = path.getNode(); + + if (node.type !== "ExpressionStatement") { + return false; + } + + return path.call( + (childPath) => expressionNeedsASIProtection(childPath, options), + "expression" + ); +} + +function expressionNeedsASIProtection(path, options) { + const node = path.getValue(); + + const maybeASIProblem = + pathNeedsParens(path, options) || + node.type === "ParenthesizedExpression" || + node.type === "TypeCastExpression" || + (node.type === "ArrowFunctionExpression" && + !shouldPrintParamsWithoutParens(path, options)) || + node.type === "ArrayExpression" || + node.type === "ArrayPattern" || + (node.type === "UnaryExpression" && + node.prefix && + (node.operator === "+" || node.operator === "-")) || + node.type === "TemplateLiteral" || + node.type === "TemplateElement" || + isJSXNode(node) || + (node.type === "BindExpression" && !node.object) || + node.type === "RegExpLiteral" || + (node.type === "Literal" && node.pattern) || + (node.type === "Literal" && node.regex); + + if (maybeASIProblem) { + return true; + } + + if (!hasNakedLeftSide(node)) { + return false; + } + + return path.call( + (childPath) => expressionNeedsASIProtection(childPath, options), + ...getLeftSidePathName(path, node) + ); +} + +module.exports = { printStatementSequence }; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index c5e7825f7196..025e7775ec79 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -42,17 +42,13 @@ const { } = require("./html-binding"); const preprocess = require("./print-preprocess"); const { - classChildNeedsASIProtection, - classPropMayCauseASIProblems, getFunctionParameters, getCallArguments, - getLeftSidePathName, getParentExportDeclaration, getTypeScriptMappedTypeModifier, hasDanglingComments, hasFlowShorthandAnnotationComment, hasLeadingOwnLineComment, - hasNakedLeftSide, hasNewlineBetweenOrAfterDecorators, hasNgSideEffect, hasPrettierIgnore, @@ -60,8 +56,6 @@ const { isExportDeclaration, isFunctionNotation, isGetterOrSetter, - isJSXNode, - isLastStatement, isLiteral, isNgForOf, isObjectType, @@ -77,11 +71,11 @@ const { locStart, locEnd } = require("./loc"); const { printOptionalToken, - printMemberLookup, printBindExpressionCallee, printTypeScriptModifiers, printDecorators, printFlowDeclaration, + adjustClause, } = require("./print/misc"); const { printImportDeclaration, @@ -117,7 +111,6 @@ const { printArrowFunctionExpression, printMethod, printReturnAndThrowArgument, - shouldPrintParamsWithoutParens, } = require("./print/function"); const { printCallExpression } = require("./print/call-expression"); const { printInterface } = require("./print/interface"); @@ -128,6 +121,9 @@ const { printAssignmentRight, } = require("./print/assignment"); const { printBinaryishExpression } = require("./print/binaryish"); +const { printStatementSequence } = require("./print/statement"); +const { printMemberExpression } = require("./print/member"); +const { printBlock } = require("./print/block"); const { printComment } = require("./print/comment"); function genericPrint(path, options, printPath, args) { @@ -404,43 +400,7 @@ function printPathNoParens(path, options, print, args) { } case "OptionalMemberExpression": case "MemberExpression": { - const parent = path.getParentNode(); - let firstNonMemberParent; - let i = 0; - do { - firstNonMemberParent = path.getParentNode(i); - i++; - } while ( - firstNonMemberParent && - (firstNonMemberParent.type === "MemberExpression" || - firstNonMemberParent.type === "OptionalMemberExpression" || - firstNonMemberParent.type === "TSNonNullExpression") - ); - - const shouldInline = - (firstNonMemberParent && - (firstNonMemberParent.type === "NewExpression" || - firstNonMemberParent.type === "BindExpression" || - (firstNonMemberParent.type === "VariableDeclarator" && - firstNonMemberParent.id.type !== "Identifier") || - (firstNonMemberParent.type === "AssignmentExpression" && - firstNonMemberParent.left.type !== "Identifier"))) || - n.computed || - (n.object.type === "Identifier" && - n.property.type === "Identifier" && - parent.type !== "MemberExpression" && - parent.type !== "OptionalMemberExpression"); - - return concat([ - path.call(print, "object"), - shouldInline - ? printMemberLookup(path, options, print) - : group( - indent( - concat([softline, printMemberLookup(path, options, print)]) - ) - ), - ]); + return printMemberExpression(path, options, print); } case "MetaProperty": return concat([ @@ -555,65 +515,8 @@ function printPathNoParens(path, options, print, args) { return "import"; case "TSModuleBlock": case "BlockStatement": - case "StaticBlock": { - const naked = path.call((bodyPath) => { - return printStatementSequence(bodyPath, options, print); - }, "body"); - - if (n.type === "StaticBlock") { - parts.push("static "); - } - - const hasContent = n.body.some((node) => node.type !== "EmptyStatement"); - const hasDirectives = n.directives && n.directives.length > 0; - - const parent = path.getParentNode(); - const parentParent = path.getParentNode(1); - if ( - !hasContent && - !hasDirectives && - !hasDanglingComments(n) && - (parent.type === "ArrowFunctionExpression" || - parent.type === "FunctionExpression" || - parent.type === "FunctionDeclaration" || - parent.type === "ObjectMethod" || - parent.type === "ClassMethod" || - parent.type === "ClassPrivateMethod" || - parent.type === "ForStatement" || - parent.type === "WhileStatement" || - parent.type === "DoWhileStatement" || - parent.type === "DoExpression" || - (parent.type === "CatchClause" && !parentParent.finalizer) || - parent.type === "TSModuleDeclaration" || - parent.type === "TSDeclareFunction" || - n.type === "StaticBlock") - ) { - return concat([...parts, "{}"]); - } - - parts.push("{"); - - // Babel 6 - if (hasDirectives) { - path.each((childPath) => { - parts.push(indent(concat([hardline, print(childPath), semi]))); - if ( - isNextLineEmpty(options.originalText, childPath.getValue(), locEnd) - ) { - parts.push(hardline); - } - }, "directives"); - } - - if (hasContent) { - parts.push(indent(concat([hardline, naked]))); - } - - parts.push(comments.printDanglingComments(path, options)); - parts.push(hardline, "}"); - - return concat(parts); - } + case "StaticBlock": + return printBlock(path, options, print); case "ThrowStatement": case "ReturnStatement": return concat([ @@ -2464,85 +2367,6 @@ function printPathNoParens(path, options, print, args) { } } -function printStatementSequence(path, options, print) { - const printed = []; - - const bodyNode = path.getNode(); - const isClass = bodyNode.type === "ClassBody"; - - path.each((stmtPath, i) => { - const stmt = stmtPath.getValue(); - - // Just in case the AST has been modified to contain falsy - // "statements," it's safer simply to skip them. - /* istanbul ignore if */ - if (!stmt) { - return; - } - - // Skip printing EmptyStatement nodes to avoid leaving stray - // semicolons lying around. - if (stmt.type === "EmptyStatement") { - return; - } - - const stmtPrinted = print(stmtPath); - const text = options.originalText; - const parts = []; - - // in no-semi mode, prepend statement with semicolon if it might break ASI - // don't prepend the only JSX element in a program with semicolon - if ( - !options.semi && - !isClass && - !isTheOnlyJSXElementInMarkdown(options, stmtPath) && - stmtNeedsASIProtection(stmtPath, options) - ) { - if (stmt.comments && stmt.comments.some((comment) => comment.leading)) { - parts.push(print(stmtPath, { needsSemi: true })); - } else { - parts.push(";", stmtPrinted); - } - } else { - parts.push(stmtPrinted); - } - - if (!options.semi && isClass) { - if (classPropMayCauseASIProblems(stmtPath)) { - parts.push(";"); - } else if ( - stmt.type === "ClassProperty" || - stmt.type === "FieldDefinition" - ) { - const nextChild = bodyNode.body[i + 1]; - if (classChildNeedsASIProtection(nextChild)) { - parts.push(";"); - } - } - } - - if (isNextLineEmpty(text, stmt, locEnd) && !isLastStatement(stmtPath)) { - parts.push(hardline); - } - - printed.push(concat(parts)); - }); - - return join(hardline, printed); -} - -function adjustClause(node, clause, forceSpace) { - if (node.type === "EmptyStatement") { - return ";"; - } - - if (node.type === "BlockStatement" || forceSpace) { - return concat([" ", clause]); - } - - return indent(concat([line, clause])); -} - function nodeStr(node, options, isFlowOrTypeScriptDirectiveLiteral) { const raw = rawText(node); const isDirectiveLiteral = @@ -2555,55 +2379,6 @@ function printRegex(node) { return `/${node.pattern}/${flags}`; } -function exprNeedsASIProtection(path, options) { - const node = path.getValue(); - - const maybeASIProblem = - pathNeedsParens(path, options) || - node.type === "ParenthesizedExpression" || - node.type === "TypeCastExpression" || - (node.type === "ArrowFunctionExpression" && - !shouldPrintParamsWithoutParens(path, options)) || - node.type === "ArrayExpression" || - node.type === "ArrayPattern" || - (node.type === "UnaryExpression" && - node.prefix && - (node.operator === "+" || node.operator === "-")) || - node.type === "TemplateLiteral" || - node.type === "TemplateElement" || - isJSXNode(node) || - (node.type === "BindExpression" && !node.object) || - node.type === "RegExpLiteral" || - (node.type === "Literal" && node.pattern) || - (node.type === "Literal" && node.regex); - - if (maybeASIProblem) { - return true; - } - - if (!hasNakedLeftSide(node)) { - return false; - } - - return path.call( - (childPath) => exprNeedsASIProtection(childPath, options), - ...getLeftSidePathName(path, node) - ); -} - -function stmtNeedsASIProtection(path, options) { - const node = path.getNode(); - - if (node.type !== "ExpressionStatement") { - return false; - } - - return path.call( - (childPath) => exprNeedsASIProtection(childPath, options), - "expression" - ); -} - function canAttachComment(node) { return ( node.type &&