Skip to content

Commit

Permalink
Extract printStatementSequence, printMemberExpression, and `print…
Browse files Browse the repository at this point in the history
…Block` (#9723)
  • Loading branch information
fisker committed Nov 19, 2020
1 parent 92d31d1 commit 002dc6c
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 254 deletions.
76 changes: 76 additions & 0 deletions 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 };
2 changes: 1 addition & 1 deletion src/language-js/print/member-chain.js
Expand Up @@ -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");

Expand Down
67 changes: 67 additions & 0 deletions 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 };
35 changes: 14 additions & 21 deletions src/language-js/print/misc.js
Expand Up @@ -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");

Expand Down Expand Up @@ -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")]);
}
Expand Down Expand Up @@ -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,
};
151 changes: 151 additions & 0 deletions 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 };

0 comments on commit 002dc6c

Please sign in to comment.