summaryrefslogtreecommitdiff
path: root/script/core
diff options
context:
space:
mode:
Diffstat (limited to 'script/core')
-rw-r--r--script/core/code-action.lua54
-rw-r--r--script/core/code-lens.lua3
-rw-r--r--script/core/command/autoRequire.lua11
-rw-r--r--script/core/command/reloadFFIMeta.lua56
-rw-r--r--script/core/completion/completion.lua122
-rw-r--r--script/core/completion/keyword.lua35
-rw-r--r--script/core/diagnostics/cast-local-type.lua3
-rw-r--r--script/core/diagnostics/helper/missing-doc-helper.lua83
-rw-r--r--script/core/diagnostics/incomplete-signature-doc.lua28
-rw-r--r--script/core/diagnostics/inject-field.lua147
-rw-r--r--script/core/diagnostics/missing-fields.lua84
-rw-r--r--script/core/diagnostics/missing-global-doc.lua75
-rw-r--r--script/core/diagnostics/missing-local-export-doc.lua52
-rw-r--r--script/core/diagnostics/name-style-check.lua35
-rw-r--r--script/core/diagnostics/param-type-mismatch.lua18
-rw-r--r--script/core/diagnostics/undefined-field.lua8
-rw-r--r--script/core/diagnostics/undefined-global.lua46
-rw-r--r--script/core/hover/description.lua87
-rw-r--r--script/core/hover/label.lua10
-rw-r--r--script/core/rename.lua47
-rw-r--r--script/core/semantic-tokens.lua36
-rw-r--r--script/core/signature.lua39
22 files changed, 864 insertions, 215 deletions
diff --git a/script/core/code-action.lua b/script/core/code-action.lua
index e226e03b..720cd4c4 100644
--- a/script/core/code-action.lua
+++ b/script/core/code-action.lua
@@ -4,6 +4,11 @@ local util = require 'utility'
local sp = require 'bee.subprocess'
local guide = require "parser.guide"
local converter = require 'proto.converter'
+local autoreq = require 'core.completion.auto-require'
+local rpath = require 'workspace.require-path'
+local furi = require 'file-uri'
+local undefined = require 'core.diagnostics.undefined-global'
+local vm = require 'vm'
---@param uri uri
---@param row integer
@@ -676,6 +681,54 @@ local function checkJsonToLua(results, uri, start, finish)
}
end
+local function findRequireTargets(visiblePaths)
+ local targets = {}
+ for _, visible in ipairs(visiblePaths) do
+ targets[#targets+1] = visible.name
+ end
+ return targets
+end
+
+local function checkMissingRequire(results, uri, start, finish)
+ local state = files.getState(uri)
+ local text = files.getText(uri)
+ if not state or not text then
+ return
+ end
+
+ local function addRequires(global, endpos)
+ autoreq.check(state, global, endpos, function(moduleFile, stemname, targetSource)
+ local visiblePaths = rpath.getVisiblePath(uri, furi.decode(moduleFile))
+ if not visiblePaths or #visiblePaths == 0 then return end
+
+ for _, target in ipairs(findRequireTargets(visiblePaths)) do
+ results[#results+1] = {
+ title = lang.script('ACTION_AUTOREQUIRE', target, global),
+ kind = 'refactor.rewrite',
+ command = {
+ title = 'autoRequire',
+ command = 'lua.autoRequire',
+ arguments = {
+ {
+ uri = guide.getUri(state.ast),
+ target = moduleFile,
+ name = global,
+ requireName = target
+ },
+ },
+ }
+ }
+ end
+ end)
+ end
+
+ guide.eachSourceBetween(state.ast, start, finish, function (source)
+ if vm.isUndefinedGlobal(source) then
+ addRequires(source[1], source.finish)
+ end
+ end)
+end
+
return function (uri, start, finish, diagnostics)
local ast = files.getState(uri)
if not ast then
@@ -688,6 +741,7 @@ return function (uri, start, finish, diagnostics)
checkSwapParams(results, uri, start, finish)
--checkExtractAsFunction(results, uri, start, finish)
checkJsonToLua(results, uri, start, finish)
+ checkMissingRequire(results, uri, start, finish)
return results
end
diff --git a/script/core/code-lens.lua b/script/core/code-lens.lua
index ed95ea6a..bc39ec86 100644
--- a/script/core/code-lens.lua
+++ b/script/core/code-lens.lua
@@ -6,7 +6,7 @@ local getRef = require 'core.reference'
local lang = require 'language'
---@class parser.state
----@field package _codeLens codeLens
+---@field package _codeLens? codeLens
---@class codeLens.resolving
---@field mode 'reference'
@@ -14,7 +14,6 @@ local lang = require 'language'
---@class codeLens.result
---@field position integer
----@field uri uri
---@field id integer
---@class codeLens
diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua
index 32911d92..a96cc918 100644
--- a/script/core/command/autoRequire.lua
+++ b/script/core/command/autoRequire.lua
@@ -135,6 +135,7 @@ return function (data)
local uri = data.uri
local target = data.target
local name = data.name
+ local requireName = data.requireName
local state = files.getState(uri)
if not state then
return
@@ -149,11 +150,13 @@ return function (data)
return #a.name < #b.name
end)
- local result = askAutoRequire(uri, visiblePaths)
- if not result then
- return
+ if not requireName then
+ requireName = askAutoRequire(uri, visiblePaths)
+ if not requireName then
+ return
+ end
end
local offset, fmt = findInsertRow(uri)
- applyAutoRequire(uri, offset, name, result, fmt)
+ applyAutoRequire(uri, offset, name, requireName, fmt)
end
diff --git a/script/core/command/reloadFFIMeta.lua b/script/core/command/reloadFFIMeta.lua
new file mode 100644
index 00000000..d00929ad
--- /dev/null
+++ b/script/core/command/reloadFFIMeta.lua
@@ -0,0 +1,56 @@
+local config = require 'config'
+local ws = require 'workspace'
+local fs = require 'bee.filesystem'
+local scope = require 'workspace.scope'
+local SDBMHash = require 'SDBMHash'
+local searchCode = require 'plugins.ffi.searchCode'
+local cdefRerence = require 'plugins.ffi.cdefRerence'
+local ffi = require 'plugins.ffi'
+
+local function createDir(uri)
+ local dir = scope.getScope(uri).uri or 'default'
+ local fileDir = fs.path(METAPATH) / ('%08x'):format(SDBMHash():hash(dir))
+ if fs.exists(fileDir) then
+ return fileDir, true
+ end
+ fs.create_directories(fileDir)
+ return fileDir
+end
+
+---@async
+return function (uri)
+ if config.get(uri, 'Lua.runtime.version') ~= 'LuaJIT' then
+ return
+ end
+
+ ws.awaitReady(uri)
+
+ local fileDir, exists = createDir(uri)
+
+ local refs = cdefRerence()
+ if not refs or #refs == 0 then
+ return
+ end
+
+ for i, v in ipairs(refs) do
+ local target_uri = v.uri
+ local codes = searchCode(refs, target_uri)
+ if not codes then
+ return
+ end
+
+ ffi.build_single(codes, fileDir, target_uri)
+ end
+
+ if not exists then
+ local client = require 'client'
+ client.setConfig {
+ {
+ key = 'Lua.workspace.library',
+ action = 'add',
+ value = tostring(fileDir),
+ uri = uri,
+ }
+ }
+ end
+end
diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua
index 0ec503de..acb3adbe 100644
--- a/script/core/completion/completion.lua
+++ b/script/core/completion/completion.lua
@@ -504,7 +504,8 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent
local kind = define.CompletionItemKind.Field
if (value.type == 'function' and not vm.isVarargFunctionWithOverloads(value))
or value.type == 'doc.type.function' then
- if oop then
+ local isMethod = value.parent.type == 'setmethod'
+ if isMethod then
kind = define.CompletionItemKind.Method
else
kind = define.CompletionItemKind.Function
@@ -512,6 +513,7 @@ local function checkFieldThen(state, name, src, word, startPos, position, parent
buildFunction(results, src, value, oop, {
label = name,
kind = kind,
+ isMethod = isMethod,
match = name:match '^[^(]+',
insertText = name:match '^[^(]+',
deprecated = vm.getDeprecated(src) and true or nil,
@@ -626,12 +628,43 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o
end
::CONTINUE::
end
+
+ local fieldResults = {}
for name, src in util.sortPairs(fields) do
if src then
- checkFieldThen(state, name, src, word, startPos, position, parent, oop, results)
+ checkFieldThen(state, name, src, word, startPos, position, parent, oop, fieldResults)
await.delay()
end
end
+
+ local scoreMap = {}
+ for i, res in ipairs(fieldResults) do
+ scoreMap[res] = i
+ end
+ table.sort(fieldResults, function (a, b)
+ local score1 = scoreMap[a]
+ local score2 = scoreMap[b]
+ if oop then
+ if not a.isMethod then
+ score1 = score1 + 10000
+ end
+ if not b.isMethod then
+ score2 = score2 + 10000
+ end
+ else
+ if a.isMethod then
+ score1 = score1 + 10000
+ end
+ if b.isMethod then
+ score2 = score2 + 10000
+ end
+ end
+ return score1 < score2
+ end)
+
+ for _, res in ipairs(fieldResults) do
+ results[#results+1] = res
+ end
end
---@async
@@ -1218,6 +1251,46 @@ local function insertDocEnum(state, pos, doc, enums)
return enums
end
+---@param state parser.state
+---@param pos integer
+---@param doc vm.node.object
+---@param enums table[]
+---@return table[]?
+local function insertDocEnumKey(state, pos, doc, enums)
+ local tbl = doc.bindSource
+ if not tbl then
+ return nil
+ end
+ local keyEnums = {}
+ for _, field in ipairs(tbl) do
+ if field.type == 'tablefield'
+ or field.type == 'tableindex' then
+ if not field.value then
+ goto CONTINUE
+ end
+ local key = guide.getKeyName(field)
+ if not key then
+ goto CONTINUE
+ end
+ enums[#enums+1] = {
+ label = ('%q'):format(key),
+ kind = define.CompletionItemKind.EnumMember,
+ id = stack(field, function (newField) ---@async
+ return {
+ detail = buildDetail(newField),
+ description = buildDesc(newField),
+ }
+ end),
+ }
+ ::CONTINUE::
+ end
+ end
+ for _, enum in ipairs(keyEnums) do
+ enums[#enums+1] = enum
+ end
+ return enums
+end
+
local function buildInsertDocFunction(doc)
local args = {}
for i, arg in ipairs(doc.args) do
@@ -1283,7 +1356,11 @@ local function insertEnum(state, pos, src, enums, isInArray, mark)
elseif src.type == 'global' and src.cate == 'type' then
for _, set in ipairs(src:getSets(state.uri)) do
if set.type == 'doc.enum' then
- insertDocEnum(state, pos, set, enums)
+ if vm.docHasAttr(set, 'key') then
+ insertDocEnumKey(state, pos, set, enums)
+ else
+ insertDocEnum(state, pos, set, enums)
+ end
end
end
end
@@ -1539,14 +1616,13 @@ local function checkTableLiteralField(state, position, tbl, fields, results)
end
end
if left then
- local hasResult = false
+ local fieldResults = {}
for _, field in ipairs(fields) do
local name = guide.getKeyName(field)
if name
and not mark[name]
and matchKey(left, tostring(name)) then
- hasResult = true
- results[#results+1] = {
+ local res = {
label = guide.getKeyName(field),
kind = define.CompletionItemKind.Property,
id = stack(field, function (newField) ---@async
@@ -1556,9 +1632,20 @@ local function checkTableLiteralField(state, position, tbl, fields, results)
}
end),
}
+ if field.optional
+ or vm.compileNode(field):isNullable() then
+ res.insertText = res.label
+ res.label = res.label.. '?'
+ end
+ fieldResults[#fieldResults+1] = res
end
end
- return hasResult
+ util.sortByScore(fieldResults, {
+ function (r) return r.insertText and 0 or 1 end,
+ util.sortCallbackOfIndex(fieldResults),
+ })
+ util.arrayMerge(results, fieldResults)
+ return #fieldResults > 0
end
end
@@ -1571,7 +1658,8 @@ local function tryCallArg(state, position, results)
if arg and arg.type == 'function' then
return
end
- local node = vm.compileCallArg({ type = 'dummyarg' }, call, argIndex)
+ ---@diagnostic disable-next-line: missing-fields
+ local node = vm.compileCallArg({ type = 'dummyarg', uri = state.uri }, call, argIndex)
if not node then
return
end
@@ -2070,7 +2158,7 @@ local function tryluaDocByErr(state, position, err, docState, results)
end
end
-local function buildluaDocOfFunction(func)
+local function buildluaDocOfFunction(func, pad)
local index = 1
local buf = {}
buf[#buf+1] = '${1:comment}'
@@ -2094,7 +2182,8 @@ local function buildluaDocOfFunction(func)
local funcArg = func.args[n]
if funcArg[1] and funcArg.type ~= 'self' then
index = index + 1
- buf[#buf+1] = ('---@param %s ${%d:%s}'):format(
+ buf[#buf+1] = ('---%s@param %s ${%d:%s}'):format(
+ pad and ' ' or '',
funcArg[1],
index,
arg
@@ -2103,7 +2192,8 @@ local function buildluaDocOfFunction(func)
end
for _, rtn in ipairs(returns) do
index = index + 1
- buf[#buf+1] = ('---@return ${%d:%s}'):format(
+ buf[#buf+1] = ('---%s@return ${%d:%s}'):format(
+ pad and ' ' or '',
index,
rtn
)
@@ -2112,7 +2202,7 @@ local function buildluaDocOfFunction(func)
return insertText
end
-local function tryluaDocOfFunction(doc, results)
+local function tryluaDocOfFunction(doc, results, pad)
if not doc.bindSource then
return
end
@@ -2134,7 +2224,7 @@ local function tryluaDocOfFunction(doc, results)
end
end
end
- local insertText = buildluaDocOfFunction(func)
+ local insertText = buildluaDocOfFunction(func, pad)
results[#results+1] = {
label = '@param;@return',
kind = define.CompletionItemKind.Snippet,
@@ -2152,9 +2242,9 @@ local function tryLuaDoc(state, position, results)
end
if doc.type == 'doc.comment' then
local line = doc.originalComment.text
- -- 尝试 ---$
- if line == '-' then
- tryluaDocOfFunction(doc, results)
+ -- 尝试 '---$' or '--- $'
+ if line == '-' or line == '- ' then
+ tryluaDocOfFunction(doc, results, line == '- ')
return
end
-- 尝试 ---@$
diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua
index e6f50242..aa0e2148 100644
--- a/script/core/completion/keyword.lua
+++ b/script/core/completion/keyword.lua
@@ -3,6 +3,7 @@ local files = require 'files'
local guide = require 'parser.guide'
local config = require 'config'
local util = require 'utility'
+local lookback = require 'core.look-backward'
local keyWordMap = {
{ 'do', function(info, results)
@@ -372,17 +373,35 @@ end"
else
newText = '::continue::'
end
+ local additional = {}
+
+ local word = lookback.findWord(info.state.lua, guide.positionToOffset(info.state, info.start) - 1)
+ if word ~= 'goto' then
+ additional[#additional+1] = {
+ start = info.start,
+ finish = info.start,
+ newText = 'goto ',
+ }
+ end
+
+ local hasContinue = guide.eachSourceType(mostInsideBlock, 'label', function (src)
+ if src[1] == 'continue' then
+ return true
+ end
+ end)
+
+ if not hasContinue then
+ additional[#additional+1] = {
+ start = endPos,
+ finish = endPos,
+ newText = newText,
+ }
+ end
results[#results+1] = {
label = 'goto continue ..',
kind = define.CompletionItemKind.Snippet,
- insertText = "goto continue",
- additionalTextEdits = {
- {
- start = endPos,
- finish = endPos,
- newText = newText,
- }
- }
+ insertText = "continue",
+ additionalTextEdits = additional,
}
return true
end }
diff --git a/script/core/diagnostics/cast-local-type.lua b/script/core/diagnostics/cast-local-type.lua
index 1998b915..26445374 100644
--- a/script/core/diagnostics/cast-local-type.lua
+++ b/script/core/diagnostics/cast-local-type.lua
@@ -16,6 +16,9 @@ return function (uri, callback)
if not loc.ref then
return
end
+ if loc[1] == '_' then
+ return
+ end
await.delay()
local locNode = vm.compileNode(loc)
if not locNode.hasDefined then
diff --git a/script/core/diagnostics/helper/missing-doc-helper.lua b/script/core/diagnostics/helper/missing-doc-helper.lua
new file mode 100644
index 00000000..116173f2
--- /dev/null
+++ b/script/core/diagnostics/helper/missing-doc-helper.lua
@@ -0,0 +1,83 @@
+local lang = require 'language'
+
+local m = {}
+
+local function findParam(docs, param)
+ if not docs then
+ return false
+ end
+
+ for _, doc in ipairs(docs) do
+ if doc.type == 'doc.param' then
+ if doc.param[1] == param then
+ return true
+ end
+ end
+ end
+
+ return false
+end
+
+local function findReturn(docs, index)
+ if not docs then
+ return false
+ end
+
+ for _, doc in ipairs(docs) do
+ if doc.type == 'doc.return' then
+ for _, ret in ipairs(doc.returns) do
+ if ret.returnIndex == index then
+ return true
+ end
+ end
+ end
+ end
+
+ return false
+end
+
+local function checkFunction(source, callback, commentId, paramId, returnId)
+ local functionName = source.parent[1]
+ local argCount = source.args and #source.args or 0
+
+ if argCount == 0 and not source.returns and not source.bindDocs then
+ callback {
+ start = source.start,
+ finish = source.finish,
+ message = lang.script(commentId, functionName),
+ }
+ end
+
+ if argCount > 0 then
+ for _, arg in ipairs(source.args) do
+ local argName = arg[1]
+ if argName ~= 'self'
+ and argName ~= '_' then
+ if not findParam(source.bindDocs, argName) then
+ callback {
+ start = arg.start,
+ finish = arg.finish,
+ message = lang.script(paramId, argName, functionName),
+ }
+ end
+ end
+ end
+ end
+
+ if source.returns then
+ for _, ret in ipairs(source.returns) do
+ for index, expr in ipairs(ret) do
+ if not findReturn(source.bindDocs, index) then
+ callback {
+ start = expr.start,
+ finish = expr.finish,
+ message = lang.script(returnId, index, functionName),
+ }
+ end
+ end
+ end
+ end
+end
+
+m.CheckFunction = checkFunction
+return m
diff --git a/script/core/diagnostics/incomplete-signature-doc.lua b/script/core/diagnostics/incomplete-signature-doc.lua
index 91f2db74..1ffbb77a 100644
--- a/script/core/diagnostics/incomplete-signature-doc.lua
+++ b/script/core/diagnostics/incomplete-signature-doc.lua
@@ -38,6 +38,19 @@ local function findReturn(docs, index)
return false
end
+--- check if there's any signature doc (@param or @return), or just comments, @async, ...
+local function findSignatureDoc(docs)
+ if not docs then
+ return false
+ end
+ for _, doc in ipairs(docs) do
+ if doc.type == 'doc.return' or doc.type == 'doc.param' then
+ return true
+ end
+ end
+ return false
+end
+
---@async
return function (uri, callback)
local state = files.getState(uri)
@@ -57,17 +70,22 @@ return function (uri, callback)
return
end
- local functionName = source.parent[1]
+ --- don't apply rule if there is no @param or @return annotation yet
+ --- so comments and @async can be applied without the need for a full documentation
+ if(not findSignatureDoc(source.bindDocs)) then
+ return
+ end
- if #source.args > 0 then
+ if source.args and #source.args > 0 then
for _, arg in ipairs(source.args) do
local argName = arg[1]
- if argName ~= 'self' then
+ if argName ~= 'self'
+ and argName ~= '_' then
if not findParam(source.bindDocs, argName) then
callback {
start = arg.start,
finish = arg.finish,
- message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM', argName, functionName),
+ message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_PARAM', argName),
}
end
end
@@ -81,7 +99,7 @@ return function (uri, callback)
callback {
start = expr.start,
finish = expr.finish,
- message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN', index, functionName),
+ message = lang.script('DIAG_INCOMPLETE_SIGNATURE_DOC_RETURN', index),
}
end
end
diff --git a/script/core/diagnostics/inject-field.lua b/script/core/diagnostics/inject-field.lua
new file mode 100644
index 00000000..2866eef8
--- /dev/null
+++ b/script/core/diagnostics/inject-field.lua
@@ -0,0 +1,147 @@
+local files = require 'files'
+local vm = require 'vm'
+local lang = require 'language'
+local guide = require 'parser.guide'
+local await = require 'await'
+local hname = require 'core.hover.name'
+
+local skipCheckClass = {
+ ['unknown'] = true,
+ ['any'] = true,
+ ['table'] = true,
+}
+
+---@async
+return function (uri, callback)
+ local ast = files.getState(uri)
+ if not ast then
+ return
+ end
+
+ ---@async
+ local function checkInjectField(src)
+ await.delay()
+
+ local node = src.node
+ if not node then
+ return
+ end
+ local ok
+ for view in vm.getInfer(node):eachView(uri) do
+ if skipCheckClass[view] then
+ return
+ end
+ ok = true
+ end
+ if not ok then
+ return
+ end
+
+ local isExact
+ local class = vm.getDefinedClass(uri, node)
+ if class then
+ for _, doc in ipairs(class:getSets(uri)) do
+ if vm.docHasAttr(doc, 'exact') then
+ isExact = true
+ break
+ end
+ end
+ if not isExact then
+ return
+ end
+ if src.type == 'setmethod'
+ and not guide.getSelfNode(node) then
+ return
+ end
+ end
+
+ for _, def in ipairs(vm.getDefs(src)) do
+ local dnode = def.node
+ if dnode
+ and not isExact
+ and vm.getDefinedClass(uri, dnode) then
+ return
+ end
+ if def.type == 'doc.type.field' then
+ return
+ end
+ if def.type == 'doc.field' then
+ return
+ end
+ end
+
+ local howToFix = ''
+ if not isExact then
+ howToFix = lang.script('DIAG_INJECT_FIELD_FIX_CLASS', {
+ node = hname(node),
+ fix = '---@class',
+ })
+ for _, ndef in ipairs(vm.getDefs(node)) do
+ if ndef.type == 'doc.type.table' then
+ howToFix = lang.script('DIAG_INJECT_FIELD_FIX_TABLE', {
+ fix = '[any]: any',
+ })
+ break
+ end
+ end
+ end
+
+ local message = lang.script('DIAG_INJECT_FIELD', {
+ class = vm.getInfer(node):view(uri),
+ field = guide.getKeyName(src),
+ fix = howToFix,
+ })
+ if src.type == 'setfield' and src.field then
+ callback {
+ start = src.field.start,
+ finish = src.field.finish,
+ message = message,
+ }
+ elseif src.type == 'setmethod' and src.method then
+ callback {
+ start = src.method.start,
+ finish = src.method.finish,
+ message = message,
+ }
+ end
+ end
+ guide.eachSourceType(ast.ast, 'setfield', checkInjectField)
+ guide.eachSourceType(ast.ast, 'setmethod', checkInjectField)
+
+ ---@async
+ local function checkExtraTableField(src)
+ await.delay()
+
+ if not src.bindSource then
+ return
+ end
+ if not vm.docHasAttr(src, 'exact') then
+ return
+ end
+ local value = src.bindSource.value
+ if not value or value.type ~= 'table' then
+ return
+ end
+ for _, field in ipairs(value) do
+ local defs = vm.getDefs(field)
+ for _, def in ipairs(defs) do
+ if def.type == 'doc.field' then
+ goto nextField
+ end
+ end
+ local message = lang.script('DIAG_INJECT_FIELD', {
+ class = vm.getInfer(src):view(uri),
+ field = guide.getKeyName(src),
+ fix = '',
+ })
+ callback {
+ start = field.start,
+ finish = field.finish,
+ message = message,
+ }
+ ::nextField::
+ end
+ end
+
+ guide.eachSourceType(ast.ast, 'doc.class', checkExtraTableField)
+end
diff --git a/script/core/diagnostics/missing-fields.lua b/script/core/diagnostics/missing-fields.lua
new file mode 100644
index 00000000..210920fd
--- /dev/null
+++ b/script/core/diagnostics/missing-fields.lua
@@ -0,0 +1,84 @@
+local vm = require 'vm'
+local files = require 'files'
+local guide = require 'parser.guide'
+local await = require 'await'
+local lang = require 'language'
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'table', function (src)
+ await.delay()
+
+ local defs = vm.getDefs(src)
+ for _, def in ipairs(defs) do
+ if def.type == 'doc.class' and def.bindSource then
+ if guide.isInRange(def.bindSource, src.start) then
+ return
+ end
+ end
+ if def.type == 'doc.type.array'
+ or def.type == 'doc.type.table' then
+ return
+ end
+ end
+ local warnings = {}
+ for _, def in ipairs(defs) do
+ if def.type == 'doc.class' then
+ if not def.fields then
+ return
+ end
+
+ local requiresKeys = {}
+ for _, field in ipairs(def.fields) do
+ if not field.optional
+ and not vm.compileNode(field):isNullable() then
+ local key = vm.getKeyName(field)
+ if key and not requiresKeys[key] then
+ requiresKeys[key] = true
+ requiresKeys[#requiresKeys+1] = key
+ end
+ end
+ end
+
+ if #requiresKeys == 0 then
+ return
+ end
+ local myKeys = {}
+ for _, field in ipairs(src) do
+ local key = vm.getKeyName(field)
+ if key then
+ myKeys[key] = true
+ end
+ end
+
+ local missedKeys = {}
+ for _, key in ipairs(requiresKeys) do
+ if not myKeys[key] then
+ missedKeys[#missedKeys+1] = ('`%s`'):format(key)
+ end
+ end
+
+ if #missedKeys == 0 then
+ return
+ end
+
+ warnings[#warnings+1] = lang.script('DIAG_MISSING_FIELDS', def.class[1], table.concat(missedKeys, ', '))
+ end
+ end
+
+ if #warnings == 0 then
+ return
+ end
+ callback {
+ start = src.start,
+ finish = src.finish,
+ message = table.concat(warnings, '\n')
+ }
+ end)
+end
diff --git a/script/core/diagnostics/missing-global-doc.lua b/script/core/diagnostics/missing-global-doc.lua
index adcdf404..e104d232 100644
--- a/script/core/diagnostics/missing-global-doc.lua
+++ b/script/core/diagnostics/missing-global-doc.lua
@@ -1,41 +1,7 @@
local files = require 'files'
-local lang = require 'language'
local guide = require "parser.guide"
local await = require 'await'
-
-local function findParam(docs, param)
- if not docs then
- return false
- end
-
- for _, doc in ipairs(docs) do
- if doc.type == 'doc.param' then
- if doc.param[1] == param then
- return true
- end
- end
- end
-
- return false
-end
-
-local function findReturn(docs, index)
- if not docs then
- return false
- end
-
- for _, doc in ipairs(docs) do
- if doc.type == 'doc.return' then
- for _, ret in ipairs(doc.returns) do
- if ret.returnIndex == index then
- return true
- end
- end
- end
- end
-
- return false
-end
+local helper = require 'core.diagnostics.helper.missing-doc-helper'
---@async
return function (uri, callback)
@@ -56,43 +22,6 @@ return function (uri, callback)
return
end
- local functionName = source.parent[1]
-
- if #source.args == 0 and not source.returns and not source.bindDocs then
- callback {
- start = source.start,
- finish = source.finish,
- message = lang.script('DIAG_MISSING_GLOBAL_DOC_COMMENT', functionName),
- }
- end
-
- if #source.args > 0 then
- for _, arg in ipairs(source.args) do
- local argName = arg[1]
- if argName ~= 'self' then
- if not findParam(source.bindDocs, argName) then
- callback {
- start = arg.start,
- finish = arg.finish,
- message = lang.script('DIAG_MISSING_GLOBAL_DOC_PARAM', argName, functionName),
- }
- end
- end
- end
- end
-
- if source.returns then
- for _, ret in ipairs(source.returns) do
- for index, expr in ipairs(ret) do
- if not findReturn(source.bindDocs, index) then
- callback {
- start = expr.start,
- finish = expr.finish,
- message = lang.script('DIAG_MISSING_GLOBAL_DOC_RETURN', index, functionName),
- }
- end
- end
- end
- end
+ helper.CheckFunction(source, callback, 'DIAG_MISSING_GLOBAL_DOC_COMMENT', 'DIAG_MISSING_GLOBAL_DOC_PARAM', 'DIAG_MISSING_GLOBAL_DOC_RETURN')
end)
end
diff --git a/script/core/diagnostics/missing-local-export-doc.lua b/script/core/diagnostics/missing-local-export-doc.lua
new file mode 100644
index 00000000..da413961
--- /dev/null
+++ b/script/core/diagnostics/missing-local-export-doc.lua
@@ -0,0 +1,52 @@
+local files = require 'files'
+local guide = require "parser.guide"
+local await = require 'await'
+local helper = require 'core.diagnostics.helper.missing-doc-helper'
+
+---@async
+local function findSetField(ast, name, callback)
+ ---@async
+ guide.eachSourceType(ast, 'setfield', function (source)
+ await.delay()
+ if source.node[1] == name then
+ local funcPtr = source.value.node
+ if not funcPtr then
+ return
+ end
+ local func = funcPtr.value
+ if not func then
+ return
+ end
+ if funcPtr.type == 'local' and func.type == 'function' then
+ helper.CheckFunction(func, callback, 'DIAG_MISSING_LOCAL_EXPORT_DOC_COMMENT', 'DIAG_MISSING_LOCAL_EXPORT_DOC_PARAM', 'DIAG_MISSING_LOCAL_EXPORT_DOC_RETURN')
+ end
+ end
+ end)
+end
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+
+ if not state then
+ return
+ end
+
+ if not state.ast then
+ return
+ end
+
+ ---@async
+ guide.eachSourceType(state.ast, 'return', function (source)
+ await.delay()
+ --table
+
+ for i, ret in ipairs(source) do
+ if ret.type == 'getlocal' then
+ if ret.node.value and ret.node.value.type == 'table' then
+ findSetField(state.ast, ret[1], callback)
+ end
+ end
+ end
+ end)
+end
diff --git a/script/core/diagnostics/name-style-check.lua b/script/core/diagnostics/name-style-check.lua
new file mode 100644
index 00000000..4bdb068f
--- /dev/null
+++ b/script/core/diagnostics/name-style-check.lua
@@ -0,0 +1,35 @@
+local files = require 'files'
+local converter = require 'proto.converter'
+local log = require 'log'
+local nameStyle = require 'provider.name-style'
+
+
+---@async
+return function (uri, callback)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+ local text = state.originText
+
+ local status, diagnosticInfos = nameStyle.nameStyleCheck(uri, text)
+
+ if not status then
+ if diagnosticInfos ~= nil then
+ log.error(diagnosticInfos)
+ end
+
+ return
+ end
+
+ if diagnosticInfos then
+ for _, diagnosticInfo in ipairs(diagnosticInfos) do
+ callback {
+ start = converter.unpackPosition(state, diagnosticInfo.range.start),
+ finish = converter.unpackPosition(state, diagnosticInfo.range["end"]),
+ message = diagnosticInfo.message,
+ data = diagnosticInfo.data
+ }
+ end
+ end
+end
diff --git a/script/core/diagnostics/param-type-mismatch.lua b/script/core/diagnostics/param-type-mismatch.lua
index da39c5e1..acbf9c8c 100644
--- a/script/core/diagnostics/param-type-mismatch.lua
+++ b/script/core/diagnostics/param-type-mismatch.lua
@@ -32,26 +32,28 @@ end
---@param funcNode vm.node
---@param i integer
+---@param uri uri
---@return vm.node?
-local function getDefNode(funcNode, i)
+local function getDefNode(funcNode, i, uri)
local defNode = vm.createNode()
- for f in funcNode:eachObject() do
- if f.type == 'function'
- or f.type == 'doc.type.function' then
- local param = f.args and f.args[i]
+ for src in funcNode:eachObject() do
+ if src.type == 'function'
+ or src.type == 'doc.type.function' then
+ local param = src.args and src.args[i]
if param then
defNode:merge(vm.compileNode(param))
if param[1] == '...' then
defNode:addOptional()
end
-
- expandGenerics(defNode)
end
end
end
if defNode:isEmpty() then
return nil
end
+
+ expandGenerics(defNode)
+
return defNode
end
@@ -91,7 +93,7 @@ return function (uri, callback)
if not refNode then
goto CONTINUE
end
- local defNode = getDefNode(funcNode, i)
+ local defNode = getDefNode(funcNode, i, uri)
if not defNode then
goto CONTINUE
end
diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua
index a83241f5..4fd55966 100644
--- a/script/core/diagnostics/undefined-field.lua
+++ b/script/core/diagnostics/undefined-field.lua
@@ -8,13 +8,6 @@ local skipCheckClass = {
['unknown'] = true,
['any'] = true,
['table'] = true,
- ['nil'] = true,
- ['number'] = true,
- ['integer'] = true,
- ['boolean'] = true,
- ['function'] = true,
- ['userdata'] = true,
- ['lightuserdata'] = true,
}
---@async
@@ -61,5 +54,4 @@ return function (uri, callback)
end
guide.eachSourceType(ast.ast, 'getfield', checkUndefinedField)
guide.eachSourceType(ast.ast, 'getmethod', checkUndefinedField)
- guide.eachSourceType(ast.ast, 'getindex', checkUndefinedField)
end
diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua
index 179c9204..d9d94959 100644
--- a/script/core/diagnostics/undefined-global.lua
+++ b/script/core/diagnostics/undefined-global.lua
@@ -20,41 +20,21 @@ return function (uri, callback)
return
end
- local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
- local rspecial = config.get(uri, 'Lua.runtime.special')
- local cache = {}
-
-- 遍历全局变量,检查所有没有 set 模式的全局变量
guide.eachSourceType(state.ast, 'getglobal', function (src) ---@async
- local key = src[1]
- if not key then
- return
- end
- if dglobals[key] then
- return
- end
- if rspecial[key] then
- return
- end
- local node = src.node
- if node.tag ~= '_ENV' then
- return
- end
- if cache[key] == nil then
- await.delay()
- cache[key] = vm.hasGlobalSets(uri, 'variable', key)
- end
- if cache[key] then
- return
- end
- local message = lang.script('DIAG_UNDEF_GLOBAL', key)
- if requireLike[key:lower()] then
- message = ('%s(%s)'):format(message, lang.script('DIAG_REQUIRE_LIKE', key))
+ if vm.isUndefinedGlobal(src) then
+ local key = src[1]
+ local message = lang.script('DIAG_UNDEF_GLOBAL', key)
+ if requireLike[key:lower()] then
+ message = ('%s(%s)'):format(message, lang.script('DIAG_REQUIRE_LIKE', key))
+ end
+
+ callback {
+ start = src.start,
+ finish = src.finish,
+ message = message,
+ undefinedGlobal = src[1]
+ }
end
- callback {
- start = src.start,
- finish = src.finish,
- message = message,
- }
end)
end
diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua
index f5890b21..75189b06 100644
--- a/script/core/hover/description.lua
+++ b/script/core/hover/description.lua
@@ -336,7 +336,7 @@ local function tryDocFieldComment(source)
end
end
-local function getFunctionComment(source)
+local function getFunctionComment(source, raw)
local docGroup = source.bindDocs
if not docGroup then
return
@@ -356,14 +356,14 @@ local function getFunctionComment(source)
if doc.type == 'doc.comment' then
local comment = normalizeComment(doc.comment.text, uri)
md:add('md', comment)
- elseif doc.type == 'doc.param' then
+ elseif doc.type == 'doc.param' and not raw then
if doc.comment then
md:add('md', ('@*param* `%s` — %s'):format(
doc.param[1],
doc.comment.text
))
end
- elseif doc.type == 'doc.return' then
+ elseif doc.type == 'doc.return' and not raw then
if hasReturnComment then
local name = {}
for _, rtn in ipairs(doc.returns) do
@@ -401,13 +401,13 @@ local function getFunctionComment(source)
end
---@async
-local function tryDocComment(source)
+local function tryDocComment(source, raw)
local md = markdown()
if source.value and source.value.type == 'function' then
source = source.value
end
if source.type == 'function' then
- local comment = getFunctionComment(source)
+ local comment = getFunctionComment(source, raw)
md:add('md', comment)
source = source.parent
end
@@ -429,7 +429,7 @@ local function tryDocComment(source)
end
---@async
-local function tryDocOverloadToComment(source)
+local function tryDocOverloadToComment(source, raw)
if source.type ~= 'doc.type.function' then
return
end
@@ -438,7 +438,7 @@ local function tryDocOverloadToComment(source)
or not doc.bindSource then
return
end
- local md = tryDocComment(doc.bindSource)
+ local md = tryDocComment(doc.bindSource, raw)
if md then
return md
end
@@ -477,38 +477,59 @@ local function tryDocEnum(source)
if not tbl then
return
end
- local md = markdown()
- md:add('lua', '{')
- for _, field in ipairs(tbl) do
- if field.type == 'tablefield'
- or field.type == 'tableindex' then
- if not field.value then
- goto CONTINUE
- end
- local key = guide.getKeyName(field)
- if not key then
- goto CONTINUE
- end
- if field.value.type == 'integer'
- or field.value.type == 'string' then
- md:add('lua', (' %s: %s = %s,'):format(key, field.value.type, field.value[1]))
+ if vm.docHasAttr(source, 'key') then
+ local md = markdown()
+ local keys = {}
+ for _, field in ipairs(tbl) do
+ if field.type == 'tablefield'
+ or field.type == 'tableindex' then
+ if not field.value then
+ goto CONTINUE
+ end
+ local key = guide.getKeyName(field)
+ if not key then
+ goto CONTINUE
+ end
+ keys[#keys+1] = ('%q'):format(key)
+ ::CONTINUE::
end
- if field.value.type == 'binary'
- or field.value.type == 'unary' then
- local number = vm.getNumber(field.value)
- if number then
- md:add('lua', (' %s: %s = %s,'):format(key, math.tointeger(number) and 'integer' or 'number', number))
+ end
+ md:add('lua', table.concat(keys, ' | '))
+ return md:string()
+ else
+ local md = markdown()
+ md:add('lua', '{')
+ for _, field in ipairs(tbl) do
+ if field.type == 'tablefield'
+ or field.type == 'tableindex' then
+ if not field.value then
+ goto CONTINUE
+ end
+ local key = guide.getKeyName(field)
+ if not key then
+ goto CONTINUE
+ end
+ if field.value.type == 'integer'
+ or field.value.type == 'string' then
+ md:add('lua', (' %s: %s = %s,'):format(key, field.value.type, field.value[1]))
+ end
+ if field.value.type == 'binary'
+ or field.value.type == 'unary' then
+ local number = vm.getNumber(field.value)
+ if number then
+ md:add('lua', (' %s: %s = %s,'):format(key, math.tointeger(number) and 'integer' or 'number', number))
+ end
end
+ ::CONTINUE::
end
- ::CONTINUE::
end
+ md:add('lua', '}')
+ return md:string()
end
- md:add('lua', '}')
- return md:string()
end
---@async
-return function (source)
+return function (source, raw)
if source.type == 'string' then
return asString(source)
end
@@ -518,10 +539,10 @@ return function (source)
if source.type == 'field' then
source = source.parent
end
- return tryDocOverloadToComment(source)
+ return tryDocOverloadToComment(source, raw)
or tryDocFieldComment(source)
or tyrDocParamComment(source)
- or tryDocComment(source)
+ or tryDocComment(source, raw)
or tryDocClassComment(source)
or tryDocModule(source)
or tryDocEnum(source)
diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua
index 6ce4dde9..62e51927 100644
--- a/script/core/hover/label.lua
+++ b/script/core/hover/label.lua
@@ -134,7 +134,7 @@ local function asField(source)
end
local function asDocFieldName(source)
- local name = source.field[1]
+ local name = vm.viewKey(source, guide.getUri(source)) or '?'
local class
for _, doc in ipairs(source.bindGroup) do
if doc.type == 'doc.class' then
@@ -143,10 +143,12 @@ local function asDocFieldName(source)
end
end
local view = vm.getInfer(source.extends):view(guide.getUri(source))
- if not class then
- return ('(field) ?.%s: %s'):format(name, view)
+ local className = class and class.class[1] or '?'
+ if name:match(guide.namePatternFull) then
+ return ('(field) %s.%s: %s'):format(className, name, view)
+ else
+ return ('(field) %s%s: %s'):format(className, name, view)
end
- return ('(field) %s.%s: %s'):format(class.class[1], name, view)
end
local function asString(source)
diff --git a/script/core/rename.lua b/script/core/rename.lua
index 507def20..534a972a 100644
--- a/script/core/rename.lua
+++ b/script/core/rename.lua
@@ -3,42 +3,59 @@ local vm = require 'vm'
local util = require 'utility'
local findSource = require 'core.find-source'
local guide = require 'parser.guide'
+local config = require 'config'
local Forcing
+---@param str string
+---@return string
local function trim(str)
return str:match '^%s*(%S+)%s*$'
end
-local function isValidName(str)
+---@param uri uri
+---@param str string
+---@return boolean
+local function isValidName(uri, str)
if not str then
return false
end
- return str:match '^[%a_][%w_]*$'
+ local allowUnicode = config.get(uri, 'Lua.runtime.unicodeName')
+ if allowUnicode then
+ return str:match '^[%a_\x80-\xff][%w_\x80-\xff]*$'
+ else
+ return str:match '^[%a_][%w_]*$'
+ end
end
-local function isValidGlobal(str)
+---@param uri uri
+---@param str string
+---@return boolean
+local function isValidGlobal(uri, str)
if not str then
return false
end
for s in str:gmatch '[^%.]*' do
- if not isValidName(trim(s)) then
+ if not isValidName(uri, trim(s)) then
return false
end
end
return true
end
-local function isValidFunctionName(str)
- if isValidGlobal(str) then
+---@param uri uri
+---@param str string
+---@return boolean
+local function isValidFunctionName(uri, str)
+ if isValidGlobal(uri, str) then
return true
end
local offset = str:find(':', 1, true)
if not offset then
return false
end
- return isValidGlobal(trim(str:sub(1, offset-1)))
- and isValidName(trim(str:sub(offset+1)))
+ return isValidGlobal(uri, trim(str:sub(1, offset-1)))
+ and isValidName(uri, trim(str:sub(offset+1)))
end
local function isFunctionGlobalName(source)
@@ -54,7 +71,7 @@ local function isFunctionGlobalName(source)
end
local function renameLocal(source, newname, callback)
- if isValidName(newname) then
+ if isValidName(guide.getUri(source), newname) then
callback(source, source.start, source.finish, newname)
return
end
@@ -62,7 +79,7 @@ local function renameLocal(source, newname, callback)
end
local function renameField(source, newname, callback)
- if isValidName(newname) then
+ if isValidName(guide.getUri(source), newname) then
callback(source, source.start, source.finish, newname)
return true
end
@@ -108,11 +125,11 @@ local function renameField(source, newname, callback)
end
local function renameGlobal(source, newname, callback)
- if isValidGlobal(newname) then
+ if isValidGlobal(guide.getUri(source), newname) then
callback(source, source.start, source.finish, newname)
return true
end
- if isValidFunctionName(newname) then
+ if isValidFunctionName(guide.getUri(source), newname) then
callback(source, source.start, source.finish, newname)
return true
end
@@ -397,7 +414,11 @@ function m.rename(uri, pos, newname)
return
end
mark[uid] = true
- if files.isLibrary(turi, true) then
+ if vm.isMetaFile(turi) then
+ return
+ end
+ if files.isLibrary(turi, true)
+ and not files.isLibrary(uri, true) then
return
end
results[#results+1] = {
diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua
index 4d191b69..e908ef7b 100644
--- a/script/core/semantic-tokens.lua
+++ b/script/core/semantic-tokens.lua
@@ -138,12 +138,20 @@ local Care = util.switch()
local uri = guide.getUri(loc)
-- 1. 值为函数的局部变量 | Local variable whose value is a function
if vm.getInfer(source):hasFunction(uri) then
- results[#results+1] = {
- start = source.start,
- finish = source.finish,
- type = define.TokenTypes['function'],
- modifieres = define.TokenModifiers.declaration,
- }
+ if source.type == 'local' then
+ results[#results+1] = {
+ start = source.start,
+ finish = source.finish,
+ type = define.TokenTypes['function'],
+ modifieres = define.TokenModifiers.declaration,
+ }
+ else
+ results[#results+1] = {
+ start = source.start,
+ finish = source.finish,
+ type = define.TokenTypes['function'],
+ }
+ end
return
end
-- 3. 特殊变量 | Special variableif source[1] == '_ENV' then
@@ -703,6 +711,14 @@ local Care = util.switch()
type = define.TokenTypes.namespace,
}
end)
+ : case 'doc.attr'
+ : call(function (source, options, results)
+ results[#results+1] = {
+ start = source.start,
+ finish = source.finish,
+ type = define.TokenTypes.decorator,
+ }
+ end)
---@param state table
---@param results table
@@ -866,6 +882,10 @@ return function (uri, start, finish)
local n = 0
guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async
+ -- skip virtual source
+ if source.virtual then
+ return
+ end
Care(source.type, source, options, results)
n = n + 1
if n % 100 == 0 then
@@ -874,6 +894,10 @@ return function (uri, start, finish)
end)
for _, comm in ipairs(state.comms) do
+ -- skip virtual comment
+ if comm.virtual then
+ return
+ end
if start <= comm.start and comm.finish <= finish then
local headPos = (comm.type == 'comment.short' and comm.text:match '^%-%s*[@|]()')
or (comm.type == 'comment.long' and comm.text:match '^@()')
diff --git a/script/core/signature.lua b/script/core/signature.lua
index 63b0cd0d..98018b21 100644
--- a/script/core/signature.lua
+++ b/script/core/signature.lua
@@ -89,6 +89,39 @@ local function makeOneSignature(source, oop, index)
}
end
+local function isEventNotMatch(call, src)
+ if not call.args or not src.args then
+ return false
+ end
+ local literal, index
+ for i = 1, 2 do
+ if not call.args[i] then
+ break
+ end
+ literal = guide.getLiteral(call.args[i])
+ if literal then
+ index = i
+ break
+ end
+ end
+ if not literal then
+ return false
+ end
+ local event = src.args[index]
+ if not event or event.type ~= 'doc.type.arg' then
+ return false
+ end
+ if not event.extends
+ or #event.extends.types ~= 1 then
+ return false
+ end
+ local eventLiteral = event.extends.types[1] and guide.getLiteral(event.extends.types[1])
+ if eventLiteral == nil then
+ return false
+ end
+ return eventLiteral ~= literal
+end
+
---@async
local function makeSignatures(text, call, pos)
local func = call.node
@@ -139,7 +172,8 @@ local function makeSignatures(text, call, pos)
for src in node:eachObject() do
if (src.type == 'function' and not vm.isVarargFunctionWithOverloads(src))
or src.type == 'doc.type.function' then
- if not mark[src] then
+ if not mark[src]
+ and not isEventNotMatch(call, src) then
mark[src] = true
signs[#signs+1] = makeOneSignature(src, oop, index)
end
@@ -149,7 +183,8 @@ local function makeSignatures(text, call, pos)
if set.type == 'doc.class' then
for _, overload in ipairs(set.calls) do
local f = overload.overload
- if not mark[f] then
+ if not mark[f]
+ and not isEventNotMatch(call, src) then
mark[f] = true
signs[#signs+1] = makeOneSignature(f, oop, index)
end