summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--script/core/guide.lua4744
-rw-r--r--script/core/guide2.lua4837
-rw-r--r--script/core/linker.lua100
-rw-r--r--script/parser/guide.lua9
-rw-r--r--test/basic/linker.lua16
-rw-r--r--test/basic/linker.txt16
6 files changed, 5048 insertions, 4674 deletions
diff --git a/script/core/guide.lua b/script/core/guide.lua
index 64192297..e0123987 100644
--- a/script/core/guide.lua
+++ b/script/core/guide.lua
@@ -1,2754 +1,27 @@
-local util = require 'utility'
-local error = error
-local type = type
-local next = next
-local tostring = tostring
-local print = print
-local ipairs = ipairs
-local tableInsert = table.insert
-local tableUnpack = table.unpack
-local tableRemove = table.remove
-local tableMove = table.move
-local tableSort = table.sort
-local tableConcat = table.concat
-local mathType = math.type
-local pairs = pairs
-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
+local linker = require 'core.linker'
+local guide = require 'parser.guide'
-local function logWarn(...)
- log.warn(...)
-end
+local osClock = os.clock
+local pairs = pairs
local m = {}
-m.ANY = {"<ANY>"}
-
-local blockTypes = {
- ['while'] = true,
- ['in'] = true,
- ['loop'] = true,
- ['repeat'] = true,
- ['do'] = true,
- ['function'] = true,
- ['ifblock'] = true,
- ['elseblock'] = true,
- ['elseifblock'] = true,
- ['main'] = true,
-}
-
-local breakBlockTypes = {
- ['while'] = true,
- ['in'] = true,
- ['loop'] = true,
- ['repeat'] = true,
-}
-
-m.childMap = {
- ['main'] = {'#', 'docs'},
- ['repeat'] = {'#', 'filter'},
- ['while'] = {'filter', '#'},
- ['in'] = {'keys', '#'},
- ['loop'] = {'loc', 'max', 'step', '#'},
- ['if'] = {'#'},
- ['ifblock'] = {'filter', '#'},
- ['elseifblock'] = {'filter', '#'},
- ['elseblock'] = {'#'},
- ['setfield'] = {'node', 'field', 'value'},
- ['setglobal'] = {'value'},
- ['local'] = {'attrs', 'value'},
- ['setlocal'] = {'value'},
- ['return'] = {'#'},
- ['do'] = {'#'},
- ['select'] = {'vararg'},
- ['table'] = {'#'},
- ['tableindex'] = {'index', 'value'},
- ['tablefield'] = {'field', 'value'},
- ['function'] = {'args', '#'},
- ['funcargs'] = {'#'},
- ['setmethod'] = {'node', 'method', 'value'},
- ['getmethod'] = {'node', 'method'},
- ['setindex'] = {'node', 'index', 'value'},
- ['getindex'] = {'node', 'index'},
- ['paren'] = {'exp'},
- ['call'] = {'node', 'args'},
- ['callargs'] = {'#'},
- ['getfield'] = {'node', 'field'},
- ['list'] = {'#'},
- ['binary'] = {1, 2},
- ['unary'] = {1},
-
- ['doc'] = {'#'},
- ['doc.class'] = {'class', '#extends', 'comment'},
- ['doc.type'] = {'#types', '#enums', 'name', 'comment'},
- ['doc.alias'] = {'alias', 'extends', 'comment'},
- ['doc.param'] = {'param', 'extends', 'comment'},
- ['doc.return'] = {'#returns', 'comment'},
- ['doc.field'] = {'field', 'extends', 'comment'},
- ['doc.generic'] = {'#generics', 'comment'},
- ['doc.generic.object'] = {'generic', 'extends', 'comment'},
- ['doc.vararg'] = {'vararg', 'comment'},
- ['doc.type.array'] = {'node'},
- ['doc.type.table'] = {'node', 'key', 'value', 'comment'},
- ['doc.type.function'] = {'#args', '#returns', 'comment'},
- ['doc.type.typeliteral'] = {'node'},
- ['doc.type.arg'] = {'extends'},
- ['doc.overload'] = {'overload', 'comment'},
- ['doc.see'] = {'name', 'field', 'comment'},
- ['doc.diagnostic'] = {'#names', 'comment'},
-}
-
-m.actionMap = {
- ['main'] = {'#'},
- ['repeat'] = {'#'},
- ['while'] = {'#'},
- ['in'] = {'#'},
- ['loop'] = {'#'},
- ['if'] = {'#'},
- ['ifblock'] = {'#'},
- ['elseifblock'] = {'#'},
- ['elseblock'] = {'#'},
- ['do'] = {'#'},
- ['function'] = {'#'},
- ['funcargs'] = {'#'},
-}
-
-local TypeSort = {
- ['boolean'] = 1,
- ['string'] = 2,
- ['integer'] = 3,
- ['number'] = 4,
- ['table'] = 5,
- ['function'] = 6,
- ['true'] = 101,
- ['false'] = 102,
- ['nil'] = 999,
-}
-
-local NIL = setmetatable({'<nil>'}, { __tostring = function () return 'nil' end })
-
---- 是否是字面量
----@param obj parser.guide.object
----@return boolean
-function m.isLiteral(obj)
- local tp = obj.type
- return tp == 'nil'
- or tp == 'boolean'
- or tp == 'string'
- or tp == 'number'
- or tp == 'table'
- or tp == 'function'
-end
-
---- 获取字面量
----@param obj parser.guide.object
----@return any
-function m.getLiteral(obj)
- local tp = obj.type
- if tp == 'boolean' then
- return obj[1]
- elseif tp == 'string' then
- return obj[1]
- elseif tp == 'number' then
- return obj[1]
- end
- return nil
-end
-
---- 寻找父函数
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getParentFunction(obj)
- for _ = 1, 1000 do
- obj = obj.parent
- if not obj then
- break
- end
- local tp = obj.type
- if tp == 'function' or tp == 'main' then
- return obj
- end
- end
- return nil
-end
-
---- 寻找父的table类型 doc.type.table
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getParentDocTypeTable(obj)
- for _ = 1, 1000 do
- local parent = obj.parent
- if not parent then
- return nil
- end
- if parent.type == 'doc.type.table' then
- return obj
- end
- obj = parent
- end
- error('guide.getParentDocTypeTable overstack')
-end
-
---- 寻找所在区块
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getBlock(obj)
- for _ = 1, 1000 do
- if not obj then
- return nil
- end
- local tp = obj.type
- 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')
-end
-
---- 寻找所在父区块
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getParentBlock(obj)
- for _ = 1, 1000 do
- obj = obj.parent
- if not obj then
- return nil
- end
- local tp = obj.type
- if blockTypes[tp] then
- return obj
- end
- end
- error('guide.getParentBlock overstack')
-end
-
---- 寻找所在可break的父区块
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getBreakBlock(obj)
- for _ = 1, 1000 do
- obj = obj.parent
- if not obj then
- return nil
- end
- local tp = obj.type
- if breakBlockTypes[tp] then
- return obj
- end
- if tp == 'function' then
- return nil
- end
- end
- error('guide.getBreakBlock overstack')
-end
-
---- 寻找doc的主体
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getDocState(obj)
- for _ = 1, 1000 do
- local parent = obj.parent
- if not parent then
- return obj
- end
- if parent.type == 'doc' then
- return obj
- end
- obj = parent
- end
- error('guide.getDocState overstack')
-end
-
---- 寻找所在父类型
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getParentType(obj, want)
- for _ = 1, 1000 do
- obj = obj.parent
- if not obj then
- return nil
- end
- if want == obj.type then
- return obj
- end
- end
- error('guide.getParentType overstack')
-end
-
---- 寻找根区块
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getRoot(obj)
- local source = obj
- if source._root then
- return source._root
- end
- for _ = 1, 1000 do
- if obj.type == 'main' then
- source._root = obj
- return obj
- end
- local parent = obj.parent
- if not parent then
- return nil
- end
- obj = parent
- end
- error('guide.getRoot overstack')
-end
-
----@param obj parser.guide.object
----@return string
-function m.getUri(obj)
- if obj.uri then
- return obj.uri
- end
- local root = m.getRoot(obj)
- if root then
- return root.uri
- end
- return ''
-end
-
-function m.getENV(source, start)
- if not start then
- start = 1
- end
- return m.getLocal(source, '_ENV', start)
- or m.getLocal(source, '@fenv', start)
-end
-
---- 寻找函数的不定参数,返回不定参在第几个参数上,以及该参数对象。
---- 如果函数是主函数,则返回`0, nil`。
----@return table
----@return integer
-function m.getFunctionVarArgs(func)
- if func.type == 'main' then
- return 0, nil
- end
- if func.type ~= 'function' then
- return nil, nil
- end
- local args = func.args
- if not args then
- return nil, nil
- end
- for i = 1, #args do
- local arg = args[i]
- if arg.type == '...' then
- return i, arg
- end
- end
- return nil, nil
-end
-
---- 获取指定区块中可见的局部变量
----@param block table
----@param name string {comment = '变量名'}
----@param pos integer {comment = '可见位置'}
-function m.getLocal(block, name, pos)
- block = m.getBlock(block)
- for _ = 1, 1000 do
- if not block then
- return nil
- end
- local locals = block.locals
- local res
- if not locals then
- goto CONTINUE
- end
- for i = 1, #locals do
- local loc = locals[i]
- if loc.effect > pos then
- break
- end
- if loc[1] == name then
- if not res or res.effect < loc.effect then
- res = loc
- end
- end
- end
- if res then
- return res, res
- end
- ::CONTINUE::
- block = m.getParentBlock(block)
- end
- error('guide.getLocal overstack')
-end
-
---- 获取指定区块中所有的可见局部变量名称
-function m.getVisibleLocals(block, pos)
- local result = {}
- m.eachSourceContain(m.getRoot(block), pos, function (source)
- local locals = source.locals
- if locals then
- for i = 1, #locals do
- local loc = locals[i]
- local name = loc[1]
- if loc.effect <= pos then
- result[name] = loc
- end
- end
- end
- end)
- return result
-end
-
---- 获取指定区块中可见的标签
----@param block table
----@param name string {comment = '标签名'}
-function m.getLabel(block, name)
- block = m.getBlock(block)
- for _ = 1, 1000 do
- if not block then
- return nil
- end
- local labels = block.labels
- if labels then
- local label = labels[name]
- if label then
- return label
- end
- end
- if block.type == 'function' then
- return nil
- end
- block = m.getParentBlock(block)
- end
- error('guide.getLocal overstack')
-end
-
-function m.getStartFinish(source)
- local start = source.start
- local finish = source.finish
- if not start then
- local first = source[1]
- if not first then
- return nil, nil
- end
- local last = source[#source]
- start = first.start
- finish = last.finish
- end
- return start, finish
-end
-
-function m.getRange(source)
- local start = source.vstart or source.start
- local finish = source.range or source.finish
- if not start then
- local first = source[1]
- if not first then
- return nil, nil
- end
- local last = source[#source]
- start = first.vstart or first.start
- finish = last.range or last.finish
- end
- return start, finish
-end
-
---- 判断source是否包含offset
-function m.isContain(source, offset)
- local start, finish = m.getStartFinish(source)
- if not start then
- return false
- end
- return start <= offset and finish >= offset
-end
-
---- 判断offset在source的影响范围内
----
---- 主要针对赋值等语句时,key包含value
-function m.isInRange(source, offset)
- local start, finish = m.getRange(source)
- if not start then
- return false
- end
- return start <= offset and finish >= offset
-end
-
-function m.isBetween(source, tStart, tFinish)
- local start, finish = m.getStartFinish(source)
- if not start then
- return false
- end
- return start <= tFinish and finish >= tStart
-end
-
-function m.isBetweenRange(source, tStart, tFinish)
- local start, finish = m.getRange(source)
- if not start then
- return false
- end
- return start <= tFinish and finish >= tStart
-end
-
---- 添加child
-function m.addChilds(list, obj, map)
- local keys = map[obj.type]
- if keys then
- for i = 1, #keys do
- local key = keys[i]
- if key == '#' then
- for i = 1, #obj do
- list[#list+1] = obj[i]
- end
- elseif obj[key] then
- list[#list+1] = obj[key]
- elseif type(key) == 'string'
- and key:sub(1, 1) == '#' then
- key = key:sub(2)
- if obj[key] then
- for i = 1, #obj[key] do
- list[#list+1] = obj[key][i]
- end
- end
- end
- end
- end
-end
-
---- 遍历所有包含offset的source
-function m.eachSourceContain(ast, offset, callback)
- local list = { ast }
- local mark = {}
- while true do
- local len = #list
- if len == 0 then
- return
- end
- local obj = list[len]
- list[len] = nil
- if not mark[obj] then
- mark[obj] = true
- if m.isInRange(obj, offset) then
- if m.isContain(obj, offset) then
- local res = callback(obj)
- if res ~= nil then
- return res
- end
- end
- m.addChilds(list, obj, m.childMap)
- end
- end
- end
-end
-
---- 遍历所有在某个范围内的source
-function m.eachSourceBetween(ast, start, finish, callback)
- local list = { ast }
- local mark = {}
- while true do
- local len = #list
- if len == 0 then
- return
- end
- local obj = list[len]
- list[len] = nil
- if not mark[obj] then
- mark[obj] = true
- if m.isBetweenRange(obj, start, finish) then
- if m.isBetween(obj, start, finish) then
- local res = callback(obj)
- if res ~= nil then
- return res
- end
- end
- m.addChilds(list, obj, m.childMap)
- end
- end
- end
-end
-
---- 遍历所有指定类型的source
-function m.eachSourceType(ast, type, callback)
- local cache = ast._typeCache
- if not cache then
- cache = {}
- ast._typeCache = cache
- m.eachSource(ast, function (source)
- local tp = source.type
- if not tp then
- return
- end
- local myCache = cache[tp]
- if not myCache then
- myCache = {}
- cache[tp] = myCache
- end
- myCache[#myCache+1] = source
- end)
- end
- local myCache = cache[type]
- if not myCache then
- return
- end
- for i = 1, #myCache do
- callback(myCache[i])
- end
-end
-
---- 遍历所有的source
-function m.eachSource(ast, callback)
- local list = { ast }
- local mark = {}
- local index = 1
- while true do
- local obj = list[index]
- if not obj then
- return
- end
- list[index] = false
- index = index + 1
- if not mark[obj] then
- mark[obj] = true
- callback(obj)
- m.addChilds(list, obj, m.childMap)
- end
- end
-end
-
---- 获取指定的 special
-function m.eachSpecialOf(ast, name, callback)
- local root = m.getRoot(ast)
- if not root.specials then
- return
- end
- local specials = root.specials[name]
- if not specials then
- return
- end
- for i = 1, #specials do
- callback(specials[i])
- end
-end
-
---- 获取偏移对应的坐标
----@param lines table
----@return integer row
----@return integer col
-function m.positionOf(lines, offset)
- if offset < 1 then
- return 0, 0
- end
- local lastLine = lines[#lines]
- if offset > lastLine.finish then
- return #lines, offset - lastLine.start
- end
- local min = 1
- local max = #lines
- for _ = 1, 100 do
- if max <= min then
- local line = lines[min]
- return min, offset - line.start + 1
- end
- local row = (max - min) // 2 + min
- local line = lines[row]
- if offset < line.start then
- max = row - 1
- elseif offset > line.finish then
- min = row + 1
- else
- return row, offset - line.start + 1
- end
- end
- error('Stack overflow!')
-end
-
---- 获取坐标对应的偏移
----@param lines table
----@param row integer
----@param col integer
----@return integer {name = 'offset'}
-function m.offsetOf(lines, row, col)
- if row < 1 then
- return 0
- end
- if row > #lines then
- local lastLine = lines[#lines]
- return lastLine.finish
- end
- local line = lines[row]
- local len = line.finish - line.start + 1
- if col < 0 then
- return line.start
- elseif col > len then
- return line.finish
- else
- return line.start + col - 1
- end
-end
-
-function m.lineContent(lines, text, row, ignoreNL)
- local line = lines[row]
- if not line then
- return ''
- end
- if ignoreNL then
- return text:sub(line.start, line.range)
- else
- return text:sub(line.start, line.finish)
- end
-end
-
-function m.lineRange(lines, row, ignoreNL)
- local line = lines[row]
- if not line then
- return 0, 0
- end
- if ignoreNL then
- return line.start, line.range
- else
- return line.start, line.finish
- end
-end
-
-function m.lineData(lines, row)
- return lines[row]
-end
-
-function m.getKeyTypeOfLiteral(obj)
- if not obj then
- return nil
- end
- local tp = obj.type
- if tp == 'field'
- or tp == 'method' then
- return 'string'
- elseif tp == 'string' then
- return 'string'
- elseif tp == 'number' then
- return 'number'
- elseif tp == 'boolean' then
- return 'boolean'
- end
-end
-
-function m.getKeyType(obj)
- if not obj then
- return nil
- end
- local tp = obj.type
- if tp == 'getglobal'
- or tp == 'setglobal' then
- return 'string'
- elseif tp == 'local'
- or tp == 'getlocal'
- or tp == 'setlocal' then
- return 'local'
- elseif tp == 'getfield'
- or tp == 'setfield'
- or tp == 'tablefield' then
- return 'string'
- elseif tp == 'getmethod'
- or tp == 'setmethod' then
- return 'string'
- elseif tp == 'getindex'
- or tp == 'setindex'
- or tp == 'tableindex' then
- return m.getKeyTypeOfLiteral(obj.index)
- elseif tp == 'field'
- or tp == 'method'
- or tp == 'doc.see.field' then
- return 'string'
- elseif tp == 'doc.class' then
- return 'string'
- elseif tp == 'doc.alias' then
- return 'string'
- elseif tp == 'doc.field' then
- return 'string'
- elseif tp == 'dummy' then
- return 'string'
- end
- return m.getKeyTypeOfLiteral(obj)
-end
-
-function m.getKeyNameOfLiteral(obj)
- if not obj then
- return nil
- end
- local tp = obj.type
- if tp == 'field'
- or tp == 'method' then
- return obj[1]
- elseif tp == 'string' then
- local s = obj[1]
- if s then
- return s
- end
- elseif tp == 'number' then
- local n = obj[1]
- if n then
- return ('%s'):format(util.viewLiteral(obj[1]))
- end
- elseif tp == 'boolean' then
- local b = obj[1]
- if b then
- return tostring(b)
- end
- end
-end
-
-function m.getKeyName(obj)
- if not obj then
- return nil
- end
- local tp = obj.type
- if tp == 'getglobal'
- or tp == 'setglobal' then
- return obj[1]
- elseif tp == 'local'
- or tp == 'getlocal'
- or tp == 'setlocal' then
- return obj[1]
- elseif tp == 'getfield'
- or tp == 'setfield'
- or tp == 'tablefield' then
- if obj.field then
- return obj.field[1]
- end
- elseif tp == 'getmethod'
- or tp == 'setmethod' then
- if obj.method then
- return obj.method[1]
- end
- elseif tp == 'getindex'
- or tp == 'setindex'
- or tp == 'tableindex' then
- return m.getKeyNameOfLiteral(obj.index)
- elseif tp == 'field'
- or tp == 'method'
- or tp == 'doc.see.field' then
- return obj[1]
- elseif tp == 'doc.class' then
- return obj.class[1]
- elseif tp == 'doc.alias' then
- 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 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
- return ('%p'):format(obj)
- elseif obj.type == 'string' then
- return ('%p'):format(obj)
- elseif obj.type == 'doc.class.name'
- or obj.type == 'doc.type.name'
- or obj.type == 'doc.see.name' then
- return ('%s'):format(obj[1])
- elseif obj.type == 'doc.class' then
- return ('%s'):format(obj.class[1])
- end
- return m.getKeyName(obj)
-end
-
---- 测试 a 到 b 的路径(不经过函数,不考虑 goto),
---- 每个路径是一个 block 。
----
---- 如果 a 在 b 的前面,返回 `"before"` 加上 2个`list<block>`
----
---- 如果 a 在 b 的后面,返回 `"after"` 加上 2个`list<block>`
----
---- 否则返回 `false`
----
---- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。
----@param a table
----@param b table
----@return string|boolean mode
----@return table pathA?
----@return table pathB?
-function m.getPath(a, b, sameFunction)
- --- 首先测试双方在同一个函数内
- if sameFunction and m.getParentFunction(a) ~= m.getParentFunction(b) then
- return false
- end
- local mode
- local objA
- local objB
- if a.finish < b.start then
- mode = 'before'
- objA = a
- objB = b
- elseif a.start > b.finish then
- mode = 'after'
- objA = b
- objB = a
- else
- return 'equal', {}, {}
- end
- local pathA = {}
- local pathB = {}
- for _ = 1, 1000 do
- objA = m.getParentBlock(objA)
- pathA[#pathA+1] = objA
- if (not sameFunction and objA.type == 'function') or objA.type == 'main' then
- break
- end
- end
- for _ = 1, 1000 do
- objB = m.getParentBlock(objB)
- pathB[#pathB+1] = objB
- if (not sameFunction and objA.type == 'function') or objB.type == 'main' then
- break
- end
- end
- -- pathA: {1, 2, 3, 4, 5}
- -- pathB: {5, 6, 2, 3}
- local top = #pathB
- local start
- for i = #pathA, 1, -1 do
- local currentBlock = pathA[i]
- if currentBlock == pathB[top] then
- start = i
- break
- end
- end
- if not start then
- return nil
- end
- -- pathA: { 1, 2, 3}
- -- pathB: {5, 6, 2, 3}
- local extra = 0
- local align = top - start
- for i = start, 1, -1 do
- local currentA = pathA[i]
- local currentB = pathB[i+align]
- if currentA ~= currentB then
- extra = i
- break
- end
- end
- -- pathA: {1}
- local resultA = {}
- for i = extra, 1, -1 do
- resultA[#resultA+1] = pathA[i]
- end
- -- pathB: {5, 6}
- local resultB = {}
- for i = extra + align, 1, -1 do
- resultB[#resultB+1] = pathB[i]
- end
- return mode, resultA, resultB
-end
-
--- 根据语法,单步搜索定义
-local function stepRefOfLocal(loc, mode)
- local results = {}
- if loc.start ~= 0 then
- results[#results+1] = loc
- end
- local refs = loc.ref
- if not refs then
- return results
- end
- for i = 1, #refs do
- local ref = refs[i]
- if ref.start == 0 then
- goto CONTINUE
- end
- if mode == 'def' then
- if ref.type == 'local'
- or ref.type == 'setlocal' then
- results[#results+1] = ref
- end
- else
- if ref.type == 'local'
- or ref.type == 'setlocal'
- or ref.type == 'getlocal' then
- results[#results+1] = ref
- end
- end
- ::CONTINUE::
- end
- return results
-end
-
-local function stepRefOfLabel(label, mode)
- local results = { label }
- if not label or mode == 'def' then
- return results
- end
- local refs = label.ref
- if not refs then
- return results
- end
- for i = 1, #refs do
- local ref = refs[i]
- results[#results+1] = ref
- end
- return results
-end
-
-local function stepRefOfDocType(status, obj, mode)
- local results = {}
- if obj.type == 'doc.class.name'
- or obj.type == 'doc.type.name'
- or obj.type == 'doc.alias.name'
- or obj.type == 'doc.extends.name'
- or obj.type == 'doc.see.name' then
- local name = obj[1]
- if not name or not status.interface.docType then
- return results
- end
- if name == 'nil'
- or name == 'any'
- or name == 'boolean'
- or name == 'string'
- or name == 'table'
- or name == 'number'
- or name == 'integer'
- or name == 'function'
- or name == 'table'
- or name == 'thread'
- or name == 'userdata'
- or name == 'lightuserdata' then
- mode = 'def'
- end
- local docs = status.interface.docType(name)
- for i = 1, #docs do
- local doc = docs[i]
- if mode == 'def' then
- if doc.type == 'doc.class.name'
- or doc.type == 'doc.alias.name' then
- results[#results+1] = doc
- end
- else
- results[#results+1] = doc
- end
- end
- else
- results[#results+1] = obj
- end
- return results
-end
-
-function m.getStepRef(status, obj, mode)
- if obj.type == 'getlocal'
- or obj.type == 'setlocal' then
- return stepRefOfLocal(obj.node, mode)
- end
- if obj.type == 'local' then
- return stepRefOfLocal(obj, mode)
- end
- if obj.type == 'label' then
- return stepRefOfLabel(obj, mode)
- end
- if obj.type == 'goto' then
- return stepRefOfLabel(obj.node, mode)
- end
- if obj.type == 'doc.class.name'
- or obj.type == 'doc.type.name'
- or obj.type == 'doc.extends.name'
- or obj.type == 'doc.alias.name' then
- return stepRefOfDocType(status, obj, mode)
- end
- if obj.type == 'function' then
- return { obj }
- end
- return nil
-end
-
--- 根据语法,单步搜索field
-local function stepFieldOfLocal(loc)
- local results = {}
- local refs = loc.ref
- for i = 1, #refs do
- local ref = refs[i]
- if ref.type == 'setglobal'
- or ref.type == 'getglobal' then
- results[#results+1] = ref
- elseif ref.type == 'getlocal' then
- local nxt = ref.next
- if nxt then
- if nxt.type == 'setfield'
- or nxt.type == 'getfield'
- or nxt.type == 'setmethod'
- or nxt.type == 'getmethod'
- or nxt.type == 'setindex'
- or nxt.type == 'getindex' then
- results[#results+1] = nxt
- end
- end
- end
- end
- return results
-end
-local function stepFieldOfTable(tbl)
- local result = {}
- for i = 1, #tbl do
- result[i] = tbl[i]
- end
- return result
-end
-function m.getStepField(obj)
- if obj.type == 'getlocal'
- or obj.type == 'setlocal' then
- return stepFieldOfLocal(obj.node)
- end
- if obj.type == 'local' then
- return stepFieldOfLocal(obj)
- end
- if obj.type == 'table' then
- return stepFieldOfTable(obj)
- end
-end
-
-local function convertSimpleList(list)
- local simple = {}
- for i = #list, 1, -1 do
- local c = list[i]
- if c.type == 'getglobal'
- or c.type == 'setglobal' then
- if c.special == '_G' then
- simple.mode = 'global'
- goto CONTINUE
- end
- local loc = c.node
- if loc.special == '_G' then
- simple.mode = 'global'
- if not simple.node then
- simple.node = c
- end
- else
- simple.mode = 'local'
- simple[#simple+1] = m.getSimpleName(loc)
- if not simple.node then
- simple.node = loc
- end
- end
- elseif c.type == 'getlocal'
- or c.type == 'setlocal' then
- if c.special == '_G' then
- simple.mode = 'global'
- goto CONTINUE
- end
- simple.mode = 'local'
- if not simple.node then
- simple.node = c.node
- end
- elseif c.type == 'local' then
- simple.mode = 'local'
- if not simple.node then
- simple.node = c
- end
- else
- if not simple.node then
- simple.node = c
- end
- end
- simple[#simple+1] = m.getSimpleName(c) or m.ANY
- ::CONTINUE::
- end
- if simple.mode == 'global' and #simple == 0 then
- simple[1] = '_G'
- simple.node = list[#list]
- end
- return simple
-end
-
--- 搜索 `a.b.c` 的等价表达式
-local function buildSimpleList(obj, max)
- local list = {}
- local cur = obj
- local limit = max and (max + 1) or 11
- for i = 1, max or limit do
- if i == limit then
- return nil
- end
- while cur.type == 'paren' do
- cur = cur.exp
- if not cur then
- return nil
- end
- end
- if cur.type == 'setfield'
- or cur.type == 'getfield'
- or cur.type == 'setmethod'
- or cur.type == 'getmethod'
- or cur.type == 'setindex'
- or cur.type == 'getindex' then
- list[i] = cur
- cur = cur.node
- elseif cur.type == 'tablefield'
- or cur.type == 'tableindex' then
- list[i] = cur
- cur = cur.parent.parent
- if cur.type == 'return' then
- list[i+1] = list[i].parent
- break
- end
- elseif cur.type == 'getlocal'
- or cur.type == 'setlocal'
- or cur.type == 'local' then
- list[i] = cur
- break
- elseif cur.type == 'setglobal'
- or cur.type == 'getglobal' then
- list[i] = cur
- break
- elseif cur.type == 'select'
- or cur.type == 'table'
- or cur.type == 'call' then
- list[i] = cur
- break
- elseif cur.type == 'string' then
- list[i] = cur
- break
- elseif cur.type == '...' then
- list[i] = cur
- break
- elseif cur.type == 'doc.class.name'
- or cur.type == 'doc.type.name'
- or cur.type == 'doc.class'
- or cur.type == 'doc.see.name' then
- list[i] = cur
- break
- elseif cur.type == 'doc.see.field' then
- list[i] = cur
- cur = cur.parent.name
- elseif cur.type == 'function'
- or cur.type == 'main' then
- break
- else
- return nil
- end
- end
- return convertSimpleList(list)
-end
-
-function m.getSimple(obj, max)
- local simpleList
- if obj.type == 'getfield'
- or obj.type == 'setfield'
- or obj.type == 'getmethod'
- or obj.type == 'setmethod'
- or obj.type == 'getindex'
- or obj.type == 'setindex'
- or obj.type == 'local'
- or obj.type == 'getlocal'
- or obj.type == 'setlocal'
- or obj.type == 'setglobal'
- or obj.type == 'getglobal'
- 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 == '...'
- or obj.type == 'doc.class.name'
- or obj.type == 'doc.class'
- or obj.type == 'doc.type.name'
- or obj.type == 'doc.see.name'
- or obj.type == 'doc.see.field' then
- simpleList = buildSimpleList(obj, max)
- elseif obj.type == 'field'
- or obj.type == 'method' then
- simpleList = buildSimpleList(obj.parent, max)
- end
- return simpleList
-end
-
----Create a new status
----@param parentStatus core.guide.status
----@param interface table
----@param deep boolean
----@return core.guide.status
-function m.status(parentStatus, interface, deep)
- ---@class core.guide.status
- local status = {
- share = parentStatus and parentStatus.share or {
- count = 0,
- cacheLock = {},
- },
- depth = parentStatus and (parentStatus.depth + 1) or 0,
- searchDeep= parentStatus and parentStatus.searchDeep or deep or -999,
- interface = parentStatus and parentStatus.interface or {},
- deep = parentStatus and parentStatus.deep,
- clock = parentStatus and parentStatus.clock or osClock(),
- results = {},
- }
- if interface then
- for k, v in pairs(interface) do
- status.interface[k] = v
- end
- end
- status.deep = status.depth <= status.searchDeep
- return status
-end
-
-function m.copyStatusResults(a, b)
- local ra = a.results
- local rb = b.results
- for i = 1, #rb do
- ra[#ra+1] = rb[i]
- end
-end
-
-function m.isGlobal(source)
- if source.type == 'setglobal'
- or source.type == 'getglobal' then
- if source.node and source.node.tag == '_ENV' then
- return true
- end
- end
- if source.type == 'field' then
- source = source.parent
- end
- if source.type == 'getfield'
- or source.type == 'setfield' then
- local node = source.node
- if node and node.special == '_G' then
- return true
- end
- end
- return false
-end
-
-function m.isDoc(source)
- return source.type:sub(1, 4) == 'doc.'
-end
-
-function m.isDocClass(source)
- return source.type == 'doc.class'
-end
-
-function m.isSet(source)
- local tp = source.type
- if tp == 'setglobal'
- or tp == 'local'
- or tp == 'setlocal'
- or tp == 'setfield'
- or tp == 'setmethod'
- or tp == 'setindex'
- or tp == 'tablefield'
- or tp == 'tableindex' then
- return true
- end
- if tp == 'call' then
- local special = m.getSpecial(source.node)
- if special == 'rawset' then
- return true
- end
- end
- return false
-end
-
-function m.isGet(source)
- local tp = source.type
- if tp == 'getglobal'
- or tp == 'getlocal'
- or tp == 'getfield'
- or tp == 'getmethod'
- or tp == 'getindex' then
- return true
- end
- if tp == 'call' then
- local special = m.getSpecial(source.node)
- if special == 'rawget' then
- return true
- end
- end
- return false
-end
-
-function m.getSpecial(source)
- if not source then
- return nil
- end
- return source.special
-end
-
---- 根据函数的调用参数,获取:调用,参数索引
-function m.getCallAndArgIndex(callarg)
- local callargs = callarg.parent
- if not callargs or callargs.type ~= 'callargs' then
- return nil
- end
- local index
- for i = 1, #callargs do
- if callargs[i] == callarg then
- index = i
- break
- end
- end
- local call = callargs.parent
- return call, index
-end
-
---- 根据函数调用的返回值,获取:调用的函数,参数列表,自己是第几个返回值
-function m.getCallValue(source)
- local value = m.getObjectValue(source) or source
- if not value then
- return
- end
- local call, index
- if value.type == 'call' then
- call = value
- index = 1
- elseif value.type == 'select' then
- call = value.vararg
- index = value.index
- if call.type ~= 'call' then
- return
- end
- else
- return
- end
- if call.node.special == 'pcall'
- or call.node.special == 'xpcall' then
- return call.args and call.args[1], call.args, index - 1
- end
- return call.node, call.args, index
-end
-
-function m.getNextRef(ref)
- local nextRef = ref.next
- if nextRef then
- if nextRef.type == 'setfield'
- or nextRef.type == 'getfield'
- or nextRef.type == 'setmethod'
- or nextRef.type == 'getmethod'
- or nextRef.type == 'setindex'
- or nextRef.type == 'getindex' then
- return nextRef
- end
- end
- -- 穿透 rawget 与 rawset
- local call, index = m.getCallAndArgIndex(ref)
- if call then
- if call.node.special == 'rawset' and index == 1 then
- return call
- end
- if call.node.special == 'rawget' and index == 1 then
- return call
- end
- end
-
- return nil
-end
-
-function m.checkSameSimpleInValueOfTable(status, value, start, pushQueue)
- if value.type ~= 'table' then
- return
- end
- for i = 1, #value do
- local field = value[i]
- pushQueue(field, start + 1)
- end
-end
-
-function m.checkStatusDepth(status)
- if status.depth <= 20 then
- return true
- end
- if m.debugMode then
- error('status.depth overflow')
- elseif DEVELOP then
- --log.warn(debug.traceback('status.depth overflow'))
- logWarn('status.depth overflow')
- end
- return false
-end
-
-function m.searchFields(status, obj, key)
- if not m.checkStatusDepth(status) then
- return
- end
- local simple = m.getSimple(obj)
- if not simple then
- return
- end
- simple[#simple+1] = key or m.ANY
- m.searchSameFields(status, simple, 'field')
- m.cleanResults(status.results)
-end
-
-function m.searchDefFields(status, obj, key)
- if not m.checkStatusDepth(status) then
- return
- end
- 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
-
----@param obj parser.guide.object
----@return parser.guide.object
-function m.getObjectValue(obj)
- while obj.type == 'paren' do
- obj = obj.exp
- if not obj then
- return nil
- end
- end
- if obj.type == 'boolean'
- or obj.type == 'number'
- or obj.type == 'integer'
- or obj.type == 'string'
- or obj.type == 'doc.type.table'
- or obj.type == 'doc.type.arrary' then
- return obj
- end
- if obj.value then
- return obj.value
- end
- if obj.type == 'field'
- or obj.type == 'method' then
- return obj.parent and obj.parent.value
- end
- if obj.type == 'call' then
- if obj.node.special == 'rawset' then
- return obj.args and obj.args[3]
- else
- return obj
- end
- end
- if obj.type == 'select' then
- return obj
- end
- return nil
-end
-
-function m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue)
- local cache, makeCache = m.getRefCache(status, mt, '__index')
- if cache then
- for _, obj in ipairs(cache) do
- pushQueue(obj, start, true)
- end
- return
- end
- cache = {}
- local newStatus = m.status(status)
- m.searchDefFields(newStatus, mt, '__index')
- local refsStatus = m.status(status)
- for i = 1, #newStatus.results do
- local indexValue = m.getObjectValue(newStatus.results[i])
- if indexValue then
- m.searchRefs(refsStatus, indexValue, 'ref')
- end
- end
- for i = 1, #refsStatus.results do
- local obj = refsStatus.results[i]
- pushQueue(obj, start, true)
- cache[i] = obj
- end
- if makeCache then
- makeCache(cache)
- end
-end
-function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue)
- if not func or func.special ~= 'setmetatable' then
- return
- end
- local call = func.parent
- local args = call.args
- if not args then
- return
- end
- local obj = args[1]
- local mt = args[2]
- if obj then
- pushQueue(obj, start, true)
- local newStatus = m.status(status)
- m.searchRefs(newStatus, obj, 'def')
- for _, def in ipairs(newStatus.results) do
- pushQueue(def, start, true)
- end
- end
- if mt then
- if not status.share.markMetaTable then
- status.share.markMetaTable = {}
- end
- if status.share.markMetaTable[mt] then
- return
- end
- status.share.markMetaTable[mt] = true
- m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue)
- status.share.markMetaTable[mt] = nil
- end
-end
-
-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, pushQueue)
- end
- status.crossMetaTableMark = false
-end
-
-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
- pushQueue(obj, start + 1)
- end
- end
-end
-
-function m.checkSameSimpleInParamSelf(status, obj, start, pushQueue)
- if obj.type ~= 'getlocal' or obj[1] ~= 'self' then
- return
- end
- local node = obj.node
- if node.tag == 'self' then
- return
- end
- if node.parent.type ~= 'funcargs' then
- return
- end
- local func = node.parent.parent
- if func.type ~= 'function' or func.parent.type ~= 'setfield' then
- return
- end
- local fieldNode = func.parent.node
- local newStatus = m.status(status)
- m.searchRefs(newStatus, fieldNode, 'ref')
- for _, ref in ipairs(newStatus.results) do
- pushQueue(ref, start, true)
- 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 stepRefOfGenericCrossTable(status, doc, typeName)
- for _, typeUnit in ipairs(doc.extends.types) do
- if typeUnit.type == 'doc.type.table' then
- for _, where in ipairs {'key', 'value'} do
- local childTypes = typeUnit[where].types
- for _, childName in ipairs(childTypes) do
- if childName[1] == typeName[1] then
- return function (obj)
- local childStatus = m.status(status)
- m.searchRefs(childStatus, obj, 'def')
- for _, res in ipairs(childStatus.results) do
- if res.type == 'doc.type.table' then
- return res[where]
- end
- if res.type == 'doc.type.array' then
- if where == 'key' then
- return status.interface and status.interface.docType('integer')[1]
- end
- if where == 'value' then
- return res.node
- end
- end
- end
- end
- end
- end
- end
- return function (obj)
- return nil
- end
- elseif typeUnit.type == 'doc.type.array' then
- return function (obj)
- local childStatus = m.status(status)
- m.searchRefs(childStatus, obj, 'def')
- for _, res in ipairs(childStatus.results) do
- if res.type == 'doc.type.array' then
- return res.node
- end
- end
- end
- end
- end
- return nil
-end
-
-local function getIteratorArg(status, args, index)
- local call = args.parent
- local node = call.node
- if not node.iterator then
- return nil
- end
- if node.type ~= 'call' then
- return nil
- end
- local results = m.checkSameSimpleInCallInSameFile(status, node.node, node.args, index + 1)
- return results[1]
-end
-
-local function stepRefOfGeneric(status, typeUnit, args, mode)
- local results = {}
- if not args then
- return results
- end
- local myName = typeUnit[1]
- for _, typeName in ipairs(typeUnit.typeGeneric[myName]) do
- if typeName == typeUnit then
- goto CONTINUE
- end
- local docArg = m.getParentType(typeName, 'doc.type.arg')
- or m.getParentType(typeName, 'doc.param')
- or m.getParentType(typeName, 'doc.type.array')
- if not docArg then
- goto CONTINUE
- end
- local doc = m.getDocState(docArg)
- if not doc.bindSources then
- goto CONTINUE
- end
- local crossTable = stepRefOfGenericCrossTable(status, docArg, typeName)
-
- -- find out param index
- local genericIndex
- if docArg.type == 'doc.param' then
- local paramName = docArg.param[1]
- for _, source in ipairs(doc.bindSources) do
- if source.type == 'function'
- and source.args then
- for i, arg in ipairs(source.args) do
- if arg[1] == paramName then
- genericIndex = i
- break
- end
- end
- end
- end
- elseif docArg.type == 'doc.type.arg' then
- for index, arg in ipairs(docArg.parent.args) do
- if arg == docArg then
- genericIndex = index
- break
- end
- end
- end
-
- local callArg = args[genericIndex]
- or getIteratorArg(status, args, genericIndex)
-
- if not callArg then
- goto CONTINUE
- end
- if crossTable then
- callArg = crossTable(callArg)
- if not callArg then
- goto CONTINUE
- end
- end
- appendValidGenericType(results, status, typeName, callArg)
- ::CONTINUE::
- end
- return results
-end
-
-function m.checkSameSimpleByDocType(status, doc, args)
- if status.share.searchingBindedDoc then
- return
- end
- if doc.type ~= 'doc.type' then
- return
- end
- local results = {}
- for _, piece in ipairs(doc.types) do
- if piece.typeGeneric then
- local pieceResult = stepRefOfGeneric(status, piece, args, 'def')
- for _, res in ipairs(pieceResult) do
- results[#results+1] = res
- end
- else
- local pieceResult = stepRefOfDocType(status, piece, 'def')
- for _, res in ipairs(pieceResult) do
- results[#results+1] = res
- end
- end
- end
- return results
-end
-
-function m.checkSameSimpleByBindDocs(status, obj, start, pushQueue, mode)
- if not obj.bindDocs then
- return
- end
- if status.share.searchingBindedDoc then
- return
- end
- local skipInfer = false
- local results = {}
- for _, doc in ipairs(obj.bindDocs) do
- if doc.type == 'doc.class' then
- results[#results+1] = doc
- elseif doc.type == 'doc.type' then
- results[#results+1] = doc
- elseif doc.type == 'doc.param' then
- -- function (x) 的情况
- if obj.type == 'local'
- and m.getKeyName(obj) == doc.param[1] then
- if obj.parent.type == 'funcargs'
- or obj.parent.type == 'in'
- or obj.parent.type == 'loop' then
- results[#results+1] = doc.extends
- end
- end
- elseif doc.type == 'doc.field' then
- results[#results+1] = doc
- elseif doc.type == 'doc.vararg' then
- if obj.type == '...' then
- results[#results+1] = doc
- end
- end
- end
- for _, res in ipairs(results) do
- if res.type == 'doc.class'
- or res.type == 'doc.type'
- or res.type == 'doc.vararg' then
- pushQueue(res, start, true)
- skipInfer = true
- end
- if res.type == 'doc.type.function' then
- pushQueue(res, start, true)
- elseif res.type == 'doc.field' then
- pushQueue(res, start + 1)
- end
- end
- return skipInfer
-end
-
-function m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode)
- if status.share.searchingBindedDoc then
- return
- end
- if not obj.bindSources then
- return
- end
- status.share.searchingBindedDoc = true
- local mark = {}
- local newStatus = m.status(status)
- for _, ref in ipairs(obj.bindSources) do
- if not mark[ref] then
- mark[ref] = true
- m.searchRefs(newStatus, ref, mode)
- end
- end
- status.share.searchingBindedDoc = nil
- for _, res in ipairs(newStatus.results) do
- pushQueue(res, start, true)
- end
-end
-
-function m.checkSameSimpleOfRefByDocReturn(status, obj, start, pushQueue, mode)
- if status.share.searchingBindedDoc then
- return
- end
- if not obj.bindSources then
- return
- end
- local index = 0
- for _, doc in ipairs(obj.bindGroup) do
- if doc.type == 'doc.return' then
- index = index + 1
- if doc == obj then
- break
- end
- end
- end
- status.share.searchingBindedDoc = true
- local mark = {}
- local newStatus = m.status(status)
- for _, ref in ipairs(obj.bindSources) do
- if not mark[ref] then
- mark[ref] = true
- m.searchRefs(newStatus, ref, mode)
- end
- end
- status.share.searchingBindedDoc = nil
- for _, res in ipairs(newStatus.results) do
- local call = res.parent
- if call.type == 'call' then
- if index == 1 then
- local sel = call.parent
- if sel.type == 'select' and sel.index == index then
- pushQueue(sel.parent, start, true)
- end
- else
- if call.extParent then
- for _, sel in ipairs(call.extParent) do
- if sel.type == 'select' and sel.index == index then
- pushQueue(sel.parent, start, true)
- end
- end
- end
- end
- end
- end
-end
-
-local function getArrayOrTableLevel(obj)
- local level = 0
- while true do
- local parent = obj.parent
- if parent.type == 'doc.type.array' then
- level = level + 1
- elseif parent.type == 'doc.type.table' then
- if obj.type == 'doc.type' then
- level = level + 1
- -- else 只存在 obj.type == 'doc.type.name' 的情况,即 table<k,v> 中的 table,这种是不需要再增加层级的
- end
- elseif parent.type == 'doc.type' and parent.parent and parent.parent.type == 'doc.type.table' then
- level = level + 1
- parent = parent.parent
- else
- break
- end
- obj = parent
- end
- return level
-end
-
-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
- obj = m.getDocState(obj)
- end
- local classStart
- for _, doc in ipairs(obj.bindGroup) do
- if doc == obj then
- classStart = true
- elseif doc.type == 'doc.class' then
- classStart = false
- end
- if classStart and doc.type == 'doc.field' then
- pushQueue(doc, start + 1)
- end
- end
- m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode)
- if mode == 'ref' then
- local pieceResult = stepRefOfDocType(status, obj.class, 'ref')
- for _, res in ipairs(pieceResult) do
- pushQueue(res, start, true)
- end
- if obj.extends then
- for _, ext in ipairs(obj.extends) do
- local pieceResult = stepRefOfDocType(status, ext, 'def')
- for _, res in ipairs(pieceResult) do
- pushQueue(res, start, true)
- end
- end
- end
- end
- return true
- elseif obj.type == 'doc.type' then
- for _, piece in ipairs(obj.types) do
- local pieceResult = stepRefOfDocType(status, piece, 'def')
- for _, res in ipairs(pieceResult) do
- pushQueue(res, start, true)
- end
- end
- if mode == 'ref' then
- 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
- pushQueue(res, start, true)
- end
-
- if mode == 'ref' then
- local state = m.getDocState(obj)
- if state.type == 'doc.type' then
- m.checkSameSimpleOfRefByDocSource(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode)
- end
- if state.type == 'doc.return' then
- m.checkSameSimpleOfRefByDocReturn(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode)
- end
- end
- return true
- elseif obj.type == 'doc.field' then
- if mode ~= 'field'
- and mode ~= 'deffield' then
- return m.checkSameSimpleByDoc(status, obj.extends, start, pushQueue, mode)
- end
- elseif obj.type == 'doc.type.array' then
- pushQueue(obj.node, start + 1, true)
- return true
- elseif obj.type == 'doc.type.table' then
- pushQueue(obj.node, start, true)
- pushQueue(obj.value, start + 1, true)
- return true
- elseif obj.type == 'doc.vararg' then
- pushQueue(obj.vararg, start, true)
- end
-end
-
-function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, pushQueue)
- local args = obj.parent
- if not args or args.type ~= 'callargs' then
- return
- end
- local callNode = args.parent.node
- if callNode.special ~= 'setmetatable' then
- return
- end
- if args[1] ~= obj then
- return
- end
- local mt = args[2]
- if mt then
- if m.hasValueMark(status, mt) then
- return
- end
- m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue)
- end
-end
-
-function m.searchSameMethodOutSelf(ref, mark)
- local selfNode
- if ref.tag == 'self' then
- selfNode = ref
- else
- if ref.type == 'getlocal'
- or ref.type == 'setlocal' then
- local node = ref.node
- if node.tag == 'self' then
- selfNode = node
- end
- end
- end
- if selfNode then
- if mark[selfNode] then
- return nil
- end
- mark[selfNode] = true
- local method = selfNode.method.node
- if mark[method] then
- return nil
- end
- mark[method] = true
- return method
- end
-end
-
-function m.searchSameMethodIntoSelf(ref, mark)
- local nxt = ref.next
- if not nxt then
- return nil
- end
- if nxt.type ~= 'setmethod' then
- return nil
- end
- 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, pushQueue, mode)
- if status.share.crossMethodMark2 then
- return
- end
- local mark = status.crossMethodMark
- if not mark then
- mark = {}
- status.crossMethodMark = mark
- end
- if mark[ref] then
- return
- end
- local selfRef = m.searchSameMethodIntoSelf(ref, mark)
- if selfRef then
- tracy.ZoneBeginN 'searchSameFieldsCrossMethod'
- local _ <close> = tracy.ZoneEnd
- -- 如果自己是method,则只检查自己内部的self引用
- status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1
- status.share.crossMethodMark2 = true
- local newStatus = m.status(status)
- m.searchRefs(newStatus, selfRef, mode)
- for _, res in ipairs(newStatus.results) do
- pushQueue(res, start, true)
- end
- status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1
- status.share.crossMethodMark2 = nil
- return
- end
- local method = m.searchSameMethodOutSelf(ref, mark)
- if method then
- pushQueue(method, start, true)
- return
- end
-end
-
-local function checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, source, index, args)
- source = m.getObjectValue(source) or source
- if not source or source.type ~= 'function' then
- return
- end
- if not source.bindDocs then
- return
- end
- local returns = {}
- for _, doc in ipairs(source.bindDocs) do
- if doc.type == 'doc.return' then
- for _, rtn in ipairs(doc.returns) do
- returns[#returns+1] = rtn
- end
- end
- end
- local rtn = returns[index]
- if not rtn then
- return
- end
- local types = m.checkSameSimpleByDocType(status, rtn, args)
- if not types then
- return
- end
- for _, res in ipairs(types) do
- results[#results+1] = res
- end
- return true
-end
-
-local function checkSameSimpleAndMergeDocFunctionReturn(status, results, docFunc, index, args)
- if docFunc.type ~= 'doc.type.function' then
- return
- end
- local rtn = docFunc.returns[index]
- if rtn then
- local types = m.checkSameSimpleByDocType(status, rtn, args)
- if types then
- for _, res in ipairs(types) do
- results[#results+1] = res
- end
- return true
- end
- end
-end
-
-local function checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, source, index)
- if not source.bindDocs then
- return
- end
- for _, doc in ipairs(source.bindDocs) do
- if doc.type == 'doc.type' then
- for _, typeUnit in ipairs(doc.types) do
- if checkSameSimpleAndMergeDocFunctionReturn(status, results, typeUnit, index) then
- return true
- end
- end
- end
- end
-end
-
-function m.checkSameSimpleInCallInSameFile(status, func, args, index)
- if not status.share.callResultsCache then
- status.share.callResultsCache = {}
- end
- local cache = status.share.callResultsCache[func]
- if not cache then
- cache = {}
- status.share.callResultsCache[func] = cache
- end
- local results = cache[index]
- if results then
- return results
- end
- results = {}
- if func.special then
- --return results
- end
- local newStatus = m.status(status)
- m.searchRefs(newStatus, func, 'def')
- local hasDocReturn
- for _, def in ipairs(newStatus.results) do
- hasDocReturn = checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, def, index)
- or checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, def, index, args)
- or checkSameSimpleAndMergeDocFunctionReturn(status, results, def, index, args)
- or hasDocReturn
- end
- if not hasDocReturn then
- for _, def in ipairs(newStatus.results) do
- local value = m.getObjectValue(def) or def
- if value.type == 'function' then
- local returns = value.returns
- if returns then
- for _, ret in ipairs(returns) do
- local exp = ret[index]
- if exp then
- results[#results+1] = exp
- end
- end
- end
- end
- end
- cache[index] = results
- end
- return results
-end
-
-function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode)
- if status.share.inBeSetValue and status.share.inBeSetValue > 0 then
- return
- end
- if status.share.inSetValue and status.share.inSetValue > 5 then
- return
- end
- local func, args, index = m.getCallValue(ref)
- if not func then
- return
- end
- if m.checkCallMark(status, func.parent, true) then
- return
- end
- status.share.inSetValue = (status.share.inSetValue or 0) + 1
- -- 检查赋值是 semetatable() 的情况
- m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue)
- local objs = m.checkSameSimpleInCallInSameFile(status, func, args, index)
- if status.interface.call then
- local cobjs = status.interface.call(func, args, index)
- if cobjs then
- for _, obj in ipairs(cobjs) do
- if not m.checkReturnMark(status, obj) then
- objs[#objs+1] = obj
- end
- end
- end
- end
- m.cleanResults(objs)
- if not status.share.callFuncMark then
- status.share.callFuncMark = {}
- end
- local mark = {}
- for _, obj in ipairs(objs) do
- if mark[obj] then
- goto CONTINUE
- end
- if status.share.callFuncMark[obj] then
- goto CONTINUE
- end
- status.share.callFuncMark[obj] = true
- local newStatus = m.status(status)
- m.searchRefs(newStatus, obj, mode)
- pushQueue(obj, start, true)
- mark[obj] = true
- for _, obj in ipairs(newStatus.results) do
- pushQueue(obj, start, true)
- mark[obj] = true
- end
- status.share.callFuncMark[obj] = nil
- ::CONTINUE::
- end
- status.share.inSetValue = (status.share.inSetValue or 0) - 1
-end
-
-local function searchRawset(ref, results)
- if m.getKeyName(ref) ~= 'rawset' then
- return
- end
- local call = ref.parent
- if call.type ~= 'call' or call.node ~= ref then
- return
- end
- if not call.args then
- return
- end
- local arg1 = call.args[1]
- if arg1.special ~= '_G' then
- -- 不会吧不会吧,不会真的有人写成 `rawset(_G._G._G, 'xxx', value)` 吧
- return
- end
- results[#results+1] = call
-end
-
-local function searchG(ref, results)
- while ref and m.getKeyName(ref) == '_G' do
- results[#results+1] = ref
- ref = ref.next
- end
- if ref then
- results[#results+1] = ref
- searchRawset(ref, results)
- end
-end
-
-local function searchEnvRef(ref, results)
- if ref.type == 'setglobal'
- or ref.type == 'getglobal' then
- results[#results+1] = ref
- searchG(ref, results)
- elseif ref.type == 'getlocal' then
- results[#results+1] = ref.next
- searchG(ref.next, results)
- end
-end
-
-function m.findGlobals(ast)
- local root = m.getRoot(ast)
- local results = {}
- local env = m.getENV(root)
- if env.ref then
- for _, ref in ipairs(env.ref) do
- searchEnvRef(ref, results)
- end
- end
- return results
-end
-
-function m.findGlobalsOfName(ast, name)
- local root = m.getRoot(ast)
- local results = {}
- local globals = m.findGlobals(root)
- for _, global in ipairs(globals) do
- if m.getKeyName(global) == name then
- results[#results+1] = global
- end
- end
- return results
-end
-
-function m.checkSameSimpleInGlobal(status, source)
- local name = m.getKeyName(source)
- if not name then
- return
- end
- local objs
- if status.interface.global then
- objs = status.interface.global(name)
- else
- objs = m.findGlobalsOfName(source, name)
- end
- return objs
-end
-
-function m.hasValueMark(status, value)
- if not status.share.valueMark then
- status.share.valueMark = {}
- end
- if status.share.valueMark[value] then
- return true
- end
- status.share.valueMark[value] = true
- return false
-end
-
-function m.checkCallMark(status, a, mark)
- if not status.share.callMark then
- status.share.callMark = {}
- end
- if mark then
- status.share.callMark[a] = mark
- else
- return status.share.callMark[a]
- end
- return false
-end
-
-function m.checkReturnMark(status, a, mark)
- if not status.share.returnMark then
- status.share.returnMark = {}
- end
- local result = status.share.returnMark[a]
- if mark then
- status.share.returnMark[a] = mark
- end
- return result
-end
-
-function m.searchSameFieldsInValue(status, ref, start, pushQueue, mode)
- if status.share.inBeSetValue and status.share.inBeSetValue > 0 then
- return
- end
- if status.share.inSetValue and status.share.inSetValue > 5 then
- return
- end
- local value = m.getObjectValue(ref)
- if not value then
- return
- end
- if m.hasValueMark(status, value) then
- return
- end
- status.share.inSetValue = (status.share.inSetValue or 0) + 1
- if not status.share.tempValueMark then
- status.share.tempValueMark = {}
- end
- if not status.share.tempValueMark[value] then
- status.share.tempValueMark[value] = true
- local newStatus = m.status(status)
- m.searchRefs(newStatus, value, mode)
- status.share.tempValueMark[value] = nil
- for _, res in ipairs(newStatus.results) do
- pushQueue(res, start, true)
- end
- pushQueue(value, start, true)
- end
- status.share.inSetValue = (status.share.inSetValue or 0) - 1
- -- 检查形如 a = f() 的分支情况
- m.checkSameSimpleInCall(status, value, start, pushQueue, mode)
-end
-
-function m.checkSameSimpleAsTableField(status, ref, start, pushQueue)
- if not status.deep then
- --return
- end
- local parent = ref.parent
- if not parent or parent.type ~= 'tablefield' then
- return
- end
- if m.hasValueMark(status, ref) then
- return
- end
- local newStatus = m.status(status)
- m.searchRefs(newStatus, parent.field, 'ref')
- for _, res in ipairs(newStatus.results) do
- pushQueue(res, start, true)
- end
-end
-
-function m.checkSearchLevel(status)
- status.share.back = status.share.back or 0
- if status.share.back >= (status.interface.searchLevel or 0) then
- -- TODO 限制向前搜索的次数
- --return true
- end
- status.share.back = status.share.back + 1
- return false
-end
-
-function m.checkSameSimpleAsReturn(status, ref, start, pushQueue)
- if not status.deep then
- return
- end
- if not ref.parent or ref.parent.type ~= 'return' then
- return
- end
- if ref.parent.parent.type ~= 'main' then
- return
- end
- if m.checkSearchLevel(status) then
- return
- end
- local newStatus = m.status(status)
- m.searchRefsAsFunctionReturn(newStatus, ref, 'ref')
- for _, res in ipairs(newStatus.results) do
- if not m.checkCallMark(status, res) then
- pushQueue(res, start, true)
- end
- end
-end
-
-function m.checkSameSimpleAsSetValue(status, ref, start, pushQueue)
- if not status.deep then
- --return
- end
- if status.share.inSetValue and status.share.inSetValue > 0 then
- return
- end
- if status.share.inBeSetValue and status.share.inBeSetValue > 5 then
- return
- end
- if ref.type == 'select' then
- return
- end
- local parent = ref.parent
- if not parent then
- return
- end
- if m.getObjectValue(parent) ~= ref then
- return
- end
- if m.hasValueMark(status, ref) then
- return
- end
- if m.checkSearchLevel(status) then
- return
- end
- local obj
- if parent.type == 'local'
- or parent.type == 'setglobal'
- or parent.type == 'setlocal' then
- obj = parent
- elseif parent.type == 'setfield' then
- obj = parent.field
- elseif parent.type == 'setmethod' then
- obj = parent.method
- end
- if not obj then
- return
- end
- status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1
- local newStatus = m.status(status)
- m.searchRefs(newStatus, obj, 'ref')
- for _, res in ipairs(newStatus.results) do
- pushQueue(res, start, true)
- end
- status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1
-end
-
-local function getTableAndIndexIfIsForPairsKeyOrValue(ref)
- if ref.type ~= 'local' then
- return
- end
-
- if not ref.parent or ref.parent.type ~= 'in' then
- return
- end
-
- if not ref.value or ref.value.type ~= 'select' then
- return
- end
-
- local rootSelectObj = ref.value
- if rootSelectObj.index ~= 1 and rootSelectObj.index ~= 2 then
- return
- end
-
- if not rootSelectObj.vararg or rootSelectObj.vararg.type ~= 'call' then
- return
- end
- local rootCallObj = rootSelectObj.vararg
-
- if not rootCallObj.node or rootCallObj.node.type ~= 'call' then
- return
- end
- local pairsCallObj = rootCallObj.node
-
- if not pairsCallObj.node
- or (pairsCallObj.node.special ~= 'pairs' and pairsCallObj.node.special ~= 'ipairs') then
- return
- end
-
- if not pairsCallObj.args or not pairsCallObj.args[1] then
- return
- end
- local tableObj = pairsCallObj.args[1]
-
- return tableObj, rootSelectObj.index
-end
-
-function m.checkSameSimpleAsKeyOrValueInForParis(status, ref, start, pushQueue)
- local tableObj, index = getTableAndIndexIfIsForPairsKeyOrValue(ref)
- if not tableObj then
- return
- end
-
- local newStatus = m.status(status)
- m.searchRefs(newStatus, tableObj, 'def')
- for _, def in ipairs(newStatus.results) do
- if def.bindDocs then
- for _, binddoc in ipairs(def.bindDocs) do
- if binddoc.type == 'doc.type' then
- if binddoc.types[1] and binddoc.types[1].type == 'doc.type.table' then
- if index == 1 then
- pushQueue(binddoc.types[1].key, start, true)
- elseif index == 2 then
- pushQueue(binddoc.types[1].value, start, true)
- end
- end
- end
- end
- end
- end
-end
-
----
----@param func parser.guide.object
----@param argIndex integer
----@return integer?
-local function findGenericFromArgIndexToReturnIndex(func, argIndex)
- if not func.bindDocs then
- return nil
- end
- local paramType
- for _, doc in ipairs(func.bindDocs) do
- if doc.type == 'doc.param' then
- if doc.extends and doc.extends.paramIndex == argIndex then
- paramType = doc.extends
- break
- end
- end
- end
- if not paramType then
- return nil
- end
- for _, typeUnit in ipairs(paramType.types) do
- if typeUnit.typeGeneric then
- local generic = typeUnit.typeGeneric[typeUnit[1]]
- if generic then
- for _, typeName in ipairs(generic) do
- local docType = typeName.parent
- if docType.returnIndex then
- return docType.returnIndex
- end
- end
- end
- end
- end
- return nil
-end
-
-function m.checkSameSimpleAsCallArg(status, ref, start, pushQueue)
- if not status.deep then
- return
- end
- local call, index = m.getCallAndArgIndex(ref)
- if not call then
- return
- end
- if call.parent.type ~= 'select' then
- return
- end
- if (status.share.inSetValue or 0) > 0 then
- return
- end
- if status.share.inBeSetValue and status.share.inBeSetValue > 5 then
- return
- end
- status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1
- local newStatus = m.status(status)
- m.searchRefs(newStatus, call.node, 'def')
- for _, func in ipairs(newStatus.results) do
- local rindex = findGenericFromArgIndexToReturnIndex(func, index)
- if rindex then
- if rindex == 1 then
- pushQueue(call.parent.parent, start, true)
- else
- if call.extParent then
- for _, slt in ipairs(call.extParent) do
- if slt.index == rindex then
- pushQueue(slt.parent, start, true)
- end
- end
- end
- end
- end
- end
- status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1
-end
-
-local function hasTypeName(doc, name)
- if doc.type == 'doc.type' then
- for _, tunit in ipairs(doc.types) do
- if tunit.type == 'doc.type.name'
- and tunit[1] == name then
- return true
- end
- end
- end
- if doc.type == 'doc.type.name'
- or doc.type == 'doc.class.name' then
- if doc[1] == name then
- return true
- end
- end
- return false
-end
-
-function m.checkSameSimpleInString(status, ref, start, pushQueue, mode)
- -- 特殊处理 ('xxx').xxx 的形式
- if ref.type ~= 'string'
- and not hasTypeName(ref, 'string') then
- return
- end
- if not status.interface.docType then
- return
- end
- if status.share.searchingBindedDoc then
- return
- end
- if not status.share.markString then
- status.share.markString = {}
- end
- local marked = ref
- if ref.type == 'string' then
- marked = 'string'
- end
- if status.share.markString[marked] then
- return
- end
- status.share.markString[marked] = true
- local newStatus = m.status(status)
- local docs = status.interface.docType('stringlib')
- local mark = {}
- for i = 1, #docs do
- local doc = docs[i]
- m.searchFields(newStatus, doc)
- end
- for _, res in ipairs(newStatus.results) do
- if mark[res] then
- goto CONTINUE
- end
- mark[res] = true
- pushQueue(res, start + 1)
- ::CONTINUE::
- end
- status.share.markString[marked] = nil
- return true
-end
-
+-- TODO: compatible
+m.getRoot = guide.getRoot
+m.eachSource = guide.eachSource
+m.eachSourceType = guide.eachSourceType
+m.eachSourceContain = guide.eachSourceContain
+m.eachSourceBetween = guide.eachSourceBetween
+m.getStartFinish = guide.getStartFinish
+m.isLiteral = guide.isLiteral
+
+---@alias guide.searchmode '"ref"'|'"def"'|'"field"'
+
+---添加结果
+---@param status guide.status
+---@param mode guide.searchmode
+---@param ref parser.guide.object
+---@param simple table
function m.pushResult(status, mode, ref, simple)
local results = status.results
if mode == 'def' then
@@ -2836,7 +109,7 @@ function m.pushResult(status, mode, ref, simple)
if ref.parent and ref.parent.type == 'return' then
results[#results+1] = ref
end
- if m.isLiteral(ref)
+ if guide.isLiteral(ref)
and ref.parent
and ref.parent.type == 'callargs'
and ref ~= simple.node then
@@ -2890,1948 +163,91 @@ function m.pushResult(status, mode, ref, simple)
end
end
-function m.checkSameSimpleName(ref, sm)
- if sm == m.ANY then
- return true
- end
-
- if m.getSimpleName(ref) == sm then
- return true
- end
- if ref.type == 'doc.type'
- and ref.array == true then
- return true
- end
- return false
-end
-
-function m.isValidSetRef(ref)
- if m.isSet(ref) then
- return true
- end
-end
-
-function m.checkSameSimple(status, simple, ref, start, force, mode, pushQueue)
- if start > #simple then
- return
- end
- for i = start, #simple do
- local sm = simple[i]
- if not force and not m.checkSameSimpleName(ref, sm) then
- return
- end
- force = false
- local cmode = mode
- local skipSearch
- if i < #simple then
- cmode = 'ref'
- else
- if mode == 'deffield' then
- if not m.isSet(ref) then
- skipSearch = true
- end
- end
- end
- -- 检查 doc
- local skipInfer = m.checkSameSimpleByBindDocs(status, ref, i, pushQueue, cmode)
- or m.checkSameSimpleByDoc(status, ref, i, pushQueue, cmode)
- -- 检查自己是字符串的分支情况
- m.checkSameSimpleInString(status, ref, i, pushQueue, cmode)
- if not skipInfer and not skipSearch then
- -- 穿透 self:func 与 mt:func
- m.searchSameFieldsCrossMethod(status, ref, i, pushQueue, cmode)
- -- 穿透赋值
- m.searchSameFieldsInValue(status, ref, i, pushQueue, cmode)
- -- 检查自己是字面量表的情况
- m.checkSameSimpleInValueOfTable(status, ref, i, pushQueue)
- -- 检查自己作为 setmetatable 第一个参数的情况
- m.checkSameSimpleInArg1OfSetMetaTable(status, ref, i, pushQueue)
- -- 检查自己作为 setmetatable 调用的情况
- m.checkSameSimpleInValueOfCallMetaTable(status, ref, i, pushQueue)
- -- 检查自己是特殊变量的分支的情况
- m.checkSameSimpleInSpecialBranch(status, ref, i, pushQueue)
- -- self 的特殊处理
- m.checkSameSimpleInParamSelf(status, ref, i, pushQueue)
- -- 自己是 call 的情况
- m.checkSameSimpleInCall(status, ref, i, pushQueue, cmode)
- if cmode == 'ref' then
- -- 检查形如 { a = f } 的情况
- m.checkSameSimpleAsTableField(status, ref, i, pushQueue)
- -- 检查形如 return m 的情况
- m.checkSameSimpleAsReturn(status, ref, i, pushQueue)
- -- 检查形如 a = f 的情况
- m.checkSameSimpleAsSetValue(status, ref, i, pushQueue)
- -- 检查形如 for k,v in pairs()/ipairs() do end 的情况
- m.checkSameSimpleAsKeyOrValueInForParis(status, ref, i, pushQueue)
- -- 检查自己是函数参数的情况(泛型) local x = call(V)
- m.checkSameSimpleAsCallArg(status, ref, i, pushQueue)
- end
- end
- if i == #simple then
- break
- end
- ref = m.getNextRef(ref)
- if not ref then
- return
- end
- end
- m.pushResult(status, mode, ref, simple)
- local value = m.getObjectValue(ref)
- if value then
- m.pushResult(status, mode, value, simple)
- 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 queues, starts, forces = allocQueue()
- local queueLen = 0
- local locks = {}
- local function appendQueue(obj, start, force)
- local lock = locks[start]
- if not lock then
- lock = {}
- locks[start] = lock
- end
- if lock[obj] then
- return
- end
- lock[obj] = true
- queueLen = queueLen + 1
- queues[queueLen] = obj
- starts[queueLen] = start
- forces[queueLen] = force
- if obj.mirror then
- if not lock[obj.mirror] then
- lock[obj.mirror] = true
- queueLen = queueLen + 1
- queues[queueLen] = obj.mirror
- starts[queueLen] = start
- forces[queueLen] = force
- end
- end
- end
- local function pushQueue(obj, start, force)
- if obj.type == 'getlocal'
- or obj.type == 'setlocal' then
- obj = obj.node
- end
- appendQueue(obj, start, force)
- if obj.type == 'local' and obj.ref then
- for _, ref in ipairs(obj.ref) do
- appendQueue(ref, start, force)
- end
- end
- if m.isGlobal(obj) then
- local refs = m.checkSameSimpleInGlobal(status, obj)
- if refs then
- for _, ref in ipairs(refs) do
- appendQueue(ref, start, force)
- end
- end
- end
- end
- if simple.mode == 'global' then
- -- 全局变量开头
- pushQueue(simple.node, 1)
- elseif simple.mode == 'local' then
- -- 局部变量开头
- pushQueue(simple.node, 1)
- else
- pushQueue(simple.node, 1)
- end
- local max = 0
- for i = 1, 1e6 do
- if queueLen <= 0 then
- break
- end
- local obj = queues[queueLen]
- local start = starts[queueLen]
- local force = forces[queueLen]
- queues[queueLen] = nil
- starts[queueLen] = nil
- forces[queueLen] = nil
- queueLen = queueLen - 1
- max = max + 1
- status.share.count = status.share.count + 1
- if status.share.count % 10000 == 0 then
- --if TEST then
- -- print('####', status.share.count, osClock() - status.clock)
- --end
- if status.interface and status.interface.pulse then
- status.interface.pulse()
- end
- end
- --if status.share.count >= 100000 then
- -- logWarn('Count too large!')
- -- break
- --end
- m.checkSameSimple(status, simple, obj, start, force, mode, pushQueue)
- if max >= 10000 then
- logWarn('Queue too large!')
- break
- end
- end
- --deallocQueue(queues, starts, forces)
-end
-
-function m.getCallerInSameFile(status, func)
- -- 搜索所有所在函数的调用者
- local funcRefs = m.status(status)
- m.searchRefOfValue(funcRefs, func)
-
- local calls = {}
- if #funcRefs.results == 0 then
- return calls
+---获取uri
+---@param obj parser.guide.object
+---@return uri
+function m.getUri(obj)
+ if obj.uri then
+ return obj.uri
end
- for _, res in ipairs(funcRefs.results) do
- local call = res.parent
- if call.type == 'call' then
- calls[#calls+1] = call
- end
+ local root = m.getRoot(obj)
+ if root then
+ return root.uri
end
- return calls
+ return ''
end
-function m.getCallerCrossFiles(status, main)
- if status.interface.link then
- return status.interface.link(main.uri)
- end
+-- TODO
+function m.findGlobals(root)
+ linker.compileLinks(root)
+ -- TODO
return {}
end
-function m.searchRefsAsFunctionReturn(status, obj, mode)
- if not status.deep then
- return
- end
- if mode == 'def' then
- return
- end
- if m.checkReturnMark(status, obj, true) then
- return
- end
- status.results[#status.results+1] = obj
- -- 搜索所在函数
- local currentFunc = m.getParentFunction(obj)
- local rtn = obj.parent
- if rtn.type ~= 'return' then
- return
- end
- -- 看看他是第几个返回值
- local index
- for i = 1, #rtn do
- if obj == rtn[i] then
- index = i
- break
- end
- end
- if not index then
- return
- end
- local calls
- if currentFunc.type == 'main' then
- calls = m.getCallerCrossFiles(status, currentFunc)
- else
- calls = m.getCallerInSameFile(status, currentFunc)
- end
- -- 搜索调用者的返回值
- if #calls == 0 then
- return
- end
- local selects = {}
- for i = 1, #calls do
- local parent = calls[i].parent
- if parent.type == 'select' and parent.index == index then
- selects[#selects+1] = parent.parent
- end
- local extParent = calls[i].extParent
- if extParent then
- for j = 1, #extParent do
- local ext = extParent[j]
- if ext.type == 'select' and ext.index == index then
- selects[#selects+1] = ext.parent
- end
- end
- end
- end
- -- 搜索调用者的引用
- for i = 1, #selects do
- m.searchRefs(status, selects[i], 'ref')
- end
-end
-
-function m.searchRefsAsFunctionSet(status, obj, mode)
- local parent = obj.parent
- if not parent then
- return
- end
- if parent.type == 'local'
- or parent.type == 'setlocal'
- or parent.type == 'setglobal'
- or parent.type == 'setfield'
- or parent.type == 'setmethod'
- or parent.type == 'tablefield' then
- m.searchRefs(status, parent, mode)
- elseif parent.type == 'setindex'
- or parent.type == 'tableindex' then
- if parent.index == obj then
- m.searchRefs(status, parent, mode)
- end
- end
-end
-
-function m.searchRefsAsFunction(status, obj, mode)
- if obj.type ~= 'function'
- and obj.type ~= 'table' then
- return
- end
- m.searchRefsAsFunctionSet(status, obj, mode)
- -- 检查自己作为返回函数时的引用
- m.searchRefsAsFunctionReturn(status, obj, mode)
-end
-
-function m.cleanResults(results)
- local mark = {}
- for i = #results, 1, -1 do
- local res = results[i]
- if res.tag == 'self'
- or mark[res] then
- results[i] = results[#results]
- results[#results] = nil
- else
- mark[res] = true
- end
- end
-end
-
---function m.getRefCache(status, obj, mode)
--- local cache = status.interface.cache and status.interface.cache()
--- if not cache then
--- return
--- end
--- if m.isGlobal(obj) then
--- obj = m.getKeyName(obj)
--- end
--- if not cache[mode] then
--- cache[mode] = {}
--- end
--- local sourceCache = cache[mode][obj]
--- if sourceCache then
--- return sourceCache
--- end
--- sourceCache = {}
--- cache[mode][obj] = sourceCache
--- return nil, function (results)
--- for i = 1, #results do
--- sourceCache[i] = results[i]
--- end
--- end
---end
-
-function m.getRefCache(status, obj, mode)
- local isDeep = status.deep
- if mode == 'infer' then
- if not isDeep then
- return nil, nil
- end
- end
- local globalCache = status.interface.cache and status.interface.cache() or {}
- if m.isGlobal(obj) then
- obj = m.getKeyName(obj)
- end
- if not obj then
- return {}
- end
- if not globalCache[mode] then
- globalCache[mode] = {}
- end
- local sourceCache = globalCache[mode][obj]
- if sourceCache then
- return sourceCache
- end
- if not status.share.cacheLock[mode] then
- status.share.cacheLock[mode] = {}
- end
- if status.share.cacheLock[mode][obj] then
- return {}
- end
- status.share.cacheLock[mode][obj] = {}
- return nil, function (results)
- sourceCache = {}
- for i = 1, #results do
- sourceCache[i] = results[i]
- end
- globalCache[mode][obj] = sourceCache
- if not isDeep then
- return
- end
- if mode == 'ref'
- or mode == 'def' then
- for i = 1, #results do
- local res = results[i]
- if not globalCache[mode][res] then
- globalCache[mode][res] = sourceCache
- end
- end
- end
- end
+-- TODO
+function m.isGlobal(source)
+ return false
end
+---搜索对象的引用
+---@param status guide.status
+---@param obj parser.guide.object
+---@param mode guide.searchmode
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
- status.results[#status.results+1] = cache[i]
- end
- return
- 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 m.checkStatusDepth(status) then
- local simple = m.getSimple(obj)
- if simple then
- m.searchSameFields(status, simple, mode)
- end
- end
- tracy.ZoneEnd()
-
- m.cleanResults(status.results)
-
- if makeCache then
- makeCache(status.results)
- end
-end
+ local root = guide.getRoot(obj)
+ linker.compileLinks(root)
-function m.searchRefOfValue(status, obj)
- local var = obj.parent
- if var.type == 'local'
- or var.type == 'set' then
- return m.searchRefs(status, var, 'ref')
- end
-end
-
-function m.allocInfer(o)
- if type(o.type) == 'table' then
- local infers = {}
- for i = 1, #o.type do
- infers[i] = {
- type = o.type[i],
- value = o.value,
- source = o.source,
- level = o.level
- }
+ local links = linker.getLinkersBySource(obj)
+ if links then
+ for _, link in ipairs(links) do
+ m.pushResult(status, mode, link.source)
end
- return infers
- else
- return {
- [1] = o,
- }
end
end
-function m.mergeTypes(types)
- local hasAny = types['any']
-
- types['any'] = nil
-
- if not next(types) then
- return 'any'
- end
- -- 同时包含 number 与 integer 时,去掉 integer
- if types['number'] and types['integer'] then
- types['integer'] = nil
- end
-
- local results = {}
- for tp in pairs(types) do
- results[#results+1] = tp
- end
- -- 只有显性的 nil 与 any 时,取 any
- if #results == 1 then
- if results[1] == 'nil' and hasAny then
- return 'any'
- else
- return results[1]
- end
- end
-
- tableSort(results, function (a, b)
- local sa = TypeSort[a] or 100
- local sb = TypeSort[b] or 100
- if sa == sb then
- return a < b
- else
- return sa < sb
- end
- end)
+---@class guide.status
+---搜索结果
+---@field results parser.guide.object[]
- return tableConcat(results, '|')
-end
-
-function m.getClassExtends(class)
- if class.type == 'doc.class.name' then
- class = class.parent
- end
- if not class.extends then
- return nil
- end
- local names = {}
- for _, ext in ipairs(class.extends) do
- names[#names+1] = ext[1]
- end
- return names
-end
-
-function m.viewInferType(infers)
- if not infers then
- return 'any'
- end
- local types = {}
- local hasDoc
- local hasDocTable
- for i = 1, #infers do
- local infer = infers[i]
- local src = infer.source
- if src.type == 'doc.class'
- or src.type == 'doc.class.name'
- or src.type == 'doc.type.name'
- or src.type == 'doc.type.array'
- or src.type == 'doc.type.table' then
- if infer.type ~= 'any' then
- hasDoc = true
- end
- if src.type == 'doc.type.array'
- or src.type == 'doc.type.table' then
- hasDocTable = true
- end
- end
- end
- if hasDoc then
- for i = 1, #infers do
- local infer = infers[i]
- local src = infer.source
- if src.type == 'doc.class'
- or src.type == 'doc.class.name'
- or src.type == 'doc.type.name'
- or src.type == 'doc.type.array'
- or src.type == 'doc.type.table'
- or src.type == 'doc.type.enum'
- or src.type == 'doc.resume' then
- local tp = infer.type or 'any'
- if hasDocTable and tp == 'table' then
- goto CONTINUE
- end
- if types[tp] == nil then
- types[tp] = true
- end
- end
- if src.type == 'doc.class'
- or src.type == 'doc.class.name' then
- local extends = m.getClassExtends(src)
- if extends then
- for _, tp in ipairs(extends) do
- types[tp] = false
- end
- end
- end
- ::CONTINUE::
- end
- for k, v in pairs(types) do
- if not v then
- types[k] = nil
- end
- end
- else
- for i = 1, #infers do
- local infer = infers[i]
- if infer.source.typeGeneric then
- goto CONTINUE
- end
- local tp = infer.type or 'any'
- types[tp] = true
- ::CONTINUE::
- end
- end
- return m.mergeTypes(types)
-end
-
-function m.checkTrue(status, source)
- local newStatus = m.status(status)
- m.searchInfer(newStatus, source)
- -- 当前认为的结果
- local current
- for _, infer in ipairs(newStatus.results) do
- -- 新的结果
- local new
- if infer.type == 'nil' then
- new = false
- elseif infer.type == 'boolean' then
- if infer.value == true then
- new = true
- elseif infer.value == false then
- new = false
- end
- end
- if new ~= nil then
- if current == nil then
- current = new
- else
- -- 如果2个结果完全相反,则返回 nil 表示不确定
- if new ~= current then
- return nil
- end
- end
- end
- end
- return current
-end
-
---- 获取特定类型的字面量值
-function m.getInferLiteral(status, source, type)
- local newStatus = m.status(status)
- m.searchInfer(newStatus, source)
- for _, infer in ipairs(newStatus.results) do
- if infer.value ~= nil then
- if type == nil or infer.type == type then
- return infer.value
- end
- end
- end
- return nil
-end
-
---- 是否包含某种类型
-function m.hasType(status, source, type)
- m.searchInfer(status, source)
- for _, infer in ipairs(status.results) do
- if infer.type == type then
- return true
- end
- end
- return false
-end
-
-function m.isSameValue(status, a, b)
- local statusA = m.status(status)
- m.searchInfer(statusA, a)
- local statusB = m.status(status)
- m.searchInfer(statusB, b)
- local infers = {}
- for _, infer in ipairs(statusA.results) do
- local literal = infer.value
- if literal then
- infers[literal] = false
- end
- end
- for _, infer in ipairs(statusB.results) do
- local literal = infer.value
- if literal then
- if infers[literal] == nil then
- return false
- end
- infers[literal] = true
- end
- end
- for k, v in pairs(infers) do
- if v == false then
- return false
- end
- end
- return true
-end
-
-function m.inferCheckLiteralTableWithDocVararg(status, source)
- if #source ~= 1 then
- return
- end
- local vararg = source[1]
- if vararg.type ~= 'varargs' then
- return
- end
- local results = m.getVarargDocType(status, source)
- status.results[#status.results+1] = {
- type = m.viewInferType(results) .. '[]',
- source = source,
- level = 100,
+---创建搜索状态
+---@param parentStatus guide.status
+---@param interface table
+---@param deep integer
+---@return guide.status
+function m.status(parentStatus, interface, deep)
+ local status = {
+ share = parentStatus and parentStatus.share or {
+ count = 0,
+ cacheLock = {},
+ },
+ depth = parentStatus and (parentStatus.depth + 1) or 0,
+ searchDeep= parentStatus and parentStatus.searchDeep or deep or -999,
+ interface = parentStatus and parentStatus.interface or {},
+ deep = parentStatus and parentStatus.deep,
+ clock = parentStatus and parentStatus.clock or osClock(),
+ results = {},
}
- return true
-end
-
-function m.inferCheckLiteral(status, source)
- if source.type == 'string' then
- status.results = m.allocInfer {
- type = 'string',
- value = source[1],
- source = source,
- level = 100,
- }
- return true
- elseif source.type == 'nil' then
- status.results = m.allocInfer {
- type = 'nil',
- value = NIL,
- source = source,
- level = 100,
- }
- return true
- elseif source.type == 'boolean' then
- status.results = m.allocInfer {
- type = 'boolean',
- value = source[1],
- source = source,
- level = 100,
- }
- return true
- elseif source.type == 'number' then
- if mathType(source[1]) == 'integer' then
- status.results = m.allocInfer {
- type = 'integer',
- value = source[1],
- source = source,
- level = 100,
- }
- return true
- else
- status.results = m.allocInfer {
- type = 'number',
- value = source[1],
- source = source,
- level = 100,
- }
- return true
- end
- elseif source.type == 'integer' then
- status.results = m.allocInfer {
- type = 'integer',
- source = source,
- level = 100,
- }
- return true
- elseif source.type == 'table' then
- if m.inferCheckLiteralTableWithDocVararg(status, source) then
- return true
- end
- status.results = m.allocInfer {
- type = 'table',
- source = source,
- level = 100,
- }
- return true
- elseif source.type == 'function' then
- status.results = m.allocInfer {
- type = 'function',
- source = source,
- level = 100,
- }
- return true
- elseif source.type == '...' then
- status.results = m.allocInfer {
- type = '...',
- source = source,
- level = 100,
- }
- return true
- end
-end
-
-local function getDocAliasExtends(status, typeUnit)
- if not status.interface.docType then
- return nil
- end
- if typeUnit.type ~= 'doc.type.name' then
- return nil
- end
- for _, doc in ipairs(status.interface.docType(typeUnit[1])) do
- if doc.type == 'doc.alias.name' then
- return doc.parent.extends
- end
- end
- return nil
-end
-
-function m.getDocTypeUnitName(status, unit)
- local typeName
- if unit.type == 'doc.type.name' then
- typeName = unit[1]
- elseif unit.type == 'doc.type.function' then
- typeName = 'function'
- elseif unit.type == 'doc.type.array' then
- typeName = m.getDocTypeUnitName(status, unit.node) .. '[]'
- elseif unit.type == 'doc.type.table' then
- typeName = ('%s<%s, %s>'):format(
- m.getDocTypeUnitName(status, unit.node),
- m.viewInferType(m.getDocTypeNames(status, unit.key)),
- m.viewInferType(m.getDocTypeNames(status, unit.value))
- )
- end
- if unit.typeGeneric then
- typeName = ('<%s>'):format(typeName)
- end
- return typeName
-end
-
-function m.getDocTypeNames(status, doc)
- local results = {}
- if not doc then
- return results
- end
- for _, unit in ipairs(doc.types) do
- local alias = getDocAliasExtends(status, unit)
- if alias then
- local aliasResults = m.getDocTypeNames(status, alias)
- for _, res in ipairs(aliasResults) do
- results[#results+1] = res
- end
- else
- local typeName = m.getDocTypeUnitName(status, unit)
- results[#results+1] = {
- type = typeName,
- source = unit,
- level = 100,
- }
- end
- end
- for _, enum in ipairs(doc.enums) do
- results[#results+1] = {
- type = enum[1],
- source = enum,
- level = 100,
- }
- end
- for _, resume in ipairs(doc.resumes) do
- if not resume.additional then
- results[#results+1] = {
- type = resume[1],
- source = resume,
- level = 100,
- }
- end
- end
- return results
-end
-
-function m.inferCheckDoc(status, source)
- if source.type == 'doc.class.name' then
- status.results[#status.results+1] = {
- type = source[1],
- source = source,
- level = 100,
- }
- return true
- end
- if source.type == 'doc.class' then
- status.results[#status.results+1] = {
- type = source.class[1],
- source = source,
- level = 100,
- }
- return true
- end
- if source.type == 'doc.type' then
- local results = m.getDocTypeNames(status, source)
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
- end
- if source.type == 'doc.type.function'
- or source.type == 'doc.type.table'
- or source.type == 'doc.type.array' then
- local typeName = m.getDocTypeUnitName(status, source)
- status.results[#status.results+1] = {
- type = typeName,
- source = source,
- level = 100,
- }
- return true
- end
- if source.type == 'doc.field' then
- local results = m.getDocTypeNames(status, source.extends)
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
- end
- if source.type == 'doc.alias.name' then
- local results = m.getDocTypeNames(status, m.getDocState(source).extends)
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
- end
-end
-
-function m.getVarargDocType(status, source)
- local func = m.getParentFunction(source)
- if not func then
- return
- end
- if not func.args then
- return
- end
- for _, arg in ipairs(func.args) do
- if arg.type == '...' then
- if arg.bindDocs then
- for _, doc in ipairs(arg.bindDocs) do
- if doc.type == 'doc.vararg' then
- return m.getDocTypeNames(status, doc.vararg)
- end
- end
- end
- end
- end
-end
-
-function m.inferCheckUpDocOfVararg(status, source)
- if not source.vararg then
- return
- end
- local results = m.getVarargDocType(status, source)
- if not results then
- return
- end
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
-end
-
-function m.inferCheckUpDoc(status, source)
- if m.inferCheckUpDocOfVararg(status, source) then
- return true
- end
- local parent = source.parent
- if parent then
- if parent.type == 'local'
- or parent.type == 'setlocal'
- or parent.type == 'setglobal' then
- source = parent
- end
- if parent.type == 'setfield'
- or parent.type == 'tablefield' then
- if parent.field == source
- or parent.value == source then
- source = parent
- end
- end
- if parent.type == 'setmethod' then
- if parent.method == source
- or parent.value == source then
- source = parent
- end
- end
- if parent.type == 'setindex'
- or parent.type == 'tableindex' then
- if parent.index == source
- or parent.value == source then
- source = parent
- end
- end
- end
- local binds = source.bindDocs
- if not binds then
- return
- end
- status.results = {}
- for i = #binds, 1, -1 do
- local doc = binds[i]
- if doc.type == 'doc.class' then
- status.results[#status.results+1] = {
- type = doc.class[1],
- source = doc,
- level = 100,
- }
- -- ---@class Class
- -- local x = { field = 1 }
- -- 这种情况下,将字面量表接受为Class的定义
- if source.value and source.value.type == 'table' then
- status.results[#status.results+1] = {
- type = source.value.type,
- source = source.value,
- level = 100,
- }
- end
- return true
- elseif doc.type == 'doc.type' then
- local results = m.getDocTypeNames(status, doc)
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
- elseif doc.type == 'doc.param' then
- -- function (x) 的情况
- if source.type == 'local'
- and m.getKeyName(source) == doc.param[1] then
- if source.parent.type == 'funcargs'
- or source.parent.type == 'in'
- or source.parent.type == 'loop' then
- local results = m.getDocTypeNames(status, doc.extends)
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
- end
- end
- end
- end
-end
-
-function m.inferCheckUnary(status, source)
- if source.type ~= 'unary' then
- return
- end
- local op = source.op
- if op.type == 'not' then
- local checkTrue = m.checkTrue(status, source[1])
- local value = nil
- if checkTrue == true then
- value = false
- elseif checkTrue == false then
- value = true
- end
- status.results = m.allocInfer {
- type = 'boolean',
- value = value,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '#' then
- status.results = m.allocInfer {
- type = 'integer',
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '~' then
- local l = m.getInferLiteral(status, source[1], 'integer')
- status.results = m.allocInfer {
- type = 'integer',
- value = l and ~l or nil,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '-' then
- local v = m.getInferLiteral(status, source[1], 'integer')
- if v then
- status.results = m.allocInfer {
- type = 'integer',
- value = - v,
- source = source,
- level = 100,
- }
- return true
- end
- v = m.getInferLiteral(status, source[1], 'number')
- status.results = m.allocInfer {
- type = 'number',
- value = v and -v or nil,
- source = source,
- level = 100,
- }
- return true
- end
-end
-
-local function mathCheck(status, a, b)
- local v1 = m.getInferLiteral(status, a, 'integer')
- 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')
- and not m.hasType(status, b, 'number')
- return int and 'integer' or 'number', v1, v2
-end
-
-function m.inferCheckBinary(status, source)
- if source.type ~= 'binary' then
- return
- end
- local op = source.op
- if op.type == 'and' then
- local isTrue = m.checkTrue(status, source[1])
- if isTrue == true then
- m.searchInfer(status, source[2])
- return true
- elseif isTrue == false then
- m.searchInfer(status, source[1])
- return true
- else
- m.searchInfer(status, source[1])
- m.searchInfer(status, source[2])
- return true
- end
- elseif op.type == 'or' then
- local isTrue = m.checkTrue(status, source[1])
- if isTrue == true then
- m.searchInfer(status, source[1])
- return true
- elseif isTrue == false then
- m.searchInfer(status, source[2])
- return true
- else
- m.searchInfer(status, source[1])
- m.searchInfer(status, source[2])
- return true
- end
- elseif op.type == '==' then
- local value = m.isSameValue(status, source[1], source[2])
- if value ~= nil then
- status.results = m.allocInfer {
- type = 'boolean',
- value = value,
- source = source,
- level = 100,
- }
- return true
- end
- --local isSame = m.isSameDef(status, source[1], source[2])
- --if isSame == true then
- -- value = true
- --else
- -- value = nil
- --end
- status.results = m.allocInfer {
- type = 'boolean',
- value = value,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '~=' then
- local value = m.isSameValue(status, source[1], source[2])
- if value ~= nil then
- status.results = m.allocInfer {
- type = 'boolean',
- value = not value,
- source = source,
- level = 100,
- }
- return true
- end
- --local isSame = m.isSameDef(status, source[1], source[2])
- --if isSame == true then
- -- value = false
- --else
- -- value = nil
- --end
- status.results = m.allocInfer {
- type = 'boolean',
- value = value,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '<=' then
- local v1 = m.getInferLiteral(status, source[1], 'integer')
- 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
- end
- status.results = m.allocInfer {
- type = 'boolean',
- value = v,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '>=' then
- local v1 = m.getInferLiteral(status, source[1], 'integer')
- 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
- end
- status.results = m.allocInfer {
- type = 'boolean',
- value = v,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '<' then
- local v1 = m.getInferLiteral(status, source[1], 'integer')
- 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
- end
- status.results = m.allocInfer {
- type = 'boolean',
- value = v,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '>' then
- local v1 = m.getInferLiteral(status, source[1], 'integer')
- 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
- end
- status.results = m.allocInfer {
- type = 'boolean',
- value = v,
- source = source,
- level = 100,
- }
- return true
- 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
- end
- status.results = m.allocInfer {
- type = 'integer',
- value = v,
- source = source,
- level = 100,
- }
- return true
- 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
- end
- status.results = m.allocInfer {
- type = 'integer',
- value = v,
- source = source,
- level = 100,
- }
- return true
- 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
- end
- status.results = m.allocInfer {
- type = 'integer',
- value = v,
- source = source,
- level = 100,
- }
- return true
- 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
- end
- status.results = m.allocInfer {
- type = 'integer',
- value = v,
- source = source,
- level = 100,
- }
- return true
- 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
- end
- status.results = m.allocInfer {
- type = 'integer',
- value = v,
- source = source,
- level = 100,
- }
- return true
- 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
- end
- status.results = m.allocInfer {
- type = 'string',
- value = v,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '^' then
- local v1 = m.getInferLiteral(status, source[1], 'integer')
- 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
- end
- status.results = m.allocInfer {
- type = 'number',
- value = v,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '/' then
- local v1 = m.getInferLiteral(status, source[1], 'integer')
- 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 and v2 ~= 0 then
- v = v1 / v2
- end
- status.results = m.allocInfer {
- type = 'number',
- value = v,
- source = source,
- level = 100,
- }
- return true
- -- 其他数学运算根据2侧的值决定,当2侧的值均为整数时返回整数
- elseif op.type == '+' then
- 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,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '-' then
- 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,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '*' then
- 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,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '%' then
- local int, v1, v2 = mathCheck(status, source[1], source[2])
- status.results = m.allocInfer {
- type = int,
- value = (v1 and v2 and v2 ~= 0) and (v1 % v2) or nil,
- source = source,
- level = 100,
- }
- return true
- elseif op.type == '//' then
- local int, v1, v2 = mathCheck(status, source[1], source[2])
- status.results = m.allocInfer {
- type = int,
- value = (v1 and v2 and v2 ~= 0) and (v1 // v2) or nil,
- source = source,
- level = 100,
- }
- return true
- end
-end
-
-function m.inferByDef(status, obj)
- if not status.share.inferedDef then
- status.share.inferedDef = {}
- end
- if status.share.inferedDef[obj] then
- return
- end
- 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)
- if #inferStatus.results == 0 then
- status.results[#status.results+1] = {
- type = 'any',
- source = src,
- level = 0,
- }
- else
- for _, infer in ipairs(inferStatus.results) do
- if not mark[infer.source] then
- mark[infer.source] = true
- status.results[#status.results+1] = infer
- end
- end
- end
- end
-end
-
-local function inferBySetOfLocal(status, source)
- if status.share[source] then
- return
- end
- status.share[source] = true
- local newStatus = m.status(status)
- if source.value then
- m.searchInfer(newStatus, source.value)
- end
- if source.ref then
- for _, ref in ipairs(source.ref) do
- if ref.type == 'setlocal' then
- break
- end
- m.searchInfer(newStatus, ref)
- end
- for _, infer in ipairs(newStatus.results) do
- status.results[#status.results+1] = infer
- end
- end
-end
-
-function m.inferByLocalRef(status, source)
- if #status.results ~= 0 then
- return
- end
- if source.type == 'local' then
- inferBySetOfLocal(status, source)
- elseif source.type == 'setlocal'
- or source.type == 'getlocal' then
- inferBySetOfLocal(status, source.node)
- end
-end
-
-function m.inferByCall(status, source)
- if not source.parent then
- return
- end
- if source.parent.type ~= 'call' then
- return
- end
- if source.parent.node == source then
- status.results[#status.results+1] = {
- type = 'function',
- source = source,
- level = 10,
- }
- return
- end
-end
-
-function m.inferByGetTable(status, source)
- if source.type == 'field'
- or source.type == 'method' then
- source = source.parent
- end
- local next = source.next
- if not next then
- return
- end
- if next.type == 'getfield'
- or next.type == 'getindex'
- or next.type == 'setmethod'
- or next.type == 'setfield'
- or next.type == 'setindex' then
- status.results[#status.results+1] = {
- type = 'table',
- source = source,
- level = 10,
- }
- elseif next.type == 'getmethod' then
- status.results[#status.results+1] = {
- type = 'table',
- source = source,
- level = 5,
- }
- status.results[#status.results+1] = {
- type = 'string',
- source = source,
- level = 5,
- }
- end
-end
-
-function m.inferByUnary(status, source)
- local parent = source.parent
- if not parent or parent.type ~= 'unary' then
- return
- end
- local op = parent.op
- if op.type == '#' then
- status.results[#status.results+1] = {
- type = 'string',
- source = source,
- level = 5,
- }
- status.results[#status.results+1] = {
- type = 'table',
- source = source,
- level = 5,
- }
- elseif op.type == '~' then
- status.results[#status.results+1] = {
- type = 'integer',
- source = source,
- level = 10,
- }
- elseif op.type == '-' then
- status.results[#status.results+1] = {
- type = 'number',
- source = source,
- level = 10,
- }
- end
-end
-
-function m.inferByBinary(status, source)
- local parent = source.parent
- if not parent or parent.type ~= 'binary' then
- return
- end
- local op = parent.op
- if op.type == '<='
- or op.type == '>='
- or op.type == '<'
- or op.type == '>'
- or op.type == '^'
- or op.type == '/'
- or op.type == '+'
- or op.type == '-'
- or op.type == '*'
- or op.type == '%' then
- status.results[#status.results+1] = {
- type = 'number',
- source = source,
- level = 10,
- }
- elseif op.type == '|'
- or op.type == '~'
- or op.type == '&'
- or op.type == '<<'
- or op.type == '>>'
- -- 整数的可能性比较高
- or op.type == '//' then
- status.results[#status.results+1] = {
- type = 'integer',
- source = source,
- level = 10,
- }
- elseif op.type == '..' then
- status.results[#status.results+1] = {
- type = 'string',
- source = source,
- level = 10,
- }
- end
-end
-
-local function mergeFunctionReturnsByDoc(status, source, index, call)
- if not source or source.type ~= 'function' then
- return
- end
- if not source.bindDocs then
- return
- end
- local returns = {}
- for _, doc in ipairs(source.bindDocs) do
- if doc.type == 'doc.return' then
- for _, rtn in ipairs(doc.returns) do
- returns[#returns+1] = rtn
- end
- end
- end
- local rtn = returns[index]
- if not rtn then
- return
- end
- local results = m.getDocTypeNames(status, rtn)
- if #results == 0 then
- return
- end
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- return true
-end
-
-local function mergeDocTypeFunctionReturns(status, source, index)
- if not source.bindDocs then
- return
- end
- for _, doc in ipairs(source.bindDocs) do
- if doc.type == 'doc.type' then
- for _, typeUnit in ipairs(doc.types) do
- if typeUnit.type == 'doc.type.function' then
- local rtn = typeUnit.returns[index]
- if rtn then
- local results = m.getDocTypeNames(status, rtn)
- for _, res in ipairs(results) do
- status.results[#status.results+1] = res
- end
- end
- end
- end
- end
- end
-end
-
-local function mergeFunctionReturns(status, source, index, call)
- local returns = source.returns
- if not returns then
- return
- end
- for i = 1, #returns do
- local rtn = returns[i]
- if rtn[index] then
- if rtn[index].type == 'call' then
- if not m.checkReturnMark(status, rtn[index], true) then
- m.inferByCallReturnAndIndex(status, rtn[index], index)
- end
- else
- local newStatus = m.status(status)
- m.searchInfer(newStatus, rtn[index])
- if #newStatus.results == 0 then
- status.results[#status.results+1] = {
- type = 'any',
- source = rtn[index],
- level = 0,
- }
- else
- for _, infer in ipairs(newStatus.results) do
- status.results[#status.results+1] = infer
- end
- end
- end
- end
- end
-end
-
-function m.inferByCallReturnAndIndex(status, call, index)
- local node = call.node
- local newStatus = m.status(status, status.interface)
- m.searchRefs(newStatus, node, 'def')
- local hasDocReturn
- for _, src in ipairs(newStatus.results) do
- if mergeDocTypeFunctionReturns(status, src, index) then
- hasDocReturn = true
- elseif mergeFunctionReturnsByDoc(status, src.value, index, call) then
- hasDocReturn = true
- end
- end
- if not hasDocReturn then
- for _, src in ipairs(newStatus.results) do
- if src.value and src.value.type == 'function' then
- if not m.checkReturnMark(status, src.value, true) then
- mergeFunctionReturns(status, src.value, index, call)
- end
- end
- end
- end
-end
-
-function m.inferByCallReturn(status, source)
- if source.type == 'call' then
- m.inferByCallReturnAndIndex(status, source, 1)
- return
- end
- if source.type ~= 'select' then
- if source.value and source.value.type == 'select' then
- source = source.value
- else
- return
- end
- end
- if not source.vararg or source.vararg.type ~= 'call' then
- return
- end
- m.inferByCallReturnAndIndex(status, source.vararg, source.index)
-end
-
-function m.inferByPCallReturn(status, source)
- if source.type ~= 'select' then
- if source.value and source.value.type == 'select' then
- source = source.value
- else
- return
- end
- end
- local call = source.vararg
- 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
- if specialName == 'pcall' then
- func = call.args[1]
- index = source.index - 1
- elseif specialName == 'xpcall' then
- func = call.args[1]
- index = source.index - 2
- else
- return
- end
- local newStatus = m.status(status, status.interface)
- m.searchRefs(newStatus, func, 'def')
- for _, src in ipairs(newStatus.results) do
- if src.value and src.value.type == 'function' then
- mergeFunctionReturns(status, src.value, index)
- end
- end
-end
-
-function m.cleanInfers(infers, obj)
- -- kick lower level infers
- local level = 0
- if obj.type ~= 'select' then
- for i = 1, #infers do
- local infer = infers[i]
- if infer.level > level then
- level = infer.level
- end
- end
- end
- -- merge infers
- local mark = {}
- for i = #infers, 1, -1 do
- local infer = infers[i]
- if infer.level < level then
- infers[i] = infers[#infers]
- infers[#infers] = nil
- goto CONTINUE
- end
- local key = ('%p'):format(infer.type, infer.source)
- if mark[key] then
- infers[i] = infers[#infers]
- infers[#infers] = nil
- else
- mark[key] = true
- end
- ::CONTINUE::
- end
- -- kick doc.generic
- if #infers > 1 then
- for i = #infers, 1, -1 do
- local infer = infers[i]
- if infer.source.typeGeneric then
- infers[i] = infers[#infers]
- infers[#infers] = nil
- end
- end
- end
-end
-
-function m.searchInfer(status, obj)
- while obj.type == 'paren' do
- obj = obj.exp
- if not obj then
- return
- end
- end
- while true do
- local value = m.getObjectValue(obj)
- if not value or value == obj then
- break
- end
- obj = value
- end
- if not obj then
- return
- end
-
- local cache, makeCache = m.getRefCache(status, obj, 'infer')
- if cache then
- for i = 1, #cache do
- status.results[#status.results+1] = cache[i]
- end
- return
- end
-
- if DEVELOP then
- status.share.clock = status.share.clock or osClock()
- end
-
- if not status.share.lockInfer then
- status.share.lockInfer = {}
- end
- if status.share.lockInfer[obj] then
- return
- end
- status.share.lockInfer[obj] = true
-
- local checked = m.inferCheckDoc(status, obj)
- or m.inferCheckUpDoc(status, obj)
- or m.inferCheckLiteral(status, obj)
- or m.inferCheckUnary(status, obj)
- or m.inferCheckBinary(status, obj)
- if checked then
- m.cleanInfers(status.results, obj)
- if makeCache then
- makeCache(status.results)
+ if interface then
+ for k, v in pairs(interface) do
+ status.interface[k] = v
end
- return
- end
-
- m.inferByLocalRef(status, obj)
- if status.deep then
- tracy.ZoneBeginN('inferByDef')
- m.inferByDef(status, obj)
- tracy.ZoneEnd()
- end
- --m.inferByCall(status, obj)
- m.inferByGetTable(status, obj)
- m.inferByUnary(status, obj)
- m.inferByBinary(status, obj)
- m.cleanInfers(status.results, obj)
- if makeCache then
- makeCache(status.results)
end
+ status.deep = status.depth <= status.searchDeep
+ return status
end
---- 请求对象的引用,包括 `a.b.c` 形式
---- 与 `return function` 形式。
---- 不穿透 `setmetatable` ,考虑由
---- 业务层进行反向 def 搜索。
+--- 请求对象的引用
+---@param obj parser.guide.object
+---@param interface table
+---@param deep integer
+---@return parser.guide.object[]
+---@return integer
function m.requestReference(obj, interface, deep)
local status = m.status(nil, interface, deep)
-- 根据 field 搜索引用
m.searchRefs(status, obj, 'ref')
- m.searchRefsAsFunction(status, obj, 'ref')
-
- if m.debugMode then
- --print('count:', status.share.count)
- end
-
- return status.results, status.share.count
-end
-
---- 请求对象的定义,包括 `a.b.c` 形式
---- 与 `return function` 形式。
---- 穿透 `setmetatable` 。
-function m.requestDefinition(obj, interface, deep)
- local status = m.status(nil, interface, deep)
- -- 根据 field 搜索定义
- m.searchRefs(status, obj, 'def')
-
- return status.results, status.share.count
-end
-
---- 请求对象的字段
----@param filterKey nil|string|table
-function m.requestFields(obj, interface, deep, filterKey)
- local status = m.status(nil, interface, deep)
-
- m.searchFields(status, obj, 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)
- m.searchInfer(status, obj)
-
- return status.results, status.share.count
-end
-
-function m.debugView(obj)
- return require 'files'.position(m.getUri(obj), obj.start), m.getUri(obj)
+ return status.results, 0
end
return m
diff --git a/script/core/guide2.lua b/script/core/guide2.lua
new file mode 100644
index 00000000..64192297
--- /dev/null
+++ b/script/core/guide2.lua
@@ -0,0 +1,4837 @@
+local util = require 'utility'
+local error = error
+local type = type
+local next = next
+local tostring = tostring
+local print = print
+local ipairs = ipairs
+local tableInsert = table.insert
+local tableUnpack = table.unpack
+local tableRemove = table.remove
+local tableMove = table.move
+local tableSort = table.sort
+local tableConcat = table.concat
+local mathType = math.type
+local pairs = pairs
+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
+
+local function logWarn(...)
+ log.warn(...)
+end
+
+local m = {}
+
+m.ANY = {"<ANY>"}
+
+local blockTypes = {
+ ['while'] = true,
+ ['in'] = true,
+ ['loop'] = true,
+ ['repeat'] = true,
+ ['do'] = true,
+ ['function'] = true,
+ ['ifblock'] = true,
+ ['elseblock'] = true,
+ ['elseifblock'] = true,
+ ['main'] = true,
+}
+
+local breakBlockTypes = {
+ ['while'] = true,
+ ['in'] = true,
+ ['loop'] = true,
+ ['repeat'] = true,
+}
+
+m.childMap = {
+ ['main'] = {'#', 'docs'},
+ ['repeat'] = {'#', 'filter'},
+ ['while'] = {'filter', '#'},
+ ['in'] = {'keys', '#'},
+ ['loop'] = {'loc', 'max', 'step', '#'},
+ ['if'] = {'#'},
+ ['ifblock'] = {'filter', '#'},
+ ['elseifblock'] = {'filter', '#'},
+ ['elseblock'] = {'#'},
+ ['setfield'] = {'node', 'field', 'value'},
+ ['setglobal'] = {'value'},
+ ['local'] = {'attrs', 'value'},
+ ['setlocal'] = {'value'},
+ ['return'] = {'#'},
+ ['do'] = {'#'},
+ ['select'] = {'vararg'},
+ ['table'] = {'#'},
+ ['tableindex'] = {'index', 'value'},
+ ['tablefield'] = {'field', 'value'},
+ ['function'] = {'args', '#'},
+ ['funcargs'] = {'#'},
+ ['setmethod'] = {'node', 'method', 'value'},
+ ['getmethod'] = {'node', 'method'},
+ ['setindex'] = {'node', 'index', 'value'},
+ ['getindex'] = {'node', 'index'},
+ ['paren'] = {'exp'},
+ ['call'] = {'node', 'args'},
+ ['callargs'] = {'#'},
+ ['getfield'] = {'node', 'field'},
+ ['list'] = {'#'},
+ ['binary'] = {1, 2},
+ ['unary'] = {1},
+
+ ['doc'] = {'#'},
+ ['doc.class'] = {'class', '#extends', 'comment'},
+ ['doc.type'] = {'#types', '#enums', 'name', 'comment'},
+ ['doc.alias'] = {'alias', 'extends', 'comment'},
+ ['doc.param'] = {'param', 'extends', 'comment'},
+ ['doc.return'] = {'#returns', 'comment'},
+ ['doc.field'] = {'field', 'extends', 'comment'},
+ ['doc.generic'] = {'#generics', 'comment'},
+ ['doc.generic.object'] = {'generic', 'extends', 'comment'},
+ ['doc.vararg'] = {'vararg', 'comment'},
+ ['doc.type.array'] = {'node'},
+ ['doc.type.table'] = {'node', 'key', 'value', 'comment'},
+ ['doc.type.function'] = {'#args', '#returns', 'comment'},
+ ['doc.type.typeliteral'] = {'node'},
+ ['doc.type.arg'] = {'extends'},
+ ['doc.overload'] = {'overload', 'comment'},
+ ['doc.see'] = {'name', 'field', 'comment'},
+ ['doc.diagnostic'] = {'#names', 'comment'},
+}
+
+m.actionMap = {
+ ['main'] = {'#'},
+ ['repeat'] = {'#'},
+ ['while'] = {'#'},
+ ['in'] = {'#'},
+ ['loop'] = {'#'},
+ ['if'] = {'#'},
+ ['ifblock'] = {'#'},
+ ['elseifblock'] = {'#'},
+ ['elseblock'] = {'#'},
+ ['do'] = {'#'},
+ ['function'] = {'#'},
+ ['funcargs'] = {'#'},
+}
+
+local TypeSort = {
+ ['boolean'] = 1,
+ ['string'] = 2,
+ ['integer'] = 3,
+ ['number'] = 4,
+ ['table'] = 5,
+ ['function'] = 6,
+ ['true'] = 101,
+ ['false'] = 102,
+ ['nil'] = 999,
+}
+
+local NIL = setmetatable({'<nil>'}, { __tostring = function () return 'nil' end })
+
+--- 是否是字面量
+---@param obj parser.guide.object
+---@return boolean
+function m.isLiteral(obj)
+ local tp = obj.type
+ return tp == 'nil'
+ or tp == 'boolean'
+ or tp == 'string'
+ or tp == 'number'
+ or tp == 'table'
+ or tp == 'function'
+end
+
+--- 获取字面量
+---@param obj parser.guide.object
+---@return any
+function m.getLiteral(obj)
+ local tp = obj.type
+ if tp == 'boolean' then
+ return obj[1]
+ elseif tp == 'string' then
+ return obj[1]
+ elseif tp == 'number' then
+ return obj[1]
+ end
+ return nil
+end
+
+--- 寻找父函数
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getParentFunction(obj)
+ for _ = 1, 1000 do
+ obj = obj.parent
+ if not obj then
+ break
+ end
+ local tp = obj.type
+ if tp == 'function' or tp == 'main' then
+ return obj
+ end
+ end
+ return nil
+end
+
+--- 寻找父的table类型 doc.type.table
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getParentDocTypeTable(obj)
+ for _ = 1, 1000 do
+ local parent = obj.parent
+ if not parent then
+ return nil
+ end
+ if parent.type == 'doc.type.table' then
+ return obj
+ end
+ obj = parent
+ end
+ error('guide.getParentDocTypeTable overstack')
+end
+
+--- 寻找所在区块
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getBlock(obj)
+ for _ = 1, 1000 do
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ 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')
+end
+
+--- 寻找所在父区块
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getParentBlock(obj)
+ for _ = 1, 1000 do
+ obj = obj.parent
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ if blockTypes[tp] then
+ return obj
+ end
+ end
+ error('guide.getParentBlock overstack')
+end
+
+--- 寻找所在可break的父区块
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getBreakBlock(obj)
+ for _ = 1, 1000 do
+ obj = obj.parent
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ if breakBlockTypes[tp] then
+ return obj
+ end
+ if tp == 'function' then
+ return nil
+ end
+ end
+ error('guide.getBreakBlock overstack')
+end
+
+--- 寻找doc的主体
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getDocState(obj)
+ for _ = 1, 1000 do
+ local parent = obj.parent
+ if not parent then
+ return obj
+ end
+ if parent.type == 'doc' then
+ return obj
+ end
+ obj = parent
+ end
+ error('guide.getDocState overstack')
+end
+
+--- 寻找所在父类型
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getParentType(obj, want)
+ for _ = 1, 1000 do
+ obj = obj.parent
+ if not obj then
+ return nil
+ end
+ if want == obj.type then
+ return obj
+ end
+ end
+ error('guide.getParentType overstack')
+end
+
+--- 寻找根区块
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getRoot(obj)
+ local source = obj
+ if source._root then
+ return source._root
+ end
+ for _ = 1, 1000 do
+ if obj.type == 'main' then
+ source._root = obj
+ return obj
+ end
+ local parent = obj.parent
+ if not parent then
+ return nil
+ end
+ obj = parent
+ end
+ error('guide.getRoot overstack')
+end
+
+---@param obj parser.guide.object
+---@return string
+function m.getUri(obj)
+ if obj.uri then
+ return obj.uri
+ end
+ local root = m.getRoot(obj)
+ if root then
+ return root.uri
+ end
+ return ''
+end
+
+function m.getENV(source, start)
+ if not start then
+ start = 1
+ end
+ return m.getLocal(source, '_ENV', start)
+ or m.getLocal(source, '@fenv', start)
+end
+
+--- 寻找函数的不定参数,返回不定参在第几个参数上,以及该参数对象。
+--- 如果函数是主函数,则返回`0, nil`。
+---@return table
+---@return integer
+function m.getFunctionVarArgs(func)
+ if func.type == 'main' then
+ return 0, nil
+ end
+ if func.type ~= 'function' then
+ return nil, nil
+ end
+ local args = func.args
+ if not args then
+ return nil, nil
+ end
+ for i = 1, #args do
+ local arg = args[i]
+ if arg.type == '...' then
+ return i, arg
+ end
+ end
+ return nil, nil
+end
+
+--- 获取指定区块中可见的局部变量
+---@param block table
+---@param name string {comment = '变量名'}
+---@param pos integer {comment = '可见位置'}
+function m.getLocal(block, name, pos)
+ block = m.getBlock(block)
+ for _ = 1, 1000 do
+ if not block then
+ return nil
+ end
+ local locals = block.locals
+ local res
+ if not locals then
+ goto CONTINUE
+ end
+ for i = 1, #locals do
+ local loc = locals[i]
+ if loc.effect > pos then
+ break
+ end
+ if loc[1] == name then
+ if not res or res.effect < loc.effect then
+ res = loc
+ end
+ end
+ end
+ if res then
+ return res, res
+ end
+ ::CONTINUE::
+ block = m.getParentBlock(block)
+ end
+ error('guide.getLocal overstack')
+end
+
+--- 获取指定区块中所有的可见局部变量名称
+function m.getVisibleLocals(block, pos)
+ local result = {}
+ m.eachSourceContain(m.getRoot(block), pos, function (source)
+ local locals = source.locals
+ if locals then
+ for i = 1, #locals do
+ local loc = locals[i]
+ local name = loc[1]
+ if loc.effect <= pos then
+ result[name] = loc
+ end
+ end
+ end
+ end)
+ return result
+end
+
+--- 获取指定区块中可见的标签
+---@param block table
+---@param name string {comment = '标签名'}
+function m.getLabel(block, name)
+ block = m.getBlock(block)
+ for _ = 1, 1000 do
+ if not block then
+ return nil
+ end
+ local labels = block.labels
+ if labels then
+ local label = labels[name]
+ if label then
+ return label
+ end
+ end
+ if block.type == 'function' then
+ return nil
+ end
+ block = m.getParentBlock(block)
+ end
+ error('guide.getLocal overstack')
+end
+
+function m.getStartFinish(source)
+ local start = source.start
+ local finish = source.finish
+ if not start then
+ local first = source[1]
+ if not first then
+ return nil, nil
+ end
+ local last = source[#source]
+ start = first.start
+ finish = last.finish
+ end
+ return start, finish
+end
+
+function m.getRange(source)
+ local start = source.vstart or source.start
+ local finish = source.range or source.finish
+ if not start then
+ local first = source[1]
+ if not first then
+ return nil, nil
+ end
+ local last = source[#source]
+ start = first.vstart or first.start
+ finish = last.range or last.finish
+ end
+ return start, finish
+end
+
+--- 判断source是否包含offset
+function m.isContain(source, offset)
+ local start, finish = m.getStartFinish(source)
+ if not start then
+ return false
+ end
+ return start <= offset and finish >= offset
+end
+
+--- 判断offset在source的影响范围内
+---
+--- 主要针对赋值等语句时,key包含value
+function m.isInRange(source, offset)
+ local start, finish = m.getRange(source)
+ if not start then
+ return false
+ end
+ return start <= offset and finish >= offset
+end
+
+function m.isBetween(source, tStart, tFinish)
+ local start, finish = m.getStartFinish(source)
+ if not start then
+ return false
+ end
+ return start <= tFinish and finish >= tStart
+end
+
+function m.isBetweenRange(source, tStart, tFinish)
+ local start, finish = m.getRange(source)
+ if not start then
+ return false
+ end
+ return start <= tFinish and finish >= tStart
+end
+
+--- 添加child
+function m.addChilds(list, obj, map)
+ local keys = map[obj.type]
+ if keys then
+ for i = 1, #keys do
+ local key = keys[i]
+ if key == '#' then
+ for i = 1, #obj do
+ list[#list+1] = obj[i]
+ end
+ elseif obj[key] then
+ list[#list+1] = obj[key]
+ elseif type(key) == 'string'
+ and key:sub(1, 1) == '#' then
+ key = key:sub(2)
+ if obj[key] then
+ for i = 1, #obj[key] do
+ list[#list+1] = obj[key][i]
+ end
+ end
+ end
+ end
+ end
+end
+
+--- 遍历所有包含offset的source
+function m.eachSourceContain(ast, offset, callback)
+ local list = { ast }
+ local mark = {}
+ while true do
+ local len = #list
+ if len == 0 then
+ return
+ end
+ local obj = list[len]
+ list[len] = nil
+ if not mark[obj] then
+ mark[obj] = true
+ if m.isInRange(obj, offset) then
+ if m.isContain(obj, offset) then
+ local res = callback(obj)
+ if res ~= nil then
+ return res
+ end
+ end
+ m.addChilds(list, obj, m.childMap)
+ end
+ end
+ end
+end
+
+--- 遍历所有在某个范围内的source
+function m.eachSourceBetween(ast, start, finish, callback)
+ local list = { ast }
+ local mark = {}
+ while true do
+ local len = #list
+ if len == 0 then
+ return
+ end
+ local obj = list[len]
+ list[len] = nil
+ if not mark[obj] then
+ mark[obj] = true
+ if m.isBetweenRange(obj, start, finish) then
+ if m.isBetween(obj, start, finish) then
+ local res = callback(obj)
+ if res ~= nil then
+ return res
+ end
+ end
+ m.addChilds(list, obj, m.childMap)
+ end
+ end
+ end
+end
+
+--- 遍历所有指定类型的source
+function m.eachSourceType(ast, type, callback)
+ local cache = ast._typeCache
+ if not cache then
+ cache = {}
+ ast._typeCache = cache
+ m.eachSource(ast, function (source)
+ local tp = source.type
+ if not tp then
+ return
+ end
+ local myCache = cache[tp]
+ if not myCache then
+ myCache = {}
+ cache[tp] = myCache
+ end
+ myCache[#myCache+1] = source
+ end)
+ end
+ local myCache = cache[type]
+ if not myCache then
+ return
+ end
+ for i = 1, #myCache do
+ callback(myCache[i])
+ end
+end
+
+--- 遍历所有的source
+function m.eachSource(ast, callback)
+ local list = { ast }
+ local mark = {}
+ local index = 1
+ while true do
+ local obj = list[index]
+ if not obj then
+ return
+ end
+ list[index] = false
+ index = index + 1
+ if not mark[obj] then
+ mark[obj] = true
+ callback(obj)
+ m.addChilds(list, obj, m.childMap)
+ end
+ end
+end
+
+--- 获取指定的 special
+function m.eachSpecialOf(ast, name, callback)
+ local root = m.getRoot(ast)
+ if not root.specials then
+ return
+ end
+ local specials = root.specials[name]
+ if not specials then
+ return
+ end
+ for i = 1, #specials do
+ callback(specials[i])
+ end
+end
+
+--- 获取偏移对应的坐标
+---@param lines table
+---@return integer row
+---@return integer col
+function m.positionOf(lines, offset)
+ if offset < 1 then
+ return 0, 0
+ end
+ local lastLine = lines[#lines]
+ if offset > lastLine.finish then
+ return #lines, offset - lastLine.start
+ end
+ local min = 1
+ local max = #lines
+ for _ = 1, 100 do
+ if max <= min then
+ local line = lines[min]
+ return min, offset - line.start + 1
+ end
+ local row = (max - min) // 2 + min
+ local line = lines[row]
+ if offset < line.start then
+ max = row - 1
+ elseif offset > line.finish then
+ min = row + 1
+ else
+ return row, offset - line.start + 1
+ end
+ end
+ error('Stack overflow!')
+end
+
+--- 获取坐标对应的偏移
+---@param lines table
+---@param row integer
+---@param col integer
+---@return integer {name = 'offset'}
+function m.offsetOf(lines, row, col)
+ if row < 1 then
+ return 0
+ end
+ if row > #lines then
+ local lastLine = lines[#lines]
+ return lastLine.finish
+ end
+ local line = lines[row]
+ local len = line.finish - line.start + 1
+ if col < 0 then
+ return line.start
+ elseif col > len then
+ return line.finish
+ else
+ return line.start + col - 1
+ end
+end
+
+function m.lineContent(lines, text, row, ignoreNL)
+ local line = lines[row]
+ if not line then
+ return ''
+ end
+ if ignoreNL then
+ return text:sub(line.start, line.range)
+ else
+ return text:sub(line.start, line.finish)
+ end
+end
+
+function m.lineRange(lines, row, ignoreNL)
+ local line = lines[row]
+ if not line then
+ return 0, 0
+ end
+ if ignoreNL then
+ return line.start, line.range
+ else
+ return line.start, line.finish
+ end
+end
+
+function m.lineData(lines, row)
+ return lines[row]
+end
+
+function m.getKeyTypeOfLiteral(obj)
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ if tp == 'field'
+ or tp == 'method' then
+ return 'string'
+ elseif tp == 'string' then
+ return 'string'
+ elseif tp == 'number' then
+ return 'number'
+ elseif tp == 'boolean' then
+ return 'boolean'
+ end
+end
+
+function m.getKeyType(obj)
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ if tp == 'getglobal'
+ or tp == 'setglobal' then
+ return 'string'
+ elseif tp == 'local'
+ or tp == 'getlocal'
+ or tp == 'setlocal' then
+ return 'local'
+ elseif tp == 'getfield'
+ or tp == 'setfield'
+ or tp == 'tablefield' then
+ return 'string'
+ elseif tp == 'getmethod'
+ or tp == 'setmethod' then
+ return 'string'
+ elseif tp == 'getindex'
+ or tp == 'setindex'
+ or tp == 'tableindex' then
+ return m.getKeyTypeOfLiteral(obj.index)
+ elseif tp == 'field'
+ or tp == 'method'
+ or tp == 'doc.see.field' then
+ return 'string'
+ elseif tp == 'doc.class' then
+ return 'string'
+ elseif tp == 'doc.alias' then
+ return 'string'
+ elseif tp == 'doc.field' then
+ return 'string'
+ elseif tp == 'dummy' then
+ return 'string'
+ end
+ return m.getKeyTypeOfLiteral(obj)
+end
+
+function m.getKeyNameOfLiteral(obj)
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ if tp == 'field'
+ or tp == 'method' then
+ return obj[1]
+ elseif tp == 'string' then
+ local s = obj[1]
+ if s then
+ return s
+ end
+ elseif tp == 'number' then
+ local n = obj[1]
+ if n then
+ return ('%s'):format(util.viewLiteral(obj[1]))
+ end
+ elseif tp == 'boolean' then
+ local b = obj[1]
+ if b then
+ return tostring(b)
+ end
+ end
+end
+
+function m.getKeyName(obj)
+ if not obj then
+ return nil
+ end
+ local tp = obj.type
+ if tp == 'getglobal'
+ or tp == 'setglobal' then
+ return obj[1]
+ elseif tp == 'local'
+ or tp == 'getlocal'
+ or tp == 'setlocal' then
+ return obj[1]
+ elseif tp == 'getfield'
+ or tp == 'setfield'
+ or tp == 'tablefield' then
+ if obj.field then
+ return obj.field[1]
+ end
+ elseif tp == 'getmethod'
+ or tp == 'setmethod' then
+ if obj.method then
+ return obj.method[1]
+ end
+ elseif tp == 'getindex'
+ or tp == 'setindex'
+ or tp == 'tableindex' then
+ return m.getKeyNameOfLiteral(obj.index)
+ elseif tp == 'field'
+ or tp == 'method'
+ or tp == 'doc.see.field' then
+ return obj[1]
+ elseif tp == 'doc.class' then
+ return obj.class[1]
+ elseif tp == 'doc.alias' then
+ 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 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
+ return ('%p'):format(obj)
+ elseif obj.type == 'string' then
+ return ('%p'):format(obj)
+ elseif obj.type == 'doc.class.name'
+ or obj.type == 'doc.type.name'
+ or obj.type == 'doc.see.name' then
+ return ('%s'):format(obj[1])
+ elseif obj.type == 'doc.class' then
+ return ('%s'):format(obj.class[1])
+ end
+ return m.getKeyName(obj)
+end
+
+--- 测试 a 到 b 的路径(不经过函数,不考虑 goto),
+--- 每个路径是一个 block 。
+---
+--- 如果 a 在 b 的前面,返回 `"before"` 加上 2个`list<block>`
+---
+--- 如果 a 在 b 的后面,返回 `"after"` 加上 2个`list<block>`
+---
+--- 否则返回 `false`
+---
+--- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。
+---@param a table
+---@param b table
+---@return string|boolean mode
+---@return table pathA?
+---@return table pathB?
+function m.getPath(a, b, sameFunction)
+ --- 首先测试双方在同一个函数内
+ if sameFunction and m.getParentFunction(a) ~= m.getParentFunction(b) then
+ return false
+ end
+ local mode
+ local objA
+ local objB
+ if a.finish < b.start then
+ mode = 'before'
+ objA = a
+ objB = b
+ elseif a.start > b.finish then
+ mode = 'after'
+ objA = b
+ objB = a
+ else
+ return 'equal', {}, {}
+ end
+ local pathA = {}
+ local pathB = {}
+ for _ = 1, 1000 do
+ objA = m.getParentBlock(objA)
+ pathA[#pathA+1] = objA
+ if (not sameFunction and objA.type == 'function') or objA.type == 'main' then
+ break
+ end
+ end
+ for _ = 1, 1000 do
+ objB = m.getParentBlock(objB)
+ pathB[#pathB+1] = objB
+ if (not sameFunction and objA.type == 'function') or objB.type == 'main' then
+ break
+ end
+ end
+ -- pathA: {1, 2, 3, 4, 5}
+ -- pathB: {5, 6, 2, 3}
+ local top = #pathB
+ local start
+ for i = #pathA, 1, -1 do
+ local currentBlock = pathA[i]
+ if currentBlock == pathB[top] then
+ start = i
+ break
+ end
+ end
+ if not start then
+ return nil
+ end
+ -- pathA: { 1, 2, 3}
+ -- pathB: {5, 6, 2, 3}
+ local extra = 0
+ local align = top - start
+ for i = start, 1, -1 do
+ local currentA = pathA[i]
+ local currentB = pathB[i+align]
+ if currentA ~= currentB then
+ extra = i
+ break
+ end
+ end
+ -- pathA: {1}
+ local resultA = {}
+ for i = extra, 1, -1 do
+ resultA[#resultA+1] = pathA[i]
+ end
+ -- pathB: {5, 6}
+ local resultB = {}
+ for i = extra + align, 1, -1 do
+ resultB[#resultB+1] = pathB[i]
+ end
+ return mode, resultA, resultB
+end
+
+-- 根据语法,单步搜索定义
+local function stepRefOfLocal(loc, mode)
+ local results = {}
+ if loc.start ~= 0 then
+ results[#results+1] = loc
+ end
+ local refs = loc.ref
+ if not refs then
+ return results
+ end
+ for i = 1, #refs do
+ local ref = refs[i]
+ if ref.start == 0 then
+ goto CONTINUE
+ end
+ if mode == 'def' then
+ if ref.type == 'local'
+ or ref.type == 'setlocal' then
+ results[#results+1] = ref
+ end
+ else
+ if ref.type == 'local'
+ or ref.type == 'setlocal'
+ or ref.type == 'getlocal' then
+ results[#results+1] = ref
+ end
+ end
+ ::CONTINUE::
+ end
+ return results
+end
+
+local function stepRefOfLabel(label, mode)
+ local results = { label }
+ if not label or mode == 'def' then
+ return results
+ end
+ local refs = label.ref
+ if not refs then
+ return results
+ end
+ for i = 1, #refs do
+ local ref = refs[i]
+ results[#results+1] = ref
+ end
+ return results
+end
+
+local function stepRefOfDocType(status, obj, mode)
+ local results = {}
+ if obj.type == 'doc.class.name'
+ or obj.type == 'doc.type.name'
+ or obj.type == 'doc.alias.name'
+ or obj.type == 'doc.extends.name'
+ or obj.type == 'doc.see.name' then
+ local name = obj[1]
+ if not name or not status.interface.docType then
+ return results
+ end
+ if name == 'nil'
+ or name == 'any'
+ or name == 'boolean'
+ or name == 'string'
+ or name == 'table'
+ or name == 'number'
+ or name == 'integer'
+ or name == 'function'
+ or name == 'table'
+ or name == 'thread'
+ or name == 'userdata'
+ or name == 'lightuserdata' then
+ mode = 'def'
+ end
+ local docs = status.interface.docType(name)
+ for i = 1, #docs do
+ local doc = docs[i]
+ if mode == 'def' then
+ if doc.type == 'doc.class.name'
+ or doc.type == 'doc.alias.name' then
+ results[#results+1] = doc
+ end
+ else
+ results[#results+1] = doc
+ end
+ end
+ else
+ results[#results+1] = obj
+ end
+ return results
+end
+
+function m.getStepRef(status, obj, mode)
+ if obj.type == 'getlocal'
+ or obj.type == 'setlocal' then
+ return stepRefOfLocal(obj.node, mode)
+ end
+ if obj.type == 'local' then
+ return stepRefOfLocal(obj, mode)
+ end
+ if obj.type == 'label' then
+ return stepRefOfLabel(obj, mode)
+ end
+ if obj.type == 'goto' then
+ return stepRefOfLabel(obj.node, mode)
+ end
+ if obj.type == 'doc.class.name'
+ or obj.type == 'doc.type.name'
+ or obj.type == 'doc.extends.name'
+ or obj.type == 'doc.alias.name' then
+ return stepRefOfDocType(status, obj, mode)
+ end
+ if obj.type == 'function' then
+ return { obj }
+ end
+ return nil
+end
+
+-- 根据语法,单步搜索field
+local function stepFieldOfLocal(loc)
+ local results = {}
+ local refs = loc.ref
+ for i = 1, #refs do
+ local ref = refs[i]
+ if ref.type == 'setglobal'
+ or ref.type == 'getglobal' then
+ results[#results+1] = ref
+ elseif ref.type == 'getlocal' then
+ local nxt = ref.next
+ if nxt then
+ if nxt.type == 'setfield'
+ or nxt.type == 'getfield'
+ or nxt.type == 'setmethod'
+ or nxt.type == 'getmethod'
+ or nxt.type == 'setindex'
+ or nxt.type == 'getindex' then
+ results[#results+1] = nxt
+ end
+ end
+ end
+ end
+ return results
+end
+local function stepFieldOfTable(tbl)
+ local result = {}
+ for i = 1, #tbl do
+ result[i] = tbl[i]
+ end
+ return result
+end
+function m.getStepField(obj)
+ if obj.type == 'getlocal'
+ or obj.type == 'setlocal' then
+ return stepFieldOfLocal(obj.node)
+ end
+ if obj.type == 'local' then
+ return stepFieldOfLocal(obj)
+ end
+ if obj.type == 'table' then
+ return stepFieldOfTable(obj)
+ end
+end
+
+local function convertSimpleList(list)
+ local simple = {}
+ for i = #list, 1, -1 do
+ local c = list[i]
+ if c.type == 'getglobal'
+ or c.type == 'setglobal' then
+ if c.special == '_G' then
+ simple.mode = 'global'
+ goto CONTINUE
+ end
+ local loc = c.node
+ if loc.special == '_G' then
+ simple.mode = 'global'
+ if not simple.node then
+ simple.node = c
+ end
+ else
+ simple.mode = 'local'
+ simple[#simple+1] = m.getSimpleName(loc)
+ if not simple.node then
+ simple.node = loc
+ end
+ end
+ elseif c.type == 'getlocal'
+ or c.type == 'setlocal' then
+ if c.special == '_G' then
+ simple.mode = 'global'
+ goto CONTINUE
+ end
+ simple.mode = 'local'
+ if not simple.node then
+ simple.node = c.node
+ end
+ elseif c.type == 'local' then
+ simple.mode = 'local'
+ if not simple.node then
+ simple.node = c
+ end
+ else
+ if not simple.node then
+ simple.node = c
+ end
+ end
+ simple[#simple+1] = m.getSimpleName(c) or m.ANY
+ ::CONTINUE::
+ end
+ if simple.mode == 'global' and #simple == 0 then
+ simple[1] = '_G'
+ simple.node = list[#list]
+ end
+ return simple
+end
+
+-- 搜索 `a.b.c` 的等价表达式
+local function buildSimpleList(obj, max)
+ local list = {}
+ local cur = obj
+ local limit = max and (max + 1) or 11
+ for i = 1, max or limit do
+ if i == limit then
+ return nil
+ end
+ while cur.type == 'paren' do
+ cur = cur.exp
+ if not cur then
+ return nil
+ end
+ end
+ if cur.type == 'setfield'
+ or cur.type == 'getfield'
+ or cur.type == 'setmethod'
+ or cur.type == 'getmethod'
+ or cur.type == 'setindex'
+ or cur.type == 'getindex' then
+ list[i] = cur
+ cur = cur.node
+ elseif cur.type == 'tablefield'
+ or cur.type == 'tableindex' then
+ list[i] = cur
+ cur = cur.parent.parent
+ if cur.type == 'return' then
+ list[i+1] = list[i].parent
+ break
+ end
+ elseif cur.type == 'getlocal'
+ or cur.type == 'setlocal'
+ or cur.type == 'local' then
+ list[i] = cur
+ break
+ elseif cur.type == 'setglobal'
+ or cur.type == 'getglobal' then
+ list[i] = cur
+ break
+ elseif cur.type == 'select'
+ or cur.type == 'table'
+ or cur.type == 'call' then
+ list[i] = cur
+ break
+ elseif cur.type == 'string' then
+ list[i] = cur
+ break
+ elseif cur.type == '...' then
+ list[i] = cur
+ break
+ elseif cur.type == 'doc.class.name'
+ or cur.type == 'doc.type.name'
+ or cur.type == 'doc.class'
+ or cur.type == 'doc.see.name' then
+ list[i] = cur
+ break
+ elseif cur.type == 'doc.see.field' then
+ list[i] = cur
+ cur = cur.parent.name
+ elseif cur.type == 'function'
+ or cur.type == 'main' then
+ break
+ else
+ return nil
+ end
+ end
+ return convertSimpleList(list)
+end
+
+function m.getSimple(obj, max)
+ local simpleList
+ if obj.type == 'getfield'
+ or obj.type == 'setfield'
+ or obj.type == 'getmethod'
+ or obj.type == 'setmethod'
+ or obj.type == 'getindex'
+ or obj.type == 'setindex'
+ or obj.type == 'local'
+ or obj.type == 'getlocal'
+ or obj.type == 'setlocal'
+ or obj.type == 'setglobal'
+ or obj.type == 'getglobal'
+ 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 == '...'
+ or obj.type == 'doc.class.name'
+ or obj.type == 'doc.class'
+ or obj.type == 'doc.type.name'
+ or obj.type == 'doc.see.name'
+ or obj.type == 'doc.see.field' then
+ simpleList = buildSimpleList(obj, max)
+ elseif obj.type == 'field'
+ or obj.type == 'method' then
+ simpleList = buildSimpleList(obj.parent, max)
+ end
+ return simpleList
+end
+
+---Create a new status
+---@param parentStatus core.guide.status
+---@param interface table
+---@param deep boolean
+---@return core.guide.status
+function m.status(parentStatus, interface, deep)
+ ---@class core.guide.status
+ local status = {
+ share = parentStatus and parentStatus.share or {
+ count = 0,
+ cacheLock = {},
+ },
+ depth = parentStatus and (parentStatus.depth + 1) or 0,
+ searchDeep= parentStatus and parentStatus.searchDeep or deep or -999,
+ interface = parentStatus and parentStatus.interface or {},
+ deep = parentStatus and parentStatus.deep,
+ clock = parentStatus and parentStatus.clock or osClock(),
+ results = {},
+ }
+ if interface then
+ for k, v in pairs(interface) do
+ status.interface[k] = v
+ end
+ end
+ status.deep = status.depth <= status.searchDeep
+ return status
+end
+
+function m.copyStatusResults(a, b)
+ local ra = a.results
+ local rb = b.results
+ for i = 1, #rb do
+ ra[#ra+1] = rb[i]
+ end
+end
+
+function m.isGlobal(source)
+ if source.type == 'setglobal'
+ or source.type == 'getglobal' then
+ if source.node and source.node.tag == '_ENV' then
+ return true
+ end
+ end
+ if source.type == 'field' then
+ source = source.parent
+ end
+ if source.type == 'getfield'
+ or source.type == 'setfield' then
+ local node = source.node
+ if node and node.special == '_G' then
+ return true
+ end
+ end
+ return false
+end
+
+function m.isDoc(source)
+ return source.type:sub(1, 4) == 'doc.'
+end
+
+function m.isDocClass(source)
+ return source.type == 'doc.class'
+end
+
+function m.isSet(source)
+ local tp = source.type
+ if tp == 'setglobal'
+ or tp == 'local'
+ or tp == 'setlocal'
+ or tp == 'setfield'
+ or tp == 'setmethod'
+ or tp == 'setindex'
+ or tp == 'tablefield'
+ or tp == 'tableindex' then
+ return true
+ end
+ if tp == 'call' then
+ local special = m.getSpecial(source.node)
+ if special == 'rawset' then
+ return true
+ end
+ end
+ return false
+end
+
+function m.isGet(source)
+ local tp = source.type
+ if tp == 'getglobal'
+ or tp == 'getlocal'
+ or tp == 'getfield'
+ or tp == 'getmethod'
+ or tp == 'getindex' then
+ return true
+ end
+ if tp == 'call' then
+ local special = m.getSpecial(source.node)
+ if special == 'rawget' then
+ return true
+ end
+ end
+ return false
+end
+
+function m.getSpecial(source)
+ if not source then
+ return nil
+ end
+ return source.special
+end
+
+--- 根据函数的调用参数,获取:调用,参数索引
+function m.getCallAndArgIndex(callarg)
+ local callargs = callarg.parent
+ if not callargs or callargs.type ~= 'callargs' then
+ return nil
+ end
+ local index
+ for i = 1, #callargs do
+ if callargs[i] == callarg then
+ index = i
+ break
+ end
+ end
+ local call = callargs.parent
+ return call, index
+end
+
+--- 根据函数调用的返回值,获取:调用的函数,参数列表,自己是第几个返回值
+function m.getCallValue(source)
+ local value = m.getObjectValue(source) or source
+ if not value then
+ return
+ end
+ local call, index
+ if value.type == 'call' then
+ call = value
+ index = 1
+ elseif value.type == 'select' then
+ call = value.vararg
+ index = value.index
+ if call.type ~= 'call' then
+ return
+ end
+ else
+ return
+ end
+ if call.node.special == 'pcall'
+ or call.node.special == 'xpcall' then
+ return call.args and call.args[1], call.args, index - 1
+ end
+ return call.node, call.args, index
+end
+
+function m.getNextRef(ref)
+ local nextRef = ref.next
+ if nextRef then
+ if nextRef.type == 'setfield'
+ or nextRef.type == 'getfield'
+ or nextRef.type == 'setmethod'
+ or nextRef.type == 'getmethod'
+ or nextRef.type == 'setindex'
+ or nextRef.type == 'getindex' then
+ return nextRef
+ end
+ end
+ -- 穿透 rawget 与 rawset
+ local call, index = m.getCallAndArgIndex(ref)
+ if call then
+ if call.node.special == 'rawset' and index == 1 then
+ return call
+ end
+ if call.node.special == 'rawget' and index == 1 then
+ return call
+ end
+ end
+
+ return nil
+end
+
+function m.checkSameSimpleInValueOfTable(status, value, start, pushQueue)
+ if value.type ~= 'table' then
+ return
+ end
+ for i = 1, #value do
+ local field = value[i]
+ pushQueue(field, start + 1)
+ end
+end
+
+function m.checkStatusDepth(status)
+ if status.depth <= 20 then
+ return true
+ end
+ if m.debugMode then
+ error('status.depth overflow')
+ elseif DEVELOP then
+ --log.warn(debug.traceback('status.depth overflow'))
+ logWarn('status.depth overflow')
+ end
+ return false
+end
+
+function m.searchFields(status, obj, key)
+ if not m.checkStatusDepth(status) then
+ return
+ end
+ local simple = m.getSimple(obj)
+ if not simple then
+ return
+ end
+ simple[#simple+1] = key or m.ANY
+ m.searchSameFields(status, simple, 'field')
+ m.cleanResults(status.results)
+end
+
+function m.searchDefFields(status, obj, key)
+ if not m.checkStatusDepth(status) then
+ return
+ end
+ 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
+
+---@param obj parser.guide.object
+---@return parser.guide.object
+function m.getObjectValue(obj)
+ while obj.type == 'paren' do
+ obj = obj.exp
+ if not obj then
+ return nil
+ end
+ end
+ if obj.type == 'boolean'
+ or obj.type == 'number'
+ or obj.type == 'integer'
+ or obj.type == 'string'
+ or obj.type == 'doc.type.table'
+ or obj.type == 'doc.type.arrary' then
+ return obj
+ end
+ if obj.value then
+ return obj.value
+ end
+ if obj.type == 'field'
+ or obj.type == 'method' then
+ return obj.parent and obj.parent.value
+ end
+ if obj.type == 'call' then
+ if obj.node.special == 'rawset' then
+ return obj.args and obj.args[3]
+ else
+ return obj
+ end
+ end
+ if obj.type == 'select' then
+ return obj
+ end
+ return nil
+end
+
+function m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue)
+ local cache, makeCache = m.getRefCache(status, mt, '__index')
+ if cache then
+ for _, obj in ipairs(cache) do
+ pushQueue(obj, start, true)
+ end
+ return
+ end
+ cache = {}
+ local newStatus = m.status(status)
+ m.searchDefFields(newStatus, mt, '__index')
+ local refsStatus = m.status(status)
+ for i = 1, #newStatus.results do
+ local indexValue = m.getObjectValue(newStatus.results[i])
+ if indexValue then
+ m.searchRefs(refsStatus, indexValue, 'ref')
+ end
+ end
+ for i = 1, #refsStatus.results do
+ local obj = refsStatus.results[i]
+ pushQueue(obj, start, true)
+ cache[i] = obj
+ end
+ if makeCache then
+ makeCache(cache)
+ end
+end
+function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue)
+ if not func or func.special ~= 'setmetatable' then
+ return
+ end
+ local call = func.parent
+ local args = call.args
+ if not args then
+ return
+ end
+ local obj = args[1]
+ local mt = args[2]
+ if obj then
+ pushQueue(obj, start, true)
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, obj, 'def')
+ for _, def in ipairs(newStatus.results) do
+ pushQueue(def, start, true)
+ end
+ end
+ if mt then
+ if not status.share.markMetaTable then
+ status.share.markMetaTable = {}
+ end
+ if status.share.markMetaTable[mt] then
+ return
+ end
+ status.share.markMetaTable[mt] = true
+ m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue)
+ status.share.markMetaTable[mt] = nil
+ end
+end
+
+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, pushQueue)
+ end
+ status.crossMetaTableMark = false
+end
+
+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
+ pushQueue(obj, start + 1)
+ end
+ end
+end
+
+function m.checkSameSimpleInParamSelf(status, obj, start, pushQueue)
+ if obj.type ~= 'getlocal' or obj[1] ~= 'self' then
+ return
+ end
+ local node = obj.node
+ if node.tag == 'self' then
+ return
+ end
+ if node.parent.type ~= 'funcargs' then
+ return
+ end
+ local func = node.parent.parent
+ if func.type ~= 'function' or func.parent.type ~= 'setfield' then
+ return
+ end
+ local fieldNode = func.parent.node
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, fieldNode, 'ref')
+ for _, ref in ipairs(newStatus.results) do
+ pushQueue(ref, start, true)
+ 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 stepRefOfGenericCrossTable(status, doc, typeName)
+ for _, typeUnit in ipairs(doc.extends.types) do
+ if typeUnit.type == 'doc.type.table' then
+ for _, where in ipairs {'key', 'value'} do
+ local childTypes = typeUnit[where].types
+ for _, childName in ipairs(childTypes) do
+ if childName[1] == typeName[1] then
+ return function (obj)
+ local childStatus = m.status(status)
+ m.searchRefs(childStatus, obj, 'def')
+ for _, res in ipairs(childStatus.results) do
+ if res.type == 'doc.type.table' then
+ return res[where]
+ end
+ if res.type == 'doc.type.array' then
+ if where == 'key' then
+ return status.interface and status.interface.docType('integer')[1]
+ end
+ if where == 'value' then
+ return res.node
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return function (obj)
+ return nil
+ end
+ elseif typeUnit.type == 'doc.type.array' then
+ return function (obj)
+ local childStatus = m.status(status)
+ m.searchRefs(childStatus, obj, 'def')
+ for _, res in ipairs(childStatus.results) do
+ if res.type == 'doc.type.array' then
+ return res.node
+ end
+ end
+ end
+ end
+ end
+ return nil
+end
+
+local function getIteratorArg(status, args, index)
+ local call = args.parent
+ local node = call.node
+ if not node.iterator then
+ return nil
+ end
+ if node.type ~= 'call' then
+ return nil
+ end
+ local results = m.checkSameSimpleInCallInSameFile(status, node.node, node.args, index + 1)
+ return results[1]
+end
+
+local function stepRefOfGeneric(status, typeUnit, args, mode)
+ local results = {}
+ if not args then
+ return results
+ end
+ local myName = typeUnit[1]
+ for _, typeName in ipairs(typeUnit.typeGeneric[myName]) do
+ if typeName == typeUnit then
+ goto CONTINUE
+ end
+ local docArg = m.getParentType(typeName, 'doc.type.arg')
+ or m.getParentType(typeName, 'doc.param')
+ or m.getParentType(typeName, 'doc.type.array')
+ if not docArg then
+ goto CONTINUE
+ end
+ local doc = m.getDocState(docArg)
+ if not doc.bindSources then
+ goto CONTINUE
+ end
+ local crossTable = stepRefOfGenericCrossTable(status, docArg, typeName)
+
+ -- find out param index
+ local genericIndex
+ if docArg.type == 'doc.param' then
+ local paramName = docArg.param[1]
+ for _, source in ipairs(doc.bindSources) do
+ if source.type == 'function'
+ and source.args then
+ for i, arg in ipairs(source.args) do
+ if arg[1] == paramName then
+ genericIndex = i
+ break
+ end
+ end
+ end
+ end
+ elseif docArg.type == 'doc.type.arg' then
+ for index, arg in ipairs(docArg.parent.args) do
+ if arg == docArg then
+ genericIndex = index
+ break
+ end
+ end
+ end
+
+ local callArg = args[genericIndex]
+ or getIteratorArg(status, args, genericIndex)
+
+ if not callArg then
+ goto CONTINUE
+ end
+ if crossTable then
+ callArg = crossTable(callArg)
+ if not callArg then
+ goto CONTINUE
+ end
+ end
+ appendValidGenericType(results, status, typeName, callArg)
+ ::CONTINUE::
+ end
+ return results
+end
+
+function m.checkSameSimpleByDocType(status, doc, args)
+ if status.share.searchingBindedDoc then
+ return
+ end
+ if doc.type ~= 'doc.type' then
+ return
+ end
+ local results = {}
+ for _, piece in ipairs(doc.types) do
+ if piece.typeGeneric then
+ local pieceResult = stepRefOfGeneric(status, piece, args, 'def')
+ for _, res in ipairs(pieceResult) do
+ results[#results+1] = res
+ end
+ else
+ local pieceResult = stepRefOfDocType(status, piece, 'def')
+ for _, res in ipairs(pieceResult) do
+ results[#results+1] = res
+ end
+ end
+ end
+ return results
+end
+
+function m.checkSameSimpleByBindDocs(status, obj, start, pushQueue, mode)
+ if not obj.bindDocs then
+ return
+ end
+ if status.share.searchingBindedDoc then
+ return
+ end
+ local skipInfer = false
+ local results = {}
+ for _, doc in ipairs(obj.bindDocs) do
+ if doc.type == 'doc.class' then
+ results[#results+1] = doc
+ elseif doc.type == 'doc.type' then
+ results[#results+1] = doc
+ elseif doc.type == 'doc.param' then
+ -- function (x) 的情况
+ if obj.type == 'local'
+ and m.getKeyName(obj) == doc.param[1] then
+ if obj.parent.type == 'funcargs'
+ or obj.parent.type == 'in'
+ or obj.parent.type == 'loop' then
+ results[#results+1] = doc.extends
+ end
+ end
+ elseif doc.type == 'doc.field' then
+ results[#results+1] = doc
+ elseif doc.type == 'doc.vararg' then
+ if obj.type == '...' then
+ results[#results+1] = doc
+ end
+ end
+ end
+ for _, res in ipairs(results) do
+ if res.type == 'doc.class'
+ or res.type == 'doc.type'
+ or res.type == 'doc.vararg' then
+ pushQueue(res, start, true)
+ skipInfer = true
+ end
+ if res.type == 'doc.type.function' then
+ pushQueue(res, start, true)
+ elseif res.type == 'doc.field' then
+ pushQueue(res, start + 1)
+ end
+ end
+ return skipInfer
+end
+
+function m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode)
+ if status.share.searchingBindedDoc then
+ return
+ end
+ if not obj.bindSources then
+ return
+ end
+ status.share.searchingBindedDoc = true
+ local mark = {}
+ local newStatus = m.status(status)
+ for _, ref in ipairs(obj.bindSources) do
+ if not mark[ref] then
+ mark[ref] = true
+ m.searchRefs(newStatus, ref, mode)
+ end
+ end
+ status.share.searchingBindedDoc = nil
+ for _, res in ipairs(newStatus.results) do
+ pushQueue(res, start, true)
+ end
+end
+
+function m.checkSameSimpleOfRefByDocReturn(status, obj, start, pushQueue, mode)
+ if status.share.searchingBindedDoc then
+ return
+ end
+ if not obj.bindSources then
+ return
+ end
+ local index = 0
+ for _, doc in ipairs(obj.bindGroup) do
+ if doc.type == 'doc.return' then
+ index = index + 1
+ if doc == obj then
+ break
+ end
+ end
+ end
+ status.share.searchingBindedDoc = true
+ local mark = {}
+ local newStatus = m.status(status)
+ for _, ref in ipairs(obj.bindSources) do
+ if not mark[ref] then
+ mark[ref] = true
+ m.searchRefs(newStatus, ref, mode)
+ end
+ end
+ status.share.searchingBindedDoc = nil
+ for _, res in ipairs(newStatus.results) do
+ local call = res.parent
+ if call.type == 'call' then
+ if index == 1 then
+ local sel = call.parent
+ if sel.type == 'select' and sel.index == index then
+ pushQueue(sel.parent, start, true)
+ end
+ else
+ if call.extParent then
+ for _, sel in ipairs(call.extParent) do
+ if sel.type == 'select' and sel.index == index then
+ pushQueue(sel.parent, start, true)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+local function getArrayOrTableLevel(obj)
+ local level = 0
+ while true do
+ local parent = obj.parent
+ if parent.type == 'doc.type.array' then
+ level = level + 1
+ elseif parent.type == 'doc.type.table' then
+ if obj.type == 'doc.type' then
+ level = level + 1
+ -- else 只存在 obj.type == 'doc.type.name' 的情况,即 table<k,v> 中的 table,这种是不需要再增加层级的
+ end
+ elseif parent.type == 'doc.type' and parent.parent and parent.parent.type == 'doc.type.table' then
+ level = level + 1
+ parent = parent.parent
+ else
+ break
+ end
+ obj = parent
+ end
+ return level
+end
+
+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
+ obj = m.getDocState(obj)
+ end
+ local classStart
+ for _, doc in ipairs(obj.bindGroup) do
+ if doc == obj then
+ classStart = true
+ elseif doc.type == 'doc.class' then
+ classStart = false
+ end
+ if classStart and doc.type == 'doc.field' then
+ pushQueue(doc, start + 1)
+ end
+ end
+ m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode)
+ if mode == 'ref' then
+ local pieceResult = stepRefOfDocType(status, obj.class, 'ref')
+ for _, res in ipairs(pieceResult) do
+ pushQueue(res, start, true)
+ end
+ if obj.extends then
+ for _, ext in ipairs(obj.extends) do
+ local pieceResult = stepRefOfDocType(status, ext, 'def')
+ for _, res in ipairs(pieceResult) do
+ pushQueue(res, start, true)
+ end
+ end
+ end
+ end
+ return true
+ elseif obj.type == 'doc.type' then
+ for _, piece in ipairs(obj.types) do
+ local pieceResult = stepRefOfDocType(status, piece, 'def')
+ for _, res in ipairs(pieceResult) do
+ pushQueue(res, start, true)
+ end
+ end
+ if mode == 'ref' then
+ 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
+ pushQueue(res, start, true)
+ end
+
+ if mode == 'ref' then
+ local state = m.getDocState(obj)
+ if state.type == 'doc.type' then
+ m.checkSameSimpleOfRefByDocSource(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode)
+ end
+ if state.type == 'doc.return' then
+ m.checkSameSimpleOfRefByDocReturn(status, state, start - getArrayOrTableLevel(obj), pushQueue, mode)
+ end
+ end
+ return true
+ elseif obj.type == 'doc.field' then
+ if mode ~= 'field'
+ and mode ~= 'deffield' then
+ return m.checkSameSimpleByDoc(status, obj.extends, start, pushQueue, mode)
+ end
+ elseif obj.type == 'doc.type.array' then
+ pushQueue(obj.node, start + 1, true)
+ return true
+ elseif obj.type == 'doc.type.table' then
+ pushQueue(obj.node, start, true)
+ pushQueue(obj.value, start + 1, true)
+ return true
+ elseif obj.type == 'doc.vararg' then
+ pushQueue(obj.vararg, start, true)
+ end
+end
+
+function m.checkSameSimpleInArg1OfSetMetaTable(status, obj, start, pushQueue)
+ local args = obj.parent
+ if not args or args.type ~= 'callargs' then
+ return
+ end
+ local callNode = args.parent.node
+ if callNode.special ~= 'setmetatable' then
+ return
+ end
+ if args[1] ~= obj then
+ return
+ end
+ local mt = args[2]
+ if mt then
+ if m.hasValueMark(status, mt) then
+ return
+ end
+ m.checkSameSimpleInValueInMetaTable(status, mt, start, pushQueue)
+ end
+end
+
+function m.searchSameMethodOutSelf(ref, mark)
+ local selfNode
+ if ref.tag == 'self' then
+ selfNode = ref
+ else
+ if ref.type == 'getlocal'
+ or ref.type == 'setlocal' then
+ local node = ref.node
+ if node.tag == 'self' then
+ selfNode = node
+ end
+ end
+ end
+ if selfNode then
+ if mark[selfNode] then
+ return nil
+ end
+ mark[selfNode] = true
+ local method = selfNode.method.node
+ if mark[method] then
+ return nil
+ end
+ mark[method] = true
+ return method
+ end
+end
+
+function m.searchSameMethodIntoSelf(ref, mark)
+ local nxt = ref.next
+ if not nxt then
+ return nil
+ end
+ if nxt.type ~= 'setmethod' then
+ return nil
+ end
+ 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, pushQueue, mode)
+ if status.share.crossMethodMark2 then
+ return
+ end
+ local mark = status.crossMethodMark
+ if not mark then
+ mark = {}
+ status.crossMethodMark = mark
+ end
+ if mark[ref] then
+ return
+ end
+ local selfRef = m.searchSameMethodIntoSelf(ref, mark)
+ if selfRef then
+ tracy.ZoneBeginN 'searchSameFieldsCrossMethod'
+ local _ <close> = tracy.ZoneEnd
+ -- 如果自己是method,则只检查自己内部的self引用
+ status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1
+ status.share.crossMethodMark2 = true
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, selfRef, mode)
+ for _, res in ipairs(newStatus.results) do
+ pushQueue(res, start, true)
+ end
+ status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1
+ status.share.crossMethodMark2 = nil
+ return
+ end
+ local method = m.searchSameMethodOutSelf(ref, mark)
+ if method then
+ pushQueue(method, start, true)
+ return
+ end
+end
+
+local function checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, source, index, args)
+ source = m.getObjectValue(source) or source
+ if not source or source.type ~= 'function' then
+ return
+ end
+ if not source.bindDocs then
+ return
+ end
+ local returns = {}
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.return' then
+ for _, rtn in ipairs(doc.returns) do
+ returns[#returns+1] = rtn
+ end
+ end
+ end
+ local rtn = returns[index]
+ if not rtn then
+ return
+ end
+ local types = m.checkSameSimpleByDocType(status, rtn, args)
+ if not types then
+ return
+ end
+ for _, res in ipairs(types) do
+ results[#results+1] = res
+ end
+ return true
+end
+
+local function checkSameSimpleAndMergeDocFunctionReturn(status, results, docFunc, index, args)
+ if docFunc.type ~= 'doc.type.function' then
+ return
+ end
+ local rtn = docFunc.returns[index]
+ if rtn then
+ local types = m.checkSameSimpleByDocType(status, rtn, args)
+ if types then
+ for _, res in ipairs(types) do
+ results[#results+1] = res
+ end
+ return true
+ end
+ end
+end
+
+local function checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, source, index)
+ if not source.bindDocs then
+ return
+ end
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.type' then
+ for _, typeUnit in ipairs(doc.types) do
+ if checkSameSimpleAndMergeDocFunctionReturn(status, results, typeUnit, index) then
+ return true
+ end
+ end
+ end
+ end
+end
+
+function m.checkSameSimpleInCallInSameFile(status, func, args, index)
+ if not status.share.callResultsCache then
+ status.share.callResultsCache = {}
+ end
+ local cache = status.share.callResultsCache[func]
+ if not cache then
+ cache = {}
+ status.share.callResultsCache[func] = cache
+ end
+ local results = cache[index]
+ if results then
+ return results
+ end
+ results = {}
+ if func.special then
+ --return results
+ end
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, func, 'def')
+ local hasDocReturn
+ for _, def in ipairs(newStatus.results) do
+ hasDocReturn = checkSameSimpleAndMergeDocTypeFunctionReturns(status, results, def, index)
+ or checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, def, index, args)
+ or checkSameSimpleAndMergeDocFunctionReturn(status, results, def, index, args)
+ or hasDocReturn
+ end
+ if not hasDocReturn then
+ for _, def in ipairs(newStatus.results) do
+ local value = m.getObjectValue(def) or def
+ if value.type == 'function' then
+ local returns = value.returns
+ if returns then
+ for _, ret in ipairs(returns) do
+ local exp = ret[index]
+ if exp then
+ results[#results+1] = exp
+ end
+ end
+ end
+ end
+ end
+ cache[index] = results
+ end
+ return results
+end
+
+function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode)
+ if status.share.inBeSetValue and status.share.inBeSetValue > 0 then
+ return
+ end
+ if status.share.inSetValue and status.share.inSetValue > 5 then
+ return
+ end
+ local func, args, index = m.getCallValue(ref)
+ if not func then
+ return
+ end
+ if m.checkCallMark(status, func.parent, true) then
+ return
+ end
+ status.share.inSetValue = (status.share.inSetValue or 0) + 1
+ -- 检查赋值是 semetatable() 的情况
+ m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue)
+ local objs = m.checkSameSimpleInCallInSameFile(status, func, args, index)
+ if status.interface.call then
+ local cobjs = status.interface.call(func, args, index)
+ if cobjs then
+ for _, obj in ipairs(cobjs) do
+ if not m.checkReturnMark(status, obj) then
+ objs[#objs+1] = obj
+ end
+ end
+ end
+ end
+ m.cleanResults(objs)
+ if not status.share.callFuncMark then
+ status.share.callFuncMark = {}
+ end
+ local mark = {}
+ for _, obj in ipairs(objs) do
+ if mark[obj] then
+ goto CONTINUE
+ end
+ if status.share.callFuncMark[obj] then
+ goto CONTINUE
+ end
+ status.share.callFuncMark[obj] = true
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, obj, mode)
+ pushQueue(obj, start, true)
+ mark[obj] = true
+ for _, obj in ipairs(newStatus.results) do
+ pushQueue(obj, start, true)
+ mark[obj] = true
+ end
+ status.share.callFuncMark[obj] = nil
+ ::CONTINUE::
+ end
+ status.share.inSetValue = (status.share.inSetValue or 0) - 1
+end
+
+local function searchRawset(ref, results)
+ if m.getKeyName(ref) ~= 'rawset' then
+ return
+ end
+ local call = ref.parent
+ if call.type ~= 'call' or call.node ~= ref then
+ return
+ end
+ if not call.args then
+ return
+ end
+ local arg1 = call.args[1]
+ if arg1.special ~= '_G' then
+ -- 不会吧不会吧,不会真的有人写成 `rawset(_G._G._G, 'xxx', value)` 吧
+ return
+ end
+ results[#results+1] = call
+end
+
+local function searchG(ref, results)
+ while ref and m.getKeyName(ref) == '_G' do
+ results[#results+1] = ref
+ ref = ref.next
+ end
+ if ref then
+ results[#results+1] = ref
+ searchRawset(ref, results)
+ end
+end
+
+local function searchEnvRef(ref, results)
+ if ref.type == 'setglobal'
+ or ref.type == 'getglobal' then
+ results[#results+1] = ref
+ searchG(ref, results)
+ elseif ref.type == 'getlocal' then
+ results[#results+1] = ref.next
+ searchG(ref.next, results)
+ end
+end
+
+function m.findGlobals(ast)
+ local root = m.getRoot(ast)
+ local results = {}
+ local env = m.getENV(root)
+ if env.ref then
+ for _, ref in ipairs(env.ref) do
+ searchEnvRef(ref, results)
+ end
+ end
+ return results
+end
+
+function m.findGlobalsOfName(ast, name)
+ local root = m.getRoot(ast)
+ local results = {}
+ local globals = m.findGlobals(root)
+ for _, global in ipairs(globals) do
+ if m.getKeyName(global) == name then
+ results[#results+1] = global
+ end
+ end
+ return results
+end
+
+function m.checkSameSimpleInGlobal(status, source)
+ local name = m.getKeyName(source)
+ if not name then
+ return
+ end
+ local objs
+ if status.interface.global then
+ objs = status.interface.global(name)
+ else
+ objs = m.findGlobalsOfName(source, name)
+ end
+ return objs
+end
+
+function m.hasValueMark(status, value)
+ if not status.share.valueMark then
+ status.share.valueMark = {}
+ end
+ if status.share.valueMark[value] then
+ return true
+ end
+ status.share.valueMark[value] = true
+ return false
+end
+
+function m.checkCallMark(status, a, mark)
+ if not status.share.callMark then
+ status.share.callMark = {}
+ end
+ if mark then
+ status.share.callMark[a] = mark
+ else
+ return status.share.callMark[a]
+ end
+ return false
+end
+
+function m.checkReturnMark(status, a, mark)
+ if not status.share.returnMark then
+ status.share.returnMark = {}
+ end
+ local result = status.share.returnMark[a]
+ if mark then
+ status.share.returnMark[a] = mark
+ end
+ return result
+end
+
+function m.searchSameFieldsInValue(status, ref, start, pushQueue, mode)
+ if status.share.inBeSetValue and status.share.inBeSetValue > 0 then
+ return
+ end
+ if status.share.inSetValue and status.share.inSetValue > 5 then
+ return
+ end
+ local value = m.getObjectValue(ref)
+ if not value then
+ return
+ end
+ if m.hasValueMark(status, value) then
+ return
+ end
+ status.share.inSetValue = (status.share.inSetValue or 0) + 1
+ if not status.share.tempValueMark then
+ status.share.tempValueMark = {}
+ end
+ if not status.share.tempValueMark[value] then
+ status.share.tempValueMark[value] = true
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, value, mode)
+ status.share.tempValueMark[value] = nil
+ for _, res in ipairs(newStatus.results) do
+ pushQueue(res, start, true)
+ end
+ pushQueue(value, start, true)
+ end
+ status.share.inSetValue = (status.share.inSetValue or 0) - 1
+ -- 检查形如 a = f() 的分支情况
+ m.checkSameSimpleInCall(status, value, start, pushQueue, mode)
+end
+
+function m.checkSameSimpleAsTableField(status, ref, start, pushQueue)
+ if not status.deep then
+ --return
+ end
+ local parent = ref.parent
+ if not parent or parent.type ~= 'tablefield' then
+ return
+ end
+ if m.hasValueMark(status, ref) then
+ return
+ end
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, parent.field, 'ref')
+ for _, res in ipairs(newStatus.results) do
+ pushQueue(res, start, true)
+ end
+end
+
+function m.checkSearchLevel(status)
+ status.share.back = status.share.back or 0
+ if status.share.back >= (status.interface.searchLevel or 0) then
+ -- TODO 限制向前搜索的次数
+ --return true
+ end
+ status.share.back = status.share.back + 1
+ return false
+end
+
+function m.checkSameSimpleAsReturn(status, ref, start, pushQueue)
+ if not status.deep then
+ return
+ end
+ if not ref.parent or ref.parent.type ~= 'return' then
+ return
+ end
+ if ref.parent.parent.type ~= 'main' then
+ return
+ end
+ if m.checkSearchLevel(status) then
+ return
+ end
+ local newStatus = m.status(status)
+ m.searchRefsAsFunctionReturn(newStatus, ref, 'ref')
+ for _, res in ipairs(newStatus.results) do
+ if not m.checkCallMark(status, res) then
+ pushQueue(res, start, true)
+ end
+ end
+end
+
+function m.checkSameSimpleAsSetValue(status, ref, start, pushQueue)
+ if not status.deep then
+ --return
+ end
+ if status.share.inSetValue and status.share.inSetValue > 0 then
+ return
+ end
+ if status.share.inBeSetValue and status.share.inBeSetValue > 5 then
+ return
+ end
+ if ref.type == 'select' then
+ return
+ end
+ local parent = ref.parent
+ if not parent then
+ return
+ end
+ if m.getObjectValue(parent) ~= ref then
+ return
+ end
+ if m.hasValueMark(status, ref) then
+ return
+ end
+ if m.checkSearchLevel(status) then
+ return
+ end
+ local obj
+ if parent.type == 'local'
+ or parent.type == 'setglobal'
+ or parent.type == 'setlocal' then
+ obj = parent
+ elseif parent.type == 'setfield' then
+ obj = parent.field
+ elseif parent.type == 'setmethod' then
+ obj = parent.method
+ end
+ if not obj then
+ return
+ end
+ status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, obj, 'ref')
+ for _, res in ipairs(newStatus.results) do
+ pushQueue(res, start, true)
+ end
+ status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1
+end
+
+local function getTableAndIndexIfIsForPairsKeyOrValue(ref)
+ if ref.type ~= 'local' then
+ return
+ end
+
+ if not ref.parent or ref.parent.type ~= 'in' then
+ return
+ end
+
+ if not ref.value or ref.value.type ~= 'select' then
+ return
+ end
+
+ local rootSelectObj = ref.value
+ if rootSelectObj.index ~= 1 and rootSelectObj.index ~= 2 then
+ return
+ end
+
+ if not rootSelectObj.vararg or rootSelectObj.vararg.type ~= 'call' then
+ return
+ end
+ local rootCallObj = rootSelectObj.vararg
+
+ if not rootCallObj.node or rootCallObj.node.type ~= 'call' then
+ return
+ end
+ local pairsCallObj = rootCallObj.node
+
+ if not pairsCallObj.node
+ or (pairsCallObj.node.special ~= 'pairs' and pairsCallObj.node.special ~= 'ipairs') then
+ return
+ end
+
+ if not pairsCallObj.args or not pairsCallObj.args[1] then
+ return
+ end
+ local tableObj = pairsCallObj.args[1]
+
+ return tableObj, rootSelectObj.index
+end
+
+function m.checkSameSimpleAsKeyOrValueInForParis(status, ref, start, pushQueue)
+ local tableObj, index = getTableAndIndexIfIsForPairsKeyOrValue(ref)
+ if not tableObj then
+ return
+ end
+
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, tableObj, 'def')
+ for _, def in ipairs(newStatus.results) do
+ if def.bindDocs then
+ for _, binddoc in ipairs(def.bindDocs) do
+ if binddoc.type == 'doc.type' then
+ if binddoc.types[1] and binddoc.types[1].type == 'doc.type.table' then
+ if index == 1 then
+ pushQueue(binddoc.types[1].key, start, true)
+ elseif index == 2 then
+ pushQueue(binddoc.types[1].value, start, true)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+---
+---@param func parser.guide.object
+---@param argIndex integer
+---@return integer?
+local function findGenericFromArgIndexToReturnIndex(func, argIndex)
+ if not func.bindDocs then
+ return nil
+ end
+ local paramType
+ for _, doc in ipairs(func.bindDocs) do
+ if doc.type == 'doc.param' then
+ if doc.extends and doc.extends.paramIndex == argIndex then
+ paramType = doc.extends
+ break
+ end
+ end
+ end
+ if not paramType then
+ return nil
+ end
+ for _, typeUnit in ipairs(paramType.types) do
+ if typeUnit.typeGeneric then
+ local generic = typeUnit.typeGeneric[typeUnit[1]]
+ if generic then
+ for _, typeName in ipairs(generic) do
+ local docType = typeName.parent
+ if docType.returnIndex then
+ return docType.returnIndex
+ end
+ end
+ end
+ end
+ end
+ return nil
+end
+
+function m.checkSameSimpleAsCallArg(status, ref, start, pushQueue)
+ if not status.deep then
+ return
+ end
+ local call, index = m.getCallAndArgIndex(ref)
+ if not call then
+ return
+ end
+ if call.parent.type ~= 'select' then
+ return
+ end
+ if (status.share.inSetValue or 0) > 0 then
+ return
+ end
+ if status.share.inBeSetValue and status.share.inBeSetValue > 5 then
+ return
+ end
+ status.share.inBeSetValue = (status.share.inBeSetValue or 0) + 1
+ local newStatus = m.status(status)
+ m.searchRefs(newStatus, call.node, 'def')
+ for _, func in ipairs(newStatus.results) do
+ local rindex = findGenericFromArgIndexToReturnIndex(func, index)
+ if rindex then
+ if rindex == 1 then
+ pushQueue(call.parent.parent, start, true)
+ else
+ if call.extParent then
+ for _, slt in ipairs(call.extParent) do
+ if slt.index == rindex then
+ pushQueue(slt.parent, start, true)
+ end
+ end
+ end
+ end
+ end
+ end
+ status.share.inBeSetValue = (status.share.inBeSetValue or 0) - 1
+end
+
+local function hasTypeName(doc, name)
+ if doc.type == 'doc.type' then
+ for _, tunit in ipairs(doc.types) do
+ if tunit.type == 'doc.type.name'
+ and tunit[1] == name then
+ return true
+ end
+ end
+ end
+ if doc.type == 'doc.type.name'
+ or doc.type == 'doc.class.name' then
+ if doc[1] == name then
+ return true
+ end
+ end
+ return false
+end
+
+function m.checkSameSimpleInString(status, ref, start, pushQueue, mode)
+ -- 特殊处理 ('xxx').xxx 的形式
+ if ref.type ~= 'string'
+ and not hasTypeName(ref, 'string') then
+ return
+ end
+ if not status.interface.docType then
+ return
+ end
+ if status.share.searchingBindedDoc then
+ return
+ end
+ if not status.share.markString then
+ status.share.markString = {}
+ end
+ local marked = ref
+ if ref.type == 'string' then
+ marked = 'string'
+ end
+ if status.share.markString[marked] then
+ return
+ end
+ status.share.markString[marked] = true
+ local newStatus = m.status(status)
+ local docs = status.interface.docType('stringlib')
+ local mark = {}
+ for i = 1, #docs do
+ local doc = docs[i]
+ m.searchFields(newStatus, doc)
+ end
+ for _, res in ipairs(newStatus.results) do
+ if mark[res] then
+ goto CONTINUE
+ end
+ mark[res] = true
+ pushQueue(res, start + 1)
+ ::CONTINUE::
+ end
+ status.share.markString[marked] = nil
+ return true
+end
+
+function m.pushResult(status, mode, ref, simple)
+ local results = status.results
+ if mode == 'def' then
+ if ref.type == 'setglobal'
+ or ref.type == 'setlocal'
+ or ref.type == 'local' then
+ results[#results+1] = ref
+ elseif 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 == 'call' then
+ if ref.node.special == 'rawset' then
+ results[#results+1] = ref
+ end
+ elseif ref.type == 'function' then
+ results[#results+1] = ref
+ elseif ref.type == 'table' then
+ results[#results+1] = ref
+ elseif ref.type == 'doc.type.function'
+ or ref.type == 'doc.class.name'
+ or ref.type == 'doc.alias.name'
+ or ref.type == 'doc.field'
+ or ref.type == 'doc.type.table'
+ or ref.type == 'doc.type.array' then
+ results[#results+1] = ref
+ elseif ref.type == 'doc.type' then
+ if #ref.enums > 0 or #ref.resumes > 0 then
+ results[#results+1] = ref
+ end
+ end
+ if ref.parent and ref.parent.type == 'return' then
+ if m.getParentFunction(ref) ~= m.getParentFunction(simple.node) then
+ results[#results+1] = ref
+ end
+ end
+ if m.isLiteral(ref)
+ and ref.parent and ref.parent.type == 'callargs'
+ and ref ~= simple.node then
+ results[#results+1] = ref
+ end
+ elseif mode == 'ref' then
+ if ref.type == 'setfield'
+ or ref.type == 'getfield'
+ or ref.type == 'tablefield' then
+ results[#results+1] = ref
+ elseif ref.type == 'setmethod'
+ or ref.type == 'getmethod' then
+ results[#results+1] = ref
+ elseif ref.type == 'setindex'
+ or ref.type == 'getindex'
+ or ref.type == 'tableindex' then
+ results[#results+1] = ref
+ elseif ref.type == 'setglobal'
+ or ref.type == 'getglobal'
+ or ref.type == 'local'
+ or ref.type == 'setlocal'
+ or ref.type == 'getlocal' 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'
+ or ref.node.special == 'rawget' then
+ results[#results+1] = ref
+ end
+ elseif ref.type == 'doc.type.function'
+ or ref.type == 'doc.class.name'
+ or ref.type == 'doc.alias.name'
+ or ref.type == 'doc.field'
+ or ref.type == 'doc.type.table'
+ or ref.type == 'doc.type.array' then
+ results[#results+1] = ref
+ elseif ref.type == 'doc.type' then
+ if #ref.enums > 0 or #ref.resumes > 0 then
+ results[#results+1] = ref
+ end
+ end
+ if ref.parent and ref.parent.type == 'return' then
+ results[#results+1] = ref
+ end
+ if m.isLiteral(ref)
+ and ref.parent
+ and ref.parent.type == 'callargs'
+ and ref ~= simple.node then
+ results[#results+1] = ref
+ end
+ elseif mode == 'field' then
+ if ref.type == 'setfield'
+ or ref.type == 'getfield'
+ or ref.type == 'tablefield' then
+ results[#results+1] = ref
+ elseif ref.type == 'setmethod'
+ or ref.type == 'getmethod' then
+ results[#results+1] = ref
+ elseif ref.type == 'setindex'
+ or ref.type == 'tableindex' then
+ results[#results+1] = ref
+ elseif ref.type == 'getindex' then
+ -- do not trust `t[1]`
+ if ref.index and ref.index.type == 'string' then
+ results[#results+1] = ref
+ end
+ elseif ref.type == 'setglobal'
+ or ref.type == 'getglobal' then
+ results[#results+1] = ref
+ elseif ref.type == 'call' then
+ if ref.node.special == 'rawset'
+ or ref.node.special == 'rawget' then
+ results[#results+1] = ref
+ end
+ elseif 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 == 'call' then
+ if ref.node.special == 'rawset' then
+ results[#results+1] = ref
+ end
+ elseif ref.type == 'doc.field' then
+ results[#results+1] = ref
+ end
+ end
+end
+
+function m.checkSameSimpleName(ref, sm)
+ if sm == m.ANY then
+ return true
+ end
+
+ if m.getSimpleName(ref) == sm then
+ return true
+ end
+ if ref.type == 'doc.type'
+ and ref.array == true then
+ return true
+ end
+ return false
+end
+
+function m.isValidSetRef(ref)
+ if m.isSet(ref) then
+ return true
+ end
+end
+
+function m.checkSameSimple(status, simple, ref, start, force, mode, pushQueue)
+ if start > #simple then
+ return
+ end
+ for i = start, #simple do
+ local sm = simple[i]
+ if not force and not m.checkSameSimpleName(ref, sm) then
+ return
+ end
+ force = false
+ local cmode = mode
+ local skipSearch
+ if i < #simple then
+ cmode = 'ref'
+ else
+ if mode == 'deffield' then
+ if not m.isSet(ref) then
+ skipSearch = true
+ end
+ end
+ end
+ -- 检查 doc
+ local skipInfer = m.checkSameSimpleByBindDocs(status, ref, i, pushQueue, cmode)
+ or m.checkSameSimpleByDoc(status, ref, i, pushQueue, cmode)
+ -- 检查自己是字符串的分支情况
+ m.checkSameSimpleInString(status, ref, i, pushQueue, cmode)
+ if not skipInfer and not skipSearch then
+ -- 穿透 self:func 与 mt:func
+ m.searchSameFieldsCrossMethod(status, ref, i, pushQueue, cmode)
+ -- 穿透赋值
+ m.searchSameFieldsInValue(status, ref, i, pushQueue, cmode)
+ -- 检查自己是字面量表的情况
+ m.checkSameSimpleInValueOfTable(status, ref, i, pushQueue)
+ -- 检查自己作为 setmetatable 第一个参数的情况
+ m.checkSameSimpleInArg1OfSetMetaTable(status, ref, i, pushQueue)
+ -- 检查自己作为 setmetatable 调用的情况
+ m.checkSameSimpleInValueOfCallMetaTable(status, ref, i, pushQueue)
+ -- 检查自己是特殊变量的分支的情况
+ m.checkSameSimpleInSpecialBranch(status, ref, i, pushQueue)
+ -- self 的特殊处理
+ m.checkSameSimpleInParamSelf(status, ref, i, pushQueue)
+ -- 自己是 call 的情况
+ m.checkSameSimpleInCall(status, ref, i, pushQueue, cmode)
+ if cmode == 'ref' then
+ -- 检查形如 { a = f } 的情况
+ m.checkSameSimpleAsTableField(status, ref, i, pushQueue)
+ -- 检查形如 return m 的情况
+ m.checkSameSimpleAsReturn(status, ref, i, pushQueue)
+ -- 检查形如 a = f 的情况
+ m.checkSameSimpleAsSetValue(status, ref, i, pushQueue)
+ -- 检查形如 for k,v in pairs()/ipairs() do end 的情况
+ m.checkSameSimpleAsKeyOrValueInForParis(status, ref, i, pushQueue)
+ -- 检查自己是函数参数的情况(泛型) local x = call(V)
+ m.checkSameSimpleAsCallArg(status, ref, i, pushQueue)
+ end
+ end
+ if i == #simple then
+ break
+ end
+ ref = m.getNextRef(ref)
+ if not ref then
+ return
+ end
+ end
+ m.pushResult(status, mode, ref, simple)
+ local value = m.getObjectValue(ref)
+ if value then
+ m.pushResult(status, mode, value, simple)
+ 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 queues, starts, forces = allocQueue()
+ local queueLen = 0
+ local locks = {}
+ local function appendQueue(obj, start, force)
+ local lock = locks[start]
+ if not lock then
+ lock = {}
+ locks[start] = lock
+ end
+ if lock[obj] then
+ return
+ end
+ lock[obj] = true
+ queueLen = queueLen + 1
+ queues[queueLen] = obj
+ starts[queueLen] = start
+ forces[queueLen] = force
+ if obj.mirror then
+ if not lock[obj.mirror] then
+ lock[obj.mirror] = true
+ queueLen = queueLen + 1
+ queues[queueLen] = obj.mirror
+ starts[queueLen] = start
+ forces[queueLen] = force
+ end
+ end
+ end
+ local function pushQueue(obj, start, force)
+ if obj.type == 'getlocal'
+ or obj.type == 'setlocal' then
+ obj = obj.node
+ end
+ appendQueue(obj, start, force)
+ if obj.type == 'local' and obj.ref then
+ for _, ref in ipairs(obj.ref) do
+ appendQueue(ref, start, force)
+ end
+ end
+ if m.isGlobal(obj) then
+ local refs = m.checkSameSimpleInGlobal(status, obj)
+ if refs then
+ for _, ref in ipairs(refs) do
+ appendQueue(ref, start, force)
+ end
+ end
+ end
+ end
+ if simple.mode == 'global' then
+ -- 全局变量开头
+ pushQueue(simple.node, 1)
+ elseif simple.mode == 'local' then
+ -- 局部变量开头
+ pushQueue(simple.node, 1)
+ else
+ pushQueue(simple.node, 1)
+ end
+ local max = 0
+ for i = 1, 1e6 do
+ if queueLen <= 0 then
+ break
+ end
+ local obj = queues[queueLen]
+ local start = starts[queueLen]
+ local force = forces[queueLen]
+ queues[queueLen] = nil
+ starts[queueLen] = nil
+ forces[queueLen] = nil
+ queueLen = queueLen - 1
+ max = max + 1
+ status.share.count = status.share.count + 1
+ if status.share.count % 10000 == 0 then
+ --if TEST then
+ -- print('####', status.share.count, osClock() - status.clock)
+ --end
+ if status.interface and status.interface.pulse then
+ status.interface.pulse()
+ end
+ end
+ --if status.share.count >= 100000 then
+ -- logWarn('Count too large!')
+ -- break
+ --end
+ m.checkSameSimple(status, simple, obj, start, force, mode, pushQueue)
+ if max >= 10000 then
+ logWarn('Queue too large!')
+ break
+ end
+ end
+ --deallocQueue(queues, starts, forces)
+end
+
+function m.getCallerInSameFile(status, func)
+ -- 搜索所有所在函数的调用者
+ local funcRefs = m.status(status)
+ m.searchRefOfValue(funcRefs, func)
+
+ local calls = {}
+ if #funcRefs.results == 0 then
+ return calls
+ end
+ for _, res in ipairs(funcRefs.results) do
+ local call = res.parent
+ if call.type == 'call' then
+ calls[#calls+1] = call
+ end
+ end
+ return calls
+end
+
+function m.getCallerCrossFiles(status, main)
+ if status.interface.link then
+ return status.interface.link(main.uri)
+ end
+ return {}
+end
+
+function m.searchRefsAsFunctionReturn(status, obj, mode)
+ if not status.deep then
+ return
+ end
+ if mode == 'def' then
+ return
+ end
+ if m.checkReturnMark(status, obj, true) then
+ return
+ end
+ status.results[#status.results+1] = obj
+ -- 搜索所在函数
+ local currentFunc = m.getParentFunction(obj)
+ local rtn = obj.parent
+ if rtn.type ~= 'return' then
+ return
+ end
+ -- 看看他是第几个返回值
+ local index
+ for i = 1, #rtn do
+ if obj == rtn[i] then
+ index = i
+ break
+ end
+ end
+ if not index then
+ return
+ end
+ local calls
+ if currentFunc.type == 'main' then
+ calls = m.getCallerCrossFiles(status, currentFunc)
+ else
+ calls = m.getCallerInSameFile(status, currentFunc)
+ end
+ -- 搜索调用者的返回值
+ if #calls == 0 then
+ return
+ end
+ local selects = {}
+ for i = 1, #calls do
+ local parent = calls[i].parent
+ if parent.type == 'select' and parent.index == index then
+ selects[#selects+1] = parent.parent
+ end
+ local extParent = calls[i].extParent
+ if extParent then
+ for j = 1, #extParent do
+ local ext = extParent[j]
+ if ext.type == 'select' and ext.index == index then
+ selects[#selects+1] = ext.parent
+ end
+ end
+ end
+ end
+ -- 搜索调用者的引用
+ for i = 1, #selects do
+ m.searchRefs(status, selects[i], 'ref')
+ end
+end
+
+function m.searchRefsAsFunctionSet(status, obj, mode)
+ local parent = obj.parent
+ if not parent then
+ return
+ end
+ if parent.type == 'local'
+ or parent.type == 'setlocal'
+ or parent.type == 'setglobal'
+ or parent.type == 'setfield'
+ or parent.type == 'setmethod'
+ or parent.type == 'tablefield' then
+ m.searchRefs(status, parent, mode)
+ elseif parent.type == 'setindex'
+ or parent.type == 'tableindex' then
+ if parent.index == obj then
+ m.searchRefs(status, parent, mode)
+ end
+ end
+end
+
+function m.searchRefsAsFunction(status, obj, mode)
+ if obj.type ~= 'function'
+ and obj.type ~= 'table' then
+ return
+ end
+ m.searchRefsAsFunctionSet(status, obj, mode)
+ -- 检查自己作为返回函数时的引用
+ m.searchRefsAsFunctionReturn(status, obj, mode)
+end
+
+function m.cleanResults(results)
+ local mark = {}
+ for i = #results, 1, -1 do
+ local res = results[i]
+ if res.tag == 'self'
+ or mark[res] then
+ results[i] = results[#results]
+ results[#results] = nil
+ else
+ mark[res] = true
+ end
+ end
+end
+
+--function m.getRefCache(status, obj, mode)
+-- local cache = status.interface.cache and status.interface.cache()
+-- if not cache then
+-- return
+-- end
+-- if m.isGlobal(obj) then
+-- obj = m.getKeyName(obj)
+-- end
+-- if not cache[mode] then
+-- cache[mode] = {}
+-- end
+-- local sourceCache = cache[mode][obj]
+-- if sourceCache then
+-- return sourceCache
+-- end
+-- sourceCache = {}
+-- cache[mode][obj] = sourceCache
+-- return nil, function (results)
+-- for i = 1, #results do
+-- sourceCache[i] = results[i]
+-- end
+-- end
+--end
+
+function m.getRefCache(status, obj, mode)
+ local isDeep = status.deep
+ if mode == 'infer' then
+ if not isDeep then
+ return nil, nil
+ end
+ end
+ local globalCache = status.interface.cache and status.interface.cache() or {}
+ if m.isGlobal(obj) then
+ obj = m.getKeyName(obj)
+ end
+ if not obj then
+ return {}
+ end
+ if not globalCache[mode] then
+ globalCache[mode] = {}
+ end
+ local sourceCache = globalCache[mode][obj]
+ if sourceCache then
+ return sourceCache
+ end
+ if not status.share.cacheLock[mode] then
+ status.share.cacheLock[mode] = {}
+ end
+ if status.share.cacheLock[mode][obj] then
+ return {}
+ end
+ status.share.cacheLock[mode][obj] = {}
+ return nil, function (results)
+ sourceCache = {}
+ for i = 1, #results do
+ sourceCache[i] = results[i]
+ end
+ globalCache[mode][obj] = sourceCache
+ if not isDeep then
+ return
+ end
+ if mode == 'ref'
+ or mode == 'def' then
+ for i = 1, #results do
+ local res = results[i]
+ if not globalCache[mode][res] then
+ globalCache[mode][res] = sourceCache
+ end
+ end
+ end
+ end
+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
+ status.results[#status.results+1] = cache[i]
+ end
+ return
+ 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 m.checkStatusDepth(status) then
+ local simple = m.getSimple(obj)
+ if simple then
+ m.searchSameFields(status, simple, mode)
+ end
+ end
+ tracy.ZoneEnd()
+
+ m.cleanResults(status.results)
+
+ if makeCache then
+ makeCache(status.results)
+ end
+end
+
+function m.searchRefOfValue(status, obj)
+ local var = obj.parent
+ if var.type == 'local'
+ or var.type == 'set' then
+ return m.searchRefs(status, var, 'ref')
+ end
+end
+
+function m.allocInfer(o)
+ if type(o.type) == 'table' then
+ local infers = {}
+ for i = 1, #o.type do
+ infers[i] = {
+ type = o.type[i],
+ value = o.value,
+ source = o.source,
+ level = o.level
+ }
+ end
+ return infers
+ else
+ return {
+ [1] = o,
+ }
+ end
+end
+
+function m.mergeTypes(types)
+ local hasAny = types['any']
+
+ types['any'] = nil
+
+ if not next(types) then
+ return 'any'
+ end
+ -- 同时包含 number 与 integer 时,去掉 integer
+ if types['number'] and types['integer'] then
+ types['integer'] = nil
+ end
+
+ local results = {}
+ for tp in pairs(types) do
+ results[#results+1] = tp
+ end
+ -- 只有显性的 nil 与 any 时,取 any
+ if #results == 1 then
+ if results[1] == 'nil' and hasAny then
+ return 'any'
+ else
+ return results[1]
+ end
+ end
+
+ tableSort(results, function (a, b)
+ local sa = TypeSort[a] or 100
+ local sb = TypeSort[b] or 100
+ if sa == sb then
+ return a < b
+ else
+ return sa < sb
+ end
+ end)
+
+ return tableConcat(results, '|')
+end
+
+function m.getClassExtends(class)
+ if class.type == 'doc.class.name' then
+ class = class.parent
+ end
+ if not class.extends then
+ return nil
+ end
+ local names = {}
+ for _, ext in ipairs(class.extends) do
+ names[#names+1] = ext[1]
+ end
+ return names
+end
+
+function m.viewInferType(infers)
+ if not infers then
+ return 'any'
+ end
+ local types = {}
+ local hasDoc
+ local hasDocTable
+ for i = 1, #infers do
+ local infer = infers[i]
+ local src = infer.source
+ if src.type == 'doc.class'
+ or src.type == 'doc.class.name'
+ or src.type == 'doc.type.name'
+ or src.type == 'doc.type.array'
+ or src.type == 'doc.type.table' then
+ if infer.type ~= 'any' then
+ hasDoc = true
+ end
+ if src.type == 'doc.type.array'
+ or src.type == 'doc.type.table' then
+ hasDocTable = true
+ end
+ end
+ end
+ if hasDoc then
+ for i = 1, #infers do
+ local infer = infers[i]
+ local src = infer.source
+ if src.type == 'doc.class'
+ or src.type == 'doc.class.name'
+ or src.type == 'doc.type.name'
+ or src.type == 'doc.type.array'
+ or src.type == 'doc.type.table'
+ or src.type == 'doc.type.enum'
+ or src.type == 'doc.resume' then
+ local tp = infer.type or 'any'
+ if hasDocTable and tp == 'table' then
+ goto CONTINUE
+ end
+ if types[tp] == nil then
+ types[tp] = true
+ end
+ end
+ if src.type == 'doc.class'
+ or src.type == 'doc.class.name' then
+ local extends = m.getClassExtends(src)
+ if extends then
+ for _, tp in ipairs(extends) do
+ types[tp] = false
+ end
+ end
+ end
+ ::CONTINUE::
+ end
+ for k, v in pairs(types) do
+ if not v then
+ types[k] = nil
+ end
+ end
+ else
+ for i = 1, #infers do
+ local infer = infers[i]
+ if infer.source.typeGeneric then
+ goto CONTINUE
+ end
+ local tp = infer.type or 'any'
+ types[tp] = true
+ ::CONTINUE::
+ end
+ end
+ return m.mergeTypes(types)
+end
+
+function m.checkTrue(status, source)
+ local newStatus = m.status(status)
+ m.searchInfer(newStatus, source)
+ -- 当前认为的结果
+ local current
+ for _, infer in ipairs(newStatus.results) do
+ -- 新的结果
+ local new
+ if infer.type == 'nil' then
+ new = false
+ elseif infer.type == 'boolean' then
+ if infer.value == true then
+ new = true
+ elseif infer.value == false then
+ new = false
+ end
+ end
+ if new ~= nil then
+ if current == nil then
+ current = new
+ else
+ -- 如果2个结果完全相反,则返回 nil 表示不确定
+ if new ~= current then
+ return nil
+ end
+ end
+ end
+ end
+ return current
+end
+
+--- 获取特定类型的字面量值
+function m.getInferLiteral(status, source, type)
+ local newStatus = m.status(status)
+ m.searchInfer(newStatus, source)
+ for _, infer in ipairs(newStatus.results) do
+ if infer.value ~= nil then
+ if type == nil or infer.type == type then
+ return infer.value
+ end
+ end
+ end
+ return nil
+end
+
+--- 是否包含某种类型
+function m.hasType(status, source, type)
+ m.searchInfer(status, source)
+ for _, infer in ipairs(status.results) do
+ if infer.type == type then
+ return true
+ end
+ end
+ return false
+end
+
+function m.isSameValue(status, a, b)
+ local statusA = m.status(status)
+ m.searchInfer(statusA, a)
+ local statusB = m.status(status)
+ m.searchInfer(statusB, b)
+ local infers = {}
+ for _, infer in ipairs(statusA.results) do
+ local literal = infer.value
+ if literal then
+ infers[literal] = false
+ end
+ end
+ for _, infer in ipairs(statusB.results) do
+ local literal = infer.value
+ if literal then
+ if infers[literal] == nil then
+ return false
+ end
+ infers[literal] = true
+ end
+ end
+ for k, v in pairs(infers) do
+ if v == false then
+ return false
+ end
+ end
+ return true
+end
+
+function m.inferCheckLiteralTableWithDocVararg(status, source)
+ if #source ~= 1 then
+ return
+ end
+ local vararg = source[1]
+ if vararg.type ~= 'varargs' then
+ return
+ end
+ local results = m.getVarargDocType(status, source)
+ status.results[#status.results+1] = {
+ type = m.viewInferType(results) .. '[]',
+ source = source,
+ level = 100,
+ }
+ return true
+end
+
+function m.inferCheckLiteral(status, source)
+ if source.type == 'string' then
+ status.results = m.allocInfer {
+ type = 'string',
+ value = source[1],
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif source.type == 'nil' then
+ status.results = m.allocInfer {
+ type = 'nil',
+ value = NIL,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif source.type == 'boolean' then
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = source[1],
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif source.type == 'number' then
+ if mathType(source[1]) == 'integer' then
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = source[1],
+ source = source,
+ level = 100,
+ }
+ return true
+ else
+ status.results = m.allocInfer {
+ type = 'number',
+ value = source[1],
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ elseif source.type == 'integer' then
+ status.results = m.allocInfer {
+ type = 'integer',
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif source.type == 'table' then
+ if m.inferCheckLiteralTableWithDocVararg(status, source) then
+ return true
+ end
+ status.results = m.allocInfer {
+ type = 'table',
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif source.type == 'function' then
+ status.results = m.allocInfer {
+ type = 'function',
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif source.type == '...' then
+ status.results = m.allocInfer {
+ type = '...',
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+end
+
+local function getDocAliasExtends(status, typeUnit)
+ if not status.interface.docType then
+ return nil
+ end
+ if typeUnit.type ~= 'doc.type.name' then
+ return nil
+ end
+ for _, doc in ipairs(status.interface.docType(typeUnit[1])) do
+ if doc.type == 'doc.alias.name' then
+ return doc.parent.extends
+ end
+ end
+ return nil
+end
+
+function m.getDocTypeUnitName(status, unit)
+ local typeName
+ if unit.type == 'doc.type.name' then
+ typeName = unit[1]
+ elseif unit.type == 'doc.type.function' then
+ typeName = 'function'
+ elseif unit.type == 'doc.type.array' then
+ typeName = m.getDocTypeUnitName(status, unit.node) .. '[]'
+ elseif unit.type == 'doc.type.table' then
+ typeName = ('%s<%s, %s>'):format(
+ m.getDocTypeUnitName(status, unit.node),
+ m.viewInferType(m.getDocTypeNames(status, unit.key)),
+ m.viewInferType(m.getDocTypeNames(status, unit.value))
+ )
+ end
+ if unit.typeGeneric then
+ typeName = ('<%s>'):format(typeName)
+ end
+ return typeName
+end
+
+function m.getDocTypeNames(status, doc)
+ local results = {}
+ if not doc then
+ return results
+ end
+ for _, unit in ipairs(doc.types) do
+ local alias = getDocAliasExtends(status, unit)
+ if alias then
+ local aliasResults = m.getDocTypeNames(status, alias)
+ for _, res in ipairs(aliasResults) do
+ results[#results+1] = res
+ end
+ else
+ local typeName = m.getDocTypeUnitName(status, unit)
+ results[#results+1] = {
+ type = typeName,
+ source = unit,
+ level = 100,
+ }
+ end
+ end
+ for _, enum in ipairs(doc.enums) do
+ results[#results+1] = {
+ type = enum[1],
+ source = enum,
+ level = 100,
+ }
+ end
+ for _, resume in ipairs(doc.resumes) do
+ if not resume.additional then
+ results[#results+1] = {
+ type = resume[1],
+ source = resume,
+ level = 100,
+ }
+ end
+ end
+ return results
+end
+
+function m.inferCheckDoc(status, source)
+ if source.type == 'doc.class.name' then
+ status.results[#status.results+1] = {
+ type = source[1],
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ if source.type == 'doc.class' then
+ status.results[#status.results+1] = {
+ type = source.class[1],
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ if source.type == 'doc.type' then
+ local results = m.getDocTypeNames(status, source)
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+ end
+ if source.type == 'doc.type.function'
+ or source.type == 'doc.type.table'
+ or source.type == 'doc.type.array' then
+ local typeName = m.getDocTypeUnitName(status, source)
+ status.results[#status.results+1] = {
+ type = typeName,
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ if source.type == 'doc.field' then
+ local results = m.getDocTypeNames(status, source.extends)
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+ end
+ if source.type == 'doc.alias.name' then
+ local results = m.getDocTypeNames(status, m.getDocState(source).extends)
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+ end
+end
+
+function m.getVarargDocType(status, source)
+ local func = m.getParentFunction(source)
+ if not func then
+ return
+ end
+ if not func.args then
+ return
+ end
+ for _, arg in ipairs(func.args) do
+ if arg.type == '...' then
+ if arg.bindDocs then
+ for _, doc in ipairs(arg.bindDocs) do
+ if doc.type == 'doc.vararg' then
+ return m.getDocTypeNames(status, doc.vararg)
+ end
+ end
+ end
+ end
+ end
+end
+
+function m.inferCheckUpDocOfVararg(status, source)
+ if not source.vararg then
+ return
+ end
+ local results = m.getVarargDocType(status, source)
+ if not results then
+ return
+ end
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+end
+
+function m.inferCheckUpDoc(status, source)
+ if m.inferCheckUpDocOfVararg(status, source) then
+ return true
+ end
+ local parent = source.parent
+ if parent then
+ if parent.type == 'local'
+ or parent.type == 'setlocal'
+ or parent.type == 'setglobal' then
+ source = parent
+ end
+ if parent.type == 'setfield'
+ or parent.type == 'tablefield' then
+ if parent.field == source
+ or parent.value == source then
+ source = parent
+ end
+ end
+ if parent.type == 'setmethod' then
+ if parent.method == source
+ or parent.value == source then
+ source = parent
+ end
+ end
+ if parent.type == 'setindex'
+ or parent.type == 'tableindex' then
+ if parent.index == source
+ or parent.value == source then
+ source = parent
+ end
+ end
+ end
+ local binds = source.bindDocs
+ if not binds then
+ return
+ end
+ status.results = {}
+ for i = #binds, 1, -1 do
+ local doc = binds[i]
+ if doc.type == 'doc.class' then
+ status.results[#status.results+1] = {
+ type = doc.class[1],
+ source = doc,
+ level = 100,
+ }
+ -- ---@class Class
+ -- local x = { field = 1 }
+ -- 这种情况下,将字面量表接受为Class的定义
+ if source.value and source.value.type == 'table' then
+ status.results[#status.results+1] = {
+ type = source.value.type,
+ source = source.value,
+ level = 100,
+ }
+ end
+ return true
+ elseif doc.type == 'doc.type' then
+ local results = m.getDocTypeNames(status, doc)
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+ elseif doc.type == 'doc.param' then
+ -- function (x) 的情况
+ if source.type == 'local'
+ and m.getKeyName(source) == doc.param[1] then
+ if source.parent.type == 'funcargs'
+ or source.parent.type == 'in'
+ or source.parent.type == 'loop' then
+ local results = m.getDocTypeNames(status, doc.extends)
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+ end
+ end
+ end
+ end
+end
+
+function m.inferCheckUnary(status, source)
+ if source.type ~= 'unary' then
+ return
+ end
+ local op = source.op
+ if op.type == 'not' then
+ local checkTrue = m.checkTrue(status, source[1])
+ local value = nil
+ if checkTrue == true then
+ value = false
+ elseif checkTrue == false then
+ value = true
+ end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = value,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '#' then
+ status.results = m.allocInfer {
+ type = 'integer',
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '~' then
+ local l = m.getInferLiteral(status, source[1], 'integer')
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = l and ~l or nil,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '-' then
+ local v = m.getInferLiteral(status, source[1], 'integer')
+ if v then
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = - v,
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ v = m.getInferLiteral(status, source[1], 'number')
+ status.results = m.allocInfer {
+ type = 'number',
+ value = v and -v or nil,
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+end
+
+local function mathCheck(status, a, b)
+ local v1 = m.getInferLiteral(status, a, 'integer')
+ 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')
+ and not m.hasType(status, b, 'number')
+ return int and 'integer' or 'number', v1, v2
+end
+
+function m.inferCheckBinary(status, source)
+ if source.type ~= 'binary' then
+ return
+ end
+ local op = source.op
+ if op.type == 'and' then
+ local isTrue = m.checkTrue(status, source[1])
+ if isTrue == true then
+ m.searchInfer(status, source[2])
+ return true
+ elseif isTrue == false then
+ m.searchInfer(status, source[1])
+ return true
+ else
+ m.searchInfer(status, source[1])
+ m.searchInfer(status, source[2])
+ return true
+ end
+ elseif op.type == 'or' then
+ local isTrue = m.checkTrue(status, source[1])
+ if isTrue == true then
+ m.searchInfer(status, source[1])
+ return true
+ elseif isTrue == false then
+ m.searchInfer(status, source[2])
+ return true
+ else
+ m.searchInfer(status, source[1])
+ m.searchInfer(status, source[2])
+ return true
+ end
+ elseif op.type == '==' then
+ local value = m.isSameValue(status, source[1], source[2])
+ if value ~= nil then
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = value,
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ --local isSame = m.isSameDef(status, source[1], source[2])
+ --if isSame == true then
+ -- value = true
+ --else
+ -- value = nil
+ --end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = value,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '~=' then
+ local value = m.isSameValue(status, source[1], source[2])
+ if value ~= nil then
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = not value,
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+ --local isSame = m.isSameDef(status, source[1], source[2])
+ --if isSame == true then
+ -- value = false
+ --else
+ -- value = nil
+ --end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = value,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '<=' then
+ local v1 = m.getInferLiteral(status, source[1], 'integer')
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '>=' then
+ local v1 = m.getInferLiteral(status, source[1], 'integer')
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '<' then
+ local v1 = m.getInferLiteral(status, source[1], 'integer')
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '>' then
+ local v1 = m.getInferLiteral(status, source[1], 'integer')
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'boolean',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'integer',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'string',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '^' then
+ local v1 = m.getInferLiteral(status, source[1], 'integer')
+ 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
+ end
+ status.results = m.allocInfer {
+ type = 'number',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '/' then
+ local v1 = m.getInferLiteral(status, source[1], 'integer')
+ 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 and v2 ~= 0 then
+ v = v1 / v2
+ end
+ status.results = m.allocInfer {
+ type = 'number',
+ value = v,
+ source = source,
+ level = 100,
+ }
+ return true
+ -- 其他数学运算根据2侧的值决定,当2侧的值均为整数时返回整数
+ elseif op.type == '+' then
+ 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,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '-' then
+ 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,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '*' then
+ 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,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '%' then
+ local int, v1, v2 = mathCheck(status, source[1], source[2])
+ status.results = m.allocInfer {
+ type = int,
+ value = (v1 and v2 and v2 ~= 0) and (v1 % v2) or nil,
+ source = source,
+ level = 100,
+ }
+ return true
+ elseif op.type == '//' then
+ local int, v1, v2 = mathCheck(status, source[1], source[2])
+ status.results = m.allocInfer {
+ type = int,
+ value = (v1 and v2 and v2 ~= 0) and (v1 // v2) or nil,
+ source = source,
+ level = 100,
+ }
+ return true
+ end
+end
+
+function m.inferByDef(status, obj)
+ if not status.share.inferedDef then
+ status.share.inferedDef = {}
+ end
+ if status.share.inferedDef[obj] then
+ return
+ end
+ 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)
+ if #inferStatus.results == 0 then
+ status.results[#status.results+1] = {
+ type = 'any',
+ source = src,
+ level = 0,
+ }
+ else
+ for _, infer in ipairs(inferStatus.results) do
+ if not mark[infer.source] then
+ mark[infer.source] = true
+ status.results[#status.results+1] = infer
+ end
+ end
+ end
+ end
+end
+
+local function inferBySetOfLocal(status, source)
+ if status.share[source] then
+ return
+ end
+ status.share[source] = true
+ local newStatus = m.status(status)
+ if source.value then
+ m.searchInfer(newStatus, source.value)
+ end
+ if source.ref then
+ for _, ref in ipairs(source.ref) do
+ if ref.type == 'setlocal' then
+ break
+ end
+ m.searchInfer(newStatus, ref)
+ end
+ for _, infer in ipairs(newStatus.results) do
+ status.results[#status.results+1] = infer
+ end
+ end
+end
+
+function m.inferByLocalRef(status, source)
+ if #status.results ~= 0 then
+ return
+ end
+ if source.type == 'local' then
+ inferBySetOfLocal(status, source)
+ elseif source.type == 'setlocal'
+ or source.type == 'getlocal' then
+ inferBySetOfLocal(status, source.node)
+ end
+end
+
+function m.inferByCall(status, source)
+ if not source.parent then
+ return
+ end
+ if source.parent.type ~= 'call' then
+ return
+ end
+ if source.parent.node == source then
+ status.results[#status.results+1] = {
+ type = 'function',
+ source = source,
+ level = 10,
+ }
+ return
+ end
+end
+
+function m.inferByGetTable(status, source)
+ if source.type == 'field'
+ or source.type == 'method' then
+ source = source.parent
+ end
+ local next = source.next
+ if not next then
+ return
+ end
+ if next.type == 'getfield'
+ or next.type == 'getindex'
+ or next.type == 'setmethod'
+ or next.type == 'setfield'
+ or next.type == 'setindex' then
+ status.results[#status.results+1] = {
+ type = 'table',
+ source = source,
+ level = 10,
+ }
+ elseif next.type == 'getmethod' then
+ status.results[#status.results+1] = {
+ type = 'table',
+ source = source,
+ level = 5,
+ }
+ status.results[#status.results+1] = {
+ type = 'string',
+ source = source,
+ level = 5,
+ }
+ end
+end
+
+function m.inferByUnary(status, source)
+ local parent = source.parent
+ if not parent or parent.type ~= 'unary' then
+ return
+ end
+ local op = parent.op
+ if op.type == '#' then
+ status.results[#status.results+1] = {
+ type = 'string',
+ source = source,
+ level = 5,
+ }
+ status.results[#status.results+1] = {
+ type = 'table',
+ source = source,
+ level = 5,
+ }
+ elseif op.type == '~' then
+ status.results[#status.results+1] = {
+ type = 'integer',
+ source = source,
+ level = 10,
+ }
+ elseif op.type == '-' then
+ status.results[#status.results+1] = {
+ type = 'number',
+ source = source,
+ level = 10,
+ }
+ end
+end
+
+function m.inferByBinary(status, source)
+ local parent = source.parent
+ if not parent or parent.type ~= 'binary' then
+ return
+ end
+ local op = parent.op
+ if op.type == '<='
+ or op.type == '>='
+ or op.type == '<'
+ or op.type == '>'
+ or op.type == '^'
+ or op.type == '/'
+ or op.type == '+'
+ or op.type == '-'
+ or op.type == '*'
+ or op.type == '%' then
+ status.results[#status.results+1] = {
+ type = 'number',
+ source = source,
+ level = 10,
+ }
+ elseif op.type == '|'
+ or op.type == '~'
+ or op.type == '&'
+ or op.type == '<<'
+ or op.type == '>>'
+ -- 整数的可能性比较高
+ or op.type == '//' then
+ status.results[#status.results+1] = {
+ type = 'integer',
+ source = source,
+ level = 10,
+ }
+ elseif op.type == '..' then
+ status.results[#status.results+1] = {
+ type = 'string',
+ source = source,
+ level = 10,
+ }
+ end
+end
+
+local function mergeFunctionReturnsByDoc(status, source, index, call)
+ if not source or source.type ~= 'function' then
+ return
+ end
+ if not source.bindDocs then
+ return
+ end
+ local returns = {}
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.return' then
+ for _, rtn in ipairs(doc.returns) do
+ returns[#returns+1] = rtn
+ end
+ end
+ end
+ local rtn = returns[index]
+ if not rtn then
+ return
+ end
+ local results = m.getDocTypeNames(status, rtn)
+ if #results == 0 then
+ return
+ end
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ return true
+end
+
+local function mergeDocTypeFunctionReturns(status, source, index)
+ if not source.bindDocs then
+ return
+ end
+ for _, doc in ipairs(source.bindDocs) do
+ if doc.type == 'doc.type' then
+ for _, typeUnit in ipairs(doc.types) do
+ if typeUnit.type == 'doc.type.function' then
+ local rtn = typeUnit.returns[index]
+ if rtn then
+ local results = m.getDocTypeNames(status, rtn)
+ for _, res in ipairs(results) do
+ status.results[#status.results+1] = res
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+local function mergeFunctionReturns(status, source, index, call)
+ local returns = source.returns
+ if not returns then
+ return
+ end
+ for i = 1, #returns do
+ local rtn = returns[i]
+ if rtn[index] then
+ if rtn[index].type == 'call' then
+ if not m.checkReturnMark(status, rtn[index], true) then
+ m.inferByCallReturnAndIndex(status, rtn[index], index)
+ end
+ else
+ local newStatus = m.status(status)
+ m.searchInfer(newStatus, rtn[index])
+ if #newStatus.results == 0 then
+ status.results[#status.results+1] = {
+ type = 'any',
+ source = rtn[index],
+ level = 0,
+ }
+ else
+ for _, infer in ipairs(newStatus.results) do
+ status.results[#status.results+1] = infer
+ end
+ end
+ end
+ end
+ end
+end
+
+function m.inferByCallReturnAndIndex(status, call, index)
+ local node = call.node
+ local newStatus = m.status(status, status.interface)
+ m.searchRefs(newStatus, node, 'def')
+ local hasDocReturn
+ for _, src in ipairs(newStatus.results) do
+ if mergeDocTypeFunctionReturns(status, src, index) then
+ hasDocReturn = true
+ elseif mergeFunctionReturnsByDoc(status, src.value, index, call) then
+ hasDocReturn = true
+ end
+ end
+ if not hasDocReturn then
+ for _, src in ipairs(newStatus.results) do
+ if src.value and src.value.type == 'function' then
+ if not m.checkReturnMark(status, src.value, true) then
+ mergeFunctionReturns(status, src.value, index, call)
+ end
+ end
+ end
+ end
+end
+
+function m.inferByCallReturn(status, source)
+ if source.type == 'call' then
+ m.inferByCallReturnAndIndex(status, source, 1)
+ return
+ end
+ if source.type ~= 'select' then
+ if source.value and source.value.type == 'select' then
+ source = source.value
+ else
+ return
+ end
+ end
+ if not source.vararg or source.vararg.type ~= 'call' then
+ return
+ end
+ m.inferByCallReturnAndIndex(status, source.vararg, source.index)
+end
+
+function m.inferByPCallReturn(status, source)
+ if source.type ~= 'select' then
+ if source.value and source.value.type == 'select' then
+ source = source.value
+ else
+ return
+ end
+ end
+ local call = source.vararg
+ 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
+ if specialName == 'pcall' then
+ func = call.args[1]
+ index = source.index - 1
+ elseif specialName == 'xpcall' then
+ func = call.args[1]
+ index = source.index - 2
+ else
+ return
+ end
+ local newStatus = m.status(status, status.interface)
+ m.searchRefs(newStatus, func, 'def')
+ for _, src in ipairs(newStatus.results) do
+ if src.value and src.value.type == 'function' then
+ mergeFunctionReturns(status, src.value, index)
+ end
+ end
+end
+
+function m.cleanInfers(infers, obj)
+ -- kick lower level infers
+ local level = 0
+ if obj.type ~= 'select' then
+ for i = 1, #infers do
+ local infer = infers[i]
+ if infer.level > level then
+ level = infer.level
+ end
+ end
+ end
+ -- merge infers
+ local mark = {}
+ for i = #infers, 1, -1 do
+ local infer = infers[i]
+ if infer.level < level then
+ infers[i] = infers[#infers]
+ infers[#infers] = nil
+ goto CONTINUE
+ end
+ local key = ('%p'):format(infer.type, infer.source)
+ if mark[key] then
+ infers[i] = infers[#infers]
+ infers[#infers] = nil
+ else
+ mark[key] = true
+ end
+ ::CONTINUE::
+ end
+ -- kick doc.generic
+ if #infers > 1 then
+ for i = #infers, 1, -1 do
+ local infer = infers[i]
+ if infer.source.typeGeneric then
+ infers[i] = infers[#infers]
+ infers[#infers] = nil
+ end
+ end
+ end
+end
+
+function m.searchInfer(status, obj)
+ while obj.type == 'paren' do
+ obj = obj.exp
+ if not obj then
+ return
+ end
+ end
+ while true do
+ local value = m.getObjectValue(obj)
+ if not value or value == obj then
+ break
+ end
+ obj = value
+ end
+ if not obj then
+ return
+ end
+
+ local cache, makeCache = m.getRefCache(status, obj, 'infer')
+ if cache then
+ for i = 1, #cache do
+ status.results[#status.results+1] = cache[i]
+ end
+ return
+ end
+
+ if DEVELOP then
+ status.share.clock = status.share.clock or osClock()
+ end
+
+ if not status.share.lockInfer then
+ status.share.lockInfer = {}
+ end
+ if status.share.lockInfer[obj] then
+ return
+ end
+ status.share.lockInfer[obj] = true
+
+ local checked = m.inferCheckDoc(status, obj)
+ or m.inferCheckUpDoc(status, obj)
+ or m.inferCheckLiteral(status, obj)
+ or m.inferCheckUnary(status, obj)
+ or m.inferCheckBinary(status, obj)
+ if checked then
+ m.cleanInfers(status.results, obj)
+ if makeCache then
+ makeCache(status.results)
+ end
+ return
+ end
+
+ m.inferByLocalRef(status, obj)
+ if status.deep then
+ tracy.ZoneBeginN('inferByDef')
+ m.inferByDef(status, obj)
+ tracy.ZoneEnd()
+ end
+ --m.inferByCall(status, obj)
+ m.inferByGetTable(status, obj)
+ m.inferByUnary(status, obj)
+ m.inferByBinary(status, obj)
+ m.cleanInfers(status.results, obj)
+ if makeCache then
+ makeCache(status.results)
+ end
+end
+
+--- 请求对象的引用,包括 `a.b.c` 形式
+--- 与 `return function` 形式。
+--- 不穿透 `setmetatable` ,考虑由
+--- 业务层进行反向 def 搜索。
+function m.requestReference(obj, interface, deep)
+ local status = m.status(nil, interface, deep)
+ -- 根据 field 搜索引用
+ m.searchRefs(status, obj, 'ref')
+
+ m.searchRefsAsFunction(status, obj, 'ref')
+
+ if m.debugMode then
+ --print('count:', status.share.count)
+ end
+
+ return status.results, status.share.count
+end
+
+--- 请求对象的定义,包括 `a.b.c` 形式
+--- 与 `return function` 形式。
+--- 穿透 `setmetatable` 。
+function m.requestDefinition(obj, interface, deep)
+ local status = m.status(nil, interface, deep)
+ -- 根据 field 搜索定义
+ m.searchRefs(status, obj, 'def')
+
+ return status.results, status.share.count
+end
+
+--- 请求对象的字段
+---@param filterKey nil|string|table
+function m.requestFields(obj, interface, deep, filterKey)
+ local status = m.status(nil, interface, deep)
+
+ m.searchFields(status, obj, 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)
+ m.searchInfer(status, obj)
+
+ return status.results, status.share.count
+end
+
+function m.debugView(obj)
+ return require 'files'.position(m.getUri(obj), obj.start), m.getUri(obj)
+end
+
+return m
diff --git a/script/core/linker.lua b/script/core/linker.lua
index d7f53fa4..6002b22c 100644
--- a/script/core/linker.lua
+++ b/script/core/linker.lua
@@ -1,4 +1,5 @@
local util = require 'utility'
+local guide = require 'parser.guide'
local function getKey(source)
if source.type == 'local' then
@@ -33,6 +34,15 @@ local function checkGlobal(source)
return nil
end
+local function checkLocal(source)
+ if source.type == 'local'
+ or source.type == 'setlocal'
+ or source.type == 'getlocal' then
+ return true
+ end
+ return nil
+end
+
local function checkTableField(source)
if source.type == 'table' then
return true
@@ -56,35 +66,61 @@ local function checkFunctionReturn(source)
return nil
end
+local IDList = {}
local function getID(source)
if source.type == 'field'
or source.type == 'method' then
source = source.parent
end
local current = source
- local idList = {}
+ local index = 0
while true do
local id, node = getKey(current)
if not id then
break
end
- idList[#idList+1] = id
+ index = index + 1
+ IDList[index] = id
source = current
if not node then
break
end
current = node
end
- util.revertTable(idList)
- local id = table.concat(idList, '|')
+ for i = index + 1, #IDList do
+ IDList[i] = nil
+ end
+ util.revertTable(IDList)
+ local id = table.concat(IDList, '|')
return id, current
end
+---@class link
+---@field id string
+-- 语法树单元
+---@field source parser.guide.object
+-- 是否是局部变量
+---@field loc boolean
+-- 是否是全局变量
+---@field global boolean
+-- 是否是字面量表中的字段
+---@field tfield boolean
+-- 返回值,文件返回值总是0,函数返回值为第几个返回值
+---@field freturn integer
+
---创建source的链接信息
+---@param source parser.guide.object
+---@return link
local function createLink(source)
local id, node = getID(source)
+ if not id then
+ return nil
+ end
return {
- id = id,
+ id = id,
+ source = source,
+ -- 局部变量
+ loc = checkLocal(node),
-- 全局变量
global = checkGlobal(node),
-- 字面量表中的字段
@@ -94,9 +130,31 @@ local function createLink(source)
}
end
+local function insertLinker(linkers, tp, link)
+ local list = linkers[tp]
+ local id = link.id
+ if not list[id] then
+ list[id] = {}
+ end
+ list[id][#list[id]+1] = link
+ link._linker = list[id]
+end
+
local m = {}
+---根据语法树单元获取关联的link列表
+---@param source parser.guide.object
+---@return link[]?
+function m.getLinkersBySource(source)
+ if not source._link then
+ source._link = createLink(source)
+ end
+ return source._link and source._link._linker
+end
+
---获取source的链接信息
+---@param source parser.guide.object
+---@return link
function m.getLink(source)
if not source._link then
source._link = createLink(source)
@@ -104,4 +162,36 @@ function m.getLink(source)
return source._link
end
+---编译整个文件的link
+---@param source parser.guide.object
+---@return table
+function m.compileLinks(source)
+ local root = guide.getRoot(source)
+ if root._linkers then
+ return root._linkers
+ end
+ local linkers = {
+ loc = {},
+ global = {},
+ tfield = {},
+ }
+ guide.eachSource(root, function (src)
+ local link = m.getLink(src)
+ if not link then
+ return
+ end
+ if link.global then
+ insertLinker(linkers, 'global', link)
+ end
+ if link.loc then
+ insertLinker(linkers, 'loc', link)
+ end
+ if link.tfield then
+ insertLinker(linkers, 'tfield', link)
+ end
+ end)
+ root._linkers = linkers
+ return linkers
+end
+
return m
diff --git a/script/parser/guide.lua b/script/parser/guide.lua
index 2369e84f..46c32f3a 100644
--- a/script/parser/guide.lua
+++ b/script/parser/guide.lua
@@ -293,10 +293,19 @@ end
---@param obj parser.guide.object
---@return parser.guide.object
function m.getRoot(obj)
+ local source = obj
+ if source._root then
+ return source._root
+ end
for _ = 1, 1000 do
if obj.type == 'main' then
+ source._root = obj
return obj
end
+ if obj._root then
+ source._root = obj._root
+ return source._root
+ end
local parent = obj.parent
if not parent then
return nil
diff --git a/test/basic/linker.lua b/test/basic/linker.lua
index bf4e50bb..0be93e8d 100644
--- a/test/basic/linker.lua
+++ b/test/basic/linker.lua
@@ -37,24 +37,28 @@ local function TEST(script)
end
CARE['id'] = true
+CARE['loc'] = true
TEST [[
local <?x?>
]] {
- id = '9',
+ id = '9',
+ loc = true,
}
TEST [[
local x
print(<?x?>)
]] {
- id = '7',
+ id = '7',
+ loc = true,
}
TEST [[
local x
<?x?> = 1
]] {
- id = '7',
+ id = '7',
+ loc = true,
}
CARE['global'] = true
@@ -76,14 +80,16 @@ TEST [[
local x
print(x.y.<?z?>)
]] {
- id = '7|"y"|"z"',
+ id = '7|"y"|"z"',
+ loc = true,
}
TEST [[
local x
function x:<?f?>() end
]] {
- id = '7|"f"',
+ id = '7|"f"',
+ loc = true,
}
TEST [[
diff --git a/test/basic/linker.txt b/test/basic/linker.txt
new file mode 100644
index 00000000..18176369
--- /dev/null
+++ b/test/basic/linker.txt
@@ -0,0 +1,16 @@
+ast -> linkers = {
+ globals = {
+ ['"X"|"Y"|"Z"'] = {src1, src2, src3},
+ ['"X"|"Y"'] = {src4, src5, src6},
+ ['"X"'] = {src7, src8, src9},
+ },
+ locals = {
+ ['7'] = {src10},
+ ['7|"x"'] = {src11},
+ },
+ tfield = {
+ ['11|"k"'] = {src12},
+ },
+}
+
+