Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Commit

Permalink
add comment attachment to the parser and remove dead acorn options
Browse files Browse the repository at this point in the history
  • Loading branch information
sebmck committed Jul 21, 2015
1 parent 9219b7f commit 0ca73d2
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 391 deletions.
129 changes: 129 additions & 0 deletions src/comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* Based on the comment attachment algorithm used in espree and estraverse.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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 THE COPYRIGHT HOLDERS AND 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 <COPYRIGHT HOLDER> 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.
*/

import { Parser } from "./state";

function last(stack) {
return stack[stack.length - 1];
}

const pp = Parser.prototype;

pp.addComment = function (comment) {
this.trailingComments.push(comment);
this.leadingComments.push(comment);
};

pp.processComment = function (node) {
var stack = this.bottomRightStack;

var lastChild;
var trailingComments;
var i;

if (this.trailingComments.length > 0) {
// If the first comment in trailingComments comes after the
// current node, then we're good - all comments in the array will
// come after the node and so it's safe to add then as official
// trailingComments.
if (this.trailingComments[0].start >= node.end) {
trailingComments = this.trailingComments;
this.trailingComments = [];
} else {
// Otherwise, if the first comment doesn't come after the
// current node, that means we have a mix of leading and trailing
// comments in the array and that leadingComments contains the
// same items as trailingComments. Reset trailingComments to
// zero items and we'll handle this by evaluating leadingComments
// later.
this.trailingComments.length = 0;
}
} else {
var lastInStack = last(stack);
if (stack.length > 0 && lastInStack.trailingComments && lastInStack.trailingComments[0].start >= node.end) {
trailingComments = lastInStack.trailingComments;
lastInStack.trailingComments = null;
}
}

// Eating the stack.
while (stack.length > 0 && last(stack).start >= node.start) {
lastChild = stack.pop();
}

if (lastChild) {
if (lastChild.leadingComments && last(lastChild.leadingComments).end <= node.start) {
node.leadingComments = lastChild.leadingComments;
lastChild.leadingComments = null;
}
} else if (this.leadingComments.length > 0) {
if (last(this.leadingComments).end <= node.start) {
node.leadingComments = this.leadingComments;
this.leadingComments = [];
} else {
// https://github.com/eslint/espree/issues/2
//
// In special cases, such as return (without a value) and
// debugger, all comments will end up as leadingComments and
// will otherwise be eliminated. This this step runs when the
// bottomRightStack is empty and there are comments left
// in leadingComments.
//
// This loop figures out the stopping point between the actual
// leading and trailing comments by finding the location of the
// first comment that comes after the given node.
for (i = 0; i < this.leadingComments.length; i++) {
if (this.leadingComments[i].end > node.start) {
break;
}
}

// Split the array based on the location of the first comment
// that comes after the node. Keep in mind that this could
// result in an empty array, and if so, the array must be
// deleted.
node.leadingComments = this.leadingComments.slice(0, i);
if (node.leadingComments.length === 0) {
node.leadingComments = null;
}

// Similarly, trailing comments are attached later. The variable
// must be reset to null if there are no trailing comments.
trailingComments = this.leadingComments.slice(i);
if (trailingComments.length === 0) {
trailingComments = null;
}
}
}

if (trailingComments) {
if (trailingComments.length && trailingComments[0].start >= node.start && last(trailingComments).end <= node.end) {
node.innerComments = trailingComments;
} else {
node.trailingComments = trailingComments;
}
}

stack.push(node);
};
2 changes: 1 addition & 1 deletion src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {

pp.parseLiteral = function (value) {
let node = this.startNode();
node.value = value;
node.rawValue = node.value = value;
node.raw = this.input.slice(this.start, this.end);
this.next();
return this.finishNode(node, "Literal");
Expand Down
21 changes: 8 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,21 @@ import "./parseutil";
import "./statement";
import "./lval";
import "./expression";
import "./node";
import "./location";
import "./lookahead";
import "./tokentype";
import { types as tokTypes } from "./tokentype";
import "./tokenize";
import "./tokencontext";

export { Parser, plugins } from "./state";
export { defaultOptions } from "./options";
export { SourceLocation } from "./location";
export { getLineInfo } from "./location";
export { Node } from "./node";
export { TokenType, types as tokTypes } from "./tokentype";
export { TokContext, types as tokContexts } from "./tokencontext";
export { isIdentifierChar, isIdentifierStart } from "./identifier";
export { Token } from "./tokenize";
export { isNewLine, lineBreak, lineBreakG } from "./whitespace";

import "./comments";
import flowPlugin from "./plugins/flow";
import jsxPlugin from "./plugins/jsx";

plugins.flow = flowPlugin;
plugins.jsx = jsxPlugin;

export function parse(input, options) {
return new Parser(getOptions(options), input).parse();
}

export { tokTypes };
3 changes: 1 addition & 2 deletions src/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ export class Position {
}

export class SourceLocation {
constructor(p, start, end) {
constructor(start, end) {
this.start = start;
this.end = end;
if (p.sourceFile !== null) this.source = p.sourceFile;
}
}

Expand Down
12 changes: 9 additions & 3 deletions src/lookahead.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ var STATE_KEYS = [
"inType",
"inFunction",
"inGenerator",
"labels"
"labels",
"tokens",
"comments"
];

pp.getState = function () {
Expand All @@ -32,8 +34,10 @@ pp.getState = function () {
var key = STATE_KEYS[i];
state[key] = this[key];
}
state.context = this.context.slice();
state.labels = this.labels.slice();
state.comments = this.comments.slice();
state.context = this.context.slice();
state.tokens = this.tokens.slice();
state.labels = this.labels.slice();
return state;
};

Expand All @@ -45,9 +49,11 @@ pp.setState = function (state) {

pp.lookahead = function () {
var old = this.getState();

this.isLookahead = true;
this.next();
this.isLookahead = false;

var curr = this.getState();
this.setState(old);
return curr;
Expand Down
7 changes: 2 additions & 5 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ export class Node {

if (parser) {
if (parser.options.locations) {
this.loc = new SourceLocation(parser, loc);
}

if (parser.options.directSourceFile) {
this.sourceFile = parser.options.directSourceFile;
this.loc = new SourceLocation(loc);
}

if (parser.options.ranges) {
Expand Down Expand Up @@ -46,6 +42,7 @@ function finishNodeAt(node, type, pos, loc) {
node.end = pos;
if (this.options.locations) node.loc.end = loc;
if (this.options.ranges) node.range[1] = pos;
this.processComment(node);
return node;
}

Expand Down
68 changes: 1 addition & 67 deletions src/options.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import { has } from "./util";
import { SourceLocation } from "./location";

// A second optional argument can be given to further configure
// the parser process. These options are recognized:

export const defaultOptions = {
// Source type ("script" or "module") for different semantics
sourceType: "script",
// `onInsertedSemicolon` can be a callback that will be called
// when a semicolon is automatically inserted. It will be passed
// th position of the comma as an offset, and if `locations` is
// enabled, it is given the location as a `{line, column}` object
// as second argument.
onInsertedSemicolon: null,
// `onTrailingComma` is similar to `onInsertedSemicolon`, but for
// trailing commas.
onTrailingComma: null,
// By default, reserved words are not enforced. Disable
// `allowReserved` to enforce them. When this option has the
// value "never", reserved words and keywords can also not be
Expand All @@ -27,31 +17,11 @@ export const defaultOptions = {
// When enabled, import/export statements are not constrained to
// appearing at the top of the program.
allowImportExportEverywhere: false,
// When enabled, hashbang directive in the beginning of file
// is allowed and treated as a line comment.
allowHashBang: false,
// When `locations` is on, `loc` properties holding objects with
// `start` and `end` properties in `{line, column}` form (with
// line being 1-based and column 0-based) will be attached to the
// nodes.
locations: false,
// A function can be passed as `onToken` option, which will
// cause Acorn to call that function with object in the same
// format as tokenize() returns. Note that you are not
// allowed to call the parser from the callback—that will
// corrupt its internal state.
onToken: null,
// A function can be passed as `onComment` option, which will
// cause Acorn to call that function with `(block, text, start,
// end)` parameters whenever a comment is skipped. `block` is a
// boolean indicating whether this is a block (`/* */`) comment,
// `text` is the content of the comment, and `start` and `end` are
// character offsets that denote the start and end of the comment.
// When the `locations` option is on, two more parameters are
// passed, the full `{line, column}` locations of the start and
// end of the comments. Note that you are not allowed to call the
// parser from the callback—that will corrupt its internal state.
onComment: null,
// Nodes have their start and end characters offsets recorded in
// `start` and `end` properties (directly on the node, rather than
// the `loc` object, which holds line/column data. To also add a
Expand All @@ -61,18 +31,6 @@ export const defaultOptions = {
//
// [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
ranges: false,
// It is possible to parse multiple files into a single AST by
// passing the tree produced by parsing the first file as
// `program` option in subsequent parses. This will add the
// toplevel forms of the parsed file to the `Program` (top) node
// of an existing parse tree.
program: null,
// When `locations` is on, you can pass this to record the source
// file in every node's `loc` object.
sourceFile: null,
// This value, if given, is stored in every node, whether
// `locations` is on or off.
directSourceFile: null,
plugins: {},
// Babel-specific options
features: {},
Expand All @@ -83,32 +41,8 @@ export const defaultOptions = {

export function getOptions(opts) {
let options = {};
for (let opt in defaultOptions)
for (let opt in defaultOptions) {
options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt];

if (Array.isArray(options.onToken)) {
let tokens = options.onToken;
options.onToken = (token) => tokens.push(token);
}
if (Array.isArray(options.onComment)) {
options.onComment = pushComment(options, options.onComment);
}

return options;
}

function pushComment(options, array) {
return function (block, text, start, end, startLoc, endLoc) {
let comment = {
type: block ? "Block" : "Line",
value: text,
start: start,
end: end
};
if (options.locations)
comment.loc = new SourceLocation(this, startLoc, endLoc);
if (options.ranges)
comment.range = [start, end];
array.push(comment);
};
}
20 changes: 16 additions & 4 deletions src/parseutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ pp.eat = function (type) {
}
};

// TODO

pp.isRelational = function (op) {
return this.type === tt.relational && this.value === op;
};

// TODO

pp.expectRelational = function (op) {
if (this.isRelational(op)) {
this.next();
} else {
this.unexpected();
}
};

// Tests whether parsed token is a contextual keyword.

pp.isContextual = function (name) {
Expand Down Expand Up @@ -52,8 +68,6 @@ pp.canInsertSemicolon = function () {

pp.insertSemicolon = function () {
if (this.canInsertSemicolon()) {
if (this.options.onInsertedSemicolon)
this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc);
return true;
}
};
Expand All @@ -67,8 +81,6 @@ pp.semicolon = function () {

pp.afterTrailingComma = function (tokType) {
if (this.type === tokType) {
if (this.options.onTrailingComma)
this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc);
this.next();
return true;
}
Expand Down
Loading

0 comments on commit 0ca73d2

Please sign in to comment.