mirror of
https://github.com/hanzoai/babel.git
synced 2026-09-24 16:56:45 +00:00
889 lines
23 KiB
TypeScript
889 lines
23 KiB
TypeScript
// From packages\babel-generator\src\printer.ts
|
||
|
||
import Buffer from "./buffer";
|
||
import type { Loc } from "./buffer";
|
||
import * as n from "./node";
|
||
import type * as t from "@babel/types";
|
||
import type {
|
||
RecordAndTuplePluginOptions,
|
||
PipelineOperatorPluginOptions,
|
||
} from "@babel/parser";
|
||
import type { Opts as jsescOptions } from "jsesc";
|
||
|
||
import * as generatorFunctions from "./generators";
|
||
import type SourceMap from "./source-map";
|
||
import * as charCodes from "charcodes";
|
||
|
||
const SCIENTIFIC_NOTATION = /e/i;
|
||
const ZERO_DECIMAL_INTEGER = /\.0+$/;
|
||
const NON_DECIMAL_LITERAL = /^0[box]/;
|
||
const PURE_ANNOTATION_RE = /^\s*[@#]__PURE__\s*$/;
|
||
|
||
const { needsParens, needsWhitespaceAfter, needsWhitespaceBefore } = n;
|
||
|
||
export type Format = {
|
||
shouldPrintComment: (comment: string) => boolean;
|
||
retainLines: boolean;
|
||
retainFunctionParens: boolean;
|
||
comments: boolean;
|
||
auxiliaryCommentBefore: string;
|
||
auxiliaryCommentAfter: string;
|
||
compact: boolean | "auto";
|
||
minified: boolean;
|
||
concise: boolean;
|
||
indent: {
|
||
adjustMultilineComment: boolean;
|
||
style: string;
|
||
};
|
||
recordAndTupleSyntaxType: RecordAndTuplePluginOptions["syntaxType"];
|
||
jsescOption: jsescOptions;
|
||
jsonCompatibleStrings?: boolean;
|
||
/**
|
||
* For use with the Hack-style pipe operator.
|
||
* Changes what token is used for pipe bodies’ topic references.
|
||
*/
|
||
topicToken?: PipelineOperatorPluginOptions["topicToken"];
|
||
/**
|
||
* @deprecated Removed in Babel 8
|
||
*/
|
||
decoratorsBeforeExport?: boolean;
|
||
};
|
||
|
||
interface AddNewlinesOptions {
|
||
addNewlines(leading: boolean, node: t.Node): number;
|
||
}
|
||
|
||
interface PrintSequenceOptions extends Partial<AddNewlinesOptions> {
|
||
statement?: boolean;
|
||
indent?: boolean;
|
||
}
|
||
|
||
interface PrintListOptions {
|
||
separator?: (this: Printer) => void;
|
||
statement?: boolean;
|
||
indent?: boolean;
|
||
}
|
||
|
||
type PrintJoinOptions = PrintListOptions &
|
||
PrintSequenceOptions & {
|
||
iterator?: (node: t.Node, index: number) => void;
|
||
};
|
||
class Printer {
|
||
constructor(format: Format, map: SourceMap) {
|
||
this.format = format;
|
||
this._buf = new Buffer(map);
|
||
|
||
this._indentChar = format.indent.style.charCodeAt(0);
|
||
this._indentRepeat = format.indent.style.length;
|
||
}
|
||
|
||
declare format: Format;
|
||
inForStatementInitCounter: number = 0;
|
||
|
||
declare _buf: Buffer;
|
||
_printStack: Array<t.Node> = [];
|
||
_indent: number = 0;
|
||
_indentChar: number = 0;
|
||
_indentRepeat: number = 0;
|
||
_insideAux: boolean = false;
|
||
_parenPushNewlineState: { printed: boolean } | null = null;
|
||
_noLineTerminator: boolean = false;
|
||
_printAuxAfterOnNextUserNode: boolean = false;
|
||
_printedComments = new Set<t.Comment>();
|
||
_endsWithInteger = false;
|
||
_endsWithWord = false;
|
||
|
||
generate(ast: t.Node) {
|
||
this.print(ast);
|
||
this._maybeAddAuxComment();
|
||
|
||
return this._buf.get();
|
||
}
|
||
|
||
/**
|
||
* Increment indent size.
|
||
*/
|
||
|
||
indent(): void {
|
||
if (this.format.compact || this.format.concise) return;
|
||
|
||
this._indent++;
|
||
}
|
||
|
||
/**
|
||
* Decrement indent size.
|
||
*/
|
||
|
||
dedent(): void {
|
||
if (this.format.compact || this.format.concise) return;
|
||
|
||
this._indent--;
|
||
}
|
||
|
||
/**
|
||
* Add a semicolon to the buffer.
|
||
*/
|
||
|
||
semicolon(force: boolean = false): void {
|
||
this._maybeAddAuxComment();
|
||
if (force) {
|
||
this._appendChar(charCodes.semicolon);
|
||
} else {
|
||
this._queue(charCodes.semicolon);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Add a right brace to the buffer.
|
||
*/
|
||
|
||
rightBrace(): void {
|
||
if (this.format.minified) {
|
||
this._buf.removeLastSemicolon();
|
||
}
|
||
this.token("}");
|
||
}
|
||
|
||
/**
|
||
* Add a space to the buffer unless it is compact.
|
||
*/
|
||
|
||
space(force: boolean = false): void {
|
||
if (this.format.compact) return;
|
||
|
||
if (force) {
|
||
this._space();
|
||
} else if (this._buf.hasContent()) {
|
||
const lastCp = this.getLastChar();
|
||
if (lastCp !== charCodes.space && lastCp !== charCodes.lineFeed) {
|
||
this._space();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Writes a token that can't be safely parsed without taking whitespace into account.
|
||
*/
|
||
|
||
word(str: string): void {
|
||
// prevent concatenating words and creating // comment out of division and regex
|
||
if (
|
||
this._endsWithWord ||
|
||
(str.charCodeAt(0) === charCodes.slash && this.endsWith(charCodes.slash))
|
||
) {
|
||
this._space();
|
||
}
|
||
|
||
this._maybeAddAuxComment();
|
||
this._append(str, false);
|
||
|
||
this._endsWithWord = true;
|
||
}
|
||
|
||
/**
|
||
* Writes a number token so that we can validate if it is an integer.
|
||
*/
|
||
|
||
number(str: string): void {
|
||
this.word(str);
|
||
|
||
// Integer tokens need special handling because they cannot have '.'s inserted
|
||
// immediately after them.
|
||
this._endsWithInteger =
|
||
Number.isInteger(+str) &&
|
||
!NON_DECIMAL_LITERAL.test(str) &&
|
||
!SCIENTIFIC_NOTATION.test(str) &&
|
||
!ZERO_DECIMAL_INTEGER.test(str) &&
|
||
str.charCodeAt(str.length - 1) !== charCodes.dot;
|
||
}
|
||
|
||
/**
|
||
* Writes a simple token.
|
||
*/
|
||
|
||
token(str: string, maybeNewline = false): void {
|
||
// space is mandatory to avoid outputting <!--
|
||
// http://javascript.spec.whatwg.org/#comment-syntax
|
||
const lastChar = this.getLastChar();
|
||
const strFirst = str.charCodeAt(0);
|
||
if (
|
||
(lastChar === charCodes.exclamationMark && str === "--") ||
|
||
// Need spaces for operators of the same kind to avoid: `a+++b`
|
||
(strFirst === charCodes.plusSign && lastChar === charCodes.plusSign) ||
|
||
(strFirst === charCodes.dash && lastChar === charCodes.dash) ||
|
||
// Needs spaces to avoid changing '34' to '34.', which would still be a valid number.
|
||
(strFirst === charCodes.dot && this._endsWithInteger)
|
||
) {
|
||
this._space();
|
||
}
|
||
|
||
this._maybeAddAuxComment();
|
||
this._append(str, maybeNewline);
|
||
}
|
||
|
||
tokenChar(char: number): void {
|
||
// space is mandatory to avoid outputting <!--
|
||
// http://javascript.spec.whatwg.org/#comment-syntax
|
||
const lastChar = this.getLastChar();
|
||
if (
|
||
// Need spaces for operators of the same kind to avoid: `a+++b`
|
||
(char === charCodes.plusSign && lastChar === charCodes.plusSign) ||
|
||
(char === charCodes.dash && lastChar === charCodes.dash) ||
|
||
// Needs spaces to avoid changing '34' to '34.', which would still be a valid number.
|
||
(char === charCodes.dot && this._endsWithInteger)
|
||
) {
|
||
this._space();
|
||
}
|
||
|
||
this._maybeAddAuxComment();
|
||
this._appendChar(char);
|
||
}
|
||
|
||
/**
|
||
* Add a newline (or many newlines), maintaining formatting.
|
||
*/
|
||
|
||
newline(i: number = 1): void {
|
||
if (this.format.retainLines || this.format.compact) return;
|
||
|
||
if (this.format.concise) {
|
||
this.space();
|
||
return;
|
||
}
|
||
|
||
const charBeforeNewline = this.endsWithCharAndNewline();
|
||
// never allow more than two lines
|
||
if (charBeforeNewline === charCodes.lineFeed) return;
|
||
|
||
if (
|
||
charBeforeNewline === charCodes.leftCurlyBrace ||
|
||
charBeforeNewline === charCodes.colon
|
||
) {
|
||
i--;
|
||
}
|
||
if (i <= 0) return;
|
||
|
||
for (let j = 0; j < i; j++) {
|
||
this._newline();
|
||
}
|
||
}
|
||
|
||
endsWith(char: number): boolean {
|
||
return this.getLastChar() === char;
|
||
}
|
||
|
||
getLastChar(): number {
|
||
return this._buf.getLastChar();
|
||
}
|
||
|
||
endsWithCharAndNewline(): number {
|
||
return this._buf.endsWithCharAndNewline();
|
||
}
|
||
|
||
removeTrailingNewline(): void {
|
||
this._buf.removeTrailingNewline();
|
||
}
|
||
|
||
exactSource(loc: Loc | undefined, cb: () => void) {
|
||
this._catchUp("start", loc);
|
||
|
||
this._buf.exactSource(loc, cb);
|
||
}
|
||
|
||
source(prop: "start" | "end", loc: Loc | undefined): void {
|
||
this._catchUp(prop, loc);
|
||
|
||
this._buf.source(prop, loc);
|
||
}
|
||
|
||
sourceWithOffset(
|
||
prop: "start" | "end",
|
||
loc: Loc | undefined,
|
||
lineOffset: number,
|
||
columnOffset: number,
|
||
): void {
|
||
this._catchUp(prop, loc);
|
||
|
||
this._buf.sourceWithOffset(prop, loc, lineOffset, columnOffset);
|
||
}
|
||
|
||
withSource(
|
||
prop: "start" | "end",
|
||
loc: Loc | undefined,
|
||
cb: () => void,
|
||
): void {
|
||
this._catchUp(prop, loc);
|
||
|
||
this._buf.withSource(prop, loc, cb);
|
||
}
|
||
|
||
_space(): void {
|
||
this._queue(charCodes.space);
|
||
}
|
||
|
||
_newline(): void {
|
||
this._queue(charCodes.lineFeed);
|
||
}
|
||
|
||
_append(str: string, maybeNewline: boolean): void {
|
||
this._maybeAddParen(str);
|
||
this._maybeIndent(str.charCodeAt(0));
|
||
|
||
this._buf.append(str, maybeNewline);
|
||
|
||
this._endsWithWord = false;
|
||
this._endsWithInteger = false;
|
||
}
|
||
|
||
_appendChar(char: number): void {
|
||
this._maybeAddParenChar(char);
|
||
this._maybeIndent(char);
|
||
|
||
this._buf.appendChar(char);
|
||
|
||
this._endsWithWord = false;
|
||
this._endsWithInteger = false;
|
||
}
|
||
|
||
_queue(char: number) {
|
||
this._maybeAddParenChar(char);
|
||
this._maybeIndent(char);
|
||
|
||
this._buf.queue(char);
|
||
|
||
this._endsWithWord = false;
|
||
this._endsWithInteger = false;
|
||
}
|
||
|
||
_maybeIndent(firstChar: number): void {
|
||
// we've got a newline before us so prepend on the indentation
|
||
if (
|
||
this._indent &&
|
||
firstChar !== charCodes.lineFeed &&
|
||
this.endsWith(charCodes.lineFeed)
|
||
) {
|
||
this._buf.queueIndentation(this._indentChar, this._getIndent());
|
||
}
|
||
}
|
||
|
||
_maybeAddParenChar(char: number): void {
|
||
// see startTerminatorless() instance method
|
||
const parenPushNewlineState = this._parenPushNewlineState;
|
||
if (!parenPushNewlineState) return;
|
||
|
||
// This function does two things:
|
||
// - If needed, prints a parenthesis
|
||
// - If the currently printed string removes the need for the paren,
|
||
// it resets the _parenPushNewlineState field.
|
||
// Almost everything removes the need for a paren, except for
|
||
// comments and whitespaces.
|
||
|
||
if (char === charCodes.space) {
|
||
// Whitespaces only, the parentheses might still be needed.
|
||
return;
|
||
}
|
||
|
||
// Check for newline or comment.
|
||
if (char !== charCodes.lineFeed) {
|
||
this._parenPushNewlineState = null;
|
||
return;
|
||
}
|
||
|
||
this.token("(");
|
||
this.indent();
|
||
parenPushNewlineState.printed = true;
|
||
}
|
||
|
||
_maybeAddParen(str: string): void {
|
||
// see startTerminatorless() instance method
|
||
const parenPushNewlineState = this._parenPushNewlineState;
|
||
if (!parenPushNewlineState) return;
|
||
|
||
// This function does two things:
|
||
// - If needed, prints a parenthesis
|
||
// - If the currently printed string removes the need for the paren,
|
||
// it resets the _parenPushNewlineState field.
|
||
// Almost everything removes the need for a paren, except for
|
||
// comments and whitespaces.
|
||
|
||
const len = str.length;
|
||
|
||
let i;
|
||
for (i = 0; i < len && str.charCodeAt(i) === charCodes.space; i++) continue;
|
||
if (i === len) {
|
||
// Whitespaces only, the parentheses might still be needed.
|
||
return;
|
||
}
|
||
|
||
// Check for newline or comment.
|
||
const cha = str.charCodeAt(i);
|
||
if (cha !== charCodes.lineFeed) {
|
||
if (
|
||
// This is not a comment (it doesn't start with /)
|
||
cha !== charCodes.slash ||
|
||
// This is not a comment (it's a / operator)
|
||
i + 1 === len
|
||
) {
|
||
// After a normal token, the parentheses aren't needed anymore
|
||
this._parenPushNewlineState = null;
|
||
return;
|
||
}
|
||
|
||
const chaPost = str.charCodeAt(i + 1);
|
||
|
||
if (chaPost === charCodes.asterisk) {
|
||
// This is a block comment
|
||
|
||
if (PURE_ANNOTATION_RE.test(str.slice(i + 2, len - 2))) {
|
||
// We avoid printing newlines after #__PURE__ comments (we treat
|
||
// then as unary operators), but we must keep the old
|
||
// parenPushNewlineState because, if a newline was forbidden, it is
|
||
// still forbidden after the comment.
|
||
return;
|
||
}
|
||
|
||
// NOTE: code flow continues from here to after these if/elses
|
||
} else if (chaPost !== charCodes.slash) {
|
||
// This is neither a block comment, nor a line comment.
|
||
// After a normal token, the parentheses aren't needed anymore
|
||
this._parenPushNewlineState = null;
|
||
return;
|
||
}
|
||
}
|
||
|
||
this.token("(");
|
||
this.indent();
|
||
parenPushNewlineState.printed = true;
|
||
}
|
||
|
||
_catchUp(prop: "start" | "end", loc?: Loc) {
|
||
if (!this.format.retainLines) return;
|
||
|
||
// catch up to this nodes newline if we're behind
|
||
const pos = loc ? loc[prop] : null;
|
||
if (pos?.line != null) {
|
||
const count = pos.line - this._buf.getCurrentLine();
|
||
|
||
for (let i = 0; i < count; i++) {
|
||
this._newline();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the current indent.
|
||
*/
|
||
|
||
_getIndent(): number {
|
||
return this._indentRepeat * this._indent;
|
||
}
|
||
|
||
printTerminatorless(node: t.Node, parent: t.Node, isLabel: boolean) {
|
||
/**
|
||
* Set some state that will be modified if a newline has been inserted before any
|
||
* non-space characters.
|
||
*
|
||
* This is to prevent breaking semantics for terminatorless separator nodes. eg:
|
||
*
|
||
* return foo;
|
||
*
|
||
* returns `foo`. But if we do:
|
||
*
|
||
* return
|
||
* foo;
|
||
*
|
||
* `undefined` will be returned and not `foo` due to the terminator.
|
||
*/
|
||
if (isLabel) {
|
||
this._noLineTerminator = true;
|
||
this.print(node, parent);
|
||
this._noLineTerminator = false;
|
||
} else {
|
||
const terminatorState = {
|
||
printed: false,
|
||
};
|
||
this._parenPushNewlineState = terminatorState;
|
||
this.print(node, parent);
|
||
/**
|
||
* Print an ending parentheses if a starting one has been printed.
|
||
*/
|
||
if (terminatorState.printed) {
|
||
this.dedent();
|
||
this.newline();
|
||
this.token(")");
|
||
}
|
||
}
|
||
}
|
||
|
||
print(node: t.Node | null, parent?: t.Node, noLineTerminator?: boolean) {
|
||
if (!node) return;
|
||
|
||
const nodeType = node.type;
|
||
const format = this.format;
|
||
|
||
const oldConcise = format.concise;
|
||
if (
|
||
// @ts-expect-error document _compact AST properties
|
||
node._compact
|
||
) {
|
||
format.concise = true;
|
||
}
|
||
|
||
const printMethod =
|
||
this[
|
||
nodeType as Exclude<
|
||
t.Node["type"],
|
||
// removed
|
||
| "Noop"
|
||
// renamed
|
||
| t.DeprecatedAliases["type"]
|
||
>
|
||
];
|
||
if (printMethod === undefined) {
|
||
throw new ReferenceError(
|
||
`unknown node of type ${JSON.stringify(
|
||
nodeType,
|
||
)} with constructor ${JSON.stringify(node.constructor.name)}`,
|
||
);
|
||
}
|
||
|
||
this._printStack.push(node);
|
||
|
||
const oldInAux = this._insideAux;
|
||
this._insideAux = node.loc == undefined;
|
||
this._maybeAddAuxComment(this._insideAux && !oldInAux);
|
||
|
||
let shouldPrintParens: boolean;
|
||
if (
|
||
format.retainFunctionParens &&
|
||
nodeType === "FunctionExpression" &&
|
||
node.extra &&
|
||
node.extra.parenthesized
|
||
) {
|
||
shouldPrintParens = true;
|
||
} else {
|
||
shouldPrintParens = needsParens(node, parent, this._printStack);
|
||
}
|
||
if (shouldPrintParens) this.token("(");
|
||
|
||
this._printLeadingComments(node);
|
||
|
||
const loc = nodeType === "Program" || nodeType === "File" ? null : node.loc;
|
||
|
||
this.exactSource(loc, printMethod.bind(this, node, parent));
|
||
|
||
if (noLineTerminator && !this._noLineTerminator) {
|
||
this._noLineTerminator = true;
|
||
this._printTrailingComments(node);
|
||
this._noLineTerminator = false;
|
||
} else {
|
||
this._printTrailingComments(node);
|
||
}
|
||
|
||
if (shouldPrintParens) this.token(")");
|
||
|
||
// end
|
||
this._printStack.pop();
|
||
|
||
format.concise = oldConcise;
|
||
this._insideAux = oldInAux;
|
||
}
|
||
|
||
_maybeAddAuxComment(enteredPositionlessNode?: boolean) {
|
||
if (enteredPositionlessNode) this._printAuxBeforeComment();
|
||
if (!this._insideAux) this._printAuxAfterComment();
|
||
}
|
||
|
||
_printAuxBeforeComment() {
|
||
if (this._printAuxAfterOnNextUserNode) return;
|
||
this._printAuxAfterOnNextUserNode = true;
|
||
|
||
const comment = this.format.auxiliaryCommentBefore;
|
||
if (comment) {
|
||
this._printComment({
|
||
type: "CommentBlock",
|
||
value: comment,
|
||
});
|
||
}
|
||
}
|
||
|
||
_printAuxAfterComment() {
|
||
if (!this._printAuxAfterOnNextUserNode) return;
|
||
this._printAuxAfterOnNextUserNode = false;
|
||
|
||
const comment = this.format.auxiliaryCommentAfter;
|
||
if (comment) {
|
||
this._printComment({
|
||
type: "CommentBlock",
|
||
value: comment,
|
||
});
|
||
}
|
||
}
|
||
|
||
getPossibleRaw(
|
||
node:
|
||
| t.StringLiteral
|
||
| t.NumericLiteral
|
||
| t.BigIntLiteral
|
||
| t.DecimalLiteral
|
||
| t.DirectiveLiteral
|
||
| t.JSXText,
|
||
): string | undefined {
|
||
const extra = node.extra;
|
||
if (
|
||
extra &&
|
||
extra.raw != null &&
|
||
extra.rawValue != null &&
|
||
node.value === extra.rawValue
|
||
) {
|
||
// @ts-expect-error: The extra.raw of these AST node types must be a string
|
||
return extra.raw;
|
||
}
|
||
}
|
||
|
||
printJoin(
|
||
nodes: Array<t.Node> | undefined | null,
|
||
parent: t.Node,
|
||
opts: PrintJoinOptions = {},
|
||
) {
|
||
if (!nodes?.length) return;
|
||
|
||
if (opts.indent) this.indent();
|
||
|
||
const newlineOpts: AddNewlinesOptions = {
|
||
addNewlines: opts.addNewlines,
|
||
};
|
||
|
||
const len = nodes.length;
|
||
for (let i = 0; i < len; i++) {
|
||
const node = nodes[i];
|
||
if (!node) continue;
|
||
|
||
if (opts.statement) this._printNewline(true, node, parent, newlineOpts);
|
||
|
||
this.print(node, parent);
|
||
|
||
if (opts.iterator) {
|
||
opts.iterator(node, i);
|
||
}
|
||
|
||
if (opts.separator && i < len - 1) {
|
||
opts.separator.call(this);
|
||
}
|
||
|
||
if (opts.statement) this._printNewline(false, node, parent, newlineOpts);
|
||
}
|
||
|
||
if (opts.indent) this.dedent();
|
||
}
|
||
|
||
printAndIndentOnComments(node: t.Node, parent: t.Node) {
|
||
const indent = node.leadingComments && node.leadingComments.length > 0;
|
||
if (indent) this.indent();
|
||
this.print(node, parent);
|
||
if (indent) this.dedent();
|
||
}
|
||
|
||
printBlock(parent: Extract<t.Node, { body: t.Statement }>) {
|
||
const node = parent.body;
|
||
|
||
if (node.type !== "EmptyStatement") {
|
||
this.space();
|
||
}
|
||
|
||
this.print(node, parent);
|
||
}
|
||
|
||
_printTrailingComments(node: t.Node) {
|
||
this._printComments(this._getComments(false, node));
|
||
}
|
||
|
||
_printLeadingComments(node: t.Node) {
|
||
this._printComments(
|
||
this._getComments(true, node),
|
||
// Don't add leading/trailing new lines to #__PURE__ annotations
|
||
true,
|
||
);
|
||
}
|
||
|
||
printInnerComments(node: t.Node, indent = true) {
|
||
if (!node.innerComments?.length) return;
|
||
if (indent) this.indent();
|
||
this._printComments(node.innerComments);
|
||
if (indent) this.dedent();
|
||
}
|
||
|
||
printSequence(
|
||
nodes: t.Node[],
|
||
parent: t.Node,
|
||
opts: PrintSequenceOptions = {},
|
||
) {
|
||
opts.statement = true;
|
||
return this.printJoin(nodes, parent, opts);
|
||
}
|
||
|
||
printList(items: t.Node[], parent: t.Node, opts: PrintListOptions = {}) {
|
||
if (opts.separator == null) {
|
||
opts.separator = commaSeparator;
|
||
}
|
||
|
||
return this.printJoin(items, parent, opts);
|
||
}
|
||
|
||
_printNewline(
|
||
leading: boolean,
|
||
node: t.Node,
|
||
parent: t.Node,
|
||
opts: AddNewlinesOptions,
|
||
) {
|
||
// Fast path since 'this.newline' does nothing when not tracking lines.
|
||
if (this.format.retainLines || this.format.compact) return;
|
||
|
||
// Fast path for concise since 'this.newline' just inserts a space when
|
||
// concise formatting is in use.
|
||
if (this.format.concise) {
|
||
this.space();
|
||
return;
|
||
}
|
||
|
||
let lines = 0;
|
||
// don't add newlines at the beginning of the file
|
||
if (this._buf.hasContent()) {
|
||
if (!leading) lines++; // always include at least a single line after
|
||
if (opts.addNewlines) lines += opts.addNewlines(leading, node) || 0;
|
||
|
||
const needs = leading ? needsWhitespaceBefore : needsWhitespaceAfter;
|
||
if (needs(node, parent)) lines++;
|
||
}
|
||
|
||
this.newline(Math.min(2, lines));
|
||
}
|
||
|
||
_getComments(leading: boolean, node: t.Node) {
|
||
// Note, we use a boolean flag here instead of passing in the attribute name as it is faster
|
||
// because this is called extremely frequently.
|
||
return (
|
||
(node && (leading ? node.leadingComments : node.trailingComments)) || null
|
||
);
|
||
}
|
||
|
||
_printComment(comment: t.Comment, skipNewLines?: 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 (this._printedComments.has(comment)) return;
|
||
|
||
if (!this.format.shouldPrintComment(comment.value)) return;
|
||
|
||
this._printedComments.add(comment);
|
||
|
||
const isBlockComment = comment.type === "CommentBlock";
|
||
|
||
// Add a newline before and after a block comment, unless explicitly
|
||
// disallowed
|
||
const printNewLines =
|
||
isBlockComment && !skipNewLines && !this._noLineTerminator;
|
||
|
||
if (printNewLines && this._buf.hasContent()) this.newline(1);
|
||
|
||
const lastCharCode = this.getLastChar();
|
||
if (
|
||
lastCharCode !== charCodes.leftSquareBracket &&
|
||
lastCharCode !== charCodes.leftCurlyBrace
|
||
) {
|
||
this.space();
|
||
}
|
||
|
||
let val;
|
||
let maybeNewline = false;
|
||
if (isBlockComment) {
|
||
val = `/*${comment.value}*/`;
|
||
if (this.format.indent.adjustMultilineComment) {
|
||
const offset = comment.loc?.start.column;
|
||
if (offset) {
|
||
const newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g");
|
||
val = val.replace(newlineRegex, "\n");
|
||
}
|
||
|
||
const indentSize = Math.max(
|
||
this._getIndent(),
|
||
this.format.retainLines ? 0 : this._buf.getCurrentColumn(),
|
||
);
|
||
val = val.replace(/\n(?!$)/g, `\n${" ".repeat(indentSize)}`);
|
||
|
||
maybeNewline = true;
|
||
}
|
||
} else if (!this._noLineTerminator) {
|
||
val = `//${comment.value}\n`;
|
||
maybeNewline = true;
|
||
} else {
|
||
val = `/*${comment.value}*/`;
|
||
}
|
||
|
||
// Avoid creating //* comments
|
||
if (this.endsWith(charCodes.slash)) this._space();
|
||
|
||
this.withSource(
|
||
"start",
|
||
comment.loc,
|
||
this._append.bind(this, val, maybeNewline),
|
||
);
|
||
|
||
if (printNewLines) this.newline(1);
|
||
}
|
||
|
||
_printComments(
|
||
comments?: readonly t.Comment[],
|
||
inlinePureAnnotation?: boolean,
|
||
) {
|
||
if (!comments?.length) return;
|
||
|
||
if (
|
||
inlinePureAnnotation &&
|
||
comments.length === 1 &&
|
||
PURE_ANNOTATION_RE.test(comments[0].value)
|
||
) {
|
||
this._printComment(
|
||
comments[0],
|
||
// Keep newlines if the comment marks a standalone call
|
||
this._buf.hasContent() && !this.endsWith(charCodes.lineFeed),
|
||
);
|
||
} else {
|
||
for (const comment of comments) {
|
||
this._printComment(comment);
|
||
}
|
||
}
|
||
}
|
||
// todo(flow->ts): was Node
|
||
printAssertions(node: Extract<t.Node, { assertions?: t.ImportAttribute[] }>) {
|
||
if (node.assertions?.length) {
|
||
this.space();
|
||
this.word("assert");
|
||
this.space();
|
||
this.token("{");
|
||
this.space();
|
||
this.printList(node.assertions, node);
|
||
this.space();
|
||
this.token("}");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Expose the node type functions and helpers on the prototype for easy usage.
|
||
Object.assign(Printer.prototype, generatorFunctions);
|
||
|
||
if (!process.env.BABEL_8_BREAKING) {
|
||
// @ts-ignore(Babel 7 vs Babel 8) Babel 7 has Noop print method
|
||
Printer.prototype.Noop = function Noop(this: Printer) {};
|
||
}
|
||
|
||
type GeneratorFunctions = typeof generatorFunctions;
|
||
interface Printer extends GeneratorFunctions {}
|
||
export default Printer;
|
||
|
||
function commaSeparator(this: Printer) {
|
||
this.token(",");
|
||
this.space();
|
||
}
|