react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / node_modules / commoner / node_modules / recast / lib / lines.js
81169 viewsvar assert = require("assert");1var sourceMap = require("source-map");2var normalizeOptions = require("./options").normalize;3var secretKey = require("private").makeUniqueKey();4var types = require("./types");5var isString = types.builtInTypes.string;6var comparePos = require("./util").comparePos;7var Mapping = require("./mapping");89// Goals:10// 1. Minimize new string creation.11// 2. Keep (de)identation O(lines) time.12// 3. Permit negative indentations.13// 4. Enforce immutability.14// 5. No newline characters.1516function getSecret(lines) {17return lines[secretKey];18}1920function Lines(infos, sourceFileName) {21assert.ok(this instanceof Lines);22assert.ok(infos.length > 0);2324if (sourceFileName) {25isString.assert(sourceFileName);26} else {27sourceFileName = null;28}2930Object.defineProperty(this, secretKey, {31value: {32infos: infos,33mappings: [],34name: sourceFileName,35cachedSourceMap: null36}37});3839if (sourceFileName) {40getSecret(this).mappings.push(new Mapping(this, {41start: this.firstPos(),42end: this.lastPos()43}));44}45}4647// Exposed for instanceof checks. The fromString function should be used48// to create new Lines objects.49exports.Lines = Lines;50var Lp = Lines.prototype;5152// These properties used to be assigned to each new object in the Lines53// constructor, but we can more efficiently stuff them into the secret and54// let these lazy accessors compute their values on-the-fly.55Object.defineProperties(Lp, {56length: {57get: function() {58return getSecret(this).infos.length;59}60},6162name: {63get: function() {64return getSecret(this).name;65}66}67});6869function copyLineInfo(info) {70return {71line: info.line,72indent: info.indent,73sliceStart: info.sliceStart,74sliceEnd: info.sliceEnd75};76}7778var fromStringCache = {};79var hasOwn = fromStringCache.hasOwnProperty;80var maxCacheKeyLen = 10;8182function countSpaces(spaces, tabWidth) {83var count = 0;84var len = spaces.length;8586for (var i = 0; i < len; ++i) {87switch (spaces.charCodeAt(i)) {88case 9: // '\t'89assert.strictEqual(typeof tabWidth, "number");90assert.ok(tabWidth > 0);9192var next = Math.ceil(count / tabWidth) * tabWidth;93if (next === count) {94count += tabWidth;95} else {96count = next;97}9899break;100101case 11: // '\v'102case 12: // '\f'103case 13: // '\r'104case 0xfeff: // zero-width non-breaking space105// These characters contribute nothing to indentation.106break;107108case 32: // ' '109default: // Treat all other whitespace like ' '.110count += 1;111break;112}113}114115return count;116}117exports.countSpaces = countSpaces;118119var leadingSpaceExp = /^\s*/;120121/**122* @param {Object} options - Options object that configures printing.123*/124function fromString(string, options) {125if (string instanceof Lines)126return string;127128string += "";129130var tabWidth = options && options.tabWidth;131var tabless = string.indexOf("\t") < 0;132var cacheable = !options && tabless && (string.length <= maxCacheKeyLen);133134assert.ok(tabWidth || tabless, "No tab width specified but encountered tabs in string\n" + string);135136if (cacheable && hasOwn.call(fromStringCache, string))137return fromStringCache[string];138139var lines = new Lines(string.split("\n").map(function(line) {140var spaces = leadingSpaceExp.exec(line)[0];141return {142line: line,143indent: countSpaces(spaces, tabWidth),144sliceStart: spaces.length,145sliceEnd: line.length146};147}), normalizeOptions(options).sourceFileName);148149if (cacheable)150fromStringCache[string] = lines;151152return lines;153}154exports.fromString = fromString;155156function isOnlyWhitespace(string) {157return !/\S/.test(string);158}159160Lp.toString = function(options) {161return this.sliceString(this.firstPos(), this.lastPos(), options);162};163164Lp.getSourceMap = function(sourceMapName, sourceRoot) {165if (!sourceMapName) {166// Although we could make up a name or generate an anonymous167// source map, instead we assume that any consumer who does not168// provide a name does not actually want a source map.169return null;170}171172var targetLines = this;173174function updateJSON(json) {175json = json || {};176177isString.assert(sourceMapName);178json.file = sourceMapName;179180if (sourceRoot) {181isString.assert(sourceRoot);182json.sourceRoot = sourceRoot;183}184185return json;186}187188var secret = getSecret(targetLines);189if (secret.cachedSourceMap) {190// Since Lines objects are immutable, we can reuse any source map191// that was previously generated. Nevertheless, we return a new192// JSON object here to protect the cached source map from outside193// modification.194return updateJSON(secret.cachedSourceMap.toJSON());195}196197var smg = new sourceMap.SourceMapGenerator(updateJSON());198var sourcesToContents = {};199200secret.mappings.forEach(function(mapping) {201var sourceCursor = mapping.sourceLines.skipSpaces(202mapping.sourceLoc.start203) || mapping.sourceLines.lastPos();204205var targetCursor = targetLines.skipSpaces(206mapping.targetLoc.start207) || targetLines.lastPos();208209while (comparePos(sourceCursor, mapping.sourceLoc.end) < 0 &&210comparePos(targetCursor, mapping.targetLoc.end) < 0) {211212var sourceChar = mapping.sourceLines.charAt(sourceCursor);213var targetChar = targetLines.charAt(targetCursor);214assert.strictEqual(sourceChar, targetChar);215216var sourceName = mapping.sourceLines.name;217218// Add mappings one character at a time for maximum resolution.219smg.addMapping({220source: sourceName,221original: { line: sourceCursor.line,222column: sourceCursor.column },223generated: { line: targetCursor.line,224column: targetCursor.column }225});226227if (!hasOwn.call(sourcesToContents, sourceName)) {228var sourceContent = mapping.sourceLines.toString();229smg.setSourceContent(sourceName, sourceContent);230sourcesToContents[sourceName] = sourceContent;231}232233targetLines.nextPos(targetCursor, true);234mapping.sourceLines.nextPos(sourceCursor, true);235}236});237238secret.cachedSourceMap = smg;239240return smg.toJSON();241};242243Lp.bootstrapCharAt = function(pos) {244assert.strictEqual(typeof pos, "object");245assert.strictEqual(typeof pos.line, "number");246assert.strictEqual(typeof pos.column, "number");247248var line = pos.line,249column = pos.column,250strings = this.toString().split("\n"),251string = strings[line - 1];252253if (typeof string === "undefined")254return "";255256if (column === string.length &&257line < strings.length)258return "\n";259260if (column >= string.length)261return "";262263return string.charAt(column);264};265266Lp.charAt = function(pos) {267assert.strictEqual(typeof pos, "object");268assert.strictEqual(typeof pos.line, "number");269assert.strictEqual(typeof pos.column, "number");270271var line = pos.line,272column = pos.column,273secret = getSecret(this),274infos = secret.infos,275info = infos[line - 1],276c = column;277278if (typeof info === "undefined" || c < 0)279return "";280281var indent = this.getIndentAt(line);282if (c < indent)283return " ";284285c += info.sliceStart - indent;286287if (c === info.sliceEnd &&288line < this.length)289return "\n";290291if (c >= info.sliceEnd)292return "";293294return info.line.charAt(c);295};296297Lp.stripMargin = function(width, skipFirstLine) {298if (width === 0)299return this;300301assert.ok(width > 0, "negative margin: " + width);302303if (skipFirstLine && this.length === 1)304return this;305306var secret = getSecret(this);307308var lines = new Lines(secret.infos.map(function(info, i) {309if (info.line && (i > 0 || !skipFirstLine)) {310info = copyLineInfo(info);311info.indent = Math.max(0, info.indent - width);312}313return info;314}));315316if (secret.mappings.length > 0) {317var newMappings = getSecret(lines).mappings;318assert.strictEqual(newMappings.length, 0);319secret.mappings.forEach(function(mapping) {320newMappings.push(mapping.indent(width, skipFirstLine, true));321});322}323324return lines;325};326327Lp.indent = function(by) {328if (by === 0)329return this;330331var secret = getSecret(this);332333var lines = new Lines(secret.infos.map(function(info) {334if (info.line) {335info = copyLineInfo(info);336info.indent += by;337}338return info339}));340341if (secret.mappings.length > 0) {342var newMappings = getSecret(lines).mappings;343assert.strictEqual(newMappings.length, 0);344secret.mappings.forEach(function(mapping) {345newMappings.push(mapping.indent(by));346});347}348349return lines;350};351352Lp.indentTail = function(by) {353if (by === 0)354return this;355356if (this.length < 2)357return this;358359var secret = getSecret(this);360361var lines = new Lines(secret.infos.map(function(info, i) {362if (i > 0 && info.line) {363info = copyLineInfo(info);364info.indent += by;365}366367return info;368}));369370if (secret.mappings.length > 0) {371var newMappings = getSecret(lines).mappings;372assert.strictEqual(newMappings.length, 0);373secret.mappings.forEach(function(mapping) {374newMappings.push(mapping.indent(by, true));375});376}377378return lines;379};380381Lp.getIndentAt = function(line) {382assert.ok(line >= 1, "no line " + line + " (line numbers start from 1)");383var secret = getSecret(this),384info = secret.infos[line - 1];385return Math.max(info.indent, 0);386};387388Lp.guessTabWidth = function() {389var secret = getSecret(this);390if (hasOwn.call(secret, "cachedTabWidth")) {391return secret.cachedTabWidth;392}393394var counts = []; // Sparse array.395var lastIndent = 0;396397for (var line = 1, last = this.length; line <= last; ++line) {398var info = secret.infos[line - 1];399var sliced = info.line.slice(info.sliceStart, info.sliceEnd);400401// Whitespace-only lines don't tell us much about the likely tab402// width of this code.403if (isOnlyWhitespace(sliced)) {404continue;405}406407var diff = Math.abs(info.indent - lastIndent);408counts[diff] = ~~counts[diff] + 1;409lastIndent = info.indent;410}411412var maxCount = -1;413var result = 2;414415for (var tabWidth = 1;416tabWidth < counts.length;417tabWidth += 1) {418if (hasOwn.call(counts, tabWidth) &&419counts[tabWidth] > maxCount) {420maxCount = counts[tabWidth];421result = tabWidth;422}423}424425return secret.cachedTabWidth = result;426};427428Lp.isOnlyWhitespace = function() {429return isOnlyWhitespace(this.toString());430};431432Lp.isPrecededOnlyByWhitespace = function(pos) {433var secret = getSecret(this);434var info = secret.infos[pos.line - 1];435var indent = Math.max(info.indent, 0);436437var diff = pos.column - indent;438if (diff <= 0) {439// If pos.column does not exceed the indentation amount, then440// there must be only whitespace before it.441return true;442}443444var start = info.sliceStart;445var end = Math.min(start + diff, info.sliceEnd);446var prefix = info.line.slice(start, end);447448return isOnlyWhitespace(prefix);449};450451Lp.getLineLength = function(line) {452var secret = getSecret(this),453info = secret.infos[line - 1];454return this.getIndentAt(line) + info.sliceEnd - info.sliceStart;455};456457Lp.nextPos = function(pos, skipSpaces) {458var l = Math.max(pos.line, 0),459c = Math.max(pos.column, 0);460461if (c < this.getLineLength(l)) {462pos.column += 1;463464return skipSpaces465? !!this.skipSpaces(pos, false, true)466: true;467}468469if (l < this.length) {470pos.line += 1;471pos.column = 0;472473return skipSpaces474? !!this.skipSpaces(pos, false, true)475: true;476}477478return false;479};480481Lp.prevPos = function(pos, skipSpaces) {482var l = pos.line,483c = pos.column;484485if (c < 1) {486l -= 1;487488if (l < 1)489return false;490491c = this.getLineLength(l);492493} else {494c = Math.min(c - 1, this.getLineLength(l));495}496497pos.line = l;498pos.column = c;499500return skipSpaces501? !!this.skipSpaces(pos, true, true)502: true;503};504505Lp.firstPos = function() {506// Trivial, but provided for completeness.507return { line: 1, column: 0 };508};509510Lp.lastPos = function() {511return {512line: this.length,513column: this.getLineLength(this.length)514};515};516517Lp.skipSpaces = function(pos, backward, modifyInPlace) {518if (pos) {519pos = modifyInPlace ? pos : {520line: pos.line,521column: pos.column522};523} else if (backward) {524pos = this.lastPos();525} else {526pos = this.firstPos();527}528529if (backward) {530while (this.prevPos(pos)) {531if (!isOnlyWhitespace(this.charAt(pos)) &&532this.nextPos(pos)) {533return pos;534}535}536537return null;538539} else {540while (isOnlyWhitespace(this.charAt(pos))) {541if (!this.nextPos(pos)) {542return null;543}544}545546return pos;547}548};549550Lp.trimLeft = function() {551var pos = this.skipSpaces(this.firstPos(), false, true);552return pos ? this.slice(pos) : emptyLines;553};554555Lp.trimRight = function() {556var pos = this.skipSpaces(this.lastPos(), true, true);557return pos ? this.slice(this.firstPos(), pos) : emptyLines;558};559560Lp.trim = function() {561var start = this.skipSpaces(this.firstPos(), false, true);562if (start === null)563return emptyLines;564565var end = this.skipSpaces(this.lastPos(), true, true);566assert.notStrictEqual(end, null);567568return this.slice(start, end);569};570571Lp.eachPos = function(callback, startPos, skipSpaces) {572var pos = this.firstPos();573574if (startPos) {575pos.line = startPos.line,576pos.column = startPos.column577}578579if (skipSpaces && !this.skipSpaces(pos, false, true)) {580return; // Encountered nothing but spaces.581}582583do callback.call(this, pos);584while (this.nextPos(pos, skipSpaces));585};586587Lp.bootstrapSlice = function(start, end) {588var strings = this.toString().split("\n").slice(589start.line - 1, end.line);590591strings.push(strings.pop().slice(0, end.column));592strings[0] = strings[0].slice(start.column);593594return fromString(strings.join("\n"));595};596597Lp.slice = function(start, end) {598if (!end) {599if (!start) {600// The client seems to want a copy of this Lines object, but601// Lines objects are immutable, so it's perfectly adequate to602// return the same object.603return this;604}605606// Slice to the end if no end position was provided.607end = this.lastPos();608}609610var secret = getSecret(this);611var sliced = secret.infos.slice(start.line - 1, end.line);612613if (start.line === end.line) {614sliced[0] = sliceInfo(sliced[0], start.column, end.column);615} else {616assert.ok(start.line < end.line);617sliced[0] = sliceInfo(sliced[0], start.column);618sliced.push(sliceInfo(sliced.pop(), 0, end.column));619}620621var lines = new Lines(sliced);622623if (secret.mappings.length > 0) {624var newMappings = getSecret(lines).mappings;625assert.strictEqual(newMappings.length, 0);626secret.mappings.forEach(function(mapping) {627var sliced = mapping.slice(this, start, end);628if (sliced) {629newMappings.push(sliced);630}631}, this);632}633634return lines;635};636637function sliceInfo(info, startCol, endCol) {638var sliceStart = info.sliceStart;639var sliceEnd = info.sliceEnd;640var indent = Math.max(info.indent, 0);641var lineLength = indent + sliceEnd - sliceStart;642643if (typeof endCol === "undefined") {644endCol = lineLength;645}646647startCol = Math.max(startCol, 0);648endCol = Math.min(endCol, lineLength);649endCol = Math.max(endCol, startCol);650651if (endCol < indent) {652indent = endCol;653sliceEnd = sliceStart;654} else {655sliceEnd -= lineLength - endCol;656}657658lineLength = endCol;659lineLength -= startCol;660661if (startCol < indent) {662indent -= startCol;663} else {664startCol -= indent;665indent = 0;666sliceStart += startCol;667}668669assert.ok(indent >= 0);670assert.ok(sliceStart <= sliceEnd);671assert.strictEqual(lineLength, indent + sliceEnd - sliceStart);672673if (info.indent === indent &&674info.sliceStart === sliceStart &&675info.sliceEnd === sliceEnd) {676return info;677}678679return {680line: info.line,681indent: indent,682sliceStart: sliceStart,683sliceEnd: sliceEnd684};685}686687Lp.bootstrapSliceString = function(start, end, options) {688return this.slice(start, end).toString(options);689};690691Lp.sliceString = function(start, end, options) {692if (!end) {693if (!start) {694// The client seems to want a copy of this Lines object, but695// Lines objects are immutable, so it's perfectly adequate to696// return the same object.697return this;698}699700// Slice to the end if no end position was provided.701end = this.lastPos();702}703704options = normalizeOptions(options);705706var infos = getSecret(this).infos;707var parts = [];708var tabWidth = options.tabWidth;709710for (var line = start.line; line <= end.line; ++line) {711var info = infos[line - 1];712713if (line === start.line) {714if (line === end.line) {715info = sliceInfo(info, start.column, end.column);716} else {717info = sliceInfo(info, start.column);718}719} else if (line === end.line) {720info = sliceInfo(info, 0, end.column);721}722723var indent = Math.max(info.indent, 0);724725var before = info.line.slice(0, info.sliceStart);726if (options.reuseWhitespace &&727isOnlyWhitespace(before) &&728countSpaces(before, options.tabWidth) === indent) {729// Reuse original spaces if the indentation is correct.730parts.push(info.line.slice(0, info.sliceEnd));731continue;732}733734var tabs = 0;735var spaces = indent;736737if (options.useTabs) {738tabs = Math.floor(indent / tabWidth);739spaces -= tabs * tabWidth;740}741742var result = "";743744if (tabs > 0) {745result += new Array(tabs + 1).join("\t");746}747748if (spaces > 0) {749result += new Array(spaces + 1).join(" ");750}751752result += info.line.slice(info.sliceStart, info.sliceEnd);753754parts.push(result);755}756757return parts.join("\n");758};759760Lp.isEmpty = function() {761return this.length < 2 && this.getLineLength(1) < 1;762};763764Lp.join = function(elements) {765var separator = this;766var separatorSecret = getSecret(separator);767var infos = [];768var mappings = [];769var prevInfo;770771function appendSecret(secret) {772if (secret === null)773return;774775if (prevInfo) {776var info = secret.infos[0];777var indent = new Array(info.indent + 1).join(" ");778var prevLine = infos.length;779var prevColumn = Math.max(prevInfo.indent, 0) +780prevInfo.sliceEnd - prevInfo.sliceStart;781782prevInfo.line = prevInfo.line.slice(7830, prevInfo.sliceEnd) + indent + info.line.slice(784info.sliceStart, info.sliceEnd);785786prevInfo.sliceEnd = prevInfo.line.length;787788if (secret.mappings.length > 0) {789secret.mappings.forEach(function(mapping) {790mappings.push(mapping.add(prevLine, prevColumn));791});792}793794} else if (secret.mappings.length > 0) {795mappings.push.apply(mappings, secret.mappings);796}797798secret.infos.forEach(function(info, i) {799if (!prevInfo || i > 0) {800prevInfo = copyLineInfo(info);801infos.push(prevInfo);802}803});804}805806function appendWithSeparator(secret, i) {807if (i > 0)808appendSecret(separatorSecret);809appendSecret(secret);810}811812elements.map(function(elem) {813var lines = fromString(elem);814if (lines.isEmpty())815return null;816return getSecret(lines);817}).forEach(separator.isEmpty()818? appendSecret819: appendWithSeparator);820821if (infos.length < 1)822return emptyLines;823824var lines = new Lines(infos);825826getSecret(lines).mappings = mappings;827828return lines;829};830831exports.concat = function(elements) {832return emptyLines.join(elements);833};834835Lp.concat = function(other) {836var args = arguments,837list = [this];838list.push.apply(list, args);839assert.strictEqual(list.length, args.length + 1);840return emptyLines.join(list);841};842843// The emptyLines object needs to be created all the way down here so that844// Lines.prototype will be fully populated.845var emptyLines = fromString("");846847848