Skip to content

Commit

Permalink
Maintain a queue to allow deletion of whitespace/semicolons without a…
Browse files Browse the repository at this point in the history
…ccessing buf.
  • Loading branch information
loganfsmyth committed Jul 5, 2016
1 parent afbcaa2 commit 3e2aea6
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 36 deletions.
80 changes: 54 additions & 26 deletions packages/babel-generator/src/buffer.js
@@ -1,13 +1,16 @@
import Position from "./position";
import trimEnd from "lodash/trimEnd";

const SPACES_RE = /^[ \t]+$/;

export default class Buffer {
constructor(map) {
this._map = map;
}

_buf = "";
_last = "";
_queue = [];

_position = new Position();
_sourcePosition = {
Expand All @@ -17,76 +20,91 @@ export default class Buffer {
};

get() {
this._flush();

return {
code: trimEnd(this._buf),
map: this._map ? this._map.get() : null,
};
}

append(str: string) {
this._flush();
this._append(str, this._sourcePosition.line, this._sourcePosition.column,
this._sourcePosition.filename);
}

_append(str, line, column, filename) {
// If there the line is ending, adding a new mapping marker is redundant
if (this._map && str[0] !== "\n") this._map.mark(this._position, this._sourcePosition);
if (this._map && str[0] !== "\n") this._map.mark(this._position, line, column, filename);

this._buf += str;
this._last = str[str.length - 1];
this._position.push(str);
}

_push(str) {
this._queue.unshift([str, this._sourcePosition.line, this._sourcePosition.column,
this._sourcePosition.filename]);
}

_flush() {
let item;
while (item = this._queue.pop()) this._append(...item);
}

line() {
this.append("\n");
this._push("\n");
}

space() {
this.append(" ");
this._push(" ");
}

whitespace(str) {
this.append(str);
this._push(str);
}

semicolon() {
this.append(";");
this._push(";");
}

removeTrailingSpaces() {
if (this._last !== " ") return;

const oldBuf = this._buf;
this._buf = this._buf.replace(/[ \t]+$/, "");
this._last = this._buf[this._buf.length - 1];
this._position.unshift(oldBuf.slice(this._buf.length));
while (this._queue.length > 0 && SPACES_RE.test(this._queue[0][0])) this._queue.shift();
}

removeTrailingNewline() {
if (this._last !== "\n") return;

this._buf = this._buf.slice(0, -1);
this._last = this._buf[this._buf.length - 1];
this._position.unshift("\n");
if (this._queue.length > 0 && this._queue[0][0] === "\n") this._queue.shift();
}

removeLastSemicolon() {
if (this._last !== ";") return;

this._buf = this._buf.slice(0, -1);
this._last = this._buf[this._buf.length - 1];
this._position.unshift(";");
if (this._queue.length > 0 && this._queue[0][0] === ";") this._queue.shift();
}

endsWith(str) {
if (Array.isArray(str)) return str.some((s) => this.endsWith(s));

if (str.length === 1) return str === this._last;
const end = this._last + this._queue.reduce((acc, item) => item[0] + acc, "");
if (str.length <= end.length) {
return end.slice(-str.length) === str;
}

return this._buf.slice(-str.length) === str;
// We assume that everything being matched is at most a single token plus some whitespace,
// which everything currently is, but otherwise we'd have to expand _last or check _buf.
return false;
}

getLast() {
if (this._queue.length > 0) {
const last = this._queue[this._queue.length - 1][0];
return last[last.length - 1];
}

return this._last;
}

hasContent() {
return !!this._last;
return this._queue.length > 0 || !!this._last;
}

/**
Expand Down Expand Up @@ -126,10 +144,20 @@ export default class Buffer {
}

getCurrentColumn() {
return this._position.column;
const extra = this._queue.reduce((acc, item) => item[0] + acc, "");
const lastIndex = extra.lastIndexOf("\n");

return lastIndex === -1 ? this._position.column + extra.length : (extra.length - 1 - lastIndex);
}

getCurrentLine() {
return this._position.line;
const extra = this._queue.reduce((acc, item) => item[0] + acc, "");

let count = 0;
for (let i = 0; i < extra.length; i++) {
if (extra[i] === "\n") count++;
}

return this._position.line + count;
}
}
20 changes: 10 additions & 10 deletions packages/babel-generator/src/source-map.js
Expand Up @@ -45,33 +45,33 @@ export default class SourceMap {
* values to insert a mapping to nothing.
*/

mark(position, sourcePos: Object) {
mark(position, line, column, filename) {
let map = this.map;
if (!map) return; // no source map

// Adding an empty mapping at the start of a generated line just clutters the map.
if (this._lastGenLine !== position.line && sourcePos.line === null) return;
if (this._lastGenLine !== position.line && line === null) return;

// If this mapping points to the same source location as the last one, we can ignore it since
// the previous one covers it.
if (this._lastGenLine === position.line && this._lastSourceLine === sourcePos.line &&
this._lastSourceColumn === sourcePos.column) {
if (this._lastGenLine === position.line && this._lastSourceLine === line &&
this._lastSourceColumn === column) {
return;
}

this._lastGenLine = position.line;
this._lastSourceLine = sourcePos.line;
this._lastSourceColumn = sourcePos.column;
this._lastSourceLine = line;
this._lastSourceColumn = column;

map.addMapping({
generated: {
line: position.line,
column: position.column
},
source: sourcePos.line == null ? null : sourcePos.filename || this.opts.sourceFileName,
original: sourcePos.line == null ? null : {
line: sourcePos.line,
column: sourcePos.column,
source: line == null ? null : filename || this.opts.sourceFileName,
original: line == null ? null : {
line: line,
column: column,
},
});
}
Expand Down

0 comments on commit 3e2aea6

Please sign in to comment.