"use strict"; const assert = require("assert"); // TODO(azz): anything that imports from main shouldn't be in a `language-*` dir. const comments = require("../main/comments"); const privateUtil = require("../common/util"); const sharedUtil = require("../common/util-shared"); const isIdentifierName = require("esutils").keyword.isIdentifierNameES6; 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; const concat = docBuilders.concat; const join = docBuilders.join; const line = docBuilders.line; const hardline = docBuilders.hardline; const softline = docBuilders.softline; const literalline = docBuilders.literalline; const group = docBuilders.group; const indent = docBuilders.indent; const align = docBuilders.align; const conditionalGroup = docBuilders.conditionalGroup; const fill = docBuilders.fill; const ifBreak = docBuilders.ifBreak; const breakParent = docBuilders.breakParent; const lineSuffixBoundary = docBuilders.lineSuffixBoundary; const addAlignmentToDoc = docBuilders.addAlignmentToDoc; const dedent = docBuilders.dedent; const docUtils = doc.utils; const willBreak = docUtils.willBreak; const isLineNext = docUtils.isLineNext; const isEmpty = docUtils.isEmpty; const rawText = docUtils.rawText; function shouldPrintComma(options, level) { level = level || "es5"; switch (options.trailingComma) { case "all": if (level === "all") { return true; } // fallthrough case "es5": if (level === "es5") { return true; } // fallthrough case "none": default: return false; } } function genericPrint(path, options, printPath, args) { const node = path.getValue(); let needsParens = false; const linesWithoutParens = printPathNoParens(path, options, printPath, args); if (!node || isEmpty(linesWithoutParens)) { return linesWithoutParens; } const decorators = []; if ( node.decorators && node.decorators.length > 0 && // If the parent node is an export declaration, it will be // responsible for printing node.decorators. !privateUtil.getParentExportDeclaration(path) ) { let separator = hardline; path.each(decoratorPath => { let prefix = "@"; let decorator = decoratorPath.getValue(); if (decorator.expression) { decorator = decorator.expression; prefix = ""; } if ( node.decorators.length === 1 && node.type !== "ClassDeclaration" && node.type !== "MethodDefinition" && node.type !== "ClassMethod" && (decorator.type === "Identifier" || decorator.type === "MemberExpression" || (decorator.type === "CallExpression" && (decorator.arguments.length === 0 || (decorator.arguments.length === 1 && (isStringLiteral(decorator.arguments[0]) || decorator.arguments[0].type === "Identifier" || decorator.arguments[0].type === "MemberExpression"))))) ) { separator = line; } decorators.push(prefix, printPath(decoratorPath), separator); }, "decorators"); } else if ( privateUtil.isExportDeclaration(node) && node.declaration && node.declaration.decorators ) { // Export declarations are responsible for printing any decorators // that logically apply to node.declaration. path.each( decoratorPath => { const decorator = decoratorPath.getValue(); const prefix = decorator.type === "Decorator" ? "" : "@"; decorators.push(prefix, printPath(decoratorPath), hardline); }, "declaration", "decorators" ); } else { // Nodes with decorators can't have parentheses, so we can avoid // computing pathNeedsParens() except in this case. needsParens = pathNeedsParens(path, options); } const parts = []; if (needsParens) { parts.unshift("("); } parts.push(linesWithoutParens); if (needsParens) { parts.push(")"); } if (decorators.length > 0) { return group(concat(decorators.concat(parts))); } return concat(parts); } function hasPrettierIgnore(path) { return privateUtil.hasIgnoreComment(path) || hasJsxIgnoreComment(path); } function hasJsxIgnoreComment(path) { const node = path.getValue(); const parent = path.getParentNode(); if (!parent || !node || !isJSXNode(node) || !isJSXNode(parent)) { return false; } // Lookup the previous sibling, ignoring any empty JSXText elements const index = parent.children.indexOf(node); let prevSibling = null; for (let i = index; i > 0; i--) { const candidate = parent.children[i - 1]; if (candidate.type === "JSXText" && !isMeaningfulJSXText(candidate)) { continue; } prevSibling = candidate; break; } return ( prevSibling && prevSibling.type === "JSXExpressionContainer" && prevSibling.expression.type === "JSXEmptyExpression" && prevSibling.expression.comments && prevSibling.expression.comments.find( comment => comment.value.trim() === "prettier-ignore" ) ); } // The following is the shared logic for // ternary operators, namely ConditionalExpression // and TSConditionalType function formatTernaryOperator(path, options, print, operatorOptions) { const n = path.getValue(); const parts = []; const operatorOpts = Object.assign( { beforeParts: () => [""], afterParts: () => [""], shouldCheckJsx: true, operatorName: "ConditionalExpression", consequentNode: "consequent", alternateNode: "alternate", testNode: "test", breakNested: true }, operatorOptions || {} ); // We print a ConditionalExpression in either "JSX mode" or "normal mode". // See tests/jsx/conditional-expression.js for more info. let jsxMode = false; const parent = path.getParentNode(); let forceNoIndent = parent.type === operatorOpts.operatorName; // Find the outermost non-ConditionalExpression parent, and the outermost // ConditionalExpression parent. We'll use these to determine if we should // print in JSX mode. let currentParent; let previousParent; let i = 0; do { previousParent = currentParent || n; currentParent = path.getParentNode(i); i++; } while (currentParent && currentParent.type === operatorOpts.operatorName); const firstNonConditionalParent = currentParent || parent; const lastConditionalParent = previousParent; if ( (operatorOpts.shouldCheckJsx && isJSXNode(n[operatorOpts.testNode])) || isJSXNode(n[operatorOpts.consequentNode]) || isJSXNode(n[operatorOpts.alternateNode]) || conditionalExpressionChainContainsJSX(lastConditionalParent) ) { jsxMode = true; forceNoIndent = true; // Even though they don't need parens, we wrap (almost) everything in // parens when using ?: within JSX, because the parens are analogous to // curly braces in an if statement. const wrap = doc => concat([ ifBreak("(", ""), indent(concat([softline, doc])), softline, ifBreak(")", "") ]); // The only things we don't wrap are: // * Nested conditional expressions in alternates // * null const isNull = node => node.type === "NullLiteral" || (node.type === "Literal" && node.value === null); parts.push( " ? ", isNull(n[operatorOpts.consequentNode]) ? path.call(print, operatorOpts.consequentNode) : wrap(path.call(print, operatorOpts.consequentNode)), " : ", n[operatorOpts.alternateNode].type === operatorOpts.operatorName || isNull(n[operatorOpts.alternateNode]) ? path.call(print, operatorOpts.alternateNode) : wrap(path.call(print, operatorOpts.alternateNode)) ); } else { // normal mode const part = concat([ line, "? ", n[operatorOpts.consequentNode].type === operatorOpts.operatorName ? ifBreak("", "(") : "", align(2, path.call(print, operatorOpts.consequentNode)), n[operatorOpts.consequentNode].type === operatorOpts.operatorName ? ifBreak("", ")") : "", line, ": ", align(2, path.call(print, operatorOpts.alternateNode)) ]); parts.push( parent.type === operatorOpts.operatorName ? options.useTabs ? dedent(indent(part)) : align(Math.max(0, options.tabWidth - 2), part) : part ); } // We want a whole chain of ConditionalExpressions to all // break if any of them break. That means we should only group around the // outer-most ConditionalExpression. const maybeGroup = doc => operatorOpts.breakNested ? parent === firstNonConditionalParent ? group(doc) : doc : group(doc); // Always group in normal mode. // Break the closing paren to keep the chain right after it: // (a // ? b // : c // ).call() const breakClosingParen = !jsxMode && parent.type === "MemberExpression" && !parent.computed; return maybeGroup( concat( [].concat( operatorOpts.beforeParts(), forceNoIndent ? concat(parts) : indent(concat(parts)), operatorOpts.afterParts(breakClosingParen) ) ) ); } function getTypeScriptMappedTypeModifier(tokenNode, keyword) { if (tokenNode.type === "TSPlusToken") { return "+" + keyword; } else if (tokenNode.type === "TSMinusToken") { return "-" + keyword; } return keyword; } function printPathNoParens(path, options, print, args) { const n = path.getValue(); const semi = options.semi ? ";" : ""; if (!n) { return ""; } if (typeof n === "string") { return n; } let parts = []; switch (n.type) { case "File": return path.call(print, "program"); case "Program": // Babel 6 if (n.directives) { path.each(childPath => { parts.push(print(childPath), semi, hardline); if ( sharedUtil.isNextLineEmpty( options.originalText, childPath.getValue(), options ) ) { parts.push(hardline); } }, "directives"); } parts.push( path.call(bodyPath => { return printStatementSequence(bodyPath, options, print); }, "body") ); parts.push( comments.printDanglingComments(path, options, /* sameIndent */ true) ); // Only force a trailing newline if there were any contents. if (n.body.length || n.comments) { parts.push(hardline); } return concat(parts); // Babel extension. case "EmptyStatement": return ""; case "ExpressionStatement": // Detect Flow-parsed directives if (n.directive) { return concat([nodeStr(n.expression, options, true), semi]); } // Do not append semicolon after the only JSX element in a program return concat([ path.call(print, "expression"), isTheOnlyJSXElementInMarkdown(options, path) ? "" : semi ]); // Babel extension. case "ParenthesizedExpression": return concat(["(", path.call(print, "expression"), ")"]); case "AssignmentExpression": return printAssignment( n.left, path.call(print, "left"), concat([" ", n.operator]), n.right, path.call(print, "right"), options ); case "BinaryExpression": case "LogicalExpression": { const parent = path.getParentNode(); const parentParent = path.getParentNode(1); const isInsideParenthesis = n !== parent.body && (parent.type === "IfStatement" || parent.type === "WhileStatement" || parent.type === "DoWhileStatement"); const parts = printBinaryishExpressions( path, print, options, /* isNested */ false, isInsideParenthesis ); // if ( // this.hasPlugin("dynamicImports") && this.lookahead().type === tt.parenLeft // ) { // // looks super weird, we want to break the children if the parent breaks // // if ( // this.hasPlugin("dynamicImports") && // this.lookahead().type === tt.parenLeft // ) { if (isInsideParenthesis) { return concat(parts); } // Break between the parens in unaries or in a member expression, i.e. // // ( // a && // b && // c // ).call() if ( parent.type === "UnaryExpression" || (parent.type === "MemberExpression" && !parent.computed) ) { return group( concat([indent(concat([softline, concat(parts)])), softline]) ); } // Avoid indenting sub-expressions in some cases where the first sub-expression is already // indented accordingly. We should indent sub-expressions where the first case isn't indented. const shouldNotIndent = parent.type === "ReturnStatement" || (parent.type === "JSXExpressionContainer" && parentParent.type === "JSXAttribute") || (n === parent.body && parent.type === "ArrowFunctionExpression") || (n !== parent.body && parent.type === "ForStatement") || (parent.type === "ConditionalExpression" && parentParent.type !== "ReturnStatement"); const shouldIndentIfInlining = parent.type === "AssignmentExpression" || parent.type === "VariableDeclarator" || parent.type === "ClassProperty" || parent.type === "TSAbstractClassProperty" || parent.type === "ClassPrivateProperty" || parent.type === "ObjectProperty" || parent.type === "Property"; const samePrecedenceSubExpression = isBinaryish(n.left) && privateUtil.shouldFlatten(n.operator, n.left.operator); if ( shouldNotIndent || (shouldInlineLogicalExpression(n) && !samePrecedenceSubExpression) || (!shouldInlineLogicalExpression(n) && shouldIndentIfInlining) ) { return group(concat(parts)); } const rest = concat(parts.slice(1)); return group( concat([ // Don't include the initial expression in the indentation // level. The first item is guaranteed to be the first // left-most expression. parts.length > 0 ? parts[0] : "", indent(rest) ]) ); } case "AssignmentPattern": return concat([ path.call(print, "left"), " = ", path.call(print, "right") ]); case "TSTypeAssertionExpression": return concat([ "<", path.call(print, "typeAnnotation"), ">", path.call(print, "expression") ]); case "MemberExpression": { const parent = path.getParentNode(); let firstNonMemberParent; let i = 0; do { firstNonMemberParent = path.getParentNode(i); i++; } while ( firstNonMemberParent && (firstNonMemberParent.type === "MemberExpression" || firstNonMemberParent.type === "TSNonNullExpression") ); const shouldInline = (firstNonMemberParent && (firstNonMemberParent.type === "NewExpression" || (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"); return concat([ path.call(print, "object"), shouldInline ? printMemberLookup(path, options, print) : group( indent( concat([softline, printMemberLookup(path, options, print)]) ) ) ]); } case "MetaProperty": return concat([ path.call(print, "meta"), ".", path.call(print, "property") ]); case "BindExpression": if (n.object) { parts.push(path.call(print, "object")); } parts.push(printBindExpressionCallee(path, options, print)); return concat(parts); case "Identifier": { return concat([ n.name, printOptionalToken(path), printTypeAnnotation(path, options, print) ]); } case "SpreadElement": case "SpreadElementPattern": case "RestProperty": case "ExperimentalRestProperty": case "ExperimentalSpreadProperty": case "SpreadProperty": case "SpreadPropertyPattern": case "RestElement": case "ObjectTypeSpreadProperty": return concat([ "...", path.call(print, "argument"), printTypeAnnotation(path, options, print) ]); case "FunctionDeclaration": case "FunctionExpression": if (isNodeStartingWithDeclare(n, options)) { parts.push("declare "); } parts.push(printFunctionDeclaration(path, print, options)); if (!n.body) { parts.push(semi); } return concat(parts); case "ArrowFunctionExpression": { if (n.async) { parts.push("async "); } if (shouldPrintParamsWithoutParens(path, options)) { parts.push(path.call(print, "params", 0)); } else { parts.push( group( concat([ printFunctionParams( path, print, options, /* expandLast */ args && (args.expandLastArg || args.expandFirstArg), /* printTypeParams */ true ), printReturnType(path, print, options) ]) ) ); } const dangling = comments.printDanglingComments( path, options, /* sameIndent */ true, comment => { const nextCharacter = sharedUtil.getNextNonSpaceNonCommentCharacterIndex( options.originalText, comment, options ); return options.originalText.substr(nextCharacter, 2) === "=>"; } ); if (dangling) { parts.push(" ", dangling); } parts.push(" =>"); const body = path.call(bodyPath => print(bodyPath, args), "body"); // We want to always keep these types of nodes on the same line // as the arrow. if ( !hasLeadingOwnLineComment(options.originalText, n.body, options) && (n.body.type === "ArrayExpression" || n.body.type === "ObjectExpression" || n.body.type === "BlockStatement" || isJSXNode(n.body) || isTemplateOnItsOwnLine(n.body, options.originalText, options) || n.body.type === "ArrowFunctionExpression") ) { return group(concat([concat(parts), " ", body])); } // We handle sequence expressions as the body of arrows specially, // so that the required parentheses end up on their own lines. if (n.body.type === "SequenceExpression") { return group( concat([ concat(parts), group( concat([" (", indent(concat([softline, body])), softline, ")"]) ) ]) ); } // if the arrow function is expanded as last argument, we are adding a // level of indentation and need to add a softline to align the closing ) // with the opening (, or if it's inside a JSXExpression (e.g. an attribute) // we should align the expression's closing } with the line with the opening {. const shouldAddSoftLine = ((args && args.expandLastArg) || path.getParentNode().type === "JSXExpressionContainer") && !(n.comments && n.comments.length); const printTrailingComma = args && args.expandLastArg && shouldPrintComma(options, "all"); // In order to avoid confusion between // a => a ? a : a // a <= a ? a : a const shouldAddParens = n.body.type === "ConditionalExpression" && !privateUtil.startsWithNoLookaheadToken( n.body, /* forbidFunctionAndClass */ false ); return group( concat([ concat(parts), group( concat([ indent( concat([ line, shouldAddParens ? ifBreak("", "(") : "", body, shouldAddParens ? ifBreak("", ")") : "" ]) ), shouldAddSoftLine ? concat([ifBreak(printTrailingComma ? "," : ""), softline]) : "" ]) ) ]) ); } case "MethodDefinition": case "TSAbstractMethodDefinition": if (n.accessibility) { parts.push(n.accessibility + " "); } if (n.static) { parts.push("static "); } if (n.type === "TSAbstractMethodDefinition") { parts.push("abstract "); } parts.push(printMethod(path, options, print)); return concat(parts); case "YieldExpression": parts.push("yield"); if (n.delegate) { parts.push("*"); } if (n.argument) { parts.push(" ", path.call(print, "argument")); } return concat(parts); case "AwaitExpression": return concat(["await ", path.call(print, "argument")]); case "ImportSpecifier": if (n.importKind) { parts.push(path.call(print, "importKind"), " "); } parts.push(path.call(print, "imported")); if (n.local && n.local.name !== n.imported.name) { parts.push(" as ", path.call(print, "local")); } return concat(parts); case "ExportSpecifier": parts.push(path.call(print, "local")); if (n.exported && n.exported.name !== n.local.name) { parts.push(" as ", path.call(print, "exported")); } return concat(parts); case "ImportNamespaceSpecifier": parts.push("* as "); if (n.local) { parts.push(path.call(print, "local")); } else if (n.id) { parts.push(path.call(print, "id")); } return concat(parts); case "ImportDefaultSpecifier": if (n.local) { return path.call(print, "local"); } return path.call(print, "id"); case "TSExportAssignment": return concat(["export = ", path.call(print, "expression"), semi]); case "ExportDefaultDeclaration": case "ExportNamedDeclaration": return printExportDeclaration(path, options, print); case "ExportAllDeclaration": parts.push("export "); if (n.exportKind === "type") { parts.push("type "); } parts.push("* from ", path.call(print, "source"), semi); return concat(parts); case "ExportNamespaceSpecifier": case "ExportDefaultSpecifier": return path.call(print, "exported"); case "ImportDeclaration": { parts.push("import "); if (n.importKind && n.importKind !== "value") { parts.push(n.importKind + " "); } const standalones = []; const grouped = []; if (n.specifiers && n.specifiers.length > 0) { path.each(specifierPath => { const value = specifierPath.getValue(); if ( value.type === "ImportDefaultSpecifier" || value.type === "ImportNamespaceSpecifier" ) { standalones.push(print(specifierPath)); } else { grouped.push(print(specifierPath)); } }, "specifiers"); if (standalones.length > 0) { parts.push(join(", ", standalones)); } if (standalones.length > 0 && grouped.length > 0) { parts.push(", "); } if ( grouped.length === 1 && standalones.length === 0 && n.specifiers && !n.specifiers.some(node => node.comments) ) { parts.push( concat([ "{", options.bracketSpacing ? " " : "", concat(grouped), options.bracketSpacing ? " " : "", "}" ]) ); } else if (grouped.length >= 1) { parts.push( group( concat([ "{", indent( concat([ options.bracketSpacing ? line : softline, join(concat([",", line]), grouped) ]) ), ifBreak(shouldPrintComma(options) ? "," : ""), options.bracketSpacing ? line : softline, "}" ]) ) ); } parts.push(" from "); } else if ( (n.importKind && n.importKind === "type") || // import {} from 'x' /{\s*}/.test( options.originalText.slice( options.locStart(n), options.locStart(n.source) ) ) ) { parts.push("{} from "); } parts.push(path.call(print, "source"), semi); return concat(parts); } case "Import": return "import"; case "BlockStatement": { const naked = path.call(bodyPath => { return printStatementSequence(bodyPath, options, print); }, "body"); const hasContent = n.body.find(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 === "ForStatement" || parent.type === "WhileStatement" || parent.type === "DoWhileStatement" || (parent.type === "CatchClause" && !parentParent.finalizer)) ) { return "{}"; } parts.push("{"); // Babel 6 if (hasDirectives) { path.each(childPath => { parts.push(indent(concat([hardline, print(childPath), semi]))); if ( sharedUtil.isNextLineEmpty( options.originalText, childPath.getValue(), options ) ) { 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 "ReturnStatement": parts.push("return"); if (n.argument) { if (returnArgumentHasLeadingComment(options, n.argument)) { parts.push( concat([ " (", indent(concat([hardline, path.call(print, "argument")])), hardline, ")" ]) ); } else if ( n.argument.type === "LogicalExpression" || n.argument.type === "BinaryExpression" || n.argument.type === "SequenceExpression" ) { parts.push( group( concat([ ifBreak(" (", " "), indent(concat([softline, path.call(print, "argument")])), softline, ifBreak(")") ]) ) ); } else { parts.push(" ", path.call(print, "argument")); } } if (hasDanglingComments(n)) { parts.push( " ", comments.printDanglingComments(path, options, /* sameIndent */ true) ); } parts.push(semi); return concat(parts); case "NewExpression": case "CallExpression": { const isNew = n.type === "NewExpression"; const optional = printOptionalToken(path); if ( // We want to keep CommonJS- and AMD-style require calls, and AMD-style // define calls, as a unit. // e.g. `define(["some/lib", (lib) => {` (!isNew && n.callee.type === "Identifier" && (n.callee.name === "require" || n.callee.name === "define")) || n.callee.type === "Import" || // Template literals as single arguments (n.arguments.length === 1 && isTemplateOnItsOwnLine( n.arguments[0], options.originalText, options )) || // Keep test declarations on a single line // e.g. `it('long name', () => {` (!isNew && isTestCall(n)) ) { return concat([ isNew ? "new " : "", path.call(print, "callee"), optional, path.call(print, "typeParameters"), concat(["(", join(", ", path.map(print, "arguments")), ")"]) ]); } // We detect calls on member lookups and possibly print them in a // special chain format. See `printMemberChain` for more info. if (!isNew && isMemberish(n.callee)) { return printMemberChain(path, options, print); } return concat([ isNew ? "new " : "", path.call(print, "callee"), optional, printFunctionTypeParameters(path, options, print), printArgumentsList(path, options, print) ]); } case "TSInterfaceDeclaration": if (isNodeStartingWithDeclare(n, options)) { parts.push("declare "); } parts.push( n.abstract ? "abstract " : "", printTypeScriptModifiers(path, options, print), "interface ", path.call(print, "id"), n.typeParameters ? path.call(print, "typeParameters") : "", " " ); if (n.heritage.length) { parts.push( group( indent( concat([ softline, "extends ", indent(join(concat([",", line]), path.map(print, "heritage"))), " " ]) ) ) ); } parts.push(path.call(print, "body")); return concat(parts); case "ObjectExpression": case "ObjectPattern": case "ObjectTypeAnnotation": case "TSInterfaceBody": case "TSTypeLiteral": { const isTypeAnnotation = n.type === "ObjectTypeAnnotation"; const shouldBreak = n.type === "TSInterfaceBody" || (n.type !== "ObjectPattern" && privateUtil.hasNewlineInRange( options.originalText, options.locStart(n), options.locEnd(n) )); const parent = path.getParentNode(0); const isFlowInterfaceLikeBody = isTypeAnnotation && parent && (parent.type === "InterfaceDeclaration" || parent.type === "DeclareInterface" || parent.type === "DeclareClass") && path.getName() === "body"; const separator = isFlowInterfaceLikeBody ? ";" : n.type === "TSInterfaceBody" || n.type === "TSTypeLiteral" ? ifBreak(semi, ";") : ","; const fields = []; const leftBrace = n.exact ? "{|" : "{"; const rightBrace = n.exact ? "|}" : "}"; let propertiesField; if (n.type === "TSTypeLiteral") { propertiesField = "members"; } else if (n.type === "TSInterfaceBody") { propertiesField = "body"; } else { propertiesField = "properties"; } if (isTypeAnnotation) { fields.push("indexers", "callProperties"); } fields.push(propertiesField); // Unfortunately, things are grouped together in the ast can be // interleaved in the source code. So we need to reorder them before // printing them. const propsAndLoc = []; fields.forEach(field => { path.each(childPath => { const node = childPath.getValue(); propsAndLoc.push({ node: node, printed: print(childPath), loc: options.locStart(node) }); }, field); }); let separatorParts = []; const props = propsAndLoc.sort((a, b) => a.loc - b.loc).map(prop => { const result = concat(separatorParts.concat(group(prop.printed))); separatorParts = [separator, line]; if ( prop.node.type === "TSPropertySignature" && privateUtil.hasNodeIgnoreComment(prop.node) ) { separatorParts.shift(); } if ( sharedUtil.isNextLineEmpty(options.originalText, prop.node, options) ) { separatorParts.push(hardline); } return result; }); const lastElem = privateUtil.getLast(n[propertiesField]); const canHaveTrailingSeparator = !( lastElem && (lastElem.type === "RestProperty" || lastElem.type === "RestElement" || lastElem.type === "ExperimentalRestProperty" || privateUtil.hasNodeIgnoreComment(lastElem)) ); let content; if (props.length === 0 && !n.typeAnnotation) { if (!hasDanglingComments(n)) { return concat([leftBrace, rightBrace]); } content = group( concat([ leftBrace, comments.printDanglingComments(path, options), softline, rightBrace, printOptionalToken(path) ]) ); } else { content = concat([ leftBrace, indent( concat([options.bracketSpacing ? line : softline, concat(props)]) ), ifBreak( canHaveTrailingSeparator && (separator !== "," || shouldPrintComma(options)) ? separator : "" ), concat([options.bracketSpacing ? line : softline, rightBrace]), printOptionalToken(path), printTypeAnnotation(path, options, print) ]); } // If we inline the object as first argument of the parent, we don't want // to create another group so that the object breaks before the return // type const parentParentParent = path.getParentNode(2); if ( (n.type === "ObjectPattern" && parent && shouldHugArguments(parent) && parent.params[0] === n) || (shouldHugType(n) && parentParentParent && shouldHugArguments(parentParentParent) && parentParentParent.params[0].typeAnnotation && parentParentParent.params[0].typeAnnotation.typeAnnotation === n) ) { return content; } return group(content, { shouldBreak }); } // Babel 6 case "ObjectProperty": // Non-standard AST node type. case "Property": if (n.method || n.kind === "get" || n.kind === "set") { return printMethod(path, options, print); } if (n.shorthand) { parts.push(path.call(print, "value")); } else { let printedLeft; if (n.computed) { printedLeft = concat(["[", path.call(print, "key"), "]"]); } else { printedLeft = printPropertyKey(path, options, print); } parts.push( printAssignment( n.key, printedLeft, ":", n.value, path.call(print, "value"), options ) ); } return concat(parts); // Babel 6 case "ClassMethod": if (n.static) { parts.push("static "); } parts = parts.concat(printObjectMethod(path, options, print)); return concat(parts); // Babel 6 case "ObjectMethod": return printObjectMethod(path, options, print); case "Decorator": return concat(["@", path.call(print, "expression")]); case "ArrayExpression": case "ArrayPattern": if (n.elements.length === 0) { if (!hasDanglingComments(n)) { parts.push("[]"); } else { parts.push( group( concat([ "[", comments.printDanglingComments(path, options), softline, "]" ]) ) ); } } else { const lastElem = privateUtil.getLast(n.elements); const canHaveTrailingComma = !( lastElem && lastElem.type === "RestElement" ); // JavaScript allows you to have empty elements in an array which // changes its length based on the number of commas. The algorithm // is that if the last argument is null, we need to force insert // a comma to ensure JavaScript recognizes it. // [,].length === 1 // [1,].length === 1 // [1,,].length === 2 // // Note that privateUtil.getLast returns null if the array is empty, but // we already check for an empty array just above so we are safe const needsForcedTrailingComma = canHaveTrailingComma && lastElem === null; parts.push( group( concat([ "[", indent( concat([ softline, printArrayItems(path, options, "elements", print) ]) ), needsForcedTrailingComma ? "," : "", ifBreak( canHaveTrailingComma && !needsForcedTrailingComma && shouldPrintComma(options) ? "," : "" ), comments.printDanglingComments( path, options, /* sameIndent */ true ), softline, "]" ]) ) ); } parts.push( printOptionalToken(path), printTypeAnnotation(path, options, print) ); return concat(parts); case "SequenceExpression": { const parent = path.getParentNode(0); if ( parent.type === "ExpressionStatement" || parent.type === "ForStatement" ) { // For ExpressionStatements and for-loop heads, which are among // the few places a SequenceExpression appears unparenthesized, we want // to indent expressions after the first. const parts = []; path.each(p => { if (p.getName() === 0) { parts.push(print(p)); } else { parts.push(",", indent(concat([line, print(p)]))); } }, "expressions"); return group(concat(parts)); } return group( concat([join(concat([",", line]), path.map(print, "expressions"))]) ); } case "ThisExpression": return "this"; case "Super": return "super"; case "NullLiteral": // Babel 6 Literal split return "null"; case "RegExpLiteral": // Babel 6 Literal split return printRegex(n); case "NumericLiteral": // Babel 6 Literal split return privateUtil.printNumber(n.extra.raw); case "BooleanLiteral": // Babel 6 Literal split case "StringLiteral": // Babel 6 Literal split case "Literal": { if (n.regex) { return printRegex(n.regex); } if (typeof n.value === "number") { return privateUtil.printNumber(n.raw); } if (typeof n.value !== "string") { return "" + n.value; } // TypeScript workaround for eslint/typescript-eslint-parser#267 // See corresponding workaround in needs-parens.js const grandParent = path.getParentNode(1); const isTypeScriptDirective = options.parser === "typescript" && typeof n.value === "string" && grandParent && (grandParent.type === "Program" || grandParent.type === "BlockStatement"); return nodeStr(n, options, isTypeScriptDirective); } case "Directive": return path.call(print, "value"); // Babel 6 case "DirectiveLiteral": return nodeStr(n, options); case "UnaryExpression": parts.push(n.operator); if (/[a-z]$/.test(n.operator)) { parts.push(" "); } parts.push(path.call(print, "argument")); return concat(parts); case "UpdateExpression": parts.push(path.call(print, "argument"), n.operator); if (n.prefix) { parts.reverse(); } return concat(parts); case "ConditionalExpression": return formatTernaryOperator(path, options, print, { beforeParts: () => [path.call(print, "test")], afterParts: breakClosingParen => [breakClosingParen ? softline : ""] }); case "VariableDeclaration": { const printed = path.map(childPath => { return print(childPath); }, "declarations"); // We generally want to terminate all variable declarations with a // semicolon, except when they in the () part of for loops. const parentNode = path.getParentNode(); const isParentForLoop = parentNode.type === "ForStatement" || parentNode.type === "ForInStatement" || parentNode.type === "ForOfStatement" || parentNode.type === "ForAwaitStatement"; const hasValue = n.declarations.some(decl => decl.init); let firstVariable; if (printed.length === 1) { firstVariable = printed[0]; } else if (printed.length > 1) { // Indent first var to comply with eslint one-var rule firstVariable = indent(printed[0]); } parts = [ isNodeStartingWithDeclare(n, options) ? "declare " : "", n.kind, firstVariable ? concat([" ", firstVariable]) : "", indent( concat( printed .slice(1) .map(p => concat([",", hasValue && !isParentForLoop ? hardline : line, p]) ) ) ) ]; if (!(isParentForLoop && parentNode.body !== n)) { parts.push(semi); } return group(concat(parts)); } case "VariableDeclarator": return printAssignment( n.id, concat([path.call(print, "id"), path.call(print, "typeParameters")]), " =", n.init, n.init && path.call(print, "init"), options ); case "WithStatement": return group( concat([ "with (", path.call(print, "object"), ")", adjustClause(n.body, path.call(print, "body")) ]) ); case "IfStatement": { const con = adjustClause(n.consequent, path.call(print, "consequent")); const opening = group( concat([ "if (", group( concat([ indent(concat([softline, path.call(print, "test")])), softline ]) ), ")", con ]) ); parts.push(opening); if (n.alternate) { const commentOnOwnLine = (hasTrailingComment(n.consequent) && n.consequent.comments.some( comment => comment.trailing && !privateUtil.isBlockComment(comment) )) || (hasDanglingComments(n) && n.comments.some( comment => !comment.leading && !comment.trailing && !privateUtil.isBlockComment(comment) )); const elseOnSameLine = n.consequent.type === "BlockStatement" && !commentOnOwnLine; parts.push(elseOnSameLine ? " " : hardline); if (hasDanglingComments(n)) { parts.push( comments.printDanglingComments(path, options, true), commentOnOwnLine ? hardline : " " ); } parts.push( "else", group( adjustClause( n.alternate, path.call(print, "alternate"), n.alternate.type === "IfStatement" ) ) ); } return concat(parts); } case "ForStatement": { const body = adjustClause(n.body, path.call(print, "body")); // We want to keep dangling comments above the loop to stay consistent. // Any comment positioned between the for statement and the parentheses // is going to be printed before the statement. const dangling = comments.printDanglingComments( path, options, /* sameLine */ true ); const printedComments = dangling ? concat([dangling, softline]) : ""; if (!n.init && !n.test && !n.update) { return concat([printedComments, group(concat(["for (;;)", body]))]); } return concat([ printedComments, group( concat([ "for (", group( concat([ indent( concat([ softline, path.call(print, "init"), ";", line, path.call(print, "test"), ";", line, path.call(print, "update") ]) ), softline ]) ), ")", body ]) ) ]); } case "WhileStatement": return group( concat([ "while (", group( concat([ indent(concat([softline, path.call(print, "test")])), softline ]) ), ")", adjustClause(n.body, path.call(print, "body")) ]) ); case "ForInStatement": // Note: esprima can't actually parse "for each (". return group( concat([ n.each ? "for each (" : "for (", path.call(print, "left"), " in ", path.call(print, "right"), ")", adjustClause(n.body, path.call(print, "body")) ]) ); case "ForOfStatement": case "ForAwaitStatement": { // Babylon 7 removed ForAwaitStatement in favor of ForOfStatement // with `"await": true`: // https://github.com/estree/estree/pull/138 const isAwait = n.type === "ForAwaitStatement" || n.await; return group( concat([ "for", isAwait ? " await" : "", " (", path.call(print, "left"), " of ", path.call(print, "right"), ")", adjustClause(n.body, path.call(print, "body")) ]) ); } case "DoWhileStatement": { const clause = adjustClause(n.body, path.call(print, "body")); const doBody = group(concat(["do", clause])); parts = [doBody]; if (n.body.type === "BlockStatement") { parts.push(" "); } else { parts.push(hardline); } parts.push("while ("); parts.push( group( concat([ indent(concat([softline, path.call(print, "test")])), softline ]) ), ")", semi ); return concat(parts); } case "DoExpression": return concat(["do ", path.call(print, "body")]); case "BreakStatement": parts.push("break"); if (n.label) { parts.push(" ", path.call(print, "label")); } parts.push(semi); return concat(parts); case "ContinueStatement": parts.push("continue"); if (n.label) { parts.push(" ", path.call(print, "label")); } parts.push(semi); return concat(parts); case "LabeledStatement": if (n.body.type === "EmptyStatement") { return concat([path.call(print, "label"), ":;"]); } return concat([ path.call(print, "label"), ": ", path.call(print, "body") ]); case "TryStatement": return concat([ "try ", path.call(print, "block"), n.handler ? concat([" ", path.call(print, "handler")]) : "", n.finalizer ? concat([" finally ", path.call(print, "finalizer")]) : "" ]); case "CatchClause": return concat([ "catch ", n.param ? concat(["(", path.call(print, "param"), ") "]) : "", path.call(print, "body") ]); case "ThrowStatement": return concat(["throw ", path.call(print, "argument"), semi]); // Note: ignoring n.lexical because it has no printing consequences. case "SwitchStatement": return concat([ group( concat([ "switch (", indent(concat([softline, path.call(print, "discriminant")])), softline, ")" ]) ), " {", n.cases.length > 0 ? indent( concat([ hardline, join( hardline, path.map(casePath => { const caseNode = casePath.getValue(); return concat([ casePath.call(print), n.cases.indexOf(caseNode) !== n.cases.length - 1 && sharedUtil.isNextLineEmpty( options.originalText, caseNode, options ) ? hardline : "" ]); }, "cases") ) ]) ) : "", hardline, "}" ]); case "SwitchCase": { if (n.test) { parts.push("case ", path.call(print, "test"), ":"); } else { parts.push("default:"); } const consequent = n.consequent.filter( node => node.type !== "EmptyStatement" ); if (consequent.length > 0) { const cons = path.call(consequentPath => { return printStatementSequence(consequentPath, options, print); }, "consequent"); parts.push( consequent.length === 1 && consequent[0].type === "BlockStatement" ? concat([" ", cons]) : indent(concat([hardline, cons])) ); } return concat(parts); } // JSX extensions below. case "DebuggerStatement": return concat(["debugger", semi]); case "JSXAttribute": parts.push(path.call(print, "name")); if (n.value) { let res; if (isStringLiteral(n.value)) { const value = rawText(n.value); res = '"' + value.slice(1, -1).replace(/"/g, """) + '"'; } else { res = path.call(print, "value"); } parts.push("=", res); } return concat(parts); case "JSXIdentifier": // Can be removed when this is fixed: // https://github.com/eslint/typescript-eslint-parser/issues/337 if (!n.name) { return "this"; } return "" + n.name; case "JSXNamespacedName": return join(":", [ path.call(print, "namespace"), path.call(print, "name") ]); case "JSXMemberExpression": return join(".", [ path.call(print, "object"), path.call(print, "property") ]); case "TSQualifiedName": return join(".", [path.call(print, "left"), path.call(print, "right")]); case "JSXSpreadAttribute": case "JSXSpreadChild": { return concat([ "{", path.call(p => { const printed = concat(["...", print(p)]); const n = p.getValue(); if (!n.comments || !n.comments.length) { return printed; } return concat([ indent( concat([ softline, comments.printComments(p, () => printed, options) ]) ), softline ]); }, n.type === "JSXSpreadAttribute" ? "argument" : "expression"), "}" ]); } case "JSXExpressionContainer": { const parent = path.getParentNode(0); const preventInline = parent.type === "JSXAttribute" && n.expression.comments && n.expression.comments.length > 0; const shouldInline = !preventInline && (n.expression.type === "ArrayExpression" || n.expression.type === "ObjectExpression" || n.expression.type === "ArrowFunctionExpression" || n.expression.type === "CallExpression" || n.expression.type === "FunctionExpression" || n.expression.type === "JSXEmptyExpression" || n.expression.type === "TemplateLiteral" || n.expression.type === "TaggedTemplateExpression" || n.expression.type === "DoExpression" || (isJSXNode(parent) && (n.expression.type === "ConditionalExpression" || isBinaryish(n.expression)))); if (shouldInline) { return group( concat(["{", path.call(print, "expression"), lineSuffixBoundary, "}"]) ); } return group( concat([ "{", indent(concat([softline, path.call(print, "expression")])), softline, lineSuffixBoundary, "}" ]) ); } case "JSXFragment": case "TSJsxFragment": case "JSXElement": { const elem = comments.printComments( path, () => printJSXElement(path, options, print), options ); return maybeWrapJSXElementInParens(path, elem); } case "JSXOpeningElement": { const n = path.getValue(); const nameHasComments = n.name && n.name.comments && n.name.comments.length > 0; // Don't break self-closing elements with no attributes and no comments if (n.selfClosing && !n.attributes.length && !nameHasComments) { return concat(["<", path.call(print, "name"), " />"]); } // don't break up opening elements with a single long text attribute if ( n.attributes && n.attributes.length === 1 && n.attributes[0].value && isStringLiteral(n.attributes[0].value) && !n.attributes[0].value.value.includes("\n") && // We should break for the following cases: //