1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 09:59:29 +00:00
candran/bin/cancheck

215 lines
7 KiB
Lua

#!/usr/bin/env lua
--[[
Based on luacheck: https://github.com/luarocks/luacheck
The MIT License (MIT)
Copyright (c) 2014 - 2018 Peter Melnichenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
-- Monkey patch Luacheck (tested against version 0.23.0) to support Candran files
local candran = require("candran")
local util = require("candran.util")
-- set a function upvalues (if several names are given, will go up the upvalue chain)
local function setupvalue(fn, val, name, ...)
for i=1, debug.getinfo(fn, "u").nups do
local n, v = debug.getupvalue(fn, i)
if n == name then
if not ... then
debug.setupvalue(fn, i, val)
else
setupvalue(v, val, ...)
end
end
end
end
-- escape a string to be used as a pattern
local function escape(str)
return str:gsub("[^%w]", "%%%0")
end
-- returns a pattern that find start and stop position of a token
local function pattern(token)
return "()"..escape(token).."()"
end
-- token aliases
local tokenAlias = {
["self"] = { "@", ":" }
}
-- Patch checker
local oldCheck = require("luacheck.check")
local function check(can)
local lua, err = candran.make(can, {chunkname="_luacheck_source"})
-- Warnings
if lua then
local r = oldCheck(lua)
-- Calculate Candran file position.
if #r.warnings > 0 then
local lua_lines = {}
for l in (lua.."\n"):gmatch("([^\n]*)\n") do
table.insert(lua_lines, l)
end
local can_lines = {}
for l in (can.."\n"):gmatch("([^\n]*)\n") do
table.insert(can_lines, l)
end
for i=#r.warnings, 1, -1 do
local warning = r.warnings[i]
-- calculating candran line
local lua_line = lua_lines[warning.line]
local source, line = lua_line:match(".*%-%- (.-)%:(%d+)$")
if source ~= "_luacheck_source" then -- line is from another file, discard
table.remove(r.warnings, i)
elseif source then
warning.can_line = tonumber(line)
-- do the same for prev_line
if warning.prev_line then
local s, l = lua_lines[warning.prev_line]:match(".*%-%- (.-)%:(%d+)$")
if s ~= "_luacheck_source" then
warning.prev_line = s..":"..l -- luacheck seems to do no validation on this, so we can redefine it to anything
elseif l then
warning.prev_line = l
end
end
-- Errors codes highlighting a identifier (alphanumeric+underscore not starting with a number):
-- 1xx: global variables
-- 2xx: unused variables
-- 3xx: unused values
-- 4xx: shadowing delarations
local isIdentifier = tonumber(warning.code) >= 100 and tonumber(warning.code) < 500
-- calculating candran column
local can_line = can_lines[warning.can_line]
local lua_token = lua_line:sub(warning.column, warning.end_column)
local token_pattern = pattern(lua_token) -- token finding pattern
-- the warning happens on the n-th instance of lua_token on this line
local lua_n = 1
for start in lua_line:gmatch(token_pattern) do
if start >= warning.column then
break
end
lua_n = lua_n + 1
end
-- Find associated candran token. If lua_n > can_nmax, the last found lua_token is used.
-- This approximation should work in like, 90% of cases.
local can_n = 1
local pos = 1
while can_n <= lua_n do
-- find first token or alias of this token
local start, stop = can_line:match(token_pattern, pos)
if tokenAlias[lua_token] then
for _, token in ipairs(tokenAlias[lua_token]) do
local nstart, nstop = can_line:match(pattern(token), pos)
if nstart and (not start or nstart < start) then
start, stop = nstart, nstop
end
end
-- for non aliases token that are identifier, check if match is a full identifier
-- (avoid things like `let e` matching the e in let)
elseif start and isIdentifier then
local prev = can_line:sub(start-1, start-1)
local next = can_line:sub(stop, stop)
if prev:match("[%w_]") or next:match("[%w_]") then
can_n = can_n - 1 -- skip this match
end
end
-- found
if start then
pos = stop
warning.can_column, warning.can_end_column = start, stop-1
can_n = can_n + 1
else
break
end
end
-- AFAIK, prev_column and prev_end_column are not displayed in any warning so we don't need to recalculate them for Candran.
end
end
end
return r
-- Syntax error
else
local line, column, msg = err:match(":(%d+):(%d+):%s*(.*)$")
local syntax_error = {
code = "011",
line = line,
column = column,
end_column = column,
msg = msg
}
return {
warnings = {syntax_error},
inline_options = {},
line_lengths = {},
line_endings = {}
}
end
end
package.loaded["luacheck.check"] = check
local runner = require("luacheck.runner")
local oldRunner = runner.new
function runner.new(opts)
-- Disable max line length checking (it is compiled code...)
opts.max_line_length = false
opts.ignore = { "4[23]1/__CAN_.*" }
return oldRunner(opts)
end
-- Patch formatter
local format = require("luacheck.format")
local function format_location(file, location, opts)
local res = ("%s:%d:%d"):format(file, location.can_line or location.line, location.can_column or location.column)
if opts.ranges then
res = ("%s-%d"):format(res, location.can_end_column or location.end_column)
end
return res
end
setupvalue(format.builtin_formatters.plain, format_location, "format_event", "format_location")
-- Fix some Luacheck messages and run
local path = util.search("luacheck.main", {"lua"})
if path then
local f = io.open(path, "r")
local code = f:read("*a")
f:close()
code = code:gsub(escape(" bug (please report at https://github.com/mpeterv/luacheck/issues)"), ", patched for Candran "..candran.VERSION.." bug. Please DO NOT report this bug to Luacheck") -- error text
:gsub(escape("\"luacheck\","), "\"cancheck\",") -- command name
:gsub("a linter and a static analyzer for Lua%.", "a linter and a static analyzer for Lua, patched for Candran "..candran.VERSION..".") -- help text
-- run
return load(code)()
else
io.stderr:write("can't find luacheck.main\n")
os.exit(1)
end