summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--script/core/completion/completion.lua20
-rw-r--r--script/core/diagnostics/undefined-doc-param.lua42
-rw-r--r--script/core/hover/description.lua63
-rw-r--r--script/core/hover/name.lua10
-rw-r--r--script/core/rename.lua19
-rw-r--r--script/parser/guide.lua4
-rw-r--r--script/parser/luadoc.lua215
-rw-r--r--script/vm/compiler.lua53
-rw-r--r--test/diagnostics/common.lua2
-rw-r--r--test/type_inference/init.lua15
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
+--]]