From 8dafda772d095fa49fc735d90a0bba209771b7da Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 2 Oct 2026 05:46:25 +0800 Subject: [PATCH] Fix ignored forX initializer (#20204) --- changelog_unreleased/javascript/20204.md | 13 ++++ src/language-js/location/index.js | 6 +- src/language-js/location/overrides.js | 36 +---------- src/language-js/print/ignored.js | 9 +-- src/language-js/print/variable-declaration.js | 11 ++-- .../should-ignored-node-print-semicolon.js | 42 +++++++++++++ .../is-for-x-statement-initializer.js | 9 +++ .../semi/__snapshots__/format.test.js.snap | 63 +++++++++++++++++++ .../js/ignore/semi/for-x-initializer.js | 10 +++ 9 files changed, 146 insertions(+), 53 deletions(-) create mode 100644 changelog_unreleased/javascript/20204.md create mode 100644 src/language-js/semicolon/should-ignored-node-print-semicolon.js create mode 100644 src/language-js/utilities/is-for-x-statement-initializer.js create mode 100644 tests/format/js/ignore/semi/for-x-initializer.js diff --git a/changelog_unreleased/javascript/20204.md b/changelog_unreleased/javascript/20204.md new file mode 100644 index 000000000..4b4b7a175 --- /dev/null +++ b/changelog_unreleased/javascript/20204.md @@ -0,0 +1,13 @@ +#### Fix `prettier-ignore`d `for` initializer (#20204 by @fisker) + + +```jsx +// Input +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} + +// Prettier stable +for (/* prettier-ignore */ let i = 0;; i < 1; i++) {} + +// Prettier main +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} +``` diff --git a/src/language-js/location/index.js b/src/language-js/location/index.js index 10d1910fb..c52293bbc 100644 --- a/src/language-js/location/index.js +++ b/src/language-js/location/index.js @@ -1,8 +1,4 @@ export { hasSameLoc, hasSameLocStart } from "./comparison.js"; export { locEndWithFullText } from "./end-with-full-text.js"; -export { - locEnd, - shouldAddContentEnd, - shouldIgnoredNodePrintSemicolon, -} from "./overrides.js"; +export { locEnd, shouldAddContentEnd } from "./overrides.js"; export { locStart } from "./start.js"; diff --git a/src/language-js/location/overrides.js b/src/language-js/location/overrides.js index 82ef7fdcb..0436ed376 100644 --- a/src/language-js/location/overrides.js +++ b/src/language-js/location/overrides.js @@ -51,40 +51,6 @@ const overrides = const shouldAddContentEnd = createTypeCheckFunction(nodeTypesWithContentEnd); -const shouldIgnoredNodePrintSemicolon = (node) => { - if (shouldAddContentEnd(node) && node.__contentEnd) { - return true; - } - - const { type } = node; - - if ( - type === "BreakStatement" || - type === "ContinueStatement" || - type === "DebuggerStatement" || - type === "VariableDeclaration" - ) { - return true; - } - - if (type === "IfStatement") { - return shouldIgnoredNodePrintSemicolon(node.alternate ?? node.consequent); - } - - if ( - type === "ForInStatement" || - type === "ForOfStatement" || - type === "ForStatement" || - type === "LabeledStatement" || - type === "WithStatement" || - type === "WhileStatement" - ) { - return shouldIgnoredNodePrintSemicolon(node.body); - } - - return false; -}; - /** @param {Node | Comment} node @return {number} @@ -116,4 +82,4 @@ function locEnd(node) { ); } -export { locEnd, shouldAddContentEnd, shouldIgnoredNodePrintSemicolon }; +export { locEnd, shouldAddContentEnd }; diff --git a/src/language-js/print/ignored.js b/src/language-js/print/ignored.js index 52ac36c10..5caf2d29a 100644 --- a/src/language-js/print/ignored.js +++ b/src/language-js/print/ignored.js @@ -1,17 +1,14 @@ import { indent, softline } from "../../document/index.js"; import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; -import { - locEnd, - locStart, - shouldIgnoredNodePrintSemicolon, -} from "../location/index.js"; +import { locEnd, locStart } from "../location/index.js"; import { shouldExpressionStatementPrintLeadingSemicolon } from "../semicolon/semicolon.js"; +import { shouldIgnoredNodePrintSemicolon } from "../semicolon/should-ignored-node-print-semicolon.js"; function printIgnored(path, options /* , print*/) { const { node } = path; let text = options.originalText.slice(locStart(node), locEnd(node)); - if (options.semi && shouldIgnoredNodePrintSemicolon(node)) { + if (options.semi && shouldIgnoredNodePrintSemicolon(path)) { text += ";"; } else if (shouldExpressionStatementPrintLeadingSemicolon(path, options)) { text = `;${text}`; diff --git a/src/language-js/print/variable-declaration.js b/src/language-js/print/variable-declaration.js index e4955f714..41096902b 100644 --- a/src/language-js/print/variable-declaration.js +++ b/src/language-js/print/variable-declaration.js @@ -1,5 +1,6 @@ import { group, hardline, indent, line } from "../../document/index.js"; import { hasComment } from "../utilities/comments.js"; +import { isForXStatementInitializer } from "../utilities/is-for-x-statement-initializer.js"; import { printDeclareToken, printSemicolon } from "./miscellaneous.js"; function printVariableDeclaration(path, options, print) { @@ -9,11 +10,7 @@ function printVariableDeclaration(path, options, print) { // We generally want to terminate all variable declarations with a // semicolon, except when they in the () part of for loops. - const isForXStatementInitializer = - (path.key === "init" && path.parent.type === "ForStatement") || - (path.key === "left" && - (path.parent.type === "ForInStatement" || - path.parent.type === "ForOfStatement")); + const isForXInitializer = isForXStatementInitializer(path); const hasValue = node.declarations.some((declarator) => declarator.init); @@ -34,11 +31,11 @@ function printVariableDeclaration(path, options, print) { .slice(1) .map((doc) => [ ",", - hasValue && !isForXStatementInitializer ? hardline : line, + hasValue && !isForXInitializer ? hardline : line, doc, ]), ), - isForXStatementInitializer ? "" : printSemicolon(options), + isForXInitializer ? "" : printSemicolon(options), ]); } diff --git a/src/language-js/semicolon/should-ignored-node-print-semicolon.js b/src/language-js/semicolon/should-ignored-node-print-semicolon.js new file mode 100644 index 000000000..ce806cfc4 --- /dev/null +++ b/src/language-js/semicolon/should-ignored-node-print-semicolon.js @@ -0,0 +1,42 @@ +import { shouldAddContentEnd } from "../location/index.js"; +import { isForXStatementInitializer } from "../utilities/is-for-x-statement-initializer.js"; + +function shouldPrintSemicolon(node) { + if (shouldAddContentEnd(node) && node.__contentEnd) { + return true; + } + + const { type } = node; + + if ( + type === "BreakStatement" || + type === "ContinueStatement" || + type === "DebuggerStatement" || + type === "VariableDeclaration" + ) { + return true; + } + + if (type === "IfStatement") { + return shouldPrintSemicolon(node.alternate ?? node.consequent); + } + + if ( + type === "ForInStatement" || + type === "ForOfStatement" || + type === "ForStatement" || + type === "LabeledStatement" || + type === "WithStatement" || + type === "WhileStatement" + ) { + return shouldPrintSemicolon(node.body); + } + + return false; +} + +function shouldIgnoredNodePrintSemicolon(path) { + return !isForXStatementInitializer(path) && shouldPrintSemicolon(path.node); +} + +export { shouldIgnoredNodePrintSemicolon }; diff --git a/src/language-js/utilities/is-for-x-statement-initializer.js b/src/language-js/utilities/is-for-x-statement-initializer.js new file mode 100644 index 000000000..9cd4f7f8a --- /dev/null +++ b/src/language-js/utilities/is-for-x-statement-initializer.js @@ -0,0 +1,9 @@ +function isForXStatementInitializer({ key, parent }) { + return ( + (key === "init" && parent.type === "ForStatement") || + (key === "left" && + (parent.type === "ForInStatement" || parent.type === "ForOfStatement")) + ); +} + +export { isForXStatementInitializer }; diff --git a/tests/format/js/ignore/semi/__snapshots__/format.test.js.snap b/tests/format/js/ignore/semi/__snapshots__/format.test.js.snap index f6698c962..1c8a4de75 100644 --- a/tests/format/js/ignore/semi/__snapshots__/format.test.js.snap +++ b/tests/format/js/ignore/semi/__snapshots__/format.test.js.snap @@ -209,6 +209,69 @@ function foo() { ================================================================================ `; +exports[`for-x-initializer.js - {"semi":false} format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] +semi: false + printWidth: 80 (default) | +=====================================input====================================== +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} +for (/* prettier-ignore */ let [a,b] in c) foo(); +for (/* prettier-ignore */ const {a,b} of c) foo(); +for ( + // prettier-ignore + let i = 0, j = 1; + i < 1; + i++ +) {} +for (/* prettier-ignore */ i = 0; i < 1; i++) {} + +=====================================output===================================== +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} +for (/* prettier-ignore */ let [a,b] in c) foo() +for (/* prettier-ignore */ const {a,b} of c) foo() +for ( + // prettier-ignore + let i = 0, j = 1; + i < 1; + i++ +) {} +for (/* prettier-ignore */ i = 0; i < 1; i++) {} + +================================================================================ +`; + +exports[`for-x-initializer.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] + printWidth: 80 (default) | +=====================================input====================================== +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} +for (/* prettier-ignore */ let [a,b] in c) foo(); +for (/* prettier-ignore */ const {a,b} of c) foo(); +for ( + // prettier-ignore + let i = 0, j = 1; + i < 1; + i++ +) {} +for (/* prettier-ignore */ i = 0; i < 1; i++) {} + +=====================================output===================================== +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} +for (/* prettier-ignore */ let [a,b] in c) foo(); +for (/* prettier-ignore */ const {a,b} of c) foo(); +for ( + // prettier-ignore + let i = 0, j = 1; + i < 1; + i++ +) {} +for (/* prettier-ignore */ i = 0; i < 1; i++) {} + +================================================================================ +`; + exports[`head-ignored.js - {"semi":false} format 1`] = ` ====================================options===================================== parsers: ["babel", "flow", "typescript"] diff --git a/tests/format/js/ignore/semi/for-x-initializer.js b/tests/format/js/ignore/semi/for-x-initializer.js new file mode 100644 index 000000000..c2e64b74f --- /dev/null +++ b/tests/format/js/ignore/semi/for-x-initializer.js @@ -0,0 +1,10 @@ +for (/* prettier-ignore */ let i = 0; i < 1; i++) {} +for (/* prettier-ignore */ let [a,b] in c) foo(); +for (/* prettier-ignore */ const {a,b} of c) foo(); +for ( + // prettier-ignore + let i = 0, j = 1; + i < 1; + i++ +) {} +for (/* prettier-ignore */ i = 0; i < 1; i++) {}