diff --git a/packages/babel-generator/src/generators/classes.ts b/packages/babel-generator/src/generators/classes.ts index c687582df4be..e1b4b401d26d 100644 --- a/packages/babel-generator/src/generators/classes.ts +++ b/packages/babel-generator/src/generators/classes.ts @@ -134,7 +134,7 @@ export function ClassAccessorProperty( // TS does not support class accessor property yet this.tsPrintClassMemberModifiers(node); - this.word("accessor"); + this.word("accessor", true); this.space(); if (node.computed) { diff --git a/packages/babel-generator/src/generators/expressions.ts b/packages/babel-generator/src/generators/expressions.ts index 02d926d85226..458e0d9af3d2 100644 --- a/packages/babel-generator/src/generators/expressions.ts +++ b/packages/babel-generator/src/generators/expressions.ts @@ -26,14 +26,11 @@ export function UnaryExpression(this: Printer, node: t.UnaryExpression) { } export function DoExpression(this: Printer, node: t.DoExpression) { - // ensure no line terminator between `async` and `do` - this.ensureNoLineTerminator(() => { - if (node.async) { - this.word("async"); - this.space(); - } - this.word("do"); - }); + if (node.async) { + this.word("async", true); + this.space(); + } + this.word("do"); this.space(); this.print(node.body, node); } @@ -230,12 +227,10 @@ export function AwaitExpression(this: Printer, node: t.AwaitExpression) { } export function YieldExpression(this: Printer, node: t.YieldExpression) { - this.word("yield"); + this.word("yield", true); if (node.delegate) { - this.ensureNoLineTerminator(() => { - this.token("*"); - }); + this.token("*"); if (node.argument) { this.space(); // line terminators are allowed after yield* @@ -360,12 +355,9 @@ export function V8IntrinsicIdentifier( } export function ModuleExpression(this: Printer, node: t.ModuleExpression) { - this.word("module"); + this.word("module", true); this.space(); - // ensure no line terminator between `module` and `{` - this.ensureNoLineTerminator(() => { - this.token("{"); - }); + this.token("{"); this.indent(); const { body } = node; if (body.body.length || body.directives.length) { diff --git a/packages/babel-generator/src/generators/methods.ts b/packages/babel-generator/src/generators/methods.ts index c560540e64ce..dd16d761cabb 100644 --- a/packages/babel-generator/src/generators/methods.ts +++ b/packages/babel-generator/src/generators/methods.ts @@ -10,6 +10,7 @@ export function _params( this.token("("); this._parameters(node.params, node); this.token(")"); + this._noLineTerminator = true; this.print(node.returnType, node, node.type === "ArrowFunctionExpression"); } @@ -72,11 +73,8 @@ export function _methodHead(this: Printer, node: t.Method | t.TSDeclareMethod) { this.space(); } - const { _noLineTerminator } = this; if (node.async) { - // ensure no line terminator between async and class element name / * - this._noLineTerminator = true; - this.word("async"); + this.word("async", true); this.space(); } @@ -87,18 +85,15 @@ export function _methodHead(this: Printer, node: t.Method | t.TSDeclareMethod) { ) { if (node.generator) { this.token("*"); - this._noLineTerminator = _noLineTerminator; } } if (node.computed) { this.token("["); - this._noLineTerminator = _noLineTerminator; this.print(key, node); this.token("]"); } else { this.print(key, node); - this._noLineTerminator = _noLineTerminator; } if ( @@ -118,13 +113,14 @@ export function _predicate( | t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression, + noLineTerminatorAfter?: boolean, ) { if (node.predicate) { if (!node.returnType) { this.token(":"); } this.space(); - this.print(node.predicate, node); + this.print(node.predicate, node, noLineTerminatorAfter); } } @@ -172,10 +168,8 @@ export function ArrowFunctionExpression( this: Printer, node: t.ArrowFunctionExpression, ) { - const { _noLineTerminator } = this; if (node.async) { - this._noLineTerminator = true; - this.word("async"); + this.word("async", true); this.space(); } @@ -188,22 +182,18 @@ export function ArrowFunctionExpression( isIdentifier((firstParam = node.params[0])) && !hasTypesOrComments(node, firstParam) ) { - this.print(firstParam, node); - this._noLineTerminator = _noLineTerminator; + this.print(firstParam, node, true); } else { - this._noLineTerminator = _noLineTerminator; this._params(node); } - this._predicate(node); - this.ensureNoLineTerminator(() => { - this.space(); - // When printing (x)/*1*/=>{}, we remove the parentheses - // and thus there aren't two contiguous inner tokens. - // We forcefully print inner comments here. - this.printInnerComments(); - this.token("=>"); - }); + this._predicate(node, true); + this.space(); + // When printing (x)/*1*/=>{}, we remove the parentheses + // and thus there aren't two contiguous inner tokens. + // We forcefully print inner comments here. + this.printInnerComments(); + this.token("=>"); this.space(); diff --git a/packages/babel-generator/src/generators/modules.ts b/packages/babel-generator/src/generators/modules.ts index 3a711f4144a0..0b8d426f22f2 100644 --- a/packages/babel-generator/src/generators/modules.ts +++ b/packages/babel-generator/src/generators/modules.ts @@ -70,6 +70,7 @@ export function _printAssertions( this: Printer, node: Extract, ) { + this.word("assert"); this.space(); this.token("{"); this.space(); @@ -94,11 +95,8 @@ export function ExportAllDeclaration( this.space(); // @ts-expect-error Fixme: assertions is not defined in DeclareExportAllDeclaration if (node.assertions?.length) { - this.ensureNoLineTerminator(() => { - this.print(node.source, node); - this.space(); - this.word("assert"); - }); + this.print(node.source, node, true); + this.space(); // @ts-expect-error Fixme: assertions is not defined in DeclareExportAllDeclaration this._printAssertions(node); } else { @@ -169,11 +167,8 @@ export function ExportNamedDeclaration( this.word("from"); this.space(); if (node.assertions?.length) { - this.ensureNoLineTerminator(() => { - this.print(node.source, node); - this.space(); - this.word("assert"); - }); + this.print(node.source, node, true); + this.space(); this._printAssertions(node); } else { this.print(node.source, node); @@ -258,10 +253,7 @@ export function ImportDeclaration(this: Printer, node: t.ImportDeclaration) { if (node.assertions?.length) { this.print(node.source, node, true); - this.ensureNoLineTerminator(() => { - this.space(); - this.word("assert"); - }); + this.space(); this._printAssertions(node); } else { this.print(node.source, node); diff --git a/packages/babel-generator/src/generators/statements.ts b/packages/babel-generator/src/generators/statements.ts index 534230447e44..a333a2cc5db6 100644 --- a/packages/babel-generator/src/generators/statements.ts +++ b/packages/babel-generator/src/generators/statements.ts @@ -1,5 +1,4 @@ import type Printer from "../printer"; -import type { PrintJoinOptions } from "../printer"; import { isFor, isForStatement, @@ -262,12 +261,7 @@ export function VariableDeclaration( } const { kind } = node; - this.word(kind); - const { _noLineTerminator } = this; - if (kind === "using") { - // ensure no line break after `using` - this._noLineTerminator = true; - } + this.word(kind, kind === "using"); this.space(); let hasInits = false; @@ -293,16 +287,6 @@ export function VariableDeclaration( // bar = "foo"; // - let iterator: PrintJoinOptions["iterator"] | undefined; - if (kind === "using") { - // Ensure no line break between `using` and the first declarator - iterator = (_, i: number) => { - if (i === 0) { - this._noLineTerminator = _noLineTerminator; - } - }; - } - this.printList(node.declarations, node, { separator: hasInits ? function (this: Printer) { @@ -310,7 +294,6 @@ export function VariableDeclaration( this.newline(); } : undefined, - iterator, indent: node.declarations.length > 1 ? true : false, }); diff --git a/packages/babel-generator/src/printer.ts b/packages/babel-generator/src/printer.ts index 59b6e0e1c48b..53d3f736e9cb 100644 --- a/packages/babel-generator/src/printer.ts +++ b/packages/babel-generator/src/printer.ts @@ -22,6 +22,7 @@ const SCIENTIFIC_NOTATION = /e/i; const ZERO_DECIMAL_INTEGER = /\.0+$/; const NON_DECIMAL_LITERAL = /^0[box]/; const PURE_ANNOTATION_RE = /^\s*[@#]__PURE__\s*$/; +const HAS_NEWLINE = /[\n\r\u2028\u2029]/; const { needsParens } = n; @@ -151,6 +152,7 @@ class Printer { } else { this._queue(charCodes.semicolon); } + this._noLineTerminator = false; } /** @@ -185,7 +187,7 @@ class Printer { * Writes a token that can't be safely parsed without taking whitespace into account. */ - word(str: string): void { + word(str: string, noLineTerminatorAfter: boolean = false): void { this._maybePrintInnerComments(); // prevent concatenating words and creating // comment out of division and regex @@ -200,6 +202,7 @@ class Printer { this._append(str, false); this._endsWithWord = true; + this._noLineTerminator = noLineTerminatorAfter; } /** @@ -243,6 +246,7 @@ class Printer { this._maybeAddAuxComment(); this._append(str, maybeNewline); + this._noLineTerminator = false; } tokenChar(char: number): void { @@ -263,6 +267,7 @@ class Printer { this._maybeAddAuxComment(); this._appendChar(char); + this._noLineTerminator = false; } /** @@ -534,13 +539,6 @@ class Printer { return this._indentRepeat * this._indent; } - ensureNoLineTerminator(fn: () => void) { - const { _noLineTerminator } = this; - this._noLineTerminator = true; - fn(); - this._noLineTerminator = _noLineTerminator; - } - printTerminatorless(node: t.Node, parent: t.Node, isLabel: boolean) { /** * Set some state that will be modified if a newline has been inserted before any @@ -558,9 +556,8 @@ class Printer { * `undefined` will be returned and not `foo` due to the terminator. */ if (isLabel) { - this.ensureNoLineTerminator(() => { - this.print(node, parent); - }); + this._noLineTerminator = true; + this.print(node, parent); } else { const terminatorState = { printed: false, @@ -581,9 +578,9 @@ class Printer { print( node: t.Node | null, parent?: t.Node, - noLineTerminator?: boolean, + noLineTerminatorAfter?: boolean, // trailingCommentsLineOffset also used to check if called from printJoin - // it will be ignored if `noLineTerminator||this._noLineTerminator` + // it will be ignored if `noLineTerminatorAfter||this._noLineTerminator` trailingCommentsLineOffset?: number, forceParens?: boolean, ) { @@ -649,16 +646,17 @@ class Printer { this.exactSource(loc, printMethod.bind(this, node, parent)); - if (noLineTerminator && !this._noLineTerminator) { + if (shouldPrintParens) { + this._printTrailingComments(node, parent); + this.token(")"); + this._noLineTerminator = noLineTerminatorAfter; + } else if (noLineTerminatorAfter && !this._noLineTerminator) { this._noLineTerminator = true; this._printTrailingComments(node, parent); - this._noLineTerminator = false; } else { this._printTrailingComments(node, parent, trailingCommentsLineOffset); } - if (shouldPrintParens) this.token(")"); - // end this._printStack.pop(); @@ -891,14 +889,26 @@ class Printer { } } - _printComment(comment: t.Comment, skipNewLines: COMMENT_SKIP_NEWLINE): void { + // Returns `true` if the comment cannot be printed in this position due to + // line terminators, signaling that the print comments loop can stop and + // resume printing comments at the next posisble position. This happens when + // printing inner comments, since if we have an inner comment with a multiline + // there is at least one inner position where line terminators are allowed. + _printComment( + comment: t.Comment, + skipNewLines: COMMENT_SKIP_NEWLINE, + ): boolean { // Some plugins (such as flow-strip-types) use this to mark comments as removed using the AST-root 'comments' property, // where they can't manually mutate the AST node comment lists. - if (comment.ignore) return; + if (comment.ignore) return false; - if (this._printedComments.has(comment)) return; + if (this._printedComments.has(comment)) return false; - if (!this.format.shouldPrintComment(comment.value)) return; + if (this._noLineTerminator && HAS_NEWLINE.test(comment.value)) { + return true; + } + + if (!this.format.shouldPrintComment(comment.value)) return false; this._printedComments.add(comment); @@ -950,6 +960,9 @@ class Printer { } else if (!this._noLineTerminator) { val = `//${comment.value}`; } else { + // It was a single-line comment, so it's guaranteed to not + // contain newlines and it can be safely printed as a block + // comment. val = `/*${comment.value}*/`; } @@ -966,6 +979,8 @@ class Printer { if (printNewLines && skipNewLines !== COMMENT_SKIP_NEWLINE.SKIP_TRAILING) { this.newline(1); } + + return false; } _printComments( @@ -975,123 +990,124 @@ class Printer { parent?: t.Node, lineOffset: number = 0, ) { - { - const nodeLoc = node.loc; - const len = comments.length; - let hasLoc = !!nodeLoc; - const nodeStartLine = hasLoc ? nodeLoc.start.line : 0; - const nodeEndLine = hasLoc ? nodeLoc.end.line : 0; - let lastLine = 0; - let leadingCommentNewline = 0; - - for (let i = 0; i < len; i++) { - const comment = comments[i]; - - if (hasLoc && comment.loc && !this._printedComments.has(comment)) { - const commentStartLine = comment.loc.start.line; - const commentEndLine = comment.loc.end.line; - if (type === COMMENT_TYPE.LEADING) { - let offset = 0; - if (i === 0) { - // Because currently we cannot handle blank lines before leading comments, - // we always wrap before and after multi-line comments. - if ( - this._buf.hasContent() && - (comment.type === "CommentLine" || - commentStartLine != commentEndLine) - ) { - offset = leadingCommentNewline = 1; - } - } else { - offset = commentStartLine - lastLine; - } - lastLine = commentEndLine; - - this.newline(offset); - this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL); + const nodeLoc = node.loc; + const len = comments.length; + let hasLoc = !!nodeLoc; + const nodeStartLine = hasLoc ? nodeLoc.start.line : 0; + const nodeEndLine = hasLoc ? nodeLoc.end.line : 0; + let lastLine = 0; + let leadingCommentNewline = 0; + const { _noLineTerminator } = this; - if (i + 1 === len) { - this.newline( - Math.max(nodeStartLine - lastLine, leadingCommentNewline), - ); - lastLine = nodeStartLine; + for (let i = 0; i < len; i++) { + const comment = comments[i]; + + if (hasLoc && comment.loc && !this._printedComments.has(comment)) { + const commentStartLine = comment.loc.start.line; + const commentEndLine = comment.loc.end.line; + if (type === COMMENT_TYPE.LEADING) { + let offset = 0; + if (i === 0) { + // Because currently we cannot handle blank lines before leading comments, + // we always wrap before and after multi-line comments. + if ( + this._buf.hasContent() && + (comment.type === "CommentLine" || + commentStartLine != commentEndLine) + ) { + offset = leadingCommentNewline = 1; } - } else if (type === COMMENT_TYPE.INNER) { - const offset = - commentStartLine - (i === 0 ? nodeStartLine : lastLine); - lastLine = commentEndLine; + } else { + offset = commentStartLine - lastLine; + } + lastLine = commentEndLine; - this.newline(offset); - this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL); + if (!_noLineTerminator) this.newline(offset); + this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL); - if (i + 1 === len) { - this.newline(Math.min(1, nodeEndLine - lastLine)); // TODO: Improve here when inner comments processing is stronger - lastLine = nodeEndLine; - } - } else { - const offset = - commentStartLine - - (i === 0 ? nodeEndLine - lineOffset : lastLine); - lastLine = commentEndLine; + if (!_noLineTerminator && i + 1 === len) { + this.newline( + Math.max(nodeStartLine - lastLine, leadingCommentNewline), + ); + lastLine = nodeStartLine; + } + } else if (type === COMMENT_TYPE.INNER) { + const offset = + commentStartLine - (i === 0 ? nodeStartLine : lastLine); + lastLine = commentEndLine; + + if (!_noLineTerminator) this.newline(offset); + if (this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL)) break; - this.newline(offset); - this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL); + if (!_noLineTerminator && i + 1 === len) { + this.newline(Math.min(1, nodeEndLine - lastLine)); // TODO: Improve here when inner comments processing is stronger + lastLine = nodeEndLine; } } else { - hasLoc = false; - - if (len === 1) { - const singleLine = comment.loc - ? comment.loc.start.line === comment.loc.end.line - : !comment.value.includes("\n"); - - const shouldSkipNewline = - singleLine && - !isStatement(node) && - !isClassBody(parent) && - !isTSInterfaceBody(parent); - - if (type === COMMENT_TYPE.LEADING) { - this._printComment( - comment, - (shouldSkipNewline && node.type !== "ObjectExpression") || - (singleLine && isFunction(parent, { body: node })) - ? COMMENT_SKIP_NEWLINE.SKIP_ALL - : COMMENT_SKIP_NEWLINE.DEFAULT, - ); - } else if (shouldSkipNewline && type === COMMENT_TYPE.TRAILING) { - this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL); - } else { - this._printComment(comment, COMMENT_SKIP_NEWLINE.DEFAULT); - } - } else if ( - type === COMMENT_TYPE.INNER && - !(node.type === "ObjectExpression" && node.properties.length > 1) && - node.type !== "ClassBody" && - node.type !== "TSInterfaceBody" - ) { - // class X { - // /*:: a: number*/ - // /*:: b: ?string*/ - // } + const offset = + commentStartLine - (i === 0 ? nodeEndLine - lineOffset : lastLine); + lastLine = commentEndLine; + + if (!_noLineTerminator) this.newline(offset); + this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL); + } + } else { + hasLoc = false; + + if (len === 1) { + const singleLine = comment.loc + ? comment.loc.start.line === comment.loc.end.line + : !comment.value.includes("\n"); + const shouldSkipNewline = + singleLine && + !isStatement(node) && + !isClassBody(parent) && + !isTSInterfaceBody(parent); + + if (type === COMMENT_TYPE.LEADING) { this._printComment( comment, - i === 0 - ? COMMENT_SKIP_NEWLINE.SKIP_LEADING - : i === len - 1 - ? COMMENT_SKIP_NEWLINE.SKIP_TRAILING + (shouldSkipNewline && node.type !== "ObjectExpression") || + (singleLine && isFunction(parent, { body: node })) + ? COMMENT_SKIP_NEWLINE.SKIP_ALL : COMMENT_SKIP_NEWLINE.DEFAULT, ); + } else if (shouldSkipNewline && type === COMMENT_TYPE.TRAILING) { + if (this._printComment(comment, COMMENT_SKIP_NEWLINE.SKIP_ALL)) { + break; + } } else { this._printComment(comment, COMMENT_SKIP_NEWLINE.DEFAULT); } + } else if ( + type === COMMENT_TYPE.INNER && + !(node.type === "ObjectExpression" && node.properties.length > 1) && + node.type !== "ClassBody" && + node.type !== "TSInterfaceBody" + ) { + // class X { + // /*:: a: number*/ + // /*:: b: ?string*/ + // } + + const skippedDueToNewlie = this._printComment( + comment, + i === 0 + ? COMMENT_SKIP_NEWLINE.SKIP_LEADING + : i === len - 1 + ? COMMENT_SKIP_NEWLINE.SKIP_TRAILING + : COMMENT_SKIP_NEWLINE.DEFAULT, + ); + if (skippedDueToNewlie) break; + } else { + this._printComment(comment, COMMENT_SKIP_NEWLINE.DEFAULT); } } + } - if (type === COMMENT_TYPE.TRAILING && hasLoc && lastLine) { - this._lastCommentLine = lastLine; - } + if (type === COMMENT_TYPE.TRAILING && hasLoc && lastLine) { + this._lastCommentLine = lastLine; } } } diff --git a/packages/babel-generator/test/fixtures/decorator-auto-accessors/comments/input.js b/packages/babel-generator/test/fixtures/decorator-auto-accessors/comments/input.js new file mode 100644 index 000000000000..0cbde9cbf6c4 --- /dev/null +++ b/packages/babel-generator/test/fixtures/decorator-auto-accessors/comments/input.js @@ -0,0 +1,6 @@ +class A { + /* 0 */ accessor /* 1 */ x /* 2 */ = /* 3 */ 0 /* 4 */; /* 5 */ + + /* 6 */ accessor /* 7 */ [/* 8 */ x /* 9 */] /* 10 + multiline */ = /* 11 */ 0 /* 12 */; /* 13 */ +} diff --git a/packages/babel-generator/test/fixtures/decorator-auto-accessors/comments/output.js b/packages/babel-generator/test/fixtures/decorator-auto-accessors/comments/output.js new file mode 100644 index 000000000000..68159011cf10 --- /dev/null +++ b/packages/babel-generator/test/fixtures/decorator-auto-accessors/comments/output.js @@ -0,0 +1,8 @@ +class A { + /* 0 */accessor /* 1 */x /* 2 */ = /* 3 */0 /* 4 */; /* 5 */ + + /* 6 */ + accessor /* 7 */ [/* 8 */x /* 9 */] + /* 10 + multiline */ = /* 11 */0 /* 12 */; /* 13 */ +} \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/decorator-auto-accessors/options.json b/packages/babel-generator/test/fixtures/decorator-auto-accessors/options.json new file mode 100644 index 000000000000..4d468b93b4ac --- /dev/null +++ b/packages/babel-generator/test/fixtures/decorator-auto-accessors/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["decorators", { "decoratorsBeforeExport": false }], + "decoratorAutoAccessors" + ], + "decoratorsBeforeExport": true +} diff --git a/packages/babel-generator/test/fixtures/regression/inner-comment-async-arrows/input.js b/packages/babel-generator/test/fixtures/regression/inner-comment-async-arrows/input.js new file mode 100644 index 000000000000..feb994fd4a8f --- /dev/null +++ b/packages/babel-generator/test/fixtures/regression/inner-comment-async-arrows/input.js @@ -0,0 +1,14 @@ +async /* 1 */ () => {}; +async () /* 2 */ => {}; +async /* 3 */ (param) => {}; +async (param) /* 4 */ => {}; +async ( /* 5 */ ) => {}; +async ( /* + * 6 with newline + */ +) => {}; + +async /* 7 */ ( /* + * 8 with newline + */ +) /* 9 */ => {}; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/regression/inner-comment-async-arrows/output.js b/packages/babel-generator/test/fixtures/regression/inner-comment-async-arrows/output.js new file mode 100644 index 000000000000..0cb039d8bc25 --- /dev/null +++ b/packages/babel-generator/test/fixtures/regression/inner-comment-async-arrows/output.js @@ -0,0 +1,14 @@ +async /* 1 */ () => {}; +async /* 2 */ () => {}; +async param /* 3 */ => {}; +async param /* 4 */ => {}; +async /* 5 */ () => {}; +async ( /* + * 6 with newline + */ +) => {}; +async /* 7 */ ( + /* + * 8 with newline + */ + /* 9 */) => {}; \ No newline at end of file