diff options
author | 最萌小汐 <sumneko@hotmail.com> | 2019-11-22 23:26:32 +0800 |
---|---|---|
committer | 最萌小汐 <sumneko@hotmail.com> | 2019-11-22 23:26:32 +0800 |
commit | d0ff66c9abe9d6abbca12fd811e0c3cb69c1033a (patch) | |
tree | bb34518d70b85de7656dbdbe958dfa221a3ff3b3 /script-beta/src/core/diagnostics | |
parent | 0a2c2ad15e1ec359171fb0dd4c72e57c5b66e9ba (diff) | |
download | lua-language-server-d0ff66c9abe9d6abbca12fd811e0c3cb69c1033a.zip |
整理一下目录结构
Diffstat (limited to 'script-beta/src/core/diagnostics')
19 files changed, 856 insertions, 0 deletions
diff --git a/script-beta/src/core/diagnostics/ambiguity-1.lua b/script-beta/src/core/diagnostics/ambiguity-1.lua new file mode 100644 index 00000000..37815fb5 --- /dev/null +++ b/script-beta/src/core/diagnostics/ambiguity-1.lua @@ -0,0 +1,69 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' + +local opMap = { + ['+'] = true, + ['-'] = true, + ['*'] = true, + ['/'] = true, + ['//'] = true, + ['^'] = true, + ['<<'] = true, + ['>>'] = true, + ['&'] = true, + ['|'] = true, + ['~'] = true, + ['..'] = true, +} + +local literalMap = { + ['number'] = true, + ['boolean'] = true, + ['string'] = true, + ['table'] = true, +} + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + local text = files.getText(uri) + guide.eachSourceType(ast.ast, 'binary', function (source) + if source.op.type ~= '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.op.type] + and first.type ~= 'unary' + and not second.op + and literalMap[second.type] + and not literalMap[first[2].type] + then + callback { + start = source.start, + finish = source.finish, + message = lang.script('DIAG_AMBIGUITY_1', text:sub(first.start, first.finish)) + } + end + end + -- (a or 0) + c --> a or (0 + c) + do + if opMap[second.op and second.op.type] + and second.type ~= 'unary' + and not first.op + and literalMap[second[1].type] + then + callback { + start = source.start, + finish = source.finish, + message = lang.script('DIAG_AMBIGUITY_1', text:sub(second.start, second.finish)) + } + end + end + end) +end diff --git a/script-beta/src/core/diagnostics/duplicate-index.lua b/script-beta/src/core/diagnostics/duplicate-index.lua new file mode 100644 index 00000000..76b1c958 --- /dev/null +++ b/script-beta/src/core/diagnostics/duplicate-index.lua @@ -0,0 +1,62 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local define = require 'proto.define' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + guide.eachSourceType(ast.ast, 'table', function (source) + local mark = {} + for _, obj in ipairs(source) do + if obj.type == 'tablefield' + or obj.type == 'tableindex' then + local name = guide.getKeyName(obj) + if name then + if not mark[name] then + mark[name] = {} + end + mark[name][#mark[name]+1] = obj.field or obj.index + end + end + end + + for name, defs in pairs(mark) do + local sname = name:match '^.|(.+)$' + if #defs > 1 and sname then + local related = {} + for i = 1, #defs do + local def = defs[i] + related[i] = { + start = def.start, + finish = def.finish, + uri = uri, + } + end + for i = 1, #defs - 1 do + local def = defs[i] + callback { + start = def.start, + finish = def.finish, + related = related, + message = lang.script('DIAG_DUPLICATE_INDEX', sname), + level = define.DiagnosticSeverity.Hint, + tags = { define.DiagnosticTag.Unnecessary }, + } + end + for i = #defs, #defs do + local def = defs[i] + callback { + start = def.start, + finish = def.finish, + related = related, + message = lang.script('DIAG_DUPLICATE_INDEX', sname), + } + end + end + end + end) +end diff --git a/script-beta/src/core/diagnostics/emmy-lua.lua b/script-beta/src/core/diagnostics/emmy-lua.lua new file mode 100644 index 00000000..b3d19c21 --- /dev/null +++ b/script-beta/src/core/diagnostics/emmy-lua.lua @@ -0,0 +1,3 @@ +return function () + +end diff --git a/script-beta/src/core/diagnostics/empty-block.lua b/script-beta/src/core/diagnostics/empty-block.lua new file mode 100644 index 00000000..2024f4e3 --- /dev/null +++ b/script-beta/src/core/diagnostics/empty-block.lua @@ -0,0 +1,49 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local define = require 'proto.define' + +-- 检查空代码块 +-- 但是排除忙等待(repeat/while) +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + guide.eachSourceType(ast.ast, 'if', function (source) + for _, block in ipairs(source) do + if #block > 0 then + return + end + end + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_EMPTY_BLOCK, + } + end) + guide.eachSourceType(ast.ast, 'loop', function (source) + if #source > 0 then + return + end + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_EMPTY_BLOCK, + } + end) + guide.eachSourceType(ast.ast, 'in', function (source) + if #source > 0 then + return + end + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_EMPTY_BLOCK, + } + end) +end diff --git a/script-beta/src/core/diagnostics/global-in-nil-env.lua b/script-beta/src/core/diagnostics/global-in-nil-env.lua new file mode 100644 index 00000000..9a0d4f35 --- /dev/null +++ b/script-beta/src/core/diagnostics/global-in-nil-env.lua @@ -0,0 +1,66 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' + +-- TODO: 检查路径是否可达 +local function mayRun(path) + return true +end + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + local root = guide.getRoot(ast.ast) + local env = guide.getENV(root) + + local nilDefs = {} + if not env.ref then + return + end + for _, ref in ipairs(env.ref) do + if ref.type == 'setlocal' then + if ref.value and ref.value.type == 'nil' then + nilDefs[#nilDefs+1] = ref + end + end + end + + if #nilDefs == 0 then + return + end + + local function check(source) + local node = source.node + if node.tag == '_ENV' then + local ok + for _, nilDef in ipairs(nilDefs) do + local mode, pathA = guide.getPath(nilDef, source) + if mode == 'before' + and mayRun(pathA) then + ok = nilDef + break + end + end + if ok then + callback { + start = source.start, + finish = source.finish, + uri = uri, + message = lang.script.DIAG_GLOBAL_IN_NIL_ENV, + related = { + { + start = ok.start, + finish = ok.finish, + uri = uri, + } + } + } + end + end + end + + guide.eachSourceType(ast.ast, 'getglobal', check) + guide.eachSourceType(ast.ast, 'setglobal', check) +end diff --git a/script-beta/src/core/diagnostics/init.lua b/script-beta/src/core/diagnostics/init.lua new file mode 100644 index 00000000..0d523f26 --- /dev/null +++ b/script-beta/src/core/diagnostics/init.lua @@ -0,0 +1,41 @@ +local files = require 'files' +local define = require 'proto.define' +local config = require 'config' +local await = require 'await' + +local function check(uri, name, level, results) + if config.config.diagnostics.disable[name] then + return + end + level = config.config.diagnostics.severity[name] or level + local severity = define.DiagnosticSeverity[level] + local clock = os.clock() + require('core.diagnostics.' .. name)(uri, function (result) + result.level = severity or result.level + result.code = name + results[#results+1] = result + end, name) + local passed = os.clock() - clock + if passed >= 0.5 then + log.warn(('Diagnostics [%s] @ [%s] takes [%.3f] sec!'):format(name, uri, passed)) + await.delay() + end +end + +return function (uri) + local ast = files.getAst(uri) + if not ast then + return nil + end + local results = {} + + for name, level in pairs(define.DiagnosticDefaultSeverity) do + check(uri, name, level, results) + end + + if #results == 0 then + return nil + end + + return results +end diff --git a/script-beta/src/core/diagnostics/lowercase-global.lua b/script-beta/src/core/diagnostics/lowercase-global.lua new file mode 100644 index 00000000..bc48e1e6 --- /dev/null +++ b/script-beta/src/core/diagnostics/lowercase-global.lua @@ -0,0 +1,39 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local config = require 'config' +local library = require 'library' + +-- 不允许定义首字母小写的全局变量(很可能是拼错或者漏删) +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + 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 + + guide.eachSourceType(ast.ast, 'setglobal', function (source) + local name = guide.getName(source) + if definedGlobal[name] then + return + end + local first = name:match '%w' + if not first then + return + end + if first:match '%l' then + callback { + start = source.start, + finish = source.finish, + message = lang.script.DIAG_LOWERCASE_GLOBAL, + } + end + end) +end diff --git a/script-beta/src/core/diagnostics/newfield-call.lua b/script-beta/src/core/diagnostics/newfield-call.lua new file mode 100644 index 00000000..75681cbc --- /dev/null +++ b/script-beta/src/core/diagnostics/newfield-call.lua @@ -0,0 +1,37 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + local lines = files.getLines(uri) + local text = files.getText(uri) + + guide.eachSourceType(ast.ast, 'table', function (source) + for i = 1, #source do + local field = source[i] + if field.type == 'call' then + local func = field.node + local args = field.args + if args then + local funcLine = guide.positionOf(lines, func.finish) + local argsLine = guide.positionOf(lines, args.start) + if argsLine > funcLine then + callback { + start = field.start, + finish = field.finish, + message = lang.script('DIAG_PREFIELD_CALL' + , text:sub(func.start, func.finish) + , text:sub(args.start, args.finish) + ) + } + end + end + end + end + end) +end diff --git a/script-beta/src/core/diagnostics/newline-call.lua b/script-beta/src/core/diagnostics/newline-call.lua new file mode 100644 index 00000000..cb318380 --- /dev/null +++ b/script-beta/src/core/diagnostics/newline-call.lua @@ -0,0 +1,38 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + local lines = files.getLines(uri) + + guide.eachSourceType(ast.ast, 'call', function (source) + local node = source.node + local args = source.args + if not args then + return + end + + -- 必须有其他人在继续使用当前对象 + if not source.next then + return + end + + local nodeRow = guide.positionOf(lines, node.finish) + local argRow = guide.positionOf(lines, args.start) + if nodeRow == argRow then + return + end + + if #args == 1 then + callback { + start = args.start, + finish = args.finish, + message = lang.script.DIAG_PREVIOUS_CALL, + } + end + end) +end diff --git a/script-beta/src/core/diagnostics/redefined-local.lua b/script-beta/src/core/diagnostics/redefined-local.lua new file mode 100644 index 00000000..f6176794 --- /dev/null +++ b/script-beta/src/core/diagnostics/redefined-local.lua @@ -0,0 +1,32 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + guide.eachSourceType(ast.ast, 'local', function (source) + local name = source[1] + if name == '_' + or name == '_ENV' then + return + end + local exist = guide.getLocal(source, name, source.start-1) + if exist then + callback { + start = source.start, + finish = source.finish, + message = lang.script('DIAG_REDEFINED_LOCAL', name), + related = { + { + start = exist.start, + finish = exist.finish, + uri = uri, + } + }, + } + end + end) +end diff --git a/script-beta/src/core/diagnostics/redundant-parameter.lua b/script-beta/src/core/diagnostics/redundant-parameter.lua new file mode 100644 index 00000000..ec14188e --- /dev/null +++ b/script-beta/src/core/diagnostics/redundant-parameter.lua @@ -0,0 +1,102 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local lang = require 'language' +local define = require 'proto.define' +local await = require 'await' + +local function countLibraryArgs(source) + local func = vm.getLibrary(source) + if not func then + return nil + end + local result = 0 + if not func.args then + return result + end + if func.args[#func.args].type == '...' then + return math.maxinteger + end + result = result + #func.args + return result +end + +local function countCallArgs(source) + local result = 0 + if not source.args then + return 0 + end + if source.node and source.node.type == 'getmethod' then + result = result + 1 + end + result = result + #source.args + return result +end + +local function countFuncArgs(source) + local result = 0 + if not source.args then + return result + end + if source.args[#source.args].type == '...' then + return math.maxinteger + end + if source.parent and source.parent.type == 'setmethod' then + result = result + 1 + end + result = result + #source.args + return result +end + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + guide.eachSourceType(ast.ast, 'call', function (source) + local callArgs = countCallArgs(source) + if callArgs == 0 then + return + end + + await.delay(function () + return files.globalVersion + end) + + local func = source.node + local funcArgs + vm.eachDef(func, function (info) + if info.mode == 'value' then + local src = info.source + if src.type == 'function' then + local args = countFuncArgs(src) + if not funcArgs or args > funcArgs then + funcArgs = args + end + end + end + end) + + funcArgs = funcArgs or countLibraryArgs(func) + if not funcArgs then + return + end + + local delta = callArgs - funcArgs + if delta <= 0 then + return + end + for i = #source.args - delta + 1, #source.args do + local arg = source.args[i] + if arg then + callback { + start = arg.start, + finish = arg.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script('DIAG_OVER_MAX_ARGS', funcArgs, callArgs) + } + end + end + end) +end diff --git a/script-beta/src/core/diagnostics/redundant-value.lua b/script-beta/src/core/diagnostics/redundant-value.lua new file mode 100644 index 00000000..be483448 --- /dev/null +++ b/script-beta/src/core/diagnostics/redundant-value.lua @@ -0,0 +1,24 @@ +local files = require 'files' +local define = require 'proto.define' +local lang = require 'language' + +return function (uri, callback, code) + local ast = files.getAst(uri) + if not ast then + return + end + + local diags = ast.diags[code] + if not diags then + return + end + + for _, info in ipairs(diags) do + callback { + start = info.start, + finish = info.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script('DIAG_OVER_MAX_VALUES', info.max, info.passed) + } + end +end diff --git a/script-beta/src/core/diagnostics/trailing-space.lua b/script-beta/src/core/diagnostics/trailing-space.lua new file mode 100644 index 00000000..e54a6e60 --- /dev/null +++ b/script-beta/src/core/diagnostics/trailing-space.lua @@ -0,0 +1,55 @@ +local files = require 'files' +local lang = require 'language' +local guide = require 'parser.guide' + +local function isInString(ast, offset) + local result = false + guide.eachSourceType(ast, 'string', function (source) + if offset >= source.start and offset <= source.finish then + result = true + end + end) + return result +end + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + local text = files.getText(uri) + local lines = files.getLines(uri) + for i = 1, #lines do + local start = lines[i].start + local range = lines[i].range + local lastChar = text:sub(range, range) + if lastChar ~= ' ' and lastChar ~= '\t' then + goto NEXT_LINE + end + if isInString(ast.ast, range) then + goto NEXT_LINE + end + local first = start + for n = range - 1, start, -1 do + local char = text:sub(n, n) + if char ~= ' ' and char ~= '\t' then + first = n + 1 + break + end + end + if first == start then + callback { + start = first, + finish = range, + message = lang.script.DIAG_LINE_ONLY_SPACE, + } + else + callback { + start = first, + finish = range, + message = lang.script.DIAG_LINE_POST_SPACE, + } + end + ::NEXT_LINE:: + end +end diff --git a/script-beta/src/core/diagnostics/undefined-env-child.lua b/script-beta/src/core/diagnostics/undefined-env-child.lua new file mode 100644 index 00000000..df096cb8 --- /dev/null +++ b/script-beta/src/core/diagnostics/undefined-env-child.lua @@ -0,0 +1,32 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local lang = require 'language' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + -- 再遍历一次 getglobal ,找出 _ENV 被重载的情况 + guide.eachSourceType(ast.ast, 'getglobal', function (source) + -- 单独验证自己是否在重载过的 _ENV 中有定义 + if source.node.tag == '_ENV' then + return + end + local setInENV = vm.eachRef(source, function (info) + if info.mode == 'set' then + return true + end + end) + if setInENV then + return + end + local key = source[1] + callback { + start = source.start, + finish = source.finish, + message = lang.script('DIAG_UNDEF_ENV_CHILD', key), + } + end) +end diff --git a/script-beta/src/core/diagnostics/undefined-global.lua b/script-beta/src/core/diagnostics/undefined-global.lua new file mode 100644 index 00000000..ed81ced3 --- /dev/null +++ b/script-beta/src/core/diagnostics/undefined-global.lua @@ -0,0 +1,63 @@ +local files = require 'files' +local vm = require 'vm' +local lang = require 'language' +local library = require 'library' +local config = require 'config' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + local globalCache = {} + + -- 遍历全局变量,检查所有没有 mode['set'] 的全局变量 + local globals = vm.getGlobals(ast.ast) + for key, infos in pairs(globals) do + if infos.mode['set'] == true then + goto CONTINUE + end + if globalCache[key] then + goto CONTINUE + end + local skey = key and key:match '^s|(.+)$' + if not skey then + goto CONTINUE + end + if library.global[skey] then + goto CONTINUE + end + if config.config.diagnostics.globals[skey] then + goto CONTINUE + end + if globalCache[key] == nil then + local uris = files.findGlobals(key) + for i = 1, #uris do + local destAst = files.getAst(uris[i]) + local destGlobals = vm.getGlobals(destAst.ast) + if destGlobals[key] and destGlobals[key].mode['set'] then + globalCache[key] = true + goto CONTINUE + end + end + end + globalCache[key] = false + local message = lang.script('DIAG_UNDEF_GLOBAL', skey) + local otherVersion = library.other[skey] + local customVersion = library.custom[skey] + if otherVersion then + message = ('%s(%s)'):format(message, lang.script('DIAG_DEFINED_VERSION', table.concat(otherVersion, '/'), config.config.runtime.version)) + elseif customVersion then + message = ('%s(%s)'):format(message, lang.script('DIAG_DEFINED_CUSTOM', table.concat(customVersion, '/'))) + end + for _, info in ipairs(infos) do + callback { + start = info.source.start, + finish = info.source.finish, + message = message, + } + end + ::CONTINUE:: + end +end diff --git a/script-beta/src/core/diagnostics/unused-function.lua b/script-beta/src/core/diagnostics/unused-function.lua new file mode 100644 index 00000000..6c53cdf7 --- /dev/null +++ b/script-beta/src/core/diagnostics/unused-function.lua @@ -0,0 +1,45 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local define = require 'proto.define' +local lang = require 'language' +local await = require 'await' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + -- 只检查局部函数与全局函数 + guide.eachSourceType(ast.ast, 'function', function (source) + local parent = source.parent + if not parent then + return + end + if parent.type ~= 'local' + and parent.type ~= 'setlocal' + and parent.type ~= 'setglobal' then + return + end + local hasSet + local hasGet = vm.eachRef(source, function (info) + if info.mode == 'get' then + return true + elseif info.mode == 'set' + or info.mode == 'declare' then + hasSet = true + end + end) + if not hasGet and hasSet then + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_UNUSED_FUNCTION, + } + end + await.delay(function () + return files.globalVersion + end) + end) +end diff --git a/script-beta/src/core/diagnostics/unused-label.lua b/script-beta/src/core/diagnostics/unused-label.lua new file mode 100644 index 00000000..e6d998ba --- /dev/null +++ b/script-beta/src/core/diagnostics/unused-label.lua @@ -0,0 +1,22 @@ +local files = require 'files' +local guide = require 'parser.guide' +local define = require 'proto.define' +local lang = require 'language' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + guide.eachSourceType(ast.ast, 'label', function (source) + if not source.ref then + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script('DIAG_UNUSED_LABEL', source[1]), + } + end + end) +end diff --git a/script-beta/src/core/diagnostics/unused-local.lua b/script-beta/src/core/diagnostics/unused-local.lua new file mode 100644 index 00000000..22b2e16b --- /dev/null +++ b/script-beta/src/core/diagnostics/unused-local.lua @@ -0,0 +1,46 @@ +local files = require 'files' +local guide = require 'parser.guide' +local define = require 'proto.define' +local lang = require 'language' + +local function hasGet(loc) + if not loc.ref then + return false + end + for _, ref in ipairs(loc.ref) do + if ref.type == 'getlocal' then + if not ref.next then + return true + end + local nextType = ref.next.type + if nextType ~= 'setmethod' + and nextType ~= 'setfield' + and nextType ~= 'setindex' then + return true + end + end + end + return false +end + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + guide.eachSourceType(ast.ast, 'local', function (source) + local name = source[1] + if name == '_' + or name == '_ENV' then + return + end + if not hasGet(source) then + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script('DIAG_UNUSED_LOCAL', name), + } + end + end) +end diff --git a/script-beta/src/core/diagnostics/unused-vararg.lua b/script-beta/src/core/diagnostics/unused-vararg.lua new file mode 100644 index 00000000..74cc08e7 --- /dev/null +++ b/script-beta/src/core/diagnostics/unused-vararg.lua @@ -0,0 +1,31 @@ +local files = require 'files' +local guide = require 'parser.guide' +local define = require 'proto.define' +local lang = require 'language' + +return function (uri, callback) + local ast = files.getAst(uri) + if not ast then + return + end + + guide.eachSourceType(ast.ast, 'function', function (source) + local args = source.args + if not args then + return + end + + for _, arg in ipairs(args) do + if arg.type == '...' then + if not arg.ref then + callback { + start = arg.start, + finish = arg.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_UNUSED_VARARG, + } + end + end + end + end) +end |