diff options
-rw-r--r-- | script/core/completion/completion.lua | 20 | ||||
-rw-r--r-- | script/core/diagnostics/undefined-doc-param.lua | 42 | ||||
-rw-r--r-- | script/core/hover/description.lua | 63 | ||||
-rw-r--r-- | script/core/hover/name.lua | 10 | ||||
-rw-r--r-- | script/core/rename.lua | 19 | ||||
-rw-r--r-- | script/parser/guide.lua | 4 | ||||
-rw-r--r-- | script/parser/luadoc.lua | 215 | ||||
-rw-r--r-- | script/vm/compiler.lua | 53 | ||||
-rw-r--r-- | test/diagnostics/common.lua | 2 | ||||
-rw-r--r-- | test/type_inference/init.lua | 15 |
10 files changed, 270 insertions, 173 deletions
diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index c882f283..908398e7 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -1944,25 +1944,25 @@ local function buildluaDocOfFunction(func) end local function tryluaDocOfFunction(doc, results) - if not doc.bindSources then + if not doc.bindSource then return end - local func - for _, source in ipairs(doc.bindSources) do - if source.type == 'function' then - func = source - break - end - end + local func = doc.bindSource.type == 'function' and doc.bindSource or nil if not func then return end for _, otherDoc in ipairs(doc.bindGroup) do - if otherDoc.type == 'doc.param' - or otherDoc.type == 'doc.return' then + if otherDoc.type == 'doc.return' then return end end + if func.args then + for _, param in ipairs(func.args) do + if param.bindDocs then + return + end + end + end local insertText = buildluaDocOfFunction(func) results[#results+1] = { label = '@param;@return', diff --git a/script/core/diagnostics/undefined-doc-param.lua b/script/core/diagnostics/undefined-doc-param.lua index 98919284..7a60a74f 100644 --- a/script/core/diagnostics/undefined-doc-param.lua +++ b/script/core/diagnostics/undefined-doc-param.lua @@ -1,21 +1,6 @@ local files = require 'files' local lang = require 'language' -local function hasParamName(func, name) - if not func.args then - return false - end - for _, arg in ipairs(func.args) do - if arg[1] == name then - return true - end - if arg.type == '...' and name == '...' then - return true - end - end - return false -end - return function (uri, callback) local state = files.getState(uri) if not state then @@ -27,26 +12,13 @@ return function (uri, callback) end for _, doc in ipairs(state.ast.docs) do - if doc.type ~= 'doc.param' then - goto CONTINUE - end - local binds = doc.bindSources - if not binds then - goto CONTINUE - end - local param = doc.param - local name = param[1] - for _, source in ipairs(binds) do - if source.type == 'function' then - if not hasParamName(source, name) then - callback { - start = param.start, - finish = param.finish, - message = lang.script('DIAG_UNDEFINED_DOC_PARAM', name) - } - end - end + if doc.type == 'doc.param' + and not doc.bindSource then + callback { + start = doc.param.start, + finish = doc.param.finish, + message = lang.script('DIAG_UNDEFINED_DOC_PARAM', doc.param[1]) + } end - ::CONTINUE:: end end diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index e5118413..c16f271b 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -82,7 +82,22 @@ local function asString(source) or asStringView(source, literal) end -local function getBindComment(source, docGroup, base) +local function getBindComment(source) + local lines = {} + for _, docComment in ipairs(source.bindComments) do + if docComment.comment.text:sub(1, 1) == '-' then + lines[#lines+1] = docComment.comment.text:sub(2) + else + lines[#lines+1] = docComment.comment.text + end + end + if not lines or #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local function lookUpDocComments(source, docGroup) if source.type == 'setlocal' or source.type == 'getlocal' then source = source.node @@ -90,14 +105,9 @@ local function getBindComment(source, docGroup, base) if source.parent.type == 'funcargs' then return end - local continue - local lines + local lines = {} for _, doc in ipairs(docGroup) do if doc.type == 'doc.comment' then - if not continue then - continue = true - lines = {} - end if doc.comment.text:sub(1, 1) == '-' then lines[#lines+1] = doc.comment.text:sub(2) else @@ -105,26 +115,19 @@ local function getBindComment(source, docGroup, base) end elseif doc.type == 'doc.type' then if doc.comment then - if not continue then - continue = true - lines = {} - end lines[#lines+1] = doc.comment.text end - elseif doc == base then - break - else - continue = false - if doc.type == 'doc.field' - or doc.type == 'doc.class' then - lines = nil + elseif doc.type == 'doc.class' then + for _, docComment in ipairs(doc.bindComments) do + if docComment.comment.text:sub(1, 1) == '-' then + lines[#lines+1] = docComment.comment.text:sub(2) + else + lines[#lines+1] = docComment.comment.text + end end end end if source.comment then - if not lines then - lines = {} - end lines[#lines+1] = source.comment.text end if not lines or #lines == 0 then @@ -137,7 +140,7 @@ local function tryDocClassComment(source) for _, def in ipairs(vm.getDefs(source)) do if def.type == 'doc.class' or def.type == 'doc.alias' then - local comment = getBindComment(def, def.bindGroup, def) + local comment = getBindComment(def) if comment then return comment end @@ -251,7 +254,7 @@ local function tryDocFieldUpComment(source) if not source.bindGroup then return end - local comment = getBindComment(source, source.bindGroup, source) + local comment = getBindComment(source) return comment end @@ -259,7 +262,7 @@ local function getFunctionComment(source) local docGroup = source.bindDocs local hasReturnComment = false - for _, doc in ipairs(docGroup) do + for _, doc in ipairs(source.bindDocs) do if doc.type == 'doc.return' and doc.comment then hasReturnComment = true break @@ -318,7 +321,7 @@ local function tryDocComment(source) return end if source.type ~= 'function' then - local comment = getBindComment(source, source.bindDocs) + local comment = lookUpDocComments(source, source.bindDocs) return comment end return getFunctionComment(source) @@ -330,14 +333,12 @@ local function tryDocOverloadToComment(source) end local doc = source.parent if doc.type ~= 'doc.overload' - or not doc.bindSources then + or not doc.bindSource then return end - for _, src in ipairs(doc.bindSources) do - local md = tryDocComment(src) - if md then - return md - end + local md = tryDocComment(doc.bindSource) + if md then + return md end end diff --git a/script/core/hover/name.lua b/script/core/hover/name.lua index 1e1f733e..3fabfb89 100644 --- a/script/core/hover/name.lua +++ b/script/core/hover/name.lua @@ -50,14 +50,12 @@ end local function asDocFunction(source, oop) local doc = guide.getParentType(source, 'doc.type') or guide.getParentType(source, 'doc.overload') - if not doc or not doc.bindSources then + if not doc or not doc.bindSource then return '' end - for _, src in ipairs(doc.bindSources) do - local name = buildName(src, oop) - if name ~= '' then - return name - end + local name = buildName(doc.bindSource, oop) + if name ~= '' then + return name end return '' end diff --git a/script/core/rename.lua b/script/core/rename.lua index 5ba93684..a4b6dd7c 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -242,16 +242,15 @@ end local function ofDocParamName(source, newname, callback) callback(source, source.start, source.finish, newname) local doc = source.parent - if doc.bindSources then - for _, src in ipairs(doc.bindSources) do - if src.type == 'local' - and src.parent.type == 'funcargs' - and src[1] == source[1] then - renameLocal(src, newname, callback) - if src.ref then - for _, ref in ipairs(src.ref) do - renameLocal(ref, newname, callback) - end + local src = doc.bindSource + if src then + if src.type == 'local' + and src.parent.type == 'funcargs' + and src[1] == source[1] then + renameLocal(src, newname, callback) + if src.ref then + for _, ref in ipairs(src.ref) do + renameLocal(ref, newname, callback) end end end diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 0768cbb4..76f0fbca 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -4,7 +4,7 @@ local type = type ---@class parser.object ---@field bindDocs parser.object[] ---@field bindGroup parser.object[] ----@field bindSources parser.object[] +---@field bindSource parser.object[] ---@field value parser.object ---@field parent parser.object ---@field type string @@ -51,6 +51,8 @@ local type = type ---@field upvalues table<string, string[]> ---@field ref parser.object[] ---@field returnIndex integer +---@field assignIndex integer +---@field docIndex integer ---@field docs parser.object[] ---@field state table ---@field comment table diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index ca23b331..1b538e84 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -860,7 +860,22 @@ local docSwitch = util.switch() end) : case 'type' : call(function () - return parseType() + local first = parseType() + if not first then + return nil + end + first.docIndex = 1 + local rests + while checkToken('symbol', ',', 1) do + nextToken() + local rest = parseType() + if not rests then + rests = {} + end + rests[#rests+1] = rest + rest.docIndex = #rests + 1 + end + return first, rests end) : case 'alias' : call(function () @@ -1444,10 +1459,17 @@ local function buildLuaDoc(comment) local doc = text:sub(startPos) parseTokens(doc, comment.start + startPos) - local result = convertTokens() + local result, rests = convertTokens() if result then result.range = comment.finish - local cstart = text:find('%S', (result.firstFinish or result.finish) - comment.start) + local finish = result.firstFinish or result.finish + if rests then + for _, rest in ipairs(rests) do + rest.range = comment.finish + finish = rest.firstFinish or result.finish + end + end + local cstart = text:find('%S', finish - comment.start) if cstart and cstart < comment.finish then result.comment = { type = 'doc.tailcomment', @@ -1456,11 +1478,16 @@ local function buildLuaDoc(comment) parent = result, text = trimTailComment(text:sub(cstart)), } + if rests then + for _, rest in ipairs(rests) do + rest.comment = result.comment + end + end end end if result then - return result + return result, rests end return { @@ -1560,7 +1587,82 @@ local function bindGeneric(binded) end end -local function bindDocsBetween(sources, binded, bindSources, start, finish) +local function bindDoc(source, binded) + local ok = false + for _, doc in ipairs(binded) do + if doc.bindSource then + goto CONTINUE + end + if doc.type == 'doc.class' + or doc.type == 'doc.type' + or doc.type == 'doc.deprecated' + or doc.type == 'doc.version' + or doc.type == 'doc.module' then + if source.type == 'function' + or source.type == 'self' then + goto CONTINUE + end + elseif doc.type == 'doc.overload' then + if not source.bindDocs then + source.bindDocs = {} + end + source.bindDocs[#source.bindDocs+1] = doc + if source.type ~= 'function' then + doc.bindSource = source + end + elseif doc.type == 'doc.param' then + local suc + if source.type == 'local' + and doc.param[1] == source[1] + and ( source.parent.type == 'funcargs' + or ( source.parent.type == 'in' + and source.finish <= source.parent.keys.finish + ) + ) then + suc = true + elseif source.type == '...' + and doc.param[1] == '...' then + suc = true + elseif source.type == 'self' + and doc.param[1] == 'self' then + suc = true + end + if source.type == 'function' then + if not source.bindDocs then + source.bindDocs = {} + end + source.bindDocs[#source.bindDocs+1] = doc + end + + if not suc then + goto CONTINUE + end + elseif doc.type == 'doc.vararg' then + if source.type ~= '...' then + goto CONTINUE + end + elseif doc.type == 'doc.return' + or doc.type == 'doc.generic' + or doc.type == 'doc.async' + or doc.type == 'doc.nodiscard' then + if source.type ~= 'function' then + goto CONTINUE + end + elseif doc.type ~= 'doc.comment' then + goto CONTINUE + end + if not source.bindDocs then + source.bindDocs = {} + end + source.bindDocs[#source.bindDocs+1] = doc + doc.bindSource = source + ok = true + ::CONTINUE:: + end + return ok +end + +local function bindDocsBetween(sources, binded, start, finish) -- 用二分法找到第一个 local max = #sources local index @@ -1583,28 +1685,14 @@ local function bindDocsBetween(sources, binded, bindSources, start, finish) end end + local ok = false -- 从前往后进行绑定 - local skipUntil for i = index, max do local src = sources[i] if src and src.start >= start then if src.start >= finish then break end - if skipUntil then - if skipUntil > src.start then - goto CONTINUE - else - skipUntil = nil - end - end - -- 遇到table后中断,处理以下情况: - -- ---@type AAA - -- local t = {x = 1, y = 2} - if src.type == 'table' then - skipUntil = skipUntil or src.finish - goto CONTINUE - end if src.start >= start then if src.type == 'local' or src.type == 'self' @@ -1615,14 +1703,17 @@ local function bindDocsBetween(sources, binded, bindSources, start, finish) or src.type == 'setfield' or src.type == 'setindex' or src.type == 'setmethod' - or src.type == 'function' then - src.bindDocs = binded - bindSources[#bindSources+1] = src + or src.type == 'function' + or src.type == '...' then + if bindDoc(src, binded) then + ok = true + end end end - ::CONTINUE:: end end + + return ok end local function bindReturnIndex(binded) @@ -1637,30 +1728,49 @@ local function bindReturnIndex(binded) end end -local function bindClassAndFields(binded) +local function bindCommentsToDoc(doc, comments) + doc.bindComments = comments + for _, comment in ipairs(comments) do + comment.bindSource = doc + end +end + +local function bindCommentsAndFields(binded) local class + local comments = {} for _, doc in ipairs(binded) do if doc.type == 'doc.class' then -- 多个class连续写在一起,只有最后一个class可以绑定source if class then - class.bindSources = nil + class.bindSource = nil end class = doc + bindCommentsToDoc(doc, comments) + comments = {} elseif doc.type == 'doc.field' then if class then class.fields[#class.fields+1] = doc doc.class = class end + bindCommentsToDoc(doc, comments) + comments = {} elseif doc.type == 'doc.operator' then if class then class.operators[#class.operators+1] = doc doc.class = class end + bindCommentsToDoc(doc, comments) + comments = {} + elseif doc.type == 'doc.alias' then + bindCommentsToDoc(doc, comments) + comments = {} + elseif doc.type == 'doc.comment' then + comments[#comments+1] = doc end end end -local function bindDoc(sources, binded) +local function bindDocWithSources(sources, binded) if not binded then return end @@ -1668,19 +1778,17 @@ local function bindDoc(sources, binded) if not lastDoc then return end - local bindSources = {} for _, doc in ipairs(binded) do doc.bindGroup = binded - doc.bindSources = bindSources end bindGeneric(binded) + bindCommentsAndFields(binded) + bindReturnIndex(binded) local row = guide.rowColOf(lastDoc.finish) - bindDocsBetween(sources, binded, bindSources, guide.positionOf(row, 0), lastDoc.start) - if #bindSources == 0 then - bindDocsBetween(sources, binded, bindSources, guide.positionOf(row + 1, 0), guide.positionOf(row + 2, 0)) + local suc = bindDocsBetween(sources, binded, guide.positionOf(row, 0), lastDoc.start) + if not suc then + bindDocsBetween(sources, binded, guide.positionOf(row + 1, 0), guide.positionOf(row + 2, 0)) end - bindReturnIndex(binded) - bindClassAndFields(binded) end local bindDocAccept = { @@ -1707,17 +1815,17 @@ local function bindDocs(state) end binded[#binded+1] = doc if isTailComment(text, doc) then - bindDoc(sources, binded) + bindDocWithSources(sources, binded) binded = nil else local nextDoc = state.ast.docs[i+1] if not isNextLine(doc, nextDoc) then - bindDoc(sources, binded) + bindDocWithSources(sources, binded) binded = nil end if not isContinuedDoc(doc, nextDoc) and not isTailComment(text, nextDoc) then - bindDoc(sources, binded) + bindDocWithSources(sources, binded) binded = nil end end @@ -1767,24 +1875,33 @@ return function (state) return comment end + local function insertDoc(doc, comment) + ast.docs[#ast.docs+1] = doc + doc.parent = ast.docs + if ast.start > doc.start then + ast.start = doc.start + end + if ast.finish < doc.finish then + ast.finish = doc.finish + end + doc.originalComment = comment + if comment.type == 'comment.long' then + findTouch(state, doc) + end + end + while true do local comment = NextComment() if not comment then break end - local doc = buildLuaDoc(comment) + local doc, rests = buildLuaDoc(comment) if doc then - ast.docs[#ast.docs+1] = doc - doc.parent = ast.docs - if ast.start > doc.start then - ast.start = doc.start - end - if ast.finish < doc.finish then - ast.finish = doc.finish - end - doc.originalComment = comment - if comment.type == 'comment.long' then - findTouch(state, doc) + insertDoc(doc, comment) + if rests then + for _, rest in ipairs(rests) do + insertDoc(rest, comment) + end end end end diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 374ada92..cda85392 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -42,14 +42,12 @@ local function bindDocs(source) end end if doc.type == 'doc.param' then - if isParam and source[1] == doc.param[1] then - local node = vm.compileNode(doc) - if doc.optional then - node:addOptional() - end - vm.setNode(source, node) - return true + local node = vm.compileNode(doc) + if doc.optional then + node:addOptional() end + vm.setNode(source, node) + return true end if doc.type == 'doc.module' then local name = doc.module @@ -334,23 +332,12 @@ function vm.getClassFields(suri, object, key, ref, pushResult) end end -- check local field and global field - if not hasFounded[key] and set.bindSources then - for _, src in ipairs(set.bindSources) do - if src.value and src.value.type == 'table' then - searchFieldSwitch('table', suri, src.value, key, ref, function (field) - local fieldKey = guide.getKeyName(field) - if fieldKey then - if not searchedFields[fieldKey] - and guide.isSet(field) then - hasFounded[fieldKey] = true - pushResult(field, true) - end - end - end) - end - searchFieldSwitch(src.type, suri, src, key, ref, function (field) + if not hasFounded[key] and set.bindSource then + local src = set.bindSource + if src.value and src.value.type == 'table' then + searchFieldSwitch('table', suri, src.value, key, ref, function (field) local fieldKey = guide.getKeyName(field) - if fieldKey and not hasFounded[fieldKey] then + if fieldKey then if not searchedFields[fieldKey] and guide.isSet(field) then hasFounded[fieldKey] = true @@ -359,6 +346,16 @@ function vm.getClassFields(suri, object, key, ref, pushResult) end end) end + searchFieldSwitch(src.type, suri, src, key, ref, function (field) + local fieldKey = guide.getKeyName(field) + if fieldKey and not hasFounded[fieldKey] then + if not searchedFields[fieldKey] + and guide.isSet(field) then + hasFounded[fieldKey] = true + pushResult(field, true) + end + end + end) end -- look into extends(if field not found) if not hasFounded[key] and set.extends then @@ -1591,18 +1588,14 @@ local compilerSwitch = util.switch() end) : case '...' : call(function (source) - local func = source.parent.parent - if func.type ~= 'function' then - return - end - if not func.bindDocs then + if not source.bindDocs then return end - for _, doc in ipairs(func.bindDocs) do + for _, doc in ipairs(source.bindDocs) do if doc.type == 'doc.vararg' then vm.setNode(source, vm.compileNode(doc)) end - if doc.type == 'doc.param' and doc.param[1] == '...' then + if doc.type == 'doc.param' then vm.setNode(source, vm.compileNode(doc)) end end diff --git a/test/diagnostics/common.lua b/test/diagnostics/common.lua index fd9528c0..ec71e77e 100644 --- a/test/diagnostics/common.lua +++ b/test/diagnostics/common.lua @@ -808,7 +808,7 @@ TEST [[ ]] TEST [[ ----@param x <!Class!> +---@param <!x!> <!Class!> ]] TEST [[ diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index 9beaf7f5..6c8afcea 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -3651,3 +3651,18 @@ end local <?n?> = test(1) ]] + +--TEST 'boolean' [[ +-----@type boolean, number +--local <?x?>, y +--]] +-- +--TEST 'number' [[ +-----@type boolean, number +--local x, <?y?> +--]] +-- +--TEST 'unknown' [[ +-----@type _, number +--local <?x?>, y +--]] |