Javascript 如何检测是否使用分号来终止由Esprima生成的Mozilla解析器AST中的表达式?
开发人员创建了一个非常简单的程序:Javascript 如何检测是否使用分号来终止由Esprima生成的Mozilla解析器AST中的表达式?,javascript,parsing,abstract-syntax-tree,esprima,Javascript,Parsing,Abstract Syntax Tree,Esprima,开发人员创建了一个非常简单的程序: var a = 6; var b = 7 console.log(a * b); 我想确保开发人员使用分号,因为我不相信所有开发人员都知道所有规则。因为我将添加其他代码质量检查,所以我想使用生成一个待检查代码的列表。当使用(选中“基于行和列”选项)解析上述简单程序时: 我该如何检查分号是否被使用?我可以推断其中一个可能没有在第二行使用,因为AST中的第二个VariableDeclaration显示它在{line:3,column:0}处结束,如下所示 其他
var a = 6;
var b = 7
console.log(a * b);
我想确保开发人员使用分号,因为我不相信所有开发人员都知道所有规则。因为我将添加其他代码质量检查,所以我想使用生成一个待检查代码的列表。当使用(选中“基于行和列”选项)解析上述简单程序时:
我该如何检查分号是否被使用?我可以推断其中一个可能没有在第二行使用,因为AST中的第二个VariableDeclaration
显示它在{line:3,column:0}
处结束,如下所示
其他使用Esprima的工具也是这样做的吗?检查\r\n
与\n
行结束符如何?Esprima不是此任务的正确工具吗
编辑
我和一位同事分享了这个问题,他告诉我“可能需要解析树”,这样我就可以有一个令牌列表。这就解决了我的部分问题。以下是Esprima提供的代币:
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "a"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "6"
},
{
"type": "Punctuator",
"value": ";"
},
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "b"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "7"
},
{
"type": "Identifier",
"value": "console"
},
{
"type": "Punctuator",
"value": "."
},
{
"type": "Identifier",
"value": "log"
},
{
"type": "Punctuator",
"value": "("
},
{
"type": "Identifier",
"value": "a"
},
{
"type": "Punctuator",
"value": "*"
},
{
"type": "Identifier",
"value": "b"
},
{
"type": "Punctuator",
"value": ")"
},
{
"type": "Punctuator",
"value": ";"
}
]
现在,我需要弄清楚如何将此令牌列表与AST结合使用,以告诉我应该在第2行上有一个分号。要捕获JavaScript解释器不会捕获的逻辑或协议错误(例如,总是以分号终止语句),您应该编写自己的状态机来模拟语法。以下是在CoffeeScript+Node.js中针对您给出的示例执行此操作的一种方法:
esprima = require 'esprima'
p_type = (is_valid) -> (token) -> is_valid(token.type)
p_value = (is_valid) -> (token) -> is_valid(token.value)
p_is = (target) -> (value) -> value is target
p_in = (targets...) -> (value) -> targets.indexOf(value) >= 0
p_tautology = () -> true
p_disjoin = (fs...) ->
switch fs.length
when 0
p_tautology
when 1
[f] = fs
(value) -> f(value)
when 2
[f, g] = fs
(value) -> f(value) || g(value)
else
[f, gs...] = fs
g = p_disjoin.apply(null, gs)
(value) -> f(value) || g(value)
p_conjoin = (fs...) ->
switch fs.length
when 0
p_tautology
when 1
[f] = fs
(value) -> f(value)
when 2
[f, g] = fs
(value) -> f(value) && g(value)
else
[f, gs...] = fs
g = p_conjoin.apply(null, gs)
(value) -> f(value) && g(value)
f_type = (token) -> token.type
f_value = (token) -> token.value
f_constant = (value) -> () -> value
f_identity = (x) -> x
f_token = (fn) -> (token) -> fn(token)
f_transition = (dispatch, transition) -> (token) -> transition[dispatch token]
f_default = (default_value, transition_fn) -> (token) -> transition_fn(token) || default_value
to_string = (value) ->
if value is null
'null'
else if value is `undefined`
'undefined'
else if typeof value is 'string'
'"' + value + '"'
else if typeof value.length is 'number' and value.length >= 0
elems = []
for e in value
elems.push to_string(e)
'[' + elems.join(', ') + ']'
else if typeof value is 'object'
if value.toString is Object::toString
attrs = []
for own k,v of value
attrs.push k + ': ' + to_string(v)
'{' + attrs.join(', ') + '}'
else
value.toString()
else
value.toString()
root =
is_valid: p_disjoin(
p_conjoin(p_type(p_is 'Keyword'), p_value(p_is 'var')),
p_type(p_is 'Identifier')
)
next_label: f_transition f_type, 'Keyword': 'variable_declaration', 'Identifier': 'identifier'
handle_error: (tokens, index) ->
if index > 0
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected variable "+
"declaration after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
else
curr_token = tokens[index]
{line, column} = curr_token.loc.start
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected variable "+
"declaration but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in '.')
next_label: f_transition f_value, '.': 'membership'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected '.' after "+
"#{to_string prev_token.value}, but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
membership: () ->
is_valid: p_type(p_is 'Identifier')
next_label: f_constant 'invocation'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected an identifier "+
"after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
invocation: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_is '(')
next_label: f_constant 'identifier'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected '(' after "+
"#{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () ->
is_valid: p_type(p_in 'Identifier')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected "+
"an identifier after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in '*')
next_label: f_transition f_value, '*': 'identifier'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: "+
"Expected a binary operator after "+
"#{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () ->
is_valid: p_conjoin p_type(p_is 'Identifier')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected "+
"an identifier after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_is ')')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: "+
"Expected ')' after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: f_constant p_type(p_is 'Punctuator'), p_value(p_is ';')
next_label: f_transition f_value, ';': 'terminator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: "+
"Expected ';' after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
terminator: () -> root
variable_declaration: () ->
is_valid: p_type(p_is 'Identifier')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected an identifier "+
"after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in '=', ',', ';')
next_label: f_token f_transition f_value, '=': 'assignment', ',': 'separator', ';': 'terminator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected '=', ',', "+
"or ';' after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
assignment: () ->
is_valid: p_type(p_in 'Boolean', 'Identifier', 'Null', 'Numeric', 'String', 'RegularExpression')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected a "+
"literal or an identifier after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in ',', ';', '.', '(', '{')
next_label: f_transition f_value, ',': 'identifier', ';': 'terminator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column: #{1 + column}: "+
"Expected ',' or ';' after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () -> root.transition.variable_declaration()
terminator: () -> root
separator: () -> root.transition.variable_declaration()
terminator: () -> root
lint = (tokens) ->
state = root
index = 0
prev_token = null
while index < tokens.length
token = tokens[index]
if state.is_valid(token)
state = state.transition[state.next_label token]()
else
state.handle_error(tokens, index)
prev_token = token
index += 1
text = '''
var a = 6;
var b = 7
console.log(a * b);
'''
tokens = esprima.tokenize(text, loc: true)
lint tokens
esprima=需要“esprima”
p_类型=(是否有效)->(令牌)->是否有效(令牌.type)
p_值=(是否有效)->(令牌)->是否有效(令牌.value)
p_is=(目标)->(值)->值是目标
p_in=(目标…)->(值)->目标。indexOf(值)>=0
p_同义反复=()->真
p_分离=(fs…)->
开关fs.length
当0
p_重言式
当1
[f] =fs
(值)->f(值)
当2
[f,g]=fs
(值)->f(值)| g(值)
其他的
[f,gs…]=fs
g=p_分离。应用(null,gs)
(值)->f(值)| g(值)
p_连体=(fs…)->
开关fs.length
当0
p_重言式
当1
[f] =fs
(值)->f(值)
当2
[f,g]=fs
(价值)->f(价值)和g(价值)
其他的
[f,gs…]=fs
g=p_连接。应用(null,gs)
(价值)->f(价值)和g(价值)
f_type=(token)->token.type
f_值=(令牌)->token.value
f_常数=(值)->()->值
f_恒等式=(x)->x
f_令牌=(fn)->(令牌)->fn(令牌)
f_transition=(调度,转换)->(令牌)->转换[调度令牌]
默认值=(默认值,转换值)->(令牌)->转换值
to_字符串=(值)->
如果值为空
“空”
如果值为“未定义”,则为else`
“未定义”
否则,如果值的类型为“字符串”
“‘+值+‘”
否则,如果type of value.length为“number”且value.length>=0
元素=[]
对于e值
元素推送至字符串(e)
“['+elems.join(',')+']”
否则,如果值的类型为“对象”
如果value.toString是Object::toString
属性=[]
对于自身的k,v值
attrs.push k+':'+到字符串(v)
“{'+attrs.join(',')+'}”
其他的
value.toString()
其他的
value.toString()
根=
_有效:p_分离(
p_连接(p_类型(p_是‘关键字’),p_值(p_是‘var’),
p_类型(p_为“标识符”)
)
下一个标签:f_转换f_类型,'关键字':'变量声明','标识符':'标识符'
句柄错误:(令牌、索引)->
如果索引>0
[prev_token,curr_token]=tokens.slice(索引-1,索引+1)
{line,column}=prev_token.loc.end
process.stderr.write(
[错误]行#{line},列#{1+列}:应为变量+
#{to_string prev_token.value}之后的声明,但已收到+
“#{to_string curr_token.value}\n”)
进程。退出(1)
其他的
当前令牌=令牌[索引]
{line,column}=curr_token.loc.start
process.stderr.write(
[错误]行#{line},列#{1+列}:应为变量+
“声明但收到35;{to_string curr_token.value}\n”)
进程。退出(1)
过渡:
标识符:()->
_是否有效:p_连接p_类型(p_是“标点符号”),p_值(p_在“.”中)
下一个_标签:f_转换f_值,'.':'成员资格'
句柄错误:(令牌、索引)->
[prev_token,curr_token]=tokens.slice(索引-1,索引+1)
{line,column}=prev_token.loc.end
process.stderr.write(
“[错误]第#{line}行、第#{1+列}列:后面应为”“。”+
“#{to_string prev_token.value},但收到#{to_string curr_token.value}\n”)
进程。退出(1)
过渡:
成员:()->
\u有效:p\u类型(p\u为“标识符”)
下一个标签:f_常量“调用”
句柄错误:(令牌、索引)->
[prev_token,curr_token]=tokens.slice(索引-1,索引+1)
{line,column}=prev_token.loc.end
process.stderr.write(
[错误]行#{line},列#{1+列}:应为标识符+
在#{to_string prev_token.value}之后,但收到+
“#{to_string curr_token.value}\n”)
进程。退出(1)
过渡:
调用:()->
_是否有效:p_连接p_类型(p_是“标点符号”),p_值(p_是“(”)
下一个标签:f_常量“标识符”
句柄错误:(令牌、索引)->
[prev_token,curr_token]=tokens.slice(索引-1,索引+1)
{line,column}=prev_token.loc.end
process.stderr.write(
“[错误]行#{line},列#{1+列}:后面应为'('+
#{to_string prev_token.value},但已收到+
“#{to_字符串
esprima = require 'esprima'
p_type = (is_valid) -> (token) -> is_valid(token.type)
p_value = (is_valid) -> (token) -> is_valid(token.value)
p_is = (target) -> (value) -> value is target
p_in = (targets...) -> (value) -> targets.indexOf(value) >= 0
p_tautology = () -> true
p_disjoin = (fs...) ->
switch fs.length
when 0
p_tautology
when 1
[f] = fs
(value) -> f(value)
when 2
[f, g] = fs
(value) -> f(value) || g(value)
else
[f, gs...] = fs
g = p_disjoin.apply(null, gs)
(value) -> f(value) || g(value)
p_conjoin = (fs...) ->
switch fs.length
when 0
p_tautology
when 1
[f] = fs
(value) -> f(value)
when 2
[f, g] = fs
(value) -> f(value) && g(value)
else
[f, gs...] = fs
g = p_conjoin.apply(null, gs)
(value) -> f(value) && g(value)
f_type = (token) -> token.type
f_value = (token) -> token.value
f_constant = (value) -> () -> value
f_identity = (x) -> x
f_token = (fn) -> (token) -> fn(token)
f_transition = (dispatch, transition) -> (token) -> transition[dispatch token]
f_default = (default_value, transition_fn) -> (token) -> transition_fn(token) || default_value
to_string = (value) ->
if value is null
'null'
else if value is `undefined`
'undefined'
else if typeof value is 'string'
'"' + value + '"'
else if typeof value.length is 'number' and value.length >= 0
elems = []
for e in value
elems.push to_string(e)
'[' + elems.join(', ') + ']'
else if typeof value is 'object'
if value.toString is Object::toString
attrs = []
for own k,v of value
attrs.push k + ': ' + to_string(v)
'{' + attrs.join(', ') + '}'
else
value.toString()
else
value.toString()
root =
is_valid: p_disjoin(
p_conjoin(p_type(p_is 'Keyword'), p_value(p_is 'var')),
p_type(p_is 'Identifier')
)
next_label: f_transition f_type, 'Keyword': 'variable_declaration', 'Identifier': 'identifier'
handle_error: (tokens, index) ->
if index > 0
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected variable "+
"declaration after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
else
curr_token = tokens[index]
{line, column} = curr_token.loc.start
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected variable "+
"declaration but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in '.')
next_label: f_transition f_value, '.': 'membership'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected '.' after "+
"#{to_string prev_token.value}, but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
membership: () ->
is_valid: p_type(p_is 'Identifier')
next_label: f_constant 'invocation'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected an identifier "+
"after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
invocation: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_is '(')
next_label: f_constant 'identifier'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected '(' after "+
"#{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () ->
is_valid: p_type(p_in 'Identifier')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected "+
"an identifier after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in '*')
next_label: f_transition f_value, '*': 'identifier'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: "+
"Expected a binary operator after "+
"#{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () ->
is_valid: p_conjoin p_type(p_is 'Identifier')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected "+
"an identifier after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_is ')')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: "+
"Expected ')' after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: f_constant p_type(p_is 'Punctuator'), p_value(p_is ';')
next_label: f_transition f_value, ';': 'terminator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: "+
"Expected ';' after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
terminator: () -> root
variable_declaration: () ->
is_valid: p_type(p_is 'Identifier')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected an identifier "+
"after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in '=', ',', ';')
next_label: f_token f_transition f_value, '=': 'assignment', ',': 'separator', ';': 'terminator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected '=', ',', "+
"or ';' after #{to_string prev_token.value}, but received "+
"#{to_string curr_token.value}\n")
process.exit(1)
transition:
assignment: () ->
is_valid: p_type(p_in 'Boolean', 'Identifier', 'Null', 'Numeric', 'String', 'RegularExpression')
next_label: f_constant 'punctuator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column #{1 + column}: Expected a "+
"literal or an identifier after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
punctuator: () ->
is_valid: p_conjoin p_type(p_is 'Punctuator'), p_value(p_in ',', ';', '.', '(', '{')
next_label: f_transition f_value, ',': 'identifier', ';': 'terminator'
handle_error: (tokens, index) ->
[prev_token, curr_token] = tokens.slice(index - 1, index + 1)
{line, column} = prev_token.loc.end
process.stderr.write(
"[Error] line #{line}, column: #{1 + column}: "+
"Expected ',' or ';' after #{to_string prev_token.value}, "+
"but received #{to_string curr_token.value}\n")
process.exit(1)
transition:
identifier: () -> root.transition.variable_declaration()
terminator: () -> root
separator: () -> root.transition.variable_declaration()
terminator: () -> root
lint = (tokens) ->
state = root
index = 0
prev_token = null
while index < tokens.length
token = tokens[index]
if state.is_valid(token)
state = state.transition[state.next_label token]()
else
state.handle_error(tokens, index)
prev_token = token
index += 1
text = '''
var a = 6;
var b = 7
console.log(a * b);
'''
tokens = esprima.tokenize(text, loc: true)
lint tokens