/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-generator/src/buffer.ts
391 строка
11 KB
liuxingbaoyu
chore: Update prettier v3.9 (#18147)
20 июл 2026, 21:37
Не верифицирован
20 июл 2026, 21:37
61bfda5
Код
Авторство
О чём код?
import type SourceMap from "./source-map.ts"; import type { SourceLocation } from "@babel/types"; import * as charcodes from "charcodes"; export type Loc = SourceLocation; export type Pos = SourceLocation["start"]; type SourcePosition = { line: number | undefined; column: number | undefined; identifierName: string | undefined; identifierNamePos: Pos | undefined; filename: string | undefined; }; const spaceIndents: string[] = []; for (let i = 0; i < 32; i++) { spaceIndents.push(" ".repeat(i * 2)); } export default class Buffer { constructor(map: SourceMap | null, indentChar: string) { this._map = map; this._indentChar = indentChar; } _map: SourceMap | null = null; _buf = ""; _str = ""; _appendCount = 0; _last = 0; _canMarkIdName = true; _indentChar = ""; _queuedChar: typeof charcodes.space | typeof charcodes.semicolon | 0 = 0; _position = { line: 1, column: 0, }; _sourcePosition: SourcePosition = { identifierName: undefined, identifierNamePos: undefined, line: undefined, column: undefined, filename: undefined, }; /** * Get the final string output from the buffer, along with the sourcemap if one exists. */ get() { const { _map, _last } = this; if (this._queuedChar !== charcodes.space) { this._flush(); } // Whatever trim is used here should not execute a regex against the // source string since it may be arbitrarily large after all transformations const code = _last === charcodes.lineFeed ? (this._buf + this._str).trimRight() : this._buf + this._str; // Creating objects with getters is expensive. if (_map === null) { return { code: code, decodedMap: undefined, map: null, rawMappings: undefined, }; } const result = { code: code, // Decoded sourcemap is free to generate. decodedMap: _map.getDecoded(), // Encoding the sourcemap is moderately CPU expensive. get map() { const resultMap = _map.get(); result.map = resultMap; return resultMap; }, set map(value) { Object.defineProperty(result, "map", { value, writable: true }); }, // Retrieving the raw mappings is very memory intensive. get rawMappings() { const mappings = _map.getRawMappings(); result.rawMappings = mappings; return mappings; }, set rawMappings(value) { Object.defineProperty(result, "rawMappings", { value, writable: true }); }, }; return result; } /** * Add a string to the buffer that cannot be reverted. */ append( str: string, maybeNewline: boolean, ignoreMapping: boolean = false, ): void { this._flush(); this._append(str, maybeNewline, ignoreMapping); } appendChar(char: number): void { this._flush(); this._appendChar(char, 1, true); } /** * Add a string to the buffer than can be reverted. */ queue(char: typeof charcodes.space | typeof charcodes.semicolon): void { this._flush(); this._queuedChar = char; } _flush(): void { const queuedChar = this._queuedChar; if (queuedChar !== 0) { this._appendChar(queuedChar, 1, true); this._queuedChar = 0; } } _appendChar(char: number, repeat: number, useSourcePos: boolean): void { this._last = char; if (char === -1) { const indent = repeat >= 64 ? this._indentChar.repeat(repeat) : spaceIndents[repeat / 2]; this._str += indent; } else { this._str += repeat > 1 ? String.fromCharCode(char).repeat(repeat) : String.fromCharCode(char); } const isSpace = char === charcodes.space; const position = this._position; if (char !== charcodes.lineFeed) { if (this._map) { const sourcePos = this._sourcePosition; if (useSourcePos && sourcePos) { this._map.mark( position, null, sourcePos.line, sourcePos.column, isSpace ? undefined : sourcePos.identifierName, isSpace ? undefined : sourcePos.identifierNamePos, sourcePos.filename, ); if (!isSpace && this._canMarkIdName) { sourcePos.identifierName = undefined; sourcePos.identifierNamePos = undefined; } } else { this._map.mark(position, null); } } position.column += repeat; } else { position.line++; position.column = 0; } } _append(str: string, maybeNewline: boolean, ignoreMapping: boolean): void { const len = str.length; const position = this._position; const sourcePos = this._sourcePosition; this._last = -1; /* LAST_CHAR_KINDS.NORMAL */ if (++this._appendCount > 4096) { // eslint-disable-next-line @typescript-eslint/no-unused-expressions +this._str; // Unexplainable huge performance boost. Ref: https://github.com/davidmarkclements/flatstr License: MIT this._buf += this._str; this._str = str; this._appendCount = 0; } else { this._str += str; } const hasMap = !ignoreMapping && this._map !== null; if (!maybeNewline && !hasMap) { position.column += len; return; } const { column, identifierName, identifierNamePos, filename } = sourcePos; let line = sourcePos.line; if ( (identifierName != null || identifierNamePos != null) && this._canMarkIdName ) { sourcePos.identifierName = undefined; sourcePos.identifierNamePos = undefined; } // Search for newline chars. We search only for `\n`, since both `\r` and // `\r\n` are normalized to `\n` during parse. We exclude `\u2028` and // `\u2029` for performance reasons, they're so uncommon that it's probably // ok. It's also unclear how other sourcemap utilities handle them... let i = str.indexOf("\n"); let last = 0; // If the string starts with a newline char, then adding a mark is redundant. // This catches both "no newlines" and "newline after several chars". if (hasMap && i !== 0) { this._map!.mark( position, str, line, column, identifierName, identifierNamePos, filename, ); } // Now, find each remaining newline char in the string. while (i !== -1) { position.line++; position.column = 0; last = i + 1; // We mark the start of each line, which happens directly after this newline char // unless this is the last char. // When manually adding multi-line content (such as a comment), `line` will be `undefined`. if (last < len && line !== undefined) { line++; if (hasMap) { this._map!.mark( position, str, line, 0, undefined, undefined, filename, ); } } i = str.indexOf("\n", last); } position.column += len - last; } removeLastSemicolon(): void { if (this._queuedChar === charcodes.semicolon) { this._queuedChar = 0; } } getLastChar(checkQueue?: boolean): number { if (!checkQueue) { return this._last; } const queuedChar = this._queuedChar; return queuedChar !== 0 ? queuedChar : this._last; } /** * This will only detect at most 1 newline after a call to `flush()`, * but this has not been found so far, and an accurate count can be achieved if needed later. */ getNewlineCount(): number { return this._queuedChar === 0 && this._last === charcodes.lineFeed ? 1 : 0; } hasContent(): boolean { return this._last !== 0; /*|| this._queuedChar !== 0*/ } /** * Certain sourcemap usecases expect mappings to be more accurate than * Babel's generic sourcemap handling allows. For now, we special-case * identifiers to allow for the primary cases to work. * The goal of this line is to ensure that the map output from Babel will * have an exact range on identifiers in the output code. Without this * line, Babel would potentially include some number of trailing tokens * that are printed after the identifier, but before another location has * been assigned. * This allows tooling like Rollup and Webpack to more accurately perform * their own transformations. Most importantly, this allows the import/export * transformations performed by those tools to loose less information when * applying their own transformations on top of the code and map results * generated by Babel itself. * * The primary example of this is the snippet: * * import mod from "mod"; * mod(); * * With this line, there will be one mapping range over "mod" and another * over "();", where previously it would have been a single mapping. */ exactSource(loc: Loc, cb: () => void) { if (!this._map) { cb(); return; } this.source("start", loc); const identifierName = loc.identifierName; const sourcePos = this._sourcePosition; if (identifierName != null) { this._canMarkIdName = false; sourcePos.identifierName = identifierName; } cb(); if (identifierName != null) { this._canMarkIdName = true; sourcePos.identifierName = undefined; sourcePos.identifierNamePos = undefined; } this.source("end", loc); } /** * Sets a given position as the current source location so generated code after this call * will be given this position in the sourcemap. */ source(prop: "start" | "end", loc: Loc): void { if (!this._map) return; // Since this is called extremely often, we reuse the same _sourcePosition // object for the whole lifetime of the buffer. this._normalizePosition(prop, loc, 0); } sourceWithOffset( prop: "start" | "end", loc: Loc, columnOffset: number, ): void { if (!this._map) return; this._normalizePosition(prop, loc, columnOffset); } _normalizePosition(prop: "start" | "end", loc: Loc, columnOffset: number) { this._flush(); const pos = loc[prop]; if (pos) { this.setSourcePosition( pos.line, // TODO: Fix https://github.com/babel/babel/issues/15712 in downstream Math.max(pos.column + columnOffset, 0), ); this._sourcePosition.filename = loc.filename; } } setSourcePosition(line: number, column: number): void { const target = this._sourcePosition; target.line = line; target.column = column; } getCurrentColumn(): number { return this._position.column + (this._queuedChar ? 1 : 0); } getCurrentLine(): number { return this._position.line; } }