diff options
55 files changed, 1269 insertions, 589 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 92e6ec8e..54ad1616 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,9 +11,11 @@ "ID", "DEVELOP", "DBGPORT", - "DBGWAIT" + "DBGWAIT", + "tracy" ], "Lua.diagnostics.disable": [ + "close-non-object" ], "Lua.runtime.version": "Lua 5.4", // Just some comment @@ -35,5 +37,10 @@ "Lua.awakened.cat": true, "Lua.develop.enable": true, "Lua.develop.debuggerPort": 11413, - "Lua.intelliSense.searchDepth": 0 + "Lua.intelliSense.searchDepth": 0, + "Lua.runtime.path": [ + "?.lua", + "script/?.lua", + "script/?/init.lua" + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4771a2ef..dca20fb7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -35,7 +35,7 @@ "type": "shell", "label": "Compile", "windows": { - "command": "chcp 65001 && ${workspaceFolder}/3rd/luamake/luamake rebuild.bat" + "command": "chcp 65001 && ${workspaceFolder}/3rd/luamake/luamake rebuild" }, "linux": { "command": "${workspaceFolder}/3rd/luamake/luamake rebuild" diff --git a/3rd/bee.lua b/3rd/bee.lua -Subproject bb6094a71d3cd41f0af22704cc1905330d4cc1f +Subproject 5a88a0aa0a9dc83fb60e913730f1de864438907 diff --git a/3rd/luamake b/3rd/luamake -Subproject 447d5165d59fcb6470ee2c69468a4549c544346 +Subproject aa20cd1b07006bc50e4a2cc09b5f30134c4a75a diff --git a/changelog.md b/changelog.md index 605a7ea3..76681f22 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,38 @@ # changelog +## 1.8.1 +* `FIX` telemetry: connect failed caused not working + +## 1.8.0 +`2020-12-23` +* `NEW` runtime: support nonstandard symbol +* `NEW` diagnostic: `close-non-object` +* `FIX` [#318](https://github.com/sumneko/lua-language-server/issues/318) + +## 1.7.4 +`2020-12-20` +* `FIX` workspace: preload may failed + +## 1.7.3 +`2020-12-20` +* `FIX` luadoc: typo of `package.config` +* `FIX` [#310](https://github.com/sumneko/lua-language-server/issues/310) + +## 1.7.2 +`2020-12-17` +* `CHG` completion: use custom tabsize +* `FIX` [#307](https://github.com/sumneko/lua-language-server/issues/307) +* `FIX` a lot of runtime errors + +## 1.7.1 +`2020-12-16` +* `NEW` setting: `diagnostics.neededFileStatus` +* `FIX` scan workspace may fails +* `FIX` quickfix: `newline-call` failed +* `FIX` a lot of other runtime errors + ## 1.7.0 +`2020-12-16` * `NEW` diagnostic: `undefined-field` * `NEW` telemetry: + [What data will be sent](https://github.com/sumneko/lua-language-server/blob/master/script/service/telemetry.lua) diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 20c24c90..1e6f3c03 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -37,6 +37,7 @@ DIAG_SET_FOR_STATE = 'Assignment to for-state variable.' DIAG_CODE_AFTER_BREAK = 'Unable to execute code after `break`.' DIAG_UNBALANCED_ASSIGNMENTS = 'The value is assigned as `nil` because the number of values is not enough. In Lua, `x, y = 1 ` is equivalent to `x, y = 1, nil` .' DIAG_REQUIRE_LIKE = 'You can treat `{}` as `require` by setting.' +DIAG_COSE_NON_OBJECT = 'Cannot close a value of this type. (Unless set `__close` meta method)' DIAG_CIRCLE_DOC_CLASS = 'Circularly inherited classes.' DIAG_DOC_FIELD_NO_CLASS = 'The field must be defined after the class.' @@ -99,6 +100,7 @@ PARSER_ERR_COMMENT_PREFIX = 'Lua should use `--` for annotations.' PARSER_MISS_SEP_IN_TABLE = 'Miss symbol `,` or `;` .' PARSER_SET_CONST = 'Assignment to const variable.' PARSER_UNICODE_NAME = 'Contains Unicode characters.' +PARSER_ERR_NONSTANDARD_SYMBOL = 'Lua should use `{symbol}` .' PARSER_LUADOC_MISS_CLASS_NAME = '<class name> expected.' PARSER_LUADOC_MISS_EXTENDS_SYMBOL = '`:` expected.' @@ -165,6 +167,7 @@ ACTION_FIX_THEN_AS_DO = 'Modify to `then` .' ACTION_FIX_DO_AS_THEN = 'Modify to `do` .' ACTION_ADD_END = 'Add `end` (infer the addition location ny indentations).' ACTION_FIX_COMMENT_PREFIX = 'Modify to `--` .' +ACTION_FIX_NONSTANDARD_SYMBOL = 'Modify to `{symbol}` .' ACTION_RUNTIME_UNICODE_NAME = 'Allow Unicode characters.' ACTION_SWAP_PARAMS = 'Change to parameter {index} or `{node}`' diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 4e43024f..b3b59c85 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -37,6 +37,7 @@ DIAG_SET_FOR_STATE = '修改了循环变量。' DIAG_CODE_AFTER_BREAK = '无法执行到 `break` 后的代码。' DIAG_UNBALANCED_ASSIGNMENTS = '由于值的数量不够而被赋值为了 `nil` 。在Lua中, `x, y = 1` 等价于 `x, y = 1, nil` 。' DIAG_REQUIRE_LIKE = '你可以在设置中将 `{}` 视为 `require`。' +DIAG_COSE_NON_OBJECT = '无法 close 此类型的值。(除非给此类型设置 `__close` 元方法)' DIAG_CIRCLE_DOC_CLASS = '循环继承的类。' DIAG_DOC_FIELD_NO_CLASS = '字段必须定义在类之后。' @@ -74,7 +75,7 @@ PARSER_MISS_FIELD = '缺少域名。' PARSER_MISS_METHOD = '缺少方法名。' PARSER_ARGS_AFTER_DOTS = '`...`必须是最后一个参数。' PARSER_KEYWORD = '关键字无法作为名称。' -PARSER_EXP_IN_ACTION = '表达式不能独立存在。' +PARSER_EXP_IN_ACTION = '该表达式不能作为语句。' PARSER_BREAK_OUTSIDE = '`break`必须在循环内部。' PARSER_MALFORMED_NUMBER = '无法构成有效数字。' PARSER_ACTION_AFTER_RETURN = '`return`之后不能再执行代码。' @@ -99,6 +100,7 @@ PARSER_ERR_COMMENT_PREFIX = 'Lua应使用`--`来进行注释。' PARSER_MISS_SEP_IN_TABLE = '需要用`,`或`;`进行分割。' PARSER_SET_CONST = '不能对常量赋值。' PARSER_UNICODE_NAME = '包含了 Unicode 字符。' +PARSER_ERR_NONSTANDARD_SYMBOL = 'Lua中应使用符号 `{symbol}`。' PARSER_LUADOC_MISS_CLASS_NAME = '缺少类名称。' PARSER_LUADOC_MISS_EXTENDS_SYMBOL = '缺少符号 `:`。' @@ -164,6 +166,7 @@ ACTION_FIX_THEN_AS_DO = '改为 `then` 。' ACTION_FIX_DO_AS_THEN = '改为 `do` 。' ACTION_ADD_END = '添加 `end` (根据缩进推测添加位置)。' ACTION_FIX_COMMENT_PREFIX = '改为 `--` 。' +ACTION_FIX_NONSTANDARD_SYMBOL = '改为 `{symbol}`' ACTION_RUNTIME_UNICODE_NAME = '允许使用 Unicode 字符。' ACTION_SWAP_PARAMS = '将其改为 `{node}` 的第 {index} 个参数' @@ -14,10 +14,10 @@ log.init(ROOT, ROOT / 'log' / 'service.log') log.info('Lua Lsp startup, root: ', ROOT) log.debug('ROOT:', ROOT:string()) +require 'tracy' + xpcall(dofile, log.debug, rootPath .. '/debugger.lua') local service = require 'service' --- TODO ---ALL_DEEP = true service.start() diff --git a/meta/template/package.lua b/meta/template/package.lua index fbbd10a4..0d6ab270 100644 --- a/meta/template/package.lua +++ b/meta/template/package.lua @@ -16,7 +16,7 @@ function require(modname) end ---#DES 'package' ---@class package* ---#DES 'package.config' ----@field conifg string +---@field config string ---#DES 'package.cpath' ---@field cpath string ---#DES 'package.loaded' diff --git a/script/config.lua b/script/config.lua index 74f2ff4e..eecd8ffa 100644 --- a/script/config.lua +++ b/script/config.lua @@ -95,15 +95,16 @@ end local ConfigTemplate = { runtime = { - version = {'Lua 5.4', String}, - path = {{ - "?.lua", - "?/init.lua", - "?/?.lua" - }, Array(String)}, - special = {{}, Hash(String, String)}, - meta = {'${version} ${language}', String}, - unicodeName = {false, Boolean}, + version = {'Lua 5.4', String}, + path = {{ + "?.lua", + "?/init.lua", + "?/?.lua" + }, Array(String)}, + special = {{}, Hash(String, String)}, + meta = {'${version} ${language}', String}, + unicodeName = {false, Boolean}, + nonstandardSymbol = {{}, Str2Hash ';'}, }, diagnostics = { enable = {true, Boolean}, diff --git a/script/core/code-action.lua b/script/core/code-action.lua index 246e1549..2a0d9907 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -156,7 +156,7 @@ local function solveSyntaxByFix(uri, err, results) } end results[#results+1] = { - title = lang.script['ACTION_' .. err.fix.title], + title = lang.script('ACTION_' .. err.fix.title, err.fix), kind = 'quickfix', edit = { changes = { @@ -207,6 +207,7 @@ end local function solveNewlineCall(uri, diag, results) local text = files.getText(uri) local lines = files.getLines(uri) + local start = define.unrange(lines, text, diag.range) results[#results+1] = { title = lang.script.ACTION_ADD_SEMICOLON, kind = 'quickfix', @@ -214,10 +215,8 @@ local function solveNewlineCall(uri, diag, results) changes = { [uri] = { { - range = { - start = diag.range.start, - ['end'] = diag.range.start, - }, + start = start, + finish = start, newText = ';', } } diff --git a/script/core/completion.lua b/script/core/completion.lua index 5db4aa1b..b1610ff0 100644 --- a/script/core/completion.lua +++ b/script/core/completion.lua @@ -1149,10 +1149,10 @@ local function buildInsertDocFunction(doc) for i, arg in ipairs(doc.args) do args[i] = ('${%d:%s}'):format(i, arg.name[1]) end - return ([[ -function (%s) - $0 -end]]):format(table.concat(args, ', ')) + return ("\z +function (%s)\ +\t$0\ +end"):format(table.concat(args, ', ')) end local function getCallEnums(source, index) diff --git a/script/core/diagnostics/close-non-object.lua b/script/core/diagnostics/close-non-object.lua new file mode 100644 index 00000000..cfefb037 --- /dev/null +++ b/script/core/diagnostics/close-non-object.lua @@ -0,0 +1,40 @@ +local files = require 'files' +local guide = require 'parser.guide' +local lang = require 'language' +local define = require 'proto.define' + +return function (uri, callback) + local state = files.getAst(uri) + if not state then + return + end + + guide.eachSourceType(state.ast, 'local', function (source) + if not source.attrs then + return + end + if source.attrs[1][1] ~= 'close' then + return + end + if not source.value then + callback { + start = source.start, + finish = source.finish, + message = lang.script.DIAG_COSE_NON_OBJECT, + } + return + end + if source.value.type == 'nil' + or source.value.type == 'number' + or source.value.type == 'boolean' + or source.value.type == 'table' + or source.value.type == 'function' then + callback { + start = source.value.start, + finish = source.value.finish, + message = lang.script.DIAG_COSE_NON_OBJECT, + } + return + end + end) +end diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index 15884758..bc3f3d8c 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -4,7 +4,8 @@ local config = require 'config' local await = require 'await' -- 把耗时最长的诊断放到最后面 -local diagLevel = { +local diagSort = { + ['undefined-field'] = 99, ['redundant-parameter'] = 100, } @@ -13,7 +14,7 @@ for k in pairs(define.DiagnosticDefaultSeverity) do diagList[#diagList+1] = k end table.sort(diagList, function (a, b) - return (diagLevel[a] or 0) < (diagLevel[b] or 0) + return (diagSort[a] or 0) < (diagSort[b] or 0) end) local function check(uri, name, results) @@ -23,7 +24,8 @@ local function check(uri, name, results) local level = config.config.diagnostics.severity[name] or define.DiagnosticDefaultSeverity[name] - local neededFileStatus = define.DiagnosticDefaultNeededFileStatus[name] + local neededFileStatus = config.config.diagnostics.neededFileStatus[name] + or define.DiagnosticDefaultNeededFileStatus[name] if neededFileStatus == 'Opened' and not files.isOpen(uri) then return end diff --git a/script/core/diagnostics/redundant-parameter.lua b/script/core/diagnostics/redundant-parameter.lua index 2fae20e8..ac52ab2a 100644 --- a/script/core/diagnostics/redundant-parameter.lua +++ b/script/core/diagnostics/redundant-parameter.lua @@ -22,7 +22,7 @@ local function countFuncArgs(source) if source.parent and source.parent.type == 'setmethod' then result = result + 1 end - if not source.args then + if not source.args or #source.args == 0 then return result end if source.args[#source.args].type == '...' then @@ -32,6 +32,22 @@ local function countFuncArgs(source) return result end +local function countOverLoadArgs(source, doc) + local result = 0 + if source.parent and source.parent.type == 'setmethod' then + result = result + 1 + end + local func = doc.overload + if not func.args or #func.args == 0 then + return result + end + if func.args[#func.args].type == '...' then + return math.maxinteger + end + result = result + #func.args + return result +end + return function (uri, callback) local ast = files.getAst(uri) if not ast then @@ -56,6 +72,16 @@ return function (uri, callback) if not funcArgs or args > funcArgs then funcArgs = args end + if def.bindDocs then + for _, doc in ipairs(def.bindDocs) do + if doc.type == 'doc.overload' then + args = countOverLoadArgs(def, doc) + if not funcArgs or args > funcArgs then + funcArgs = args + end + end + end + end end end diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index a8fadfed..31bd9008 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -12,7 +12,9 @@ return function (uri, callback) end local function getAllDocClassFromInfer(src) + tracy.ZoneBeginN('undefined-field getInfers') local infers = vm.getInfers(src, 0) + tracy.ZoneEnd() if not infers then return nil @@ -29,7 +31,7 @@ return function (uri, callback) local allDocClass = {} for i = 1, #infers do local infer = infers[i] - if infer.type ~= '_G' and infer.type ~= 'any' then + if infer.type ~= '_G' and infer.type ~= 'any' and infer.type ~= 'table' then local inferSource = infer.source if inferSource.type == 'doc.class' then addTo(allDocClass, inferSource) @@ -53,7 +55,7 @@ return function (uri, callback) local fields = {} local empty = true for _, docClass in ipairs(allDocClass) do - local refs = vm.getFieldsOfDocClassAnyNotGet(docClass) + local refs = vm.getDefFields(docClass) for _, ref in ipairs(refs) do local name = vm.getKeyName(ref) @@ -90,13 +92,13 @@ return function (uri, callback) if not fields[fieldName] then local message = lang.script('DIAG_UNDEF_FIELD', fieldName) - if src.type == 'getfield' then + if src.type == 'getfield' and src.field then callback { start = src.field.start, finish = src.field.finish, message = message, } - elseif src.type == 'getmethod' then + elseif src.type == 'getmethod' and src.method then callback { start = src.method.start, finish = src.method.finish, diff --git a/script/core/diagnostics/unused-function.lua b/script/core/diagnostics/unused-function.lua index 1463d081..e6ae9386 100644 --- a/script/core/diagnostics/unused-function.lua +++ b/script/core/diagnostics/unused-function.lua @@ -4,6 +4,7 @@ local vm = require 'vm' local define = require 'proto.define' local lang = require 'language' local await = require 'await' +local client = require 'provider.client' local function isToBeClosed(source) if not source.attrs then @@ -32,7 +33,7 @@ return function (uri, callback) and parent.type ~= 'setlocal' then return end - if isToBeClosed(source) then + if isToBeClosed(parent) then return end local hasGet @@ -44,12 +45,21 @@ return function (uri, callback) end end if not hasGet then - callback { - start = source.start, - finish = source.finish, - tags = { define.DiagnosticTag.Unnecessary }, - message = lang.script.DIAG_UNUSED_FUNCTION, - } + if client.isVSCode() then + callback { + start = source.start, + finish = source.finish, + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_UNUSED_FUNCTION, + } + else + callback { + start = source.keyword[1], + finish = source.keyword[2], + tags = { define.DiagnosticTag.Unnecessary }, + message = lang.script.DIAG_UNUSED_FUNCTION, + } + end end end) end diff --git a/script/core/document-symbol.lua b/script/core/document-symbol.lua index 7392b337..f06e613c 100644 --- a/script/core/document-symbol.lua +++ b/script/core/document-symbol.lua @@ -76,8 +76,9 @@ local function buildTable(tbl) if not field then break end - if field.type == 'tablefield' then - buf[i] = ('%s'):format(field.field[1]) + if field.type == 'tablefield' + and field.field then + buf[#buf+1] = ('%s'):format(field.field[1]) end end return table.concat(buf, ', ') diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index f9a0fc1d..570dec3f 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -109,7 +109,7 @@ end local function buildEnumChunk(docType, name) local enums = vm.getDocEnums(docType) - if #enums == 0 then + if not enums or #enums == 0 then return end local types = {} diff --git a/script/core/keyword.lua b/script/core/keyword.lua index cace541b..08600868 100644 --- a/script/core/keyword.lua +++ b/script/core/keyword.lua @@ -15,10 +15,10 @@ local keyWordMap = { label = 'do .. end', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -do - $0 -end]], + insertText = "\z +do\ +\t$0\ +end", } end return true @@ -64,38 +64,38 @@ end]], label = 'for .. in', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -${1:key, value} in ${2:pairs(${3:t})} do - $0 -end]] + insertText = "\z +${1:key, value} in ${2:pairs(${3:t})} do\ +\t$0\ +end" } results[#results+1] = { label = 'for i = ..', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -${1:i} = ${2:1}, ${3:10, 1} do - $0 -end]] + insertText = "\z +${1:i} = ${2:1}, ${3:10, 1} do\ +\t$0\ +end" } else results[#results+1] = { label = 'for .. in', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -for ${1:key, value} in ${2:pairs(${3:t})} do - $0 -end]] + insertText = "\z +for ${1:key, value} in ${2:pairs(${3:t})} do\ +\t$0\ +end" } results[#results+1] = { label = 'for i = ..', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -for ${1:i} = ${2:1}, ${3:10, 1} do - $0 -end]] + insertText = "\z +for ${1:i} = ${2:1}, ${3:10, 1} do\ +\t$0\ +end" } end return true @@ -106,26 +106,26 @@ end]] label = 'function ()', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = isExp and [[ -($1) - $0 -end]] or [[ -$1($2) - $0 -end]] + insertText = isExp and "\z +($1)\ +\t$0\ +end" or "\z +$1($2)\ +\t$0\ +end" } else results[#results+1] = { label = 'function ()', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = isExp and [[ -function ($1) - $0 -end]] or [[ -function $1($2) - $0 -end]] + insertText = isExp and "\z +function ($1)\ +\t$0\ +end" or "\z +function $1($2)\ +\t$0\ +end" } end return true @@ -137,20 +137,20 @@ end]] label = 'if .. then', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -$1 then - $0 -end]] + insertText = "\z +$1 then\ +\t$0\ +end" } else results[#results+1] = { label = 'if .. then', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -if $1 then - $0 -end]] + insertText = "\z +if $1 then\ +\t$0\ +end" } end return true @@ -161,20 +161,20 @@ end]] label = 'in ..', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -${1:pairs(${2:t})} do - $0 -end]] + insertText = "\z +${1:pairs(${2:t})} do\ +\t$0\ +end" } else results[#results+1] = { label = 'in ..', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -in ${1:pairs(${2:t})} do - $0 -end]] + insertText = "\z +in ${1:pairs(${2:t})} do\ +\t$0\ +end" } end return true @@ -185,20 +185,20 @@ end]] label = 'local function', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -function $1($2) - $0 -end]] + insertText = "\z +function $1($2)\ +\t$0\ +end" } else results[#results+1] = { label = 'local function', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -local function $1($2) - $0 -end]] + insertText = "\z +local function $1($2)\ +\t$0\ +end" } end return false @@ -219,10 +219,10 @@ end]] label = 'repeat .. until', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -repeat - $0 -until $1]] + insertText = "\z +repeat\ +\t$0\ +until $1" } end return true @@ -247,20 +247,20 @@ until $1]] label = 'while .. do', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -${1:true} do - $0 -end]] + insertText = "\z +${1:true} do\ +\t$0\ +end" } else results[#results+1] = { label = 'while .. do', kind = define.CompletionItemKind.Snippet, insertTextFormat = 2, - insertText = [[ -while ${1:true} do - $0 -end]] + insertText = "\z +while ${1:true} do\ +\t$0\ +end" } end return true diff --git a/script/core/rename.lua b/script/core/rename.lua index 54867f77..b823cb86 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -113,10 +113,16 @@ local function trim(str) end local function isValidName(str) + if not str then + return false + end return str:match '^[%a_][%w_]*$' end local function isValidGlobal(str) + if not str then + return false + end for s in str:gmatch '[^%.]*' do if not isValidName(trim(s)) then return false @@ -431,6 +437,9 @@ local accept = { local m = {} function m.rename(uri, pos, newname) + if not newname then + return nil + end local ast = files.getAst(uri) if not ast then return nil diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index 170bffa8..37eabda7 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -120,6 +120,21 @@ Care['doc.type.name'] = function (source, results) end end +Care['nonstandardSymbol.comment'] = function (source, results) + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.comment, + } +end +Care['nonstandardSymbol.continue'] = function (source, results) + results[#results+1] = { + start = source.start, + finish = source.finish, + type = define.TokenTypes.keyword, + } +end + local function buildTokens(results, text, lines) local tokens = {} local lastLine = 0 diff --git a/script/core/signature.lua b/script/core/signature.lua index 2f652f39..6e3d7e11 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -6,16 +6,16 @@ local hoverDesc = require 'core.hover.description' local function findNearCall(uri, ast, pos) local text = files.getText(uri) - -- 检查 `f()$` 的情况,注意要区别于 `f($` - if text:sub(pos, pos) == ')' then - return nil - end - local nearCall guide.eachSourceContain(ast.ast, pos, function (src) if src.type == 'call' or src.type == 'table' or src.type == 'function' then + -- call()$ + if src.finish <= pos + and text:sub(src.finish, src.finish) == ')' then + return + end if not nearCall or nearCall.start < src.start then nearCall = src end diff --git a/script/files.lua b/script/files.lua index 71981a83..9ffcd2a1 100644 --- a/script/files.lua +++ b/script/files.lua @@ -211,7 +211,11 @@ end --- 遍历文件 function m.eachFile() - return pairs(m.fileMap) + local map = {} + for uri, file in pairs(m.fileMap) do + map[uri] = file + end + return pairs(map) end function m.compileAst(uri, text) @@ -242,8 +246,9 @@ function m.compileAst(uri, text) , 'lua' , config.config.runtime.version , { - special = config.config.runtime.special, - unicodeName = config.config.runtime.unicodeName, + special = config.config.runtime.special, + unicodeName = config.config.runtime.unicodeName, + nonstandardSymbol = config.config.runtime.nonstandardSymbol, } ) local passed = os.clock() - clock diff --git a/script/glob/gitignore.lua b/script/glob/gitignore.lua index 3f942bfb..7dfd4591 100644 --- a/script/glob/gitignore.lua +++ b/script/glob/gitignore.lua @@ -116,6 +116,7 @@ function mt:checkDirectory(catch, path, matcher) end function mt:simpleMatch(path) + path = path:gsub('^[/\\]+', '') for i = #self.matcher, 1, -1 do local matcher = self.matcher[i] local catch = matcher(path) @@ -147,18 +148,18 @@ function mt:finishMatch(path) return false end -function mt:scan(callback) +function mt:scan(root, callback) local files = {} if type(callback) ~= 'function' then callback = nil end local list = {} - local result = self:callInterface('list', '') + local result = self:callInterface('list', root) if type(result) ~= 'table' then return files end for _, path in ipairs(result) do - list[#list+1] = path:match '([^/\\]+)[/\\]*$' + list[#list+1] = path end while #list > 0 do local current = list[#list] @@ -181,7 +182,7 @@ function mt:scan(callback) if filename and filename ~= '.' and filename ~= '..' then - list[#list+1] = current .. '/' .. filename + list[#list+1] = path end end end diff --git a/script/parser/ast.lua b/script/parser/ast.lua index 2fdb99ef..127bb08c 100644 --- a/script/parser/ast.lua +++ b/script/parser/ast.lua @@ -48,6 +48,13 @@ local VersionOp = { ['//'] = {'Lua 5.3', 'Lua 5.4'}, } +local SymbolAlias = { + ['||'] = 'or', + ['&&'] = 'and', + ['!='] = '~=', + ['!'] = 'not', +} + local function checkOpVersion(op) local versions = VersionOp[op.type] if not versions then @@ -305,41 +312,84 @@ local Defs = { end end, CLongComment = function (start1, finish1, start2, finish2) - PushError { - type = 'ERR_C_LONG_COMMENT', + if State.options.nonstandardSymbol and State.options.nonstandardSymbol['/**/'] then + else + PushError { + type = 'ERR_C_LONG_COMMENT', + start = start1, + finish = finish2 - 1, + fix = { + title = 'FIX_C_LONG_COMMENT', + { + start = start1, + finish = finish1 - 1, + text = '--[[', + }, + { + start = start2, + finish = finish2 - 1, + text = '--]]' + }, + } + } + end + return { + type = 'nonstandardSymbol.comment', start = start1, finish = finish2 - 1, - fix = { - title = 'FIX_C_LONG_COMMENT', - { - start = start1, - finish = finish1 - 1, - text = '--[[', - }, - { - start = start2, - finish = finish2 - 1, - text = '--]]' - }, - } } end, - CCommentPrefix = function (start, finish) - PushError { - type = 'ERR_COMMENT_PREFIX', - start = start, - finish = finish - 1, - fix = { - title = 'FIX_COMMENT_PREFIX', - { - start = start, - finish = finish - 1, - text = '--', - }, + CCommentPrefix = function (start, finish, commentFinish) + if State.options.nonstandardSymbol and State.options.nonstandardSymbol['//'] then + else + PushError { + type = 'ERR_COMMENT_PREFIX', + start = start, + finish = finish - 1, + fix = { + title = 'FIX_COMMENT_PREFIX', + { + start = start, + finish = finish - 1, + text = '--', + }, + } } + end + return { + type = 'nonstandardSymbol.comment', + start = start, + finish = commentFinish - 1, } end, String = function (start, quote, str, finish) + if quote == '`' then + if State.options.nonstandardSymbol and State.options.nonstandardSymbol['`'] then + else + PushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = start, + finish = finish - 1, + info = { + symbol = '"', + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = '"', + { + start = start, + finish = start, + text = '"', + }, + { + start = finish - 1, + finish = finish - 1, + text = '"', + }, + } + } + end + end return { type = 'string', start = start, @@ -642,6 +692,29 @@ local Defs = { return call end, BinaryOp = function (start, op) + if SymbolAlias[op] then + if State.options.nonstandardSymbol and State.options.nonstandardSymbol[op] then + else + PushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = start, + finish = start + #op - 1, + info = { + symbol = SymbolAlias[op], + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = SymbolAlias[op], + { + start = start, + finish = start + #op - 1, + text = SymbolAlias[op], + }, + } + } + end + op = SymbolAlias[op] + end return { type = op, start = start, @@ -649,6 +722,29 @@ local Defs = { } end, UnaryOp = function (start, op) + if SymbolAlias[op] then + if State.options.nonstandardSymbol and State.options.nonstandardSymbol[op] then + else + PushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = start, + finish = start + #op - 1, + info = { + symbol = SymbolAlias[op], + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = SymbolAlias[op], + { + start = start, + finish = start + #op - 1, + text = SymbolAlias[op], + }, + } + } + end + op = SymbolAlias[op] + end return { type = op, start = start, @@ -855,6 +951,19 @@ local Defs = { finish = start, } end, + ASSIGN = function (start, symbol) + if State.options.nonstandardSymbol and State.options.nonstandardSymbol[symbol] then + else + PushError { + type = 'UNSUPPORT_SYMBOL', + start = start, + finish = start + #symbol - 1, + info = { + version = 'Lua', + } + } + end + end, DOT = function (start) return { type = '.', @@ -1133,7 +1242,7 @@ local Defs = { end, LocalName = function (name, attrs) if not name then - return name + return end name.attrs = attrs return name @@ -1197,7 +1306,7 @@ local Defs = { return end if not name then - return nil + return end name.type = 'label' return name @@ -1216,7 +1325,7 @@ local Defs = { return end if not name then - return nil + return end name.type = 'goto' return name @@ -1356,6 +1465,20 @@ local Defs = { } return block end, + RTContinue = function (_, pos, ...) + if State.options.nonstandardSymbol and State.options.nonstandardSymbol['continue'] then + return pos, ... + else + return false + end + end, + Continue = function (start, finish) + return { + type = 'nonstandardSymbol.continue', + start = start, + finish = finish - 1, + } + end, Lua = function (start, actions, finish) actions.type = 'main' actions.start = start @@ -1435,6 +1558,16 @@ local Defs = { } } end, + MissQuote3 = function (pos) + PushError { + type = 'MISS_SYMBOL', + start = pos, + finish = pos, + info = { + symbol = "`" + } + } + end, MissEscX = function (pos) PushError { type = 'MISS_ESC_X', diff --git a/script/parser/grammar.lua b/script/parser/grammar.lua index ad107ef5..f222a283 100644 --- a/script/parser/grammar.lua +++ b/script/parser/grammar.lua @@ -58,6 +58,12 @@ end defs.None = function () end defs.np = m.Cp() / function (n) return n+1 end defs.NameBody = m.R('az', 'AZ', '__', '\x80\xff') * m.R('09', 'az', 'AZ', '__', '\x80\xff')^0 +defs.NoNil = function (o) + if o == nil then + return + end + return o +end m.setmaxstack(1000) @@ -118,6 +124,7 @@ NOT <- Sp 'not' Cut OR <- Sp {'or'} Cut RETURN <- Sp 'return' Cut TRUE <- Sp 'true' Cut +CONTINUE <- Sp 'continue' Cut DO <- Sp {} 'do' {} Cut / Sp({} 'then' {} Cut) -> ErrDo @@ -180,9 +187,9 @@ UnaryList <- NOT / '~' !'=' POWER <- Sp {'^'} -BinaryOp <-( Sp {} {'or'} Cut - / Sp {} {'and'} Cut - / Sp {} {'<=' / '>=' / '<'!'<' / '>'!'>' / '~=' / '=='} +BinaryOp <-( Sp {} {'or' / '||'} Cut + / Sp {} {'and' / '&&'} Cut + / Sp {} {'<=' / '>=' / '<'!'<' / '>'!'>' / '~=' / '==' / '!='} / Sp {} ({} '=' {}) -> ErrEQ / Sp {} ({} '!=' {}) -> ErrUEQ / Sp {} {'|'} @@ -194,7 +201,7 @@ BinaryOp <-( Sp {} {'or'} Cut / Sp {} {'*' / '//' / '/' / '%'} / Sp {} {'^'} )-> BinaryOp -UnaryOp <-( Sp {} {'not' Cut / '#' / '~' !'=' / '-' !'-'} +UnaryOp <-( Sp {} {'not' Cut / '#' / '~' !'=' / '-' !'-' / '!' !'='} )-> UnaryOp PL <- Sp '(' @@ -215,9 +222,11 @@ COLON <- Sp ({} ':' !':') -> COLON LABEL <- Sp '::' ASSIGN <- Sp '=' !'=' + / Sp ({} {'+=' / '-=' / '*=' / '\='}) + -> ASSIGN AssignOrEQ <- Sp ({} '==' {}) -> ErrAssign - / Sp '=' + / ASSIGN DirtyBR <- BR / {} -> MissBR DirtyTR <- TR / {} -> MissTR @@ -250,6 +259,9 @@ StringDef <- {'"'} / {"'"} {~(Esc / !%nl !"'" .)*~} -> 1 ("'" / {} -> MissQuote2) + / {'`'} + {(!%nl !'`' .)*} -> 1 + ('`' / {} -> MissQuote3) / ('[' {} {:eq: '='* :} {} '[' %nl? {(!StringClose .)*} -> 1 (StringClose / {})) @@ -321,7 +333,7 @@ Single <- FreeName Suffix <- SuffixWithoutCall / ({} PL SuffixCall DirtyPR {}) -> Call -SuffixCall <- Sp ({} {| (COMMA / Exp)+ |} {}) +SuffixCall <- Sp ({} {| (COMMA / Exp->NoNil)+ |} {}) -> PackExpList / %nil SuffixWithoutCall @@ -359,7 +371,7 @@ TableField <- COMMA / SEMICOLON / NewIndex / NewField - / Exp + / Exp->NoNil Index <- BL DirtyExp DirtyBR NewIndex <- Sp ({} Index NeedAssign DirtyExp {}) -> NewIndex @@ -402,11 +414,12 @@ CrtAction <- Semicolon / LocalFunction / Local / Set + / Continue / Call / ExpInAction UnkAction <- ({} {Word+}) -> UnknownAction - / ({} '//' {} (LongComment / ShortComment)) + / ({} '//' {} (LongComment / ShortComment) {}) -> CCommentPrefix / ({} {. (!Sps !CrtAction .)*}) -> UnknownAction @@ -425,10 +438,14 @@ Do <- Sp ({} Break <- Sp ({} BREAK {}) -> Break +Continue <- Sp ({} CONTINUE {}) + => RTContinue + -> Continue + Return <- Sp ({} RETURN ReturnExpList {}) -> Return ReturnExpList - <- Sp {| Exp (Sp ',' MaybeExp)* |} + <- Sp {| Exp->NoNil (Sp ',' MaybeExp)* |} / Sp {| !Exp !',' |} / ExpList @@ -461,7 +478,7 @@ LoopBody <- FOR LoopArgs NeedDo {} {| (!END Action)* |} NeedEnd LoopArgs <- MustName AssignOrEQ - ({} {| (COMMA / !DO !END Exp)* |} {}) + ({} {| (COMMA / !DO !END Exp->NoNil)* |} {}) -> PackLoopArgs In <- InBody @@ -469,9 +486,9 @@ In <- InBody InBody <- FOR InNameList NeedIn InExpList NeedDo {} {| (!END Action)* |} NeedEnd -InNameList <- ({} {| (COMMA / !IN !DO !END Name)* |} {}) +InNameList <- ({} {| (COMMA / !IN !DO !END Name->NoNil)* |} {}) -> PackInNameList -InExpList <- ({} {| (COMMA / !DO !DO !END Exp)* |} {}) +InExpList <- ({} {| (COMMA / !DO !DO !END Exp->NoNil)* |} {}) -> PackInExpList While <- WhileBody diff --git a/script/parser/guide.lua b/script/parser/guide.lua index a6b11744..a6149dd7 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -17,6 +17,8 @@ local setmetatable = setmetatable local assert = assert local select = select local osClock = os.clock +local tonumber = tonumber +local tointeger = math.tointeger local DEVELOP = _G.DEVELOP local log = log local _G = _G @@ -25,13 +27,9 @@ local function logWarn(...) log.warn(...) end -_ENV = nil - local m = {} -m.ANY = {"ANY"} - -m.ANYNOTGET = {"ANYNOTGET"} +m.ANY = {"<ANY>"} local blockTypes = { ['while'] = true, @@ -99,6 +97,7 @@ m.childMap = { ['doc.vararg'] = {'vararg', 'comment'}, ['doc.type.table'] = {'key', 'value', 'comment'}, ['doc.type.function'] = {'#args', '#returns', 'comment'}, + ['doc.type.typeliteral'] = {'node'}, ['doc.overload'] = {'overload', 'comment'}, ['doc.see'] = {'name', 'field'}, } @@ -178,6 +177,9 @@ function m.getBlock(obj) if blockTypes[tp] then return obj end + if obj == obj.parent then + error('obj == obj.parent?', obj.type) + end obj = obj.parent end error('guide.getBlock overstack') @@ -664,6 +666,10 @@ function m.lineRange(lines, row, ignoreNL) end end +function m.lineData(lines, row) + return lines[row] +end + function m.getKeyTypeOfLiteral(obj) if not obj then return nil @@ -714,6 +720,8 @@ function m.getKeyType(obj) return 'string' elseif tp == 'doc.field' then return 'string' + elseif tp == 'dummy' then + return 'string' end return m.getKeyTypeOfLiteral(obj) end @@ -781,14 +789,24 @@ function m.getKeyName(obj) return obj.alias[1] elseif tp == 'doc.field' then return obj.field[1] + elseif tp == 'dummy' then + return obj[1] end return m.getKeyNameOfLiteral(obj) end function m.getSimpleName(obj) if obj.type == 'call' then - local key = obj.args and obj.args[2] - return m.getKeyName(key) + local node = obj.node + if not node then + return + end + if node.special == 'rawset' + or node.special == 'rawget' then + local key = obj.args and obj.args[2] + return m.getKeyName(key) + end + return ('%p'):format(obj) elseif obj.type == 'table' then return ('%p'):format(obj) elseif obj.type == 'select' then @@ -1136,7 +1154,8 @@ local function buildSimpleList(obj, max) list[i] = cur break elseif cur.type == 'select' - or cur.type == 'table' then + or cur.type == 'table' + or cur.type == 'call' then list[i] = cur break elseif cur.type == 'string' then @@ -1177,6 +1196,7 @@ function m.getSimple(obj, max) or obj.type == 'tablefield' or obj.type == 'tableindex' or obj.type == 'select' + or obj.type == 'call' or obj.type == 'table' or obj.type == 'string' or obj.type == 'doc.class.name' @@ -1359,16 +1379,13 @@ function m.getNextRef(ref) return nil end -function m.checkSameSimpleInValueOfTable(status, value, start, queue) +function m.checkSameSimpleInValueOfTable(status, value, start, pushQueue) if value.type ~= 'table' then return end for i = 1, #value do local field = value[i] - queue[#queue+1] = { - obj = field, - start = start + 1, - } + pushQueue(field, start + 1) end end @@ -1382,6 +1399,16 @@ function m.searchFields(status, obj, key) m.cleanResults(status.results) end +function m.searchDefFields(status, obj, key) + local simple = m.getSimple(obj) + if not simple then + return + end + simple[#simple+1] = key or m.ANY + m.searchSameFields(status, simple, 'deffield') + m.cleanResults(status.results) +end + function m.getObjectValue(obj) while obj.type == 'paren' do obj = obj.exp @@ -1400,11 +1427,13 @@ function m.getObjectValue(obj) end if obj.type == 'field' or obj.type == 'method' then - return obj.parent.value + return obj.parent and obj.parent.value end if obj.type == 'call' then if obj.node.special == 'rawset' then return obj.args[3] + else + return obj end end if obj.type == 'select' then @@ -1413,9 +1442,9 @@ function m.getObjectValue(obj) return nil end -function m.checkSameSimpleInValueInMetaTable(status, mt, start, queue) +function m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) local newStatus = m.status(status) - m.searchFields(newStatus, mt, '__index') + m.searchDefFields(newStatus, mt, '__index') local refsStatus = m.status(status) for i = 1, #newStatus.results do local indexValue = m.getObjectValue(newStatus.results[i]) @@ -1425,14 +1454,10 @@ function m.checkSameSimpleInValueInMetaTable(status, mt, start, queue) end for i = 1, #refsStatus.results do local obj = refsStatus.results[i] - queue[#queue+1] = { - obj = obj, - start = start, - force = true, - } + pushQueue(obj, start, true) end end -function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, queue) +function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) if not func or func.special ~= 'setmetatable' then return end @@ -1441,48 +1466,60 @@ function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, queue) local obj = args[1] local mt = args[2] if obj then - queue[#queue+1] = { - obj = obj, - start = start, - force = true, - } + pushQueue(obj, start, true) end if mt then - m.checkSameSimpleInValueInMetaTable(status, mt, start, queue) + m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) end end -function m.checkSameSimpleInValueOfCallMetaTable(status, call, start, queue) +function m.checkSameSimpleInValueOfCallMetaTable(status, call, start, pushQueue) if status.crossMetaTableMark then return end status.crossMetaTableMark = true if call.type == 'call' then - m.checkSameSimpleInValueOfSetMetaTable(status, call.node, start, queue) + m.checkSameSimpleInValueOfSetMetaTable(status, call.node, start, pushQueue) end status.crossMetaTableMark = false end -function m.checkSameSimpleInSpecialBranch(status, obj, start, queue) +function m.checkSameSimpleInSpecialBranch(status, obj, start, pushQueue) if status.interface.index then local results = status.interface.index(obj) if not results then return end for _, res in ipairs(results) do - queue[#queue+1] = { - obj = res, - start = start + 1, - } + pushQueue(obj, start + 1) end end end +local function appendValidGenericType(results, status, typeName, obj) + if typeName.parent.type == 'doc.type.typeliteral' then + if obj.type == 'string' and status.interface.docType then + local docs = status.interface.docType(obj[1]) + for i = 1, #docs do + local doc = docs[i] + if doc.type == 'doc.class.name' + or doc.type == 'doc.alias.name' then + results[#results+1] = doc + break + end + end + end + else + -- 发现没有使用 `T`,则沿用既有逻辑直接返回实参 + results[#results+1] = obj + end +end + local function stepRefOfGeneric(status, typeUnit, args, mode) + local results = {} if not args then - return nil + return results end - local results = {} local myName = typeUnit[1] for _, typeName in ipairs(typeUnit.typeGeneric[myName]) do if typeName == typeUnit then @@ -1502,7 +1539,7 @@ local function stepRefOfGeneric(status, typeUnit, args, mode) and source.parent.type == 'funcargs' then for index, arg in ipairs(source.parent) do if arg == source then - results[#results+1] = args[index] + appendValidGenericType(results, status, typeName, args[index]) end end end @@ -1536,7 +1573,7 @@ function m.checkSameSimpleByDocType(status, doc, args) return results end -function m.checkSameSimpleByBindDocs(status, obj, start, queue, mode) +function m.checkSameSimpleByBindDocs(status, obj, start, pushQueue, mode) if not obj.bindDocs then return end @@ -1567,30 +1604,19 @@ function m.checkSameSimpleByBindDocs(status, obj, start, queue, mode) for _, res in ipairs(results) do if res.type == 'doc.class' or res.type == 'doc.type' then - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) skipInfer = true end if res.type == 'doc.type.function' then - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) elseif res.type == 'doc.field' then - queue[#queue+1] = { - obj = res, - start = start + 1, - } + pushQueue(res, start + 1) end end return skipInfer end -function m.checkSameSimpleOfRefByDocSource(status, obj, start, queue, mode) +function m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) if status.share.searchingBindedDoc then return end @@ -1608,15 +1634,11 @@ function m.checkSameSimpleOfRefByDocSource(status, obj, start, queue, mode) end status.share.searchingBindedDoc = nil for _, res in ipairs(newStatus.results) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end end -function m.checkSameSimpleByDoc(status, obj, start, queue, mode) +function m.checkSameSimpleByDoc(status, obj, start, pushQueue, mode) if obj.type == 'doc.class.name' or obj.type == 'doc.class' then if obj.type == 'doc.class.name' then @@ -1630,30 +1652,19 @@ function m.checkSameSimpleByDoc(status, obj, start, queue, mode) classStart = false end if classStart and doc.type == 'doc.field' then - queue[#queue+1] = { - obj = doc, - start = start + 1, - } + pushQueue(doc, start + 1) end end - m.checkSameSimpleOfRefByDocSource(status, obj, start, queue, mode) + m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) if mode == 'ref' then local pieceResult = stepRefOfDocType(status, obj.class, 'ref') for _, res in ipairs(pieceResult) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end if obj.extends then local pieceResult = stepRefOfDocType(status, obj.extends, 'def') for _, res in ipairs(pieceResult) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end end end @@ -1662,47 +1673,36 @@ function m.checkSameSimpleByDoc(status, obj, start, queue, mode) for _, piece in ipairs(obj.types) do local pieceResult = stepRefOfDocType(status, piece, 'def') for _, res in ipairs(pieceResult) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end end if mode == 'ref' then - m.checkSameSimpleOfRefByDocSource(status, obj, start, queue, mode) + m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) end return true elseif obj.type == 'doc.type.name' or obj.type == 'doc.see.name' then local pieceResult = stepRefOfDocType(status, obj, 'def') for _, res in ipairs(pieceResult) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end local state = m.getDocState(obj) if state.type == 'doc.type' and mode == 'ref' then - m.checkSameSimpleOfRefByDocSource(status, state, start, queue, mode) + m.checkSameSimpleOfRefByDocSource(status, state, start, pushQueue, mode) end return true elseif obj.type == 'doc.field' then - if mode ~= 'field' then - return m.checkSameSimpleByDoc(status, obj.extends, start, queue, mode) + if mode ~= 'field' + and mode ~= 'deffield' then + return m.checkSameSimpleByDoc(status, obj.extends, start, pushQueue, mode) end elseif obj.type == 'doc.type.array' then - queue[#queue+1] = { - obj = obj.node, - start = start + 1, - force = true, - } + pushQueue(obj.node, start + 1, true) return true end end -function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, queue) +function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, pushQueue) local args = obj.parent if not args or args.type ~= 'callargs' then return @@ -1715,11 +1715,11 @@ function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, queue) if m.checkValueMark(status, obj, mt) then return end - m.checkSameSimpleInValueInMetaTable(status, mt, start, queue) + m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue) end end -function m.searchSameMethodCrossSelf(ref, mark) +function m.searchSameMethodOutSelf(ref, mark) local selfNode if ref.tag == 'self' then selfNode = ref @@ -1737,70 +1737,76 @@ function m.searchSameMethodCrossSelf(ref, mark) return nil end mark[selfNode] = true - return selfNode.method.node + local method = selfNode.method.node + if mark[method] then + return nil + end + mark[method] = true + return method end end -function m.searchSameMethod(ref, mark) - if mark['method'] then - return nil - end +function m.searchSameMethodIntoSelf(ref, mark) local nxt = ref.next if not nxt then return nil end - if nxt.type == 'setmethod' then - mark['method'] = true - return ref + if nxt.type ~= 'setmethod' then + return nil end - return nil + if mark[ref] then + return nil + end + mark[ref] = true + local value = nxt.value + if not value or value.type ~= 'function' then + return nil + end + local selfRef = value.locals and value.locals[1] + if not selfRef or selfRef.tag ~= 'self' then + return nil + end + if mark[selfRef] then + return nil + end + mark[selfRef] = true + return selfRef end -function m.searchSameFieldsCrossMethod(status, ref, start, queue) +function m.searchSameFieldsCrossMethod(status, ref, start, pushQueue) + if status.share.crossMethodLock then + return + end local mark = status.crossMethodMark if not mark then mark = {} status.crossMethodMark = mark end - local method = m.searchSameMethod(ref, mark) - or m.searchSameMethodCrossSelf(ref, mark) - if not method then + local selfRef = m.searchSameMethodIntoSelf(ref, mark) + if selfRef then + tracy.ZoneBeginN 'searchSameFieldsCrossMethod' + local _ <close> = tracy.ZoneEnd + -- 如果自己是method,则只检查自己内部的self引用 + local results = m.getStepRef(status, selfRef, 'ref') + for _, res in ipairs(results) do + pushQueue(res, start, true) + end return end - local methodStatus = m.status(status) - m.searchRefs(methodStatus, method, 'ref') - for _, md in ipairs(methodStatus.results) do - queue[#queue+1] = { - obj = md, - start = start, - force = true, - } - local nxt = md.next - if not nxt then - goto CONTINUE - end - if nxt.type == 'setmethod' then - local func = nxt.value - if not func then - goto CONTINUE - end - local selfNode = func.locals and func.locals[1] - if not selfNode or not selfNode.ref then - goto CONTINUE - end - if mark[selfNode] then - goto CONTINUE - end - mark[selfNode] = true - for _, selfRef in ipairs(selfNode.ref) do - queue[#queue+1] = { - obj = selfRef, - start = start, - force = true, - } - end + local method = m.searchSameMethodOutSelf(ref, mark) + if method then + tracy.ZoneBeginN 'searchSameFieldsCrossMethod' + local _ <close> = tracy.ZoneEnd + -- 如果自己是self,则找出父级的method,以及父级method的引用 + local newStatus = m.status(status) + newStatus.crossMethodLock = true + m.searchRefs(newStatus, method, 'ref') + newStatus.crossMethodLock = false + for _, res in ipairs(newStatus.results) do + mark[res] = true + pushQueue(res, start, true) end - ::CONTINUE:: + return end end @@ -1885,7 +1891,7 @@ function m.checkSameSimpleInCallInSameFile(status, func, args, index) return results end -function m.checkSameSimpleInCall(status, ref, start, queue, mode) +function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode) local func, args, index = m.getCallValue(ref) if not func then return @@ -1899,7 +1905,7 @@ function m.checkSameSimpleInCall(status, ref, start, queue, mode) end status.share.crossCallCount = status.share.crossCallCount + 1 -- 检查赋值是 semetatable() 的情况 - m.checkSameSimpleInValueOfSetMetaTable(status, func, start, queue) + m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) -- 检查赋值是 func() 的情况 local objs = m.checkSameSimpleInCallInSameFile(status, func, args, index) if status.interface.call then @@ -1916,19 +1922,11 @@ function m.checkSameSimpleInCall(status, ref, start, queue, mode) local newStatus = m.status(status) for _, obj in ipairs(objs) do m.searchRefs(newStatus, obj, mode) - queue[#queue+1] = { - obj = obj, - start = start, - force = true, - } + pushQueue(obj, start, true) end status.share.crossCallCount = status.share.crossCallCount - 1 for _, obj in ipairs(newStatus.results) do - queue[#queue+1] = { - obj = obj, - start = start, - force = true, - } + pushQueue(obj, start, true) end end @@ -1997,7 +1995,7 @@ function m.findGlobalsOfName(ast, name) return results end -function m.checkSameSimpleInGlobal(status, name, source, start, queue) +function m.checkSameSimpleInGlobal(status, name, source, start, pushQueue) if not name then return end @@ -2009,11 +2007,7 @@ function m.checkSameSimpleInGlobal(status, name, source, start, queue) end if objs then for _, obj in ipairs(objs) do - queue[#queue+1] = { - obj = obj, - start = start, - force = true, - } + pushQueue(obj, start, true) end end end @@ -2054,7 +2048,7 @@ function m.checkReturnMark(status, a, mark) return result end -function m.searchSameFieldsInValue(status, ref, start, queue, mode) +function m.searchSameFieldsInValue(status, ref, start, pushQueue, mode) local value = m.getObjectValue(ref) if not value then return @@ -2065,22 +2059,14 @@ function m.searchSameFieldsInValue(status, ref, start, queue, mode) local newStatus = m.status(status) m.searchRefs(newStatus, value, mode) for _, res in ipairs(newStatus.results) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end - queue[#queue+1] = { - obj = value, - start = start, - force = true, - } + pushQueue(value, start, true) -- 检查形如 a = f() 的分支情况 - m.checkSameSimpleInCall(status, value, start, queue, mode) + m.checkSameSimpleInCall(status, value, start, pushQueue, mode) end -function m.checkSameSimpleAsTableField(status, ref, start, queue) +function m.checkSameSimpleAsTableField(status, ref, start, pushQueue) if not status.deep then --return end @@ -2094,11 +2080,7 @@ function m.checkSameSimpleAsTableField(status, ref, start, queue) local newStatus = m.status(status) m.searchRefs(newStatus, parent.field, 'ref') for _, res in ipairs(newStatus.results) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end end @@ -2112,7 +2094,7 @@ function m.checkSearchLevel(status) return false end -function m.checkSameSimpleAsReturn(status, ref, start, queue) +function m.checkSameSimpleAsReturn(status, ref, start, pushQueue) if not status.deep then return end @@ -2129,16 +2111,15 @@ function m.checkSameSimpleAsReturn(status, ref, start, queue) m.searchRefsAsFunctionReturn(newStatus, ref, 'ref') for _, res in ipairs(newStatus.results) do if not m.checkCallMark(status, res) then - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end end end -function m.checkSameSimpleAsSetValue(status, ref, start, queue) +function m.checkSameSimpleAsSetValue(status, ref, start, pushQueue) + if not status.deep then + --return + end if ref.type == 'select' then return end @@ -2171,11 +2152,7 @@ function m.checkSameSimpleAsSetValue(status, ref, start, queue) local newStatus = m.status(status) m.searchRefs(newStatus, obj, 'ref') for _, res in ipairs(newStatus.results) do - queue[#queue+1] = { - obj = res, - start = start, - force = true, - } + pushQueue(res, start, true) end end @@ -2192,7 +2169,7 @@ local function hasTypeName(doc, name) return false end -function m.checkSameSimpleInString(status, ref, start, queue, mode) +function m.checkSameSimpleInString(status, ref, start, pushQueue, mode) -- 特殊处理 ('xxx').xxx 的形式 if ref.type ~= 'string' and not hasTypeName(ref, 'string') then @@ -2204,6 +2181,13 @@ function m.checkSameSimpleInString(status, ref, start, queue, mode) if status.share.searchingBindedDoc then return end + if not status.share.markString then + status.share.markString = {} + end + if status.share.markString[ref] then + return + end + status.share.markString[ref] = true local newStatus = m.status(status) local docs = status.interface.docType('string*') local mark = {} @@ -2216,12 +2200,10 @@ function m.checkSameSimpleInString(status, ref, start, queue, mode) goto CONTINUE end mark[res] = true - queue[#queue+1] = { - obj = res, - start = start + 1, - } + pushQueue(res, start + 1) ::CONTINUE:: end + status.share.markString[ref] = nil return true end @@ -2259,7 +2241,7 @@ function m.pushResult(status, mode, ref, simple) end end if m.isLiteral(ref) - and ref.parent.type == 'callargs' + and ref.parent and ref.parent.type == 'callargs' and ref ~= simple.node then results[#results+1] = ref end @@ -2316,7 +2298,7 @@ function m.pushResult(status, mode, ref, simple) results[#results+1] = ref elseif ref.type == 'getindex' then -- do not trust `t[1]` - if ref.index.type == 'string' then + if ref.index and ref.index.type == 'string' then results[#results+1] = ref end elseif ref.type == 'setglobal' @@ -2336,6 +2318,30 @@ function m.pushResult(status, mode, ref, simple) or ref.type == 'doc.field' then results[#results+1] = ref end + elseif mode == 'deffield' then + if ref.type == 'setfield' + or ref.type == 'tablefield' then + results[#results+1] = ref + elseif ref.type == 'setmethod' then + results[#results+1] = ref + elseif ref.type == 'setindex' + or ref.type == 'tableindex' then + results[#results+1] = ref + elseif ref.type == 'setglobal' then + results[#results+1] = ref + elseif ref.type == 'function' then + results[#results+1] = ref + elseif ref.type == 'table' then + results[#results+1] = ref + elseif ref.type == 'call' then + if ref.node.special == 'rawset' then + results[#results+1] = ref + end + elseif ref.type == 'doc.type.function' + or ref.type == 'doc.class.name' + or ref.type == 'doc.field' then + results[#results+1] = ref + end end end @@ -2344,7 +2350,7 @@ function m.checkSameSimpleName(ref, sm) return true end - if sm == m.ANYNOTGET and not m.isGet(ref) then + if sm == m.ANY_DEF and m.isSet(ref) then return true end @@ -2358,10 +2364,7 @@ function m.checkSameSimpleName(ref, sm) return false end -function m.checkSameSimple(status, simple, data, mode, queue) - local ref = data.obj - local start = data.start - local force = data.force +function m.checkSameSimple(status, simple, ref, start, force, mode, pushQueue) if start > #simple then return end @@ -2376,30 +2379,30 @@ function m.checkSameSimple(status, simple, data, mode, queue) cmode = 'ref' end -- 检查 doc - local skipInfer = m.checkSameSimpleByBindDocs(status, ref, i, queue, cmode) - or m.checkSameSimpleByDoc(status, ref, i, queue, cmode) + local skipInfer = m.checkSameSimpleByBindDocs(status, ref, i, pushQueue, cmode) + or m.checkSameSimpleByDoc(status, ref, i, pushQueue, cmode) -- 检查自己是字符串的分支情况 - m.checkSameSimpleInString(status, ref, i, queue, cmode) + m.checkSameSimpleInString(status, ref, i, pushQueue, cmode) if not skipInfer then -- 穿透 self:func 与 mt:func - m.searchSameFieldsCrossMethod(status, ref, i, queue) + m.searchSameFieldsCrossMethod(status, ref, i, pushQueue) -- 穿透赋值 - m.searchSameFieldsInValue(status, ref, i, queue, cmode) + m.searchSameFieldsInValue(status, ref, i, pushQueue, cmode) -- 检查自己是字面量表的情况 - m.checkSameSimpleInValueOfTable(status, ref, i, queue) + m.checkSameSimpleInValueOfTable(status, ref, i, pushQueue) -- 检查自己作为 setmetatable 第一个参数的情况 - m.checkSameSimpleInArg1OfSetMetaTable(status, ref, i, queue) + m.checkSameSimpleInArg1OfSetMetaTable(status, ref, i, pushQueue) -- 检查自己作为 setmetatable 调用的情况 - m.checkSameSimpleInValueOfCallMetaTable(status, ref, i, queue) + m.checkSameSimpleInValueOfCallMetaTable(status, ref, i, pushQueue) -- 检查自己是特殊变量的分支的情况 - m.checkSameSimpleInSpecialBranch(status, ref, i, queue) + m.checkSameSimpleInSpecialBranch(status, ref, i, pushQueue) if cmode == 'ref' then -- 检查形如 { a = f } 的情况 - m.checkSameSimpleAsTableField(status, ref, i, queue) + m.checkSameSimpleAsTableField(status, ref, i, pushQueue) -- 检查形如 return m 的情况 - m.checkSameSimpleAsReturn(status, ref, i, queue) + m.checkSameSimpleAsReturn(status, ref, i, pushQueue) -- 检查形如 a = f 的情况 - m.checkSameSimpleAsSetValue(status, ref, i, queue) + m.checkSameSimpleAsSetValue(status, ref, i, pushQueue) end end if i == #simple then @@ -2417,55 +2420,81 @@ function m.checkSameSimple(status, simple, data, mode, queue) end end +local queuesPool = {} +local startsPool = {} +local forcesPool = {} +local poolSize = 0 + +local function allocQueue() + if poolSize <= 0 then + return {}, {}, {} + else + local queues = queuesPool[poolSize] + local starts = startsPool[poolSize] + local forces = forcesPool[poolSize] + poolSize = poolSize - 1 + return queues, starts, forces + end +end + +local function deallocQueue(queues, starts, forces) + poolSize = poolSize + 1 + queuesPool[poolSize] = queues + startsPool[poolSize] = starts + forcesPool[poolSize] = forces +end + function m.searchSameFields(status, simple, mode) - local queue = {} + local queues, starts, forces = allocQueue() + local queueLen = 0 + local function pushQueue(obj, start, force) + queueLen = queueLen + 1 + queues[queueLen] = obj + starts[queueLen] = start + forces[queueLen] = force + end if simple.mode == 'global' then -- 全局变量开头 - m.checkSameSimpleInGlobal(status, simple[1], simple.node, 1, queue) + m.checkSameSimpleInGlobal(status, simple[1], simple.node, 1, pushQueue) elseif simple.mode == 'local' then -- 局部变量开头 - queue[1] = { - obj = simple.node, - start = 1, - } + pushQueue(simple.node, 1) local refs = simple.node.ref if refs then for i = 1, #refs do - queue[#queue+1] = { - obj = refs[i], - start = 1, - } + pushQueue(refs[i], 1) end end else - queue[1] = { - obj = simple.node, - start = 1, - } + pushQueue(simple.node, 1) end local max = 0 local locks = {} for i = 1, 1e6 do - local data = queue[i] - if not data then - return + if queueLen <= 0 then + break end - local lock = locks[data.start] + local obj = queues[queueLen] + local start = starts[queueLen] + local force = forces[queueLen] + queueLen = queueLen - 1 + local lock = locks[start] if not lock then lock = {} - locks[data.start] = lock + locks[start] = lock end - if not lock[data.obj] then - lock[data.obj] = true + if not lock[obj] then + lock[obj] = true max = max + 1 status.share.count = status.share.count + 1 - m.checkSameSimple(status, simple, data, mode, queue) + m.checkSameSimple(status, simple, obj, start, force, mode, pushQueue) if max >= 10000 then logWarn('Queue too large!') break end end end + deallocQueue(queues, starts, forces) end function m.getCallerInSameFile(status, func) @@ -2655,6 +2684,9 @@ function m.getRefCache(status, obj, mode) end function m.searchRefs(status, obj, mode) + if not obj then + return + end local cache, makeCache = m.getRefCache(status, obj, mode) if cache then for i = 1, #cache do @@ -2664,13 +2696,16 @@ function m.searchRefs(status, obj, mode) end -- 检查单步引用 + tracy.ZoneBeginN('searchRefs getStepRef') local res = m.getStepRef(status, obj, mode) if res then for i = 1, #res do status.results[#status.results+1] = res[i] end end + tracy.ZoneEnd() -- 检查simple + tracy.ZoneBeginN('searchRefs searchSameFields') if status.depth <= 100 then local simple = m.getSimple(obj) if simple then @@ -2684,6 +2719,7 @@ function m.searchRefs(status, obj, mode) logWarn('status.depth overflow') end end + tracy.ZoneEnd() m.cleanResults(status.results) @@ -3283,6 +3319,8 @@ local function mathCheck(status, a, b) or m.getInferLiteral(status, a, 'number') local v2 = m.getInferLiteral(status, b, 'integer') or m.getInferLiteral(status, a, 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local int = m.hasType(status, a, 'integer') and m.hasType(status, b, 'integer') and not m.hasType(status, a, 'number') @@ -3374,6 +3412,8 @@ function m.inferCheckBinary(status, source) or m.getInferLiteral(status, source[1], 'number') local v2 = m.getInferLiteral(status, source[2], 'integer') or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local v if v1 and v2 then v = v1 <= v2 @@ -3390,6 +3430,8 @@ function m.inferCheckBinary(status, source) or m.getInferLiteral(status, source[1], 'number') local v2 = m.getInferLiteral(status, source[2], 'integer') or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local v if v1 and v2 then v = v1 >= v2 @@ -3406,6 +3448,8 @@ function m.inferCheckBinary(status, source) or m.getInferLiteral(status, source[1], 'number') local v2 = m.getInferLiteral(status, source[2], 'integer') or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local v if v1 and v2 then v = v1 < v2 @@ -3422,6 +3466,8 @@ function m.inferCheckBinary(status, source) or m.getInferLiteral(status, source[1], 'number') local v2 = m.getInferLiteral(status, source[2], 'integer') or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local v if v1 and v2 then v = v1 > v2 @@ -3436,6 +3482,8 @@ function m.inferCheckBinary(status, source) elseif op.type == '|' then local v1 = m.getInferLiteral(status, source[1], 'integer') local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) local v if v1 and v2 then v = v1 | v2 @@ -3450,6 +3498,8 @@ function m.inferCheckBinary(status, source) elseif op.type == '~' then local v1 = m.getInferLiteral(status, source[1], 'integer') local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) local v if v1 and v2 then v = v1 ~ v2 @@ -3464,6 +3514,8 @@ function m.inferCheckBinary(status, source) elseif op.type == '&' then local v1 = m.getInferLiteral(status, source[1], 'integer') local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) local v if v1 and v2 then v = v1 & v2 @@ -3478,6 +3530,8 @@ function m.inferCheckBinary(status, source) elseif op.type == '<<' then local v1 = m.getInferLiteral(status, source[1], 'integer') local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) local v if v1 and v2 then v = v1 << v2 @@ -3492,6 +3546,8 @@ function m.inferCheckBinary(status, source) elseif op.type == '>>' then local v1 = m.getInferLiteral(status, source[1], 'integer') local v2 = m.getInferLiteral(status, source[2], 'integer') + v1 = tointeger(v1) + v2 = tointeger(v2) local v if v1 and v2 then v = v1 >> v2 @@ -3506,6 +3562,8 @@ function m.inferCheckBinary(status, source) elseif op.type == '..' then local v1 = m.getInferLiteral(status, source[1], 'string') local v2 = m.getInferLiteral(status, source[2], 'string') + v1 = type(v1) == 'string' and v1 or nil + v2 = type(v2) == 'string' and v2 or nil local v if v1 and v2 then v = v1 .. v2 @@ -3522,6 +3580,8 @@ function m.inferCheckBinary(status, source) or m.getInferLiteral(status, source[1], 'number') local v2 = m.getInferLiteral(status, source[2], 'integer') or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local v if v1 and v2 then v = v1 ^ v2 @@ -3538,9 +3598,11 @@ function m.inferCheckBinary(status, source) or m.getInferLiteral(status, source[1], 'number') local v2 = m.getInferLiteral(status, source[2], 'integer') or m.getInferLiteral(status, source[2], 'number') + v1 = tonumber(v1) + v2 = tonumber(v2) local v - if v1 and v2 then - v = v1 > v2 + if v1 and v2 and v2 ~= 0 then + v = v1 / v2 end status.results = m.allocInfer { type = 'number', @@ -3581,7 +3643,7 @@ function m.inferCheckBinary(status, source) local int, v1, v2 = mathCheck(status, source[1], source[2]) status.results = m.allocInfer { type = int, - value = (v1 and v2) and (v1 % v2) or nil, + value = (v1 and v2 and v2 ~= 0) and (v1 % v2) or nil, source = source, level = 100, } @@ -3590,7 +3652,7 @@ function m.inferCheckBinary(status, source) local int, v1, v2 = mathCheck(status, source[1], source[2]) status.results = m.allocInfer { type = int, - value = (v1 and v2) and (v1 // v2) or nil, + value = (v1 and v2 and v2 ~= 0) and (v1 // v2) or nil, source = source, level = 100, } @@ -3608,7 +3670,9 @@ function m.inferByDef(status, obj) status.share.inferedDef[obj] = true local mark = {} local newStatus = m.status(status, status.interface) + tracy.ZoneBeginN('inferByDef searchRefs') m.searchRefs(newStatus, obj, 'def') + tracy.ZoneEnd() for _, src in ipairs(newStatus.results) do local inferStatus = m.status(newStatus) m.searchInfer(inferStatus, src) @@ -3913,6 +3977,9 @@ function m.inferByPCallReturn(status, source) if not call or call.type ~= 'call' then return end + if not call.args then + return + end local node = call.node local specialName = node.special local func, index @@ -3989,6 +4056,9 @@ function m.searchInfer(status, obj) end obj = value end + if not obj then + return + end local cache, makeCache = m.getRefCache(status, obj, 'infer') if cache then @@ -4024,7 +4094,9 @@ function m.searchInfer(status, obj) end if status.deep then + tracy.ZoneBeginN('inferByDef') m.inferByDef(status, obj) + tracy.ZoneEnd() end m.inferBySet(status, obj) m.inferByCall(status, obj) @@ -4068,8 +4140,8 @@ function m.requestDefinition(obj, interface, deep) return status.results, status.share.count end ---- 请求对象的域 ----@param filterKey nil|string|table nil表fields不做限制;string表fields必须同名;table取值为guild.ANYSET表fields必须满足isSet() +--- 请求对象的字段 +---@param filterKey nil|string|table function m.requestFields(obj, interface, deep, filterKey) local status = m.status(nil, interface, deep) @@ -4078,6 +4150,16 @@ function m.requestFields(obj, interface, deep, filterKey) return status.results, status.share.count end +--- 请求对象的定义字段 +---@param filterKey nil|string|table +function m.requestDefFields(obj, interface, deep, filterKey) + local status = m.status(nil, interface, deep) + + m.searchDefFields(status, obj, filterKey) + + return status.results, status.share.count +end + --- 请求对象的类型推测 function m.requestInfer(obj, interface, deep) local status = m.status(nil, interface, deep) diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index ae644a65..990b4606 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -56,6 +56,7 @@ Symbol <- ({} { / '...' / '+' / '#' + / '`' } {}) -> Symbol ]], { @@ -450,12 +451,41 @@ function parseType(parent) if not tp then break end + + -- 处理 `T` 的情况 + local typeLiteral = nil + if tp == 'symbol' and content == '`' then + nextToken() + if not checkToken('symbol', '`', 2) then + break + end + tp, content = peekToken() + if not tp then + break + end + -- TypeLiteral,指代类型的字面值。比如,对于类 Cat 来说,它的 TypeLiteral 是 "Cat" + typeLiteral = { + type = 'doc.type.typeliteral', + parent = result, + start = getStart(), + finish = nil, + node = nil, + } + end + if tp == 'name' then nextToken() local typeUnit = parseTypeUnit(result, content) if not typeUnit then break end + if typeLiteral then + nextToken() + typeLiteral.finish = getFinish() + typeLiteral.node = typeUnit + typeUnit.parent = typeLiteral + typeUnit = typeLiteral + end result.types[#result.types+1] = typeUnit if not result.start then result.start = typeUnit.start @@ -907,11 +937,22 @@ local function buildLuaDoc(comment) return result end +---当前行在注释doc前是否有代码 +local function haveCodeBeforeDocInCurLine(lineData, docStartCol) + return docStartCol > lineData.sp + lineData.tab + 3 +end + local function isNextLine(lns, binded, doc) if not binded then return false end local lastDoc = binded[#binded] + local lastDocStartRow, lastDocStartCol = guide.positionOf(lns, lastDoc.originalComment.start) + local lastDocStartLineData = guide.lineData(lns, lastDocStartRow) + if haveCodeBeforeDocInCurLine(lastDocStartLineData, lastDocStartCol) then + return false + end + local lastRow = guide.positionOf(lns, lastDoc.finish) local newRow = guide.positionOf(lns, doc.start) return newRow - lastRow == 1 @@ -1034,6 +1075,7 @@ return function (_, state) if ast.finish < doc.finish then ast.finish = doc.finish end + doc.originalComment = comment end end diff --git a/script/proto/define.lua b/script/proto/define.lua index 88493abe..50f4cd87 100644 --- a/script/proto/define.lua +++ b/script/proto/define.lua @@ -157,6 +157,7 @@ m.DiagnosticDefaultSeverity = { ['redundant-value'] = 'Hint', ['code-after-break'] = 'Hint', ['unbalanced-assignments'] = 'Warning', + ['close-non-object'] = 'Warning', ['duplicate-doc-class'] = 'Warning', ['undefined-doc-class'] = 'Warning', @@ -196,6 +197,7 @@ m.DiagnosticDefaultNeededFileStatus = { ['redundant-value'] = 'Opened', ['code-after-break'] = 'Opened', ['unbalanced-assignments'] = 'Any', + ['close-non-object'] = 'Any', ['duplicate-doc-class'] = 'Any', ['undefined-doc-class'] = 'Any', diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index b155a591..1d8b779d 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -64,6 +64,9 @@ end local function buildDiagnostic(uri, diag) local lines = files.getLines(uri) local text = files.getText(uri) + if not text or not lines then + return + end local relatedInformation if diag.related then diff --git a/script/provider/provider.lua b/script/provider/provider.lua index 2fb999e7..32778399 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -42,6 +42,11 @@ local function updateConfig() exclude = configs[3], } + if not updated then + log.warn('No config?', util.dump(configs)) + return + end + local oldConfig = util.deepCopy(config.config) local oldOther = util.deepCopy(config.other) config.setConfig(updated, other) @@ -206,7 +211,10 @@ proto.on('textDocument/hover', function (params) end local md = markdown() md:add('lua', hover.label) - md:add('md', "---") + if hover.label and #hover.label > 0 + and hover.description and #hover.description > 0 then + md:add('md', "---") + end md:add('md', hover.description) return { contents = { diff --git a/script/service/service.lua b/script/service/service.lua index 82c192b6..41c8e7bf 100644 --- a/script/service/service.lua +++ b/script/service/service.lua @@ -145,12 +145,24 @@ function m.startTimer() end end +function m.testVersion() + local stack = debug.setcstacklimit(200) + debug.setcstacklimit(stack + 1) + if debug.setcstacklimit(stack) == stack + 1 then + proto.notify('window/showMessage', { + type = 2, + message = 'It seems to be running in Lua 5.4.0 or Lua 5.4.1 . Please upgrade to Lua 5.4.2 or above. Otherwise, it may encounter weird "C stack overflow", resulting in failure to work properly', + }) + end +end + function m.start() util.enableCloseFunction() await.setErrorHandle(log.error) pub.recruitBraves(4) proto.listen() m.report() + m.testVersion() require 'provider' diff --git a/script/service/telemetry.lua b/script/service/telemetry.lua index ec004a27..52ad193b 100644 --- a/script/service/telemetry.lua +++ b/script/service/telemetry.lua @@ -1,9 +1,10 @@ -local net = require 'service.net' -local timer = require 'timer' -local config = require 'config' -local client = require 'provider.client' -local nonil = require 'without-check-nil' -local util = require 'utility' +local net = require 'service.net' +local timer = require 'timer' +local config = require 'config' +local client = require 'provider.client' +local nonil = require 'without-check-nil' +local util = require 'utility' +local platform = require 'bee.platform' local tokenPath = (ROOT / 'log' / 'token'):string() local token = util.loadFile(tokenPath) @@ -34,6 +35,16 @@ local function pushClientInfo(link) )) end +local function pushPlatformInfo(link) + send(link, string.pack('zzzzz' + , 'platform' + , token + , ('%s %s'):format(platform.OS, platform.Arch) + , ('%s %s'):format(platform.CRT, platform.CRTVersion) + , ('%s %s'):format(platform.Compiler, platform.CompilerVersion) + )) +end + local function pushErrorLog(link) if not log.firstError then return @@ -53,9 +64,19 @@ timer.wait(5, function () if not config.config.telemetry.enable then return end - local link = net.connect('tcp', '119.45.194.183', 11577) - pushClientInfo(link) - pushErrorLog(link) + local suc, link = pcall(net.connect, 'tcp', 'moe-loli.love', 11577) + if not suc then + suc, link = pcall(net.connect, 'tcp', '119.45.194.183', 11577) + end + if not suc then + return + end + function link:on_connect() + pushClientInfo(link) + pushPlatformInfo(link) + pushErrorLog(link) + self:close() + end end)() timer.loop(1, function () if not config.config.telemetry.enable then diff --git a/script/timer.lua b/script/timer.lua index 1d4343f1..7857be6f 100644 --- a/script/timer.lua +++ b/script/timer.lua @@ -2,6 +2,8 @@ local setmetatable = setmetatable local mathMax = math.max local mathFloor = math.floor local osClock = os.clock +local xpcall = xpcall +local logError = log.error _ENV = nil @@ -43,7 +45,7 @@ local function mWakeup(self) end self._running = false if self._onTimer then - self:_onTimer() + xpcall(self._onTimer, logError, self) end if self._removed then return diff --git a/script/tracy.lua b/script/tracy.lua new file mode 100644 index 00000000..bf93a103 --- /dev/null +++ b/script/tracy.lua @@ -0,0 +1,30 @@ +local originTracy + +local function enable() + if not originTracy then + local suc = pcall(require, 'luatracy') + if suc then + originTracy = tracy + else + originTracy = { + ZoneBeginN = function (info) end, + ZoneEnd = function () end, + } + end + end + tracy = originTracy +end + +local function disable() + tracy = { + ZoneBeginN = function (info) end, + ZoneEnd = function () end, + } +end + +disable() + +return { + enable = enable, + disable = disable, +} diff --git a/script/utility.lua b/script/utility.lua index 2386998b..a1eec7eb 100644 --- a/script/utility.lua +++ b/script/utility.lua @@ -19,6 +19,7 @@ local utf8Len = utf8.len local mathHuge = math.huge local inf = 1 / 0 local nan = 0 / 0 +local utf8 = utf8 _ENV = nil @@ -477,11 +478,18 @@ function m.viewLiteral(v) end function m.utf8Len(str, start, finish) - local len, pos = utf8Len(str, start, finish, true) - if len then - return len + local len = 0 + for _ = 1, 10000 do + local clen, pos = utf8Len(str, start, finish, true) + if clen then + len = len + clen + break + else + len = len + 1 + utf8Len(str, start, pos - 1, true) + start = pos + 1 + end end - return 1 + m.utf8Len(str, start, pos-1) + m.utf8Len(str, pos+1, finish) + return len end function m.revertTable(t) diff --git a/script/vm/eachDef.lua b/script/vm/eachDef.lua index 7825d2b1..8d031f42 100644 --- a/script/vm/eachDef.lua +++ b/script/vm/eachDef.lua @@ -33,6 +33,9 @@ function vm.getDefs(source, deep) deep = deep or -999 if guide.isGlobal(source) then local key = guide.getKeyName(source) + if not key then + return {} + end return vm.getGlobalSets(key) else local cache = vm.getCache('eachDef')[source] diff --git a/script/vm/eachField.lua b/script/vm/eachField.lua index 2620aa41..690f6aa4 100644 --- a/script/vm/eachField.lua +++ b/script/vm/eachField.lua @@ -25,6 +25,27 @@ local function getFields(source, deep, filterKey) return results end +local function getDefFields(source, deep, filterKey) + local unlock = vm.lock('eachDefField', source) + if not unlock then + return {} + end + + while source.type == 'paren' do + source = source.exp + if not source then + return {} + end + end + deep = config.config.intelliSense.searchDepth + (deep or 0) + + await.delay() + local results = guide.requestDefFields(source, vm.interface, deep, filterKey) + + unlock() + return results +end + local function getFieldsBySource(source, deep, filterKey) deep = deep or -999 local cache = vm.getCache('eachField')[source] @@ -38,12 +59,28 @@ local function getFieldsBySource(source, deep, filterKey) return cache end +local function getDefFieldsBySource(source, deep, filterKey) + deep = deep or -999 + local cache = vm.getCache('eachDefField')[source] + if not cache or cache.deep < deep then + cache = getDefFields(source, deep, filterKey) + cache.deep = deep + if not filterKey then + vm.getCache('eachDefField')[source] = cache + end + end + return cache +end + function vm.getFields(source, deep) if source.special == '_G' then return vm.getGlobals '*' end if guide.isGlobal(source) then local name = guide.getKeyName(source) + if not name then + return {} + end local cache = vm.getCache('eachFieldOfGlobal')[name] or getFieldsBySource(source, deep) vm.getCache('eachFieldOfGlobal')[name] = cache @@ -53,13 +90,20 @@ function vm.getFields(source, deep) end end -function vm.getFieldsOfDocClassAnyNotGet(source, deep) - if not guide.isDocClass(source) then - return {} +function vm.getDefFields(source, deep) + if source.special == '_G' then + return vm.getGlobalSets '*' + end + if guide.isGlobal(source) then + local name = guide.getKeyName(source) + if not name then + return {} + end + local cache = vm.getCache('eachDefFieldOfGlobal')[name] + or getDefFieldsBySource(source, deep) + vm.getCache('eachDefFieldOfGlobal')[name] = cache + return cache + else + return getDefFieldsBySource(source, deep) end - - local cache = vm.getCache('eachFieldOfDocClass')[source] - or getFieldsBySource(source, deep, guide.ANYNOTGET) - vm.getCache('eachFieldOfDocClass')[source] = cache - return cache end diff --git a/script/vm/eachRef.lua b/script/vm/eachRef.lua index e9229c38..1073ecbe 100644 --- a/script/vm/eachRef.lua +++ b/script/vm/eachRef.lua @@ -32,6 +32,9 @@ function vm.getRefs(source, deep) deep = deep or -999 if guide.isGlobal(source) then local key = guide.getKeyName(source) + if not key then + return {} + end return vm.getGlobals(key) else local cache = vm.getCache('eachRef')[source] diff --git a/script/vm/getDocs.lua b/script/vm/getDocs.lua index 1c54d593..632dd1c2 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/getDocs.lua @@ -51,6 +51,9 @@ local function getDocTypes(name) end function vm.getDocEnums(doc, mark, results) + if not doc then + return nil + end mark = mark or {} if mark[doc] then return nil diff --git a/script/vm/guideInterface.lua b/script/vm/guideInterface.lua index a73067c5..7fd515eb 100644 --- a/script/vm/guideInterface.lua +++ b/script/vm/guideInterface.lua @@ -22,7 +22,7 @@ function m.searchFileReturn(results, ast, index) end function m.require(args, index) - local reqName = args[1] and args[1][1] + local reqName = args and args[1] and args[1][1] if not reqName then return nil end diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index f1dc2fb9..4503f0ee 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -17,7 +17,7 @@ local function getOnePath(path, searcher) local start = stemSearcher:match '()%?' or 1 for pos = start, #stemPath do local word = stemPath:sub(start, pos) - local newSearcher = stemSearcher:gsub('%?', word) + local newSearcher = stemSearcher:gsub('%?', (word:gsub('%%', '%%%%'))) if newSearcher == stemPath then return word end diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index c76eec55..c493823d 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -34,30 +34,32 @@ function m.init(uri) log.init(ROOT, logPath) end -local function interfaceFactory(root) - return { - type = function (path) - if fs.is_directory(fs.path(root .. '/' .. path)) then - return 'directory' +local globInteferFace = { + type = function (path) + local result + pcall(function () + if fs.is_directory(fs.path(path)) then + result = 'directory' else - return 'file' + result = 'file' end - end, - list = function (path) - local fullPath = fs.path(root .. '/' .. path) - if not fs.exists(fullPath) then - return nil - end - local paths = {} - pcall(function () - for fullpath in fullPath:list_directory() do - paths[#paths+1] = fullpath:string() - end - end) - return paths + end) + return result + end, + list = function (path) + local fullPath = fs.path(path) + if not fs.exists(fullPath) then + return nil end - } -end + local paths = {} + pcall(function () + for fullpath in fullPath:list_directory() do + paths[#paths+1] = fullpath:string() + end + end) + return paths + end +} --- 创建排除文件匹配器 function m.getNativeMatcher() @@ -68,7 +70,6 @@ function m.getNativeMatcher() return m.nativeMatcher end - local interface = interfaceFactory(m.path) local pattern = {} -- config.workspace.ignoreDir for path in pairs(config.config.workspace.ignoreDir) do @@ -119,7 +120,7 @@ function m.getNativeMatcher() pattern[#pattern+1] = path end - m.nativeMatcher = glob.gitignore(pattern, m.matchOption, interface) + m.nativeMatcher = glob.gitignore(pattern, m.matchOption, globInteferFace) m.nativeVersion = config.version return m.nativeMatcher @@ -140,16 +141,18 @@ function m.getLibraryMatchers() end m.libraryMatchers = {} for path, pattern in pairs(librarys) do - local nPath = fs.absolute(fs.path(path)):string() - local matcher = glob.gitignore(pattern, m.matchOption) - if platform.OS == 'Windows' then - matcher:setOption 'ignoreCase' + if fs.exists(fs.path(path)) then + local nPath = fs.absolute(fs.path(path)):string() + local matcher = glob.gitignore(pattern, m.matchOption, globInteferFace) + if platform.OS == 'Windows' then + matcher:setOption 'ignoreCase' + end + log.debug('getLibraryMatchers', path, nPath) + m.libraryMatchers[#m.libraryMatchers+1] = { + path = nPath, + matcher = matcher + } end - log.debug('getLibraryMatchers', path, nPath) - m.libraryMatchers[#m.libraryMatchers+1] = { - path = nPath, - matcher = matcher - } end m.libraryVersion = config.version @@ -168,7 +171,7 @@ end local function loadFileFactory(root, progress, isLibrary) return function (path) - local uri = furi.encode(root .. '/' .. path) + local uri = furi.encode(path) if not files.isLua(uri) then return end @@ -246,15 +249,11 @@ function m.awaitPreload() local native = m.getNativeMatcher() local librarys = m.getLibraryMatchers() if native then - native:scan(nativeLoader) + native:scan(m.path, nativeLoader) end for _, library in ipairs(librarys) do - local libraryInterface = interfaceFactory(library.path) - local libraryLoader = loadFileFactory(library.path, progress, true) - for k, v in pairs(libraryInterface) do - library.matcher:setInterface(k, v) - end - library.matcher:scan(libraryLoader) + local libraryLoader = loadFileFactory(library.path, progress, true) + library.matcher:scan(library.path, libraryLoader) end log.info(('Found %d files.'):format(progress.max)) @@ -18,6 +18,7 @@ log.debug('测试开始') ac = {} --dofile((ROOT / 'build_package.lua'):string()) +require 'tracy' local function loadAllLibs() assert(require 'bee.filesystem') @@ -46,6 +47,7 @@ local function main() debug.setcstacklimit(1000) require 'parser.guide'.debugMode = true require 'language' 'zh-cn' + require 'utility'.enableCloseFunction() local function test(name) local clock = os.clock() print(('测试[%s]...'):format(name)) @@ -80,3 +82,5 @@ loadAllLibs() main() log.debug('测试完成') +require 'bee.thread'.sleep(1) +os.exit() diff --git a/test/completion/init.lua b/test/completion/init.lua index 662df84e..69ca83c3 100644 --- a/test/completion/init.lua +++ b/test/completion/init.lua @@ -1071,10 +1071,10 @@ function$ { label = 'function ()', kind = define.CompletionItemKind.Snippet, - insertText = [[ -function $1($2) - $0 -end]], + insertText = "\z +function $1($2)\ +\t$0\ +end", }, } @@ -1089,10 +1089,10 @@ local t = function$ { label = 'function ()', kind = define.CompletionItemKind.Snippet, - insertText = [[ -function ($1) - $0 -end]], + insertText = "\z +function ($1)\ +\t$0\ +end", }, } Cared['insertText'] = false @@ -1964,10 +1964,10 @@ f($) { label = 'fun(x: number, y: number):string', kind = define.CompletionItemKind.Function, - insertText = [[ -function (${1:x}, ${2:y}) - $0 -end]], + insertText = "\z +function (${1:x}, ${2:y})\ +\t$0\ +end", }, } Cared['insertText'] = nil diff --git a/test/crossfile/definition.lua b/test/crossfile/definition.lua index 839a3e89..30d796c8 100644 --- a/test/crossfile/definition.lua +++ b/test/crossfile/definition.lua @@ -615,3 +615,28 @@ TEST { ]] }, } + +TEST { + { + path = 'a.lua', + content = [[ + local lib = {} + + function lib:fn1() + return self + end + + function lib:<!fn2!>() + end + + return lib:fn1() + ]] + }, + { + path = 'b.lua', + content = [[ + local app = require 'a' + print(app.<?fn2?>) + ]] + }, +} diff --git a/test/definition/bug.lua b/test/definition/bug.lua index e7158848..8c446123 100644 --- a/test/definition/bug.lua +++ b/test/definition/bug.lua @@ -146,3 +146,26 @@ t.<!f1!> = t.f2 print(t.<?f2?>) ]] + +TEST [[ +---@type string +string.xx = '' +string.xx:<?format?>() +]] + +TEST [[ +---@class Foo +Foo = {} +function Foo:Constructor() + self.<!bar1!> = 1 +end + +---@class Foo2: Foo +Foo2 = {} +function Foo2:Constructor() +end + +---@type Foo2 +local v +v.<?bar1?> +]] diff --git a/test/definition/luadoc.lua b/test/definition/luadoc.lua index 31134135..1f3dae00 100644 --- a/test/definition/luadoc.lua +++ b/test/definition/luadoc.lua @@ -169,3 +169,87 @@ end AAAA.a.<?SSDF?> ]] + +TEST [[ +---@class Cat +local <!m!> ---hahaha +---@class Dog +local m2 +---@type Cat +local <?<!v!>?> +]] + +TEST [[ +---@class Cat +local <!m!> --hahaha +---@class Dog +local m2 +---@type Cat +local <?<!v!>?> +]] + +TEST [[ +---@class Cat + local <!m!> ---hahaha + + ---@class Dog + local m2 + ---@type Cat + local <?<!v!>?> +]] + +TEST [[ +---@class Foo +local Foo = {} +function Foo:<!bar1!>() end + +---@generic T +---@param arg1 T +---@return T +function Generic(arg1) print(arg1) end + +local v1 = Generic(Foo) +print(v1.<?bar1?>) +]] + +TEST [[ +---@class Foo +local Foo = {} +function Foo:bar1() end + +---@generic T +---@param arg1 T +---@return T +function Generic(arg1) print(arg1) end + +local v1 = Generic("Foo") +print(v1.<?bar1?>) +]] + +TEST [[ +---@class Foo +local Foo = {} +function Foo:bar1() end + +---@generic T +---@param arg1 `T` +---@return T +function Generic(arg1) print(arg1) end + +local v1 = Generic(Foo) +print(v1.<?bar1?>) +]] + +TEST [[ +---@class Foo +local Foo = {} +function Foo:<!bar1!>() end + +---@generic T +---@param arg1 `T` +---@return T +function Generic(arg1) print(arg1) end + +local v1 = Generic("Foo") +print(v1.<?bar1?>) +]] diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua index cfd0f4cb..d95bf380 100644 --- a/test/diagnostics/init.lua +++ b/test/diagnostics/init.lua @@ -76,7 +76,7 @@ local <!x!> ]] TEST [[ -local x <close> +local x <close> = print ]] TEST [[ @@ -319,6 +319,16 @@ return [[ ]] ]=] +config.config.diagnostics.disable['close-non-object'] = true +TEST [[ +local _ <close> = function () end +]] + +config.config.diagnostics.disable['close-non-object'] = nil +TEST [[ +local _ <close> = <!1!> +]] + config.config.diagnostics.disable['unused-local'] = true TEST [[ local f = <!function () end!> @@ -340,8 +350,7 @@ TEST [[ --<!function F() end!> --]] -config.config.diagnostics.disable['unused-local'] = false -config.config.diagnostics.disable['unused-function'] = true +config.config.diagnostics.disable['unused-local'] = nil TEST [[ local mt, x function mt:m() @@ -823,11 +832,7 @@ TEST [[ ---@class class local t ]] - -TEST [[ -local _ <close> = function () end -]] - +---[==[ -- checkUndefinedField 通用 TEST [[ ---@class Foo @@ -945,3 +950,19 @@ v2 = v v2:method1() v2:method2() -- 这个感觉实际应该报错更合适 ]] + +TEST [[ +---@type table +T1 = {} +print(T1.f1) +---@type table* +T2 = {} +print(T2.<!f2!>) +]] +--]==] +TEST [[ +---@overload fun(...) +local function f() end + +f(1) +]] diff --git a/test/full/example.lua b/test/full/example.lua index b19f0485..4f6090ee 100644 --- a/test/full/example.lua +++ b/test/full/example.lua @@ -41,6 +41,7 @@ local function testIfExit(path) local lines = parser:lines(buf) for i = 1, max do files.removeAll() + files.open('') files.setText('', buf) diag('', function () end) local passed = os.clock() - clock @@ -52,6 +53,8 @@ local function testIfExit(path) print(('基准诊断测试[%s]单次耗时:%.10f'):format(path:filename():string(), need)) end end + +require 'tracy' .enable() testIfExit(ROOT / 'test' / 'example' / 'vm.txt') testIfExit(ROOT / 'test' / 'example' / 'largeGlobal.txt') testIfExit(ROOT / 'test' / 'example' / 'guide.txt') diff --git a/test/full/init.lua b/test/full/init.lua index f370671e..ad34da7d 100644 --- a/test/full/init.lua +++ b/test/full/init.lua @@ -11,3 +11,4 @@ end require 'full.normal' require 'full.example' require 'full.dirty' +require 'full.self' diff --git a/test/full/self.lua b/test/full/self.lua new file mode 100644 index 00000000..247702ae --- /dev/null +++ b/test/full/self.lua @@ -0,0 +1,29 @@ +local files = require 'files' +local fsu = require 'fs-utility' +local furi = require 'file-uri' +local diag = require 'provider.diagnostic' +local config = require 'config' +files.removeAll() + +fsu.scanDirectory(ROOT, function (path) + if path:extension():string() ~= '.lua' then + return + end + local uri = furi.encode(path:string()) + local text = fsu.loadFile(path) + files.setText(uri, text) + files.open(uri) +end) + +config.config.diagnostics.disable['undefined-field'] = true +config.config.diagnostics.disable['redundant-parameter'] = true +diag.start() + +local clock = os.clock() + +for uri in files.eachFile() do + diag.doDiagnostic(uri) +end + +local passed = os.clock() - clock +print('基准全量诊断用时:', passed) diff --git a/test/signature/init.lua b/test/signature/init.lua index 765e0814..fca995bd 100644 --- a/test/signature/init.lua +++ b/test/signature/init.lua @@ -174,3 +174,19 @@ function Foo(param01: any, param02: any) ]], arg = {14, 25}, } + +TEST [[ +function f1(a, b) +end + +function f2(c, d) +end + +f2(f1(),$) +]] +{ + label = [[ +function f2(c: any, d: any) +]], + arg = {21, 26}, +} diff --git a/test/???.md b/test/???.md deleted file mode 100644 index 46a4b58a..00000000 --- a/test/???.md +++ /dev/null @@ -1,94 +0,0 @@ -# 如何搜索引用 - -```lua -local x = 1 -print(x) -- 通过语法搜索到 local x -``` - -```lua -local function f() -end - -local x = f -print(x) -- 通过 x 的赋值搜索到函数 -``` - -```lua -X.Y.Z = 1 -print(X.Y.Z) -- 通过 field 的赋值行为搜索 -``` - -```lua -local function f() - return f -end - -local x = f() -print(x) -- 引用不穿透函数调用? -``` - -```lua -local t = { - x = 1 -} - -print(t.x) -- 引用穿透表 -``` - -```lua -local function f() - return { - x = 1 - } -end - -local t = f() -print(t.x) -- 是否穿透函数返回的表? -``` - -在栈帧上标记值? - -```lua -X.Y.Z = 1 -local t = X.Y -print(t.Z) -``` - -字符串匹配? -1. t -> Z -2. X.Y -> Z -3. X.Y.Z = 1 - -语义匹配? -1. t -> Z -2. X.Y -> Z -3. X -> Y -> Z -4. X.Y.Z = 1 - -建立标记? -```lua -{ - type = 'set', - key = { - 's|X', - 's|Y', - 's|Z', - } - v = 1, -}, -{ - type = 'local', - key = 't', - v = { - 's|X', - 's|Y', - }, -}, -{ - type = 'get', - key = { - 'l|t', - 's|Z', - } -} -``` |