/* * Copyright (C) 2013 Apple Inc. All rights reserved. * Copyright (C) 2015 Tobias Reiss * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ FormatterContentBuilder = class FormatterContentBuilder { constructor(indentString) { this._originalContent = null; this._formattedContent = []; this._formattedContentLength = 0; this._startOfLine = true; this._currentLine = null; this.lastTokenWasNewline = false; this.lastTokenWasWhitespace = false; this.lastNewlineAppendWasMultiple = false; this._indent = 0; this._indentString = indentString; this._indentCache = ["", this._indentString]; this._mapping = {original: [0], formatted: [0]}; this._originalLineEndings = []; this._formattedLineEndings = []; this._originalOffset = 0; this._formattedOffset = 0; this._lastOriginalPosition = 0; this._lastFormattedPosition = 0; } // Public get indentString() { return this._indentString; } get originalContent() { return this._originalContent; } get formattedContent() { let formatted = this._formattedContent.join(""); console.assert(formatted.length === this._formattedContentLength); return formatted; } get sourceMapData() { return { mapping: this._mapping, originalLineEndings: this._originalLineEndings, formattedLineEndings: this._formattedLineEndings, }; } get lastToken() { return this._formattedContent.lastValue; } get currentLine() { if (!this._currentLine) this._currentLine = this._formattedContent.slice(this._formattedContent.lastIndexOf("\n") + 1).join(""); return this._currentLine; } get indentLevel() { return this._indent; } get indented() { return this._indent > 0; } get originalOffset() { return this._originalOffset; } set originalOffset(offset) { this._originalOffset = offset; } setOriginalContent(originalContent) { console.assert(!this._originalContent); this._originalContent = originalContent; } setOriginalLineEndings(originalLineEndings) { console.assert(!this._originalLineEndings.length); this._originalLineEndings = originalLineEndings; } appendNonToken(string) { if (!string) return; if (this._startOfLine) this._appendIndent(); console.assert(!string.includes("\n"), "Appended a string with newlines. This breaks the source map."); this._append(string); this._startOfLine = false; this.lastTokenWasNewline = false; this.lastTokenWasWhitespace = false; } appendToken(string, originalPosition) { if (this._startOfLine) this._appendIndent(); this._addMappingIfNeeded(originalPosition); console.assert(!string.includes("\n"), "Appended a string with newlines. This breaks the source map."); this._append(string); this._startOfLine = false; this.lastTokenWasNewline = false; this.lastTokenWasWhitespace = false; } appendStringWithPossibleNewlines(string, originalPosition) { let currentPosition = originalPosition; let lines = string.split("\n"); for (let i = 0; i < lines.length; ++i) { let line = lines[i]; if (line) { this.appendToken(line, currentPosition); currentPosition += line.length; } if (i < lines.length - 1) { this.appendNewline(true); currentPosition += 1; } } } appendMapping(originalPosition) { if (this._startOfLine) this._appendIndent(); this._addMappingIfNeeded(originalPosition); this._startOfLine = false; this.lastTokenWasNewline = false; this.lastTokenWasWhitespace = false; } appendSpace() { if (!this._startOfLine) { this._append(" "); this.lastTokenWasNewline = false; this.lastTokenWasWhitespace = true; } } appendNewline(force) { if ((!this.lastTokenWasNewline && !this._startOfLine) || force) { if (this.lastTokenWasWhitespace) this._popFormattedContent(); this._append("\n"); this._addFormattedLineEnding(); this._startOfLine = true; this.lastTokenWasNewline = true; this.lastTokenWasWhitespace = false; this.lastNewlineAppendWasMultiple = false; } } appendMultipleNewlines(newlines) { console.assert(newlines > 0); let wasMultiple = newlines > 1; while (newlines-- > 0) this.appendNewline(true); if (wasMultiple) this.lastNewlineAppendWasMultiple = true; } removeLastNewline() { console.assert(this.lastTokenWasNewline); console.assert(this.lastToken === "\n"); if (this.lastTokenWasNewline) { this._popFormattedContent(); this._formattedLineEndings.pop(); this.lastTokenWasNewline = this.lastToken === "\n"; this.lastTokenWasWhitespace = this.lastToken === " "; this._startOfLine = this.lastTokenWasNewline; } } removeLastWhitespace() { console.assert(this.lastTokenWasWhitespace); console.assert(this.lastToken === " "); if (this.lastTokenWasWhitespace) { this._popFormattedContent(); // No need to worry about `_startOfLine` and `lastTokenWasNewline` // because `appendSpace` takes care of not adding whitespace // to the beginning of a line. this.lastTokenWasNewline = this.lastToken === "\n"; this.lastTokenWasWhitespace = this.lastToken === " "; } } indent() { ++this._indent; } dedent() { --this._indent; console.assert(this._indent >= 0); if (this._indent < 0) this._indent = 0; } indentToLevel(level) { if (this._indent === level) return; while (this._indent < level) this.indent(); while (this._indent > level) this.dedent(); } addOriginalLineEnding(originalPosition) { this._originalLineEndings.push(originalPosition); } finish() { while (this.lastTokenWasNewline) this.removeLastNewline(); this.appendNewline(); } // Private _popFormattedContent() { let removed = this._formattedContent.pop(); this._formattedContentLength -= removed.length; this._currentLine = null; } _append(str) { console.assert(str, "Should not append an empty string"); this._formattedContent.push(str); this._formattedContentLength += str.length; this._currentLine = null; } _appendIndent() { // Indent is already in the cache. if (this._indent < this._indentCache.length) { let indent = this._indentCache[this._indent]; if (indent) this._append(indent); return; } // Indent was not in the cache, fill up the cache up with what was needed. let maxCacheIndent = 20; let max = Math.min(this._indent, maxCacheIndent); for (let i = this._indentCache.length; i <= max; ++i) this._indentCache[i] = this._indentCache[i - 1] + this._indentString; // Append indents as needed. let indent = this._indent; do { if (indent >= maxCacheIndent) this._append(this._indentCache[maxCacheIndent]); else this._append(this._indentCache[indent]); indent -= maxCacheIndent; } while (indent > 0); } _addMappingIfNeeded(originalPosition) { if (originalPosition - this._lastOriginalPosition === this._formattedContentLength - this._lastFormattedPosition) return; this._mapping.original.push(this._originalOffset + originalPosition); this._mapping.formatted.push(this._formattedOffset + this._formattedContentLength); this._lastOriginalPosition = originalPosition; this._lastFormattedPosition = this._formattedContentLength; } _addFormattedLineEnding() { console.assert(this._formattedContent.lastValue === "\n"); this._formattedLineEndings.push(this._formattedContentLength - 1); } };