mirror of
				https://github.com/Reuh/candran.git
				synced 2025-10-27 17:59:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			214 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			214 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
 | |
| 	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
 |