From 47da0804a92b9e1d1337c6cf4652ec67dcfc3ecb Mon Sep 17 00:00:00 2001 From: Yang Su Date: Tue, 29 Jan 2019 23:24:34 -0800 Subject: [PATCH] JavaScript: fix edge cases in type cast comment detection regex (#5793) Fix edge cases involving {...} ([record @type](http://usejsdoc.org/tags-type.html)) and newlines in prettier's detection code for type cast comments --- CHANGELOG.unreleased.md | 17 ++++++ src/language-js/needs-parens.js | 28 +++++++++- .../comments/__snapshots__/jsfmt.spec.js.snap | 52 +++++++++++++++++++ tests/comments/closure-compiler-type-cast.js | 26 ++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index dc4af692d..8efaf0146 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -67,3 +67,20 @@ Examples: Prix·:·32·€ ``` + +- JavaScript: fix record type cast comment detection ([#5793] by [@yangsu]) + + Previously, type cast comments with record types were ignored and prettier + stripped the subsequent parens. Prettier master handles these cases correctly. + + + ```js + // Input + const v = /** @type {{key: number}} */ (value); + + // Output (Prettier stable) + const v = /** @type {{key: number}} */ value; + + // Output (Prettier master) + const v = /** @type {{key: number}} */ (value); + ``` diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index 1ed0f2f33..2a8fde98c 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -34,11 +34,37 @@ function hasClosureCompilerTypeCastComment(text, path, locStart, locEnd) { comment => comment.leading && comments.isBlockComment(comment) && - comment.value.match(/^\*\s*@type\s*{[^}]+}\s*$/) && + isTypeCastComment(comment.value) && util.getNextNonSpaceNonCommentCharacter(text, comment, locEnd) === "(" ) ); } + + function isTypeCastComment(comment) { + const trimmed = comment.trim(); + if (!/^\*\s*@type\s*\{[^]+\}$/.test(trimmed)) { + return false; + } + let isCompletelyClosed = false; + let unpairedBracketCount = 0; + for (const char of trimmed) { + if (char === "{") { + if (isCompletelyClosed) { + return false; + } + unpairedBracketCount++; + } else if (char === "}") { + if (unpairedBracketCount === 0) { + return false; + } + unpairedBracketCount--; + if (unpairedBracketCount === 0) { + isCompletelyClosed = true; + } + } + } + return unpairedBracketCount === 0; + } } function needsParens(path, options) { diff --git a/tests/comments/__snapshots__/jsfmt.spec.js.snap b/tests/comments/__snapshots__/jsfmt.spec.js.snap index b8e23fbd8..20bb0a247 100644 --- a/tests/comments/__snapshots__/jsfmt.spec.js.snap +++ b/tests/comments/__snapshots__/jsfmt.spec.js.snap @@ -350,6 +350,32 @@ var newArray = /** @type {array} */ (numberOrString).map(x => x); var newArray = /** @type {array} */ ((numberOrString)).map(x => x); var newArray = /** @type {array} */ ((numberOrString).map(x => x)); + +const data = functionCall( + arg1, + arg2, + /** @type {{height: number, width: number}} */ (arg3)); + +// Invalid type casts +const v = /** @type {} */ (value); +const v = /** @type {}} */ (value); +const v = /** @type } */ (value); +const v = /** @type { */ (value); +const v = /** @type {{} */ (value); + +const style = /** @type {{ + width: number, + height: number, + marginTop: number, + marginLeft: number, + marginRight: number, + marginBottom: number, +}} */ ({ + width, + height, + ...margins, +}); + =====================================output===================================== // test to make sure comments are attached correctly let inlineComment = /* some comment */ someReallyLongFunctionCall( @@ -374,6 +400,32 @@ var newArray = /** @type {array} */ (numberOrString).map(x => x); var newArray = /** @type {array} */ (numberOrString).map(x => x); var newArray = /** @type {array} */ (numberOrString.map(x => x)); +const data = functionCall( + arg1, + arg2, + /** @type {{height: number, width: number}} */ (arg3) +); + +// Invalid type casts +const v = /** @type {} */ value; +const v = /** @type {}} */ value; +const v = /** @type } */ value; +const v = /** @type { */ value; +const v = /** @type {{} */ value; + +const style = /** @type {{ + width: number, + height: number, + marginTop: number, + marginLeft: number, + marginRight: number, + marginBottom: number, +}} */ ({ + width, + height, + ...margins +}); + ================================================================================ `; diff --git a/tests/comments/closure-compiler-type-cast.js b/tests/comments/closure-compiler-type-cast.js index 65ffaef86..647b2ca2e 100644 --- a/tests/comments/closure-compiler-type-cast.js +++ b/tests/comments/closure-compiler-type-cast.js @@ -18,3 +18,29 @@ function returnValue() { var newArray = /** @type {array} */ (numberOrString).map(x => x); var newArray = /** @type {array} */ ((numberOrString)).map(x => x); var newArray = /** @type {array} */ ((numberOrString).map(x => x)); + + +const data = functionCall( + arg1, + arg2, + /** @type {{height: number, width: number}} */ (arg3)); + +// Invalid type casts +const v = /** @type {} */ (value); +const v = /** @type {}} */ (value); +const v = /** @type } */ (value); +const v = /** @type { */ (value); +const v = /** @type {{} */ (value); + +const style = /** @type {{ + width: number, + height: number, + marginTop: number, + marginLeft: number, + marginRight: number, + marginBottom: number, +}} */ ({ + width, + height, + ...margins, +});