diff --git a/src/main/java/software/amazon/smithy/lsp/SmithyTextDocumentService.java b/src/main/java/software/amazon/smithy/lsp/SmithyTextDocumentService.java index ebb34e3a..c3baab5e 100644 --- a/src/main/java/software/amazon/smithy/lsp/SmithyTextDocumentService.java +++ b/src/main/java/software/amazon/smithy/lsp/SmithyTextDocumentService.java @@ -127,6 +127,10 @@ public SmithyTextDocumentService(Optional client, File tempFile) this.temporaryFolder = tempFile; } + public void setProject(SmithyProject project) { + this.project = project; + } + public void setClient(LanguageClient client) { this.client = Optional.of(client); } diff --git a/src/test/java/software/amazon/smithy/lsp/DefinitionProviderTest.java b/src/test/java/software/amazon/smithy/lsp/DefinitionProviderTest.java new file mode 100644 index 00000000..e3026f1f --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/DefinitionProviderTest.java @@ -0,0 +1,175 @@ +package software.amazon.smithy.lsp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static software.amazon.smithy.lsp.TestUtils.MAIN_MODEL_FILENAME; +import static software.amazon.smithy.lsp.TestUtils.getV1Dir; +import static software.amazon.smithy.lsp.TestUtils.getV2Dir; +import static software.amazon.smithy.lsp.TestUtils.assertLocationEquals; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import org.junit.Test; +import org.eclipse.lsp4j.DefinitionParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import software.amazon.smithy.lsp.ext.Harness; + +public class DefinitionProviderTest { + + @Test + public void shapeNameInCommentDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 43, 37).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 20, 0, 21, 14); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 45, 37).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 22, 0, 23, 14); + } + + @Test + public void preludeTargetDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 36, 12).get(0); + assertTrue(v1.getUri().endsWith("prelude.smithy")); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 38, 12).get(0); + assertTrue(v2.getUri().endsWith("prelude.smithy")); + } + + @Test + public void preludeTraitDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 25, 3).get(0); + assertTrue(v1.getUri().endsWith("prelude.smithy")); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 27, 3).get(0); + assertTrue(v2.getUri().endsWith("prelude.smithy")); + } + + @Test + public void preludeMemberTraitDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 59, 10).get(0); + assertTrue(v1.getUri().endsWith("prelude.smithy")); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 61, 10).get(0); + assertTrue(v2.getUri().endsWith("prelude.smithy")); + } + + @Test + public void memberTargetDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 12, 18).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 4, 0, 4, 23); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 14, 18).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 6, 0, 6, 23); + } + + @Test + public void selfDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 35, 0).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 35, 0, 37, 1); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 37, 0).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 37, 0, 39, 1); + } + + @Test + public void operationInputDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 52, 16).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 57, 0, 61, 1); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 54, 16).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 59, 0, 63, 1); + } + + @Test + public void operationOutputDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 53, 17).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 63, 0, 66, 1); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 55, 17).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 65, 0, 68, 1); + } + + @Test + public void operationErrorDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 54, 14).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 69, 0, 72, 1); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 56, 14).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 71, 0, 74, 1); + } + + @Test + public void resourceReadDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 76, 12).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 51, 0, 55, 1); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 78, 12).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 53, 0, 57, 1); + } + + @Test + public void noMatchDefinition() { + List v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 0, 0); + assertTrue(v1.isEmpty()); + + List v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 0, 0); + assertTrue(v2.isEmpty()); + } + + @Test + public void resourceIdDefinition() { + Location v1 = getDefinitionLocations(getV1Dir(), MAIN_MODEL_FILENAME, 75, 28).get(0); + assertLocationEquals(v1, MAIN_MODEL_FILENAME, 79, 0, 79, 11); + + Location v2 = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 77, 28).get(0); + assertLocationEquals(v2, MAIN_MODEL_FILENAME, 81, 0, 81, 11); + } + + @Test + public void operationInputMixinDefinition() { + Location location = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 143, 24).get(0); + assertLocationEquals(location, MAIN_MODEL_FILENAME, 112, 0, 118, 1); + } + + @Test + public void operationOutputMixinDefinition() { + Location location = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 149, 36).get(0); + assertLocationEquals(location, MAIN_MODEL_FILENAME, 121, 0, 123, 1); + } + + @Test + public void structureMixinDefinition() { + Location location = getDefinitionLocations(getV2Dir(), MAIN_MODEL_FILENAME, 134, 36).get(0); + assertLocationEquals(location, MAIN_MODEL_FILENAME, 112, 0, 118, 1); + } + + @Test + public void whenThereAreUnknownTraits() throws Exception { + Path rootDir = Paths.get(getClass().getResource("ext/models").toURI()); + List locations = getDefinitionLocations(rootDir, "unknown-trait.smithy", 10, 13); + assertEquals(locations.size(), 1); + } + + private static List getDefinitionLocations(Path rootDir, String filename, int line, int column) { + Path model = rootDir.resolve(filename); + try (Harness hs = Harness.builder().paths(model).build()) { + StubClient client = new StubClient(); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); + TextDocumentIdentifier tdi = new TextDocumentIdentifier(hs.file(filename).toString()); + DefinitionParams params = getDefinitionParams(tdi, line, column); + try { + return tds.definition(params).get().getLeft(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private static DefinitionParams getDefinitionParams(TextDocumentIdentifier tdi, int line, int character) { + return new DefinitionParams(tdi, new Position(line, character)); + } +} diff --git a/src/test/java/software/amazon/smithy/lsp/HoverProviderTest.java b/src/test/java/software/amazon/smithy/lsp/HoverProviderTest.java new file mode 100644 index 00000000..e6972eef --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/HoverProviderTest.java @@ -0,0 +1,253 @@ +package software.amazon.smithy.lsp; + +import static org.junit.Assert.assertNull; +import static software.amazon.smithy.lsp.TestUtils.MAIN_MODEL_FILENAME; +import static software.amazon.smithy.lsp.TestUtils.getV1Dir; +import static software.amazon.smithy.lsp.TestUtils.getV2Dir; +import static software.amazon.smithy.lsp.TestUtils.assertStringContains; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.eclipse.lsp4j.HoverParams; +import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.Test; +import software.amazon.smithy.cli.dependencies.DependencyResolver; +import software.amazon.smithy.cli.dependencies.ResolvedArtifact; +import software.amazon.smithy.lsp.ext.Harness; +import software.amazon.smithy.lsp.ext.MockDependencyResolver; + +public class HoverProviderTest { + + @Test + public void shapeNameInCommentHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 43, 37).getValue(); + assertStringContains(v1, "structure MultiTrait"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 45, 37).getValue(); + assertStringContains(v2, "structure MultiTrait"); + } + + @Test + public void preludeTraitHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 25, 3).getValue(); + assertStringContains(v1, "/// Specializes a structure for use only as the input"); + assertStringContains(v1, "structure input {}"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 27, 3).getValue(); + assertStringContains(v2, "/// Specializes a structure for use only as the input"); + assertStringContains(v2, "structure input {}"); + } + + @Test + public void preludeMemberTraitHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 59, 10).getValue(); + assertStringContains(v1, "/// Marks a structure member as required"); + assertStringContains(v1, "structure required {}"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 61, 10).getValue(); + assertStringContains(v2, "/// Marks a structure member as required"); + assertStringContains(v2, "structure required {}"); + } + + @Test + public void preludeTargetHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 36, 12).getValue(); + assertStringContains(v1, "string String"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 38, 12).getValue(); + assertStringContains(v2, "string String"); + } + + @Test + public void memberTargetHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 12, 18).getValue(); + assertStringContains(v1, "structure SingleLine {}"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 14, 18).getValue(); + assertStringContains(v2, "structure SingleLine {}"); + } + + @Test + public void memberIdentifierHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 65, 7).getValue(); + assertStringContains(v1, "string String"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 67, 7).getValue(); + assertStringContains(v2, "string String"); + } + + @Test + public void selfHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 36, 0).getValue(); + assertStringContains(v1, "@input\n@tags"); + assertStringContains(v1, "structure MultiTraitAndLineComments {\n a: String\n}"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 38, 0).getValue(); + assertStringContains(v2, "@input\n@tags"); + assertStringContains(v2, "structure MultiTraitAndLineComments {\n a: String\n}"); + } + + @Test + public void operationInputHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 52, 16).getValue(); + assertStringContains(v1, "structure MyOperationInput"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 54, 16).getValue(); + assertStringContains(v2, "structure MyOperationInput"); + } + + @Test + public void operationOutputHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 53, 17).getValue(); + assertStringContains(v1, "structure MyOperationOutput"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 55, 17).getValue(); + assertStringContains(v2, "structure MyOperationOutput"); + } + + @Test + public void operationErrorHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 54, 14).getValue(); + assertStringContains(v1, "structure MyError"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 56, 14).getValue(); + assertStringContains(v2, "structure MyError"); + } + + @Test + public void resourceIdHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 75, 28).getValue(); + assertStringContains(v1, "string MyId"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 77, 28).getValue(); + assertStringContains(v2, "string MyId"); + } + + @Test + public void resourceReadHover() { + String v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 76, 12).getValue(); + assertStringContains(v1, "operation MyOperation"); + + String v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 78, 12).getValue(); + assertStringContains(v2, "operation MyOperation"); + } + + @Test + public void noMatchHover() { + MarkupContent v1 = getHoverContent(getV1Dir(), MAIN_MODEL_FILENAME, 0, 0); + assertNull(v1.getValue()); + + MarkupContent v2 = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 0, 0); + assertNull(v2.getValue()); + } + + @Test + public void multiFileHover() { + String filename = "test.smithy"; + String importsFilename = "extras-to-import.smithy"; + String v1 = getHoverContent(getV1Dir(), filename, 6, 15, MAIN_MODEL_FILENAME, importsFilename).getValue(); + assertStringContains(v1, "structure emptyTraitStruct"); + + String v2 = getHoverContent(getV2Dir(), filename, 7, 15, MAIN_MODEL_FILENAME, importsFilename).getValue(); + assertStringContains(v2, "structure emptyTraitStruct"); + } + + @Test + public void operationInlineMixinHover() { + String content = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 143, 36).getValue(); + assertStringContains(content, "@mixin\nstructure UserDetails"); + } + + @Test + public void falseOperationInlineHover() { + String content = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 176, 18).getValue(); + assertStringContains(content, "structure FalseInlinedFooInput"); + } + + @Test + public void providesHoverContentWhenThereAreUnknownTraits() { + String contents = getHoverContent(getBaseDir(), "unknown-trait.smithy", 7, 10).getValue(); + assertStringContains(contents, "structure Foo"); + } + + @Test + public void hoverContentIncludesValidations() { + String contents = getHoverContent(getBaseDir(), "unknown-trait.smithy", 7, 10).getValue(); + assertStringContains(contents, "WARNING: Unable to resolve trait `com.external#unknownTrait`"); + } + + @Test + public void hoverContentIncludesNamespace() { + String contents = getHoverContent(getV2Dir(), MAIN_MODEL_FILENAME, 21, 4).getValue(); + assertStringContains(contents, "namespace smithy.api"); + } + + @Test + public void hoverContentIncludesImports() { + String contents = getHoverContent(getV2Dir(), "cluttered-preamble.smithy", 26, 11, "extras-to-import.smithy", "test.smithy").getValue(); + assertStringContains(contents, "use com.example#OtherStructure"); + assertStringContains(contents, "use com.extras#Extra"); + } + + @Test + public void traitsFromJars() { + String modelFilename = "test-traits.smithy"; + String jarFilename = "smithy-test-traits.jar"; + Path rootDir = getBaseDir().resolve("jars"); + Path jarImportModelPath = rootDir.resolve(jarFilename); + + DependencyResolver dependencyResolver = new MockDependencyResolver( + ResolvedArtifact.fromCoordinates(jarImportModelPath, "com.example:smithy-test-traits:0.0.1") + ); + Harness.Builder builder = Harness.builder() + .dependencyResolver(dependencyResolver); + + String contents = getHoverContent(builder, rootDir, modelFilename, 6, 2).getValue(); + assertStringContains(contents, "structure test"); + } + + private static Path getBaseDir() { + try { + return Paths.get(HoverProviderTest.class.getResource("ext/models").toURI()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static MarkupContent getHoverContent(Path rootDir, String filename, int line, int column, String... otherModels) { + return getHoverContent(Harness.builder(), rootDir, filename, line, column, otherModels); + } + + private static MarkupContent getHoverContent( + Harness.Builder builder, + Path rootDir, + String filename, + int line, + int column, + String... otherModels + ) { + List paths = new ArrayList<>(); + for (String model: otherModels) { + paths.add(rootDir.resolve(model)); + } + paths.add(rootDir.resolve(filename)); + try (Harness hs = builder.paths(paths).build()) { + StubClient client = new StubClient(); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); + TextDocumentIdentifier tdi = new TextDocumentIdentifier(hs.file(filename).toString()); + + HoverParams params = new HoverParams(tdi, new Position(line, column)); + try { + return tds.hover(params).get().getContents().getRight(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/test/java/software/amazon/smithy/lsp/SmithyTextDocumentServiceTest.java b/src/test/java/software/amazon/smithy/lsp/SmithyTextDocumentServiceTest.java index 5c0e4773..9a35f49a 100644 --- a/src/test/java/software/amazon/smithy/lsp/SmithyTextDocumentServiceTest.java +++ b/src/test/java/software/amazon/smithy/lsp/SmithyTextDocumentServiceTest.java @@ -17,23 +17,23 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static software.amazon.smithy.lsp.TestUtils.MAIN_MODEL_FILENAME; +import static software.amazon.smithy.lsp.TestUtils.assertLocationEquals; +import static software.amazon.smithy.lsp.TestUtils.getV1Dir; +import static software.amazon.smithy.lsp.TestUtils.getV2Dir; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionParams; -import org.eclipse.lsp4j.DefinitionParams; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.DidChangeTextDocumentParams; @@ -41,16 +41,10 @@ import org.eclipse.lsp4j.DidSaveTextDocumentParams; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.DocumentSymbolParams; -import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.HoverParams; import org.eclipse.lsp4j.Location; -import org.eclipse.lsp4j.MarkupContent; -import org.eclipse.lsp4j.MessageActionItem; -import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.PublishDiagnosticsParams; import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4j.ShowMessageRequestParams; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; @@ -58,23 +52,17 @@ import org.eclipse.lsp4j.TextDocumentItem; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.eclipse.lsp4j.services.LanguageClient; import org.junit.Test; import software.amazon.smithy.lsp.ext.Harness; import software.amazon.smithy.lsp.ext.SmithyProjectTest; -import software.amazon.smithy.lsp.ext.model.SmithyBuildExtensions; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SetUtils; public class SmithyTextDocumentServiceTest { - // All successful hover responses are wrapped between these strings - private static final String HOVER_DEFAULT_PREFIX = "```smithy\n$version: \"2.0\"\n\n"; - private static final String HOVER_DEFAULT_SUFFIX = "\n```"; - @Test - public void correctlyAttributingDiagnostics() throws Exception { + public void correctlyAttributingDiagnostics() { String brokenFileName = "foo/broken.smithy"; String goodFileName = "good.smithy"; @@ -82,9 +70,9 @@ public void correctlyAttributingDiagnostics() throws Exception { MapUtils.entry(brokenFileName, "$version: \"2\"\nnamespace testFoo\n string_ MyId"), MapUtils.entry(goodFileName, "$version: \"2\"\nnamespace testBla")); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - tds.createProject(hs.getConfig(), hs.getRoot()); + tds.setProject(hs.getProject()); File broken = hs.file(brokenFileName); File good = hs.file(goodFileName); @@ -100,13 +88,11 @@ public void correctlyAttributingDiagnostics() throws Exception { .filter(pds -> (pds.getDiagnostics().size() > 0)).map(PublishDiagnosticsParams::getUri) .collect(Collectors.toSet()); assertEquals(SetUtils.of(uri(broken)), filesWithDiagnostics); - } - } @Test - public void sendingDiagnosticsToTheClient() throws Exception { + public void sendingDiagnosticsToTheClient() { String brokenFileName = "foo/broken.smithy"; String goodFileName = "good.smithy"; @@ -114,11 +100,10 @@ public void sendingDiagnosticsToTheClient() throws Exception { MapUtils.entry(brokenFileName, "$version: \"2\"\nnamespace testFoo; string_ MyId"), MapUtils.entry(goodFileName, "$version: \"2\"\nnamespace testBla")); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().files(files).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); File broken = hs.file(brokenFileName); File good = hs.file(goodFileName); @@ -149,20 +134,17 @@ public void sendingDiagnosticsToTheClient() throws Exception { // list of diagnostics against files with no errors assertEquals(1, filePublishedDiagnostics(good, client.diagnostics).size()); assertEquals(ListUtils.of(), filePublishedDiagnostics(good, client.diagnostics).get(0).getDiagnostics()); - } - } @Test public void attributesDiagnosticsForUnknownTraits() throws Exception { String modelFilename = "ext/models/unknown-trait.smithy"; Path modelFilePath = Paths.get(getClass().getResource(modelFilename).toURI()); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), ListUtils.of(modelFilePath))) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().paths(modelFilePath).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); File modelFile = hs.file(modelFilename); @@ -178,75 +160,17 @@ public void attributesDiagnosticsForUnknownTraits() throws Exception { } @Test - public void allowsDefinitionWhenThereAreUnknownTraits() throws Exception { - Path baseDir = Paths.get(getClass().getResource("ext/models").toURI()); - String modelFilename = "unknown-trait.smithy"; - Path modelFilePath = baseDir.resolve(modelFilename); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), ListUtils.of(modelFilePath))) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - - // We should still be able to respond with a location when there are unknown traits in the model - TextDocumentIdentifier tdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - int locationCount = tds.definition(definitionParams(tdi, 10, 13)).get().getLeft().size(); - assertEquals(locationCount, 1); - } - } - - @Test - public void allowsHoverWhenThereAreUnknownTraits() throws Exception { - Path baseDir = Paths.get(getClass().getResource("ext/models").toURI()); - String modelFilename = "unknown-trait.smithy"; - Path modelFilePath = baseDir.resolve(modelFilename); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), ListUtils.of(modelFilePath))) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - - // We should still be able to respond with hover content when there are unknown traits in the model - TextDocumentIdentifier tdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - Hover hover = tds.hover(hoverParams(tdi, 14, 13)).get(); - correctHover("namespace com.foo\n\n", "structure Bar {\n member: Foo\n}", hover); - } - } - - @Test - public void hoverOnBrokenShapeAppendsValidations() throws Exception { - Path baseDir = Paths.get(getClass().getResource("ext/models").toURI()); - String modelFilename = "unknown-trait.smithy"; - Path modelFilePath = baseDir.resolve(modelFilename); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), ListUtils.of(modelFilePath))) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - - TextDocumentIdentifier tdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - Hover hover = tds.hover(hoverParams(tdi, 10, 13)).get(); - MarkupContent hoverContent = hover.getContents().getRight(); - assertEquals(hoverContent.getKind(),"markdown"); - assertTrue(hoverContent.getValue().startsWith("```smithy")); - assertTrue(hoverContent.getValue().contains("structure Foo {}")); - assertTrue(hoverContent.getValue().contains("WARNING: Unable to resolve trait `com.external#unknownTrait`")); - } - } - - @Test - public void handlingChanges() throws Exception { + public void handlingChanges() { String fileName1 = "foo/bla.smithy"; String fileName2 = "good.smithy"; Map files = MapUtils.ofEntries(MapUtils.entry(fileName1, "namespace testFoo\n string MyId"), MapUtils.entry(fileName2, "namespace testBla")); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().files(files).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); File file1 = hs.file(fileName1); File file2 = hs.file(fileName2); @@ -258,192 +182,19 @@ public void handlingChanges() throws Exception { // Only diagnostics for existing files are reported assertEquals(SetUtils.of(uri(file1), uri(file2)), SetUtils.copyOf(getUris(client.diagnostics))); - - } - - } - - @Test - public void definitionsV1() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - List modelFiles = ListUtils.of(modelMain); - - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - - // Resolves via token => shape name. - DefinitionParams commentParams = definitionParams(mainTdi, 43, 37); - Location commentLocation = tds.definition(commentParams).get().getLeft().get(0); - - // Resolves via shape target location in model. - DefinitionParams memberParams = definitionParams(mainTdi, 12, 18); - Location memberTargetLocation = tds.definition(memberParams).get().getLeft().get(0); - - // Resolves via member shape target location in prelude. - DefinitionParams preludeTargetParams = definitionParams(mainTdi, 36, 12); - Location preludeTargetLocation = tds.definition(preludeTargetParams).get().getLeft().get(0); - - // Resolves via top-level trait location in prelude. - DefinitionParams preludeTraitParams = definitionParams(mainTdi, 25, 3); - Location preludeTraitLocation = tds.definition(preludeTraitParams).get().getLeft().get(0); - - // Resolves via member-applied trait location in prelude. - DefinitionParams preludeMemberTraitParams = definitionParams(mainTdi, 59, 10); - Location preludeMemberTraitLocation = tds.definition(preludeMemberTraitParams).get().getLeft().get(0); - - // Resolves to current location. - DefinitionParams selfParams = definitionParams(mainTdi, 36, 0); - Location selfLocation = tds.definition(selfParams).get().getLeft().get(0); - - // Resolves via operation input. - DefinitionParams inputParams = definitionParams(mainTdi, 52, 16); - Location inputLocation = tds.definition(inputParams).get().getLeft().get(0); - - // Resolves via operation output. - DefinitionParams outputParams = definitionParams(mainTdi, 53, 17); - Location outputLocation = tds.definition(outputParams).get().getLeft().get(0); - - // Resolves via operation error. - DefinitionParams errorParams = definitionParams(mainTdi, 54, 14); - Location errorLocation = tds.definition(errorParams).get().getLeft().get(0); - - // Resolves via resource ids. - DefinitionParams idParams = definitionParams(mainTdi, 75, 29); - Location idLocation = tds.definition(idParams).get().getLeft().get(0); - - // Resolves via resource read. - DefinitionParams readParams = definitionParams(mainTdi, 76, 12); - Location readLocation = tds.definition(readParams).get().getLeft().get(0); - - // Does not correspond to shape. - DefinitionParams noMatchParams = definitionParams(mainTdi, 0, 0); - List noMatchLocationList = (List) tds.definition(noMatchParams).get().getLeft(); - - correctLocation(commentLocation, modelFilename, 20, 0, 21, 14); - correctLocation(memberTargetLocation, modelFilename, 4, 0, 4, 23); - correctLocation(selfLocation, modelFilename, 35, 0, 37, 1); - correctLocation(inputLocation, modelFilename, 57, 0, 61, 1); - correctLocation(outputLocation, modelFilename, 63, 0, 66, 1); - correctLocation(errorLocation, modelFilename, 69, 0, 72, 1); - correctLocation(idLocation, modelFilename, 79, 0, 79, 11); - correctLocation(readLocation, modelFilename, 51, 0, 55, 1); - assertTrue(preludeTargetLocation.getUri().endsWith("prelude.smithy")); - assertTrue(preludeTraitLocation.getUri().endsWith("prelude.smithy")); - assertTrue(preludeMemberTraitLocation.getUri().endsWith("prelude.smithy")); - assertTrue(noMatchLocationList.isEmpty()); - } - } - - @Test - public void definitionsV2() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - List modelFiles = ListUtils.of(modelMain); - - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - - // Resolves via token => shape name. - DefinitionParams commentParams = definitionParams(mainTdi, 45, 37); - Location commentLocation = tds.definition(commentParams).get().getLeft().get(0); - - // Resolves via shape target location in model. - DefinitionParams memberParams = definitionParams(mainTdi, 14, 18); - Location memberTargetLocation = tds.definition(memberParams).get().getLeft().get(0); - - // Resolves via member shape target location in prelude. - DefinitionParams preludeTargetParams = definitionParams(mainTdi, 38, 12); - Location preludeTargetLocation = tds.definition(preludeTargetParams).get().getLeft().get(0); - - // Resolves via top-level trait location in prelude. - DefinitionParams preludeTraitParams = definitionParams(mainTdi, 27, 3); - Location preludeTraitLocation = tds.definition(preludeTraitParams).get().getLeft().get(0); - - // Resolves via member-applied trait location in prelude. - DefinitionParams preludeMemberTraitParams = definitionParams(mainTdi, 61, 10); - Location preludeMemberTraitLocation = tds.definition(preludeMemberTraitParams).get().getLeft().get(0); - - // Resolves to current location. - DefinitionParams selfParams = definitionParams(mainTdi, 38, 0); - Location selfLocation = tds.definition(selfParams).get().getLeft().get(0); - - // Resolves via operation input. - DefinitionParams inputParams = definitionParams(mainTdi, 54, 16); - Location inputLocation = tds.definition(inputParams).get().getLeft().get(0); - - // Resolves via operation output. - DefinitionParams outputParams = definitionParams(mainTdi, 55, 17); - Location outputLocation = tds.definition(outputParams).get().getLeft().get(0); - - // Resolves via operation error. - DefinitionParams errorParams = definitionParams(mainTdi, 56, 14); - Location errorLocation = tds.definition(errorParams).get().getLeft().get(0); - - // Resolves via resource ids. - DefinitionParams idParams = definitionParams(mainTdi, 77, 29); - Location idLocation = tds.definition(idParams).get().getLeft().get(0); - - // Resolves via resource read. - DefinitionParams readParams = definitionParams(mainTdi, 78, 12); - Location readLocation = tds.definition(readParams).get().getLeft().get(0); - - // Does not correspond to shape. - DefinitionParams noMatchParams = definitionParams(mainTdi, 0, 0); - List noMatchLocationList = (List) tds.definition(noMatchParams).get().getLeft(); - - // Resolves via mixin target on operation input. - DefinitionParams mixinInputParams = definitionParams(mainTdi, 143, 24); - Location mixinInputLocation = tds.definition(mixinInputParams).get().getLeft().get(0); - - // Resolves via mixin target on operation output. - DefinitionParams mixinOutputParams = definitionParams(mainTdi, 149, 36); - Location mixinOutputLocation = tds.definition(mixinOutputParams).get().getLeft().get(0); - - // Resolves via mixin target on structure. - DefinitionParams mixinStructureParams = definitionParams(mainTdi, 134, 36); - Location mixinStructureLocation = tds.definition(mixinStructureParams).get().getLeft().get(0); - - correctLocation(commentLocation, modelFilename, 22, 0, 23, 14); - correctLocation(memberTargetLocation, modelFilename, 6, 0, 6, 23); - correctLocation(selfLocation, modelFilename, 37, 0, 39, 1); - correctLocation(inputLocation, modelFilename, 59, 0, 63, 1); - correctLocation(outputLocation, modelFilename, 65, 0, 68, 1); - correctLocation(errorLocation, modelFilename, 71, 0, 74, 1); - correctLocation(idLocation, modelFilename, 81, 0, 81, 11); - correctLocation(readLocation, modelFilename, 53, 0, 57, 1); - correctLocation(mixinInputLocation, modelFilename, 112, 0, 118, 1); - correctLocation(mixinOutputLocation, modelFilename, 121, 0, 123, 1); - correctLocation(mixinStructureLocation, modelFilename, 112, 0, 118, 1); - assertTrue(preludeTargetLocation.getUri().endsWith("prelude.smithy")); - assertTrue(preludeTraitLocation.getUri().endsWith("prelude.smithy")); - assertTrue(preludeMemberTraitLocation.getUri().endsWith("prelude.smithy")); - assertTrue(noMatchLocationList.isEmpty()); } } @Test public void completionsV1() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - List modelFiles = ListUtils.of(modelMain); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + Path baseDir = getV1Dir(); + Path modelMain = baseDir.resolve(MAIN_MODEL_FILENAME); + try (Harness hs = Harness.builder().paths(modelMain).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); + + TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(MAIN_MODEL_FILENAME).toString()); CompletionParams traitParams = completionParams(mainTdi, 85, 10); List traitCompletionItems = tds.completion(traitParams).get().getLeft(); @@ -470,256 +221,16 @@ public void completionsV1() throws Exception { } } - @Test - public void hoverV1() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - String testFilename = "test.smithy"; - Path modelTest = baseDir.resolve(testFilename); - String clutteredPreambleFilename = "cluttered-preamble.smithy"; - Path modelClutteredPreamble = baseDir.resolve(clutteredPreambleFilename); - String extrasToImportFilename = "extras-to-import.smithy"; - Path modelExtras = baseDir.resolve(extrasToImportFilename); - List modelFiles = ListUtils.of(modelMain, modelTest, modelClutteredPreamble, modelExtras); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - TextDocumentIdentifier testTdi = new TextDocumentIdentifier(hs.file(testFilename).toString()); - TextDocumentIdentifier clutteredTdi = new TextDocumentIdentifier(hs.file(clutteredPreambleFilename).toString()); - - // Namespace and use statements in hover response - String preludeHoverPrefix = "namespace smithy.api\n\n"; - String mainHoverPrefix = "namespace com.foo\n\n"; - String testHoverPrefix = "namespace com.example\n\nuse com.foo#emptyTraitStruct\n\n"; - String clutteredHoverWithDependenciesPrefix = "namespace com.clutter\n\nuse " + - "com.example#OtherStructure\nuse com.extras#Extra\n\n"; - String clutteredHoverWithNoDependenciesPrefix = "namespace com.clutter\n\n"; - - // Resolves via top-level trait location in prelude. - Hover preludeTraitHover = tds.hover(hoverParams(mainTdi, 25, 3)).get(); - MarkupContent preludeTraitHoverContents = preludeTraitHover.getContents().getRight(); - assertEquals(preludeTraitHoverContents.getKind(), "markdown"); - assertTrue(preludeTraitHoverContents.getValue().startsWith(HOVER_DEFAULT_PREFIX + preludeHoverPrefix + - "/// Specializes a structure for use only as the input")); - assertTrue(preludeTraitHoverContents.getValue().endsWith("structure input {}" + HOVER_DEFAULT_SUFFIX)); - - // Resolves via member shape target location in prelude. - Hover preludeMemberTraitHover = tds.hover(hoverParams(mainTdi, 59, 10)).get(); - MarkupContent preludeMemberTraitHoverContents = preludeMemberTraitHover.getContents().getRight(); - assertEquals(preludeMemberTraitHoverContents.getKind(), "markdown"); - assertTrue(preludeMemberTraitHoverContents.getValue().startsWith(HOVER_DEFAULT_PREFIX + preludeHoverPrefix + - "/// Marks a structure member as required")); - assertTrue(preludeMemberTraitHoverContents.getValue().endsWith("structure required {}" + HOVER_DEFAULT_SUFFIX)); - - // Resolves via member shape target location in prelude. - Hover preludeTargetHover = tds.hover(hoverParams(mainTdi, 36, 12)).get(); - correctHover(preludeHoverPrefix , "string String", preludeTargetHover); - - // Resolves via token => shape name. - Hover commentHover = tds.hover(hoverParams(mainTdi, 43, 37)).get(); - correctHover(mainHoverPrefix, "@input\n@tags([\n \"foo\"\n])\nstructure MultiTrait {\n a: String\n}", commentHover); - - // Resolves via shape target location in model. - Hover memberTargetHover = tds.hover(hoverParams(mainTdi, 12, 18)).get(); - correctHover(mainHoverPrefix, "structure SingleLine {}", memberTargetHover); - - // Resolves from member key to shape target location in model. - Hover memberIdentifierHover = tds.hover(hoverParams(mainTdi, 64, 7)).get(); - correctHover(preludeHoverPrefix, "string String", memberIdentifierHover); - - // Resolves to current location. - Hover selfHover = tds.hover(hoverParams(mainTdi, 36, 0)).get(); - correctHover(mainHoverPrefix, "@input\n@tags([\n \"a\"\n \"b\"\n \"c\"\n \"d\"\n \"e\"\n \"f\"\n" - + "])\nstructure MultiTraitAndLineComments {\n a: String\n}", selfHover); - - // Resolves via operation input. - Hover inputHover = tds.hover(hoverParams(mainTdi, 52, 16)).get(); - correctHover(mainHoverPrefix, "structure MyOperationInput {\n foo: String\n @required\n myId: MyId\n}", - inputHover); - - // Resolves via operation output. - Hover outputHover = tds.hover(hoverParams(mainTdi, 53, 17)).get(); - correctHover(mainHoverPrefix, "structure MyOperationOutput {\n corge: String\n qux: String\n}", outputHover); - - // Resolves via operation error. - Hover errorHover = tds.hover(hoverParams(mainTdi, 54, 14)).get(); - correctHover(mainHoverPrefix, "@error(\"client\")\nstructure MyError {\n blah: String\n blahhhh: Integer\n}", - errorHover); - - // Resolves via resource ids. - Hover idHover = tds.hover(hoverParams(mainTdi, 75, 29)).get(); - correctHover(mainHoverPrefix, "string MyId", idHover); - - // Resolves via resource read. - Hover readHover = tds.hover(hoverParams(mainTdi, 76, 12)).get(); - assertTrue(readHover.getContents().getRight().getValue().contains("@http(\n method: \"PUT\"\n " - + "uri: \"/bar\"\n code: 200\n)\n@readonly\noperation MyOperation {\n input: " - + "MyOperationInput\n output: MyOperationOutput\n errors: [\n MyError\n ]\n}")); - - // Does not correspond to shape. - Hover noMatchHover = tds.hover(hoverParams(mainTdi, 0, 0)).get(); - assertNull(noMatchHover.getContents().getRight().getValue()); - - // Resolves between multiple model files. - Hover multiFileHover = tds.hover(hoverParams(testTdi, 7, 15)).get(); - correctHover(testHoverPrefix, "@emptyTraitStruct\nstructure OtherStructure {\n foo: String\n bar: String\n" - + " baz: Integer\n}", multiFileHover); - - // Resolves a shape including its dependencies in the preamble - Hover clutteredWithDependenciesHover = tds.hover(hoverParams(clutteredTdi, 25, 17)).get(); - correctHover(clutteredHoverWithDependenciesPrefix, "/// With doc comment\n" - + "structure StructureWithDependencies {\n" - + " extra: Extra\n example: OtherStructure\n}", clutteredWithDependenciesHover); - - // Resolves shape with no dependencies, but doesn't include cluttered preamble - Hover clutteredWithNoDependenciesHover = tds.hover(hoverParams(clutteredTdi, 30, 17)).get(); - correctHover(clutteredHoverWithNoDependenciesPrefix, "structure StructureWithNoDependencies {\n" - + " member: String\n}", clutteredWithNoDependenciesHover); - - } - } - - @Test - public void hoverV2() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - String testFilename = "test.smithy"; - Path modelTest = baseDir.resolve(testFilename); - String clutteredPreambleFilename = "cluttered-preamble.smithy"; - Path modelClutteredPreamble = baseDir.resolve(clutteredPreambleFilename); - String extrasToImportFilename = "extras-to-import.smithy"; - Path modelExtras = baseDir.resolve(extrasToImportFilename); - List modelFiles = ListUtils.of(modelMain, modelTest, modelClutteredPreamble, modelExtras); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); - TextDocumentIdentifier testTdi = new TextDocumentIdentifier(hs.file(testFilename).toString()); - TextDocumentIdentifier clutteredTdi = new TextDocumentIdentifier(hs.file(clutteredPreambleFilename).toString()); - - // Namespace and use statements in hover response - String preludeHoverPrefix = "namespace smithy.api\n\n"; - String mainHoverPrefix = "namespace com.foo\n\n"; - String testHoverPrefix = "namespace com.example\n\nuse com.foo#emptyTraitStruct\n\n"; - String clutteredHoverWithDependenciesPrefix = "namespace com.clutter\n\nuse " + - "com.example#OtherStructure\nuse com.extras#Extra\n\n"; - String clutteredHoverInlineOpPrefix = "namespace com.clutter\n\n"; - - // Resolves via top-level trait location in prelude. - Hover preludeTraitHover = tds.hover(hoverParams(mainTdi, 27, 3)).get(); - MarkupContent preludeTraitHoverContents = preludeTraitHover.getContents().getRight(); - assertEquals(preludeTraitHoverContents.getKind(), "markdown"); - assertTrue(preludeTraitHoverContents.getValue().startsWith(HOVER_DEFAULT_PREFIX + preludeHoverPrefix - + "/// Specializes a structure for use only as the" + " input")); - assertTrue(preludeTraitHoverContents.getValue().endsWith("structure input {}" + HOVER_DEFAULT_SUFFIX)); - - // Resolves via member shape target location in prelude. - Hover preludeMemberTraitHover = tds.hover(hoverParams(mainTdi, 61, 10)).get(); - MarkupContent preludeMemberTraitHoverContents = preludeMemberTraitHover.getContents().getRight(); - assertEquals(preludeMemberTraitHoverContents.getKind(), "markdown"); - assertTrue(preludeMemberTraitHoverContents.getValue().startsWith(HOVER_DEFAULT_PREFIX + preludeHoverPrefix - + "/// Marks a structure member as required")); - assertTrue(preludeMemberTraitHoverContents.getValue().endsWith("structure required {}" + HOVER_DEFAULT_SUFFIX)); - - // Resolves via member shape target location in prelude. - Hover preludeTargetHover = tds.hover(hoverParams(mainTdi, 38, 12)).get(); - correctHover(preludeHoverPrefix, "string String", preludeTargetHover); - - // Resolves via token => shape name. - Hover commentHover = tds.hover(hoverParams(mainTdi, 45, 37)).get(); - correctHover(mainHoverPrefix, "@input\n@tags([\n \"foo\"\n])\nstructure MultiTrait {\n a: String\n}", commentHover); - - // Resolves via shape target location in model. - Hover memberTargetHover = tds.hover(hoverParams(mainTdi, 14, 18)).get(); - correctHover(mainHoverPrefix, "structure SingleLine {}", memberTargetHover); - - // Resolves from member key to shape target location in model. - Hover memberIdentifierHover = tds.hover(hoverParams(mainTdi, 66, 7)).get(); - correctHover(preludeHoverPrefix, "string String", memberIdentifierHover); - - // Resolves to current location. - Hover selfHover = tds.hover(hoverParams(mainTdi, 38, 0)).get(); - correctHover(mainHoverPrefix, "@input\n@tags([\n \"a\"\n \"b\"\n \"c\"\n \"d\"\n \"e\"\n \"f\"\n" - + "])\nstructure MultiTraitAndLineComments {\n a: String\n}", selfHover); - - // Resolves via operation input. - Hover inputHover = tds.hover(hoverParams(mainTdi, 54, 16)).get(); - correctHover(mainHoverPrefix, "structure MyOperationInput {\n foo: String\n @required\n myId: MyId\n}", - inputHover); - - // Resolves via operation output. - Hover outputHover = tds.hover(hoverParams(mainTdi, 55, 17)).get(); - correctHover(mainHoverPrefix, "structure MyOperationOutput {\n corge: String\n qux: String\n}", outputHover); - - // Resolves via operation error. - Hover errorHover = tds.hover(hoverParams(mainTdi, 56, 14)).get(); - correctHover(mainHoverPrefix, "@error(\"client\")\nstructure MyError {\n blah: String\n blahhhh: Integer\n}", - errorHover); - - // Resolves via resource ids. - Hover idHover = tds.hover(hoverParams(mainTdi, 77, 29)).get(); - correctHover(mainHoverPrefix, "string MyId", idHover); - - // Resolves via resource read. - Hover readHover = tds.hover(hoverParams(mainTdi, 78, 12)).get(); - assertTrue(readHover.getContents().getRight().getValue().contains("@http(\n method: \"PUT\"\n " - + "uri: \"/bar\"\n code: 200\n)\n@readonly\noperation MyOperation {\n input: " - + "MyOperationInput\n output: MyOperationOutput\n errors: [\n MyError\n ]\n}")); - - // Does not correspond to shape. - Hover noMatchHover = tds.hover(hoverParams(mainTdi, 0, 0)).get(); - assertNull(noMatchHover.getContents().getRight().getValue()); - - // Resolves between multiple model files. - Hover multiFileHover = tds.hover(hoverParams(testTdi, 7, 15)).get(); - correctHover(testHoverPrefix, "@emptyTraitStruct\nstructure OtherStructure {\n foo: String\n bar: String\n" - + " baz: Integer\n}", multiFileHover); - - // Resolves mixin used within an inlined input/output in an operation shape - Hover operationInlineMixinHover = tds.hover(hoverParams(mainTdi, 143, 36)).get(); - correctHover(mainHoverPrefix, "@mixin\nstructure UserDetails {\n status: String\n}", operationInlineMixinHover); - - // Resolves mixin used on a structure - Hover structureMixinHover = tds.hover(hoverParams(mainTdi, 134, 45)).get(); - correctHover(mainHoverPrefix, "@mixin\nstructure UserDetails {\n status: String\n}", structureMixinHover); - - // Resolves shape with a name that matches operation input/output suffix but is not inlined - Hover falseOperationInlineHover = tds.hover(hoverParams(mainTdi, 176, 18)).get(); - correctHover(mainHoverPrefix, "structure FalseInlinedFooInput {\n a: String\n}", falseOperationInlineHover); - - // Resolves a shape including its dependencies in the preamble - Hover clutteredWithDependenciesHover = tds.hover(hoverParams(clutteredTdi, 26, 17)).get(); - correctHover(clutteredHoverWithDependenciesPrefix, "/// With doc comment\n@mixin\n" - + "structure StructureWithDependencies {\n" - + " extra: Extra\n example: OtherStructure\n}", clutteredWithDependenciesHover); - - // Resolves operation with inlined input/output, but doesn't include cluttered preamble - Hover clutteredInlineOpHover = tds.hover(hoverParams(clutteredTdi, 31, 17)).get(); - correctHover(clutteredHoverInlineOpPrefix, "operation ClutteredInlineOperation {\n" - + " input: ClutteredInlineOperationIn\n" - + " output: ClutteredInlineOperationOut\n}", clutteredInlineOpHover); - } - } - @Test public void completionsV2() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - List modelFiles = ListUtils.of(modelMain); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + Path baseDir = getV2Dir(); + Path modelMain = baseDir.resolve(MAIN_MODEL_FILENAME); + try (Harness hs = Harness.builder().paths(modelMain).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); - TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(modelFilename).toString()); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); + + TextDocumentIdentifier mainTdi = new TextDocumentIdentifier(hs.file(MAIN_MODEL_FILENAME).toString()); CompletionParams traitParams = completionParams(mainTdi, 87, 10); List traitCompletionItems = tds.completion(traitParams).get().getLeft(); @@ -747,17 +258,14 @@ public void completionsV2() throws Exception { } @Test - public void runSelectorV1() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - List modelFiles = ListUtils.of(modelMain); + public void runSelectorV1() { + Path baseDir = getV1Dir(); + Path modelMain = baseDir.resolve(MAIN_MODEL_FILENAME); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().paths(modelMain).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); Either> result = tds.runSelector("[id|namespace=com.foo]"); @@ -769,21 +277,19 @@ public void runSelectorV1() throws Exception { .findFirst(); assertTrue(location.isPresent()); - correctLocation(location.get(), modelFilename, 20, 0, 21, 14); + assertLocationEquals(location.get(), MAIN_MODEL_FILENAME, 20, 0, 21, 14); } } @Test - public void runSelectorV2() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); - String modelFilename = "main.smithy"; - Path modelMain = baseDir.resolve(modelFilename); - List modelFiles = ListUtils.of(modelMain); + public void runSelectorV2() { + Path baseDir = getV2Dir(); + Path modelMain = baseDir.resolve(MAIN_MODEL_FILENAME); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().paths(modelMain).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); tds.setClient(client); Either> result = tds.runSelector("[id|namespace=com.foo]"); @@ -796,20 +302,18 @@ public void runSelectorV2() throws Exception { .findFirst(); assertTrue(location.isPresent()); - correctLocation(location.get(), modelFilename, 22, 0, 23, 14); + assertLocationEquals(location.get(), MAIN_MODEL_FILENAME, 22, 0, 23, 14); } } @Test - public void runSelectorAgainstModelWithErrorsV1() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); + public void runSelectorAgainstModelWithErrorsV1() { + Path baseDir = getV1Dir(); Path broken = baseDir.resolve("broken.smithy"); - List modelFiles = ListUtils.of(broken); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().paths(broken).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); Either> result = tds.runSelector("[id|namespace=com.foo]"); @@ -819,15 +323,13 @@ public void runSelectorAgainstModelWithErrorsV1() throws Exception { } @Test - public void runSelectorAgainstModelWithErrorsV2() throws Exception { - Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); + public void runSelectorAgainstModelWithErrorsV2() { + Path baseDir = getV2Dir(); Path broken = baseDir.resolve("broken.smithy"); - List modelFiles = ListUtils.of(broken); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { - SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); + try (Harness hs = Harness.builder().paths(broken).build()) { StubClient client = new StubClient(); - tds.createProject(hs.getConfig(), hs.getRoot()); - tds.setClient(client); + SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.of(client), hs.getTempFolder()); + tds.setProject(hs.getProject()); Either> result = tds.runSelector("[id|namespace=com.foo]"); @@ -837,7 +339,7 @@ public void runSelectorAgainstModelWithErrorsV2() throws Exception { } @Test - public void ensureVersionDiagnostic() throws Exception { + public void ensureVersionDiagnostic() { String fileName1 = "no-version.smithy"; String fileName2 = "old-version.smithy"; String fileName3 = "good-version.smithy"; @@ -848,7 +350,7 @@ public void ensureVersionDiagnostic() throws Exception { MapUtils.entry(fileName3, "$version: \"2\"\nnamespace test3") ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); StubClient client = new StubClient(); tds.createProject(hs.getConfig(), hs.getRoot()); @@ -875,13 +377,13 @@ public void documentSymbols() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/document-symbols").toURI()); String currentFile = "current.smithy"; + Path currentFilePath = baseDir.resolve(currentFile); String anotherFile = "another.smithy"; + Path anotherFilePath = baseDir.resolve(anotherFile); - List files = ListUtils.of(baseDir.resolve(currentFile),baseDir.resolve(anotherFile)); - - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().paths(currentFilePath, anotherFilePath).build()) { SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder()); - tds.createProject(hs.getConfig(), hs.getRoot()); + tds.setProject(hs.getProject()); TextDocumentIdentifier currentDocumentIdent = new TextDocumentIdentifier(uri(hs.file(currentFile))); @@ -899,48 +401,6 @@ public void documentSymbols() throws Exception { } - private static class StubClient implements LanguageClient { - public List diagnostics = new ArrayList<>(); - public List shown = new ArrayList<>(); - public List logged = new ArrayList<>(); - - public StubClient() { - } - - public void clear() { - this.diagnostics.clear(); - this.shown.clear(); - this.logged.clear(); - } - - @Override - public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { - this.diagnostics.add(diagnostics); - } - - @Override - public void telemetryEvent(Object object) { - // TODO Auto-generated method stub - - } - - @Override - public void logMessage(MessageParams message) { - this.logged.add(message); - } - - @Override - public void showMessage(MessageParams messageParams) { - this.shown.add(messageParams); - } - - @Override - public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { - // TODO Auto-generated method stub - return null; - } - } - private Set getUris(Collection diagnostics) { return diagnostics.stream().map(PublishDiagnosticsParams::getUri).collect(Collectors.toSet()); } @@ -967,28 +427,6 @@ private String uri(File f) { return f.toURI().toString(); } - private DefinitionParams definitionParams(TextDocumentIdentifier tdi, int line, int character) { - return new DefinitionParams(tdi, new Position(line, character)); - } - - private HoverParams hoverParams(TextDocumentIdentifier tdi, int line, int character) { - return new HoverParams(tdi, new Position(line, character)); - } - - private void correctHover(String expectedPrefix, String expectedBody, Hover hover) { - MarkupContent content = hover.getContents().getRight(); - assertEquals("markdown", content.getKind()); - assertEquals(HOVER_DEFAULT_PREFIX + expectedPrefix + expectedBody + HOVER_DEFAULT_SUFFIX, content.getValue()); - } - - private void correctLocation(Location location, String uri, int startLine, int startCol, int endLine, int endCol) { - assertEquals(startLine, location.getRange().getStart().getLine()); - assertEquals(startCol, location.getRange().getStart().getCharacter()); - assertEquals(endLine, location.getRange().getEnd().getLine()); - assertEquals(endCol, location.getRange().getEnd().getCharacter()); - assertTrue(location.getUri().endsWith(uri)); - } - private CompletionParams completionParams(TextDocumentIdentifier tdi, int line, int character) { return new CompletionParams(tdi, new Position(line, character)); } diff --git a/src/test/java/software/amazon/smithy/lsp/SmithyVersionRefactoringTest.java b/src/test/java/software/amazon/smithy/lsp/SmithyVersionRefactoringTest.java index f8cca93c..542b9bc8 100644 --- a/src/test/java/software/amazon/smithy/lsp/SmithyVersionRefactoringTest.java +++ b/src/test/java/software/amazon/smithy/lsp/SmithyVersionRefactoringTest.java @@ -41,14 +41,14 @@ public class SmithyVersionRefactoringTest { @Test - public void noVersionCodeAction() throws Exception { + public void noVersionCodeAction() { String filename = "no-version.smithy"; Map files = MapUtils.ofEntries( MapUtils.entry(filename, "namespace test") ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { Range range0 = new Range(new Position(0, 0), new Position(0, 0)); CodeActionParams params = new CodeActionParams( @@ -65,14 +65,14 @@ public void noVersionCodeAction() throws Exception { } @Test - public void outdatedVersionCodeAction() throws Exception { + public void outdatedVersionCodeAction() { String filename = "old-version.smithy"; Map files = MapUtils.ofEntries( MapUtils.entry(filename, "$version: \"1\"\nnamespace test2") ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { Range range0 = new Range(new Position(0, 0), new Position(0, 0)); Range firstLineRange = new Range(new Position(0, 0), new Position(0, 13)); @@ -90,14 +90,14 @@ public void outdatedVersionCodeAction() throws Exception { } @Test - public void correctVersionCodeAction() throws Exception { + public void correctVersionCodeAction() { String filename = "version.smithy"; Map files = MapUtils.ofEntries( MapUtils.entry(filename, "$version: \"2\"\nnamespace test2") ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { Range range0 = new Range(new Position(0, 0), new Position(0, 0)); CodeActionParams params = new CodeActionParams( diff --git a/src/test/java/software/amazon/smithy/lsp/StubClient.java b/src/test/java/software/amazon/smithy/lsp/StubClient.java new file mode 100644 index 00000000..4649e37c --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/StubClient.java @@ -0,0 +1,53 @@ +package software.amazon.smithy.lsp; + +import org.eclipse.lsp4j.MessageActionItem; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.ShowMessageRequestParams; +import org.eclipse.lsp4j.services.LanguageClient; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +class StubClient implements LanguageClient { + public List diagnostics = new ArrayList<>(); + public List shown = new ArrayList<>(); + public List logged = new ArrayList<>(); + + public StubClient() { + } + + public void clear() { + this.diagnostics.clear(); + this.shown.clear(); + this.logged.clear(); + } + + @Override + public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { + this.diagnostics.add(diagnostics); + } + + @Override + public void telemetryEvent(Object object) { + // TODO Auto-generated method stub + + } + + @Override + public void logMessage(MessageParams message) { + this.logged.add(message); + } + + @Override + public void showMessage(MessageParams messageParams) { + this.shown.add(messageParams); + } + + @Override + public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/test/java/software/amazon/smithy/lsp/TestUtils.java b/src/test/java/software/amazon/smithy/lsp/TestUtils.java new file mode 100644 index 00000000..5674ed49 --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/TestUtils.java @@ -0,0 +1,58 @@ +package software.amazon.smithy.lsp; + +import static org.junit.Assert.assertTrue; + +import java.nio.file.Path; +import java.nio.file.Paths; +import junit.framework.AssertionFailedError; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import software.amazon.smithy.lsp.ext.SmithyProjectTest; + +public final class TestUtils { + public static final String MAIN_MODEL_FILENAME = "main.smithy"; + + public static Path getV1Dir() { + try { + return Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Path getV2Dir() { + try { + return Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void assertLocationEquals( + Location actual, + String filename, + int startLine, + int startCol, + int endLine, + int endCol + ) { + Range actualRange = actual.getRange(); + Range expectedRange = new Range(new Position(startLine, startCol), new Position(endLine, endCol)); + if (!expectedRange.equals(actualRange)) { + throw new AssertionFailedError("Expected range:\n" + expectedRange + "\nBut was:\n" + actualRange); + } + if (!actual.getUri().endsWith(filename)) { + throw new AssertionFailedError("Expected uri to end with filename:\n" + filename + + "\nBut was:\n" + actual.getUri()); + } + } + + public static void assertStringContains(String actual, String expected) { + if (!actual.contains(expected)) { + throw new AssertionError("Expected string to contain:\n----------\n" + expected + "\n----------\n\n" + + "but got:\n----------\n" + actual + "\n----------\n"); + } + assertTrue(actual.contains(expected)); + } +} diff --git a/src/test/java/software/amazon/smithy/lsp/ext/CompletionsTest.java b/src/test/java/software/amazon/smithy/lsp/ext/CompletionsTest.java index 4f0fadc9..f81af8aa 100644 --- a/src/test/java/software/amazon/smithy/lsp/ext/CompletionsTest.java +++ b/src/test/java/software/amazon/smithy/lsp/ext/CompletionsTest.java @@ -46,7 +46,7 @@ public void resolveCurrentNamespace() throws Exception { MapUtils.entry("test/def2.smithy", testContent) ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { SmithyProject proj = hs.getProject(); DocumentPreamble testPreamble = Document.detectPreamble(hs.readFile(hs.file("test/def2.smithy"))); @@ -75,7 +75,7 @@ public void multiFileV1() throws Exception { MapUtils.entry("trait-def.smithy", traitDef) ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { SmithyProject proj = hs.getProject(); // Complete match assertEquals(SetUtils.of("Foo"), completeNames(proj, "Foo", false)); @@ -120,7 +120,7 @@ public void multiFileV2() throws Exception { MapUtils.entry("trait-def.smithy", traitDef) ); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { SmithyProject proj = hs.getProject(); // Complete match assertEquals(SetUtils.of("Foo"), completeNames(proj, "Foo", false)); diff --git a/src/test/java/software/amazon/smithy/lsp/ext/Harness.java b/src/test/java/software/amazon/smithy/lsp/ext/Harness.java index 26019edd..f0d383ff 100644 --- a/src/test/java/software/amazon/smithy/lsp/ext/Harness.java +++ b/src/test/java/software/amazon/smithy/lsp/ext/Harness.java @@ -21,14 +21,13 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import software.amazon.smithy.build.model.MavenRepository; import software.amazon.smithy.cli.dependencies.DependencyResolver; -import software.amazon.smithy.cli.dependencies.FileCacheResolver; import software.amazon.smithy.cli.dependencies.ResolvedArtifact; import software.amazon.smithy.lsp.Utils; import software.amazon.smithy.lsp.ext.model.SmithyBuildExtensions; @@ -64,16 +63,6 @@ public SmithyBuildExtensions getConfig() { return this.config; } - private static File safeCreateFile(String path, String contents, File root) throws Exception { - File f = Paths.get(root.getAbsolutePath(), path).toFile(); - new File(f.getParent()).mkdirs(); - try (FileWriter fw = new FileWriter(f)) { - fw.write(contents); - fw.flush(); - } - - return f; - } public File file(String path) { return Paths.get(root.getAbsolutePath(), path).toFile(); @@ -88,46 +77,103 @@ public void close() { root.deleteOnExit(); } - public static Harness create(SmithyBuildExtensions ext) throws Exception { - File hs = Files.createTempDirectory("hs").toFile(); - File tmp = Files.createTempDirectory("tmp").toFile(); - return loadHarness(ext, hs, tmp, new MockDependencyResolver(ListUtils.of())); + private static Map createFiles(List paths, File hs) { + Map files = new HashMap<>(); + try { + for (Path path : paths) { + String contents; + String uri = path.toString(); + if (Utils.isJarFile(uri)) { + contents = String.join(System.lineSeparator(), Utils.jarFileContents(uri)); + } else { + contents = IoUtils.readUtf8File(path); + } + files.put(uri, contents); + safeCreateFile(path.getFileName().toString(), contents, hs); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return files; } - public static Harness create(SmithyBuildExtensions ext, Map files) throws Exception { - File hs = Files.createTempDirectory("hs").toFile(); - File tmp = Files.createTempDirectory("tmp").toFile(); - for (Entry entry : files.entrySet()) { - safeCreateFile(entry.getKey(), entry.getValue(), hs); - } - return loadHarness(ext, hs, tmp, new MockDependencyResolver(ListUtils.of())); + public static Builder builder() { + return new Builder(); } - public static Harness create(SmithyBuildExtensions ext, List files) throws Exception { - File hs = Files.createTempDirectory("hs").toFile(); - File tmp = Files.createTempDirectory("tmp").toFile(); - for (Path path : files) { - if (Utils.isJarFile(path.toString())) { - String contents = String.join(System.lineSeparator(), Utils.jarFileContents(path.toString())); - safeCreateFile(path.getFileName().toString(), contents, hs); - } else { - safeCreateFile(path.getFileName().toString(), IoUtils.readUtf8File(path), hs); - } + public static class Builder { + private SmithyBuildExtensions extensions = SmithyBuildExtensions.builder().build(); + private DependencyResolver dependencyResolver = new MockDependencyResolver(); + private List paths; + private Map files; + + private Builder() {} + + public Builder extensions(SmithyBuildExtensions extensions) { + this.extensions = extensions; + return this; } - return loadHarness(ext, hs, tmp, new MockDependencyResolver(ListUtils.of())); - } - public static Harness create(SmithyBuildExtensions ext, DependencyResolver resolver) throws Exception { - File hs = Files.createTempDirectory("hs").toFile(); - File tmp = Files.createTempDirectory("tmp").toFile(); - return loadHarness(ext, hs, tmp, resolver); + public Builder dependencyResolver(DependencyResolver dependencyResolver) { + this.dependencyResolver = dependencyResolver; + return this; + } + + public Builder paths(Path... paths) { + this.paths = ListUtils.of(paths); + return this; + } + + public Builder paths(List paths) { + this.paths = paths; + return this; + } + + public Builder files(Map files) { + this.files = files; + return this; + } + + public Harness build() { + try { + File hs = Files.createTempDirectory("hs").toFile(); + File tmp = Files.createTempDirectory("tmp").toFile(); + if (this.files == null && this.paths != null) { + this.files = createFiles(this.paths, hs); + } else if (this.paths == null && this.files != null) { + for (Entry entry : files.entrySet()) { + safeCreateFile(entry.getKey(), entry.getValue(), hs); + } + } else { + this.files = new HashMap<>(); + } + + return loadHarness(this.extensions, hs, tmp, this.dependencyResolver); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } - private static Harness loadHarness(SmithyBuildExtensions ext, File hs, File tmp, DependencyResolver resolver) throws Exception { + private static Harness loadHarness( + SmithyBuildExtensions ext, + File hs, + File tmp, + DependencyResolver resolver + ) throws Exception { Either loaded = SmithyProject.load(ext, hs, resolver); if (loaded.isRight()) return new Harness(hs, tmp, loaded.getRight(), ext); else throw loaded.getLeft(); } + + private static void safeCreateFile(String path, String contents, File root) throws Exception { + File f = Paths.get(root.getAbsolutePath(), path).toFile(); + new File(f.getParent()).mkdirs(); + try (FileWriter fw = new FileWriter(f)) { + fw.write(contents); + fw.flush(); + } + } } diff --git a/src/test/java/software/amazon/smithy/lsp/ext/MockDependencyResolver.java b/src/test/java/software/amazon/smithy/lsp/ext/MockDependencyResolver.java index d2489b54..265c6f11 100644 --- a/src/test/java/software/amazon/smithy/lsp/ext/MockDependencyResolver.java +++ b/src/test/java/software/amazon/smithy/lsp/ext/MockDependencyResolver.java @@ -5,16 +5,21 @@ import software.amazon.smithy.build.model.MavenRepository; import software.amazon.smithy.cli.dependencies.DependencyResolver; import software.amazon.smithy.cli.dependencies.ResolvedArtifact; +import software.amazon.smithy.utils.ListUtils; public class MockDependencyResolver implements DependencyResolver { final List artifacts; final List repositories = new ArrayList<>(); final List coordinates = new ArrayList<>(); - MockDependencyResolver(List artifacts) { + public MockDependencyResolver(List artifacts) { this.artifacts = artifacts; } + public MockDependencyResolver(ResolvedArtifact... artifacts) { + this.artifacts = ListUtils.of(artifacts); + } + @Override public void addRepository(MavenRepository repository) { repositories.add(repository); diff --git a/src/test/java/software/amazon/smithy/lsp/ext/SmithyProjectTest.java b/src/test/java/software/amazon/smithy/lsp/ext/SmithyProjectTest.java index 407038d7..cfc7719a 100644 --- a/src/test/java/software/amazon/smithy/lsp/ext/SmithyProjectTest.java +++ b/src/test/java/software/amazon/smithy/lsp/ext/SmithyProjectTest.java @@ -15,17 +15,21 @@ package software.amazon.smithy.lsp.ext; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; @@ -50,24 +54,19 @@ import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SetUtils; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.StringContains.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - public class SmithyProjectTest { @Test - public void respectingImports() throws Exception { - List imports = Arrays.asList("bla", "foo"); + public void respectingImports() { + List imports = ListUtils.of("bla", "foo"); Map files = MapUtils.ofEntries(MapUtils.entry("test.smithy", "namespace testRoot"), MapUtils.entry("bar/test.smithy", "namespace testBar"), MapUtils.entry("foo/test.smithy", "namespace testFoo"), MapUtils.entry("bla/test.smithy", "namespace testBla")); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().imports(imports).build(), files)) { + SmithyBuildExtensions extensions = SmithyBuildExtensions.builder().imports(imports).build(); + + try (Harness hs = Harness.builder().extensions(extensions).files(files).build()) { File inFoo = hs.file("foo/test.smithy"); File inBla = hs.file("bla/test.smithy"); @@ -84,8 +83,7 @@ public void respectingEmptyConfig() throws Exception { MapUtils.entry("foo/test.smithy", "namespace testFoo"), MapUtils.entry("bla/test.smithy", "namespace testBla")); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { - + try (Harness hs = Harness.builder().files(files).build()) { List expectedFiles = Files.walk(hs.getRoot().toPath()) .filter(f -> f.getFileName().toString().endsWith(Constants.SMITHY_EXTENSION)).map(Path::toFile) .collect(Collectors.toList()); @@ -102,7 +100,7 @@ public void defaultsToMavenCentral() throws Exception { MockDependencyResolver delegate = new MockDependencyResolver(ListUtils.of()); File cache = File.createTempFile("classpath", ".json"); DependencyResolver resolver = new FileCacheResolver(cache, System.currentTimeMillis(), delegate); - try (Harness hs = Harness.create(extensions, resolver)) { + try (Harness hs = Harness.builder().extensions(extensions).dependencyResolver(resolver).build()) { assertEquals(delegate.repositories.stream().findFirst().get().getUrl(), "https://repo.maven.apache.org/maven2"); } } @@ -134,7 +132,7 @@ public void cachesExternalJars() throws Exception { ResolvedArtifact artifact = ResolvedArtifact.fromCoordinates(jar.toPath(), "com.foo:bar:1.0.0"); MockDependencyResolver delegate = new MockDependencyResolver(ListUtils.of(artifact)); DependencyResolver resolver = new FileCacheResolver(cache, System.currentTimeMillis(), delegate); - try (Harness hs = Harness.create(extensions, resolver)) { + try (Harness hs = Harness.builder().extensions(extensions).dependencyResolver(resolver).build()) { assertTrue(delegate.repositories.containsAll(expectedRepos)); assertEquals(expectedRepos.size(), delegate.repositories.size()); assertEquals(dependency, delegate.coordinates.get(0)); @@ -145,7 +143,7 @@ public void cachesExternalJars() throws Exception { @Test public void ableToLoadWithUnknownTrait() throws Exception { Path modelFile = Paths.get(getClass().getResource("models/unknown-trait.smithy").toURI()); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), ListUtils.of(modelFile))) { + try (Harness hs = Harness.builder().paths(modelFile).build()) { ValidatedResult modelValidatedResult = hs.getProject().getModel(); assertFalse(modelValidatedResult.isBroken()); } @@ -156,9 +154,8 @@ public void ignoresUnmodeledApplyStatements() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); Path main = baseDir.resolve("apply.smithy"); Path imports = baseDir.resolve("apply-imports.smithy"); - List modelFiles = ListUtils.of(main, imports); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { + try (Harness hs = Harness.builder().paths(main, imports).build()) { Map locationMap = hs.getProject().getLocations(); // Structure shape unchanged by apply @@ -186,7 +183,7 @@ public void ignoresUnmodeledApplyStatements() throws Exception { // https://github.com/awslabs/smithy-language-server/issues/100 @Test - public void allowsEmptyStructsWithMixins() throws Exception { + public void allowsEmptyStructsWithMixins() { String fileText = "$version: \"2\"\n" + "\n" + "namespace demo\n" + @@ -200,7 +197,7 @@ public void allowsEmptyStructsWithMixins() throws Exception { Map files = MapUtils.of("main.smithy", fileText); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) { + try (Harness hs = Harness.builder().files(files).build()) { assertNotNull(hs.getProject()); Map locationMap = hs.getProject().getLocations(); @@ -215,9 +212,8 @@ public void handlesSameOperationNameBetweenNamespaces() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/operation-name-conflict").toURI()); Path modelA = baseDir.resolve("a.smithy"); Path modelB = baseDir.resolve("b.smithy"); - List modelFiles = ListUtils.of(modelA, modelB); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { + try (Harness hs = Harness.builder().paths(modelA, modelB).build()) { Map locationMap = hs.getProject().getLocations(); correctLocation(locationMap, "a#HelloWorld", 4, 0, 13, 1); @@ -230,9 +226,8 @@ public void definitionLocationsV1() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); Path modelMain = baseDir.resolve("main.smithy"); Path modelTest = baseDir.resolve("test.smithy"); - List modelFiles = ListUtils.of(modelMain, modelTest); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { + try (Harness hs = Harness.builder().paths(modelMain, modelTest).build()) { Map locationMap = hs.getProject().getLocations(); correctLocation(locationMap, "com.foo#SingleLine", 4, 0, 4, 23); @@ -250,9 +245,8 @@ public void definitionLocationsV2() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); Path modelMain = baseDir.resolve("main.smithy"); Path modelTest = baseDir.resolve("test.smithy"); - List modelFiles = ListUtils.of(modelMain, modelTest); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { + try (Harness hs = Harness.builder().paths(modelMain, modelTest).build()) { Map locationMap = hs.getProject().getLocations(); correctLocation(locationMap, "com.foo#SingleLine", 6, 0, 6, 23); @@ -261,7 +255,7 @@ public void definitionLocationsV2() throws Exception { correctLocation(locationMap, "com.foo#MultiTrait", 22, 0,23, 14); correctLocation(locationMap, "com.foo#MultiTraitAndLineComments", 37, 0,39, 1); correctLocation(locationMap, "com.foo#MultiTraitAndDocComments", 48, 0, 50, 1); - correctLocation(locationMap, "com.example#OtherStructure", 7, 0, 11, 1); + correctLocation(locationMap, "com.example#OtherStructure", 8, 0, 13, 1); correctLocation(locationMap, "com.foo#StructWithDefaultSugar", 97, 0, 99, 1); correctLocation(locationMap, "com.foo#MyInlineOperation", 101, 0, 109, 1); correctLocation(locationMap, "com.foo#MyInlineOperationFooInput", 102, 13, 105, 5); @@ -374,9 +368,8 @@ public void shapeIdFromLocationV1() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v1").toURI()); Path modelMain = baseDir.resolve("main.smithy"); Path modelTest = baseDir.resolve("test.smithy"); - List modelFiles = ListUtils.of(modelMain, modelTest); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { + try (Harness hs = Harness.builder().paths(modelMain, modelTest).build()) { SmithyProject project = hs.getProject(); String uri = hs.file("main.smithy").toString(); String testUri = hs.file("test.smithy").toString(); @@ -420,9 +413,8 @@ public void shapeIdFromLocationV2() throws Exception { Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/v2").toURI()); Path modelMain = baseDir.resolve("main.smithy"); Path modelTest = baseDir.resolve("test.smithy"); - List modelFiles = ListUtils.of(modelMain, modelTest); - try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) { + try (Harness hs = Harness.builder().paths(modelMain, modelTest).build()) { SmithyProject project = hs.getProject(); String uri = hs.file("main.smithy").toString(); String testUri = hs.file("test.smithy").toString(); @@ -434,7 +426,6 @@ public void shapeIdFromLocationV2() throws Exception { // Position on shape end line, but after char end assertFalse(project.getShapeIdFromLocation(uri, new Position(16, 10)).isPresent()); // Position on shape start line - Optional foo = project.getShapeIdFromLocation(uri, new Position(6, 10)); assertEquals(ShapeId.from("com.foo#SingleLine"), project.getShapeIdFromLocation(uri, new Position(6, 10)).get()); // Position on multi-line shape start line @@ -492,7 +483,7 @@ public void shapeIdFromLocationV2() throws Exception { assertEquals(ShapeId.from("com.foo#Suit$HEART"), project.getShapeIdFromLocation(uri, new Position(160,8)).get()); assertEquals(ShapeId.from("com.example#OtherStructure"), project.getShapeIdFromLocation(testUri, - new Position(7, 15)).get()); + new Position(8, 15)).get()); } } diff --git a/src/test/resources/software/amazon/smithy/lsp/ext/models/jars/smithy-test-traits.jar b/src/test/resources/software/amazon/smithy/lsp/ext/models/jars/smithy-test-traits.jar new file mode 100644 index 00000000..f775bfa6 Binary files /dev/null and b/src/test/resources/software/amazon/smithy/lsp/ext/models/jars/smithy-test-traits.jar differ diff --git a/src/test/resources/software/amazon/smithy/lsp/ext/models/jars/test-traits.smithy b/src/test/resources/software/amazon/smithy/lsp/ext/models/jars/test-traits.smithy new file mode 100644 index 00000000..4a24f099 --- /dev/null +++ b/src/test/resources/software/amazon/smithy/lsp/ext/models/jars/test-traits.smithy @@ -0,0 +1,26 @@ +$version: "1.0" + +namespace ns.test + +use smithy.test#test + +@test() +service Weather { + version: "2022-05-24", + operations: [GetCurrentTime] +} + +@readonly +operation GetCurrentTime { + input: GetCurrentTimeInput, + output: GetCurrentTimeOutput +} + +@input +structure GetCurrentTimeInput {} + +@output +structure GetCurrentTimeOutput { + @required + time: Timestamp, +} diff --git a/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/main.smithy b/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/main.smithy index 93485a2f..9448e754 100644 --- a/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/main.smithy +++ b/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/main.smithy @@ -201,3 +201,16 @@ structure FalseInlinedReversedBarOutput { @trait structure emptyTraitStruct {} + +@emptyTraitStruct +structure UsingLocalTrait {} + +structure MemberWithDocComment { + /// Comment + /// Multi line + @tags(["foo"]) + member: String +} + +@smithy.api#tags(["foo"]) +structure ShapeWithAbsoluteShapeIdTrait {} diff --git a/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/test.smithy b/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/test.smithy index 6654c3a3..165e6232 100644 --- a/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/test.smithy +++ b/src/test/resources/software/amazon/smithy/lsp/ext/models/v2/test.smithy @@ -3,12 +3,14 @@ $version: "2.0" namespace com.example use com.foo#emptyTraitStruct +use com.extras#Extra @emptyTraitStruct structure OtherStructure { foo: String bar: String baz: Integer + qux: Extra }