diff --git a/bin/moonc b/bin/moonc index d7a9220..ed7f527 100755 --- a/bin/moonc +++ b/bin/moonc @@ -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)) diff --git a/moonscript/cmd/lint.lua b/moonscript/cmd/lint.lua new file mode 100644 index 0000000..24e144a --- /dev/null +++ b/moonscript/cmd/lint.lua @@ -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 +} diff --git a/moonscript/cmd/lint.moon b/moonscript/cmd/lint.moon new file mode 100644 index 0000000..dd3568d --- /dev/null +++ b/moonscript/cmd/lint.moon @@ -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 } diff --git a/moonscript/compile.lua b/moonscript/compile.lua index 83f95cd..0970eab 100644 --- a/moonscript/compile.lua +++ b/moonscript/compile.lua @@ -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 diff --git a/moonscript/compile.moon b/moonscript/compile.moon index 0d99946..33bb719 100644 --- a/moonscript/compile.moon +++ b/moonscript/compile.moon @@ -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, ...