diff --git a/cspell.json b/cspell.json index 664700efcfb4..9c50df1d0ded 100644 --- a/cspell.json +++ b/cspell.json @@ -354,6 +354,7 @@ "Transloadit", "TSAs", "tsep", + "TSESTree", "TSJS", "tslib", "typeahead", diff --git a/package.json b/package.json index edd74fd48c61..734eed047259 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "devDependencies": { "@babel/core": "7.11.1", "@babel/preset-env": "7.11.0", + "@babel/types": "7.11.0", "@glimmer/reference": "0.58.0", "@rollup/plugin-alias": "3.1.1", "@rollup/plugin-babel": "5.2.0", @@ -92,6 +93,9 @@ "@rollup/plugin-json": "4.1.0", "@rollup/plugin-node-resolve": "9.0.0", "@rollup/plugin-replace": "2.3.3", + "@types/estree": "0.0.45", + "@types/node": "14.6.0", + "@typescript-eslint/types": "3.9.1", "babel-loader": "8.1.0", "benchmark": "2.1.4", "builtin-modules": "3.1.0", @@ -139,7 +143,7 @@ "lint:typecheck": "tsc", "lint:eslint": "cross-env EFF_NO_LINK_RULES=true eslint . --format friendly", "lint:changelog": "node ./scripts/lint-changelog.js", - "lint:prettier": "prettier \"**/*.{md,json,yml,html,css,js}\" \"!test*\" --check", + "lint:prettier": "prettier \"**/*.{md,json,yml,html,css,js,d.ts}\" \"!test*\" --check", "lint:dist": "eslint --no-eslintrc --no-ignore --env=es6,browser --parser-options=ecmaVersion:2016 \"dist/!(bin-prettier|index|third-party).js\"", "lint:spellcheck": "cspell \"**/*\" \".github/**/*\"", "lint:deps": "node ./scripts/check-deps.js", diff --git a/src/document/index.js b/src/document/index.js index 4020cf063bc7..3629a9c212ba 100644 --- a/src/document/index.js +++ b/src/document/index.js @@ -1,5 +1,9 @@ "use strict"; +/** + * @typedef {import("./doc-builders").Doc} Doc + */ + module.exports = { builders: require("./doc-builders"), printer: require("./doc-printer"), diff --git a/src/language-js/comments.js b/src/language-js/comments.js index a039e808d996..f91f377a21a6 100644 --- a/src/language-js/comments.js +++ b/src/language-js/comments.js @@ -578,7 +578,7 @@ function handleCommentAfterArrowParams(text, enclosingNode, comment, options) { comment, options.locEnd ); - if (text.slice(index, index + 2) === "=>") { + if (index !== false && text.slice(index, index + 2) === "=>") { addDanglingComment(enclosingNode, comment); return true; } @@ -672,9 +672,12 @@ function handleLastFunctionArgComments( text, options.locEnd(enclosingNode.id) ); - return getNextNonSpaceNonCommentCharacterIndexWithStartIndex( - text, - functionParamLeftParenIndex + 1 + return ( + functionParamLeftParenIndex !== false && + getNextNonSpaceNonCommentCharacterIndexWithStartIndex( + text, + functionParamLeftParenIndex + 1 + ) ); })(); if (options.locStart(comment) > functionParamRightParenIndex) { @@ -930,6 +933,11 @@ function isBlockComment(comment) { return comment.type === "Block" || comment.type === "CommentBlock"; } +/** + * @param {any} node + * @param {(comment: any) => boolean} fn + * @returns boolean + */ function hasLeadingComment(node, fn = () => true) { if (node.leadingComments) { return node.leadingComments.some(fn); diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index 523751e3454b..8df5d92e6aa7 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -1,5 +1,6 @@ "use strict"; +/** @type {import("assert")} */ const assert = require("assert"); const { diff --git a/src/language-js/print/call-arguments.js b/src/language-js/print/call-arguments.js index d683fae6091c..bb151b0e8dac 100644 --- a/src/language-js/print/call-arguments.js +++ b/src/language-js/print/call-arguments.js @@ -159,7 +159,7 @@ function printCallArguments(path, options, print) { shouldBreakForArrowFunction; // We want to print the last argument with a special flag - let printedExpanded; + let printedExpanded = []; let i = 0; const printArgument = (argPath) => { if (shouldGroupFirst && i === 0) { diff --git a/src/language-js/print/member-chain.js b/src/language-js/print/member-chain.js index e847ae75b676..15734d116a3a 100644 --- a/src/language-js/print/member-chain.js +++ b/src/language-js/print/member-chain.js @@ -80,10 +80,9 @@ function printMemberChain(path, options, print) { // if it is cut off by a parenthesis, we only account for one typed empty // line after that parenthesis if (nextChar === ")") { - return isNextLineEmptyAfterIndex( - originalText, - nextCharIndex + 1, - options.locEnd + return ( + nextCharIndex !== false && + isNextLineEmptyAfterIndex(originalText, nextCharIndex + 1) ); } diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js index 052c9f2f34c7..dc0f8ccf506d 100644 --- a/src/language-js/print/module.js +++ b/src/language-js/print/module.js @@ -3,8 +3,13 @@ const { builders: { concat, softline, group, indent, join, line, ifBreak }, } = require("../../document"); + const { shouldPrintComma } = require("../utils"); +/** + * @typedef {import("../../document").Doc} Doc + */ + function printModuleSource(path, options, print) { const node = path.getValue(); return node.source ? concat([" from ", path.call(print, "source")]) : ""; @@ -12,6 +17,8 @@ function printModuleSource(path, options, print) { function printModuleSpecifiers(path, options, print) { const node = path.getValue(); + + /** @type{Doc[]} */ const parts = [node.type === "ImportDeclaration" ? " " : ""]; if (node.specifiers && node.specifiers.length > 0) { diff --git a/src/language-js/print/ternary.js b/src/language-js/print/ternary.js index 219ca72b4c62..422efaa5ee0b 100644 --- a/src/language-js/print/ternary.js +++ b/src/language-js/print/ternary.js @@ -19,6 +19,22 @@ const { }, } = require("../../document"); +/** + * @typedef {import("../../document").Doc} Doc + * @typedef {import("../../common/fast-path")} FastPath + * + * @typedef {any} Options - Prettier options (TBD ...) + * + * @typedef {Object} OperatorOptions + * @property {() => Array} beforeParts - Parts to print before the `?`. + * @property {(breakClosingParen: boolean) => Array} afterParts - Parts to print after the conditional expression. + * @property {boolean} shouldCheckJsx - Whether to check for and print in JSX mode. + * @property {string} conditionalNodeType - The type of the conditional expression node, ie "ConditionalExpression" or "TSConditionalType". + * @property {string} consequentNodePropertyName - The property at which the consequent node can be found on the main node, eg "consequent". + * @property {string} alternateNodePropertyName - The property at which the alternate node can be found on the main node, eg "alternate". + * @property {string[]} testNodePropertyNames - The properties at which the test nodes can be found on the main node, eg "test". + */ + // If we have nested conditional expressions, we want to print them in JSX mode // if there's at least one JSXElement somewhere in the tree. // @@ -118,16 +134,7 @@ function conditionalExpressionChainContainsJSX(node) { * The following is the shared logic for * ternary operators, namely ConditionalExpression * and TSConditionalType - * @typedef {import("../../document/doc-builders").Doc} Doc - * @typedef {Object} OperatorOptions - * @property {() => Array} beforeParts - Parts to print before the `?`. - * @property {(breakClosingParen: boolean) => Array} afterParts - Parts to print after the conditional expression. - * @property {boolean} shouldCheckJsx - Whether to check for and print in JSX mode. - * @property {string} conditionalNodeType - The type of the conditional expression node, ie "ConditionalExpression" or "TSConditionalType". - * @property {string} consequentNodePropertyName - The property at which the consequent node can be found on the main node, eg "consequent". - * @property {string} alternateNodePropertyName - The property at which the alternate node can be found on the main node, eg "alternate". - * @property {string[]} testNodePropertyNames - The properties at which the test nodes can be found on the main node, eg "test". - * @param {import("../../common/fast-path")} path - The path to the ConditionalExpression/TSConditionalType node. + * @param {FastPath} path - The path to the ConditionalExpression/TSConditionalType node. * @param {Options} options - Prettier options * @param {Function} print - Print function to call recursively * @param {OperatorOptions} operatorOptions diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index e718b996111b..2e728379e296 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -1,5 +1,8 @@ "use strict"; +/** @typedef {import("../document").Doc} Doc */ + +/** @type {import("assert")} */ const assert = require("assert"); // TODO(azz): anything that imports from main shouldn't be in a `language-*` dir. @@ -256,7 +259,9 @@ function printPathNoParens(path, options, print, args) { return htmlBinding; } + /** @type{Doc[]} */ let parts = []; + switch (n.type) { case "JsExpressionRoot": return path.call(print, "node"); @@ -481,17 +486,23 @@ function printPathNoParens(path, options, print, args) { // ) const hasJSX = isJSXNode(n.right); - const firstGroupIndex = parts.findIndex((part) => part.type === "group"); + + const firstGroupIndex = parts.findIndex( + (part) => typeof part !== "string" && part.type === "group" + ); + // Separate the leftmost expression, possibly with its leading comments. const headParts = parts.slice( 0, firstGroupIndex === -1 ? 1 : firstGroupIndex + 1 ); + const rest = concat( parts.slice(headParts.length, hasJSX ? -1 : undefined) ); const groupId = Symbol("logicalChain-" + ++uid); + const chain = group( concat([ // Don't include the initial expression in the indentation @@ -672,8 +683,9 @@ function printPathNoParens(path, options, print, args) { options.locEnd ); return ( + nextCharacter !== false && options.originalText.slice(nextCharacter, nextCharacter + 2) === - "=>" + "=>" ); } ); @@ -4067,7 +4079,10 @@ function printReturnType(path, print, options) { function printExportDeclaration(path, options, print) { const decl = path.getValue(); + const semi = options.semi ? ";" : ""; + + /** @type{Doc[]} */ const parts = ["export "]; const isDefault = decl.default || decl.type === "ExportDefaultDeclaration"; @@ -4823,7 +4838,9 @@ function printBinaryishExpressions( isNested, isInsideParenthesis ) { + /** @type{Doc[]} */ let parts = []; + const node = path.getValue(); // We treat BinaryExpression and LogicalExpression nodes the same. diff --git a/src/language-js/types/estree.d.ts b/src/language-js/types/estree.d.ts new file mode 100644 index 000000000000..2be273170609 --- /dev/null +++ b/src/language-js/types/estree.d.ts @@ -0,0 +1,97 @@ +import * as ESTree from "estree"; +import * as Babel from "@babel/types"; +import { TSESTree } from "@typescript-eslint/types"; +import * as NGTree from "angular-estree-parser/lib/types"; + +// WORKAROUND HACK for typescript-eslint issue: +// https://github.com/typescript-eslint/typescript-eslint/issues/2388 +// MUST REMOVE to avoid errors with TypeScript 4.0.0: +import * as ts from "typescript"; +declare module "typescript" { + type NamedTupleMember = Node; +} + +type AdditionalFields = { + extra?: { + parenthesized?: boolean; + parenStart?: number; + raw?: string; + }; + comments?: Comment[]; + trailingComments?: ReadonlyArray | Comment[]; + leadingComments?: ReadonlyArray | Comment[]; +}; + +export type Comment = (ESTree.Comment | Babel.Comment | TSESTree.Comment) & { + printed?: boolean; + trailing?: boolean; + leading?: boolean; +}; + +export type Node = (ESTree.Node | Babel.Node | TSESTree.Node | NGTree.NGNode) & + AdditionalFields; + +export type TemplateLiteral = ( + | ESTree.TemplateLiteral + | Babel.TemplateLiteral + | TSESTree.TemplateLiteral +) & + AdditionalFields; + +export type CallExpression = ( + | ESTree.CallExpression + | Babel.CallExpression + | TSESTree.CallExpression +) & + AdditionalFields; + +export type OptionalCallExpression = ( + | Babel.OptionalCallExpression + | TSESTree.OptionalCallExpression +) & + AdditionalFields; + +export type MemberExpression = ( + | ESTree.MemberExpression + | Babel.MemberExpression + | TSESTree.MemberExpression +) & + AdditionalFields; + +export type OptionalMemberExpression = ( + | Babel.OptionalMemberExpression + | TSESTree.OptionalMemberExpression +) & + AdditionalFields; + +export type Expression = ( + | ESTree.Expression + | Babel.Expression + | TSESTree.Expression +) & + AdditionalFields; + +export type BindExpression = Babel.BindExpression & AdditionalFields; + +export type Property = (ESTree.Property | Babel.Property | TSESTree.Property) & + AdditionalFields; + +export type ClassPrivateProperty = Babel.ClassPrivateProperty & + AdditionalFields; + +export type ObjectTypeProperty = Babel.ObjectTypeProperty & AdditionalFields; + +export type JSXElement = (Babel.JSXElement | TSESTree.JSXElement) & + AdditionalFields; + +export type TaggedTemplateExpression = ( + | ESTree.TaggedTemplateExpression + | Babel.TaggedTemplateExpression + | TSESTree.TaggedTemplateExpression +) & + AdditionalFields; + +export type Literal = (ESTree.Literal | Babel.Literal | TSESTree.Literal) & + AdditionalFields; + +export { ESTree, Babel, TSESTree, NGTree }; diff --git a/src/language-js/utils.js b/src/language-js/utils.js index ae6f8ec46e67..cc45f3749d80 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -11,6 +11,24 @@ const { } = require("../common/util"); const handleComments = require("./comments"); +/** + * @typedef {import("./types/estree").Node} Node + * @typedef {import("./types/estree").TemplateLiteral} TemplateLiteral + * @typedef {import("./types/estree").Comment} Comment + * @typedef {import("./types/estree").MemberExpression} MemberExpression + * @typedef {import("./types/estree").OptionalMemberExpression} OptionalMemberExpression + * @typedef {import("./types/estree").CallExpression} CallExpression + * @typedef {import("./types/estree").OptionalCallExpression} OptionalCallExpression + * @typedef {import("./types/estree").Expression} Expression + * @typedef {import("./types/estree").Property} Property + * @typedef {import("./types/estree").ObjectTypeProperty} ObjectTypeProperty + * @typedef {import("./types/estree").JSXElement} JSXElement + * @typedef {import("./types/estree").TaggedTemplateExpression} TaggedTemplateExpression + * @typedef {import("./types/estree").Literal} Literal + * + * @typedef {import("../common/fast-path")} FastPath + */ + // We match any whitespace except line terminators because // Flow annotation comments cannot be split across lines. For example: // @@ -26,6 +44,10 @@ const FLOW_SHORTHAND_ANNOTATION = new RegExp( ); const FLOW_ANNOTATION = new RegExp(`^${NON_LINE_TERMINATING_WHITE_SPACE}*::`); +/** + * @param {Node} node + * @returns {boolean} + */ function hasFlowShorthandAnnotationComment(node) { // https://flow.org/en/docs/types/comments/ // Syntax example: const r = new (window.Request /*: Class */)(""); @@ -34,14 +56,23 @@ function hasFlowShorthandAnnotationComment(node) { node.extra && node.extra.parenthesized && node.trailingComments && - node.trailingComments[0].value.match(FLOW_SHORTHAND_ANNOTATION) + FLOW_SHORTHAND_ANNOTATION.test(node.trailingComments[0].value) ); } +/** + * @param {Comment[]} comments + * @returns {boolean} + */ function hasFlowAnnotationComment(comments) { - return comments && comments[0].value.match(FLOW_ANNOTATION); + return comments && FLOW_ANNOTATION.test(comments[0].value); } +/** + * @param {Node} node + * @param {(Node) => boolean} fn + * @returns {boolean} + */ function hasNode(node, fn) { if (!node || typeof node !== "object") { return false; @@ -55,6 +86,10 @@ function hasNode(node, fn) { : Object.keys(node).some((key) => hasNode(node[key], fn)); } +/** + * @param {Node} node + * @returns {boolean} + */ function hasNakedLeftSide(node) { return ( node.type === "AssignmentExpression" || @@ -115,7 +150,7 @@ function getLeftSidePathName(path, node) { if (node.expression) { return ["expression"]; } - throw new Error("Unexpected node has no left side", node); + throw new Error("Unexpected node has no left side."); } const exportDeclarationTypes = new Set([ @@ -125,10 +160,19 @@ const exportDeclarationTypes = new Set([ "ExportNamedDeclaration", "ExportAllDeclaration", ]); + +/** + * @param {Node} node + * @returns {boolean} + */ function isExportDeclaration(node) { return node && exportDeclarationTypes.has(node.type); } +/** + * @param {FastPath} path + * @returns {Node | null} + */ function getParentExportDeclaration(path) { const parentNode = path.getParentNode(); if (path.getName() === "declaration" && isExportDeclaration(parentNode)) { @@ -138,6 +182,10 @@ function getParentExportDeclaration(path) { return null; } +/** + * @param {Node} node + * @returns {boolean} + */ function isLiteral(node) { return ( node.type === "BooleanLiteral" || @@ -155,6 +203,10 @@ function isLiteral(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isLiteralLikeValue(node) { return ( isLiteral(node) || @@ -173,6 +225,10 @@ function isLiteralLikeValue(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isNumericLiteral(node) { return ( node.type === "NumericLiteral" || @@ -180,6 +236,10 @@ function isNumericLiteral(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isStringLiteral(node) { return ( node.type === "StringLiteral" || @@ -187,10 +247,18 @@ function isStringLiteral(node) { ); } -function isObjectType(n) { - return n.type === "ObjectTypeAnnotation" || n.type === "TSTypeLiteral"; +/** + * @param {Node} node + * @returns {boolean} + */ +function isObjectType(node) { + return node.type === "ObjectTypeAnnotation" || node.type === "TSTypeLiteral"; } +/** + * @param {Node} node + * @returns {boolean} + */ function isFunctionOrArrowExpression(node) { return ( node.type === "FunctionExpression" || @@ -198,6 +266,10 @@ function isFunctionOrArrowExpression(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isFunctionOrArrowExpressionWithBody(node) { return ( node.type === "FunctionExpression" || @@ -206,12 +278,21 @@ function isFunctionOrArrowExpressionWithBody(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isTemplateLiteral(node) { return node.type === "TemplateLiteral"; } -// `inject` is used in AngularJS 1.x, `async` in Angular 2+ -// example: https://docs.angularjs.org/guide/unit-testing#using-beforeall- +/** + * Note: `inject` is used in AngularJS 1.x, `async` in Angular 2+ + * example: https://docs.angularjs.org/guide/unit-testing#using-beforeall- + * + * @param {Node} node + * @returns {boolean} + */ function isAngularTestWrapper(node) { return ( (node.type === "CallExpression" || @@ -223,6 +304,10 @@ function isAngularTestWrapper(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isJSXNode(node) { return node.type === "JSXElement" || node.type === "JSXFragment"; } @@ -253,6 +338,10 @@ function isJSXWhitespaceExpression(node) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function isMemberExpressionChain(node) { if ( node.type !== "MemberExpression" && @@ -270,6 +359,11 @@ function isGetterOrSetter(node) { return node.kind === "get" || node.kind === "set"; } +/** + * @param {Node} nodeA + * @param {Node} nodeB + * @returns {boolean} + */ function sameLocStart(nodeA, nodeB, options) { return options.locStart(nodeA) === options.locStart(nodeB); } @@ -283,6 +377,10 @@ function isFunctionNotation(node, options) { // Hack to differentiate between the following two which have the same ast // type T = { method: () => void }; // type T = { method(): void }; +/** + * @param {Node} node + * @returns {boolean} + */ function isObjectTypePropertyAFunction(node, options) { return ( (node.type === "ObjectTypeProperty" || @@ -310,15 +408,24 @@ const binaryishNodeTypes = new Set([ "LogicalExpression", "NGPipeExpression", ]); + +/** + * @param {Node} node + * @returns {boolean} + */ function isBinaryish(node) { return binaryishNodeTypes.has(node.type); } +/** + * @param {Node} node + * @returns {boolean} + */ function isMemberish(node) { return ( node.type === "MemberExpression" || node.type === "OptionalMemberExpression" || - (node.type === "BindExpression" && node.object) + (node.type === "BindExpression" && Boolean(node.object)) ); } @@ -335,6 +442,11 @@ const flowTypeAnnotations = new Set([ "BooleanLiteralTypeAnnotation", "StringTypeAnnotation", ]); + +/** + * @param {Node} node + * @returns {boolean} + */ function isSimpleFlowType(node) { return ( node && @@ -345,6 +457,10 @@ function isSimpleFlowType(node) { const unitTestRe = /^(skip|[fx]?(it|describe|test))$/; +/** + * @param {CallExpression} node + * @returns {boolean} + */ function isSkipOrOnlyBlock(node) { return ( (node.callee.type === "MemberExpression" || @@ -357,12 +473,16 @@ function isSkipOrOnlyBlock(node) { ); } -function isUnitTestSetUp(n) { +/** + * @param {CallExpression} node + * @returns {boolean} + */ +function isUnitTestSetUp(node) { const unitTestSetUpRe = /^(before|after)(Each|All)$/; return ( - n.callee.type === "Identifier" && - unitTestSetUpRe.test(n.callee.name) && - n.arguments.length === 1 + node.callee.type === "Identifier" && + unitTestSetUpRe.test(node.callee.name) && + node.arguments.length === 1 ); } @@ -401,14 +521,26 @@ function isTestCall(n, parent) { return false; } +/** + * @param {Node} node + * @returns {boolean} + */ function hasLeadingComment(node) { return node.comments && node.comments.some((comment) => comment.leading); } +/** + * @param {Node} node + * @returns {boolean} + */ function hasTrailingComment(node) { return node.comments && node.comments.some((comment) => comment.trailing); } +/** + * @param {Node} node + * @returns {boolean} + */ function hasTrailingLineComment(node) { return ( node.comments && @@ -418,12 +550,20 @@ function hasTrailingLineComment(node) { ); } +/** + * @param {CallExpression | OptionalCallExpression} node + * @returns {boolean} + */ function isCallOrOptionalCallExpression(node) { return ( node.type === "CallExpression" || node.type === "OptionalCallExpression" ); } +/** + * @param {Node} node + * @returns {boolean} + */ function hasDanglingComments(node) { return ( node.comments && @@ -432,6 +572,10 @@ 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) { @@ -455,7 +599,11 @@ function isNgForOf(node, index, parentNode) { ); } -/** @param node {import("estree").TemplateLiteral} */ +/** + * + * @param {TemplateLiteral} node + * @returns {boolean} + */ function isSimpleTemplateLiteral(node) { if (node.expressions.length === 0) { return false; @@ -507,15 +655,18 @@ function isSimpleTemplateLiteral(node) { }); } -function getFlowVariance(path) { - if (!path.variance) { +/** + * @param {ObjectTypeProperty} node + */ +function getFlowVariance(node) { + if (!node.variance) { return null; } // Babel 7.0 currently uses variance node type, and flow should // follow suit soon: // https://github.com/babel/babel/issues/4722 - const variance = path.variance.kind || path.variance; + const variance = node.variance.kind || node.variance; switch (variance) { case "plus": @@ -528,6 +679,10 @@ function getFlowVariance(path) { } } +/** + * @param {FastPath} path + * @returns {boolean} + */ function classPropMayCauseASIProblems(path) { const node = path.getNode(); @@ -593,6 +748,11 @@ function classChildNeedsASIProtection(node) { } } +/** + * @param {string} tokenNode + * @param {string} keyword + * @returns {string} + */ function getTypeScriptMappedTypeModifier(tokenNode, keyword) { if (tokenNode === "+") { return "+" + keyword; @@ -623,6 +783,10 @@ const containsNonJsxWhitespaceRegex = new RegExp( // Meaningful if it contains non-whitespace characters, // or it contains whitespace without a new line. +/** + * @param {Node} node + * @returns {boolean} + */ function isMeaningfulJSXText(node) { return ( isLiteral(node) && @@ -631,6 +795,10 @@ function isMeaningfulJSXText(node) { ); } +/** + * @param {FastPath} path + * @returns {boolean} + */ function hasJsxIgnoreComment(path) { const node = path.getValue(); const parent = path.getParentNode(); @@ -661,6 +829,10 @@ function hasJsxIgnoreComment(path) { ); } +/** + * @param {JSXElement} node + * @returns {boolean} + */ function isEmptyJSXElement(node) { if (node.children.length === 0) { return true; @@ -675,10 +847,18 @@ function isEmptyJSXElement(node) { return isLiteral(child) && !isMeaningfulJSXText(child); } +/** + * @param {FastPath} path + * @returns {boolean} + */ function hasPrettierIgnore(path) { return hasIgnoreComment(path) || hasJsxIgnoreComment(path); } +/** + * @param {FastPath} path + * @returns {boolean} + */ function isLastStatement(path) { const parent = path.getParentNode(); if (!parent) { @@ -691,14 +871,26 @@ function isLastStatement(path) { return body && body[body.length - 1] === node; } +/** + * @param {string} text + * @param {Node} typeAnnotation + * @returns {boolean} + */ function isFlowAnnotationComment(text, typeAnnotation, options) { const start = options.locStart(typeAnnotation); const end = skipWhitespace(text, options.locEnd(typeAnnotation)); return ( - text.slice(start, start + 2) === "/*" && text.slice(end, end + 2) === "*/" + end !== false && + text.slice(start, start + 2) === "/*" && + text.slice(end, end + 2) === "*/" ); } +/** + * @param {string} text + * @param {Node} node + * @returns {boolean} + */ function hasLeadingOwnLineComment(text, node, options) { if (isJSXNode(node)) { return hasNodeIgnoreComment(node); @@ -784,6 +976,11 @@ function isSimpleNumber(numberString) { return /^(\d+|\d+\.\d+)$/.test(numberString); } +/** + * @param {Node} node + * @param {Node} parentNode + * @returns {boolean} + */ function isJestEachTemplateLiteral(node, parentNode) { /** * describe.each`table`(name, fn) @@ -813,10 +1010,19 @@ function isJestEachTemplateLiteral(node, parentNode) { ); } +/** + * @param {TemplateLiteral} template + * @returns {boolean} + */ function templateLiteralHasNewLines(template) { return template.quasis.some((quasi) => quasi.value.raw.includes("\n")); } +/** + * @param {TemplateLiteral | TaggedTemplateExpression} n + * @param {string} text + * @returns {boolean} + */ function isTemplateOnItsOwnLine(n, text, options) { return ( ((n.type === "TemplateLiteral" && templateLiteralHasNewLines(n)) || @@ -826,6 +1032,10 @@ function isTemplateOnItsOwnLine(n, text, options) { ); } +/** + * @param {Node} node + * @returns {boolean} + */ function needsHardlineAfterDanglingComment(node) { if (!node.comments) { return false; @@ -869,6 +1079,10 @@ function isFunctionCompositionArgs(args) { // `connect(a, b, c)(d)` // In the above call expression, the second call is the parent node and the // first call is the current node. +/** + * @param {FastPath} path + * @returns {boolean} + */ function isLongCurriedCallExpression(path) { const node = path.getValue(); const parent = path.getParentNode(); @@ -882,7 +1096,7 @@ function isLongCurriedCallExpression(path) { } /** - * @param {import('estree').Node} node + * @param {Node} node * @param {number} depth * @returns {boolean} */ @@ -894,7 +1108,7 @@ function isSimpleCallArgument(node, depth) { const isChildSimple = (child) => isSimpleCallArgument(child, depth + 1); const regexpPattern = - (node.type === "Literal" && node.regex && node.regex.pattern) || + (node.type === "Literal" && "regex" in node && node.regex.pattern) || (node.type === "RegExpLiteral" && node.pattern); if (regexpPattern && regexpPattern.length > 5) { @@ -997,9 +1211,15 @@ function shouldPrintComma(options, level = "es5") { ); } -// Tests if an expression starts with `{`, or (if forbidFunctionClassAndDoExpr -// holds) `function`, `class`, or `do {}`. Will be overzealous if there's -// already necessary grouping parentheses. +/** + * Tests if an expression starts with `{`, or (if forbidFunctionClassAndDoExpr + * holds) `function`, `class`, or `do {}`. Will be overzealous if there's + * already necessary grouping parentheses. + * + * @param {Node} node + * @param {boolean} forbidFunctionClassAndDoExpr + * @returns {boolean} + */ function startsWithNoLookaheadToken(node, forbidFunctionClassAndDoExpr) { node = getLeftMost(node); switch (node.type) { diff --git a/src/main/comments.js b/src/main/comments.js index 6e7c354d05ef..2fc7962ab360 100644 --- a/src/main/comments.js +++ b/src/main/comments.js @@ -1,6 +1,8 @@ "use strict"; -/** @type {any} */ + +/** @type {import("assert")} */ const assert = require("assert"); + const { concat, line, @@ -11,6 +13,7 @@ const { join, cursor, } = require("../document").builders; + const { hasNewline, skipNewline, @@ -20,6 +23,7 @@ const { addDanglingComment, addTrailingComment, } = require("../common/util"); + const childNodesCacheKey = Symbol("child-nodes"); function getSortedChildNodes(node, options, resultArray) { diff --git a/tsconfig.json b/tsconfig.json index 9337695a97ee..92b75bbccc60 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,6 @@ "src/main/ast-to-doc.js", "src/main/core.js", "src/common/create-ignorer.js", - "src/common/parser-create-error.js", "src/config/resolve-config.js", "src/main/options-normalizer.js", "src/common/get-file-info.js", @@ -24,7 +23,6 @@ "src/main/options.js", // [TBD] src/language-* source directory trees with known JSDoc type issues: "src/language-html", - "src/language-js", // [TBD] JavaScript sources affected by JSDoc issues in src/language-*: "src/common/load-plugins.js", "src/cli/index.js", diff --git a/yarn.lock b/yarn.lock index dfba94503dab..8023dff3ea04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -857,7 +857,7 @@ globals "^11.1.0" lodash "^4.17.19" -"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/types@7.11.0", "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== @@ -1299,7 +1299,7 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/estree@*": +"@types/estree@*", "@types/estree@0.0.45": version "0.0.45" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== @@ -1358,6 +1358,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== +"@types/node@14.6.0": + version "14.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.0.tgz#7d4411bf5157339337d7cff864d9ff45f177b499" + integrity sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"