summaryrefslogtreecommitdiff
path: root/deps/npm/node_modules/readable-stream/node_modules/unreachable-branch-transform/node_modules/recast/lib/lines.js
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/node_modules/readable-stream/node_modules/unreachable-branch-transform/node_modules/recast/lib/lines.js')
-rw-r--r--deps/npm/node_modules/readable-stream/node_modules/unreachable-branch-transform/node_modules/recast/lib/lines.js878
1 files changed, 878 insertions, 0 deletions
diff --git a/deps/npm/node_modules/readable-stream/node_modules/unreachable-branch-transform/node_modules/recast/lib/lines.js b/deps/npm/node_modules/readable-stream/node_modules/unreachable-branch-transform/node_modules/recast/lib/lines.js
new file mode 100644
index 0000000000..b83f8a9521
--- /dev/null
+++ b/deps/npm/node_modules/readable-stream/node_modules/unreachable-branch-transform/node_modules/recast/lib/lines.js
@@ -0,0 +1,878 @@
+var assert = require("assert");
+var sourceMap = require("source-map");
+var normalizeOptions = require("./options").normalize;
+var secretKey = require("private").makeUniqueKey();
+var types = require("./types");
+var isString = types.builtInTypes.string;
+var comparePos = require("./util").comparePos;
+var Mapping = require("./mapping");
+
+// Goals:
+// 1. Minimize new string creation.
+// 2. Keep (de)identation O(lines) time.
+// 3. Permit negative indentations.
+// 4. Enforce immutability.
+// 5. No newline characters.
+
+function getSecret(lines) {
+ return lines[secretKey];
+}
+
+function Lines(infos, sourceFileName) {
+ assert.ok(this instanceof Lines);
+ assert.ok(infos.length > 0);
+
+ if (sourceFileName) {
+ isString.assert(sourceFileName);
+ } else {
+ sourceFileName = null;
+ }
+
+ Object.defineProperty(this, secretKey, {
+ value: {
+ infos: infos,
+ mappings: [],
+ name: sourceFileName,
+ cachedSourceMap: null
+ }
+ });
+
+ if (sourceFileName) {
+ getSecret(this).mappings.push(new Mapping(this, {
+ start: this.firstPos(),
+ end: this.lastPos()
+ }));
+ }
+}
+
+// Exposed for instanceof checks. The fromString function should be used
+// to create new Lines objects.
+exports.Lines = Lines;
+var Lp = Lines.prototype;
+
+// These properties used to be assigned to each new object in the Lines
+// constructor, but we can more efficiently stuff them into the secret and
+// let these lazy accessors compute their values on-the-fly.
+Object.defineProperties(Lp, {
+ length: {
+ get: function() {
+ return getSecret(this).infos.length;
+ }
+ },
+
+ name: {
+ get: function() {
+ return getSecret(this).name;
+ }
+ }
+});
+
+function copyLineInfo(info) {
+ return {
+ line: info.line,
+ indent: info.indent,
+ locked: info.locked,
+ sliceStart: info.sliceStart,
+ sliceEnd: info.sliceEnd
+ };
+}
+
+var fromStringCache = {};
+var hasOwn = fromStringCache.hasOwnProperty;
+var maxCacheKeyLen = 10;
+
+function countSpaces(spaces, tabWidth) {
+ var count = 0;
+ var len = spaces.length;
+
+ for (var i = 0; i < len; ++i) {
+ switch (spaces.charCodeAt(i)) {
+ case 9: // '\t'
+ assert.strictEqual(typeof tabWidth, "number");
+ assert.ok(tabWidth > 0);
+
+ var next = Math.ceil(count / tabWidth) * tabWidth;
+ if (next === count) {
+ count += tabWidth;
+ } else {
+ count = next;
+ }
+
+ break;
+
+ case 11: // '\v'
+ case 12: // '\f'
+ case 13: // '\r'
+ case 0xfeff: // zero-width non-breaking space
+ // These characters contribute nothing to indentation.
+ break;
+
+ case 32: // ' '
+ default: // Treat all other whitespace like ' '.
+ count += 1;
+ break;
+ }
+ }
+
+ return count;
+}
+exports.countSpaces = countSpaces;
+
+var leadingSpaceExp = /^\s*/;
+
+// As specified here: http://www.ecma-international.org/ecma-262/6.0/#sec-line-terminators
+var lineTerminatorSeqExp =
+ /\u000D\u000A|\u000D(?!\u000A)|\u000A|\u2028|\u2029/;
+
+/**
+ * @param {Object} options - Options object that configures printing.
+ */
+function fromString(string, options) {
+ if (string instanceof Lines)
+ return string;
+
+ string += "";
+
+ var tabWidth = options && options.tabWidth;
+ var tabless = string.indexOf("\t") < 0;
+ var locked = !! (options && options.locked);
+ var cacheable = !options && tabless && (string.length <= maxCacheKeyLen);
+
+ assert.ok(tabWidth || tabless, "No tab width specified but encountered tabs in string\n" + string);
+
+ if (cacheable && hasOwn.call(fromStringCache, string))
+ return fromStringCache[string];
+
+ var lines = new Lines(string.split(lineTerminatorSeqExp).map(function(line) {
+ var spaces = leadingSpaceExp.exec(line)[0];
+ return {
+ line: line,
+ indent: countSpaces(spaces, tabWidth),
+ // Boolean indicating whether this line can be reindented.
+ locked: locked,
+ sliceStart: spaces.length,
+ sliceEnd: line.length
+ };
+ }), normalizeOptions(options).sourceFileName);
+
+ if (cacheable)
+ fromStringCache[string] = lines;
+
+ return lines;
+}
+exports.fromString = fromString;
+
+function isOnlyWhitespace(string) {
+ return !/\S/.test(string);
+}
+
+Lp.toString = function(options) {
+ return this.sliceString(this.firstPos(), this.lastPos(), options);
+};
+
+Lp.getSourceMap = function(sourceMapName, sourceRoot) {
+ if (!sourceMapName) {
+ // Although we could make up a name or generate an anonymous
+ // source map, instead we assume that any consumer who does not
+ // provide a name does not actually want a source map.
+ return null;
+ }
+
+ var targetLines = this;
+
+ function updateJSON(json) {
+ json = json || {};
+
+ isString.assert(sourceMapName);
+ json.file = sourceMapName;
+
+ if (sourceRoot) {
+ isString.assert(sourceRoot);
+ json.sourceRoot = sourceRoot;
+ }
+
+ return json;
+ }
+
+ var secret = getSecret(targetLines);
+ if (secret.cachedSourceMap) {
+ // Since Lines objects are immutable, we can reuse any source map
+ // that was previously generated. Nevertheless, we return a new
+ // JSON object here to protect the cached source map from outside
+ // modification.
+ return updateJSON(secret.cachedSourceMap.toJSON());
+ }
+
+ var smg = new sourceMap.SourceMapGenerator(updateJSON());
+ var sourcesToContents = {};
+
+ secret.mappings.forEach(function(mapping) {
+ var sourceCursor = mapping.sourceLines.skipSpaces(
+ mapping.sourceLoc.start
+ ) || mapping.sourceLines.lastPos();
+
+ var targetCursor = targetLines.skipSpaces(
+ mapping.targetLoc.start
+ ) || targetLines.lastPos();
+
+ while (comparePos(sourceCursor, mapping.sourceLoc.end) < 0 &&
+ comparePos(targetCursor, mapping.targetLoc.end) < 0) {
+
+ var sourceChar = mapping.sourceLines.charAt(sourceCursor);
+ var targetChar = targetLines.charAt(targetCursor);
+ assert.strictEqual(sourceChar, targetChar);
+
+ var sourceName = mapping.sourceLines.name;
+
+ // Add mappings one character at a time for maximum resolution.
+ smg.addMapping({
+ source: sourceName,
+ original: { line: sourceCursor.line,
+ column: sourceCursor.column },
+ generated: { line: targetCursor.line,
+ column: targetCursor.column }
+ });
+
+ if (!hasOwn.call(sourcesToContents, sourceName)) {
+ var sourceContent = mapping.sourceLines.toString();
+ smg.setSourceContent(sourceName, sourceContent);
+ sourcesToContents[sourceName] = sourceContent;
+ }
+
+ targetLines.nextPos(targetCursor, true);
+ mapping.sourceLines.nextPos(sourceCursor, true);
+ }
+ });
+
+ secret.cachedSourceMap = smg;
+
+ return smg.toJSON();
+};
+
+Lp.bootstrapCharAt = function(pos) {
+ assert.strictEqual(typeof pos, "object");
+ assert.strictEqual(typeof pos.line, "number");
+ assert.strictEqual(typeof pos.column, "number");
+
+ var line = pos.line,
+ column = pos.column,
+ strings = this.toString().split(lineTerminatorSeqExp),
+ string = strings[line - 1];
+
+ if (typeof string === "undefined")
+ return "";
+
+ if (column === string.length &&
+ line < strings.length)
+ return "\n";
+
+ if (column >= string.length)
+ return "";
+
+ return string.charAt(column);
+};
+
+Lp.charAt = function(pos) {
+ assert.strictEqual(typeof pos, "object");
+ assert.strictEqual(typeof pos.line, "number");
+ assert.strictEqual(typeof pos.column, "number");
+
+ var line = pos.line,
+ column = pos.column,
+ secret = getSecret(this),
+ infos = secret.infos,
+ info = infos[line - 1],
+ c = column;
+
+ if (typeof info === "undefined" || c < 0)
+ return "";
+
+ var indent = this.getIndentAt(line);
+ if (c < indent)
+ return " ";
+
+ c += info.sliceStart - indent;
+
+ if (c === info.sliceEnd &&
+ line < this.length)
+ return "\n";
+
+ if (c >= info.sliceEnd)
+ return "";
+
+ return info.line.charAt(c);
+};
+
+Lp.stripMargin = function(width, skipFirstLine) {
+ if (width === 0)
+ return this;
+
+ assert.ok(width > 0, "negative margin: " + width);
+
+ if (skipFirstLine && this.length === 1)
+ return this;
+
+ var secret = getSecret(this);
+
+ var lines = new Lines(secret.infos.map(function(info, i) {
+ if (info.line && (i > 0 || !skipFirstLine)) {
+ info = copyLineInfo(info);
+ info.indent = Math.max(0, info.indent - width);
+ }
+ return info;
+ }));
+
+ if (secret.mappings.length > 0) {
+ var newMappings = getSecret(lines).mappings;
+ assert.strictEqual(newMappings.length, 0);
+ secret.mappings.forEach(function(mapping) {
+ newMappings.push(mapping.indent(width, skipFirstLine, true));
+ });
+ }
+
+ return lines;
+};
+
+Lp.indent = function(by) {
+ if (by === 0)
+ return this;
+
+ var secret = getSecret(this);
+
+ var lines = new Lines(secret.infos.map(function(info) {
+ if (info.line && ! info.locked) {
+ info = copyLineInfo(info);
+ info.indent += by;
+ }
+ return info
+ }));
+
+ if (secret.mappings.length > 0) {
+ var newMappings = getSecret(lines).mappings;
+ assert.strictEqual(newMappings.length, 0);
+ secret.mappings.forEach(function(mapping) {
+ newMappings.push(mapping.indent(by));
+ });
+ }
+
+ return lines;
+};
+
+Lp.indentTail = function(by) {
+ if (by === 0)
+ return this;
+
+ if (this.length < 2)
+ return this;
+
+ var secret = getSecret(this);
+
+ var lines = new Lines(secret.infos.map(function(info, i) {
+ if (i > 0 && info.line && ! info.locked) {
+ info = copyLineInfo(info);
+ info.indent += by;
+ }
+
+ return info;
+ }));
+
+ if (secret.mappings.length > 0) {
+ var newMappings = getSecret(lines).mappings;
+ assert.strictEqual(newMappings.length, 0);
+ secret.mappings.forEach(function(mapping) {
+ newMappings.push(mapping.indent(by, true));
+ });
+ }
+
+ return lines;
+};
+
+Lp.lockIndentTail = function () {
+ if (this.length < 2) {
+ return this;
+ }
+
+ var infos = getSecret(this).infos;
+
+ return new Lines(infos.map(function (info, i) {
+ info = copyLineInfo(info);
+ info.locked = i > 0;
+ return info;
+ }));
+};
+
+Lp.getIndentAt = function(line) {
+ assert.ok(line >= 1, "no line " + line + " (line numbers start from 1)");
+ var secret = getSecret(this),
+ info = secret.infos[line - 1];
+ return Math.max(info.indent, 0);
+};
+
+Lp.guessTabWidth = function() {
+ var secret = getSecret(this);
+ if (hasOwn.call(secret, "cachedTabWidth")) {
+ return secret.cachedTabWidth;
+ }
+
+ var counts = []; // Sparse array.
+ var lastIndent = 0;
+
+ for (var line = 1, last = this.length; line <= last; ++line) {
+ var info = secret.infos[line - 1];
+ var sliced = info.line.slice(info.sliceStart, info.sliceEnd);
+
+ // Whitespace-only lines don't tell us much about the likely tab
+ // width of this code.
+ if (isOnlyWhitespace(sliced)) {
+ continue;
+ }
+
+ var diff = Math.abs(info.indent - lastIndent);
+ counts[diff] = ~~counts[diff] + 1;
+ lastIndent = info.indent;
+ }
+
+ var maxCount = -1;
+ var result = 2;
+
+ for (var tabWidth = 1;
+ tabWidth < counts.length;
+ tabWidth += 1) {
+ if (hasOwn.call(counts, tabWidth) &&
+ counts[tabWidth] > maxCount) {
+ maxCount = counts[tabWidth];
+ result = tabWidth;
+ }
+ }
+
+ return secret.cachedTabWidth = result;
+};
+
+Lp.isOnlyWhitespace = function() {
+ return isOnlyWhitespace(this.toString());
+};
+
+Lp.isPrecededOnlyByWhitespace = function(pos) {
+ var secret = getSecret(this);
+ var info = secret.infos[pos.line - 1];
+ var indent = Math.max(info.indent, 0);
+
+ var diff = pos.column - indent;
+ if (diff <= 0) {
+ // If pos.column does not exceed the indentation amount, then
+ // there must be only whitespace before it.
+ return true;
+ }
+
+ var start = info.sliceStart;
+ var end = Math.min(start + diff, info.sliceEnd);
+ var prefix = info.line.slice(start, end);
+
+ return isOnlyWhitespace(prefix);
+};
+
+Lp.getLineLength = function(line) {
+ var secret = getSecret(this),
+ info = secret.infos[line - 1];
+ return this.getIndentAt(line) + info.sliceEnd - info.sliceStart;
+};
+
+Lp.nextPos = function(pos, skipSpaces) {
+ var l = Math.max(pos.line, 0),
+ c = Math.max(pos.column, 0);
+
+ if (c < this.getLineLength(l)) {
+ pos.column += 1;
+
+ return skipSpaces
+ ? !!this.skipSpaces(pos, false, true)
+ : true;
+ }
+
+ if (l < this.length) {
+ pos.line += 1;
+ pos.column = 0;
+
+ return skipSpaces
+ ? !!this.skipSpaces(pos, false, true)
+ : true;
+ }
+
+ return false;
+};
+
+Lp.prevPos = function(pos, skipSpaces) {
+ var l = pos.line,
+ c = pos.column;
+
+ if (c < 1) {
+ l -= 1;
+
+ if (l < 1)
+ return false;
+
+ c = this.getLineLength(l);
+
+ } else {
+ c = Math.min(c - 1, this.getLineLength(l));
+ }
+
+ pos.line = l;
+ pos.column = c;
+
+ return skipSpaces
+ ? !!this.skipSpaces(pos, true, true)
+ : true;
+};
+
+Lp.firstPos = function() {
+ // Trivial, but provided for completeness.
+ return { line: 1, column: 0 };
+};
+
+Lp.lastPos = function() {
+ return {
+ line: this.length,
+ column: this.getLineLength(this.length)
+ };
+};
+
+Lp.skipSpaces = function(pos, backward, modifyInPlace) {
+ if (pos) {
+ pos = modifyInPlace ? pos : {
+ line: pos.line,
+ column: pos.column
+ };
+ } else if (backward) {
+ pos = this.lastPos();
+ } else {
+ pos = this.firstPos();
+ }
+
+ if (backward) {
+ while (this.prevPos(pos)) {
+ if (!isOnlyWhitespace(this.charAt(pos)) &&
+ this.nextPos(pos)) {
+ return pos;
+ }
+ }
+
+ return null;
+
+ } else {
+ while (isOnlyWhitespace(this.charAt(pos))) {
+ if (!this.nextPos(pos)) {
+ return null;
+ }
+ }
+
+ return pos;
+ }
+};
+
+Lp.trimLeft = function() {
+ var pos = this.skipSpaces(this.firstPos(), false, true);
+ return pos ? this.slice(pos) : emptyLines;
+};
+
+Lp.trimRight = function() {
+ var pos = this.skipSpaces(this.lastPos(), true, true);
+ return pos ? this.slice(this.firstPos(), pos) : emptyLines;
+};
+
+Lp.trim = function() {
+ var start = this.skipSpaces(this.firstPos(), false, true);
+ if (start === null)
+ return emptyLines;
+
+ var end = this.skipSpaces(this.lastPos(), true, true);
+ assert.notStrictEqual(end, null);
+
+ return this.slice(start, end);
+};
+
+Lp.eachPos = function(callback, startPos, skipSpaces) {
+ var pos = this.firstPos();
+
+ if (startPos) {
+ pos.line = startPos.line,
+ pos.column = startPos.column
+ }
+
+ if (skipSpaces && !this.skipSpaces(pos, false, true)) {
+ return; // Encountered nothing but spaces.
+ }
+
+ do callback.call(this, pos);
+ while (this.nextPos(pos, skipSpaces));
+};
+
+Lp.bootstrapSlice = function(start, end) {
+ var strings = this.toString().split(
+ lineTerminatorSeqExp
+ ).slice(
+ start.line - 1,
+ end.line
+ );
+
+ strings.push(strings.pop().slice(0, end.column));
+ strings[0] = strings[0].slice(start.column);
+
+ return fromString(strings.join("\n"));
+};
+
+Lp.slice = function(start, end) {
+ if (!end) {
+ if (!start) {
+ // The client seems to want a copy of this Lines object, but
+ // Lines objects are immutable, so it's perfectly adequate to
+ // return the same object.
+ return this;
+ }
+
+ // Slice to the end if no end position was provided.
+ end = this.lastPos();
+ }
+
+ var secret = getSecret(this);
+ var sliced = secret.infos.slice(start.line - 1, end.line);
+
+ if (start.line === end.line) {
+ sliced[0] = sliceInfo(sliced[0], start.column, end.column);
+ } else {
+ assert.ok(start.line < end.line);
+ sliced[0] = sliceInfo(sliced[0], start.column);
+ sliced.push(sliceInfo(sliced.pop(), 0, end.column));
+ }
+
+ var lines = new Lines(sliced);
+
+ if (secret.mappings.length > 0) {
+ var newMappings = getSecret(lines).mappings;
+ assert.strictEqual(newMappings.length, 0);
+ secret.mappings.forEach(function(mapping) {
+ var sliced = mapping.slice(this, start, end);
+ if (sliced) {
+ newMappings.push(sliced);
+ }
+ }, this);
+ }
+
+ return lines;
+};
+
+function sliceInfo(info, startCol, endCol) {
+ var sliceStart = info.sliceStart;
+ var sliceEnd = info.sliceEnd;
+ var indent = Math.max(info.indent, 0);
+ var lineLength = indent + sliceEnd - sliceStart;
+
+ if (typeof endCol === "undefined") {
+ endCol = lineLength;
+ }
+
+ startCol = Math.max(startCol, 0);
+ endCol = Math.min(endCol, lineLength);
+ endCol = Math.max(endCol, startCol);
+
+ if (endCol < indent) {
+ indent = endCol;
+ sliceEnd = sliceStart;
+ } else {
+ sliceEnd -= lineLength - endCol;
+ }
+
+ lineLength = endCol;
+ lineLength -= startCol;
+
+ if (startCol < indent) {
+ indent -= startCol;
+ } else {
+ startCol -= indent;
+ indent = 0;
+ sliceStart += startCol;
+ }
+
+ assert.ok(indent >= 0);
+ assert.ok(sliceStart <= sliceEnd);
+ assert.strictEqual(lineLength, indent + sliceEnd - sliceStart);
+
+ if (info.indent === indent &&
+ info.sliceStart === sliceStart &&
+ info.sliceEnd === sliceEnd) {
+ return info;
+ }
+
+ return {
+ line: info.line,
+ indent: indent,
+ // A destructive slice always unlocks indentation.
+ locked: false,
+ sliceStart: sliceStart,
+ sliceEnd: sliceEnd
+ };
+}
+
+Lp.bootstrapSliceString = function(start, end, options) {
+ return this.slice(start, end).toString(options);
+};
+
+Lp.sliceString = function(start, end, options) {
+ if (!end) {
+ if (!start) {
+ // The client seems to want a copy of this Lines object, but
+ // Lines objects are immutable, so it's perfectly adequate to
+ // return the same object.
+ return this;
+ }
+
+ // Slice to the end if no end position was provided.
+ end = this.lastPos();
+ }
+
+ options = normalizeOptions(options);
+
+ var infos = getSecret(this).infos;
+ var parts = [];
+ var tabWidth = options.tabWidth;
+
+ for (var line = start.line; line <= end.line; ++line) {
+ var info = infos[line - 1];
+
+ if (line === start.line) {
+ if (line === end.line) {
+ info = sliceInfo(info, start.column, end.column);
+ } else {
+ info = sliceInfo(info, start.column);
+ }
+ } else if (line === end.line) {
+ info = sliceInfo(info, 0, end.column);
+ }
+
+ var indent = Math.max(info.indent, 0);
+
+ var before = info.line.slice(0, info.sliceStart);
+ if (options.reuseWhitespace &&
+ isOnlyWhitespace(before) &&
+ countSpaces(before, options.tabWidth) === indent) {
+ // Reuse original spaces if the indentation is correct.
+ parts.push(info.line.slice(0, info.sliceEnd));
+ continue;
+ }
+
+ var tabs = 0;
+ var spaces = indent;
+
+ if (options.useTabs) {
+ tabs = Math.floor(indent / tabWidth);
+ spaces -= tabs * tabWidth;
+ }
+
+ var result = "";
+
+ if (tabs > 0) {
+ result += new Array(tabs + 1).join("\t");
+ }
+
+ if (spaces > 0) {
+ result += new Array(spaces + 1).join(" ");
+ }
+
+ result += info.line.slice(info.sliceStart, info.sliceEnd);
+
+ parts.push(result);
+ }
+
+ return parts.join(options.lineTerminator);
+};
+
+Lp.isEmpty = function() {
+ return this.length < 2 && this.getLineLength(1) < 1;
+};
+
+Lp.join = function(elements) {
+ var separator = this;
+ var separatorSecret = getSecret(separator);
+ var infos = [];
+ var mappings = [];
+ var prevInfo;
+
+ function appendSecret(secret) {
+ if (secret === null)
+ return;
+
+ if (prevInfo) {
+ var info = secret.infos[0];
+ var indent = new Array(info.indent + 1).join(" ");
+ var prevLine = infos.length;
+ var prevColumn = Math.max(prevInfo.indent, 0) +
+ prevInfo.sliceEnd - prevInfo.sliceStart;
+
+ prevInfo.line = prevInfo.line.slice(
+ 0, prevInfo.sliceEnd) + indent + info.line.slice(
+ info.sliceStart, info.sliceEnd);
+
+ // If any part of a line is indentation-locked, the whole line
+ // will be indentation-locked.
+ prevInfo.locked = prevInfo.locked || info.locked;
+
+ prevInfo.sliceEnd = prevInfo.line.length;
+
+ if (secret.mappings.length > 0) {
+ secret.mappings.forEach(function(mapping) {
+ mappings.push(mapping.add(prevLine, prevColumn));
+ });
+ }
+
+ } else if (secret.mappings.length > 0) {
+ mappings.push.apply(mappings, secret.mappings);
+ }
+
+ secret.infos.forEach(function(info, i) {
+ if (!prevInfo || i > 0) {
+ prevInfo = copyLineInfo(info);
+ infos.push(prevInfo);
+ }
+ });
+ }
+
+ function appendWithSeparator(secret, i) {
+ if (i > 0)
+ appendSecret(separatorSecret);
+ appendSecret(secret);
+ }
+
+ elements.map(function(elem) {
+ var lines = fromString(elem);
+ if (lines.isEmpty())
+ return null;
+ return getSecret(lines);
+ }).forEach(separator.isEmpty()
+ ? appendSecret
+ : appendWithSeparator);
+
+ if (infos.length < 1)
+ return emptyLines;
+
+ var lines = new Lines(infos);
+
+ getSecret(lines).mappings = mappings;
+
+ return lines;
+};
+
+exports.concat = function(elements) {
+ return emptyLines.join(elements);
+};
+
+Lp.concat = function(other) {
+ var args = arguments,
+ list = [this];
+ list.push.apply(list, args);
+ assert.strictEqual(list.length, args.length + 1);
+ return emptyLines.join(list);
+};
+
+// The emptyLines object needs to be created all the way down here so that
+// Lines.prototype will be fully populated.
+var emptyLines = fromString("");