diff --git a/packages/babel-generator/src/buffer.js b/packages/babel-generator/src/buffer.js index c6b043a18f..14bd4d2e07 100644 --- a/packages/babel-generator/src/buffer.js +++ b/packages/babel-generator/src/buffer.js @@ -1,4 +1,3 @@ -import Position from "./position"; import type SourceMap from "./source-map"; import trimEnd from "lodash/trimEnd"; @@ -21,7 +20,10 @@ export default class Buffer { _last: string = ""; _queue: Array = []; - _position: Position = new Position; + _position: Object = { + line: 1, + column: 0, + }; _sourcePosition: Object = { line: null, column: null, @@ -67,11 +69,21 @@ export default class Buffer { _append(str: string, line: number, column: number, filename: ?string): void { // If there the line is ending, adding a new mapping marker is redundant - if (this._map && str[0] !== "\n") this._map.mark(this._position, line, column, filename); + if (this._map && str[0] !== "\n") { + this._map.mark(this._position.line, this._position.column, line, column, filename); + } this._buf.push(str); this._last = str[str.length - 1]; - this._position.push(str); + + for (let i = 0; i < str.length; i++) { + if (str[i] === "\n") { + this._position.line++; + this._position.column = 0; + } else { + this._position.column++; + } + } } removeTrailingSpaces(): void { diff --git a/packages/babel-generator/src/generators/expressions.js b/packages/babel-generator/src/generators/expressions.js index 36f0485a91..38f4973665 100644 --- a/packages/babel-generator/src/generators/expressions.js +++ b/packages/babel-generator/src/generators/expressions.js @@ -137,8 +137,7 @@ export let YieldExpression = buildYieldAwait("yield"); export let AwaitExpression = buildYieldAwait("await"); export function EmptyStatement() { - this._lastPrintedIsEmptyStatement = true; - this.semicolon(); + this.semicolon(true /* force */); } export function ExpressionStatement(node: Object) { @@ -157,7 +156,7 @@ export function AssignmentPattern(node: Object) { export function AssignmentExpression(node: Object, parent: Object) { // Somewhere inside a for statement `init` node but doesn't usually // needs a paren except for `in` expressions: `for (a in b ? a : b;;)` - let parens = this._inForStatementInitCounter && node.operator === "in" && + let parens = this.inForStatementInitCounter && node.operator === "in" && !n.needsParens(node, parent); if (parens) { diff --git a/packages/babel-generator/src/generators/statements.js b/packages/babel-generator/src/generators/statements.js index e4aa15fd50..c3229db035 100644 --- a/packages/babel-generator/src/generators/statements.js +++ b/packages/babel-generator/src/generators/statements.js @@ -48,9 +48,9 @@ export function ForStatement(node: Object) { this.keyword("for"); this.token("("); - this._inForStatementInitCounter++; + this.inForStatementInitCounter++; this.print(node.init, node); - this._inForStatementInitCounter--; + this.inForStatementInitCounter--; this.token(";"); if (node.test) { diff --git a/packages/babel-generator/src/index.js b/packages/babel-generator/src/index.js index aa3a6150cb..f660233c6e 100644 --- a/packages/babel-generator/src/index.js +++ b/packages/babel-generator/src/index.js @@ -13,22 +13,16 @@ class Generator extends Printer { constructor(ast, opts, code) { opts = opts || {}; - let comments = ast.comments || []; - let tokens = ast.tokens || []; + const tokens = ast.tokens || []; let format = Generator.normalizeOptions(code, opts, tokens); let map = opts.sourceMaps ? new SourceMap(opts, code) : null; super(format, map); - this.comments = comments; - this.tokens = tokens; - this.format = format; - this.opts = opts; this.ast = ast; - this._inForStatementInitCounter = 0; - this.whitespace = new Whitespace(tokens); + this._whitespace = tokens.length > 0 ? new Whitespace(tokens) : null; } format: { @@ -48,12 +42,7 @@ class Generator extends Printer { } }; - auxiliaryCommentBefore: string; - auxiliaryCommentAfter: string; - whitespace: Whitespace; - comments: Array; - tokens: Array; - opts: Object; + _whitespace: Whitespace; ast: Object; /** diff --git a/packages/babel-generator/src/position.js b/packages/babel-generator/src/position.js deleted file mode 100644 index 09283868d5..0000000000 --- a/packages/babel-generator/src/position.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Track current position in code generation. - */ - -export default class Position { - column: number; - line: number; - - constructor() { - this.line = 1; - this.column = 0; - } - - /** - * Push a string to the current position, mantaining the current line and column. - */ - - push(str: string): void { - for (let i = 0; i < str.length; i++) { - if (str[i] === "\n") { - this.line++; - this.column = 0; - } else { - this.column++; - } - } - } -} diff --git a/packages/babel-generator/src/printer.js b/packages/babel-generator/src/printer.js index f8ec23ebe7..6ecd82530d 100644 --- a/packages/babel-generator/src/printer.js +++ b/packages/babel-generator/src/printer.js @@ -7,36 +7,27 @@ import * as t from "babel-types"; export default class Printer { constructor(format, map) { - this._format = format || {}; + this.format = format || {}; this._buf = new Buffer(map); this.insideAux = false; - this.printAuxAfterOnNextUserNode = false; + this._printAuxAfterOnNextUserNode = false; this._printStack = []; - this.printedCommentStarts = {}; - this.parenPushNewlineState = null; + this._printedCommentStarts = {}; + this._parenPushNewlineState = null; this._indent = 0; + this.inForStatementInitCounter = 0; } - printedCommentStarts: Object; - parenPushNewlineState: ?Object; - - /** - * Get the current indent. - */ - - _getIndent(): string { - if (this._format.compact || this._format.concise) { - return ""; - } else { - return repeat(this._format.indent.style, this._indent); - } - } + _printedCommentStarts: Object; + _parenPushNewlineState: ?Object; /** * Increment indent size. */ indent(): void { + if (this.format.compact || this.format.concise) return; + this._indent++; } @@ -45,6 +36,8 @@ export default class Printer { */ dedent(): void { + if (this.format.compact || this.format.concise) return; + this._indent--; } @@ -52,8 +45,8 @@ export default class Printer { * Add a semicolon to the buffer. */ - semicolon(): void { - this._append(";", true /* queue */); + semicolon(force: boolean = false): void { + this._append(";", !force /* queue */); } /** @@ -63,7 +56,7 @@ export default class Printer { rightBrace(): void { if (!this.endsWith("\n")) this.newline(); - if (this._format.minified && !this._lastPrintedIsEmptyStatement) { + if (this.format.minified) { this._buf.removeLastSemicolon(); } this.token("}"); @@ -83,7 +76,7 @@ export default class Printer { */ space(force: boolean = false): void { - if (this._format.compact) return; + if (this.format.compact) return; if ((this._buf.hasContent() && !this.endsWith(" ") && !this.endsWith("\n")) || force) { this._space(); @@ -126,9 +119,9 @@ export default class Printer { */ newline(i?: number): void { - if (this._format.retainLines || this._format.compact) return; + if (this.format.retainLines || this.format.compact) return; - if (this._format.concise) { + if (this.format.concise) { this.space(); return; } @@ -188,16 +181,16 @@ export default class Printer { _maybeIndent(str: string): void { // we've got a newline before us so prepend on the indentation - if (!this._format.compact && this._indent && this.endsWith("\n") && str[0] !== "\n") { + if (this._indent && this.endsWith("\n") && str[0] !== "\n") { this._buf.queue(this._getIndent()); } } _maybeAddParen(str: string): void { // see startTerminatorless() instance method - let parenPushNewlineState = this.parenPushNewlineState; + let parenPushNewlineState = this._parenPushNewlineState; if (!parenPushNewlineState) return; - this.parenPushNewlineState = null; + this._parenPushNewlineState = null; let i; for (i = 0; i < str.length && str[i] === " "; i++) continue; @@ -213,7 +206,7 @@ export default class Printer { } _catchUp(prop: string, loc: Object) { - if (!this._format.retainLines) return; + if (!this.format.retainLines) return; // catch up to this nodes newline if we're behind const pos = loc ? loc[prop] : null; @@ -224,6 +217,14 @@ export default class Printer { } } + /** + * Get the current indent. + */ + + _getIndent(): string { + return repeat(this.format.indent.style, this._indent); + } + /** * Set some state that will be modified if a newline has been inserted before any * non-space characters. @@ -241,7 +242,7 @@ export default class Printer { */ startTerminatorless(): Object { - return this.parenPushNewlineState = { + return this._parenPushNewlineState = { printed: false }; } @@ -261,18 +262,9 @@ export default class Printer { print(node, parent, opts = {}) { if (!node) return; - this._lastPrintedIsEmptyStatement = false; - - if (parent && parent._compact) { - node._compact = true; - } - - let oldInAux = this.insideAux; - this.insideAux = !node.loc; - - let oldConcise = this._format.concise; + let oldConcise = this.format.concise; if (node._compact) { - this._format.concise = true; + this.format.concise = true; } let printMethod = this[node.type]; @@ -282,27 +274,27 @@ export default class Printer { this._printStack.push(node); - if (node.loc) this.printAuxAfterComment(); - this.printAuxBeforeComment(oldInAux); + let oldInAux = this.insideAux; + this.insideAux = !node.loc; + if (!this.insideAux) this.printAuxAfterComment(); + else if (!oldInAux) this._printAuxBeforeComment(); let needsParens = n.needsParens(node, parent, this._printStack); if (needsParens) this.token("("); - this.printLeadingComments(node, parent); + this._printLeadingComments(node, parent); this._printNewline(true, node, parent, opts); - if (opts.before) opts.before(); - let loc = (t.isProgram(node) || t.isFile(node)) ? null : node.loc; this.withSource("start", loc, () => { this[node.type](node, parent); }); // Check again if any of our children may have left an aux comment on the stack - if (node.loc) this.printAuxAfterComment(); + if (!this.insideAux) this.printAuxAfterComment(); - this.printTrailingComments(node, parent); + this._printTrailingComments(node, parent); if (needsParens) this.token(")"); @@ -310,17 +302,19 @@ export default class Printer { this._printStack.pop(); if (opts.after) opts.after(); - this._format.concise = oldConcise; + this.format.concise = oldConcise; this.insideAux = oldInAux; this._printNewline(false, node, parent, opts); } - printAuxBeforeComment(wasInAux) { - let comment = this._format.auxiliaryCommentBefore; - if (!wasInAux && this.insideAux && !this.printAuxAfterOnNextUserNode) { - this.printAuxAfterOnNextUserNode = true; - if (comment) this.printComment({ + _printAuxBeforeComment() { + if (this._printAuxAfterOnNextUserNode) return; + this._printAuxAfterOnNextUserNode = true; + + const comment = this.format.auxiliaryCommentBefore; + if (comment) { + this._printComment({ type: "CommentBlock", value: comment }); @@ -328,10 +322,12 @@ export default class Printer { } printAuxAfterComment() { - if (this.printAuxAfterOnNextUserNode) { - this.printAuxAfterOnNextUserNode = false; - let comment = this._format.auxiliaryCommentAfter; - if (comment) this.printComment({ + if (!this._printAuxAfterOnNextUserNode) return; + this._printAuxAfterOnNextUserNode = false; + + const comment = this.format.auxiliaryCommentAfter; + if (comment) { + this._printComment({ type: "CommentBlock", value: comment }); @@ -339,7 +335,7 @@ export default class Printer { } getPossibleRaw(node) { - if (this._format.minified) return; + if (this.format.minified) return; let extra = node.extra; if (extra && extra.raw != null && extra.rawValue != null && node.value === extra.rawValue) { @@ -398,28 +394,18 @@ export default class Printer { this.print(node, parent); } - generateComment(comment) { - let val = comment.value; - if (comment.type === "CommentLine") { - val = `//${val}`; - } else { - val = `/*${val}*/`; - } - return val; + _printTrailingComments(node, parent) { + this._printComments(this._getComments(false, node, parent)); } - printTrailingComments(node, parent) { - this.printComments(this.getComments(false, node, parent)); - } - - printLeadingComments(node, parent) { - this.printComments(this.getComments(true, node, parent)); + _printLeadingComments(node, parent) { + this._printComments(this._getComments(true, node, parent)); } printInnerComments(node, indent = true) { if (!node.innerComments) return; if (indent) this.indent(); - this.printComments(node.innerComments); + this._printComments(node.innerComments); if (indent) this.dedent(); } @@ -438,7 +424,7 @@ export default class Printer { _printNewline(leading, node, parent, opts) { // Fast path since 'this.newline' does nothing when not tracking lines. - if (this._format.retainLines || this._format.compact) return; + if (this.format.retainLines || this.format.compact) return; if (!opts.statement && !n.isUserWhitespacable(node, parent)) { return; @@ -446,19 +432,19 @@ export default class Printer { // Fast path for concise since 'this.newline' just inserts a space when // concise formatting is in use. - if (this._format.concise) { + if (this.format.concise) { this.space(); return; } let lines = 0; - if (node.start != null && !node._ignoreUserWhitespace && this.tokens.length) { + if (node.start != null && !node._ignoreUserWhitespace && this._whitespace) { // user node if (leading) { - lines = this.whitespace.getNewlinesBefore(node); + lines = this._whitespace.getNewlinesBefore(node); } else { - lines = this.whitespace.getNewlinesAfter(node); + lines = this._whitespace.getNewlinesAfter(node); } } else { // generated node @@ -476,47 +462,47 @@ export default class Printer { this.newline(lines); } - getComments(leading, node) { + _getComments(leading, 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)) || []; } - shouldPrintComment(comment) { - if (this._format.shouldPrintComment) { - return this._format.shouldPrintComment(comment.value); + _shouldPrintComment(comment) { + if (this.format.shouldPrintComment) { + return this.format.shouldPrintComment(comment.value); } else { - if (!this._format.minified && + if (!this.format.minified && (comment.value.indexOf("@license") >= 0 || comment.value.indexOf("@preserve") >= 0)) { return true; } else { - return this._format.comments; + return this.format.comments; } } } - printComment(comment) { - if (!this.shouldPrintComment(comment)) return; + _printComment(comment) { + if (!this._shouldPrintComment(comment)) return; if (comment.ignore) return; comment.ignore = true; if (comment.start != null) { - if (this.printedCommentStarts[comment.start]) return; - this.printedCommentStarts[comment.start] = true; + if (this._printedCommentStarts[comment.start]) return; + this._printedCommentStarts[comment.start] = true; } // Exclude comments from source mappings since they will only clutter things. this.withSource("start", comment.loc, () => { // whitespace before - this.newline(this.whitespace.getNewlinesBefore(comment)); + this.newline(this._whitespace ? this._whitespace.getNewlinesBefore(comment) : 0); if (!this.endsWith("[") && !this.endsWith("{")) this.space(); - let val = this.generateComment(comment); + let val = comment.type === "CommentLine" ? `//${comment.value}` : `/*${comment.value}*/`; // - if (comment.type === "CommentBlock" && this._format.indent.adjustMultilineComment) { + if (comment.type === "CommentBlock" && this.format.indent.adjustMultilineComment) { let offset = comment.loc && comment.loc.start.column; if (offset) { let newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g"); @@ -529,7 +515,7 @@ export default class Printer { // force a newline for line comments when retainLines is set in case the next printed node // doesn't catch up - if ((this._format.compact || this._format.concise || this._format.retainLines) && + if ((this.format.compact || this.format.concise || this.format.retainLines) && comment.type === "CommentLine") { val += "\n"; } @@ -538,15 +524,16 @@ export default class Printer { this.token(val); // whitespace after - this.newline(this.whitespace.getNewlinesAfter(comment)); + this.newline((this._whitespace ? this._whitespace.getNewlinesAfter(comment) : 0) || + (comment.type === "CommentLine" ? 1 : 0)); }); } - printComments(comments?: Array) { + _printComments(comments?: Array) { if (!comments || !comments.length) return; for (let comment of comments) { - this.printComment(comment); + this._printComment(comment); } } } diff --git a/packages/babel-generator/src/source-map.js b/packages/babel-generator/src/source-map.js index 36bfe89097..4486bbf737 100644 --- a/packages/babel-generator/src/source-map.js +++ b/packages/babel-generator/src/source-map.js @@ -1,5 +1,4 @@ import sourceMap from "source-map"; -import type Position from "./position"; /** * Build a sourcemap. @@ -7,24 +6,18 @@ import type Position from "./position"; export default class SourceMap { constructor(opts, code) { - this.opts = opts; - this.last = {generated: {}, original: {}}; + this._opts = opts; + this._map = new sourceMap.SourceMapGenerator({ + file: opts.sourceMapTarget, + sourceRoot: opts.sourceRoot + }); - if (opts.sourceMaps) { - this.map = new sourceMap.SourceMapGenerator({ - file: opts.sourceMapTarget, - sourceRoot: opts.sourceRoot + if (typeof code === "string") { + this._map.setSourceContent(opts.sourceFileName, code); + } else if (typeof code === "object") { + Object.keys(code).forEach((sourceFileName) => { + this._map.setSourceContent(sourceFileName, code[sourceFileName]); }); - - if (typeof code === "string") { - this.map.setSourceContent(opts.sourceFileName, code); - } else if (typeof code === "object") { - Object.keys(code).forEach((sourceFileName) => { - this.map.setSourceContent(sourceFileName, code[sourceFileName]); - }); - } - } else { - this.map = null; } } @@ -33,12 +26,7 @@ export default class SourceMap { */ get() { - let map = this.map; - if (map) { - return map.toJSON(); - } else { - return map; - } + return this._map.toJSON(); } /** @@ -46,30 +34,27 @@ export default class SourceMap { * values to insert a mapping to nothing. */ - mark(position: Position, line: number, column: number, filename: ?string) { - let map = this.map; - if (!map) return; // no source map - + mark(generatedLine: number, generatedColumn: number, line: number, column: number, filename: ?string) { // Adding an empty mapping at the start of a generated line just clutters the map. - if (this._lastGenLine !== position.line && line === null) return; + if (this._lastGenLine !== generatedLine && line === null) return; // If this mapping points to the same source location as the last one, we can ignore it since // the previous one covers it. - if (this._lastGenLine === position.line && this._lastSourceLine === line && + if (this._lastGenLine === generatedLine && this._lastSourceLine === line && this._lastSourceColumn === column) { return; } - this._lastGenLine = position.line; + this._lastGenLine = generatedLine; this._lastSourceLine = line; this._lastSourceColumn = column; - map.addMapping({ + this._map.addMapping({ generated: { - line: position.line, - column: position.column + line: generatedLine, + column: generatedColumn, }, - source: line == null ? null : filename || this.opts.sourceFileName, + source: line == null ? null : filename || this._opts.sourceFileName, original: line == null ? null : { line: line, column: column, diff --git a/packages/babel-generator/src/whitespace.js b/packages/babel-generator/src/whitespace.js index 035f5bb272..2eb4f9b308 100644 --- a/packages/babel-generator/src/whitespace.js +++ b/packages/babel-generator/src/whitespace.js @@ -24,7 +24,7 @@ export default class Whitespace { endToken = tokens[index]; } - return this.getNewlinesBetween(startToken, endToken); + return this._getNewlinesBetween(startToken, endToken); } /** @@ -47,13 +47,7 @@ export default class Whitespace { if (endToken && endToken.type.label === "eof") { return 1; } else { - let lines = this.getNewlinesBetween(startToken, endToken); - if (node.type === "CommentLine" && !lines) { - // line comment - return 1; - } else { - return lines; - } + return this._getNewlinesBetween(startToken, endToken); } } @@ -61,7 +55,7 @@ export default class Whitespace { * Count all the newlines between two tokens. */ - getNewlinesBetween(startToken, endToken) { + _getNewlinesBetween(startToken, endToken) { if (!endToken || !endToken.loc) return 0; let start = startToken ? startToken.loc.end.line : 1;