Mike Sherov
twitter / github : @mikesherov
Principal Engineer - SkillShare
Maintainer: Esprima, ESLint, ESTree
While wearing floaties
function Scanner(source) {
this.source = source;
this.position = 0;
}
Scanner.prototype.peek = function() {
return {
value: this.source[this.position]
position: this.position
};
}
Scanner.prototype.advance = function() {
var result = this.peek();
this.position++;
return result;
}
function Lexer(scanner) {
this.scanner = scanner;
}
Lexer.prototype.getToken = function() {
var char = this.scanner.advance();
if (char.value === '+') {
var operator = '+';
if (this.scanner.peek().value === '+') {
operator = '++';
this.scanner.advance(); // consume peeked token
}
return {
type: 'punctuator',
value: operator,
range: {
start: char.pos,
end: char.pos + operator.length
}
};
}
...
}
function Parser(lexer) {
this.lexer = lexer;
this.lex();
}
// advances to the next token
Parser.prototype.lex = function() {
this.cur = this.lexer.getToken();
};
// token comparison
Parser.prototype.match = function(value) {
return this.cur.value === value &&
this.cur.type === 'punctuator';
};
// advances to the next token,
// and fails if value is incorrect
Parser.prototype.expect = function(value) {
this.lex();
if (!this.match(value)) {
throw new Error('unexpected token: ' + token.value);
}
};
Parser.prototype.parseCondExpr = function() {
var expr = this.parseBinaryExpr();
// is current token a `?`
if (this.match('?')) {
// advance past `?`
this.lex();
var consequent = this.parseAssignmentExpression();
// move past :, but fail if it's not :
this.expect(':');
var alternate = this.parseAssignmentExpression();
return {
type: 'ConditionalExpression',
test: expr,
consequent: consequent,
alternate: alternate
};
}
return expr;
};
CondExpr :
BinaryExpr
BinaryExpr ? AssignmentExpr : AssignmentExpr
Grammar (EBNF)
Terminology
<test> ? <consequent> : <alternate>
A fast recursive descent Javascript parser
Finally, an AST!
hasAnswer ? 42 : 0;
{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "ConditionalExpression",
"test": {
"type": "Identifier",
"name": "hasAnswer"
},
"consequent": {
"type": "Literal",
"value": 42,
"raw": "42"
},
"alternate": {
"type": "Literal",
"value": 0,
"raw": "0"
}
}
}
]
}
if (900 === yearsOldYouReach) {
lookAsGood = youWilNot;
}
How do you describe this is in a way that uses AST terminology?
{
"type": "BinaryExpression",
"operator": "===",
"left": {
"type": "Literal",
"value": 900,
"raw": "900"
},
"right": {
"type": "Identifier",
"name": "yearsOldYouReach"
}
}
A Binary Expression where the operator is an equality operator, and whose left hand side is a literal, and whose right hand side is not.
A Javascript Linter
Calls callbacks for each visited node
{
create(context) {
BinaryExpression(node) {
// will be called once for each
// binary expression in the tree
}
}
}
API for reporting errors
context.report({
node,
message: "Expected literal to be on the right side of {{operator}}.",
data: {
operator: node.operator
}
fix: fixer => fixer.replaceText(node, getFixedString(node))
});
disallowYodaConditions
const comparators = new Set(['==', '===', '!=', '!==', '>', '<', '>=', '<=']);
module.exports = {
create(context) {
BinaryExpression(node) {
if (comparators.has(node.operator) && node.left.type === 'Literal') {
context.report({
node,
message: "Expected literal to be on the right side of {{operator}}.",
data: {
operator: node.operator
}
});
}
}
}
};