343 lines
9.8 KiB
JavaScript
343 lines
9.8 KiB
JavaScript
/*
|
|
* 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);
|
|
}
|
|
};
|