diff --git a/changelog.md b/changelog.md index daefdb5ae..b97df9f56 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +* `FIX` Rename type names referenced by `---@see`, including renaming from the annotation [#3470](https://github.com/LuaLS/lua-language-server/issues/3470) ## 3.19.2 `2026-10-13` diff --git a/script/core/rename.lua b/script/core/rename.lua index 2473bc702..07211ff0c 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -235,6 +235,22 @@ local function ofLabel(source, newname, callback) end end +local function getSeeType(source) + local globalVar = vm.getGlobal('type', source[1]) + if not globalVar then + return + end + for _, target in ipairs(globalVar:getSets(guide.getUri(source))) do + if target.type == 'doc.class' then + return target.class + elseif target.type == 'doc.alias' then + return target.alias + elseif target.type == 'doc.enum' then + return target.enum + end + end +end + ---@async local function ofDocTypeName(source, newname, callback) local oldname = source[1] @@ -243,7 +259,9 @@ local function ofDocTypeName(source, newname, callback) return end local uri = guide.getUri(source) + local declarations = {} for _, doc in ipairs(globalVar:getSets(uri)) do + declarations[doc] = true if doc.type == 'doc.class' then callback(doc, doc.class.start, doc.class.finish, newname) end @@ -261,6 +279,23 @@ local function ofDocTypeName(source, newname, callback) callback(doc, doc.start, doc.finish, newname) end end + for refUri in files.eachFile(uri) do + local text = files.getText(refUri) + if text and text:find(oldname, 1, true) then + local state = files.getState(refUri) + if state then + guide.eachSourceType(state.ast, 'doc.see.name', function (doc) + if doc[1] ~= oldname then + return + end + local target = getSeeType(doc) + if target and declarations[target.parent] then + callback(doc, doc.start, doc.finish, newname) + end + end) + end + end + end end local function ofDocParamName(source, newname, callback) @@ -283,7 +318,12 @@ end ---@async local function rename(source, newname, callback) - if source.type == 'label' + if source.type == 'doc.see.name' then + local target = getSeeType(source) + if target then + return ofDocTypeName(target, newname, callback) + end + elseif source.type == 'label' or source.type == 'goto' then return ofLabel(source, newname, callback) elseif source.type == 'local' then @@ -325,7 +365,11 @@ local function rename(source, newname, callback) end local function prepareRename(source) - if source.type == 'label' + if source.type == 'doc.see.name' then + if getSeeType(source) then + return source, source[1] + end + elseif source.type == 'label' or source.type == 'goto' or source.type == 'local' or source.type == 'setlocal' @@ -384,6 +428,7 @@ local accept = { ['doc.enum.name'] = true, ['doc.field.name'] = true, ['doc.extends.name'] = true, + ['doc.see.name'] = true, } local m = {} diff --git a/test/rename/init.lua b/test/rename/init.lua index dcb5f9e50..a611b6bb0 100644 --- a/test/rename/init.lua +++ b/test/rename/init.lua @@ -246,3 +246,121 @@ TEST ('A', 'C') [[ ]] [[ ---@class B: C ]] + +for _, declaration in ipairs { + '---@class OldName', + '---@alias OldName string', + '---@enum OldName', +} do + local renamed = declaration:gsub('OldName', 'NewName') + TEST ('OldName', 'NewName') (declaration .. [[ + +---@see OldName +---@type OldName +local value +OldName = 1 +print(OldName) +]]) (renamed .. [[ + +---@see NewName +---@type NewName +local value +OldName = 1 +print(OldName) +]]) + + TEST ('OldName', 'NewName') ([[ +---@see OldName +]] .. declaration .. [[ + +---@type OldName +local value +OldName = 1 +print(OldName) +]]) ([[ +---@see NewName +]] .. renamed .. [[ + +---@type NewName +local value +OldName = 1 +print(OldName) +]]) +end + +TEST ('Missing', 'NewName') [[ +---@see Missing +]] [[ +---@see Missing +]] + +TEST ('OldName', 'NewName') [[ +---@see OldName +OldName = 1 +]] [[ +---@see OldName +OldName = 1 +]] + +TEST ('RepeatedType', 'RenamedType') ( + '---@class RepeatedType\n' + .. ('---@see RepeatedType\n'):rep(64) + .. 'RepeatedType = 1\nprint(RepeatedType)\n' +) ( + '---@class RenamedType\n' + .. ('---@see RenamedType\n'):rep(64) + .. 'RepeatedType = 1\nprint(RepeatedType)\n' +) + +do + local scope = require 'workspace.scope' + local folder = scope.createFolder(TESTURI .. '.rename-a') + local otherFolder = scope.createFolder(TESTURI .. '.rename-b') + local classUri = folder.uri .. '/class.lua' + local uri = folder.uri .. '/see.lua' + local otherUri = otherFolder.uri .. '/other.lua' + local classScript = '---@class OldName\n' + local seeScript = '---@see OldName\nOldName = 1\nprint(OldName)\n' + local _ = function () + files.remove(classUri) + files.remove(uri) + files.remove(otherUri) + folder:remove() + otherFolder:remove() + end + files.setText(classUri, classScript) + files.setText(uri, seeScript) + files.setText(otherUri, classScript .. seeScript) + for _, sourceUri in ipairs { classUri, uri } do + local state = files.getState(sourceUri) + local script = files.getText(sourceUri) + local offset = assert(script:find('OldName', 1, true)) + local position = guide.offsetToPosition(state, offset) + local prepared = assert(core.prepareRename(sourceUri, position)) + assert(prepared.text == 'OldName') + local positions = assert(core.rename(sourceUri, position, 'NewName')) + assert(#positions == 2) + local found = {} + for _, edit in ipairs(positions) do + local editState = files.getState(edit.uri) + local editScript = files.getText(edit.uri) + local editOffset = assert(editScript:find('OldName', 1, true)) - 1 + assert(edit.start == guide.offsetToPosition(editState, editOffset)) + assert(edit.finish == guide.offsetToPosition(editState, editOffset + #'OldName')) + assert(edit.text == 'NewName') + found[edit.uri] = true + end + assert(found[classUri] and found[uri]) + end +end + +for _, script in ipairs { + '---@see Missing\n', + '---@see OldName\nOldName = 1\n', +} do + files.setText(TESTURI, script) + local state = files.getState(TESTURI) + local position = guide.offsetToPosition(state, #'---@see ') + assert(core.prepareRename(TESTURI, position) == nil) + files.remove(TESTURI) +end