From a50bc4e14af5e129a33f16d61be568e07f63f2bf Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Thu, 6 Jan 2022 09:31:58 +0900 Subject: [PATCH] Preserve end-of-line comment for if statement without blocks (#12017) * Print as dangling comments * Add tests * Add changelog * Use symbol instead of string --- changelog_unreleased/javascript/12017.md | 22 ++++++++ src/language-js/comments.js | 20 ++++++- src/language-js/printer-estree.js | 12 ++++- src/language-js/utils.js | 8 +++ .../js/if/__snapshots__/jsfmt.spec.js.snap | 53 +++++++++++++++++++ .../js/if/expr_and_same_line_comments.js | 19 +++++++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 changelog_unreleased/javascript/12017.md create mode 100644 tests/format/js/if/expr_and_same_line_comments.js diff --git a/changelog_unreleased/javascript/12017.md b/changelog_unreleased/javascript/12017.md new file mode 100644 index 000000000..726ce45c5 --- /dev/null +++ b/changelog_unreleased/javascript/12017.md @@ -0,0 +1,22 @@ +#### Preserve end-of-line comment for if statements without blocks (#12017 by @sosukesuzuki) + + +```jsx +// Input +if (condition1) expression1; // comment A +else if (condition2) expression2; // comment B +else expression3; // comment C + +// Prettier stable +if (condition1) expression1; +// comment A +else if (condition2) expression2; +// comment B +else expression3; // comment C + +// Prettier main +if (condition1) expression1; // comment A +else if (condition2) expression2; // comment B +else expression3; // comment C + +``` diff --git a/src/language-js/comments.js b/src/language-js/comments.js index d1eb48b03..f908647ac 100644 --- a/src/language-js/comments.js +++ b/src/language-js/comments.js @@ -27,6 +27,7 @@ const { isObjectProperty, getComments, CommentCheckFlags, + markerForIfWithoutBlockAndSameLineComment, } = require("./utils.js"); const { locStart, locEnd } = require("./loc.js"); @@ -206,7 +207,24 @@ function handleIfStatementComments({ if (precedingNode.type === "BlockStatement") { addTrailingComment(precedingNode, comment); } else { - addDanglingComment(enclosingNode, comment); + const isSingleLineComment = + comment.type === "SingleLine" || + comment.loc.start.line === comment.loc.end.line; + const isSameLineComment = + comment.loc.start.line === precedingNode.loc.start.line; + if (isSingleLineComment && isSameLineComment) { + // example: + // if (cond1) expr1; // comment A + // else if (cond2) expr2; // comment A + // else expr3; + addDanglingComment( + precedingNode, + comment, + markerForIfWithoutBlockAndSameLineComment + ); + } else { + addDanglingComment(enclosingNode, comment); + } } return true; } diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index faf2aa6af..db10d65ac 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -28,6 +28,7 @@ const { hasIgnoreComment, isCallExpression, isMemberExpression, + markerForIfWithoutBlockAndSameLineComment, } = require("./utils.js"); const { locStart, locEnd } = require("./loc.js"); @@ -180,7 +181,7 @@ function printPathNoParens(path, options, print, args) { // Babel extension. case "EmptyStatement": return ""; - case "ExpressionStatement": + case "ExpressionStatement": { // Detect Flow and TypeScript directives if (node.directive) { return [printDirective(node.expression, options), semi]; @@ -200,11 +201,20 @@ function printPathNoParens(path, options, print, args) { } } + const danglingComment = printDanglingComments( + path, + options, + /** sameIndent */ true, + ({ marker }) => marker === markerForIfWithoutBlockAndSameLineComment + ); + // Do not append semicolon after the only JSX element in a program return [ print("expression"), isTheOnlyJsxElementInMarkdown(options, path) ? "" : semi, + danglingComment ? [" ", danglingComment] : "", ]; + } // Babel non-standard node. Used for Closure-style type casts. See postprocess.js. case "ParenthesizedExpression": { const shouldHug = diff --git a/src/language-js/utils.js b/src/language-js/utils.js index 75cd89dd0..693e52b80 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -1315,6 +1315,13 @@ function isEnabledHackPipeline(options) { return Boolean(options.__isUsingHackPipeline); } +/** + * This is used as a marker for dangling comments. + */ +const markerForIfWithoutBlockAndSameLineComment = Symbol( + "ifWithoutBlockAndSameLineComment" +); + module.exports = { getFunctionParameters, iterateFunctionParametersPath, @@ -1379,4 +1386,5 @@ module.exports = { hasComment, getComments, CommentCheckFlags, + markerForIfWithoutBlockAndSameLineComment, }; diff --git a/tests/format/js/if/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/if/__snapshots__/jsfmt.spec.js.snap index 819abc509..fc49b1e98 100644 --- a/tests/format/js/if/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/js/if/__snapshots__/jsfmt.spec.js.snap @@ -87,6 +87,59 @@ function f() { ================================================================================ `; +exports[`expr_and_same_line_comments.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +if (a === 0) doSomething(); // comment A1 +else if (a === 1) doSomethingElse(); // comment B1 +else if (a === 2) doSomethingElse(); // comment C1 + +if (a === 0) doSomething(); /* comment A2 */ +else if (a === 1) doSomethingElse(); /* comment B2 */ +else if (a === 2) doSomethingElse(); /* comment C2 */ + +if (a === 0) expr; // comment A3 +else if (a === 1) expr; // comment B3 +else if (a === 2) expr; // comment C3 + +if (a === 0) expr; /* comment A4 */ +else if (a === 1) expr; /* comment B4 */ +else if (a === 2) expr; /* comment C4 */ + +if (a === 0) looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment A5 +else if (a === 1) looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment B5 +else if (a === 2) looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment C5 + +=====================================output===================================== +if (a === 0) doSomething(); // comment A1 +else if (a === 1) doSomethingElse(); // comment B1 +else if (a === 2) doSomethingElse(); // comment C1 + +if (a === 0) doSomething(); /* comment A2 */ +else if (a === 1) doSomethingElse(); /* comment B2 */ +else if (a === 2) doSomethingElse(); /* comment C2 */ + +if (a === 0) expr; // comment A3 +else if (a === 1) expr; // comment B3 +else if (a === 2) expr; // comment C3 + +if (a === 0) expr; /* comment A4 */ +else if (a === 1) expr; /* comment B4 */ +else if (a === 2) expr; /* comment C4 */ + +if (a === 0) + looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment A5 +else if (a === 1) + looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment B5 +else if (a === 2) + looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment C5 + +================================================================================ +`; + exports[`if_comments.js format 1`] = ` ====================================options===================================== parsers: ["babel", "flow", "typescript"] diff --git a/tests/format/js/if/expr_and_same_line_comments.js b/tests/format/js/if/expr_and_same_line_comments.js new file mode 100644 index 000000000..9611becde --- /dev/null +++ b/tests/format/js/if/expr_and_same_line_comments.js @@ -0,0 +1,19 @@ +if (a === 0) doSomething(); // comment A1 +else if (a === 1) doSomethingElse(); // comment B1 +else if (a === 2) doSomethingElse(); // comment C1 + +if (a === 0) doSomething(); /* comment A2 */ +else if (a === 1) doSomethingElse(); /* comment B2 */ +else if (a === 2) doSomethingElse(); /* comment C2 */ + +if (a === 0) expr; // comment A3 +else if (a === 1) expr; // comment B3 +else if (a === 2) expr; // comment C3 + +if (a === 0) expr; /* comment A4 */ +else if (a === 1) expr; /* comment B4 */ +else if (a === 2) expr; /* comment C4 */ + +if (a === 0) looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment A5 +else if (a === 1) looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment B5 +else if (a === 2) looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong; // comment C5