diff --git a/.eslintrc.yml b/.eslintrc.yml index 26bc7af25..4b4ebfc6e 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -109,3 +109,16 @@ overrides: - src/language-*/**/*.js rules: prettier-internal-rules/directly-loc-start-end: error + - files: + - src/language-js/**/*.js + rules: + prettier-internal-rules/no-node-comments: + - error + - file: "src/language-js/utils.js" + functions: + - hasComment + - getComments + - "src/language-js/parse-postprocess.js" + - "src/language-js/parser-babel.js" + - "src/language-js/parser-meriyah.js" + - "src/language-js/pragma.js" diff --git a/package.json b/package.json index 706f5d42a..7479d4de3 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "eslint-formatter-friendly": "7.0.0", "eslint-plugin-import": "2.22.1", "eslint-plugin-jest": "24.1.3", - "eslint-plugin-prettier-internal-rules": "file:scripts/tools/eslint-plugin-prettier-internal-rules", + "eslint-plugin-prettier-internal-rules": "link:scripts/tools/eslint-plugin-prettier-internal-rules", "eslint-plugin-react": "7.21.5", "eslint-plugin-unicorn": "23.0.0", "execa": "4.1.0", diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js index 9436ff11b..412f6a982 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js @@ -4,6 +4,7 @@ module.exports = { rules: { "better-parent-property-check-in-needs-parens": require("./better-parent-property-check-in-needs-parens"), "directly-loc-start-end": require("./directly-loc-start-end"), + "no-node-comments": require("./no-node-comments"), "prefer-fast-path-each": require("./prefer-fast-path-each"), "require-json-extensions": require("./require-json-extensions"), }, diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/no-node-comments.js b/scripts/tools/eslint-plugin-prettier-internal-rules/no-node-comments.js new file mode 100644 index 000000000..761911b0e --- /dev/null +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/no-node-comments.js @@ -0,0 +1,85 @@ +"use strict"; +const path = require("path"); + +// `node.comments` +const memberExpressionSelector = [ + "MemberExpression[computed=false]", + "", + ">", + "Identifier.property", + '[name="comments"]', +].join(""); + +// `const {comments} = node` +// `const {comments: nodeComments} = node` +const objectPatternSelector = [ + "ObjectPattern", + ">", + "Property.properties", + ">", + "Identifier.key", + '[name="comments"]', +].join(""); + +const selector = `:matches(${memberExpressionSelector}, ${objectPatternSelector})`; + +const messageId = "no-node-comments"; + +module.exports = { + meta: { + type: "suggestion", + docs: { + url: + "https://github.com/prettier/prettier/blob/master/scripts/eslint-plugin-prettier-internal-rules/no-node-comments.js", + }, + messages: { + [messageId]: "Do not access node.comments.", + }, + }, + create(context) { + const fileName = context.getFilename(); + const ignored = new Map( + context.options.map((option) => { + if (typeof option === "string") { + option = { file: option }; + } + const { file, functions } = option; + return [ + path.join(__dirname, "../../..", file), + functions ? new Set(functions) : true, + ]; + }) + ); + return { + [selector](node) { + if (ignored.has(fileName)) { + const functionNames = ignored.get(fileName); + if (functionNames === true) { + return; + } + let isIgnored; + let currentNode = node.parent; + while (currentNode) { + if ( + currentNode.type === "FunctionDeclaration" && + currentNode.id && + currentNode.id.type === "Identifier" && + functionNames.has(currentNode.id.name) + ) { + isIgnored = true; + break; + } + currentNode = currentNode.parent; + } + if (isIgnored) { + return; + } + } + context.report({ + node, + messageId, + }); + }, + }; + }, +}; diff --git a/src/language-js/comments.js b/src/language-js/comments.js index 0433fe3c6..af0866175 100644 --- a/src/language-js/comments.js +++ b/src/language-js/comments.js @@ -19,6 +19,8 @@ const { hasFlowShorthandAnnotationComment, hasFlowAnnotationComment, hasIgnoreComment, + hasComment, + CommentCheckFlags, } = require("./utils"); const { locStart, locEnd } = require("./loc"); @@ -873,10 +875,7 @@ function hasLeadingComment(node, fn = () => true) { if (node.leadingComments) { return node.leadingComments.some(fn); } - if (node.comments) { - return node.comments.some((comment) => comment.leading && fn(comment)); - } - return false; + return hasComment(node, CommentCheckFlags.Leading, fn); } /** diff --git a/src/language-js/print/angular.js b/src/language-js/print/angular.js index b370d1f8c..f025aeeae 100644 --- a/src/language-js/print/angular.js +++ b/src/language-js/print/angular.js @@ -3,7 +3,7 @@ const { builders: { concat, join, line, group }, } = require("../../document"); -const { hasNode } = require("../utils"); +const { hasNode, hasComment, getComments } = require("../utils"); const { printBinaryishExpression } = require("./binaryish"); /** @typedef {import("../../common/fast-path")} FastPath */ @@ -15,9 +15,9 @@ function printAngular(path, options, print) { return concat( [].concat( path.call(print, "node"), - !n.node.comments || n.node.comments.length === 0 + !hasComment(n.node) ? [] - : concat([" //", n.node.comments[0].value.trimEnd()]) + : concat([" //", getComments(n.node)[0].value.trimEnd()]) ) ); case "NGPipeExpression": diff --git a/src/language-js/print/array.js b/src/language-js/print/array.js index 845dd1dba..ce7925b6b 100644 --- a/src/language-js/print/array.js +++ b/src/language-js/print/array.js @@ -5,7 +5,7 @@ const { builders: { concat, line, softline, group, indent, ifBreak }, } = require("../../document"); const { getLast, isNextLineEmpty } = require("../../common/util"); -const { hasDanglingComments, shouldPrintComma } = require("../utils"); +const { shouldPrintComma, hasComment, CommentCheckFlags } = require("../utils"); const { locEnd } = require("../loc"); const { printOptionalToken, printTypeAnnotation } = require("./misc"); @@ -20,7 +20,7 @@ function printArray(path, options, print) { const openBracket = n.type === "TupleExpression" ? "#[" : "["; const closeBracket = "]"; if (n.elements.length === 0) { - if (!hasDanglingComments(n)) { + if (!hasComment(n, CommentCheckFlags.Dangling)) { parts.push(openBracket, closeBracket); } else { parts.push( diff --git a/src/language-js/print/binaryish.js b/src/language-js/print/binaryish.js index f6edc26a5..a1c736d88 100644 --- a/src/language-js/print/binaryish.js +++ b/src/language-js/print/binaryish.js @@ -8,10 +8,11 @@ const { } = require("../../document"); const { hasLeadingOwnLineComment, - hasTrailingLineComment, isBinaryish, isJSXNode, shouldFlatten, + hasComment, + CommentCheckFlags, } = require("../utils"); /** @typedef {import("../../document").Doc} Doc */ @@ -250,7 +251,10 @@ function printBinaryishExpressions( // If there's only a single binary expression, we want to create a group // in order to avoid having a small right part like -1 be on its own line. const parent = path.getParentNode(); - const shouldBreak = hasTrailingLineComment(node.left); + const shouldBreak = hasComment( + node.left, + CommentCheckFlags.Trailing | CommentCheckFlags.Line + ); const shouldGroup = shouldBreak || (!(isInsideParenthesis && node.type === "LogicalExpression") && @@ -266,7 +270,7 @@ function printBinaryishExpressions( // The root comments are already printed, but we need to manually print // the other ones since we don't call the normal print on BinaryExpression, // only for the left and right parts - if (isNested && node.comments) { + if (isNested && hasComment(node)) { parts = normalizeParts( printComments(path, () => concat(parts), options).parts ); diff --git a/src/language-js/print/block.js b/src/language-js/print/block.js index b4a7f5051..aacffab1e 100644 --- a/src/language-js/print/block.js +++ b/src/language-js/print/block.js @@ -5,7 +5,7 @@ const { isNextLineEmpty } = require("../../common/util"); const { builders: { concat, hardline, indent }, } = require("../../document"); -const { hasDanglingComments } = require("../utils"); +const { hasComment, CommentCheckFlags } = require("../utils"); const { locEnd } = require("../loc"); const { printStatementSequence } = require("./statement"); @@ -32,7 +32,7 @@ function printBlock(path, options, print) { if ( !hasContent && !hasDirectives && - !hasDanglingComments(n) && + !hasComment(n, CommentCheckFlags.Dangling) && (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration" || diff --git a/src/language-js/print/call-arguments.js b/src/language-js/print/call-arguments.js index 0561f1b8b..d5c943eb0 100644 --- a/src/language-js/print/call-arguments.js +++ b/src/language-js/print/call-arguments.js @@ -9,8 +9,8 @@ const { const { getFunctionParameters, iterateFunctionParametersPath, - hasLeadingComment, - hasTrailingComment, + hasComment, + CommentCheckFlags, isFunctionCompositionArgs, isJSXNode, isLongCurriedCallExpression, @@ -55,7 +55,7 @@ function printCallArguments(path, options, print) { getFunctionParameters(args[0]).length === 0 && args[0].body.type === "BlockStatement" && args[1].type === "ArrayExpression" && - !args.some((arg) => arg.comments) + !args.some((arg) => hasComment(arg)) ) { return concat([ "(", @@ -235,9 +235,9 @@ function printCallArguments(path, options, print) { function couldGroupArg(arg) { return ( (arg.type === "ObjectExpression" && - (arg.properties.length > 0 || arg.comments)) || + (arg.properties.length > 0 || hasComment(arg))) || (arg.type === "ArrayExpression" && - (arg.elements.length > 0 || arg.comments)) || + (arg.elements.length > 0 || hasComment(arg))) || (arg.type === "TSTypeAssertion" && couldGroupArg(arg.expression)) || (arg.type === "TSAsExpression" && couldGroupArg(arg.expression)) || arg.type === "FunctionExpression" || @@ -271,8 +271,8 @@ function shouldGroupLastArg(args) { const lastArg = getLast(args); const penultimateArg = getPenultimate(args); return ( - !hasLeadingComment(lastArg) && - !hasTrailingComment(lastArg) && + !hasComment(lastArg, CommentCheckFlags.Leading) && + !hasComment(lastArg, CommentCheckFlags.Trailing) && couldGroupArg(lastArg) && // If the last two arguments are of the same type, // disable last element expansion. @@ -287,7 +287,7 @@ function shouldGroupFirstArg(args) { const [firstArg, secondArg] = args; return ( - (!firstArg.comments || !firstArg.comments.length) && + !hasComment(firstArg) && (firstArg.type === "FunctionExpression" || (firstArg.type === "ArrowFunctionExpression" && firstArg.body.type === "BlockStatement")) && diff --git a/src/language-js/print/class.js b/src/language-js/print/class.js index 384ff81cd..add6caafa 100644 --- a/src/language-js/print/class.js +++ b/src/language-js/print/class.js @@ -5,8 +5,8 @@ const { builders: { concat, join, line, hardline, softline, group, indent, ifBreak }, } = require("../../document"); const { - hasTrailingComment, - hasTrailingLineComment, + hasComment, + CommentCheckFlags, hasNewlineBetweenOrAfterDecorators, } = require("../utils"); const { getTypeParametersGroupId } = require("./type-parameters"); @@ -33,10 +33,8 @@ function printClass(path, options, print) { // Keep old behaviour of extends in same line // If there is only on extends and there are not comments const groupMode = - (n.id && hasTrailingComment(n.id)) || - (n.superClass && - n.superClass.comments && - n.superClass.comments.length !== 0) || + (n.id && hasComment(n.id, CommentCheckFlags.Trailing)) || + (n.superClass && hasComment(n.superClass)) || (n.extends && n.extends.length !== 0) || // DeclareClass (n.mixins && n.mixins.length !== 0) || (n.implements && n.implements.length !== 0); @@ -105,7 +103,10 @@ function hasMultipleHeritage(node) { function shouldIndentOnlyHeritageClauses(node) { return ( node.typeParameters && - !hasTrailingLineComment(node.typeParameters) && + !hasComment( + node.typeParameters, + CommentCheckFlags.Trailing | CommentCheckFlags.Line + ) && !hasMultipleHeritage(node) ); } @@ -177,7 +178,7 @@ function printClassMethod(path, options, print) { function printClassBody(path, options, print) { const n = path.getValue(); - if (!n.comments && n.body.length === 0) { + if (!hasComment(n) && n.body.length === 0) { return "{}"; } diff --git a/src/language-js/print/function-parameters.js b/src/language-js/print/function-parameters.js index 7a8d696ca..bcdb6c903 100644 --- a/src/language-js/print/function-parameters.js +++ b/src/language-js/print/function-parameters.js @@ -19,6 +19,7 @@ const { isObjectTypePropertyAFunction, hasRestParameter, shouldPrintComma, + hasComment, } = require("../utils"); const { locEnd } = require("../loc"); const { printFunctionTypeParameters } = require("./misc"); @@ -59,7 +60,7 @@ function printFunctionParameters( const isParametersInTestCall = isTestCall(parent); const shouldHugParameters = shouldHugFunctionParameters(functionNode); const shouldExpandParameters = - expandArg && !parameters.some((node) => node.comments); + expandArg && !parameters.some((node) => hasComment(node)); const printed = []; iterateFunctionParametersPath(path, (parameterPath, index) => { const isLastParameter = index === parameters.length - 1; @@ -173,7 +174,7 @@ function shouldHugFunctionParameters(node) { } const [parameter] = parameters; return ( - !parameter.comments && + !hasComment(parameter) && (parameter.type === "ObjectPattern" || parameter.type === "ArrayPattern" || (parameter.type === "Identifier" && diff --git a/src/language-js/print/function.js b/src/language-js/print/function.js index 8f5aa69c1..df4ee059b 100644 --- a/src/language-js/print/function.js +++ b/src/language-js/print/function.js @@ -11,7 +11,6 @@ const { } = require("../../document"); const { getFunctionParameters, - hasDanglingComments, hasLeadingOwnLineComment, isFlowAnnotationComment, isJSXNode, @@ -21,6 +20,9 @@ const { returnArgumentHasLeadingComment, isBinaryish, isLineComment, + hasComment, + getComments, + CommentCheckFlags, } = require("../utils"); const { locEnd } = require("../loc"); const { printFunctionParameters } = require("./function-parameters"); @@ -207,7 +209,7 @@ function printArrowFunctionExpression(path, options, print, args) { const shouldAddSoftLine = ((args && args.expandLastArg) || path.getParentNode().type === "JSXExpressionContainer") && - !(n.comments && n.comments.length); + !hasComment(n); const printTrailingComma = args && args.expandLastArg && shouldPrintComma(options, "all"); @@ -246,10 +248,10 @@ function canPrintParamsWithoutParens(node) { return ( parameters.length === 1 && !node.typeParameters && - !hasDanglingComments(node) && + !hasComment(node, CommentCheckFlags.Dangling) && parameters[0].type === "Identifier" && !parameters[0].typeAnnotation && - !parameters[0].comments && + !hasComment(parameters[0]) && !parameters[0].optional && !node.predicate && !node.returnType @@ -333,15 +335,15 @@ function printReturnAndThrowArgument(path, options, print) { } } - const lastComment = - Array.isArray(node.comments) && node.comments[node.comments.length - 1]; + const comments = getComments(node); + const lastComment = comments[comments.length - 1]; const isLastCommentLine = lastComment && isLineComment(lastComment); if (isLastCommentLine) { parts.push(semi); } - if (hasDanglingComments(node)) { + if (hasComment(node, CommentCheckFlags.Dangling)) { parts.push( " ", printDanglingComments(path, options, /* sameIndent */ true) diff --git a/src/language-js/print/interface.js b/src/language-js/print/interface.js index 49b9cf7c2..84f42657e 100644 --- a/src/language-js/print/interface.js +++ b/src/language-js/print/interface.js @@ -3,11 +3,7 @@ const { builders: { concat, join, line, group, indent, ifBreak }, } = require("../../document"); -const { - hasTrailingComment, - hasTrailingLineComment, - identity, -} = require("../utils"); +const { hasComment, identity, CommentCheckFlags } = require("../utils"); const { getTypeParametersGroupId } = require("./type-parameters"); const { printTypeScriptModifiers } = require("./misc"); @@ -39,7 +35,11 @@ function printInterface(path, options, print) { } const shouldIndentOnlyHeritageClauses = - n.typeParameters && !hasTrailingLineComment(n.typeParameters); + n.typeParameters && + !hasComment( + n.typeParameters, + CommentCheckFlags.Trailing | CommentCheckFlags.Line + ); if (n.extends && n.extends.length !== 0) { extendsParts.push( @@ -56,7 +56,7 @@ function printInterface(path, options, print) { } if ( - (n.id && hasTrailingComment(n.id)) || + (n.id && hasComment(n.id, CommentCheckFlags.Trailing)) || (n.extends && n.extends.length !== 0) ) { const printedExtends = concat(extendsParts); diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index b25913e9c..c2a621527 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -20,7 +20,6 @@ const { const { getLast, getPreferredQuote } = require("../../common/util"); const { - hasTrailingComment, isEmptyJSXElement, isJSXWhitespaceExpression, isJSXNode, @@ -31,8 +30,8 @@ const { isCallOrOptionalCallExpression, isStringLiteral, isBinaryish, - isBlockComment, - isLineComment, + hasComment, + CommentCheckFlags, } = require("../utils"); const pathNeedsParens = require("../needs-parens"); const { willPrintOwnComments } = require("../comments"); @@ -508,11 +507,9 @@ function printJsxExpressionContainer(path, options, print) { const n = path.getValue(); const parent = path.getParentNode(0); - const hasComments = n.expression.comments && n.expression.comments.length > 0; - const shouldInline = n.expression.type === "JSXEmptyExpression" || - (!hasComments && + (!hasComment(n.expression) && (n.expression.type === "ArrayExpression" || n.expression.type === "ObjectExpression" || n.expression.type === "ArrowFunctionExpression" || @@ -547,10 +544,8 @@ function printJsxOpeningElement(path, options, print) { const n = path.getValue(); const nameHasComments = - (n.name && n.name.comments && n.name.comments.length > 0) || - (n.typeParameters && - n.typeParameters.comments && - n.typeParameters.comments.length > 0); + (n.name && hasComment(n.name)) || + (n.typeParameters && hasComment(n.typeParameters)); // Don't break self-closing elements with no attributes and no comments if (n.selfClosing && !n.attributes.length && !nameHasComments) { @@ -579,7 +574,7 @@ function printJsxOpeningElement(path, options, print) { // // comment // > !nameHasComments && - (!n.attributes[0].comments || !n.attributes[0].comments.length) + !hasComment(n.attributes[0]) ) { return group( concat([ @@ -594,7 +589,8 @@ function printJsxOpeningElement(path, options, print) { } const lastAttrHasTrailingComments = - n.attributes.length && hasTrailingComment(getLast(n.attributes)); + n.attributes.length && + hasComment(getLast(n.attributes), CommentCheckFlags.Trailing); const bracketSameLine = // Simple tags (no attributes and no comment in tag name) should be @@ -646,16 +642,10 @@ function printJsxClosingElement(path, options, print) { parts.push(" comment.leading && isLineComment(comment)) - ) { + if (hasComment(n.name, CommentCheckFlags.Leading | CommentCheckFlags.Line)) { parts.push(indent(concat([hardline, printed])), hardline); } else if ( - Array.isArray(n.name.comments) && - n.name.comments.some( - (comment) => comment.leading && isBlockComment(comment) - ) + hasComment(n.name, CommentCheckFlags.Leading | CommentCheckFlags.Block) ) { parts.push(" ", printed); } else { @@ -669,9 +659,8 @@ function printJsxClosingElement(path, options, print) { function printJsxOpeningClosingFragment(path, options /*, print*/) { const n = path.getValue(); - const hasComment = n.comments && n.comments.length; - const hasOwnLineComment = - hasComment && !n.comments.every((comment) => isBlockComment(comment)); + const nodeHasComment = hasComment(n); + const hasOwnLineComment = hasComment(n, CommentCheckFlags.Line); const isOpeningFragment = n.type === "JSXOpeningFragment"; return concat([ isOpeningFragment ? "<" : " isBlockComment(comment)); + const requiresHardline = hasComment(n, CommentCheckFlags.Line); return concat([ printDanglingComments(path, options, /* sameIndent */ !requiresHardline), @@ -719,7 +707,7 @@ function printJsxSpreadAttribute(path, options, print) { (p) => { const printed = concat(["...", print(p)]); const n = p.getValue(); - if (!n.comments || !n.comments.length || !willPrintOwnComments(p)) { + if (!hasComment(n) || !willPrintOwnComments(p)) { return printed; } return concat([ diff --git a/src/language-js/print/member-chain.js b/src/language-js/print/member-chain.js index 16247fdb3..508595982 100644 --- a/src/language-js/print/member-chain.js +++ b/src/language-js/print/member-chain.js @@ -11,14 +11,14 @@ const { } = require("../../common/util"); const pathNeedsParens = require("../needs-parens"); const { - hasLeadingComment, - hasTrailingComment, isCallOrOptionalCallExpression, isFunctionOrArrowExpression, isLongCurriedCallExpression, isMemberish, isNumericLiteral, isSimpleCallArgument, + hasComment, + CommentCheckFlags, } = require("../utils"); const { locEnd } = require("../loc"); @@ -244,10 +244,7 @@ function printMemberChain(path, options, print) { } currentGroup.push(printedNodes[i]); - if ( - printedNodes[i].node.comments && - printedNodes[i].node.comments.some((comment) => comment.trailing) - ) { + if (hasComment(printedNodes[i].node, CommentCheckFlags.Trailing)) { groups.push(currentGroup); currentGroup = []; hasSeenCallExpression = false; @@ -308,7 +305,9 @@ function printMemberChain(path, options, print) { } const shouldMerge = - groups.length >= 2 && !groups[1][0].node.comments && shouldNotWrap(groups); + groups.length >= 2 && + !hasComment(groups[1][0].node) && + shouldNotWrap(groups); function printGroup(printedGroup) { const printed = printedGroup.map((tuple) => tuple.printed); @@ -339,14 +338,19 @@ function printMemberChain(path, options, print) { const cutoff = shouldMerge ? 3 : 2; const flatGroups = flat(groups); - const hasComment = - flatGroups.slice(1, -1).some((node) => hasLeadingComment(node.node)) || - flatGroups.slice(0, -1).some((node) => hasTrailingComment(node.node)) || - (groups[cutoff] && hasLeadingComment(groups[cutoff][0].node)); + const nodeHasComment = + flatGroups + .slice(1, -1) + .some((node) => hasComment(node.node, CommentCheckFlags.Leading)) || + flatGroups + .slice(0, -1) + .some((node) => hasComment(node.node, CommentCheckFlags.Trailing)) || + (groups[cutoff] && + hasComment(groups[cutoff][0].node, CommentCheckFlags.Leading)); // If we only have a single `.`, we shouldn't do anything fancy and just // render everything concatenated together. - if (groups.length <= cutoff && !hasComment) { + if (groups.length <= cutoff && !nodeHasComment) { if (isLongCurriedCallExpression(path)) { return oneLine; } @@ -390,7 +394,7 @@ function printMemberChain(path, options, print) { // * any group but the last one has a hard line, // * the last call's arguments have a hard line and other calls have non-trivial arguments. if ( - hasComment || + nodeHasComment || (callExpressions.length > 2 && callExpressions.some( (expr) => !expr.arguments.every((arg) => isSimpleCallArgument(arg, 0)) diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js index d8d5a7021..af0de27b9 100644 --- a/src/language-js/print/module.js +++ b/src/language-js/print/module.js @@ -6,7 +6,8 @@ const { const { printDanglingComments } = require("../../main/comments"); const { - hasDanglingComments, + hasComment, + CommentCheckFlags, shouldPrintComma, needsHardlineAfterDanglingComment, } = require("../utils"); @@ -55,7 +56,7 @@ function printExportDeclaration(path, options, print) { parts.push(" default"); } - if (hasDanglingComments(node)) { + if (hasComment(node, CommentCheckFlags.Dangling)) { parts.push( " ", printDanglingComments(path, options, /* sameIndent */ true) @@ -203,7 +204,7 @@ function printModuleSpecifiers(path, options, print) { const canBreak = groupedSpecifiers.length > 1 || standaloneSpecifiers.length > 0 || - node.specifiers.some((node) => node.comments); + node.specifiers.some((node) => hasComment(node)); if (canBreak) { parts.push( diff --git a/src/language-js/print/object.js b/src/language-js/print/object.js index bff624c68..a5546b1a9 100644 --- a/src/language-js/print/object.js +++ b/src/language-js/print/object.js @@ -11,10 +11,10 @@ const { hasNewline, } = require("../../common/util"); const { - hasDanglingComments, shouldPrintComma, - hasNodeIgnoreComment, - isBlockComment, + hasComment, + getComments, + CommentCheckFlags, } = require("../utils"); const { locStart, locEnd } = require("../loc"); @@ -116,7 +116,7 @@ function printObject(path, options, print) { (prop.node.type === "TSPropertySignature" || prop.node.type === "TSMethodSignature" || prop.node.type === "TSConstructSignatureDeclaration") && - hasNodeIgnoreComment(prop.node) + hasComment(prop.node, CommentCheckFlags.PrettierIgnore) ) { separatorParts.shift(); } @@ -128,10 +128,8 @@ function printObject(path, options, print) { if (n.inexact) { let printed; - if (hasDanglingComments(n)) { - const hasLineComments = !n.comments.every((comment) => - isBlockComment(comment) - ); + if (hasComment(n, CommentCheckFlags.Dangling)) { + const hasLineComments = hasComment(n, CommentCheckFlags.Line); const printedDanglingComments = printDanglingComments( path, options, @@ -140,10 +138,7 @@ function printObject(path, options, print) { printed = concat([ printedDanglingComments, hasLineComments || - hasNewline( - options.originalText, - locEnd(n.comments[n.comments.length - 1]) - ) + hasNewline(options.originalText, locEnd(getLast(getComments(n)))) ? hardline : line, "...", @@ -164,12 +159,12 @@ function printObject(path, options, print) { lastElem.type === "TSCallSignatureDeclaration" || lastElem.type === "TSMethodSignature" || lastElem.type === "TSConstructSignatureDeclaration") && - hasNodeIgnoreComment(lastElem)) + hasComment(lastElem, CommentCheckFlags.PrettierIgnore)) ); let content; if (props.length === 0) { - if (!hasDanglingComments(n)) { + if (!hasComment(n, CommentCheckFlags.Dangling)) { return concat([ leftBrace, rightBrace, diff --git a/src/language-js/print/statement.js b/src/language-js/print/statement.js index a4a95c909..427a65295 100644 --- a/src/language-js/print/statement.js +++ b/src/language-js/print/statement.js @@ -13,6 +13,8 @@ const { isJSXNode, isLastStatement, isTheOnlyJSXElementInMarkdown, + hasComment, + CommentCheckFlags, } = require("../utils"); const { locEnd } = require("../loc"); const { shouldPrintParamsWithoutParens } = require("./function"); @@ -47,7 +49,7 @@ function printStatement({ path, index, bodyNode, isClass }, options, print) { !isTheOnlyJSXElementInMarkdown(options, path) && statementNeedsASIProtection(path, options) ) { - if (node.comments && node.comments.some((comment) => comment.leading)) { + if (hasComment(node, CommentCheckFlags.Leading)) { parts.push(print(path, { needsSemi: true })); } else { parts.push(";", printed); diff --git a/src/language-js/print/template-literal.js b/src/language-js/print/template-literal.js index 157ddc018..81f68121e 100644 --- a/src/language-js/print/template-literal.js +++ b/src/language-js/print/template-literal.js @@ -20,6 +20,7 @@ const { isBinaryish, isJestEachTemplateLiteral, isSimpleTemplateLiteral, + hasComment, } = require("../utils"); function printTemplateLiteral(path, print, options) { @@ -81,7 +82,7 @@ function printTemplateLiteral(path, print, options) { // Breaks at the template element boundaries (${ and }) are preferred to breaking // in the middle of a MemberExpression if ( - (expression.comments && expression.comments.length) || + hasComment(expression) || expression.type === "MemberExpression" || expression.type === "OptionalMemberExpression" || expression.type === "ConditionalExpression" || @@ -201,7 +202,7 @@ function printJestEachTemplateLiteral(path, options, print) { function printTemplateExpression(path, print) { const node = path.getValue(); let printed = print(path); - if (node.comments && node.comments.length) { + if (hasComment(node)) { printed = group(concat([indent(concat([softline, printed])), softline])); } return concat(["${", printed, lineSuffixBoundary, "}"]); diff --git a/src/language-js/print/ternary.js b/src/language-js/print/ternary.js index 580074850..43da1f587 100644 --- a/src/language-js/print/ternary.js +++ b/src/language-js/print/ternary.js @@ -2,7 +2,7 @@ const flat = require("lodash/flatten"); const { hasNewlineInRange } = require("../../common/util"); -const { isJSXNode, isBlockComment } = require("../utils"); +const { isJSXNode, isBlockComment, getComments } = require("../utils"); const { locStart, locEnd } = require("../loc"); const { builders: { @@ -277,10 +277,12 @@ function printTernary(path, options, print) { // break if any of them break. That means we should only group around the // outer-most ConditionalExpression. const comments = flat([ - ...testNodePropertyNames.map((propertyName) => node[propertyName].comments), - consequentNode.comments, - alternateNode.comments, - ]).filter(Boolean); + ...testNodePropertyNames.map((propertyName) => + getComments(node[propertyName]) + ), + getComments(consequentNode), + getComments(alternateNode), + ]); const shouldBreak = comments.some( (comment) => isBlockComment(comment) && diff --git a/src/language-js/print/type-parameters.js b/src/language-js/print/type-parameters.js index 2fe3029c6..eb1e9fc79 100644 --- a/src/language-js/print/type-parameters.js +++ b/src/language-js/print/type-parameters.js @@ -5,9 +5,9 @@ const { builders: { concat, join, line, hardline, softline, group, indent, ifBreak }, } = require("../../document"); const { - hasDanglingComments, isTestCall, - isBlockComment, + hasComment, + CommentCheckFlags, isTSXFile, shouldPrintComma, getFunctionParameters, @@ -82,12 +82,10 @@ function printTypeParameters(path, options, print, paramsKey) { function printDanglingCommentsForInline(path, options) { const n = path.getValue(); - if (!hasDanglingComments(n)) { + if (!hasComment(n, CommentCheckFlags.Dangling)) { return ""; } - const hasOnlyBlockComments = n.comments.every((comment) => - isBlockComment(comment) - ); + const hasOnlyBlockComments = !hasComment(n, CommentCheckFlags.Line); const printed = printDanglingComments( path, options, diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 42d31381d..e02dd375e 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -35,11 +35,11 @@ const preprocess = require("./print-preprocess"); const { getCallArguments, getParentExportDeclaration, - hasDanglingComments, hasFlowShorthandAnnotationComment, hasNewlineBetweenOrAfterDecorators, hasPrettierIgnore, - hasTrailingComment, + hasComment, + CommentCheckFlags, isExportDeclaration, isFunctionNotation, isGetterOrSetter, @@ -261,7 +261,7 @@ function printPathNoParens(path, options, print, args) { case "Program": { const hasContents = - !n.body.every(({ type }) => type === "EmptyStatement") || n.comments; + !n.body.every(({ type }) => type === "EmptyStatement") || hasComment(n); // Babel 6 if (n.directives) { @@ -324,7 +324,7 @@ function printPathNoParens(path, options, print, args) { ]); // Babel non-standard node. Used for Closure-style type casts. See postprocess.js. case "ParenthesizedExpression": { - const shouldHug = !n.expression.comments; + const shouldHug = !hasComment(n.expression); if (shouldHug) { return concat(["(", path.call(print, "expression"), ")"]); } @@ -568,7 +568,7 @@ function printPathNoParens(path, options, print, args) { parts.push(" "); } - if (n.argument.comments && n.argument.comments.length > 0) { + if (hasComment(n.argument)) { parts.push( group( concat([ @@ -611,7 +611,7 @@ function printPathNoParens(path, options, print, args) { const hasValue = n.declarations.some((decl) => decl.init); let firstVariable; - if (printed.length === 1 && !n.declarations[0].comments) { + if (printed.length === 1 && !hasComment(n.declarations[0])) { firstVariable = printed[0]; } else if (printed.length > 0) { // Indent first var to comply with eslint one-var rule @@ -668,16 +668,15 @@ function printPathNoParens(path, options, print, args) { if (n.alternate) { const commentOnOwnLine = - (hasTrailingComment(n.consequent) && - n.consequent.comments.some( - (comment) => comment.trailing && !isBlockComment(comment) - )) || - needsHardlineAfterDanglingComment(n); + hasComment( + n.consequent, + CommentCheckFlags.Trailing | CommentCheckFlags.Line + ) || needsHardlineAfterDanglingComment(n); const elseOnSameLine = n.consequent.type === "BlockStatement" && !commentOnOwnLine; parts.push(elseOnSameLine ? " " : hardline); - if (hasDanglingComments(n)) { + if (hasComment(n, CommentCheckFlags.Dangling)) { parts.push( comments.printDanglingComments(path, options, true), commentOnOwnLine ? hardline : " " @@ -849,23 +848,22 @@ function printPathNoParens(path, options, print, args) { ]); case "CatchClause": if (n.param) { - const hasComments = - n.param.comments && - n.param.comments.some( - (comment) => - !isBlockComment(comment) || - (comment.leading && - hasNewline(options.originalText, locEnd(comment))) || - (comment.trailing && - hasNewline(options.originalText, locStart(comment), { - backwards: true, - })) - ); + const parameterHasComments = hasComment( + n.param, + (comment) => + !isBlockComment(comment) || + (comment.leading && + hasNewline(options.originalText, locEnd(comment))) || + (comment.trailing && + hasNewline(options.originalText, locStart(comment), { + backwards: true, + })) + ); const param = path.call(print, "param"); return concat([ "catch ", - hasComments + parameterHasComments ? concat(["(", indent(concat([softline, param])), softline, ") "]) : concat(["(", param, ") "]), path.call(print, "body"), diff --git a/src/language-js/utils.js b/src/language-js/utils.js index 16184d372..a9302d2cb 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -344,7 +344,7 @@ function isJSXWhitespaceExpression(node) { node.type === "JSXExpressionContainer" && isLiteral(node.expression) && node.expression.value === " " && - !node.expression.comments + !hasComment(node.expression) ); } @@ -564,35 +564,6 @@ 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 && - node.comments.some( - (comment) => comment.trailing && !isBlockComment(comment) - ) - ); -} - /** * @param {CallExpression | OptionalCallExpression} node * @returns {boolean} @@ -603,17 +574,6 @@ function isCallOrOptionalCallExpression(node) { ); } -/** - * @param {Node} node - * @returns {boolean} - */ -function hasDanglingComments(node) { - return ( - node.comments && - node.comments.some((comment) => !comment.leading && !comment.trailing) - ); -} - /** * * @param {any} node @@ -632,7 +592,7 @@ function isSimpleTemplateLiteral(node) { return expressions.every((expr) => { // Disallow comments since printDocToString can't print them here - if (expr.comments) { + if (hasComment(expr)) { return false; } @@ -660,7 +620,7 @@ function isSimpleTemplateLiteral(node) { return false; } head = head.object; - if (head.comments) { + if (hasComment(head)) { return false; } } @@ -819,10 +779,7 @@ function hasJsxIgnoreComment(path) { prevSibling && prevSibling.type === "JSXExpressionContainer" && prevSibling.expression.type === "JSXEmptyExpression" && - prevSibling.expression.comments && - prevSibling.expression.comments.some((comment) => - isPrettierIgnoreComment(comment) - ) + hasNodeIgnoreComment(prevSibling.expression) ); } @@ -893,12 +850,9 @@ function hasLeadingOwnLineComment(text, node) { return hasNodeIgnoreComment(node); } - const res = - node.comments && - node.comments.some( - (comment) => comment.leading && hasNewline(text, locEnd(comment)) - ); - return res; + return hasComment(node, CommentCheckFlags.Leading, (comment) => + hasNewline(text, locEnd(comment)) + ); } // This recurses the return argument, looking for the first token @@ -1036,11 +990,11 @@ function isTemplateOnItsOwnLine(n, text) { * @returns {boolean} */ function needsHardlineAfterDanglingComment(node) { - if (!node.comments) { + if (!hasComment(node)) { return false; } const lastDanglingComment = getLast( - node.comments.filter((comment) => !comment.leading && !comment.trailing) + getComments(node, CommentCheckFlags.Dangling) ); return lastDanglingComment && !isBlockComment(lastDanglingComment); } @@ -1453,18 +1407,13 @@ function iterateCallArgumentsPath(path, iteratee) { } function isPrettierIgnoreComment(comment) { - return comment.value.trim() === "prettier-ignore"; + return comment.value.trim() === "prettier-ignore" && !comment.unignore; } function hasNodeIgnoreComment(node) { return ( node && - ((node.comments && - node.comments.length > 0 && - node.comments.some( - (comment) => isPrettierIgnoreComment(comment) && !comment.unignore - )) || - node.prettierIgnore) + (node.prettierIgnore || hasComment(node, CommentCheckFlags.PrettierIgnore)) ); } @@ -1473,6 +1422,86 @@ function hasIgnoreComment(path) { return hasNodeIgnoreComment(node); } +const CommentCheckFlags = { + /** @type {number} Check comment is a leading comment */ + Leading: 1 << 1, + /** @type {number} Check comment is a trailing comment */ + Trailing: 1 << 2, + /** @type {number} Check comment is a dangling comment */ + Dangling: 1 << 3, + /** @type {number} Check comment is a block comment */ + Block: 1 << 4, + /** @type {number} Check comment is a line comment */ + Line: 1 << 5, + /** @type {number} Check comment is a `prettier-ignore` comment */ + PrettierIgnore: 1 << 6, + /** @type {number} Check comment is the first attched comment */ + First: 1 << 7, + /** @type {number} Check comment is the last attched comment */ + Last: 1 << 8, +}; + +/** + * @returns {function} + */ +const getCommentTestFunction = (flags, fn) => { + if (typeof flags === "function") { + fn = flags; + flags = 0; + } + if (flags || fn) { + return (comment, index, comments) => + !( + (flags & CommentCheckFlags.Leading && !comment.leading) || + (flags & CommentCheckFlags.Trailing && !comment.trailing) || + (flags & CommentCheckFlags.Dangling && + (comment.leading || comment.trailing)) || + (flags & CommentCheckFlags.Block && !isBlockComment(comment)) || + (flags & CommentCheckFlags.Line && !isLineComment(comment)) || + (flags & CommentCheckFlags.First && index !== 0) || + (flags & CommentCheckFlags.Last && index !== comments.length - 1) || + (flags & CommentCheckFlags.PrettierIgnore && + !isPrettierIgnoreComment(comment)) || + (fn && !fn(comment)) + ); + } +}; +/** + * @param {Node} node + * @param {number | function} [flags] + * @param {function} [fn] + * @returns {boolean} + */ +function hasComment(node, flags, fn) { + if (!node || !Array.isArray(node.comments) || node.comments.length === 0) { + return false; + } + const test = getCommentTestFunction(flags, fn); + return test + ? node.comments.some((comment, index, comments) => + test(comment, index, comments) + ) + : true; +} + +/** + * @param {Node} node + * @param {number | function} [flags] + * @param {function} [fn] + * @returns {Comment[]} + */ +function getComments(node, flags, fn) { + if (!node || !Array.isArray(node.comments)) { + return []; + } + const test = getCommentTestFunction(flags, fn); + return test + ? node.comments.filter((comment, index, comments) => + test(comment, index, comments) + ) + : node.comments; +} + module.exports = { classChildNeedsASIProtection, classPropMayCauseASIProblems, @@ -1484,19 +1513,14 @@ module.exports = { getLeftSidePathName, getParentExportDeclaration, getTypeScriptMappedTypeModifier, - hasDanglingComments, hasFlowAnnotationComment, hasFlowShorthandAnnotationComment, - hasLeadingComment, hasLeadingOwnLineComment, hasNakedLeftSide, hasNewlineBetweenOrAfterDecorators, hasNode, hasPrettierIgnore, - hasTrailingComment, - hasTrailingLineComment, hasIgnoreComment, - hasNodeIgnoreComment, identity, isBinaryish, isBlockComment, @@ -1542,4 +1566,7 @@ module.exports = { shouldFlatten, startsWithNoLookaheadToken, getPrecedence, + hasComment, + getComments, + CommentCheckFlags, }; diff --git a/yarn.lock b/yarn.lock index 61e48b201..ffff03a19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3186,8 +3186,9 @@ eslint-plugin-jest@24.1.3: dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" -"eslint-plugin-prettier-internal-rules@file:scripts/tools/eslint-plugin-prettier-internal-rules": - version "1.0.3" +"eslint-plugin-prettier-internal-rules@link:scripts/tools/eslint-plugin-prettier-internal-rules": + version "0.0.0" + uid "" eslint-plugin-react@7.21.5: version "7.21.5"