diff options
Diffstat (limited to 'script/core/diagnostics.lua')
-rw-r--r-- | script/core/diagnostics.lua | 1082 |
1 files changed, 0 insertions, 1082 deletions
diff --git a/script/core/diagnostics.lua b/script/core/diagnostics.lua deleted file mode 100644 index 0ec98954..00000000 --- a/script/core/diagnostics.lua +++ /dev/null @@ -1,1082 +0,0 @@ -local lang = require 'language' -local config = require 'config' -local library = require 'core.library' -local buildGlobal = require 'vm.global' -local DiagnosticSeverity = require 'constant.DiagnosticSeverity' -local DiagnosticDefaultSeverity = require 'constant.DiagnosticDefaultSeverity' -local DiagnosticTag = require 'constant.DiagnosticTag' - -local mt = {} -mt.__index = mt - -local function isContainPos(obj, start, finish) - if obj.start <= start and obj.finish >= finish then - return true - end - return false -end - -function mt:searchUnusedLocals(callback) - self.vm:eachSource(function (source) - local loc = source:bindLocal() - if not loc then - return - end - if loc:get 'emmy arg' then - return - end - local name = loc:getName() - if name == '_' or name == '_ENV' or name == '' then - return - end - if source:action() ~= 'local' then - return - end - if loc:get 'hide' then - return - end - if loc.tags then - for _, tag in ipairs(loc.tags) do - if tag[1] == 'close' then - return - end - end - end - local used = loc:eachInfo(function (info) - if info.type == 'get' then - return true - end - end) - if not used then - callback(source.start, source.finish, name) - end - end) -end - -function mt:searchUnusedFunctions(callback) - self.vm:eachSource(function (source) - local loc = source:bindLocal() - if not loc then - return - end - if loc:get 'emmy arg' then - return - end - if source:action() ~= 'local' then - return - end - if loc:get 'hide' then - return - end - local used = loc:eachInfo(function (info) - if info.type == 'get' then - return true - end - end) - if used then - return - end - loc:eachInfo(function (info, src) - if info.type == 'set' or info.type == 'local' then - local v = src:bindValue() - local func = v and v:getFunction() - if func and func:getSource().uri == self.vm.uri then - callback(func:getSource().start, func:getSource().finish) - end - end - end) - end) -end - -function mt:searchUndefinedGlobal(callback) - local definedGlobal = {} - for name in pairs(config.config.diagnostics.globals) do - definedGlobal[name] = true - end - local envValue = buildGlobal(self.vm.lsp) - envValue:eachInfo(function (info) - if info.type == 'set child' then - local name = info[1] - definedGlobal[name] = true - end - end) - self.vm:eachSource(function (source) - if not source:get 'global' then - return - end - local name = source:getName() - if name == '' then - return - end - local parent = source:get 'parent' - if not parent then - return - end - if not parent:get 'ENV' and not source:get 'in index' then - return - end - if definedGlobal[name] then - return - end - if type(name) ~= 'string' then - return - end - callback(source.start, source.finish, name) - end) -end - -function mt:searchUnusedLabel(callback) - self.vm:eachSource(function (source) - local label = source:bindLabel() - if not label then - return - end - if source:action() ~= 'set' then - return - end - local used = label:eachInfo(function (info) - if info.type == 'get' then - return true - end - end) - if not used then - callback(source.start, source.finish, label:getName()) - end - end) -end - -function mt:searchUnusedVararg(callback) - self.vm:eachSource(function (source) - local value = source:bindFunction() - if not value then - return - end - local func = value:getFunction() - if not func then - return - end - if func._dotsSource and not func._dotsLoad then - callback(func._dotsSource.start, func._dotsSource.finish) - end - end) -end - -local function isInString(vm, start, finish) - return vm:eachSource(function (source) - if source.type == 'string' and isContainPos(source, start, finish) then - return true - end - end) -end - -function mt:searchSpaces(callback) - local vm = self.vm - local lines = self.lines - for i = 1, #lines do - local line = lines:line(i) - - if line:find '^[ \t]+$' then - local start, finish = lines:range(i) - if isInString(vm, start, finish) then - goto NEXT_LINE - end - callback(start, finish, lang.script.DIAG_LINE_ONLY_SPACE) - goto NEXT_LINE - end - - local pos = line:find '[ \t]+$' - if pos then - local start, finish = lines:range(i) - start = start + pos - 1 - if isInString(vm, start, finish) then - goto NEXT_LINE - end - callback(start, finish, lang.script.DIAG_LINE_POST_SPACE) - goto NEXT_LINE - end - - ::NEXT_LINE:: - end -end - -function mt:searchRedefinition(callback) - local used = {} - local uri = self.uri - self.vm:eachSource(function (source) - local loc = source:bindLocal() - if not loc then - return - end - local shadow = loc:shadow() - if not shadow then - return - end - if used[shadow] then - return - end - used[shadow] = true - if loc:get 'hide' then - return - end - local name = loc:getName() - if name == '_' or name == '_ENV' or name == '' then - return - end - local related = {} - for i = 1, #shadow do - related[i] = { - start = shadow[i]:getSource().start, - finish = shadow[i]:getSource().finish, - uri = uri, - } - end - for i = 2, #shadow do - callback(shadow[i]:getSource().start, shadow[i]:getSource().finish, name, related) - end - end) -end - -function mt:searchNewLineCall(callback) - local lines = self.lines - self.vm:eachSource(function (source) - if source.type ~= 'simple' then - return - end - for i = 1, #source - 1 do - local callSource = source[i] - local funcSource = source[i-1] - if callSource.type ~= 'call' then - goto CONTINUE - end - local callLine = lines:rowcol(callSource.start) - local funcLine = lines:rowcol(funcSource.finish) - if callLine > funcLine then - callback(callSource.start, callSource.finish) - end - :: CONTINUE :: - end - end) -end - -function mt:searchNewFieldCall(callback) - local lines = self.lines - self.vm:eachSource(function (source) - if source.type ~= 'table' then - return - end - for i = 1, #source do - local field = source[i] - if field.type == 'simple' then - local callSource = field[#field] - local funcSource = field[#field-1] - local callLine = lines:rowcol(callSource.start) - local funcLine = lines:rowcol(funcSource.finish) - if callLine > funcLine then - callback(funcSource.start, callSource.finish - , lines.buf:sub(funcSource.start, funcSource.finish) - , lines.buf:sub(callSource.start, callSource.finish) - ) - end - end - end - end) -end - -function mt:searchRedundantParameters(callback) - self.vm:eachSource(function (source) - local args = source:bindCall() - if not args then - return - end - - -- 回调函数不检查 - local simple = source:get 'simple' - if simple and simple[2] == source then - local loc = simple[1]:bindLocal() - if loc then - local source = loc:getSource() - if source:get 'arg' then - return - end - end - end - - local value = source:findCallFunction() - if not value then - return - end - - local func = value:getFunction() - -- 参数中有 ... ,不用再检查了 - if func:hasDots() then - return - end - local max = #func.args - local passed = #args - -- function m.open() end - -- m:open() - -- 这种写法不算错 - if passed == 1 and source:get 'has object' then - return - end - for i = max + 1, passed do - local extra = args[i] - callback(extra.start, extra.finish, max, passed) - end - end) -end - -local opMap = { - ['+'] = true, - ['-'] = true, - ['*'] = true, - ['/'] = true, - ['//'] = true, - ['^'] = true, - ['<<'] = true, - ['>>'] = true, - ['&'] = true, - ['|'] = true, - ['~'] = true, - ['..'] = true, -} - -local literalMap = { - ['number'] = true, - ['boolean'] = true, - ['string'] = true, - ['table'] = true, -} - -function mt:searchAmbiguity1(callback) - self.vm:eachSource(function (source) - if source.op ~= 'or' then - return - end - local first = source[1] - local second = source[2] - -- a + (b or 0) --> (a + b) or 0 - do - if opMap[first.op] - and first.type ~= 'unary' - and not second.op - and literalMap[second.type] - and not first.brackets - then - callback(source.start, source.finish, first.start, first.finish) - end - end - -- (a or 0) + c --> a or (0 + c) - do - if opMap[second.op] - and second.type ~= 'unary' - and not first.op - and literalMap[second[1].type] - and not second.brackets - then - callback(source.start, source.finish, second.start, second.finish) - end - end - end) -end - -function mt:searchLowercaseGlobal(callback) - local definedGlobal = {} - for name in pairs(config.config.diagnostics.globals) do - definedGlobal[name] = true - end - for name in pairs(library.global) do - definedGlobal[name] = true - end - self.vm:eachSource(function (source) - if source.type == 'name' - and source:get 'parent' - and not source:get 'simple' - and not source:get 'table index' - and source:action() == 'set' - then - local name = source[1] - if definedGlobal[name] then - return - end - local first = name:match '%w' - if not first then - return - end - if first:match '%l' then - callback(source.start, source.finish) - end - end - end) -end - -function mt:searchDuplicateIndex(callback) - self.vm:eachSource(function (source) - if source.type ~= 'table' then - return - end - local mark = {} - for _, obj in ipairs(source) do - if obj.type == 'pair' then - local key = obj[1] - local name - if key.type == 'index' then - if key[1].type == 'string' then - name = key[1][1] - end - elseif key.type == 'name' then - name = key[1] - end - if name then - if mark[name] then - mark[name][#mark[name]+1] = obj - else - mark[name] = { obj } - end - end - end - end - for name, defs in pairs(mark) do - if #defs > 1 then - local related = {} - for i = 1, #defs do - related[i] = { - start = defs[i][1].start, - finish = defs[i][2].finish, - uri = self.uri, - } - end - for i = 1, #defs - 1 do - callback(defs[i][1].start, defs[i][2].finish, name, related, 'unused') - end - for i = #defs, #defs do - callback(defs[i][1].start, defs[i][1].finish, name, related, 'duplicate') - end - end - end - end) -end - -function mt:searchDuplicateMethod(callback) - local uri = self.uri - local mark = {} - local map = {} - self.vm:eachSource(function (source) - local parent = source:get 'parent' - if not parent then - return - end - if mark[parent] then - return - end - mark[parent] = true - local relates = {} - parent:eachInfo(function (info, src) - local k = info[1] - if info.type ~= 'set child' then - return - end - if type(k) ~= 'string' then - return - end - if src.start == 0 then - return - end - if not src:get 'object' then - return - end - if map[src] then - return - end - if not relates[k] then - relates[k] = map[src] or { - name = k, - } - end - map[src] = relates[k] - relates[k][#relates[k]+1] = { - start = src.start, - finish = src.finish, - uri = src.uri - } - end) - end) - for src, relate in pairs(map) do - if #relate > 1 and src.uri == uri then - callback(src.start, src.finish, relate.name, relate) - end - end -end - -function mt:searchEmptyBlock(callback) - self.vm:eachSource(function (source) - -- 认为空repeat与空while是合法的 - -- 要去vm中激活source - if source.type == 'if' then - for _, block in ipairs(source) do - if #block > 0 then - return - end - end - callback(source.start, source.finish) - return - end - if source.type == 'loop' - or source.type == 'in' - then - if #source == 0 then - callback(source.start, source.finish) - end - return - end - end) -end - -function mt:searchRedundantValue(callback) - self.vm:eachSource(function (source) - if source.type == 'set' or source.type == 'local' then - local args = source[1] - local values = source[2] - if not source[2] then - return - end - local argCount, valueCount - if args.type == 'list' then - argCount = #args - else - argCount = 1 - end - if values.type == 'list' then - valueCount = #values - else - valueCount = 1 - end - for i = argCount + 1, valueCount do - local value = values[i] - callback(value.start, value.finish, argCount, valueCount) - end - end - end) -end - -function mt:searchUndefinedEnvChild(callback) - self.vm:eachSource(function (source) - if not source:get 'global' then - return - end - local name = source:getName() - if name == '' then - return - end - if source:get 'in index' then - return - end - local parent = source:get 'parent' - if parent:get 'ENV' then - return - end - local value = source:bindValue() - if not value then - return - end - if value:getSource() == source then - callback(source.start, source.finish, name) - end - return - end) -end - -function mt:searchGlobalInNilEnv(callback) - self.vm:eachSource(function (source) - if not source:get 'global' then - return - end - local name = source:getName() - if name == '' then - return - end - local parentSource = source:get 'parent' :getSource() - if parentSource and parentSource.type == 'nil' then - callback(source.start, source.finish, { - { - start = parentSource.start, - finish = parentSource.finish, - uri = self.uri, - } - }) - end - return - end) -end - -function mt:checkEmmyClass(source, callback) - local class = source:get 'emmy.class' - if not class then - return - end - -- class重复定义 - local name = class:getName() - local related = {} - self.vm.emmyMgr:eachClass(name, function (class) - if class.type ~= 'emmy.class' and class.type ~= 'emmy.alias' then - return - end - local src = class:getSource() - if src ~= source then - related[#related+1] = { - start = src.start, - finish = src.finish, - uri = src.uri, - } - end - end) - if #related > 0 then - callback(source.start, source.finish, lang.script.DIAG_DUPLICATE_CLASS ,related) - end - -- 继承不存在的class - local extends = class.extends - if not extends then - return - end - local parent = self.vm.emmyMgr:eachClass(extends, function (parent) - if parent.type == 'emmy.class' then - return parent - end - end) - if not parent then - callback(source[2].start, source[2].finish, lang.script.DIAG_UNDEFINED_CLASS) - return - end - - -- class循环继承 - local related = {} - local current = class - for _ = 1, 10 do - local extends = current.extends - if not extends then - break - end - related[#related+1] = { - start = current:getSource().start, - finish = current:getSource().finish, - uri = current:getSource().uri, - } - current = self.vm.emmyMgr:eachClass(extends, function (parent) - if parent.type == 'emmy.class' then - return parent - end - end) - if not current then - break - end - if current:getName() == class:getName() then - callback(source.start, source.finish, lang.script.DIAG_CYCLIC_EXTENDS, related) - break - end - end -end - -function mt:checkEmmyType(source, callback) - for _, tpsource in ipairs(source) do - -- TODO 临时决绝办法,重构后解决 - local name - if tpsource.type == 'emmyArrayType' then - name = tpsource[1][1] - else - name = tpsource[1] - end - local class = self.vm.emmyMgr:eachClass(name, function (class) - if class.type == 'emmy.class' or class.type == 'emmy.alias' then - return class - end - end) - if not class then - callback(tpsource.start, tpsource.finish, lang.script.DIAG_UNDEFINED_CLASS) - end - end -end - -function mt:checkEmmyAlias(source, callback) - local class = source:get 'emmy.alias' - if not class then - return - end - -- class重复定义 - local name = class:getName() - local related = {} - self.vm.emmyMgr:eachClass(name, function (class) - if class.type ~= 'emmy.class' and class.type ~= 'emmy.alias' then - return - end - local src = class:getSource() - if src ~= source then - related[#related+1] = { - start = src.start, - finish = src.finish, - uri = src.uri, - } - end - end) - if #related > 0 then - callback(source.start, source.finish, lang.script.DIAG_DUPLICATE_CLASS ,related) - end -end - -function mt:checkEmmyParam(source, callback, mark) - local func = source:get 'emmy function' - if not func then - return - end - if mark[func] then - return - end - mark[func] = true - - -- 检查不存在的参数 - local emmyParams = func:getEmmyParams() - local funcParams = {} - if func.args then - for _, arg in ipairs(func.args) do - funcParams[arg.name] = true - end - end - for _, param in ipairs(emmyParams) do - local name = param:getName() - if not funcParams[name] then - callback(param:getSource()[1].start, param:getSource()[1].finish, lang.script.DIAG_INEXISTENT_PARAM) - end - end - - -- 检查重复的param - local lists = {} - for _, param in ipairs(emmyParams) do - local name = param:getName() - if not lists[name] then - lists[name] = {} - end - lists[name][#lists[name]+1] = param:getSource()[1] - end - for _, list in pairs(lists) do - if #list > 1 then - local related = {} - for _, src in ipairs(list) do - related[#related+1] = { - src.start, - src.finish, - src.uri, - } - callback(src.start, src.finish, lang.script.DIAG_DUPLICATE_PARAM) - end - end - end -end - -function mt:checkEmmyField(source, callback, mark) - ---@type EmmyClass - local class = source:get 'target class' - -- 必须写在 class 的后面 - if not class then - callback(source.start, source.finish, lang.script.DIAG_NEED_CLASS) - end - - -- 检查重复的 field - if class and not mark[class] then - mark[class] = true - local lists = {} - class:eachField(function (field) - local name = field:getName() - if not lists[name] then - lists[name] = {} - end - lists[name][#lists[name]+1] = field:getSource()[2] - end) - for _, list in pairs(lists) do - if #list > 1 then - local related = {} - for _, src in ipairs(list) do - related[#related+1] = { - src.start, - src.finish, - src.uri, - } - callback(src.start, src.finish, lang.script.DIAG_DUPLICATE_FIELD) - end - end - end - end -end - -function mt:searchEmmyLua(callback) - local mark = {} - self.vm:eachSource(function (source) - if source.type == 'emmyClass' then - self:checkEmmyClass(source, callback) - elseif source.type == 'emmyType' then - self:checkEmmyType(source, callback) - elseif source.type == 'emmyAlias' then - self:checkEmmyAlias(source, callback) - elseif source.type == 'emmyParam' then - self:checkEmmyParam(source, callback, mark) - elseif source.type == 'emmyField' then - self:checkEmmyField(source, callback, mark) - end - end) -end - -function mt:searchSetConstLocal(callback) - local mark = {} - self.vm:eachSource(function (source) - local loc = source:bindLocal() - if not loc then - return - end - if mark[loc] then - return - end - mark[loc] = true - if not loc.tags then - return - end - local const - for _, tag in ipairs(loc.tags) do - if tag[1] == 'const' then - const = true - break - end - end - if not const then - return - end - loc:eachInfo(function (info, src) - if info.type == 'set' then - callback(src.start, src.finish) - end - end) - end) -end - -function mt:searchSetForState(callback) - local locs = {} - self.vm:eachSource(function (source) - if source.type == 'loop' then - locs[#locs+1] = source.arg:bindLocal() - elseif source.type == 'in' then - --self.vm:forList(source.arg, function (arg) - -- locs[#locs+1] = arg:bindLocal() - --end) - end - end) - for i = 1, #locs do - local loc = locs[i] - loc:eachInfo(function (info, src) - if info.type == 'set' then - callback(src.start, src.finish) - end - end) - end -end - -function mt:doDiagnostics(func, code, callback) - if config.config.diagnostics.disable[code] then - return - end - local level = config.config.diagnostics.severity[code] - if not DiagnosticSeverity[level] then - level = DiagnosticDefaultSeverity[code] - end - func(self, function (start, finish, ...) - local data = callback(...) - data.code = code - data.start = start - data.finish = finish - data.level = data.level or DiagnosticSeverity[level] - self.datas[#self.datas+1] = data - end) - if coroutine.isyieldable() then - if self.vm:isRemoved() then - coroutine.yield('stop') - else - coroutine.yield() - end - end -end - -return function (vm, lines, uri) - local session = setmetatable({ - vm = vm, - lines = lines, - uri = uri, - datas = {}, - }, mt) - - -- 未使用的局部变量 - session:doDiagnostics(session.searchUnusedLocals, 'unused-local', function (key) - return { - message = lang.script('DIAG_UNUSED_LOCAL', key), - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- 未使用的函数 - session:doDiagnostics(session.searchUnusedFunctions, 'unused-function', function () - return { - message = lang.script.DIAG_UNUSED_FUNCTION, - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- 读取未定义全局变量 - session:doDiagnostics(session.searchUndefinedGlobal, 'undefined-global', function (key) - local message = lang.script('DIAG_UNDEF_GLOBAL', key) - local otherVersion = library.other[key] - local customLib = library.custom[key] - if otherVersion then - message = ('%s(%s)'):format(message, lang.script('DIAG_DEFINED_VERSION', table.concat(otherVersion, '/'), config.config.runtime.version)) - end - if customLib then - message = ('%s(%s)'):format(message, lang.script('DIAG_DEFINED_CUSTOM', table.concat(customLib, '/'))) - end - return { - message = message, - } - end) - -- 未使用的Label - session:doDiagnostics(session.searchUnusedLabel, 'unused-label', function (key) - return { - message = lang.script('DIAG_UNUSED_LABEL', key), - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- 未使用的不定参数 - session:doDiagnostics(session.searchUnusedVararg, 'unused-vararg', function () - return { - message = lang.script.DIAG_UNUSED_VARARG, - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- 只有空格与制表符的行,以及后置空格 - session:doDiagnostics(session.searchSpaces, 'trailing-space', function (message) - return { - message = message, - } - end) - -- 重定义局部变量 - session:doDiagnostics(session.searchRedefinition, 'redefined-local', function (key, related) - return { - message = lang.script('DIAG_REDEFINED_LOCAL', key), - related = related, - } - end) - -- 以括号开始的一行(可能被误解析为了上一行的call) - session:doDiagnostics(session.searchNewLineCall, 'newline-call', function () - return { - message = lang.script.DIAG_PREVIOUS_CALL, - } - end) - -- 以字符串开始的field(可能被误解析为了上一行的call) - session:doDiagnostics(session.searchNewFieldCall, 'newfield-call', function (func, call) - return { - message = lang.script('DIAG_PREFIELD_CALL', func, call), - } - end) - -- 调用函数时的参数数量是否超过函数的接收数量 - session:doDiagnostics(session.searchRedundantParameters, 'redundant-parameter', function (max, passed) - return { - message = lang.script('DIAG_OVER_MAX_ARGS', max, passed), - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- x or 0 + 1 - session:doDiagnostics(session.searchAmbiguity1, 'ambiguity-1', function (start, finish) - return { - message = lang.script('DIAG_AMBIGUITY_1', lines.buf:sub(start, finish)), - } - end) - -- 不允许定义首字母小写的全局变量(很可能是拼错或者漏删) - session:doDiagnostics(session.searchLowercaseGlobal, 'lowercase-global', function () - return { - message = lang.script.DIAG_LOWERCASE_GLOBAL, - } - end) - -- 未定义的变量(重载了 `_ENV`) - session:doDiagnostics(session.searchUndefinedEnvChild, 'undefined-env-child', function (key) - if vm.envType == '_ENV' then - return { - message = lang.script('DIAG_UNDEF_ENV_CHILD', key), - } - else - return { - message = lang.script('DIAG_UNDEF_FENV_CHILD', key), - } - end - end) - -- 全局变量不可用(置空了 `_ENV`) - session:doDiagnostics(session.searchGlobalInNilEnv, 'global-in-nil-env', function (related) - if vm.envType == '_ENV' then - return { - message = lang.script.DIAG_GLOBAL_IN_NIL_ENV, - related = related, - } - else - return { - message = lang.script.DIAG_GLOBAL_IN_NIL_FENV, - related = related, - } - end - end) - -- 构建表时重复定义field - session:doDiagnostics(session.searchDuplicateIndex, 'duplicate-index', function (key, related, type) - if type == 'unused' then - return { - message = lang.script('DIAG_DUPLICATE_INDEX', key), - related = related, - level = DiagnosticSeverity.Hint, - tags = {DiagnosticTag.Unnecessary}, - } - else - return { - message = lang.script('DIAG_DUPLICATE_INDEX', key), - related = related, - } - end - end) - -- 往表里面塞重复的method - --session:doDiagnostics(session.searchDuplicateMethod, 'duplicate-method', function (key, related) - -- return { - -- message = lang.script('DIAG_DUPLICATE_METHOD', key), - -- related = related, - -- } - --end) - -- 空代码块 - session:doDiagnostics(session.searchEmptyBlock, 'empty-block', function () - return { - message = lang.script.DIAG_EMPTY_BLOCK, - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- 多余的赋值 - session:doDiagnostics(session.searchRedundantValue, 'redundant-value', function (max, passed) - return { - message = lang.script('DIAG_OVER_MAX_VALUES', max, passed), - tags = {DiagnosticTag.Unnecessary}, - } - end) - -- Emmy相关的检查 - session:doDiagnostics(session.searchEmmyLua, 'emmy-lua', function (message, related) - return { - message = message, - related = related, - } - end) - -- 检查给const变量赋值 - session:doDiagnostics(session.searchSetConstLocal, 'set-const', function () - return { - message = lang.script.DIAG_SET_CONST - } - end) - -- 检查修改for的内置变量 - session:doDiagnostics(session.searchSetForState, 'set-for-state', function () - return { - message = lang.script.DIAG_SET_FOR_STATE, - } - end) - return session.datas -end |