| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * Copyright (C) 2015 Tobias Reiss <tobi+webkit@basecode.de> |
| * |
| * 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); |
| } |
| }; |