diff --git a/src/language-js/print/angular.js b/src/language-js/print/angular.js new file mode 100644 index 000000000..b370d1f8c --- /dev/null +++ b/src/language-js/print/angular.js @@ -0,0 +1,127 @@ +"use strict"; + +const { + builders: { concat, join, line, group }, +} = require("../../document"); +const { hasNode } = require("../utils"); +const { printBinaryishExpression } = require("./binaryish"); + +/** @typedef {import("../../common/fast-path")} FastPath */ + +function printAngular(path, options, print) { + const n = path.getValue(); + switch (n.type) { + case "NGRoot": + return concat( + [].concat( + path.call(print, "node"), + !n.node.comments || n.node.comments.length === 0 + ? [] + : concat([" //", n.node.comments[0].value.trimEnd()]) + ) + ); + case "NGPipeExpression": + return printBinaryishExpression(path, options, print); + case "NGChainedExpression": + return group( + join( + concat([";", line]), + path.map( + (childPath) => + hasNgSideEffect(childPath) + ? print(childPath) + : concat(["(", print(childPath), ")"]), + "expressions" + ) + ) + ); + case "NGEmptyExpression": + return ""; + case "NGQuotedExpression": + return concat([n.prefix, ": ", n.value.trim()]); + case "NGMicrosyntax": + return concat( + path.map( + (childPath, index) => + concat([ + index === 0 + ? "" + : isNgForOf(childPath.getValue(), index, n) + ? " " + : concat([";", line]), + print(childPath), + ]), + "body" + ) + ); + case "NGMicrosyntaxKey": + return /^[$_a-z][\w$]*(-[$_a-z][\w$])*$/i.test(n.name) + ? n.name + : JSON.stringify(n.name); + case "NGMicrosyntaxExpression": + return concat([ + path.call(print, "expression"), + n.alias === null ? "" : concat([" as ", path.call(print, "alias")]), + ]); + case "NGMicrosyntaxKeyedExpression": { + const index = path.getName(); + const parentNode = path.getParentNode(); + const shouldNotPrintColon = + isNgForOf(n, index, parentNode) || + (((index === 1 && (n.key.name === "then" || n.key.name === "else")) || + (index === 2 && + n.key.name === "else" && + parentNode.body[index - 1].type === + "NGMicrosyntaxKeyedExpression" && + parentNode.body[index - 1].key.name === "then")) && + parentNode.body[0].type === "NGMicrosyntaxExpression"); + return concat([ + path.call(print, "key"), + shouldNotPrintColon ? " " : ": ", + path.call(print, "expression"), + ]); + } + case "NGMicrosyntaxLet": + return concat([ + "let ", + path.call(print, "key"), + n.value === null ? "" : concat([" = ", path.call(print, "value")]), + ]); + case "NGMicrosyntaxAs": + return concat([ + path.call(print, "key"), + " as ", + path.call(print, "alias"), + ]); + } +} + +function isNgForOf(node, index, parentNode) { + return ( + node.type === "NGMicrosyntaxKeyedExpression" && + node.key.name === "of" && + index === 1 && + parentNode.body[0].type === "NGMicrosyntaxLet" && + parentNode.body[0].value === null + ); +} + +/** identify if an angular expression seems to have side effects */ +/** + * @param {FastPath} path + * @returns {boolean} + */ +function hasNgSideEffect(path) { + return hasNode(path.getValue(), (node) => { + switch (node.type) { + case undefined: + return false; + case "CallExpression": + case "OptionalCallExpression": + case "AssignmentExpression": + return true; + } + }); +} + +module.exports = { printAngular }; diff --git a/src/language-js/html-binding.js b/src/language-js/print/html-binding.js similarity index 98% rename from src/language-js/html-binding.js rename to src/language-js/print/html-binding.js index 65dba36cd..ebf1cbf18 100644 --- a/src/language-js/html-binding.js +++ b/src/language-js/print/html-binding.js @@ -2,7 +2,7 @@ const { builders: { concat, join, line, group, softline, indent }, -} = require("../document"); +} = require("../../document"); function printHtmlBinding(path, options, print) { const node = path.getValue(); diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index 4c21a063e..06551ac27 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -13,6 +13,7 @@ const { fill, ifBreak, lineSuffixBoundary, + join, }, utils: { willBreak, isLineNext, isEmpty }, } = require("../../document"); @@ -707,15 +708,50 @@ function printJsxSpreadAttribute(path, options, print) { ]); } +function printJsx(path, options, print) { + const n = path.getValue(); + switch (n.type) { + case "JSXAttribute": + return printJsxAttribute(path, options, print); + case "JSXIdentifier": + 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 "JSXSpreadAttribute": + return printJsxSpreadAttribute(path, options, print); + case "JSXSpreadChild": { + // Same as `printJsxSpreadAttribute` + const printJsxSpreadChild = printJsxSpreadAttribute; + return printJsxSpreadChild(path, options, print); + } + case "JSXExpressionContainer": + return printJsxExpressionContainer(path, options, print); + case "JSXFragment": + case "JSXElement": + return printJsxElement(path, options, print); + case "JSXOpeningElement": + return printJsxOpeningElement(path, options, print); + case "JSXClosingElement": + return printJsxClosingElement(path, options, print); + case "JSXOpeningFragment": + case "JSXClosingFragment": + return printJsxOpeningClosingFragment(path, options /*, print*/); + case "JSXEmptyExpression": + return printJsxEmptyExpression(path, options /*, print*/); + case "JSXText": + /* istanbul ignore next */ + throw new Error("JSXTest should be handled by JSXElement"); + } +} + module.exports = { - printJsxElement, - printJsxAttribute, - printJsxOpeningElement, - printJsxClosingElement, - printJsxOpeningClosingFragment, - printJsxExpressionContainer, - printJsxEmptyExpression, - printJsxSpreadAttribute, - // Alias - printJsxSpreadChild: printJsxSpreadAttribute, + printJsx, }; diff --git a/src/language-js/print/type-parameters.js b/src/language-js/print/type-parameters.js index f4c53039f..2fe3029c6 100644 --- a/src/language-js/print/type-parameters.js +++ b/src/language-js/print/type-parameters.js @@ -8,7 +8,9 @@ const { hasDanglingComments, isTestCall, isBlockComment, + isTSXFile, shouldPrintComma, + getFunctionParameters, } = require("../utils"); const { shouldHugType } = require("./type-annotation"); @@ -97,4 +99,64 @@ function printDanglingCommentsForInline(path, options) { return concat([printed, hardline]); } -module.exports = { printTypeParameters, getTypeParametersGroupId }; +function printTypeParameter(path, options, print) { + const n = path.getValue(); + const parts = []; + const parent = path.getParentNode(); + if (parent.type === "TSMappedType") { + parts.push("[", path.call(print, "name")); + if (n.constraint) { + parts.push(" in ", path.call(print, "constraint")); + } + if (parent.nameType) { + parts.push( + " as ", + path.callParent((path) => { + return path.call(print, "nameType"); + }) + ); + } + parts.push("]"); + return concat(parts); + } + + if (n.variance) { + parts.push(path.call(print, "variance")); + } + + parts.push(path.call(print, "name")); + + if (n.bound) { + parts.push(": "); + parts.push(path.call(print, "bound")); + } + + if (n.constraint) { + parts.push(" extends ", path.call(print, "constraint")); + } + + if (n.default) { + parts.push(" = ", path.call(print, "default")); + } + + // Keep comma if the file extension is .tsx and + // has one type parameter that isn't extend with any types. + // Because, otherwise formatted result will be invalid as tsx. + const grandParent = path.getNode(2); + if ( + getFunctionParameters(parent).length === 1 && + isTSXFile(options) && + !n.constraint && + grandParent.type === "ArrowFunctionExpression" + ) { + parts.push(","); + } + + return concat(parts); +} + +module.exports = { + printTypeParameter, + printTypeParameters, + getTypeParametersGroupId, +}; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 025e7775e..eda0fc57d 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -36,13 +36,8 @@ const clean = require("./clean"); const { insertPragma } = require("./pragma"); const handleComments = require("./comments"); const pathNeedsParens = require("./needs-parens"); -const { - printHtmlBinding, - isVueEventBindingExpression, -} = require("./html-binding"); const preprocess = require("./print-preprocess"); const { - getFunctionParameters, getCallArguments, getParentExportDeclaration, getTypeScriptMappedTypeModifier, @@ -50,18 +45,15 @@ const { hasFlowShorthandAnnotationComment, hasLeadingOwnLineComment, hasNewlineBetweenOrAfterDecorators, - hasNgSideEffect, hasPrettierIgnore, hasTrailingComment, isExportDeclaration, isFunctionNotation, isGetterOrSetter, isLiteral, - isNgForOf, isObjectType, isObjectTypePropertyAFunction, isTheOnlyJSXElementInMarkdown, - isTSXFile, isBlockComment, needsHardlineAfterDanglingComment, rawText, @@ -69,6 +61,12 @@ const { } = require("./utils"); const { locStart, locEnd } = require("./loc"); +const { + printHtmlBinding, + isVueEventBindingExpression, +} = require("./print/html-binding"); +const { printAngular } = require("./print/angular"); +const { printJsx } = require("./print/jsx"); const { printOptionalToken, printBindExpressionCallee, @@ -92,19 +90,11 @@ const { printTypeAnnotation, shouldHugType, } = require("./print/type-annotation"); -const { - printJsxElement, - printJsxAttribute, - printJsxOpeningElement, - printJsxClosingElement, - printJsxOpeningClosingFragment, - printJsxExpressionContainer, - printJsxEmptyExpression, - printJsxSpreadAttribute, - printJsxSpreadChild, -} = require("./print/jsx"); const { printClass, printClassMethod } = require("./print/class"); -const { printTypeParameters } = require("./print/type-parameters"); +const { + printTypeParameter, + printTypeParameters, +} = require("./print/type-parameters"); const { printPropertyKey } = require("./print/property"); const { printFunctionDeclaration, @@ -251,6 +241,16 @@ function printPathNoParens(path, options, print, args) { return htmlBinding; } + const printedAngular = printAngular(path, options, print); + if (typeof printedAngular !== "undefined") { + return printedAngular; + } + + const printedJsx = printJsx(path, options, print); + if (typeof printedJsx !== "undefined") { + return printedJsx; + } + /** @type{Doc[]} */ let parts = []; @@ -359,7 +359,6 @@ function printPathNoParens(path, options, print, args) { return printVariableDeclarator(path, options, print); case "BinaryExpression": case "LogicalExpression": - case "NGPipeExpression": return printBinaryishExpression(path, options, print); case "AssignmentPattern": return concat([ @@ -1052,43 +1051,8 @@ function printPathNoParens(path, options, print, args) { // JSX extensions below. case "DebuggerStatement": return concat(["debugger", semi]); - case "JSXAttribute": - return printJsxAttribute(path, options, print); - case "JSXIdentifier": - 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": - return printJsxSpreadAttribute(path, options, print); - case "JSXSpreadChild": - return printJsxSpreadChild(path, options, print); - case "JSXExpressionContainer": - return printJsxExpressionContainer(path, options, print); - case "JSXFragment": - case "JSXElement": - return printJsxElement(path, options, print); - case "JSXOpeningElement": - return printJsxOpeningElement(path, options, print); - case "JSXClosingElement": - return printJsxClosingElement(path, options, print); - case "JSXOpeningFragment": - case "JSXClosingFragment": - return printJsxOpeningClosingFragment(path, options /*, print*/); - case "JSXText": - /* istanbul ignore next */ - throw new Error("JSXTest should be handled by JSXElement"); - case "JSXEmptyExpression": - return printJsxEmptyExpression(path, options /*, print*/); case "ClassBody": if (!n.comments && n.body.length === 0) { return "{}"; @@ -1743,59 +1707,8 @@ function printPathNoParens(path, options, print, args) { return printTypeParameters(path, options, print, "params"); case "TSTypeParameter": - case "TypeParameter": { - const parent = path.getParentNode(); - if (parent.type === "TSMappedType") { - parts.push("[", path.call(print, "name")); - if (n.constraint) { - parts.push(" in ", path.call(print, "constraint")); - } - if (parent.nameType) { - parts.push( - " as ", - path.callParent((path) => { - return path.call(print, "nameType"); - }) - ); - } - parts.push("]"); - return concat(parts); - } - - if (n.variance) { - parts.push(path.call(print, "variance")); - } - - parts.push(path.call(print, "name")); - - if (n.bound) { - parts.push(": "); - parts.push(path.call(print, "bound")); - } - - if (n.constraint) { - parts.push(" extends ", path.call(print, "constraint")); - } - - if (n.default) { - parts.push(" = ", path.call(print, "default")); - } - - // Keep comma if the file extension is .tsx and - // has one type parameter that isn't extend with any types. - // Because, otherwise formatted result will be invalid as tsx. - const grandParent = path.getNode(2); - if ( - getFunctionParameters(parent).length === 1 && - isTSXFile(options) && - !n.constraint && - grandParent.type === "ArrowFunctionExpression" - ) { - parts.push(","); - } - - return concat(parts); - } + case "TypeParameter": + return printTypeParameter(path, options, print); case "TypeofTypeAnnotation": return concat(["typeof ", path.call(print, "argument")]); case "InferredPredicate": @@ -2251,87 +2164,6 @@ function printPathNoParens(path, options, print, args) { return concat(parts); - case "NGRoot": - return concat( - [].concat( - path.call(print, "node"), - !n.node.comments || n.node.comments.length === 0 - ? [] - : concat([" //", n.node.comments[0].value.trimEnd()]) - ) - ); - case "NGChainedExpression": - return group( - join( - concat([";", line]), - path.map( - (childPath) => - hasNgSideEffect(childPath) - ? print(childPath) - : concat(["(", print(childPath), ")"]), - "expressions" - ) - ) - ); - case "NGEmptyExpression": - return ""; - case "NGQuotedExpression": - return concat([n.prefix, ": ", n.value.trim()]); - case "NGMicrosyntax": - return concat( - path.map( - (childPath, index) => - concat([ - index === 0 - ? "" - : isNgForOf(childPath.getValue(), index, n) - ? " " - : concat([";", line]), - print(childPath), - ]), - "body" - ) - ); - case "NGMicrosyntaxKey": - return /^[$_a-z][\w$]*(-[$_a-z][\w$])*$/i.test(n.name) - ? n.name - : JSON.stringify(n.name); - case "NGMicrosyntaxExpression": - return concat([ - path.call(print, "expression"), - n.alias === null ? "" : concat([" as ", path.call(print, "alias")]), - ]); - case "NGMicrosyntaxKeyedExpression": { - const index = path.getName(); - const parentNode = path.getParentNode(); - const shouldNotPrintColon = - isNgForOf(n, index, parentNode) || - (((index === 1 && (n.key.name === "then" || n.key.name === "else")) || - (index === 2 && - n.key.name === "else" && - parentNode.body[index - 1].type === - "NGMicrosyntaxKeyedExpression" && - parentNode.body[index - 1].key.name === "then")) && - parentNode.body[0].type === "NGMicrosyntaxExpression"); - return concat([ - path.call(print, "key"), - shouldNotPrintColon ? " " : ": ", - path.call(print, "expression"), - ]); - } - case "NGMicrosyntaxLet": - return concat([ - "let ", - path.call(print, "key"), - n.value === null ? "" : concat([" = ", path.call(print, "value")]), - ]); - case "NGMicrosyntaxAs": - return concat([ - path.call(print, "key"), - " as ", - path.call(print, "alias"), - ]); - case "PipelineBareFunction": return path.call(print, "callee"); case "PipelineTopicExpression": diff --git a/src/language-js/utils.js b/src/language-js/utils.js index a98e8c8b6..16184d372 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -614,34 +614,6 @@ function hasDanglingComments(node) { ); } -/** identify if an angular expression seems to have side effects */ -/** - * @param {FastPath} path - * @returns {boolean} - */ -function hasNgSideEffect(path) { - return hasNode(path.getValue(), (node) => { - switch (node.type) { - case undefined: - return false; - case "CallExpression": - case "OptionalCallExpression": - case "AssignmentExpression": - return true; - } - }); -} - -function isNgForOf(node, index, parentNode) { - return ( - node.type === "NGMicrosyntaxKeyedExpression" && - node.key.name === "of" && - index === 1 && - parentNode.body[0].type === "NGMicrosyntaxLet" && - parentNode.body[0].value === null - ); -} - /** * * @param {any} node @@ -1519,7 +1491,6 @@ module.exports = { hasLeadingOwnLineComment, hasNakedLeftSide, hasNewlineBetweenOrAfterDecorators, - hasNgSideEffect, hasNode, hasPrettierIgnore, hasTrailingComment, @@ -1549,7 +1520,6 @@ module.exports = { isMeaningfulJSXText, isMemberExpressionChain, isMemberish, - isNgForOf, isNumericLiteral, isObjectType, isObjectTypePropertyAFunction,