diff --git a/src/main/java/software/amazon/smithy/lsp/language/DocumentSymbolHandler.java b/src/main/java/software/amazon/smithy/lsp/language/DocumentSymbolHandler.java index 7aa47fa0..da8d8a9c 100644 --- a/src/main/java/software/amazon/smithy/lsp/language/DocumentSymbolHandler.java +++ b/src/main/java/software/amazon/smithy/lsp/language/DocumentSymbolHandler.java @@ -5,59 +5,191 @@ package software.amazon.smithy.lsp.language; +import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; +import java.util.ListIterator; import java.util.function.Consumer; import org.eclipse.lsp4j.DocumentSymbol; -import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.jsonrpc.messages.Either; import software.amazon.smithy.lsp.document.Document; -import software.amazon.smithy.lsp.protocol.LspAdapter; import software.amazon.smithy.lsp.syntax.Syntax; public record DocumentSymbolHandler(Document document, List statements) { + // Statement types that may appear before the start of a shape's members, which + // we need to skip. + private static final EnumSet BEFORE_MEMBER_TYPES = EnumSet.of( + Syntax.Statement.Type.ForResource, + Syntax.Statement.Type.Mixins, + Syntax.Statement.Type.Block + ); + /** * @return A list of DocumentSymbol */ public List> handle() { - return statements.stream() - .mapMulti(this::addSymbols) - .toList(); + List> result = new ArrayList<>(); + // Passing around the list would make the code super noisy, and we'd have + // to do Either.forRight everywhere, so use a consumer. + addSymbols((symbol) -> result.add(Either.forRight(symbol))); + return result; + } + + private void addSymbols(Consumer consumer) { + var listIterator = statements.listIterator(); + while (listIterator.hasNext()) { + var statement = listIterator.next(); + if (statement instanceof Syntax.Statement.Namespace namespace) { + consumer.accept(namespaceSymbol(namespace)); + } else if (statement instanceof Syntax.Statement.ShapeDef shapeDef) { + var symbol = rootSymbol(shapeDef); + consumer.accept(symbol); + addMemberSymbols(listIterator, symbol); + } + } + } + + private void addMemberSymbols(ListIterator listIterator, DocumentSymbol parent) { + // We only want to collect members within the block, so we can use Block's lastMemberIndex + // to tell us when to stop. + int lastMemberIndex = 0; + while (listIterator.hasNext()) { + var statement = listIterator.next(); + + if (!BEFORE_MEMBER_TYPES.contains(statement.type())) { + // No members + listIterator.previous(); + return; + } + + if (statement instanceof Syntax.Statement.Block block) { + // Update the parent's range to cover all its members + var blockEnd = document.positionAtIndex(block.end()); + parent.getRange().setEnd(blockEnd); + lastMemberIndex = block.lastStatementIndex(); + break; + } + } + + List children = childrenSymbols(listIterator, lastMemberIndex); + if (!children.isEmpty()) { + parent.setChildren(children); + } } - private void addSymbols(Syntax.Statement statement, Consumer> consumer) { - switch (statement) { - case Syntax.Statement.TraitApplication app -> addSymbol(consumer, app.id(), SymbolKind.Class); + private List childrenSymbols(ListIterator listIterator, int lastChildIndex) { + List children = new ArrayList<>(); - case Syntax.Statement.ShapeDef def -> addSymbol(consumer, def.shapeName(), SymbolKind.Class); + while (listIterator.nextIndex() <= lastChildIndex) { + var statement = listIterator.next(); - case Syntax.Statement.EnumMemberDef def -> addSymbol(consumer, def.name(), SymbolKind.Enum); + switch (statement) { + case Syntax.Statement.MemberDef def -> children.add(memberDefSymbol(def)); - case Syntax.Statement.ElidedMemberDef def -> addSymbol(consumer, def.name(), SymbolKind.Property); + case Syntax.Statement.EnumMemberDef def -> children.add(enumMemberDefSymbol(def)); - case Syntax.Statement.MemberDef def -> { - addSymbol(consumer, def.name(), SymbolKind.Property); - if (def.target() != null) { - addSymbol(consumer, def.target(), SymbolKind.Class); + case Syntax.Statement.ElidedMemberDef def -> children.add(elidedMemberDefSymbol(def)); + + case Syntax.Statement.NodeMemberDef def -> children.add(nodeMemberDefSymbol(def)); + + case Syntax.Statement.InlineMemberDef def -> children.add(inlineMemberSymbol(listIterator, def)); + + default -> { } } - default -> { - } } + + return children; + } + + private DocumentSymbol namespaceSymbol(Syntax.Statement.Namespace namespace) { + return new DocumentSymbol( + namespace.namespace().stringValue(), + SymbolKind.Namespace, + document.rangeOf(namespace), + document.rangeOfValue(namespace.namespace()) + ); + } + + private DocumentSymbol rootSymbol(Syntax.Statement.ShapeDef shapeDef) { + return new DocumentSymbol( + shapeDef.shapeName().stringValue(), + getSymbolKind(shapeDef), + document.rangeOf(shapeDef), + document.rangeOfValue(shapeDef.shapeName()) + ); + } + + private static SymbolKind getSymbolKind(Syntax.Statement.ShapeDef shapeDef) { + return switch (shapeDef.shapeType().stringValue()) { + case "enum", "intEnum" -> SymbolKind.Enum; + case "operation", "service", "resource" -> SymbolKind.Interface; + default -> SymbolKind.Class; + }; + } + + private DocumentSymbol memberDefSymbol(Syntax.Statement.MemberDef memberDef) { + var detail = memberDef.target() == null + ? null + : memberDef.target().stringValue(); + + return new DocumentSymbol( + memberDef.name().stringValue(), + SymbolKind.Field, + document.rangeOf(memberDef), + document.rangeOfValue(memberDef.name()), + detail + ); + } + + private DocumentSymbol enumMemberDefSymbol(Syntax.Statement.EnumMemberDef enumMemberDef) { + return new DocumentSymbol( + enumMemberDef.name().stringValue(), + SymbolKind.EnumMember, + document.rangeOf(enumMemberDef), + document.rangeOfValue(enumMemberDef.name()) + ); + } + + private DocumentSymbol elidedMemberDefSymbol(Syntax.Statement.ElidedMemberDef elidedMemberDef) { + var range = document.rangeOf(elidedMemberDef); + return new DocumentSymbol( + "$" + elidedMemberDef.name().stringValue(), + SymbolKind.Field, + range, + range + ); } - private void addSymbol( - Consumer> consumer, - Syntax.Ident ident, - SymbolKind symbolKind + private DocumentSymbol nodeMemberDefSymbol(Syntax.Statement.NodeMemberDef nodeMemberDef) { + String detail = switch (nodeMemberDef.value()) { + case Syntax.Ident ident -> ident.stringValue(); + case null, default -> null; + }; + + return new DocumentSymbol( + nodeMemberDef.name().stringValue(), + SymbolKind.Property, + document.rangeOf(nodeMemberDef), + document.rangeOfValue(nodeMemberDef.name()), + detail + ); + } + + private DocumentSymbol inlineMemberSymbol( + ListIterator listIterator, + Syntax.Statement.InlineMemberDef inlineMemberDef ) { - Range range = LspAdapter.identRange(ident, document); - if (range == null) { - return; - } + var inlineSymbol = new DocumentSymbol( + inlineMemberDef.name().stringValue(), + SymbolKind.Property, + document.rangeOf(inlineMemberDef), + document.rangeOfValue(inlineMemberDef.name()) + ); - DocumentSymbol symbol = new DocumentSymbol(ident.stringValue(), symbolKind, range, range); - consumer.accept(Either.forRight(symbol)); + addMemberSymbols(listIterator, inlineSymbol); + return inlineSymbol; } } diff --git a/src/main/java/software/amazon/smithy/lsp/protocol/LspAdapter.java b/src/main/java/software/amazon/smithy/lsp/protocol/LspAdapter.java index 8335786e..41fca697 100644 --- a/src/main/java/software/amazon/smithy/lsp/protocol/LspAdapter.java +++ b/src/main/java/software/amazon/smithy/lsp/protocol/LspAdapter.java @@ -120,19 +120,7 @@ public static Range of(int startLine, int startCharacter, int endLine, int endCh * @return The range of the identifier in the given document */ public static Range identRange(Syntax.Ident ident, Document document) { - int line = document.lineOfIndex(ident.start()); - if (line < 0) { - return null; - } - - int lineStart = document.indexOfLine(line); - if (lineStart < 0) { - return null; - } - - int startCharacter = ident.start() - lineStart; - int endCharacter = ident.end() - lineStart; - return LspAdapter.lineSpan(line, startCharacter, endCharacter); + return document.rangeOfValue(ident); } /** diff --git a/src/main/java/software/amazon/smithy/lsp/syntax/Parser.java b/src/main/java/software/amazon/smithy/lsp/syntax/Parser.java index 7c9e5239..2e02cda3 100644 --- a/src/main/java/software/amazon/smithy/lsp/syntax/Parser.java +++ b/src/main/java/software/amazon/smithy/lsp/syntax/Parser.java @@ -642,21 +642,12 @@ private void operationMembers(Syntax.Statement.Block parent) { ws(); - if (isIdentStart()) { - var opMemberDef = new Syntax.Statement.MemberDef(parent, memberName); - opMemberDef.start = opMemberStart; - opMemberDef.colonPos = colonPos; - opMemberDef.target = ident(); - setEnd(opMemberDef); - addStatement(opMemberDef); - } else { - var nodeMemberDef = new Syntax.Statement.NodeMemberDef(parent, memberName); - nodeMemberDef.start = opMemberStart; - nodeMemberDef.colonPos = colonPos; - nodeMemberDef.value = parseNode(); - setEnd(nodeMemberDef); - addStatement(nodeMemberDef); - } + var nodeMemberDef = new Syntax.Statement.NodeMemberDef(parent, memberName); + nodeMemberDef.start = opMemberStart; + nodeMemberDef.colonPos = colonPos; + nodeMemberDef.value = parseNode(); + setEnd(nodeMemberDef); + addStatement(nodeMemberDef); ws(); } diff --git a/src/test/java/software/amazon/smithy/lsp/language/DocumentSymbolTest.java b/src/test/java/software/amazon/smithy/lsp/language/DocumentSymbolTest.java index 434c8612..a2cef703 100644 --- a/src/test/java/software/amazon/smithy/lsp/language/DocumentSymbolTest.java +++ b/src/test/java/software/amazon/smithy/lsp/language/DocumentSymbolTest.java @@ -6,22 +6,25 @@ package software.amazon.smithy.lsp.language; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasItems; -import static software.amazon.smithy.lsp.document.DocumentTest.safeString; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.nullValue; +import static software.amazon.smithy.lsp.LspMatchers.hasText; import java.util.ArrayList; import java.util.List; +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.SymbolKind; import org.junit.jupiter.api.Test; -import software.amazon.smithy.lsp.TestWorkspace; -import software.amazon.smithy.lsp.project.IdlFile; -import software.amazon.smithy.lsp.project.Project; -import software.amazon.smithy.lsp.project.ProjectTest; -import software.amazon.smithy.lsp.project.SmithyFile; +import software.amazon.smithy.lsp.document.Document; +import software.amazon.smithy.lsp.syntax.Syntax; public class DocumentSymbolTest { @Test public void documentSymbols() { - String model = safeString(""" + var document = Document.of(""" $version: "2" namespace com.foo @@ -31,32 +34,307 @@ public void documentSymbols() { structure Foo { @required bar: Bar + + baz: Baz + } + + operation MyOp { + input := { + foo: String + } + + output: MyOpOutput + } + + resource MyResource { + identifiers: { + myId: String + myOtherId: String + } + properties: { + myProperty: Foo + myOtherProperty: String + } + get: MyOp + operations: [ + MyOp + ] } + """); + var symbols = getDocumentSymbols(document); - structure Bar { - @myTrait("foo") - baz: Baz + assertThat(symbols, hasSize(5)); + + var nsSymbol = symbols.get(0); + assertThat(nsSymbol.getName(), equalTo("com.foo")); + assertThat(nsSymbol.getKind(), equalTo(SymbolKind.Namespace)); + assertThat(nsSymbol.getRange(), hasText(document, equalTo("namespace com.foo"))); + assertThat(nsSymbol.getSelectionRange(), hasText(document, equalTo("com.foo"))); + assertThat(nsSymbol.getDetail(), nullValue()); + assertThat(nsSymbol.getChildren(), nullValue()); + + var myTraitSymbol = symbols.get(1); + assertThat(myTraitSymbol.getName(), equalTo("myTrait")); + assertThat(myTraitSymbol.getKind(), equalTo(SymbolKind.Class)); + assertThat(myTraitSymbol.getRange(), hasText(document, equalTo("string myTrait"))); + assertThat(myTraitSymbol.getSelectionRange(), hasText(document, equalTo("myTrait"))); + assertThat(myTraitSymbol.getDetail(), nullValue()); + assertThat(myTraitSymbol.getChildren(), nullValue()); + + var fooSymbol = symbols.get(2); + assertThat(fooSymbol.getName(), equalTo("Foo")); + assertThat(fooSymbol.getKind(), equalTo(SymbolKind.Class)); + assertThat(fooSymbol.getRange(), hasText(document, allOf( + containsString("structure Foo"), + containsString("bar: Bar"), + containsString("baz: Baz") + ))); + assertThat(fooSymbol.getSelectionRange(), hasText(document, equalTo("Foo"))); + assertThat(fooSymbol.getDetail(), nullValue()); + assertThat(fooSymbol.getChildren(), hasSize(2)); + + var barMemberSymbol = fooSymbol.getChildren().get(0); + assertThat(barMemberSymbol.getName(), equalTo("bar")); + assertThat(barMemberSymbol.getKind(), equalTo(SymbolKind.Field)); + assertThat(barMemberSymbol.getRange(), hasText(document, equalTo("bar: Bar"))); + assertThat(barMemberSymbol.getSelectionRange(), hasText(document, equalTo("bar"))); + assertThat(barMemberSymbol.getDetail(), equalTo("Bar")); + assertThat(barMemberSymbol.getChildren(), nullValue()); + + var myOpSymbol = symbols.get(3); + assertThat(myOpSymbol.getName(), equalTo("MyOp")); + assertThat(myOpSymbol.getKind(), equalTo(SymbolKind.Interface)); + assertThat(myOpSymbol.getRange(), hasText(document, allOf( + containsString("operation MyOp"), + containsString("input :="), + containsString("output: MyOpOutput") + ))); + assertThat(myOpSymbol.getSelectionRange(), hasText(document, equalTo("MyOp"))); + assertThat(myOpSymbol.getChildren(), hasSize(2)); + + var myOpInputSymbol = myOpSymbol.getChildren().get(0); + assertThat(myOpInputSymbol.getName(), equalTo("input")); + assertThat(myOpInputSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myOpInputSymbol.getRange(), hasText(document, allOf( + containsString("input :="), + containsString("foo: String") + ))); + assertThat(myOpInputSymbol.getSelectionRange(), hasText(document, equalTo("input"))); + assertThat(myOpInputSymbol.getDetail(), nullValue()); + assertThat(myOpInputSymbol.getChildren(), hasSize(1)); + + var myOpInputFooMemberSymbol = myOpInputSymbol.getChildren().get(0); + assertThat(myOpInputFooMemberSymbol.getName(), equalTo("foo")); + assertThat(myOpInputFooMemberSymbol.getKind(), equalTo(SymbolKind.Field)); + assertThat(myOpInputFooMemberSymbol.getRange(), hasText(document, equalTo("foo: String"))); + assertThat(myOpInputFooMemberSymbol.getSelectionRange(), hasText(document, equalTo("foo"))); + assertThat(myOpInputFooMemberSymbol.getDetail(), equalTo("String")); + assertThat(myOpInputFooMemberSymbol.getChildren(), nullValue()); + + var myOpOutputSymbol = myOpSymbol.getChildren().get(1); + assertThat(myOpOutputSymbol.getName(), equalTo("output")); + assertThat(myOpOutputSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myOpOutputSymbol.getRange(), hasText(document, equalTo("output: MyOpOutput"))); + assertThat(myOpOutputSymbol.getSelectionRange(), hasText(document, equalTo("output"))); + assertThat(myOpOutputSymbol.getDetail(), equalTo("MyOpOutput")); + assertThat(myOpOutputSymbol.getChildren(), nullValue()); + + var myResourceSymbol = symbols.get(4); + assertThat(myResourceSymbol.getName(), equalTo("MyResource")); + assertThat(myResourceSymbol.getKind(), equalTo(SymbolKind.Interface)); + assertThat(myResourceSymbol.getRange(), hasText(document, allOf( + containsString("resource MyResource"), + containsString("myId: String"), + containsString("get: MyOp") + ))); + assertThat(myResourceSymbol.getSelectionRange(), hasText(document, equalTo("MyResource"))); + assertThat(myResourceSymbol.getDetail(), nullValue()); + assertThat(myResourceSymbol.getChildren(), hasSize(4)); + + var myResourceIdentifiersSymbol = myResourceSymbol.getChildren().get(0); + assertThat(myResourceIdentifiersSymbol.getName(), equalTo("identifiers")); + assertThat(myResourceIdentifiersSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myResourceIdentifiersSymbol.getRange(), hasText(document, allOf( + containsString("identifiers:"), + containsString("myId: String"), + containsString("myOtherId: String") + ))); + assertThat(myResourceIdentifiersSymbol.getSelectionRange(), hasText(document, equalTo("identifiers"))); + assertThat(myResourceIdentifiersSymbol.getDetail(), nullValue()); + assertThat(myResourceIdentifiersSymbol.getChildren(), nullValue()); + var myResourcePropertiesSymbol = myResourceSymbol.getChildren().get(1); + assertThat(myResourcePropertiesSymbol.getName(), equalTo("properties")); + assertThat(myResourcePropertiesSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myResourcePropertiesSymbol.getRange(), hasText(document, allOf( + containsString("properties:"), + containsString("myProperty: Foo"), + containsString("myOtherProperty: String") + ))); + assertThat(myResourcePropertiesSymbol.getSelectionRange(), hasText(document, equalTo("properties"))); + assertThat(myResourcePropertiesSymbol.getDetail(), nullValue()); + assertThat(myResourcePropertiesSymbol.getChildren(), nullValue()); + var myResourceGetSymbol = myResourceSymbol.getChildren().get(2); + assertThat(myResourceGetSymbol.getName(), equalTo("get")); + assertThat(myResourceGetSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myResourceGetSymbol.getRange(), hasText(document, equalTo("get: MyOp"))); + assertThat(myResourceGetSymbol.getSelectionRange(), hasText(document, equalTo("get"))); + assertThat(myResourceGetSymbol.getDetail(), equalTo("MyOp")); + assertThat(myResourceGetSymbol.getChildren(), nullValue()); + var myResourceOperationsSymbol = myResourceSymbol.getChildren().get(3); + assertThat(myResourceOperationsSymbol.getName(), equalTo("operations")); + assertThat(myResourceOperationsSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myResourceOperationsSymbol.getRange(), hasText(document, allOf( + containsString("operations: ["), + containsString("MyOp") + ))); + assertThat(myResourceOperationsSymbol.getSelectionRange(), hasText(document, equalTo("operations"))); + assertThat(myResourceOperationsSymbol.getDetail(), nullValue()); + assertThat(myResourceOperationsSymbol.getChildren(), nullValue()); + } + + @Test + public void handlesForResourceAndMixins() { + var document = Document.of(""" + operation MyOp for MyResource with [MyMixin] { + input: MyOpInput + output := for MyResource with [MyMixin] { + foo: String + $bar + } } + """); + var symbols = getDocumentSymbols(document); + assertThat(symbols.size(), equalTo(1)); + + var myOpSymbol = symbols.get(0); + assertThat(myOpSymbol.getName(), equalTo("MyOp")); + assertThat(myOpSymbol.getKind(), equalTo(SymbolKind.Interface)); + assertThat(myOpSymbol.getRange(), hasText(document, allOf( + containsString("operation MyOp for MyResource with [MyMixin]"), + containsString("input: MyOpInput"), + containsString("output :="), + containsString("$bar") + ))); + assertThat(myOpSymbol.getSelectionRange(), hasText(document, equalTo("MyOp"))); + assertThat(myOpSymbol.getDetail(), nullValue()); + assertThat(myOpSymbol.getChildren(), hasSize(2)); + + var myOpInputSymbol = myOpSymbol.getChildren().get(0); + assertThat(myOpInputSymbol.getName(), equalTo("input")); + assertThat(myOpInputSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myOpInputSymbol.getRange(), hasText(document, equalTo("input: MyOpInput"))); + assertThat(myOpInputSymbol.getSelectionRange(), hasText(document, equalTo("input"))); + assertThat(myOpInputSymbol.getDetail(), equalTo("MyOpInput")); + assertThat(myOpInputSymbol.getChildren(), nullValue()); + + var myOpOutputSymbol = myOpSymbol.getChildren().get(1); + assertThat(myOpOutputSymbol.getName(), equalTo("output")); + assertThat(myOpOutputSymbol.getKind(), equalTo(SymbolKind.Property)); + assertThat(myOpOutputSymbol.getRange(), hasText(document, allOf( + containsString("output := for MyResource with [MyMixin]"), + containsString("foo: String"), + containsString("$bar") + ))); + assertThat(myOpOutputSymbol.getSelectionRange(), hasText(document, equalTo("output"))); + assertThat(myOpOutputSymbol.getDetail(), nullValue()); + assertThat(myOpOutputSymbol.getChildren(), hasSize(2)); + + var fooMemberSymbol = myOpOutputSymbol.getChildren().get(0); + assertThat(fooMemberSymbol.getName(), equalTo("foo")); + assertThat(fooMemberSymbol.getKind(), equalTo(SymbolKind.Field)); + assertThat(fooMemberSymbol.getRange(), hasText(document, equalTo("foo: String"))); + assertThat(fooMemberSymbol.getSelectionRange(), hasText(document, equalTo("foo"))); + assertThat(fooMemberSymbol.getDetail(), equalTo("String")); + assertThat(fooMemberSymbol.getChildren(), nullValue()); + + var barMemberSymbol = myOpOutputSymbol.getChildren().get(1); + assertThat(barMemberSymbol.getName(), equalTo("$bar")); + assertThat(barMemberSymbol.getKind(), equalTo(SymbolKind.Field)); + assertThat(barMemberSymbol.getRange(), hasText(document, equalTo("$bar"))); + assertThat(barMemberSymbol.getSelectionRange(), hasText(document, equalTo("$bar"))); + assertThat(barMemberSymbol.getDetail(), nullValue()); + assertThat(barMemberSymbol.getChildren(), nullValue()); + } - @myTrait("abc") - integer Baz + @Test + public void enums() { + var document = Document.of(""" + enum MyEnum { + FOO + BAR = "bar" + } + + intEnum MyIntEnum { + FOO + BAR = 1 + } """); - List names = getDocumentSymbolNames(model); + var symbols = getDocumentSymbols(document); + + assertThat(symbols.size(), equalTo(2)); + var myEnumSymbol = symbols.get(0); + assertThat(myEnumSymbol.getName(), equalTo("MyEnum")); + assertThat(myEnumSymbol.getKind(), equalTo(SymbolKind.Enum)); + assertThat(myEnumSymbol.getRange(), hasText(document, allOf( + containsString("enum MyEnum"), + containsString("BAR = \"bar\"") + ))); + assertThat(myEnumSymbol.getSelectionRange(), hasText(document, equalTo("MyEnum"))); + assertThat(myEnumSymbol.getDetail(), nullValue()); + assertThat(myEnumSymbol.getChildren(), hasSize(2)); + + var myEnumFooSymbol = myEnumSymbol.getChildren().get(0); + assertThat(myEnumFooSymbol.getName(), equalTo("FOO")); + assertThat(myEnumFooSymbol.getKind(), equalTo(SymbolKind.EnumMember)); + assertThat(myEnumFooSymbol.getRange(), hasText(document, equalTo("FOO"))); + assertThat(myEnumFooSymbol.getSelectionRange(), hasText(document, equalTo("FOO"))); + assertThat(myEnumFooSymbol.getDetail(), nullValue()); + assertThat(myEnumFooSymbol.getChildren(), nullValue()); + + var myEnumBarSymbol = myEnumSymbol.getChildren().get(1); + assertThat(myEnumBarSymbol.getName(), equalTo("BAR")); + assertThat(myEnumBarSymbol.getKind(), equalTo(SymbolKind.EnumMember)); + assertThat(myEnumBarSymbol.getRange(), hasText(document, equalTo("BAR = \"bar\""))); + assertThat(myEnumBarSymbol.getSelectionRange(), hasText(document, equalTo("BAR"))); + assertThat(myEnumBarSymbol.getDetail(), nullValue()); + assertThat(myEnumBarSymbol.getChildren(), nullValue()); + + var myIntEnumSymbol = symbols.get(1); + assertThat(myIntEnumSymbol.getName(), equalTo("MyIntEnum")); + assertThat(myIntEnumSymbol.getKind(), equalTo(SymbolKind.Enum)); + assertThat(myIntEnumSymbol.getRange(), hasText(document, allOf( + containsString("intEnum MyIntEnum"), + containsString("BAR = 1") + ))); + assertThat(myIntEnumSymbol.getSelectionRange(), hasText(document, equalTo("MyIntEnum"))); + assertThat(myIntEnumSymbol.getDetail(), nullValue()); + assertThat(myIntEnumSymbol.getChildren(), hasSize(2)); + + var myIntEnumFooSymbol = myIntEnumSymbol.getChildren().get(0); + assertThat(myIntEnumFooSymbol.getName(), equalTo("FOO")); + assertThat(myIntEnumFooSymbol.getKind(), equalTo(SymbolKind.EnumMember)); + assertThat(myIntEnumFooSymbol.getRange(), hasText(document, equalTo("FOO"))); + assertThat(myIntEnumFooSymbol.getSelectionRange(), hasText(document, equalTo("FOO"))); + assertThat(myIntEnumFooSymbol.getDetail(), nullValue()); + assertThat(myIntEnumFooSymbol.getChildren(), nullValue()); - assertThat(names, hasItems("myTrait", "Foo", "bar", "Bar", "baz", "Baz")); + var myIntEnumBarSymbol = myIntEnumSymbol.getChildren().get(1); + assertThat(myIntEnumBarSymbol.getName(), equalTo("BAR")); + assertThat(myIntEnumBarSymbol.getKind(), equalTo(SymbolKind.EnumMember)); + assertThat(myIntEnumBarSymbol.getRange(), hasText(document, equalTo("BAR = 1"))); + assertThat(myIntEnumBarSymbol.getSelectionRange(), hasText(document, equalTo("BAR"))); + assertThat(myIntEnumBarSymbol.getDetail(), nullValue()); + assertThat(myIntEnumBarSymbol.getChildren(), nullValue()); } - private static List getDocumentSymbolNames(String text) { - TestWorkspace workspace = TestWorkspace.singleModel(text); - Project project = ProjectTest.load(workspace.getRoot()); - String uri = workspace.getUri("main.smithy"); - IdlFile idlFile = (IdlFile) (SmithyFile) project.getProjectFile(uri); + private static List getDocumentSymbols(Document document) { + Syntax.IdlParseResult parseResult = Syntax.parseIdl(document); - List names = new ArrayList<>(); - var handler = new DocumentSymbolHandler(idlFile.document(), idlFile.getParse().statements()); + List symbols = new ArrayList<>(); + var handler = new DocumentSymbolHandler(document, parseResult.statements()); for (var sym : handler.handle()) { - names.add(sym.getRight().getName()); + symbols.add(sym.getRight()); } - return names; + return symbols; } } diff --git a/src/test/java/software/amazon/smithy/lsp/syntax/IdlParserTest.java b/src/test/java/software/amazon/smithy/lsp/syntax/IdlParserTest.java index 27cb12fe..8bd7e550 100644 --- a/src/test/java/software/amazon/smithy/lsp/syntax/IdlParserTest.java +++ b/src/test/java/software/amazon/smithy/lsp/syntax/IdlParserTest.java @@ -113,12 +113,12 @@ public void parsesOp() { assertTypesEqual(text, Syntax.Statement.Type.ShapeDef, Syntax.Statement.Type.ShapeDef, - Syntax.Statement.Type.MemberDef, + Syntax.Statement.Type.NodeMemberDef, Syntax.Statement.Type.ShapeDef, - Syntax.Statement.Type.MemberDef, - Syntax.Statement.Type.MemberDef, + Syntax.Statement.Type.NodeMemberDef, + Syntax.Statement.Type.NodeMemberDef, Syntax.Statement.Type.ShapeDef, - Syntax.Statement.Type.MemberDef, + Syntax.Statement.Type.NodeMemberDef, Syntax.Statement.Type.NodeMemberDef); }