create a basic lint tool

This commit is contained in:
leaf corcoran 2013-12-26 01:29:00 -08:00
parent 0726982099
commit cc534a01f3
5 changed files with 236 additions and 6 deletions

View File

@ -9,8 +9,8 @@ local dump_tree = require"moonscript.dump".tree
local alt_getopt = require "alt_getopt"
local lfs = require "lfs"
local opts, ind = alt_getopt.get_opts(arg, "vhwt:pTXb", {
print = "p", tree = "T", version = "v", help = "h"
local opts, ind = alt_getopt.get_opts(arg, "lvhwt:pTXb", {
print = "p", tree = "T", version = "v", help = "h", lint = "l"
})
local read_stdin = arg[1] == "--"
@ -25,6 +25,7 @@ local help = [[Usage: %s [options] files...
-p Write output to standard out
-T Write parse tree instead of code (to stdout)
-X Write line rewrite map instead of code (to stdout)
-l Perform lint on the file instead of compiling
-b Dump parse and compile time (doesn't write output)
-v Print version
@ -377,6 +378,15 @@ if opts.w then
end
print "\nQuitting..."
elseif opts.l then
for _, fname in pairs(files) do
lint = require "moonscript.cmd.lint"
local res = lint.lint_file(fname)
if res then
print(res)
print()
end
end
else
for _, fname in ipairs(files) do
local success, err = compile_and_write(fname, target_dir..convert_path(fname))

145
moonscript/cmd/lint.lua Normal file
View File

@ -0,0 +1,145 @@
local insert
do
local _obj_0 = table
insert = _obj_0.insert
end
local Set
do
local _obj_0 = require("moonscript.data")
Set = _obj_0.Set
end
local Block
do
local _obj_0 = require("moonscript.compile")
Block = _obj_0.Block
end
local whitelist_globals = Set({
"print"
})
local LinterBlock
do
local _parent_0 = Block
local _base_0 = {
block = function(self, ...)
do
local _with_0 = _parent_0.block(self, ...)
_with_0.value_compilers = self.value_compilers
return _with_0
end
end
}
_base_0.__index = _base_0
setmetatable(_base_0, _parent_0.__base)
local _class_0 = setmetatable({
__init = function(self, lint_errors, ...)
if lint_errors == nil then
lint_errors = { }
end
self.lint_errors = lint_errors
_parent_0.__init(self, ...)
local vc = self.value_compilers
self.value_compilers = setmetatable({
raw_value = function(block, name)
if name:match("^[%w_]+$") and not block:has_name(name) and not whitelist_globals[name] then
local stm = block.current_stms[block.current_stm_i]
insert(self.lint_errors, {
"accessing global " .. tostring(name),
stm[-1]
})
end
return vc.raw_value(block, name)
end
}, {
__index = vc
})
end,
__base = _base_0,
__name = "LinterBlock",
__parent = _parent_0
}, {
__index = function(cls, name)
local val = rawget(_base_0, name)
if val == nil then
return _parent_0[name]
else
return val
end
end,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
_base_0.__class = _class_0
if _parent_0.__inherited then
_parent_0.__inherited(_parent_0, _class_0)
end
LinterBlock = _class_0
end
local format_lint
format_lint = function(errors, code, header)
if not (next(errors)) then
return
end
local pos_to_line, get_line
do
local _obj_0 = require("moonscript.util")
pos_to_line, get_line = _obj_0.pos_to_line, _obj_0.get_line
end
local formatted
do
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #errors do
local _des_0 = errors[_index_0]
local msg, pos
msg, pos = _des_0[1], _des_0[2]
if pos then
local line = pos_to_line(code, pos)
msg = "line " .. tostring(line) .. ": " .. tostring(msg)
local line_text = "> " .. get_line(code, line)
local sep_len = math.max(#msg, #line_text)
_accum_0[_len_0] = table.concat({
msg,
("="):rep(sep_len),
line_text
}, "\n")
else
_accum_0[_len_0] = msg
end
_len_0 = _len_0 + 1
end
formatted = _accum_0
end
if header then
table.insert(formatted, 1, header)
end
return table.concat(formatted, "\n\n")
end
local lint_code
lint_code = function(code, name)
if name == nil then
name = "string input"
end
local parse = require("moonscript.parse")
local tree, err = parse.string(code)
if not (tree) then
return nil, err
end
local scope = LinterBlock()
scope:stms(tree)
return format_lint(scope.lint_errors, code, name)
end
local lint_file
lint_file = function(fname)
local f, err = io.open(fname)
if not (f) then
return nil, err
end
return lint_code(f:read("*a"), fname)
end
return {
lint_code = lint_code,
lint_file = lint_file
}

72
moonscript/cmd/lint.moon Normal file
View File

@ -0,0 +1,72 @@
import insert from table
import Set from require "moonscript.data"
import Block from require "moonscript.compile"
-- globals allowed to be referenced
whitelist_globals = Set {
"print"
}
class LinterBlock extends Block
new: (@lint_errors={}, ...) =>
super ...
vc = @value_compilers
@value_compilers = setmetatable {
raw_value: (block, name) ->
if name\match("^[%w_]+$") and not block\has_name(name) and not whitelist_globals[name]
stm = block.current_stms[block.current_stm_i]
insert @lint_errors, {
"accessing global #{name}"
stm[-1]
}
vc.raw_value block, name
}, __index: vc
block: (...) =>
with super ...
.value_compilers = @value_compilers
format_lint = (errors, code, header) ->
return unless next errors
import pos_to_line, get_line from require "moonscript.util"
formatted = for {msg, pos} in *errors
if pos
line = pos_to_line code, pos
msg = "line #{line}: #{msg}"
line_text = "> " .. get_line code, line
sep_len = math.max #msg, #line_text
table.concat {
msg
"="\rep sep_len
line_text
}, "\n"
else
msg
table.insert formatted, 1, header if header
table.concat formatted, "\n\n"
lint_code = (code, name="string input") ->
parse = require "moonscript.parse"
tree, err = parse.string code
return nil, err unless tree
scope = LinterBlock!
scope\stms tree
format_lint scope.lint_errors, code, name
lint_file = (fname) ->
f, err = io.open fname
return nil, err unless f
lint_code f\read("*a"), fname
{ :lint_code, :lint_file }

View File

@ -271,6 +271,7 @@ do
footer = "end",
export_all = false,
export_proper = false,
value_compilers = value_compilers,
__tostring = function(self)
local h
if "string" == type(self.header) then
@ -459,7 +460,7 @@ do
end,
is_value = function(self, node)
local t = ntype(node)
return value_compilers[t] ~= nil or t == "value"
return self.value_compilers[t] ~= nil or t == "value"
end,
name = function(self, node, ...)
return self:value(node, ...)
@ -472,7 +473,7 @@ do
else
action = node[1]
end
local fn = value_compilers[action]
local fn = self.value_compilers[action]
if not fn then
error("Failed to compile value: " .. dump.value(node))
end

View File

@ -161,6 +161,8 @@ class Block
export_all: false
export_proper: false
value_compilers: value_compilers
__tostring: =>
h = if "string" == type @header
@header
@ -316,7 +318,7 @@ class Block
is_value: (node) =>
t = ntype node
value_compilers[t] != nil or t == "value"
@value_compilers[t] != nil or t == "value"
-- line wise compile functions
name: (node, ...) => @value node, ...
@ -328,7 +330,7 @@ class Block
else
node[1]
fn = value_compilers[action]
fn = @value_compilers[action]
error "Failed to compile value: "..dump.value node if not fn
out = fn self, node, ...