From eb6165ea4cafe386d8aa38f7ad33a40e14d404fe Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 23 Jun 2022 22:25:30 +0700 Subject: [PATCH 1/4] enable autocomplete of array enum --- script/core/completion/completion.lua | 90 +++++++++++++++++---------- script/parser/guide.lua | 1 + script/vm/compiler.lua | 15 ++++- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 243a9a760..b8e863cca 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -56,6 +56,7 @@ local function trim(str) end local function findNearestSource(state, position) + ---@type parser.object local source guide.eachSourceContain(state.ast, position, function (src) source = src @@ -1132,24 +1133,34 @@ local function cleanEnums(enums, source) return enums end -local function checkTypingEnum(state, position, defs, str, results) +---@return boolean +local function insertEnum(state, src, enums, isInArray) + if src.type == 'doc.type.string' + or src.type == 'doc.type.integer' + or src.type == 'doc.type.boolean' then + ---@cast src parser.object + enums[#enums+1] = { + label = vm.viewObject(src, state.uri), + description = src.comment, + kind = define.CompletionItemKind.EnumMember, + } + elseif src.type == 'doc.type.code' then + enums[#enums+1] = { + label = src[1], + description = src.comment, + kind = define.CompletionItemKind.EnumMember, + } + elseif isInArray and src.type == 'doc.type.array' then + for i, d in ipairs(vm.getDefs(src.node)) do + insertEnum(state, d, enums, isInArray) + end + end +end + +local function checkTypingEnum(state, position, defs, str, results, isInArray) local enums = {} for _, def in ipairs(defs) do - if def.type == 'doc.type.string' - or def.type == 'doc.type.integer' - or def.type == 'doc.type.boolean' then - enums[#enums+1] = { - label = vm.viewObject(def, state.uri), - description = def.comment and def.comment.text, - kind = define.CompletionItemKind.EnumMember, - } - elseif def.type == 'doc.type.code' then - enums[#enums+1] = { - label = def[1], - description = def.comment and def.comment.text, - kind = define.CompletionItemKind.EnumMember, - } - end + insertEnum(state, def, enums, isInArray) end cleanEnums(enums, str) for _, res in ipairs(enums) do @@ -1157,7 +1168,7 @@ local function checkTypingEnum(state, position, defs, str, results) end end -local function checkEqualEnumLeft(state, position, source, results) +local function checkEqualEnumLeft(state, position, source, results, isInArray) if not source then return end @@ -1167,7 +1178,7 @@ local function checkEqualEnumLeft(state, position, source, results) end end) local defs = vm.getDefs(source) - checkTypingEnum(state, position, defs, str, results) + checkTypingEnum(state, position, defs, str, results, isInArray) end local function checkEqualEnum(state, position, results) @@ -1211,9 +1222,14 @@ local function checkEqualEnumInString(state, position, results) end checkEqualEnumLeft(state, position, parent[1], results) end + if (parent.type == 'tableexp') then + checkEqualEnumLeft(state, position, parent.parent.parent, results, true) + return + end if parent.type == 'local' then checkEqualEnumLeft(state, position, parent, results) end + if parent.type == 'setlocal' or parent.type == 'setglobal' or parent.type == 'setfield' @@ -1435,24 +1451,10 @@ local function tryCallArg(state, position, results) if not node then return end + local enums = {} for src in node:eachObject() do - if src.type == 'doc.type.string' - or src.type == 'doc.type.integer' - or src.type == 'doc.type.boolean' then - ---@cast src parser.object - enums[#enums+1] = { - label = vm.viewObject(src, state.uri), - description = src.comment, - kind = define.CompletionItemKind.EnumMember, - } - elseif src.type == 'doc.type.code' then - enums[#enums+1] = { - label = src[1], - description = src.comment, - kind = define.CompletionItemKind.EnumMember, - } - end + insertEnum(state, src, enums, arg and arg.type == 'table') if src.type == 'doc.type.function' then ---@cast src parser.object local insertText = buildInsertDocFunction(src) @@ -1496,6 +1498,7 @@ local function tryTable(state, position, results) if source.type ~= 'table' then tbl = source.parent end + local defs = vm.getFields(tbl) for _, field in ipairs(defs) do local name = guide.getKeyName(field) @@ -1507,6 +1510,24 @@ local function tryTable(state, position, results) checkTableLiteralField(state, position, tbl, fields, results) end +local function tryArray(state, position, results) + local source = findNearestSource(state, position) + if not source then + return + end + if source.type ~= 'table' and (not source.parent or source.parent.type ~= 'table') then + return + end + local mark = {} + local fields = {} + local tbl = source + if source.type ~= 'table' then + tbl = source.parent + end + -- { } inside when enum + checkEqualEnumLeft(state, position, tbl, results, true) +end + local function getComment(state, position) local offset = guide.positionToOffset(state, position) local symbolOffset = lookBackward.findAnyOffset(state.lua, offset) @@ -2001,6 +2022,7 @@ local function tryCompletions(state, position, triggerCharacter, results) trySpecial(state, position, results) tryCallArg(state, position, results) tryTable(state, position, results) + tryArray(state, position, results) tryWord(state, position, triggerCharacter, results) tryIndex(state, position, results) trySymbol(state, position, results) diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 56239fb1d..c05c82550 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -889,6 +889,7 @@ local isSetMap = { ['doc.alias.name'] = true, ['doc.field.name'] = true, ['doc.type.field'] = true, + ['doc.type.array'] = true, } function m.isSet(source) local tp = source.type diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index ad3295f7c..d0063d001 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -805,7 +805,7 @@ local function isValidCallArgNode(source, node) return node.type == 'doc.type.function' end if source.type == 'table' then - return node.type == 'doc.type.table' + return node.type == 'doc.type.table' or node.type == 'doc.type.array' or ( node.type == 'global' and node.cate == 'type' ---@cast node vm.global @@ -1274,6 +1274,7 @@ local compilerSwitch = util.switch() or source.parent.type == 'setlocal' or source.parent.type == 'tablefield' or source.parent.type == 'tableindex' + or source.parent.type == 'tableexp' or source.parent.type == 'setfield' or source.parent.type == 'setindex' then local parentNode = vm.compileNode(source.parent) @@ -1490,6 +1491,18 @@ local compilerSwitch = util.switch() end) : case 'tableexp' : call(function (source) + if (source.parent.type == 'table') then + local node = vm.compileNode(source.parent) + for n in node:eachObject() do + if (n.type == 'global' + and n.cate == 'type') then + vm.setNode(source, vm.compileNode(n)) + elseif n.type == 'doc.type.array' then + vm.setNode(source, vm.compileNode(n.node)) + end + end + return + end vm.setNode(source, vm.compileNode(source.value)) end) : case 'function.return' From bd517b96174441ace37885353d5570f2d11e6404 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 27 Jun 2022 12:32:29 +0700 Subject: [PATCH 2/4] fix some cases where we would return emus twice or would screw up other completions --- script/core/completion/completion.lua | 3 +++ script/vm/compiler.lua | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index b8e863cca..815b00fd0 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -1524,6 +1524,9 @@ local function tryArray(state, position, results) if source.type ~= 'table' then tbl = source.parent end + if source.parent.type == 'callargs' and source.parent.parent.type == 'call' then + return + end -- { } inside when enum checkEqualEnumLeft(state, position, tbl, results, true) end diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index d0063d001..409b2f47c 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -1494,14 +1494,10 @@ local compilerSwitch = util.switch() if (source.parent.type == 'table') then local node = vm.compileNode(source.parent) for n in node:eachObject() do - if (n.type == 'global' - and n.cate == 'type') then - vm.setNode(source, vm.compileNode(n)) - elseif n.type == 'doc.type.array' then + if n.type == 'doc.type.array' then vm.setNode(source, vm.compileNode(n.node)) end end - return end vm.setNode(source, vm.compileNode(source.value)) end) From d0c242b4bc8bf39329655272e2f3922a9d559bda Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 27 Jun 2022 13:34:32 +0700 Subject: [PATCH 3/4] add unit tests for arrays --- test/completion/common.lua | 258 +++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/test/completion/common.lua b/test/completion/common.lua index 274310002..fe09dea22 100644 --- a/test/completion/common.lua +++ b/test/completion/common.lua @@ -1639,6 +1639,264 @@ f('') } } +TEST [[ +---@alias Option string | "AAA" | "BBB" | "CCC" +---@param x Option[] +function f(x) +end + +f({}) +]] +{ + { + label = '"AAA"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + }, + { + label = '"BBB"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + }, + { + label = '"CCC"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + } +} + +TEST [[ +---@alias Option string | "AAA" | "BBB" | "CCC" +---@param x Option[] +function f(x) +end + +f({""}) +]] +{ + { + label = '"AAA"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + }, + { + label = '"BBB"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + }, + { + label = '"CCC"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + } +} + +TEST [[ +---@alias Option string | "AAA" | "BBB" | "CCC" +---@param x Option[] +function f(x) +end + +f() +]] + (nil) + +TEST [[ +---@alias Option "AAA" | "BBB" | "CCC" + +---@type Option[] +local l = {} +]] +{ + { + label = '"AAA"', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"BBB"', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"CCC"', + kind = define.CompletionItemKind.EnumMember, + } +} + +TEST [[ +---@alias Option "AAA" | "BBB" | "CCC" + +---@type Option[] +local l = {""} +]] +{ + { + label = '"AAA"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + }, + { + label = '"BBB"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + }, + { + label = '"CCC"', + kind = define.CompletionItemKind.EnumMember, + textEdit = EXISTS + } +} + +TEST [[ +---@alias Option "AAA" | "BBB" | "CCC" + +---@type Option[] +local l = +]] + (nil) + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean + +---@type OptionObj[] +local l = { {} } +]] +{ + { + label = 'a', + kind = define.CompletionItemKind.Property, + }, + { + label = 'b', + kind = define.CompletionItemKind.Property, + } +} + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean + +---@type OptionObj[] +local l = { } +]] + (nil) + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean + +---@type OptionObj[] +local l = +]] + (nil) + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean +---@field children OptionObj[] + +---@type OptionObj[] +local l = { + { + a = true, + children = { {} } + } +} +]] +{ + { + label = 'a', + kind = define.CompletionItemKind.Property, + }, + { + label = 'b', + kind = define.CompletionItemKind.Property, + }, + { + label = 'children', + kind = define.CompletionItemKind.Property, + } +} + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean +---@field children OptionObj[] + +---@type OptionObj[] +local l = { + { + children = {} + } +} +]] +(nil) + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean +---@field children OptionObj[] + +---@type OptionObj[] +local l = { + { + children = + } +} +]] +(nil) + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean +---@param x OptionObj[] +function f(x) +end + +f({ {} }) +]] +{ + { + label = 'a', + kind = define.CompletionItemKind.Property, + }, + { + label = 'b', + kind = define.CompletionItemKind.Property, + } +} + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean +---@param x OptionObj[] +function f(x) +end + +f({}) +]] + (nil) + +TEST [[ +---@class OptionObj +---@field a boolean +---@field b boolean +---@param x OptionObj[] +function f(x) +end + +f() +]] + (nil) + TEST [[ ---this is ---a multi line From a824f07a2df80f66a8017fd1d6dac8c3df5cf6e2 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 27 Jun 2022 21:11:04 +0700 Subject: [PATCH 4/4] remove some unused fields and make sure doc type array gets included as a compiled node --- script/core/completion/completion.lua | 2 -- script/vm/compiler.lua | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 815b00fd0..6b63809e4 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -1518,8 +1518,6 @@ local function tryArray(state, position, results) if source.type ~= 'table' and (not source.parent or source.parent.type ~= 'table') then return end - local mark = {} - local fields = {} local tbl = source if source.type ~= 'table' then tbl = source.parent diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 409b2f47c..6238469a7 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -1285,7 +1285,7 @@ local compilerSwitch = util.switch() if not guide.isBasicType(pn.name) then vm.setNode(source, pn) end - elseif pn.type == 'doc.type.table' then + elseif pn.type == 'doc.type.table' or pn.type == 'doc.type.array' then vm.setNode(source, pn) end end