diff --git a/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/BuildToolTest.java b/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/BuildToolTest.java index 0797bf2..384da80 100644 --- a/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/BuildToolTest.java +++ b/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/BuildToolTest.java @@ -238,7 +238,8 @@ void codeInsightUsesTheSelectedMavenAndGradleContexts() throws Exception { for (var query : List.of( List.of("describe", "module", "java.base"), List.of("describe", "type", "example.Example"), List.of("describe", "member", "example.Example.run"), List.of("describe", "file", "Example.java"), - List.of("describe", "types", "example.Example"), List.of("find", "usages", "example.Example"), + List.of("describe", "types", "example.Example"), List.of("describe", "origin", "example.Example"), + List.of("find", "usages", "example.Example"), List.of("find", "callers", "example.Example.run"), List.of("find", "callees", "example.Example.run"), List.of("find", "impl", "example.Example"), List.of("find", "impl", "example.Example.run"), List.of("find", "supertypes", "example.Example"))) { @@ -248,15 +249,15 @@ void codeInsightUsesTheSelectedMavenAndGradleContexts() throws Exception { var arguments = new ArrayList<>(List.of("-p", selector)); arguments.addAll(query); assertEquals(0, fixture.run(arguments.toArray(String[]::new)), query.toString()); - assertEquals(List.of("--class-path", "dependencies", "--source-path", "sources"), fixture.received.subList(0, 4)); - assertEquals("--qualified-path", fixture.received.get(4)); - assertEquals(query.get(1).equals("file") ? directory.resolve("Example.java").toString() : query.getLast(), fixture.received.getLast()); + var received = query.get(1).equals("origin") ? fixture.toolQueries.getLast() : fixture.received; + assertEquals(List.of("--class-path", "dependencies", "--source-path", "sources"), received.subList(0, 4)); + assertEquals(query.get(1).equals("file") ? directory.resolve("Example.java").toString() : query.getLast(), received.getLast()); assertEquals("test", value(fixture.resolution(), type.equals("maven") ? "--scope" : "--source-set")); if (type.equals("gradle")) assertEquals("runtime", value(fixture.resolution(), "--classpath")); if (query.get(1).equals("impl")) { assertEquals(2, fixture.toolQueries.size()); assertEquals(fixture.received.subList(0, 4), fixture.toolQueries.getFirst().subList(0, 4)); - assertEquals(query.getLast().equals("example.Example") ? "--subtypes" : "--overrides", fixture.received.get(5)); + assertEquals(query.getLast().equals("example.Example") ? "--subtypes" : "--overrides", fixture.received.get(4)); assertEquals(1, fixture.queries.stream().filter(argumentsForJig -> argumentsForJig.contains("--resolve-options")).count()); } } diff --git a/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/CodeInsightTest.java b/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/CodeInsightTest.java index 5ec11d4..7609ce1 100644 --- a/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/CodeInsightTest.java +++ b/src/com.netflix.tools.ja.test/com/netflix/tools/ja/test/CodeInsightTest.java @@ -44,7 +44,7 @@ class CodeInsightTest { @Test void describesExplicitSubjectKindsAndFindsExplicitRelationships() throws Exception { for (var command : List.of("describe", "find")) { - var subjects = command.equals("describe") ? List.of("module", "type", "member", "file", "types") + var subjects = command.equals("describe") ? List.of("module", "type", "member", "file", "types", "origin") : List.of("usages", "callers", "callees", "impl", "supertypes"); for (var subject : subjects) { var invocation = JaInvocation.parse(Path.of(""), new String[] {"-p", ":client", command, subject, "example.Store"}); @@ -67,7 +67,7 @@ void requiresAnExplicitQuestionAndSingleSubject() { @Test void completesSubjectKindsAndQuestions() { - assertEquals(List.of("file", "member", "module", "type", "types"), complete(List.of("describe"), "")); + assertEquals(List.of("file", "member", "module", "origin", "type", "types"), complete(List.of("describe"), "")); assertEquals(List.of("callees", "callers", "impl", "supertypes", "usages"), complete(List.of("find"), "")); assertEquals(List.of("impl"), complete(List.of("find"), "im")); } @@ -92,7 +92,8 @@ void delegatesQuestionsWithTheSelectedCompilationContext(@TempDir Path directory for (var query : List.of( List.of("describe", "module", "java.base"), List.of("describe", "type", "java.lang.String"), List.of("describe", "member", "java.lang.String.repeat"), List.of("describe", "file", "Example.java"), - List.of("describe", "types", "Example.java"), List.of("find", "usages", "java.lang.String"), + List.of("describe", "types", "Example.java"), List.of("describe", "origin", "java.lang.String"), + List.of("find", "usages", "java.lang.String"), List.of("find", "callers", "java.lang.String.repeat"), List.of("find", "callees", "java.lang.String.repeat"), List.of("find", "impl", "java.lang.Runnable"), List.of("find", "supertypes", "java.lang.String"))) { var jist = new Probe("jist"); @@ -104,9 +105,9 @@ void delegatesQuestionsWithTheSelectedCompilationContext(@TempDir Path directory JaInvocation.parse(directory, query.toArray(String[]::new)), InputStream.nullInputStream(), new PrintStream(output), new PrintStream(errors)); assertEquals(0, result, errors.toString()); var expected = new ArrayList<>(List.of("--module-source-path", "example.app=" + directory, "--module", "example.app")); - expected.add("--qualified-path"); if (query.getFirst().equals("describe")) { if (query.get(1).equals("types")) expected.add("--referenced-types"); + else if (query.get(1).equals("origin")) expected.addAll(List.of("-l", "--source", "none", "--qualified-path", "--visibility", "all")); else { if (query.get(1).equals("module")) expected.addAll(List.of("--kind", "module")); else if (query.get(1).equals("member")) expected.addAll(List.of("--kind", "method", "--kind", "field")); @@ -133,7 +134,8 @@ void semanticCompletionUsesTheSelectedProjectContext(@TempDir Path directory) th for (var query : List.of( List.of("describe", "module", "--kind", "module"), List.of("describe", "type", "--kind", "type"), List.of("describe", "member", "--kind", "method", "--kind", "field"), - List.of("describe", "types", "--kind", "type"), List.of("find", "usages", "--usages"), + List.of("describe", "types", "--kind", "type"), List.of("describe", "origin"), + List.of("find", "usages", "--usages"), List.of("find", "callers", "--callers"), List.of("find", "callees", "--callees"), List.of("find", "impl", "--kind", "type", "--kind", "method"), List.of("find", "supertypes", "--supertypes"))) { @@ -165,13 +167,37 @@ void implementationSearchUsesReportedTypeKinds(@TempDir Path directory) throws E var lookup = new ArrayList<>(context); lookup.addAll(List.of("-l", "--kind", "type", "--source", "none", "--visibility", "all", "example.Root")); var query = new ArrayList<>(context); - query.addAll(List.of("--qualified-path", "--subtypes", "example.Root")); + query.addAll(List.of("--subtypes", "example.Root")); assertEquals(List.of(lookup, query), jist.invocations, kind); assertEquals(1, jig.invocations.size()); assertEquals("", output.toString()); } } + @Test + void originReportsOnlyTheExactSymbolAndItsOverloads(@TempDir Path directory) throws Exception { + Files.writeString(directory.resolve("module-info.java"), "module example.app {}"); + var jist = new Probe("jist"); + jist.symbols = """ + class example.Root(/classes/example/Root.class) + method example.Root.run()(/classes/example/Root.class) + method example.Root.run(int)(/classes/example/Root.class) + class example.Root.run.Nested(/classes/example/Root$run$Nested.class) + """; + var output = new ByteArrayOutputStream(); + var errors = new ByteArrayOutputStream(); + + int result = new CommandRunner(ModuleLayer.boot(), ToolRuntime.of(new Probe("jig"), jist), new ToolCatalog(List.of()), () -> null).run( + JaInvocation.parse(directory, new String[] {"describe", "origin", "example.Root.run"}), InputStream.nullInputStream(), + new PrintStream(output), new PrintStream(errors)); + + assertEquals(0, result, errors.toString()); + assertEquals(""" + method example.Root.run()(/classes/example/Root.class) + method example.Root.run(int)(/classes/example/Root.class) + """, output.toString()); + } + @Test void implementationSearchLeavesMethodValidationAndInheritanceToJist(@TempDir Path directory) throws Exception { Files.writeString(directory.resolve("module-info.java"), "module example.app {}"); diff --git a/src/com.netflix.tools.ja/com/netflix/tools/ja/CommandRunner.java b/src/com.netflix.tools.ja/com/netflix/tools/ja/CommandRunner.java index d9bd866..a0b3438 100644 --- a/src/com.netflix.tools.ja/com/netflix/tools/ja/CommandRunner.java +++ b/src/com.netflix.tools.ja/com/netflix/tools/ja/CommandRunner.java @@ -370,9 +370,17 @@ case Browse(var type) -> { resolved, in, out, err); } if (commandLine.command() instanceof Command.Describe(var request)) { - var arguments = insightArguments(request.arguments(commandLine.workingDirectory())); - return toolRunner.run(toolInvocation(commandLine, "jist", arguments), resolutionArguments, - resolved, in, out, err); + var invocation = toolInvocation(commandLine, "jist", request.arguments(commandLine.workingDirectory())); + if (request.subject() != DescribeRequest.Subject.ORIGIN) { + return toolRunner.run(invocation, resolutionArguments, resolved, in, out, err); + } + var origins = new ByteArrayOutputStream(); + int result = toolRunner.run(invocation, resolutionArguments, resolved, in, + new PrintStream(origins, true, StandardCharsets.UTF_8), err); + if (result == 0) { + printExactOrigins(origins.toString(StandardCharsets.UTF_8), request.target(), out); + } + return result; } if (commandLine.command() instanceof Command.Find(var request)) { var arguments = request.arguments(); @@ -385,7 +393,7 @@ case Browse(var type) -> { } arguments = request.implementationArguments(symbols.toString(StandardCharsets.UTF_8)); } - return toolRunner.run(toolInvocation(commandLine, "jist", insightArguments(arguments)), resolutionArguments, + return toolRunner.run(toolInvocation(commandLine, "jist", arguments), resolutionArguments, resolved, in, out, err); } if (commandLine.command() instanceof Source(var symbol)) { @@ -410,14 +418,17 @@ case Browse(var type) -> { throw new IllegalArgumentException("Command " + commandLine.command() + " is not implemented"); } - private static List insightArguments(List arguments) { - if (System.console() != null) { - return arguments; + private static void printExactOrigins(String origins, String target, PrintStream out) { + origins.lines().filter(line -> originSymbol(line).equals(target)).forEach(out::println); + } + + private static String originSymbol(String line) { + int separator = line.indexOf(' '); + if (separator < 0) { + return ""; } - var qualified = new ArrayList(); - qualified.add("--qualified-path"); - qualified.addAll(arguments); - return List.copyOf(qualified); + int signature = line.indexOf('(', separator + 1); + return signature < 0 ? "" : line.substring(separator + 1, signature); } private static JaInvocation toolInvocation(JaInvocation invocation, String tool, List arguments) { diff --git a/src/com.netflix.tools.ja/com/netflix/tools/ja/DescribeRequest.java b/src/com.netflix.tools.ja/com/netflix/tools/ja/DescribeRequest.java index 700b890..3126058 100644 --- a/src/com.netflix.tools.ja/com/netflix/tools/ja/DescribeRequest.java +++ b/src/com.netflix.tools.ja/com/netflix/tools/ja/DescribeRequest.java @@ -26,7 +26,8 @@ public enum Subject { TYPE("type", "Describe a Java type and its API", List.of("--kind", "type", "--kind", "method", "--kind", "field")), MEMBER("member", "Describe a method or field", List.of("--kind", "method", "--kind", "field")), FILE("file", "Describe declarations in a source or class file", List.of("--kind", "type", "--kind", "method", "--kind", "field")), - TYPES("types", "List resolved types for a Java type or source file", List.of("--referenced-types")); + TYPES("types", "List resolved types for a Java type or source file", List.of("--referenced-types")), + ORIGIN("origin", "Show where a Java symbol is resolved", List.of()); private final String command; private final String description; @@ -64,7 +65,7 @@ static Subject parse(String value) { static DescribeRequest parse(List arguments) { if (arguments.size() != 2) { - throw new IllegalArgumentException("Usage: ja describe "); + throw new IllegalArgumentException("Usage: ja describe "); } return new DescribeRequest(Subject.parse(arguments.getFirst()), arguments.getLast()); } @@ -76,11 +77,15 @@ List arguments(Path workingDirectory) { if (subject == Subject.TYPES) { return List.of("--referenced-types", resolvedTarget); } + if (subject == Subject.ORIGIN) { + return List.of("-l", "--source", "none", "--qualified-path", "--visibility", "all", resolvedTarget); + } var arguments = new ArrayList<>(subject.kinds); arguments.addAll(List.of("--source", switch (subject) { case MODULE -> "definition"; case MEMBER -> "doc"; case TYPE, FILE, TYPES -> "signature"; + case ORIGIN -> throw new AssertionError(); })); arguments.add(resolvedTarget); return List.copyOf(arguments);