Skip to content

Conversation

@milesziemer
Copy link
Contributor

Adds support for 'textDocument/references', 'textDocument/rename', and
'textDocument/prepareRename'. These all work very similarly, so I
implemented them together.

This implementation only works for root shapes, not members or resource
identifiers/properties (which I call resource fields for brevity).
Originally I intended to make it work for members, but for 'complete'
member support, you really need to support resource fields, otherwise if
you tried to rename a member, it wouldn't rename the resource field it
references, and finding references wouldn't show fully accurate
information. The problem is, the relationship between members and
resource fields can be implicit, and/or determined by a number of
different traits (resourceIdentifier, property, references,
nestedProperties). And some of these traits refer to the resource
field by string name. Current KnowledgeIndex implementations don't
support going from an arbitrary member -> field bindings and back. Plus,
we can't reliably use KnowledgeIndicies because they usually operate on
a complete, valid model, whereas the language server wants to operate on
the minimum subset of a possibly invalid model.

I think it would be possible still to support members in the future,
either with some clever code in the language server or enhancements to
the smithy library, but I didn't want to block rename/references for
regular shapes on that.

Some specific implementation details to note:

  1. Rename/References throw ResponseErrorException on an unsupported
    position (like a member name), or when the rename would be invalid
    (i.e. renaming a shape from a dependency, or providing an invalid
    shape name). This is what the spec says you should do, and we should
    update other features to do the same.
  2. Rename disambiguates shapes that would be conflicting after the rename
    by using an absolute shape id, and removes conflicting use statements.
  3. Rename/References both work on references to members of the target
    shape, i.e. com.foo#Foo$bar, only if the request position is before
    the $, otherwise it assumes you're getting the reference of a
    member.
  4. Prepare returns the range of the identifier to be renamed (or throws
    an error as specified in 1.), so if you go to rename com.foo#Bar,
    the rename range will be the range of Bar.

Other notable changes:

  1. Changed DocumentId to only have distinct types for root shapes and
    members. The type will be ROOT when the cursor is in any part of
    the shape id before a member, and MEMBER only when the cursor is
    actually within the member part of the shape id. The rest of the
    other possible id types didn't seem to be useful enough to keep.
  2. Added a line field to Syntax.Node.Str, which makes it much
    more efficient to get the range of a Str or Ident.
  3. Fixed an issue in TextWithPositions when there are multiple positions
    on the same line.

Rev: Fix shape search resolution

Fixes an issue with ShapeSearch::findShape where the local namespace was
checked before imports. It was inconsistent with the way the model
loader resolves shapes, so you could end up jumping to the wrong
definition/references, or renaming the wrong shape.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Adds support for 'textDocument/references', 'textDocument/rename', and
'textDocument/prepareRename'. These all work very similarly, so I
implemented them together.

This implementation only works for root shapes, not members or resource
identifiers/properties (which I call resource fields for brevity).
Originally I intended to make it work for members, but for 'complete'
member support, you really need to support resource fields, otherwise if
you tried to rename a member, it wouldn't rename the resource field it
references, and finding references wouldn't show fully accurate
information. The problem is, the relationship between members and
resource fields can be implicit, and/or determined by a number of
different traits (`resourceIdentifier`, `property`, `references`,
`nestedProperties`). And some of these traits refer to the resource
field by string name. Current KnowledgeIndex implementations don't
support going from an arbitrary member -> field bindings and back. Plus,
we can't reliably use KnowledgeIndicies because they usually operate on
a complete, valid model, whereas the language server wants to operate on
the minimum subset of a possibly invalid model.

I think it would be possible still to support members in the future,
either with some clever code in the language server or enhancements to
the smithy library, but I didn't want to block rename/references for
regular shapes on that.

Some specific implementation details to note:
1. Rename/References throw ResponseErrorException on an unsupported
   position (like a member name), or when the rename would be invalid
   (i.e. renaming a shape from a dependency, or providing an invalid
   shape name). This is what the spec says you should do, and we should
   update other features to do the same.
2. Rename disambiguates shapes that would be conflicting after the rename
   by using an absolute shape id, and removes conflicting use statements.
3. Rename/References both work on references to members of the target
   shape, i.e. `com.foo#Foo$bar`, only if the request position is before
   the `$`, otherwise it assumes you're getting the reference of a
   member.
4. Prepare returns the range of the identifier to be renamed (or throws
   an error as specified in 1.), so if you go to rename `com.foo#Bar`,
   the rename range will be the range of `Bar`.

Other notable changes:
1. Changed `DocumentId` to only have distinct types for root shapes and
   members. The type will be `ROOT` when the cursor is in any part of
   the shape id before a member, and `MEMBER` only when the cursor is
   actually within the member part of the shape id. The rest of the
   other possible id types didn't seem to be useful enough to keep.
2. Added a `line` field to Syntax.Node.Str, which makes it _much_
   more efficient to get the range of a Str or Ident.
3. Fixed an issue in TextWithPositions when there are multiple positions
   on the same line.
Fixes an issue with ShapeSearch::findShape where the local namespace was
checked before imports. It was inconsistent with the way the model
loader resolves shapes, so you could end up jumping to the wrong
definition/references, or renaming the wrong shape.
@milesziemer milesziemer requested a review from a team as a code owner March 26, 2025 19:24
@milesziemer milesziemer requested review from sugmanue and yefrig and removed request for sugmanue March 26, 2025 19:24
Test assertions weren't working properly with windows line separators
Copy link
Contributor

@yefrig yefrig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some small comments but otherwise LGTM.

Also tested functionality by building this PR locally and using it in Neovim 0.12.

LOGGER.finest("PrepareRename");
String uri = params.getTextDocument().getUri();
ProjectAndFile projectAndFile = state.findProjectAndFile(uri);
if (projectAndFile == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice if we could extract some of this logic to a helper function but not sure if it's worth the trouble given the different return types of each request method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I agree. There's also a lot of duplicated setup code in each of the handlers, for example the ReferencesHandler.Config class which is shared with rename. I'm thinking I'll follow up with some changes to refactor all that

private static ResponseErrorException notSupported() {
var error = new ResponseError();
error.setCode(ResponseErrorCode.RequestFailed);
error.setMessage("Not supported here.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
error.setMessage("Not supported here.");
error.setMessage("References not supported here.");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

addPendingReferences();
}

private void collect(Syntax.Statement statement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if I like collect() since the function returns void and uses side effects

Comment on lines 109 to 113
var conflictingShape = ShapeSearch.findShape(sourceFile.getParse(), conflictingId, config.model())
.orElse(null);
if (conflictingShape == null) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be improved? it looks awkward lol but maybe Optional is not that flexible

* identifiers, so this class a single subclass {@link Ident}.
*/
public static sealed class Str extends Node {
final int line;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this supposed to be line number?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this lineNumber

@milesziemer milesziemer merged commit 21f0d5c into smithy-lang:main Mar 27, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants