diff --git a/build.sbt b/build.sbt index 9ec0252a7..721c3b415 100644 --- a/build.sbt +++ b/build.sbt @@ -71,18 +71,16 @@ commands += "scalafixAll --check" :: "publishLocal" :: s } -// Shared module that owns the canonical SemanticDB proto schema and the -// associated symbol/builder utilities. Both the Java compiler plugin -// (semanticdb-javac) and the Kotlin compiler plugin (semanticdb-kotlinc) -// depend on it instead of carrying their own divergent copies of the proto. +// Shared module with the SCIP shard utilities (symbol encoder, document +// builder, on-disk writer) consumed by both the Java compiler plugin +// (semanticdb-javac) and the Kotlin compiler plugin (semanticdb-kotlinc). lazy val semanticdbShared = project .in(file("semanticdb-shared")) .settings( moduleName := "semanticdb-shared", javaOnlySettings, - (Compile / PB.targets) := - Seq(PB.gens.java(V.protobuf) -> (Compile / sourceManaged).value), - libraryDependencies += "com.google.protobuf" % "protobuf-java" % V.protobuf + libraryDependencies += + "org.scip-code" % "scip-java-bindings" % V.scipBindings ) lazy val gradlePlugin = project @@ -329,8 +327,10 @@ lazy val semanticdbKotlinc = project // classpath via Provided so the assembled fat-jar does not bundle it. libraryDependencies += "org.jetbrains.kotlin" % "kotlin-stdlib" % V.kotlinVersion % Provided, - // The SemanticDB proto schema and the generated Java classes live in - // semanticdbShared; we get them transitively via .dependsOn below. + // SCIP message classes come from semanticdbShared (which depends on + // scip-java-bindings); this adds the Kotlin DSL extensions on top. + libraryDependencies += + "org.scip-code" % "scip-kotlin-bindings" % V.scipBindings, // kotlin-compiler-embeddable is supplied by kotlinc at runtime libraryDependencies += "org.jetbrains.kotlin" % "kotlin-compiler-embeddable" % V.kotlinVersion % diff --git a/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/GradleBuildTool.scala b/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/GradleBuildTool.scala index b72a2bd87..765d609f0 100644 --- a/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/GradleBuildTool.scala +++ b/scip-java/src/main/scala/com/sourcegraph/scip_java/buildtools/GradleBuildTool.scala @@ -35,13 +35,13 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { } /** - * Diagnose the case where Gradle finished successfully but our SemanticDB - * compiler plugin never produced any `.semanticdb` files. This used to be - * silently rescued by a `-javaagent` fallback; now it surfaces as a clear - * error pointing at the two known causes. + * Diagnose the case where Gradle finished successfully but our SCIP compiler + * plugin never produced any `.scip` shards. This used to be silently rescued + * by a `-javaagent` fallback; now it surfaces as a clear error pointing at + * the two known causes. */ private def reportMissingSemanticdbOutput(): Unit = { - if (containsFileWithSuffix(targetroot, ".semanticdb")) + if (containsFileWithSuffix(targetroot, ".scip")) return if (!containsFileWithSuffix(index.workingDirectory, ".class")) // Project produced no compiled JVM output — nothing to index, stay quiet. @@ -50,9 +50,9 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { .app .reporter .error( - s"""scip-java: Gradle finished successfully but produced no SemanticDB output in $targetroot. + s"""scip-java: Gradle finished successfully but produced no SCIP shards in $targetroot. | - |This means our SemanticDB compiler plugin was not attached to one or more JavaCompile tasks. Two known causes: + |This means our SCIP compiler plugin was not attached to one or more JavaCompile tasks. Two known causes: | | 1. The 'compileOnly' configuration was already resolved before our init script ran. | Check the Gradle output above for warnings of the form: diff --git a/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/IndexSemanticdbCommand.scala b/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/IndexSemanticdbCommand.scala index 6b1dbcb23..dd66c1f45 100644 --- a/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/IndexSemanticdbCommand.scala +++ b/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/IndexSemanticdbCommand.scala @@ -17,7 +17,7 @@ import moped.cli.Command import moped.cli.CommandParser import org.scip_code.scip.ToolInfo -@Description("Converts SemanticDB files into a single SCIP index file.") +@Description("Aggregates per-source SCIP shards into a single SCIP index file.") @Usage("scip-java index-semanticdb [OPTIONS ...] [POSITIONAL ARGUMENTS ...]") @ExampleUsage( "scip-java index-semanticdb --out=myindex.scip my/targetroot1 my/targetroot2" @@ -26,14 +26,14 @@ import org.scip_code.scip.ToolInfo final case class IndexSemanticdbCommand( @Description("The name of the output file.") output: Path = Paths.get("index.scip"), - @Description("Whether to process the SemanticDB files in parallel") + @Description("Whether to process the SCIP shards in parallel") parallel: Boolean = true, @Description( "Whether to emit parent->child relationships for 'Find references' and 'Find implementations'. " + "This flag exists as a workaround for the issue https://github.com/sourcegraph/sourcegraph/issues/50927" ) emitInverseRelationships: Boolean = true, - @Description("Directories that contain SemanticDB files.") + @Description("Directories that contain SCIP shards.") @PositionalArguments() targetroot: List[Path] = Nil, @Description( diff --git a/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/SnapshotCommand.scala b/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/SnapshotCommand.scala index bf7c79e1a..4908754e8 100644 --- a/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/SnapshotCommand.scala +++ b/scip-java/src/main/scala/com/sourcegraph/scip_java/commands/SnapshotCommand.scala @@ -51,27 +51,35 @@ case class SnapshotCommand( attrs: BasicFileAttributes ): FileVisitResult = { if (scipPattern.matches(file)) { - foundScipFile = true val index = Index.parseFrom(Files.readAllBytes(file)) - val root = URI.create(index.getMetadata.getProjectRoot) - index - .getDocumentsList - .asScala - .foreach { doc => - val sourcepath = Paths.get(root.resolve(doc.getRelativePath)) - val source = - new String( - Files.readAllBytes(sourcepath), - StandardCharsets.UTF_8 + // Per-source SCIP shards under META-INF/scip/ carry no Metadata; + // only the aggregated index does. Skip shards so `scip-java + // snapshot ` doesn't trip over them. + val projectRoot = index.getMetadata.getProjectRoot + if (!projectRoot.isEmpty) { + foundScipFile = true + val root = URI.create(projectRoot) + index + .getDocumentsList + .asScala + .foreach { doc => + val sourcepath = Paths.get( + root.resolve(doc.getRelativePath) ) - val document = ScipPrinters.printTextDocument(doc, source) - val snapshotOutput = output.resolve(doc.getRelativePath) - Files.createDirectories(snapshotOutput.getParent) - Files.write( - snapshotOutput, - document.getBytes(StandardCharsets.UTF_8) - ) - } + val source = + new String( + Files.readAllBytes(sourcepath), + StandardCharsets.UTF_8 + ) + val document = ScipPrinters.printTextDocument(doc, source) + val snapshotOutput = output.resolve(doc.getRelativePath) + Files.createDirectories(snapshotOutput.getParent) + Files.write( + snapshotOutput, + document.getBytes(StandardCharsets.UTF_8) + ) + } + } } super.visitFile(file, attrs) } diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/RangeComparator.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/RangeComparator.java deleted file mode 100644 index 951028c22..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/RangeComparator.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb.Range; - -/** - * Comparator that sorts SemanticDB ranges by appearance in the document. - * - *

We can't guarantee ordering of SymbolOccurrence in SemanticDB payloads so it's good to sort - * them before processing. - */ -public class RangeComparator implements java.util.Comparator { - - @Override - public int compare(Range r1, Range r2) { - int byStartLine = Integer.compare(r1.getStartLine(), r2.getStartLine()); - if (byStartLine != 0) { - return byStartLine; - } - int byStartCharacter = Integer.compare(r1.getStartCharacter(), r2.getStartCharacter()); - if (byStartCharacter != 0) { - return byStartCharacter; - } - int byEndLine = Integer.compare(r1.getEndLine(), r2.getEndLine()); - if (byEndLine != 0) { - return byEndLine; - } - return Integer.compare(r1.getEndCharacter(), r2.getEndCharacter()); - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipSemanticdb.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipSemanticdb.java index 540dd83d6..77ff6fe74 100644 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipSemanticdb.java +++ b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipSemanticdb.java @@ -1,35 +1,53 @@ package com.sourcegraph.scip_semanticdb; import com.google.protobuf.CodedInputStream; -import com.sourcegraph.semanticdb.Semanticdb; -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence; -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence.Role; import com.sourcegraph.semanticdb.SemanticdbSymbols; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; import org.scip_code.scip.Document; import org.scip_code.scip.Index; import org.scip_code.scip.Metadata; import org.scip_code.scip.Occurrence; import org.scip_code.scip.ProtocolVersion; import org.scip_code.scip.Relationship; -import org.scip_code.scip.Signature; import org.scip_code.scip.SymbolInformation; -import org.scip_code.scip.SymbolRole; import org.scip_code.scip.TextEncoding; import org.scip_code.scip.ToolInfo; -import java.io.IOException; -import java.net.URI; -import java.nio.file.*; -import java.util.*; - -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -/** The core logic that converts SemanticDB into SCIP. */ +/** + * Aggregates per-source SCIP shards (one {@link Index} per {@code *.scip} file emitted by the + * compiler plugins) into a single SCIP index. The aggregator: + * + *

+ */ public class ScipSemanticdb { + private static final PathMatcher JAR_PATTERN = + FileSystems.getDefault().getPathMatcher("glob:**.jar"); + private static final PathMatcher SCIP_PATTERN = + FileSystems.getDefault().getPathMatcher("glob:**.scip"); + private final ScipWriter writer; private final ScipSemanticdbOptions options; @@ -45,12 +63,13 @@ public static void run(ScipSemanticdbOptions options) throws IOException { private void run() throws IOException { PackageTable packages = new PackageTable(options); - List files = SemanticdbWalker.findSemanticdbFiles(options); - Collections.sort(files); + SymbolRewriter rewriter = new SymbolRewriter(packages); + List shards = findShards(); + Collections.sort(shards); if (options.reporter.hasErrors()) return; - if (files.isEmpty() && !options.allowEmptyIndex) { + if (shards.isEmpty() && !options.allowEmptyIndex) { options.reporter.error( - "No SemanticDB files found. " + "No SCIP shards found. " + "This typically means that `scip-java` is unable to automatically " + "index this codebase. If you are using Gradle or Maven, please report an issue to " + "https://github.com/sourcegraph/scip-java and include steps to reproduce. " @@ -58,243 +77,43 @@ private void run() throws IOException { + "of the steps from https://sourcegraph.github.io/scip-java/docs/manual-configuration.html"); return; } - options.reporter.startProcessing(files.size()); - runTyped(files, packages); + options.reporter.startProcessing(shards.size()); + writer.emitTyped(metadataIndex()); + + Map> inverseReferences = computeInverseReferences(shards, rewriter); + shardStream(shards).forEach(shard -> processShard(shard, rewriter, inverseReferences)); writer.build(); options.reporter.endProcessing(); } - private void runTyped(List files, PackageTable packages) { - writer.emitTyped(typedMetadata()); - InverseReferenceRelationships references = inverseReferenceRelationships(files); - filesStream(files).forEach(document -> processTypedDocument(document, packages, references)); - } - - private String typedSymbol(String symbol, Package pkg) { - if (symbol.isEmpty()) { - return ""; - } - if (symbol.startsWith("local")) { - return "local " + symbol.substring("local".length()); - } - return "semanticdb maven " + pkg.repoName() + " " + pkg.version() + " " + symbol; - } - - private static SymbolInformation.Kind scipKind(Semanticdb.SymbolInformation info) { - Semanticdb.SymbolInformation.Kind kind = info.getKind(); - int properties = info.getProperties(); - boolean isStatic = (properties & Semanticdb.SymbolInformation.Property.STATIC_VALUE) > 0; - boolean isAbstract = (properties & Semanticdb.SymbolInformation.Property.ABSTRACT_VALUE) > 0; - boolean isEnum = (properties & Semanticdb.SymbolInformation.Property.ENUM_VALUE) > 0; - - switch (kind) { - case CLASS: - if (isEnum) { - return SymbolInformation.Kind.Enum; - } else { - return SymbolInformation.Kind.Class; - } - case CONSTRUCTOR: - return SymbolInformation.Kind.Constructor; - case FIELD: - if (isStatic) { - return SymbolInformation.Kind.StaticField; - } else { - return SymbolInformation.Kind.Field; - } - case INTERFACE: - return SymbolInformation.Kind.Interface; - case LOCAL: - if (isStatic) { - return SymbolInformation.Kind.StaticVariable; - } else { - return SymbolInformation.Kind.Variable; - } - case MACRO: - return SymbolInformation.Kind.Macro; - case METHOD: - if (isStatic) { - return SymbolInformation.Kind.StaticMethod; - } else if (isAbstract) { - return SymbolInformation.Kind.AbstractMethod; - } else { - return SymbolInformation.Kind.Method; - } - case OBJECT: - return SymbolInformation.Kind.Object; - case PACKAGE: - return SymbolInformation.Kind.Package; - case PACKAGE_OBJECT: - return SymbolInformation.Kind.PackageObject; - case PARAMETER: - return SymbolInformation.Kind.Parameter; - case SELF_PARAMETER: - return SymbolInformation.Kind.SelfParameter; - case TRAIT: - return SymbolInformation.Kind.Trait; - case TYPE: - if (isEnum) { - return SymbolInformation.Kind.Enum; - } else { - return SymbolInformation.Kind.Type; - } - case TYPE_PARAMETER: - return SymbolInformation.Kind.TypeParameter; - case UNKNOWN_KIND: - return SymbolInformation.Kind.UnspecifiedKind; - } - - return SymbolInformation.Kind.UnspecifiedKind; - } - - public static boolean isDefinitionRole(Role role) { - return role == Role.DEFINITION || role == Role.SYNTHETIC_DEFINITION; - } - - private void processTypedDocument( - Path path, PackageTable packages, InverseReferenceRelationships references) { - for (ScipTextDocument doc : parseTextDocument(path).collect(Collectors.toList())) { - if (doc.semanticdb.getOccurrencesCount() == 0) { - continue; - } - - Path absolutePath = Paths.get(URI.create(doc.semanticdb.getUri())); - String relativePath = - StreamSupport.stream(options.sourceroot.relativize(absolutePath).spliterator(), false) - .map(p -> p.getFileName().toString()) - .collect(Collectors.joining("/")); - Document.Builder tdoc = Document.newBuilder().setRelativePath(relativePath); - for (SymbolOccurrence occ : doc.sortedSymbolOccurrences()) { - if (occ.getSymbol().isEmpty()) { - continue; - } - int role = 0; - if (isDefinitionRole(occ.getRole())) { - role |= SymbolRole.Definition_VALUE; - } - boolean isSingleLineRange = occ.getRange().getStartLine() == occ.getRange().getEndLine(); - Iterable range = - isSingleLineRange - ? Arrays.asList( - occ.getRange().getStartLine(), - occ.getRange().getStartCharacter(), - occ.getRange().getEndCharacter()) - : Arrays.asList( - occ.getRange().getStartLine(), - occ.getRange().getStartCharacter(), - occ.getRange().getEndLine(), - occ.getRange().getEndCharacter()); - Package pkg = packages.packageForSymbol(occ.getSymbol()).orElse(Package.EMPTY); - Occurrence.Builder occBuilder = - Occurrence.newBuilder() - .addAllRange(range) - .setSymbol(typedSymbol(occ.getSymbol(), pkg)) - .setSymbolRoles(role); - // Add enclosing_range if it exists - if (occ.hasEnclosingRange()) { - Semanticdb.Range enclosingRange = occ.getEnclosingRange(); - boolean isEnclosingSingleLine = - enclosingRange.getStartLine() == enclosingRange.getEndLine(); - Iterable enclosingRangeInts = - isEnclosingSingleLine - ? Arrays.asList( - enclosingRange.getStartLine(), - enclosingRange.getStartCharacter(), - enclosingRange.getEndCharacter()) - : Arrays.asList( - enclosingRange.getStartLine(), - enclosingRange.getStartCharacter(), - enclosingRange.getEndLine(), - enclosingRange.getEndCharacter()); - occBuilder.addAllEnclosingRange(enclosingRangeInts); - } - tdoc.addOccurrences(occBuilder); - } - Symtab symtab = new Symtab(doc.semanticdb); - for (Semanticdb.SymbolInformation info : doc.semanticdb.getSymbolsList()) { - if (info.getSymbol().isEmpty()) { - continue; - } - Package pkg = packages.packageForSymbol(info.getSymbol()).orElse(Package.EMPTY); - SymbolInformation.Builder scipInfo = - SymbolInformation.newBuilder().setSymbol(typedSymbol(info.getSymbol(), pkg)); - - scipInfo.setDisplayName(info.getDisplayName()); - if (!info.getEnclosingSymbol().isEmpty()) { - scipInfo.setEnclosingSymbol(typedSymbol(info.getEnclosingSymbol(), pkg)); - } - - scipInfo.setKind(scipKind(info)); - - // TODO: this can be removed once https://github.com/sourcegraph/sourcegraph/issues/50927 is - // fixed. - ArrayList inverseReferences = references.map.get(info.getSymbol()); - if (inverseReferences != null) { - for (String inverseReference : inverseReferences) { - Package inverseReferencePkg = - packages.packageForSymbol(inverseReference).orElse(Package.EMPTY); - scipInfo.addRelationships( - Relationship.newBuilder() - .setSymbol(typedSymbol(inverseReference, inverseReferencePkg)) - .setIsImplementation(true) - .setIsReference(true)); + /** + * Returns every {@code *.scip} shard under {@code options.targetroots}. A targetroot that happens + * to be a {@code .jar} is included as-is so callers can pick out shards stored inside. + */ + private List findShards() throws IOException { + List shards = new ArrayList<>(); + SimpleFileVisitor visitor = + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (SCIP_PATTERN.matches(file)) shards.add(file); + return FileVisitResult.CONTINUE; } - } - - for (int i = 0; i < info.getDefinitionRelationshipsCount(); i++) { - String definitionSymbol = info.getDefinitionRelationships(i); - if (definitionSymbol.isEmpty()) { - continue; - } - Package definitionSymbolPkg = - packages.packageForSymbol(definitionSymbol).orElse(Package.EMPTY); - Semanticdb.SymbolInformation definitionInfo = symtab.symbols.get(definitionSymbol); - - scipInfo.addRelationships( - Relationship.newBuilder() - .setSymbol(typedSymbol(definitionSymbol, definitionSymbolPkg)) - .setIsDefinition(true) - .setIsReference( - definitionInfo != null - && definitionInfo.getDisplayName().equals(info.getDisplayName()) - && supportsReferenceRelationship(info))); - } - for (int i = 0; i < info.getOverriddenSymbolsCount(); i++) { - String overriddenSymbol = info.getOverriddenSymbols(i); - if (overriddenSymbol.isEmpty()) { - continue; - } - if (isIgnoredOverriddenSymbol(overriddenSymbol)) { - continue; + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + options.reporter.error(exc); + return FileVisitResult.CONTINUE; } - Package overriddenSymbolPkg = - packages.packageForSymbol(overriddenSymbol).orElse(Package.EMPTY); - scipInfo.addRelationships( - Relationship.newBuilder() - .setSymbol(typedSymbol(overriddenSymbol, overriddenSymbolPkg)) - .setIsImplementation(true) - .setIsReference(supportsReferenceRelationship(info))); - } - if (info.hasSignature()) { - String language = - doc.semanticdb.getLanguage().toString().toLowerCase(Locale.ROOT).intern(); - String signature = new SignatureFormatter(info, symtab).formatSymbol(); - Signature.Builder signatureDocumentation = - Signature.newBuilder().setLanguage(language).setText(signature); - scipInfo.setSignatureDocumentation(signatureDocumentation); - } - String documentation = info.getDocumentation().getMessage(); - if (!documentation.isEmpty()) { - scipInfo.addDocumentation(documentation); - } - tdoc.addSymbols(scipInfo); - } - writer.emitTyped(Index.newBuilder().addDocuments(tdoc).build()); + }; + for (Path root : options.targetroots) { + if (JAR_PATTERN.matches(root)) shards.add(root); + else if (Files.isDirectory(root)) Files.walkFileTree(root, visitor); } + return shards; } - private Index typedMetadata() { + private Index metadataIndex() { return Index.newBuilder() .setMetadata( Metadata.newBuilder() @@ -309,123 +128,145 @@ private Index typedMetadata() { .build(); } - private Stream filesStream(List files) { - return options.parallel ? files.parallelStream() : files.stream(); - } - - private static class InverseReferenceRelationships { - public final Map> map; - - private InverseReferenceRelationships(Map> map) { - this.map = map; + private void processShard( + Path shardPath, SymbolRewriter rewriter, Map> inverseReferences) { + for (Document shard : readShard(shardPath)) { + Document rewritten = rewriteDocument(shard, rewriter, inverseReferences); + writer.emitTyped(Index.newBuilder().addDocuments(rewritten).build()); + options.reporter.processedOneItem(); } } - private InverseReferenceRelationships inverseReferenceRelationships(List files) { - if (!options.emitInverseRelationships) { - return new InverseReferenceRelationships(Collections.emptyMap()); + private Document rewriteDocument( + Document shard, SymbolRewriter rewriter, Map> inverseReferences) { + Document.Builder out = + Document.newBuilder() + .setLanguage(shard.getLanguage()) + .setRelativePath(shard.getRelativePath()); + if (!shard.getText().isEmpty()) out.setText(shard.getText()); + + for (Occurrence occ : shard.getOccurrencesList()) { + Occurrence.Builder rebuilt = + Occurrence.newBuilder() + .addAllRange(occ.getRangeList()) + .setSymbol(rewriter.rewrite(occ.getSymbol())) + .setSymbolRoles(occ.getSymbolRoles()); + if (occ.getEnclosingRangeCount() > 0) { + rebuilt.addAllEnclosingRange(occ.getEnclosingRangeList()); + } + out.addOccurrences(rebuilt); } - return new InverseReferenceRelationships( - filesStream(files) - .flatMap(this::parseTextDocument) - .flatMap(this::referenceRelationships) - .collect( - Collectors.groupingBy( - SymbolRelationship::getTo, - Collectors.mapping( - SymbolRelationship::getFrom, Collectors.toCollection(ArrayList::new))))); - } - private Stream referenceRelationships(ScipTextDocument document) { - ArrayList relationships = new ArrayList<>(); - for (int i = 0; i < document.semanticdb.getSymbolsCount(); i++) { - Semanticdb.SymbolInformation info = document.semanticdb.getSymbols(i); - if (!supportsReferenceRelationship(info)) { - continue; + for (SymbolInformation info : shard.getSymbolsList()) { + SymbolInformation.Builder rebuilt = + SymbolInformation.newBuilder() + .setSymbol(rewriter.rewrite(info.getSymbol())) + .setDisplayName(info.getDisplayName()) + .setKind(info.getKind()); + if (info.hasSignatureDocumentation()) { + rebuilt.setSignatureDocumentation(info.getSignatureDocumentation()); + } + if (!info.getEnclosingSymbol().isEmpty()) { + rebuilt.setEnclosingSymbol(rewriter.rewrite(info.getEnclosingSymbol())); + } + for (String doc : info.getDocumentationList()) rebuilt.addDocumentation(doc); + for (Relationship rel : info.getRelationshipsList()) { + rebuilt.addRelationships( + Relationship.newBuilder(rel).setSymbol(rewriter.rewrite(rel.getSymbol()))); } - if (info.getSymbol().isEmpty() || SemanticdbSymbols.isLocal(info.getSymbol())) { - continue; + List inverse = inverseReferences.get(info.getSymbol()); + if (inverse != null) { + for (String overrider : inverse) { + rebuilt.addRelationships( + Relationship.newBuilder() + .setSymbol(rewriter.rewrite(overrider)) + .setIsImplementation(true) + .setIsReference(true)); + } } - for (int j = 0; j < info.getOverriddenSymbolsCount(); j++) { - String overriddenSymbol = info.getOverriddenSymbols(j); - if (SemanticdbSymbols.isLocal(overriddenSymbol)) { - continue; + out.addSymbols(rebuilt); + } + return out.build(); + } + + /** + * Builds {@code overridden-symbol → [overriding-symbols]} for every method-style relationship. + * Class/interface parent relationships are excluded because they shouldn't surface as + * find-references results (TODO: drop once sourcegraph#50927 is fixed). + */ + private Map> computeInverseReferences( + List shards, SymbolRewriter rewriter) { + if (!options.emitInverseRelationships) return Collections.emptyMap(); + Map> result = new HashMap<>(); + for (Path shard : shards) { + for (Document doc : readShard(shard)) { + for (SymbolInformation info : doc.getSymbolsList()) { + if (!supportsReferenceRelationship(info)) continue; + if (info.getSymbol().isEmpty() || SemanticdbSymbols.isLocal(info.getSymbol())) continue; + for (Relationship rel : info.getRelationshipsList()) { + if (!rel.getIsImplementation()) continue; + if (SemanticdbSymbols.isLocal(rel.getSymbol())) continue; + if (isIgnoredOverriddenSymbol(rel.getSymbol())) continue; + result.computeIfAbsent(rel.getSymbol(), k -> new ArrayList<>()).add(info.getSymbol()); + } } - relationships.add(new SymbolRelationship(info.getSymbol(), overriddenSymbol)); } } - return relationships.stream(); + return result; } - private static boolean supportsReferenceRelationship(Semanticdb.SymbolInformation info) { + private static boolean supportsReferenceRelationship(SymbolInformation info) { switch (info.getKind()) { - case INTERFACE: - case TYPE: - case CLASS: - case OBJECT: - case PACKAGE_OBJECT: + case Class: + case Enum: + case Interface: + case Type: + case Object: + case PackageObject: return false; default: return true; } } - private Stream parseTextDocument(Path semanticdbPath) { - try { - return textDocumentsParseFrom(semanticdbPath).getDocumentsList().stream() - .filter(sdb -> !sdb.getOccurrencesList().isEmpty()) - .map(sdb -> new ScipTextDocument(semanticdbPath, sdb, options.sourceroot)); - } catch (IOException e) { - options.reporter.error("invalid protobuf: " + semanticdbPath); - options.reporter.error(e); - return Stream.empty(); - } + private static boolean isIgnoredOverriddenSymbol(String symbol) { + // Skip java/lang/Object# from cross-shard reference relationships; it's the parent + // of every class and would dominate "find implementations" results. + return symbol.endsWith("java/lang/Object#"); } - private static PathMatcher jarPattern = FileSystems.getDefault().getPathMatcher("glob:**.jar"); + private Stream shardStream(List shards) { + return options.parallel ? shards.parallelStream() : shards.stream(); + } - private Semanticdb.TextDocuments textDocumentsParseFrom(Path semanticdbPath) throws IOException { - if (jarPattern.matches(semanticdbPath)) { - return textDocumentsParseJarFile(semanticdbPath); + private Collection readShard(Path shardPath) { + try { + if (JAR_PATTERN.matches(shardPath)) return readShardsFromJar(shardPath); + return Index.parseFrom(parseFromBytes(Files.readAllBytes(shardPath))).getDocumentsList(); + } catch (IOException e) { + options.reporter.error("invalid SCIP shard: " + shardPath); + options.reporter.error(e); + return Collections.emptyList(); } - return textDocumentsParseFromBytes(Files.readAllBytes(semanticdbPath)); } - private Semanticdb.TextDocuments textDocumentsParseJarFile(Path jarFile) throws IOException { - Semanticdb.TextDocuments.Builder result = Semanticdb.TextDocuments.newBuilder(); - try (JarFile file = new JarFile(jarFile.toFile())) { - Enumeration entries = file.entries(); + private Collection readShardsFromJar(Path jarFile) throws IOException { + List result = new ArrayList<>(); + try (JarFile jar = new JarFile(jarFile.toFile())) { + Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { - JarEntry element = entries.nextElement(); - if (element.getName().endsWith(".semanticdb")) { - byte[] bytes = InputStreamBytes.readAll(file.getInputStream(element)); - result.addAllDocuments(textDocumentsParseFromBytes(bytes).getDocumentsList()); - } + JarEntry entry = entries.nextElement(); + if (!entry.getName().endsWith(".scip")) continue; + byte[] bytes = InputStreamBytes.readAll(jar.getInputStream(entry)); + result.addAll(Index.parseFrom(parseFromBytes(bytes)).getDocumentsList()); } } - return result.build(); - } - - private Semanticdb.TextDocuments textDocumentsParseFromBytes(byte[] bytes) throws IOException { - try { - CodedInputStream in = CodedInputStream.newInstance(bytes); - in.setRecursionLimit(1000); - return Semanticdb.TextDocuments.parseFrom(in); - } catch (NoSuchMethodError ignored) { - // NOTE(olafur): For some reason, NoSuchMethodError gets thrown when running - // `snapshots/run` - // in the sbt build. I'm unable to reproduce the error in `snapshots/test` or - // when running the - // published version - // of `scip-java index`. - return Semanticdb.TextDocuments.parseFrom(bytes); - } + return result; } - private boolean isIgnoredOverriddenSymbol(String symbol) { - // Skip java/lang/Object# and similar symbols from Scala since it's the parent - // of all classes - // making it noisy for "find implementations" results. - return symbol.equals("java/lang/Object#"); + private static CodedInputStream parseFromBytes(byte[] bytes) { + CodedInputStream in = CodedInputStream.newInstance(bytes); + in.setRecursionLimit(1000); + return in; } } diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipTextDocument.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipTextDocument.java deleted file mode 100644 index 13c5dddae..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/ScipTextDocument.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -/** Wrapper around a SemanticDB TextDocument with SCIP-related utilities. */ -public class ScipTextDocument { - public final Path semanticdbPath; - public final Semanticdb.TextDocument semanticdb; - - public ScipTextDocument( - Path semanticdbPath, Semanticdb.TextDocument semanticdb, Path sourceroot) { - this.semanticdbPath = semanticdbPath; - String uri = sourceroot.resolve(semanticdb.getUri()).toUri().toString(); - this.semanticdb = Semanticdb.TextDocument.newBuilder(semanticdb).setUri(uri).build(); - } - - @Override - public String toString() { - return "ScipDocument{" + "path=" + semanticdbPath + ", semanticdb=" + semanticdb + '}'; - } - - public List sortedSymbolOccurrences() { - return ScipTextDocument.sortedSymbolOccurrences(semanticdb); - } - - public static List sortedSymbolOccurrences( - Semanticdb.TextDocument semanticdb) { - ArrayList result = - new ArrayList<>(semanticdb.getOccurrencesList().size()); - result.addAll(semanticdb.getOccurrencesList()); - for (Semanticdb.Synthetic synthetic : semanticdb.getSyntheticsList()) { - addAllSyntheticOccurrences(synthetic, result); - } - result.sort((o1, o2) -> new RangeComparator().compare(o1.getRange(), o2.getRange())); - return result; - } - - private static void addAllSyntheticOccurrences( - Semanticdb.Synthetic synthetic, ArrayList buffer) { - Semanticdb.Range offsetRange = - Semanticdb.Range.newBuilder(synthetic.getRange()) - .setStartLine(synthetic.getRange().getEndLine()) - .setStartCharacter(synthetic.getRange().getEndCharacter()) - .build(); - new SemanticdbTreeVisitor() { - @Override - void visitIdTree(Semanticdb.IdTree tree) { - Semanticdb.SymbolOccurrence syntheticOccurrence = - Semanticdb.SymbolOccurrence.newBuilder() - .setRange(offsetRange) - .setSymbol(tree.getSymbol()) - .setRole(Semanticdb.SymbolOccurrence.Role.REFERENCE) - .build(); - buffer.add(syntheticOccurrence); - } - }.visitTree(synthetic.getTree()); - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SemanticdbTreeVisitor.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SemanticdbTreeVisitor.java deleted file mode 100644 index ca9b296cd..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SemanticdbTreeVisitor.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb.*; - -public abstract class SemanticdbTreeVisitor { - public void visitTree(Tree tree) { - if (tree.hasApplyTree()) { - this.visitApplyTree(tree.getApplyTree()); - } else if (tree.hasFunctionTree()) { - this.visitFunctionTree(tree.getFunctionTree()); - } else if (tree.hasIdTree()) { - this.visitIdTree(tree.getIdTree()); - } else if (tree.hasLiteralTree()) { - this.visitLiteralTree(tree.getLiteralTree()); - } else if (tree.hasMacroExpansionTree()) { - this.visitMacroExpansionTree(tree.getMacroExpansionTree()); - } else if (tree.hasOriginalTree()) { - this.visitOriginalTree(tree.getOriginalTree()); - } else if (tree.hasSelectTree()) { - this.visitSelectTree(tree.getSelectTree()); - } else if (tree.hasTypeApplyTree()) { - this.visitTypeApplyTree(tree.getTypeApplyTree()); - } else if (tree.hasAnnotationTree()) { - this.visitAnnotationTree(tree.getAnnotationTree()); - } else if (tree.hasAssignTree()) { - this.visitAssignTree(tree.getAssignTree()); - } else if (tree.hasBinopTree()) { - this.visitBinaryOperatorTree(tree.getBinopTree()); - } - } - - void visitApplyTree(ApplyTree tree) { - visitTree(tree.getFunction()); - for (Tree argument : tree.getArgumentsList()) { - visitTree(argument); - } - } - - void visitFunctionTree(FunctionTree tree) { - for (IdTree parameter : tree.getParametersList()) { - visitIdTree(parameter); - } - visitTree(tree.getBody()); - } - - void visitIdTree(IdTree tree) {} - - void visitLiteralTree(LiteralTree tree) {} - - void visitMacroExpansionTree(MacroExpansionTree tree) { - visitTree(tree.getBeforeExpansion()); - } - - void visitOriginalTree(OriginalTree tree) {} - - void visitSelectTree(SelectTree tree) { - visitTree(tree.getQualifier()); - visitIdTree(tree.getId()); - } - - void visitTypeApplyTree(TypeApplyTree tree) { - visitTree(tree.getFunction()); - } - - void visitAnnotationTree(AnnotationTree tree) { - for (Tree parameter : tree.getParametersList()) { - visitTree(parameter); - } - } - - void visitAssignTree(AssignTree tree) { - visitTree(tree.getLhs()); - visitTree(tree.getRhs()); - } - - void visitBinaryOperatorTree(BinaryOperatorTree tree) { - visitTree(tree.getLhs()); - visitTree(tree.getRhs()); - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SemanticdbWalker.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SemanticdbWalker.java deleted file mode 100644 index c0cd0a717..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SemanticdbWalker.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.List; - -/** A file visitor that recursively collects all SemanticDB files in a given directory. */ -public class SemanticdbWalker extends SimpleFileVisitor { - private final ArrayList result; - private final ScipSemanticdbOptions options; - private final PathMatcher semanticdbPattern = - FileSystems.getDefault().getPathMatcher("glob:**.semanticdb"); - - public SemanticdbWalker(ScipSemanticdbOptions options) { - this.options = options; - result = new ArrayList<>(); - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (semanticdbPattern.matches(file)) { - result.add(file); - } - return super.visitFile(file, attrs); - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - options.reporter.error(exc); - return FileVisitResult.CONTINUE; - } - - public static List findSemanticdbFiles(ScipSemanticdbOptions options) throws IOException { - SemanticdbWalker walker = new SemanticdbWalker(options); - PathMatcher jarPattern = FileSystems.getDefault().getPathMatcher("glob:**.jar"); - for (Path root : options.targetroots) { - if (jarPattern.matches(root)) { - walker.result.add(root); - } else { - Files.walkFileTree(root, walker); - } - } - return walker.result; - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SignatureFormatter.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SignatureFormatter.java deleted file mode 100644 index 216c2adb3..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SignatureFormatter.java +++ /dev/null @@ -1,822 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb.SymbolInformation.Property; -import com.sourcegraph.semanticdb.Semanticdb.*; - -import com.sourcegraph.semanticdb.SemanticdbSymbols; - -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import static com.sourcegraph.semanticdb.SemanticdbBuilders.typeRef; - -public class SignatureFormatter { - private static final Type OBJECT_TYPE_REF = typeRef("java/lang/Object#"); - private static final Type PRODUCT_TYPE_REF = typeRef("scala/Product#"); - private static final Type SCALA_SERIALIZABLE_TYPE_REF = typeRef("scala/package.Serializable#"); - private static final Type JAVA_SERIALIZABLE_TYPE_REF = typeRef("java/util/Serializable#"); - private static final Type SCALA_ANY_TYPE_REF = typeRef("scala/Any#"); - private static final Type SCALA_ANYREF_TYPE_REF = typeRef("scala/AnyRef#"); - - private static final Type WILDCARD_TYPE_REF = typeRef("local_wildcard"); - - private static final Type NOTHING_SYMBOL = typeRef("scala/Nothing#"); - private static final String FUNCTION_SYMBOL_PREFIX = "scala/Function"; - // Special case scala/Function object to not conflict with Function1 for example - private static final String FUNCTION_OBJECT = "scala/Function."; - private static final String TUPLE_SYMBOL_PREFIX = "scala/Tuple"; - private static final String ARRAY_SYMBOL = "scala/Array#"; - private static final String ENUM_SYMBOL = "java/lang/Enum#"; - private static final String ANNOTATION_SYMBOL = "java/lang/annotation/Annotation#"; - private static final Set REDUNDANT_CLASS_PARENTS = new HashSet<>(); - private static final Set CASE_CLASS_PARENTS = new HashSet<>(); - - { - REDUNDANT_CLASS_PARENTS.add(OBJECT_TYPE_REF); - REDUNDANT_CLASS_PARENTS.add(SCALA_ANY_TYPE_REF); - REDUNDANT_CLASS_PARENTS.add(SCALA_ANYREF_TYPE_REF); - - CASE_CLASS_PARENTS.add(PRODUCT_TYPE_REF); - CASE_CLASS_PARENTS.add(SCALA_SERIALIZABLE_TYPE_REF); - CASE_CLASS_PARENTS.add(JAVA_SERIALIZABLE_TYPE_REF); - } - - private final StringBuilder s = new StringBuilder(); - private final SymbolInformation symbolInformation; - private final Symtab symtab; - private final boolean isScala; - - public SignatureFormatter(SymbolInformation symbolInformation, Symtab symtab) { - this.symbolInformation = symbolInformation; - this.symtab = symtab; - this.isScala = symbolInformation.getLanguage() == Language.SCALA; - } - - public String formatSymbol() { - try { - return formatSymbolUnsafe(); - } catch (Exception e) { - throw new SignatureFormatterException(symbolInformation, e); - } - } - - private String formatSymbolUnsafe() { - Signature signature = symbolInformation.getSignature(); - if (signature.hasClassSignature()) { - formatClassSignature(signature.getClassSignature()); - } else if (signature.hasMethodSignature()) { - formatMethodSignature(signature.getMethodSignature()); - } else if (signature.hasValueSignature()) { - formatValueSignature(signature.getValueSignature()); - } else if (signature.hasTypeSignature()) { - formatTypeParameterSignature(signature.getTypeSignature()); - } - return s.toString(); - } - - private void formatClassSignature(ClassSignature classSignature) { - boolean isAnnotation = - classSignature.getParentsList().stream() - .anyMatch(t -> t.getTypeRef().getSymbol().equals(ANNOTATION_SYMBOL)); - - boolean isEnum = has(Property.ENUM); - boolean isInterface = symbolInformation.getKind() == SymbolInformation.Kind.INTERFACE; - - printKeywordln(formatAnnotations()); - - printKeyword(formatAccess()); - if (!isEnum && !isAnnotation && !isInterface) printKeyword(formatModifiers()); - - switch (symbolInformation.getKind()) { - case CLASS: - if (isEnum) { - printKeyword("enum"); - } else { - printKeyword("class"); - } - break; - case INTERFACE: - if (isAnnotation) { - printKeyword("@interface"); - break; - } - printKeyword("interface"); - break; - case OBJECT: - printKeyword("object"); - break; - case TRAIT: - printKeyword("trait"); - break; - case PACKAGE_OBJECT: - printKeyword("package object"); - break; - default: - break; - } - s.append(symbolInformation.getDisplayName()); - if (symbolInformation.getKind() == SymbolInformation.Kind.CLASS && has(Property.CASE)) { - primaryConstructor(classSignature) - .ifPresent( - constructorSignature -> - formatScalaParameterList(constructorSignature.getParameterListsList())); - } - - List typeParameters = getSymlinks(classSignature.getTypeParameters()); - if (!typeParameters.isEmpty()) { - s.append( - typeParameters.stream() - .map(this::formatTypeParameter) - .collect(Collectors.joining(", ", isScala ? "[" : "<", isScala ? "]" : ">"))); - } - - boolean hasNonRedundantParent = - classSignature.getParentsList().size() > 0 - && !REDUNDANT_CLASS_PARENTS.contains(classSignature.getParentsList().get(0)); - - boolean isCaseClass = - isScala - && symbolInformation.getKind() == SymbolInformation.Kind.CLASS - && has(Property.CASE); - - List nonSyntheticParents = - classSignature.getParentsList().stream() - .filter(parent -> !REDUNDANT_CLASS_PARENTS.contains(parent)) - .filter(parent -> !parent.getTypeRef().getSymbol().equals(ENUM_SYMBOL)) - .filter(parent -> isCaseClass && !CASE_CLASS_PARENTS.contains(parent)) - .filter(parent -> !parent.getTypeRef().getSymbol().equals(ANNOTATION_SYMBOL)) - .collect(Collectors.toList()); - - if (nonSyntheticParents.isEmpty()) return; - - if (isScala) { - printKeyword(" extends"); - s.append( - nonSyntheticParents.stream().map(this::formatType).collect(Collectors.joining(" with "))); - return; - } - - // Determine which parents from ClassSignature.parents are classes or interfaces so we know to - // use - // 'extends' or 'implements'. - // The logic is as follows: - // 1. If the symbol has type CLASS, there will always be at least 1 parent. For enums, this is - // java/lang/Enum#, otherwise it is java/lang/Object# if no superclass is specified. - // Therefore, if the parents list contains java/lang/Object# type or the symbol is an enum, - // then no superclass was defined and all parents are interfaces and we must print - // 'implements' - // followed by all superinterfaces. - // Else if it is not an enum and the list of non-synthetic parents is non empty, a superclass - // was specified and we must print it with the 'extends' keyword prefixed, followed by - // 'implements' and all superinterfaces, if any. - // 2. If the symbol has type INTERFACE, then any defined parents must also be interfaces, so if - // the list of non-synthetic parents is not empty, print 'implements' and all - // superinterfaces. - switch (symbolInformation.getKind()) { - case CLASS: - // if no superclass or is an enum, every non synthetic parent is an interface - if (isEnum || !hasNonRedundantParent) { - printKeyword(" implements"); - - String superInterfaces = - nonSyntheticParents.stream().map(this::formatType).collect(Collectors.joining(", ")); - s.append(superInterfaces); - } else { - // else if has a superclass and is not an enum - printKeyword(" extends"); - s.append(formatType(nonSyntheticParents.get(0))); - - String superInterfaces = - nonSyntheticParents.stream() - .skip(1) - .map(this::formatType) - .collect(Collectors.joining(", ")); - if (!superInterfaces.isEmpty()) { - printKeyword(" implements"); - s.append(superInterfaces); - } - } - break; - case INTERFACE: - // can only extend other interfaces - printKeyword(" extends"); - - String superInterfaces = - nonSyntheticParents.stream().map(this::formatType).collect(Collectors.joining(", ")); - s.append(superInterfaces); - } - } - - private void formatMethodSignature(MethodSignature methodSignature) { - if (isScala) { - formatScalaMethodSignature(methodSignature); - return; - } - - printKeywordln(formatAnnotations()); - printKeyword(formatAccess()); - printKeyword(formatModifiers()); - - List typeParameters = getSymlinks(methodSignature.getTypeParameters()); - if (!typeParameters.isEmpty()) { - printKeyword( - typeParameters.stream() - .map(this::formatTypeParameter) - .collect(Collectors.joining(", ", "<", ">"))); - } - - if (symbolInformation.getKind() == SymbolInformation.Kind.CONSTRUCTOR) { - String owner = SymbolDescriptor.parseFromSymbol(symbolInformation.getSymbol()).owner; - // Fix for https://github.com/sourcegraph/scip-java/issues/150 - if (!owner.equals(SemanticdbSymbols.NONE)) { - s.append(SymbolDescriptor.parseFromSymbol(owner).descriptor.name); - } - } else { - printKeyword(formatType(methodSignature.getReturnType())); - s.append(symbolInformation.getDisplayName()); - } - - s.append( - methodSignature.getParameterListsList().stream() - .flatMap((params) -> getSymlinks(params).stream()) - .map(this::formatTermParameter) - .collect(Collectors.joining(", ", "(", ")"))); - - if (!methodSignature.getThrowsList().isEmpty()) { - printKeyword(" throws"); - s.append( - methodSignature.getThrowsList().stream() - .map(this::formatType) - .collect(Collectors.joining(", "))); - } - } - - private String formatTermParameter(SymbolInformation info) { - if (info == null) return ""; - if (isScala) { - return info.getDisplayName() - + ": " - + formatType(info.getSignature().getValueSignature().getTpe()); - } - return formatType(info.getSignature().getValueSignature().getTpe()) - + " " - + info.getDisplayName(); - } - - private void formatScalaMethodSignature(MethodSignature methodSignature) { - printKeywordln(formatAnnotations()); - printKeyword(formatAccess()); - printKeyword(formatModifiers()); - if (has(Property.VAL)) { - printKeyword("val"); - } else if (has(Property.VAR)) { - printKeyword("var"); - } else { - printKeyword("def"); - } - s.append( - symbolInformation.getKind() == SymbolInformation.Kind.CONSTRUCTOR - ? "this" - : symbolInformation.getDisplayName()); - formatScalaParameterList(methodSignature.getParameterListsList()); - if (symbolInformation.getKind() != SymbolInformation.Kind.CONSTRUCTOR) { - printKeyword(":"); - s.append(this.formatType(methodSignature.getReturnType())); - } - } - - private Optional primaryConstructor(ClassSignature classSignature) { - Symtab scopeSymtab = symtab.withHardlinks(classSignature.getDeclarations()); - int n = classSignature.getDeclarations().getSymlinksCount(); - for (int i = 0; i < n; i++) { - String symlink = classSignature.getDeclarations().getSymlinks(i); - SymbolInformation info = scopeSymtab.symbols.get(symlink); - if (info != null - && info.getKind() == SymbolInformation.Kind.CONSTRUCTOR - && has(Property.PRIMARY, info) - && info.hasSignature() - && info.getSignature().hasMethodSignature()) { - return Optional.of(info.getSignature().getMethodSignature()); - } - } - return Optional.empty(); - } - - private void formatScalaParameterList(List parameterList) { - for (Scope scope : parameterList) { - List infos = - scope.getHardlinksCount() > 0 ? scope.getHardlinksList() : getSymlinks(scope); - s.append( - infos.stream() - .map(this::formatTermParameter) - .collect(Collectors.joining(", ", "(", ")"))); - } - } - - private void formatValueSignature(ValueSignature valueSignature) { - printKeywordln(formatAnnotations()); - if (isEnumConstant()) { - String ownerSym = SymbolDescriptor.parseFromSymbol(symbolInformation.getSymbol()).owner; - SymbolInformation ownerInfo = symtab.symbols.get(ownerSym); - List enumConstants = - getSymlinks(ownerInfo.getSignature().getClassSignature().getDeclarations()).stream() - .filter(Objects::nonNull) - .filter(this::isEnumConstant) - .collect(Collectors.toList()); - int ordinal = enumConstants.indexOf(symbolInformation); - s.append(ownerInfo.getDisplayName()).append('.'); - s.append(this.symbolInformation.getDisplayName()); - s.append(" /* ordinal ").append(ordinal).append(" */"); - } else { - printKeyword(formatAccess()); - printKeyword(formatModifiers()); - if (isScala) { - s.append(symbolInformation.getDisplayName()); - printKeyword(":"); - printKeyword(formatType(valueSignature.getTpe())); - } else { - printKeyword(formatType(valueSignature.getTpe())); - s.append(symbolInformation.getDisplayName()); - } - } - } - - private void formatTypeParameterSignature(TypeSignature typeSignature) { - if (isScala && symbolInformation.getKind() == SymbolInformation.Kind.TYPE) { - printKeyword("type"); - } - s.append(symbolInformation.getDisplayName()); - if (typeSignature.hasLowerBound() - && (!isScala || !typeSignature.getLowerBound().equals(NOTHING_SYMBOL))) { - printKeyword(isScala ? " >:" : " super"); - s.append(formatType(typeSignature.getLowerBound())); - } - if (typeSignature.hasUpperBound() - && !typeSignature.getUpperBound().equals(isScala ? SCALA_ANY_TYPE_REF : OBJECT_TYPE_REF)) { - printKeyword(isScala ? " <:" : " extends"); - s.append(formatType(typeSignature.getUpperBound())); - } - } - - /** - * Transforms symlinks from a Scope into a List of SymbolInformation's looked up in the Symtab. - */ - private List getSymlinks(Scope scope) { - ArrayList symlinks = new ArrayList<>(); - for (int i = 0; i < scope.getSymlinksCount(); i++) { - SymbolInformation info = symtab.symbols.get(scope.getSymlinks(i)); - if (info != null) { - symlinks.add(info); - } - } - return symlinks; - } - - /** - * Formats one of a method's/class's type parameter symbols through recursion from the - * SymbolInformation extracted from the Symtab. This works by the signature being a TypeSignature. - */ - private String formatTypeParameter(SymbolInformation typeInfo) { - return new SignatureFormatter(typeInfo, symtab).formatSymbol(); - } - - private String formatTypeArguments(List typeArguments) { - if (typeArguments.isEmpty()) return ""; - - return typeArguments.stream() - .map(this::formatType) - .collect(Collectors.joining(", ", isScala ? "[" : "<", isScala ? "]" : ">")); - } - - private String formatAnnotations() { - return formatAnnotations(symbolInformation); - } - - private String formatAnnotations(SymbolInformation symInfo) { - return symInfo.getAnnotationsList().stream() - .map(this::formatAnnotation) - .collect(Collectors.joining("\n")); - } - - private String formatAnnotation(AnnotationTree annotation) { - StringBuilder b = new StringBuilder(); - b.append('@'); - b.append(formatType(annotation.getTpe())); - - if (annotation.getParametersCount() == 1) { - b.append('('); - - Tree parameter = annotation.getParameters(0); - // if only 1 parameter, and its LHS is named 'value', we can omit the 'value = ' - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.3 - AssignTree firstParam = parameter.getAssignTree(); - if (parameter.hasAssignTree() - && SymbolDescriptor.parseFromSymbol(firstParam.getLhs().getIdTree().getSymbol()) - .descriptor - .name - .equals("value")) { - b.append(formatTree(firstParam.getRhs())); - } else { - b.append(formatTree(parameter)); - } - - b.append(')'); - } else if (annotation.getParametersCount() > 1) { - b.append('('); - String parameter = - annotation.getParametersList().stream() - .map(this::formatTree) - .collect(Collectors.joining(", ")); - b.append(parameter); - b.append(')'); - } - - return b.toString(); - } - - private String formatTree(Tree tree) { - if (tree.hasIdTree()) { - return SymbolDescriptor.parseFromSymbol(tree.getIdTree().getSymbol()).descriptor.name; - } else if (tree.hasLiteralTree()) { - return formatConstant(tree.getLiteralTree().getConstant()); - } else if (tree.hasSelectTree()) { - return formatTree(tree.getSelectTree().getQualifier()) - + "." - + SymbolDescriptor.parseFromSymbol(tree.getSelectTree().getId().getSymbol()) - .descriptor - .name; - } else if (tree.hasAnnotationTree()) { - return formatAnnotation(tree.getAnnotationTree()); - } else if (tree.hasApplyTree()) { - if (tree.getApplyTree().getFunction().hasIdTree() - && tree.getApplyTree().getFunction().getIdTree().getSymbol().equals(ARRAY_SYMBOL)) { - return tree.getApplyTree().getArgumentsList().stream() - .map(this::formatTree) - .collect(Collectors.joining(", ", "{", "}")); - } else { - throw new IllegalArgumentException( - "unexpected apply tree function " + tree.getApplyTree().getFunction()); - } - } else if (tree.hasBinopTree()) { - return formatTree(tree.getBinopTree().getLhs()) - + " " - + formatBinaryOperator(tree.getBinopTree().getOp()) - + " " - + formatTree(tree.getBinopTree().getRhs()); - } else if (tree.hasAssignTree()) { - return formatTree(tree.getAssignTree().getLhs()) - + " = " - + formatTree(tree.getAssignTree().getRhs()); - } else if (tree.hasUnaryopTree()) { - return formatUnaryOperation(tree.getUnaryopTree()); - } else if (tree.hasCastTree()) { - return "(" - + formatType(tree.getCastTree().getTpe()) - + ")" - + " " - + formatTree(tree.getCastTree().getValue()); - } - - throw new IllegalArgumentException("tree was of unexpected type " + tree); - } - - private String formatUnaryOperation(UnaryOperatorTree tree) { - String formattedValue = formatTree(tree.getTree()); - switch (tree.getOp()) { - case UNARY_MINUS: - return "-" + formattedValue; - case UNARY_PLUS: - return "-" + formattedValue; - case UNARY_POSTFIX_INCREMENT: - return formattedValue + "++"; - case UNARY_POSTFIX_DECREMENT: - return formattedValue + "--"; - case UNARY_PREFIX_DECREMENT: - return "--" + formattedValue; - case UNARY_PREFIX_INCREMENT: - return "++" + formattedValue; - - case UNARY_BITWISE_COMPLEMENT: - return "~" + formattedValue; - case UNARY_LOGICAL_COMPLEMENT: - return "!" + formattedValue; - } - - throw new IllegalArgumentException( - "unary operation of unexpected type" + tree.getOp().toString()); - } - - private String formatConstant(Constant constant) { - if (constant.hasUnitConstant()) { - return isScala ? "()" : "scala.Unit()"; - } else if (constant.hasBooleanConstant()) { - return Boolean.toString(constant.getBooleanConstant().getValue()); - } else if (constant.hasByteConstant()) { - return Integer.toString(constant.getByteConstant().getValue()); - } else if (constant.hasShortConstant()) { - return Integer.toString(constant.getShortConstant().getValue()); - } else if (constant.hasCharConstant()) { - return String.format("'%s'", (char) constant.getCharConstant().getValue()); - } else if (constant.hasIntConstant()) { - return Integer.toString(constant.getIntConstant().getValue()); - } else if (constant.hasLongConstant()) { - return Long.toString(constant.getLongConstant().getValue()); - } else if (constant.hasFloatConstant()) { - return Float.toString(constant.getFloatConstant().getValue()) + 'f'; - } else if (constant.hasDoubleConstant()) { - return Double.toString(constant.getDoubleConstant().getValue()); - } else if (constant.hasStringConstant()) { - return '"' + constant.getStringConstant().getValue() + '"'; - } else if (constant.hasNullConstant()) { - return "null"; - } - throw new IllegalArgumentException("constant was not of known type " + constant); - } - - private String formatBinaryOperator(BinaryOperator op) { - switch (op) { - case PLUS: - return "+"; - case MINUS: - return "-"; - case MULTIPLY: - return "*"; - case DIVIDE: - return "/"; - case REMAINDER: - return "%"; - case GREATER_THAN: - return ">"; - case LESS_THAN: - return "<"; - case AND: - return "&"; - case XOR: - return "^"; - case OR: - return "|"; - case CONDITIONAL_AND: - return "&&"; - case CONDITIONAL_OR: - return "||"; - case SHIFT_LEFT: - return "<<"; - case SHIFT_RIGHT: - return ">>"; - case SHIFT_RIGHT_UNSIGNED: - return ">>>"; - case EQUAL_TO: - return "=="; - case NOT_EQUAL_TO: - return "!="; - case GREATER_THAN_EQUAL: - return ">="; - case LESS_THAN_EQUAL: - return "<="; - case UNRECOGNIZED: - throw new IllegalArgumentException("unexpected binary operator " + op); - } - return null; - } - - private String formatType(Type type) { - StringBuilder b = new StringBuilder(); - if (type.hasTypeRef()) { - TypeRef typeRef = type.getTypeRef(); - if (typeRef.getSymbol().equals(ARRAY_SYMBOL)) { - if (isScala) { - b.append("Array["); - b.append(formatType(typeRef.getTypeArguments(0))); - b.append("]"); - } else { - b.append(formatType(typeRef.getTypeArguments(0))); - b.append("[]"); - } - } else if (isScala - && typeRef.getSymbol().startsWith(FUNCTION_SYMBOL_PREFIX) - && typeRef.getTypeArgumentsCount() > 0 - && !typeRef.getSymbol().startsWith(FUNCTION_OBJECT)) { - int n = typeRef.getTypeArgumentsCount() - 1; - if (n == 0) { - // Special-case for Function1[A, B]: don't wrap `A` in parenthesis like this `(A) => B` - s.append(formatType(typeRef.getTypeArguments(0))); - } else { - b.append( - typeRef.getTypeArgumentsList().stream() - .limit(Math.max(0, n)) - .map(this::formatType) - .collect(Collectors.joining(", ", "(", ")"))); - } - b.append(" => "); - b.append(formatType(typeRef.getTypeArguments(n))); - } else if (isScala && typeRef.getSymbol().startsWith(TUPLE_SYMBOL_PREFIX)) { - b.append( - typeRef.getTypeArgumentsList().stream() - .map(this::formatType) - .collect(Collectors.joining(", ", "(", ")"))); - } else { - b.append(symbolDisplayName(typeRef.getSymbol())); - b.append(formatTypeArguments(typeRef.getTypeArgumentsList())); - } - } else if (type.hasSingleType()) { - SingleType tpe = type.getSingleType(); - if (tpe.hasPrefix()) { - b.append(formatType(tpe.getPrefix())); - } - SymbolInformation info = symtab.symbols.get(tpe.getSymbol()); - if (info != null) { - b.append(info.getDisplayName()).append(".type"); - } - } else if (type.hasThisType()) { - b.append("this.type"); - } else if (type.hasSuperType()) { - SuperType tpe = type.getSuperType(); - if (tpe.hasPrefix()) { - b.append(formatType(tpe.getPrefix())).append("."); - } - b.append("super"); - } else if (type.hasConstantType()) { - b.append(this.formatConstant(type.getConstantType().getConstant())); - } else if (type.hasIntersectionType()) { - b.append( - type.getIntersectionType().getTypesList().stream() - .map(this::formatType) - .collect(Collectors.joining(isScala ? " with " : " & "))); - } else if (type.hasUnionType()) { - b.append( - type.getIntersectionType().getTypesList().stream() - .map(this::formatType) - .collect(Collectors.joining(" | "))); - } else if (type.hasStructuralType()) { - StructuralType tpe = type.getStructuralType(); - int n = tpe.getDeclarations().getHardlinksCount(); - if (n == 0) { - b.append(" {}"); - } else { - b.append(formatType(tpe.getTpe())).append(" {"); - Symtab hardlinkSymtab = symtab.withHardlinks(tpe.getDeclarations()); - for (int i = 0; i < n; i++) { - SymbolInformation info = tpe.getDeclarations().getHardlinks(i); - if (i > 0) { - b.append(";"); - } - b.append(" ").append(new SignatureFormatter(info, hardlinkSymtab).formatSymbol()); - } - b.append(" }"); - } - } else if (type.hasExistentialType()) { - AtomicInteger hardlinkStep = new AtomicInteger(); - TypeRef typeRef = type.getExistentialType().getTpe().getTypeRef(); - b.append(symbolDisplayName(type.getExistentialType().getTpe().getTypeRef().getSymbol())); - b.append( - typeRef.getTypeArgumentsList().stream() - .map( - (typeArg) -> { - // if hardlink (aka wildcard) we need to reach into declarations at index - // hardlinkStep - if (typeArg.equals(WILDCARD_TYPE_REF)) { - SymbolInformation hardlink = - type.getExistentialType() - .getDeclarations() - .getHardlinks(hardlinkStep.getAndIncrement()); - return symbolDisplayName(hardlink.getSymbol()) - + new SignatureFormatter(hardlink, symtab).formatSymbol(); - } - // else for symlink we can use the usual path - return formatType(typeArg); - }) - .collect(Collectors.joining(", ", isScala ? "[" : "<", isScala ? "[" : ">"))); - } else if (type.hasByNameType()) { - b.append("=> ").append(formatType(type.getByNameType().getTpe())); - } else if (type.hasRepeatedType()) { - b.append(formatType(type.getRepeatedType().getTpe())).append("*"); - } - - return b.toString().trim(); - } - - private String formatAccess() { - Access access = symbolInformation.getAccess(); - if (access.hasPrivateAccess()) { - return "private"; - } else if (!isScala && access.hasPublicAccess()) { - return "public"; - } else if (access.hasProtectedAccess()) { - return "protected"; - } else if (isScala && access.hasPrivateThisAccess()) { - return "private[this]"; - } else if (isScala && access.hasPrivateWithinAccess()) { - String name = - SymbolDescriptor.parseFromSymbol(access.getPrivateWithinAccess().getSymbol()) - .descriptor - .name; - return String.format("protected[%s]", name); - } - return ""; - } - - // https://checkstyle.sourceforge.io/config_modifier.html#ModifierOrder - private String formatModifiers() { - ArrayList modifiers = new ArrayList<>(); - if (has(Property.ABSTRACT)) { - if (isScala && symbolInformation.getKind() != SymbolInformation.Kind.CLASS) { - } else { - modifiers.add("abstract"); - } - } - if (has(Property.DEFAULT)) modifiers.add("default"); - if (has(Property.STATIC)) modifiers.add("static"); - if (has(Property.FINAL)) { - if (symbolInformation.getKind() != SymbolInformation.Kind.OBJECT - && symbolInformation.getKind() != SymbolInformation.Kind.PACKAGE_OBJECT) { - modifiers.add("final"); - } - } - if (has(Property.IMPLICIT)) modifiers.add("implicit"); - if (has(Property.SEALED)) modifiers.add("sealed"); - if (has(Property.CASE)) modifiers.add("case"); - return String.join(" ", modifiers); - } - - private void printKeyword(String keyword) { - if (keyword.isEmpty()) return; - s.append(keyword).append(' '); - } - - private void printKeywordln(String keyword) { - if (keyword.isEmpty()) return; - s.append(keyword).append('\n'); - } - - private boolean isEnumConstant(SymbolInformation symInfo) { - if (!(has(Property.ENUM, symInfo) - && has(Property.FINAL, symInfo) - && has(Property.STATIC, symInfo) - && symInfo.getAccess().hasPublicAccess())) { - return false; - } - SymbolInformation owner = - symtab.symbols.get(SymbolDescriptor.parseFromSymbol(symInfo.getSymbol()).owner); - if (owner == null) return false; - return owner.getKind() == SymbolInformation.Kind.CLASS && has(Property.ENUM, owner); - } - - private boolean isEnumConstant() { - return isEnumConstant(symbolInformation); - } - - private boolean has(Property property, SymbolInformation symInfo) { - return (symInfo.getProperties() & property.getNumber()) > 0; - } - - private boolean has(Property property) { - return has(property, symbolInformation); - } - - /** - * Transforms a SemanticDB symbol string into its Java identifier display string. As SemanticDB - * uses Scala "primitive" types for Java primitives (but not for Java boxing primitive wrappers), - * we check for those first before attempting to decode a SemanticDB symbol. - */ - public String symbolDisplayName(String symbol) { - if (isScala) { - return symbolScalaDisplayName(symbol); - } - return symbolJavaDisplayName(symbol); - } - - private String symbolScalaDisplayName(String symbol) { - if ("local_wildcard".equals(symbol)) { - return "*"; - } - return SymbolDescriptor.parseFromSymbol(symbol).descriptor.name; - } - - private String symbolJavaDisplayName(String symbol) { - switch (symbol) { - case "local_wildcard": - return "?"; - case "scala/Boolean#": - return "boolean"; - case "scala/Byte#": - return "byte"; - case "scala/Short#": - return "short"; - case "scala/Int#": - return "int"; - case "scala/Long#": - return "long"; - case "scala/Char#": - return "char"; - case "scala/Float#": - return "float"; - case "scala/Double#": - return "double"; - case "scala/Unit#": - return "void"; - default: - return SymbolDescriptor.parseFromSymbol(symbol).descriptor.name; - } - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SignatureFormatterException.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SignatureFormatterException.java deleted file mode 100644 index 32799afb2..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SignatureFormatterException.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb; - -public class SignatureFormatterException extends RuntimeException { - public SignatureFormatterException( - Semanticdb.SymbolInformation symbolInformation, Throwable cause) { - super( - String.format( - "failed to format symbol '%s'\n%s", symbolInformation.getSymbol(), symbolInformation), - cause); - } - - @Override - public synchronized Throwable fillInStackTrace() { - // This exception doesn't have a relevant stack trace. The cause exception - // already points to the culprit filename and line number. - return this; - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolOccurrences.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolOccurrences.java deleted file mode 100644 index 5ffe51001..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolOccurrences.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb; - -import java.util.ArrayList; -import java.util.List; - -public class SymbolOccurrences { - public List occurrences = new ArrayList<>(); - - public void addSyntheticDefinition(String sym) { - occurrences.add( - Semanticdb.SymbolOccurrence.newBuilder() - .setSymbol(sym) - .setRole(Semanticdb.SymbolOccurrence.Role.SYNTHETIC_DEFINITION) - .build()); - } - - public void addDefinition(String sym) { - occurrences.add( - Semanticdb.SymbolOccurrence.newBuilder() - .setSymbol(sym) - .setRole(Semanticdb.SymbolOccurrence.Role.DEFINITION) - .build()); - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolRelationship.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolRelationship.java deleted file mode 100644 index 66eb1ae47..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolRelationship.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -public class SymbolRelationship { - public final String from; - public final String to; - - public SymbolRelationship(String from, String to) { - this.from = from; - this.to = to; - } - - public String getFrom() { - return from; - } - - public String getTo() { - return to; - } -} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolRewriter.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolRewriter.java new file mode 100644 index 000000000..4f03307a0 --- /dev/null +++ b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/SymbolRewriter.java @@ -0,0 +1,27 @@ +package com.sourcegraph.scip_semanticdb; + +import com.sourcegraph.semanticdb.SemanticdbSymbols; + +/** + * Rewrites bare descriptor symbols emitted by the compiler plugins (e.g. {@code + * _root_/com/example/Foo#}) into fully-qualified SCIP symbols (e.g. {@code semanticdb maven + * com.example:my-lib 1.0 _root_/com/example/Foo#}) by looking up the descriptor's owning package in + * a {@link PackageTable}. + * + *

Local symbols (those starting with {@code "local "}) are passed through unchanged. + */ +final class SymbolRewriter { + private final PackageTable packages; + + SymbolRewriter(PackageTable packages) { + this.packages = packages; + } + + /** Rewrites {@code symbol}, or returns {@code symbol} unchanged when no rewriting applies. */ + String rewrite(String symbol) { + if (symbol == null || symbol.isEmpty()) return symbol; + if (SemanticdbSymbols.isLocal(symbol)) return symbol; + Package pkg = packages.packageForSymbol(symbol).orElse(Package.EMPTY); + return "semanticdb " + pkg.scipTypedEncoding() + " " + symbol; + } +} diff --git a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/Symtab.java b/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/Symtab.java deleted file mode 100644 index 5cd54d7cf..000000000 --- a/scip-semanticdb/src/main/java/com/sourcegraph/scip_semanticdb/Symtab.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sourcegraph.scip_semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb; - -import java.util.HashMap; - -public class Symtab { - public final HashMap symbols = new HashMap<>(); - - public Symtab(Semanticdb.TextDocument document) { - for (Semanticdb.SymbolInformation symbolInformation : document.getSymbolsList()) { - symbols.put(symbolInformation.getSymbol(), symbolInformation); - } - } - - public Symtab withHardlinks(Semanticdb.Scope scope) { - Symtab hardlinkSymtab = new Symtab(Semanticdb.TextDocument.getDefaultInstance()); - hardlinkSymtab.symbols.putAll(this.symbols); - for (int i = 0; i < scope.getHardlinksCount(); i++) { - Semanticdb.SymbolInformation info = scope.getHardlinks(i); - hardlinkSymtab.symbols.put(info.getSymbol(), info); - } - return hardlinkSymtab; - } -} diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/ScipJavaSignatureFormatter.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/ScipJavaSignatureFormatter.java new file mode 100644 index 000000000..85c057bd5 --- /dev/null +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/ScipJavaSignatureFormatter.java @@ -0,0 +1,608 @@ +package com.sourcegraph.semanticdb_javac; + +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.ModifiersTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ParenthesizedTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.Trees; +import com.sun.source.util.TreePath; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.SimpleTypeVisitor8; + +/** + * Renders the {@code signature_documentation} text for a Java declaration directly from javac + * {@link Element}s and {@link Tree}s. + * + *

This is the Java-language path of the old {@code SignatureFormatter}, ported onto javac's + * element / type / tree API so the compiler plugin no longer has to first marshal everything into + * SemanticDB proto messages. Output is intentionally byte-identical with the previous output so + * existing golden snapshots continue to match. + */ +final class ScipJavaSignatureFormatter { + private static final String UNRESOLVED = "unresolved_type"; + + private final Trees trees; + private final CompilationUnitTree compilationUnit; + + ScipJavaSignatureFormatter(Trees trees, CompilationUnitTree compilationUnit) { + this.trees = trees; + this.compilationUnit = compilationUnit; + } + + /** + * Returns the signature text for {@code sym}, or {@code ""} when {@code sym} has no signature. + */ + String format(Element sym, Tree declTree) { + if (sym instanceof TypeElement) { + return formatClass((TypeElement) sym, declTree); + } else if (sym instanceof ExecutableElement) { + return formatMethod((ExecutableElement) sym, declTree); + } else if (sym instanceof VariableElement) { + return formatVariable((VariableElement) sym, declTree); + } else if (sym instanceof TypeParameterElement) { + return formatTypeParameter((TypeParameterElement) sym); + } + return ""; + } + + // -------- Class / interface / annotation / enum -------- + + private String formatClass(TypeElement sym, Tree declTree) { + StringBuilder s = new StringBuilder(); + boolean isAnnotation = sym.getKind() == ElementKind.ANNOTATION_TYPE; + boolean isEnum = sym.getKind() == ElementKind.ENUM; + boolean isInterface = sym.getKind() == ElementKind.INTERFACE || isAnnotation; + + printKeywordln(s, formatAnnotations(declTree)); + printKeyword(s, formatAccess(sym)); + if (!isEnum && !isAnnotation && !isInterface) printKeyword(s, formatModifiers(sym)); + + switch (sym.getKind()) { + case CLASS: + printKeyword(s, "class"); + break; + case ENUM: + printKeyword(s, "enum"); + break; + case ANNOTATION_TYPE: + printKeyword(s, "@interface"); + break; + case INTERFACE: + printKeyword(s, "interface"); + break; + default: + break; + } + s.append(sym.getSimpleName()); + + List typeParameters = sym.getTypeParameters(); + if (!typeParameters.isEmpty()) { + s.append( + typeParameters.stream() + .map(this::formatTypeParameter) + .collect(Collectors.joining(", ", "<", ">"))); + } + + List nonSynthetic = new ArrayList<>(); + boolean hasNonRedundantParent = false; + boolean firstParent = true; + for (TypeMirror parent : superTypes(sym)) { + if (firstParent) { + hasNonRedundantParent = !isJavaLangObject(parent); + firstParent = false; + } + if (isJavaLangObject(parent)) continue; + if (isEnumParent(parent)) continue; + if (isAnnotationParent(parent)) continue; + nonSynthetic.add(parent); + } + if (nonSynthetic.isEmpty()) return s.toString(); + + // Mirror SignatureFormatter's Java extends/implements logic. + switch (sym.getKind()) { + case CLASS: + case ENUM: + if (isEnum || !hasNonRedundantParent) { + printKeyword(s, " implements"); + s.append(nonSynthetic.stream().map(this::formatType).collect(Collectors.joining(", "))); + } else { + printKeyword(s, " extends"); + s.append(formatType(nonSynthetic.get(0))); + String supers = + nonSynthetic.stream().skip(1).map(this::formatType).collect(Collectors.joining(", ")); + if (!supers.isEmpty()) { + printKeyword(s, " implements"); + s.append(supers); + } + } + break; + case INTERFACE: + case ANNOTATION_TYPE: + printKeyword(s, " extends"); + s.append(nonSynthetic.stream().map(this::formatType).collect(Collectors.joining(", "))); + break; + default: + break; + } + return s.toString(); + } + + /** Mirrors {@code Types.directSupertypes} (which we don't carry directly). */ + private List superTypes(TypeElement sym) { + List parents = new ArrayList<>(); + TypeMirror superClass = sym.getSuperclass(); + if (superClass != null && !(superClass instanceof NoType)) { + parents.add(superClass); + } + parents.addAll(sym.getInterfaces()); + return parents; + } + + // -------- Method / constructor -------- + + private String formatMethod(ExecutableElement sym, Tree declTree) { + StringBuilder s = new StringBuilder(); + printKeywordln(s, formatAnnotations(declTree)); + printKeyword(s, formatAccess(sym)); + printKeyword(s, formatModifiers(sym)); + + List typeParameters = sym.getTypeParameters(); + if (!typeParameters.isEmpty()) { + printKeyword( + s, + typeParameters.stream() + .map(this::formatTypeParameter) + .collect(Collectors.joining(", ", "<", ">"))); + } + + if (sym.getKind() == ElementKind.CONSTRUCTOR) { + Element owner = sym.getEnclosingElement(); + if (owner != null) s.append(owner.getSimpleName()); + } else { + printKeyword(s, formatType(sym.getReturnType())); + s.append(sym.getSimpleName()); + } + + s.append( + sym.getParameters().stream() + .map(this::formatParameter) + .collect(Collectors.joining(", ", "(", ")"))); + + List thrown = sym.getThrownTypes(); + if (!thrown.isEmpty()) { + printKeyword(s, " throws"); + s.append(thrown.stream().map(this::formatType).collect(Collectors.joining(", "))); + } + return s.toString(); + } + + /** {@code } parameter form (no leading annotations or modifiers). */ + private String formatParameter(VariableElement param) { + return formatType(param.asType()) + " " + param.getSimpleName(); + } + + // -------- Field / parameter -------- + + private String formatVariable(VariableElement sym, Tree declTree) { + StringBuilder s = new StringBuilder(); + printKeywordln(s, formatAnnotations(declTree)); + + if (isEnumConstant(sym)) { + Element owner = sym.getEnclosingElement(); + List constants = new ArrayList<>(); + for (Element e : owner.getEnclosedElements()) { + if (e.getKind() == ElementKind.ENUM_CONSTANT) constants.add((VariableElement) e); + } + int ordinal = constants.indexOf(sym); + s.append(owner.getSimpleName()).append('.'); + s.append(sym.getSimpleName()); + String args = enumConstantArgs(declTree); + if (!args.isEmpty()) s.append('(').append(args).append(')'); + s.append(" /* ordinal ").append(ordinal).append(" */"); + return s.toString(); + } + + printKeyword(s, formatAccess(sym)); + printKeyword(s, formatModifiers(sym)); + printKeyword(s, formatType(sym.asType())); + s.append(sym.getSimpleName()); + return s.toString(); + } + + // -------- Type parameter -------- + + private String formatTypeParameter(TypeParameterElement sym) { + StringBuilder s = new StringBuilder(); + s.append(sym.getSimpleName()); + List bounds = sym.getBounds(); + // javac surfaces `T extends Object` as `bounds = [Object]`; treat it as no bound. + if (bounds.size() == 1 && isJavaLangObject(bounds.get(0))) return s.toString(); + if (!bounds.isEmpty()) { + printKeyword(s, " extends"); + s.append(bounds.stream().map(this::formatType).collect(Collectors.joining(" & "))); + } + return s.toString(); + } + + // -------- Type rendering -------- + + private String formatType(TypeMirror tpe) { + if (tpe == null) return UNRESOLVED; + String result = tpe.accept(typeVisitor, null); + return result == null ? UNRESOLVED : result; + } + + private final SimpleTypeVisitor8 typeVisitor = + new SimpleTypeVisitor8() { + @Override + public String visitDeclared(DeclaredType t, Void unused) { + StringBuilder b = new StringBuilder(); + Element elem = t.asElement(); + b.append(elem == null ? UNRESOLVED : elem.getSimpleName()); + List args = t.getTypeArguments(); + if (!args.isEmpty()) { + b.append( + args.stream() + .map(ScipJavaSignatureFormatter.this::formatType) + .collect(Collectors.joining(", ", "<", ">"))); + } + return b.toString(); + } + + @Override + public String visitArray(ArrayType t, Void unused) { + return formatType(t.getComponentType()) + "[]"; + } + + @Override + public String visitPrimitive(PrimitiveType t, Void unused) { + return primitiveName(t.getKind()); + } + + @Override + public String visitTypeVariable(TypeVariable t, Void unused) { + Element elem = t.asElement(); + return elem == null ? UNRESOLVED : elem.getSimpleName().toString(); + } + + @Override + public String visitIntersection(IntersectionType t, Void unused) { + return t.getBounds().stream() + .map(ScipJavaSignatureFormatter.this::formatType) + .collect(Collectors.joining(" & ")); + } + + @Override + public String visitWildcard(WildcardType t, Void unused) { + if (t.getExtendsBound() != null) return "? extends " + formatType(t.getExtendsBound()); + if (t.getSuperBound() != null) return "? super " + formatType(t.getSuperBound()); + return "?"; + } + + @Override + public String visitNoType(NoType t, Void unused) { + return primitiveName(t.getKind()); + } + }; + + private static String primitiveName(TypeKind kind) { + switch (kind) { + case BOOLEAN: + return "boolean"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case LONG: + return "long"; + case CHAR: + return "char"; + case FLOAT: + return "float"; + case DOUBLE: + return "double"; + case VOID: + return "void"; + default: + return UNRESOLVED; + } + } + + // -------- Access / modifiers -------- + + private static String formatAccess(Element sym) { + for (Modifier m : sym.getModifiers()) { + if (m == Modifier.PRIVATE) return "private"; + if (m == Modifier.PUBLIC) return "public"; + if (m == Modifier.PROTECTED) return "protected"; + } + return ""; + } + + /** Mirrors {@code SignatureFormatter.formatModifiers} for the Java path. */ + private static String formatModifiers(Element sym) { + boolean isStatic = sym.getModifiers().contains(Modifier.STATIC); + boolean isFinal = sym.getModifiers().contains(Modifier.FINAL); + boolean isAbstract = sym.getModifiers().contains(Modifier.ABSTRACT); + boolean isDefault = sym.getModifiers().contains(Modifier.DEFAULT); + // Default interface methods get both ABSTRACT and DEFAULT set; drop ABSTRACT. + if (isAbstract && isDefault) isAbstract = false; + + List mods = new ArrayList<>(); + if (isAbstract) mods.add("abstract"); + if (isDefault) mods.add("default"); + if (isStatic) mods.add("static"); + if (isFinal) mods.add("final"); + return String.join(" ", mods); + } + + // -------- Annotations -------- + + private String formatAnnotations(Tree declTree) { + ModifiersTree mods = modifiers(declTree); + if (mods == null) return ""; + List annotations = mods.getAnnotations(); + if (annotations.isEmpty()) return ""; + return annotations.stream().map(this::formatAnnotation).collect(Collectors.joining("\n")); + } + + private static ModifiersTree modifiers(Tree tree) { + if (tree instanceof ClassTree) return ((ClassTree) tree).getModifiers(); + if (tree instanceof MethodTree) return ((MethodTree) tree).getModifiers(); + if (tree instanceof VariableTree) return ((VariableTree) tree).getModifiers(); + return null; + } + + private String formatAnnotation(AnnotationTree annotation) { + StringBuilder b = new StringBuilder(); + b.append('@'); + b.append(formatAnnotationType(annotation)); + + List args = annotation.getArguments(); + if (args.size() == 1) { + b.append('('); + ExpressionTree first = args.get(0); + // `@Foo(value = X)` collapses to `@Foo(X)`, per JLS 9.7.3. + if (first instanceof AssignmentTree) { + AssignmentTree assign = (AssignmentTree) first; + if (isValueIdentifier(assign.getVariable())) { + b.append(formatExpression(assign.getExpression())); + } else { + b.append(formatExpression(first)); + } + } else { + b.append(formatExpression(first)); + } + b.append(')'); + } else if (args.size() > 1) { + b.append( + args.stream().map(this::formatExpression).collect(Collectors.joining(", ", "(", ")"))); + } + return b.toString(); + } + + private String formatAnnotationType(AnnotationTree annotation) { + Element elem = elementOf(annotation.getAnnotationType()); + if (elem != null) return elem.getSimpleName().toString(); + return annotation.getAnnotationType().toString(); + } + + private static boolean isValueIdentifier(ExpressionTree tree) { + return tree instanceof IdentifierTree + && "value".contentEquals(((IdentifierTree) tree).getName()); + } + + private String formatExpression(ExpressionTree expr) { + if (expr instanceof LiteralTree) return formatLiteral(((LiteralTree) expr).getValue()); + if (expr instanceof IdentifierTree) return ((IdentifierTree) expr).getName().toString(); + if (expr instanceof MemberSelectTree) { + MemberSelectTree select = (MemberSelectTree) expr; + return formatExpression(select.getExpression()) + "." + select.getIdentifier(); + } + if (expr instanceof NewArrayTree) { + List inits = ((NewArrayTree) expr).getInitializers(); + if (inits == null) inits = Collections.emptyList(); + return inits.stream().map(this::formatExpression).collect(Collectors.joining(", ", "{", "}")); + } + if (expr instanceof AnnotationTree) return formatAnnotation((AnnotationTree) expr); + if (expr instanceof AssignmentTree) { + AssignmentTree assign = (AssignmentTree) expr; + return formatExpression(assign.getVariable()) + + " = " + + formatExpression(assign.getExpression()); + } + if (expr instanceof BinaryTree) { + BinaryTree bin = (BinaryTree) expr; + return formatExpression(bin.getLeftOperand()) + + " " + + binaryOperator(bin.getKind()) + + " " + + formatExpression(bin.getRightOperand()); + } + if (expr instanceof UnaryTree) { + UnaryTree un = (UnaryTree) expr; + return unaryOperator(un.getKind(), formatExpression(un.getExpression())); + } + if (expr instanceof ParenthesizedTree) { + return formatExpression(((ParenthesizedTree) expr).getExpression()); + } + if (expr instanceof TypeCastTree) { + TypeCastTree cast = (TypeCastTree) expr; + TypeMirror type = typeOf(cast.getType()); + String typeText = type != null ? formatType(type) : cast.getType().toString(); + return "(" + typeText + ") " + formatExpression(cast.getExpression()); + } + return expr.toString(); + } + + private static String formatLiteral(Object value) { + if (value == null) return "null"; + if (value instanceof String) return "\"" + value + "\""; + if (value instanceof Character) return "'" + value + "'"; + if (value instanceof Float) return value + "f"; + return value.toString(); + } + + private static String binaryOperator(Tree.Kind kind) { + switch (kind) { + case PLUS: + return "+"; + case MINUS: + return "-"; + case MULTIPLY: + return "*"; + case DIVIDE: + return "/"; + case REMAINDER: + return "%"; + case GREATER_THAN: + return ">"; + case LESS_THAN: + return "<"; + case AND: + return "&"; + case XOR: + return "^"; + case OR: + return "|"; + case CONDITIONAL_AND: + return "&&"; + case CONDITIONAL_OR: + return "||"; + case LEFT_SHIFT: + return "<<"; + case RIGHT_SHIFT: + return ">>"; + case UNSIGNED_RIGHT_SHIFT: + return ">>>"; + case EQUAL_TO: + return "=="; + case NOT_EQUAL_TO: + return "!="; + case GREATER_THAN_EQUAL: + return ">="; + case LESS_THAN_EQUAL: + return "<="; + default: + throw new IllegalArgumentException("unexpected binary operator " + kind); + } + } + + private static String unaryOperator(Tree.Kind kind, String value) { + switch (kind) { + case UNARY_MINUS: + // The old SignatureFormatter.formatUnaryOperation rendered UNARY_PLUS as "-value". + // Preserve that behavior to keep snapshots stable. + case UNARY_PLUS: + return "-" + value; + case POSTFIX_INCREMENT: + return value + "++"; + case POSTFIX_DECREMENT: + return value + "--"; + case PREFIX_INCREMENT: + return "++" + value; + case PREFIX_DECREMENT: + return "--" + value; + case BITWISE_COMPLEMENT: + return "~" + value; + case LOGICAL_COMPLEMENT: + return "!" + value; + default: + throw new IllegalArgumentException("unexpected unary operator " + kind); + } + } + + // -------- Helpers -------- + + private static void printKeyword(StringBuilder s, String keyword) { + if (keyword.isEmpty()) return; + s.append(keyword).append(' '); + } + + private static void printKeywordln(StringBuilder s, String keyword) { + if (keyword.isEmpty()) return; + s.append(keyword).append('\n'); + } + + private static boolean isEnumConstant(Element sym) { + if (sym.getKind() != ElementKind.ENUM_CONSTANT) return false; + Element owner = sym.getEnclosingElement(); + return owner != null && owner.getKind() == ElementKind.ENUM; + } + + /** + * Renders the constructor arguments of an enum constant declaration ("A", 420), or {@code ""} if + * the initializer is not a {@code new EnumType(...)} expression or carries no arguments. + */ + private static String enumConstantArgs(Tree declTree) { + if (!(declTree instanceof VariableTree)) return ""; + ExpressionTree initializer = ((VariableTree) declTree).getInitializer(); + if (!(initializer instanceof NewClassTree)) return ""; + return ((NewClassTree) initializer) + .getArguments().stream().map(Object::toString).collect(Collectors.joining(", ")); + } + + private static boolean isEnumParent(TypeMirror parent) { + return isQualified(parent, "java.lang.Enum"); + } + + private static boolean isAnnotationParent(TypeMirror parent) { + return isQualified(parent, "java.lang.annotation.Annotation"); + } + + private static boolean isJavaLangObject(TypeMirror tpe) { + return isQualified(tpe, "java.lang.Object"); + } + + private static boolean isQualified(TypeMirror tpe, String qualifiedName) { + if (!(tpe instanceof DeclaredType)) return false; + Element elem = ((DeclaredType) tpe).asElement(); + return elem instanceof TypeElement + && qualifiedName.contentEquals(((TypeElement) elem).getQualifiedName()); + } + + private Element elementOf(Tree tree) { + TreePath path = trees.getPath(compilationUnit, tree); + return path == null ? null : trees.getElement(path); + } + + private TypeMirror typeOf(Tree tree) { + TreePath path = trees.getPath(compilationUnit, tree); + return path == null ? null : trees.getTypeMirror(path); + } +} diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/ScipVisitor.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/ScipVisitor.java new file mode 100644 index 000000000..1e35660f8 --- /dev/null +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/ScipVisitor.java @@ -0,0 +1,536 @@ +package com.sourcegraph.semanticdb_javac; + +import com.sourcegraph.semanticdb.LocalSymbolsCache; +import com.sourcegraph.semanticdb.ScipDocumentBuilder; +import com.sourcegraph.semanticdb.SemanticdbSymbols; +import com.sun.source.tree.AnnotatedTypeTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.LineMap; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.PackageTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeParameterTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import org.scip_code.scip.Occurrence; +import org.scip_code.scip.Relationship; +import org.scip_code.scip.Signature; +import org.scip_code.scip.SymbolInformation; +import org.scip_code.scip.SymbolRole; + +/** + * Walks a typechecked compilation unit and feeds SCIP {@link Occurrence}/{@link SymbolInformation} + * messages into a {@link ScipDocumentBuilder}. + * + *

Replaces the old {@code SemanticdbVisitor} + {@code SemanticdbSignatures} + {@code + * SemanticdbTrees} chain that first produced SemanticDB protos and then converted them to SCIP. + * Symbols are emitted in their bare form (e.g. {@code _root_/com/example/Foo#bar().}); the + * aggregator prefixes them with the resolved package coordinates at index time. + */ +final class ScipVisitor extends TreePathScanner { + private final GlobalSymbolsCache globals; + private final LocalSymbolsCache locals; + private final Types types; + private final Trees trees; + private final Elements elements; + private final CompilationUnitTree compUnitTree; + private final ScipDocumentBuilder documentBuilder; + private final ScipJavaSignatureFormatter signatureFormatter; + private final LinkedHashMap nodes = new LinkedHashMap<>(); + private final LinkedHashMap declTrees = new LinkedHashMap<>(); + + private String source; + + ScipVisitor( + GlobalSymbolsCache globals, + LocalSymbolsCache locals, + CompilationUnitTree compUnitTree, + Types types, + Trees trees, + Elements elements, + ScipDocumentBuilder documentBuilder) { + this.globals = globals; + this.locals = locals; + this.types = types; + this.trees = trees; + this.elements = elements; + this.compUnitTree = compUnitTree; + this.documentBuilder = documentBuilder; + this.signatureFormatter = new ScipJavaSignatureFormatter(trees, compUnitTree); + this.source = readSource(); + } + + void visitCompilationUnit() { + scan(compUnitTree, null); + resolveNodes(); + } + + // ======================================= + // TreePathScanner overrides + // ======================================= + @Override + public Void scan(Tree tree, Void unused) { + if (tree != null) { + TreePath path = new TreePath(getCurrentPath(), tree); + nodes.put(tree, path); + } + return super.scan(tree, unused); + } + + @Override + public Void visitPackage(PackageTree node, Void unused) { + // JDK 17+ TreePathScanner would otherwise recurse into the package name's + // identifiers and emit a self-reference for `package X.Y;`. JDK 11 does + // not. Skipping the package subtree keeps output stable across JDKs. + return null; + } + + // ======================================= + // Tree resolution + // ======================================= + private void resolveNodes() { + Set ignoreNodes = new HashSet<>(); + for (Tree node : nodes.keySet()) { + if (node instanceof NewClassTree) { + NewClassTree newClassTree = (NewClassTree) node; + if (newClassTree.getClassBody() == null) { + if (newClassTree.getIdentifier() instanceof ParameterizedTypeTree) { + ParameterizedTypeTree paramNode = (ParameterizedTypeTree) newClassTree.getIdentifier(); + ignoreNodes.add(paramNode.getType()); + } + ignoreNodes.add(newClassTree.getIdentifier()); + } + } + } + + for (Map.Entry entry : nodes.entrySet()) { + Tree node = entry.getKey(); + if (ignoreNodes.contains(node)) continue; + if (node instanceof TypeParameterTree) { + resolveTypeParameterTree((TypeParameterTree) node, entry.getValue()); + } else if (node instanceof ClassTree) { + resolveClassTree((ClassTree) node, entry.getValue()); + } else if (node instanceof MethodTree) { + resolveMethodTree((MethodTree) node, entry.getValue()); + } else if (node instanceof VariableTree) { + resolveVariableTree((VariableTree) node, entry.getValue()); + } else if (node instanceof IdentifierTree) { + resolveIdentifierTree((IdentifierTree) node, entry.getValue()); + } else if (node instanceof MemberReferenceTree) { + resolveMemberReferenceTree((MemberReferenceTree) node, entry.getValue()); + } else if (node instanceof MemberSelectTree) { + resolveMemberSelectTree((MemberSelectTree) node, entry.getValue()); + } else if (node instanceof NewClassTree) { + resolveNewClassTree((NewClassTree) node, entry.getValue()); + } + } + } + + private void resolveClassTree(ClassTree node, TreePath treePath) { + Element sym = trees.getElement(treePath); + if (sym != null && sym.getSimpleName().length() > 0) { + emitDefinition(sym, node, sym.getSimpleName(), CompilerRange.FROM_POINT_WITH_TEXT_SEARCH); + } + } + + private void resolveTypeParameterTree(TypeParameterTree node, TreePath treePath) { + Element sym = trees.getElement(treePath); + if (sym != null && sym.getSimpleName().length() > 0) { + emitDefinition(sym, node, sym.getSimpleName(), CompilerRange.FROM_POINT_TO_SYMBOL_NAME); + } + } + + private void resolveMethodTree(MethodTree node, TreePath treePath) { + Element sym = trees.getElement(treePath); + if (sym == null) return; + Element owner = sym.getEnclosingElement(); + if (sym.getKind() == ElementKind.CONSTRUCTOR && isAnonymous(owner)) return; + Name name = + sym.getKind() == ElementKind.CONSTRUCTOR ? owner.getSimpleName() : sym.getSimpleName(); + emitDefinition(sym, node, name, CompilerRange.FROM_POINT_WITH_TEXT_SEARCH); + } + + private void resolveVariableTree(VariableTree node, TreePath treePath) { + Element sym = trees.getElement(treePath); + if (sym == null) return; + int[] range = + emitDefinition(sym, node, sym.getSimpleName(), CompilerRange.FROM_POINT_WITH_TEXT_SEARCH); + if (sym.getKind() == ElementKind.ENUM_CONSTANT) { + TreePath typeTreePath = nodes.get(node.getInitializer()); + Element typeSym = trees.getElement(typeTreePath); + if (typeSym != null && range != null) { + emitOccurrence(typeSym, range, 0, null); + } + } + } + + private void resolveIdentifierTree(IdentifierTree node, TreePath treePath) { + Name nodeName = node.getName(); + if (nodeName == null) return; + Element sym = trees.getElement(treePath); + if (sym == null) return; + boolean isThis = nodeName.toString().equals("this"); + boolean isSuper = !isThis && nodeName.toString().equals("super"); + // exclude `this.` references but include `this(` and `super(` references + if (!(((sym.getKind() == ElementKind.CONSTRUCTOR) == isThis) || isSuper)) return; + TreePath parentPath = treePath.getParentPath(); + Element parentSym = trees.getElement(parentPath); + if (parentSym == null || parentSym.getKind() != null) { + emitReference(sym, node, sym.getSimpleName(), CompilerRange.FROM_START_TO_END); + } + } + + private void resolveMemberReferenceTree(MemberReferenceTree node, TreePath treePath) { + Element sym = trees.getElement(treePath); + if (sym == null) return; + emitReference(sym, node, sym.getSimpleName(), CompilerRange.FROM_END_TO_SYMBOL_NAME); + } + + private void resolveMemberSelectTree(MemberSelectTree node, TreePath treePath) { + Element sym = trees.getElement(treePath); + if (sym == null) return; + emitReference(sym, node, sym.getSimpleName(), CompilerRange.FROM_END_TO_SYMBOL_NAME); + } + + private void resolveNewClassTree(NewClassTree node, TreePath treePath) { + if (node.getIdentifier() == null || node.getClassBody() != null) return; + Element sym = trees.getElement(treePath); + if (sym == null) return; + TreePath parentPath = treePath.getParentPath(); + Element parentSym = trees.getElement(parentPath); + if (parentSym != null && parentSym.getKind() == ElementKind.ENUM_CONSTANT) return; + + TreePath identifierTreePath = nodes.get(node.getIdentifier()); + Element identifierSym = trees.getElement(identifierTreePath); + if (identifierSym != null) { + emitReference(sym, node, identifierSym.getSimpleName(), CompilerRange.FROM_TEXT_SEARCH); + } else if (node.getIdentifier().getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node.getIdentifier(); + if (annotatedTypeTree.getUnderlyingType() != null + && annotatedTypeTree.getUnderlyingType().getKind() == Tree.Kind.IDENTIFIER) { + IdentifierTree ident = (IdentifierTree) annotatedTypeTree.getUnderlyingType(); + emitReference(sym, ident, ident.getName(), CompilerRange.FROM_TEXT_SEARCH); + } + } + } + + // ======================================= + // Occurrence + SymbolInformation emission + // ======================================= + private int[] emitDefinition(Element sym, Tree tree, Name name, CompilerRange kind) { + int[] range = computeRange(tree, kind, sym, name == null ? null : name.toString()); + if (range == null) return null; + emitOccurrence(sym, range, SymbolRole.Definition_VALUE, computeEnclosingRange(tree)); + declTrees.put(sym, tree); + emitSymbolInformation(sym, tree); + return range; + } + + private void emitReference(Element sym, Tree tree, Name name, CompilerRange kind) { + int[] range = computeRange(tree, kind, sym, name == null ? null : name.toString()); + if (range == null) return; + emitOccurrence(sym, range, 0 /* reference */, null); + } + + private void emitOccurrence(Element sym, int[] range, int role, int[] enclosingRange) { + String symbol = scipSymbol(sym); + if (symbol.isEmpty()) return; + Occurrence.Builder b = Occurrence.newBuilder().setSymbol(symbol).setSymbolRoles(role); + for (int v : range) b.addRange(v); + if (enclosingRange != null) { + for (int v : enclosingRange) b.addEnclosingRange(v); + } + documentBuilder.addOccurrence(b.build()); + } + + private void emitSymbolInformation(Element sym, Tree tree) { + String symbol = scipSymbol(sym); + if (symbol.isEmpty()) return; + + SymbolInformation.Builder builder = + SymbolInformation.newBuilder() + .setSymbol(symbol) + .setDisplayName(sym.getSimpleName().toString()); + + String signatureText = signatureFormatter.format(sym, tree); + if (!signatureText.isEmpty()) { + builder.setSignatureDocumentation( + Signature.newBuilder().setLanguage("java").setText(signatureText)); + } + + String documentation = docComment(tree); + if (documentation != null && !documentation.isEmpty()) { + builder.addDocumentation(documentation); + } + + if (SemanticdbSymbols.isLocal(symbol)) { + String enclosingSymbol = scipSymbol(sym.getEnclosingElement()); + if (!enclosingSymbol.isEmpty()) builder.setEnclosingSymbol(enclosingSymbol); + } + + SymbolInformation.Kind kind = scipKind(sym); + if (kind != SymbolInformation.Kind.UnspecifiedKind) builder.setKind(kind); + + if (sym instanceof TypeElement) { + for (TypeElement parent : parentTypeElements((TypeElement) sym)) { + String parentSymbol = scipSymbol(parent); + if (parentSymbol.isEmpty()) continue; + builder.addRelationships( + Relationship.newBuilder().setSymbol(parentSymbol).setIsImplementation(true)); + } + } else if (sym instanceof ExecutableElement && sym.getKind() == ElementKind.METHOD) { + for (String overridden : + overriddenSymbols((ExecutableElement) sym, sym.getEnclosingElement(), new HashSet<>())) { + if (overridden.isEmpty()) continue; + builder.addRelationships( + Relationship.newBuilder() + .setSymbol(overridden) + .setIsImplementation(true) + .setIsReference(true)); + } + } + + if (sym.getKind() == ElementKind.ENUM_CONSTANT && tree instanceof VariableTree) { + VariableTree variableTree = (VariableTree) tree; + if (variableTree.getInitializer() instanceof NewClassTree) { + String args = + ((NewClassTree) variableTree.getInitializer()) + .getArguments().stream() + .map(ExpressionTree::toString) + .collect(Collectors.joining(", ")); + if (!args.isEmpty()) { + builder.setDisplayName(sym.getSimpleName().toString() + "(" + args + ")"); + } + } + } + + documentBuilder.addSymbol(builder.build()); + } + + private SymbolInformation.Kind scipKind(Element sym) { + Set mods = sym.getModifiers(); + boolean isStatic = mods.contains(Modifier.STATIC); + boolean isAbstract = mods.contains(Modifier.ABSTRACT); + boolean isDefault = mods.contains(Modifier.DEFAULT); + switch (sym.getKind()) { + case ENUM: + return SymbolInformation.Kind.Enum; + case CLASS: + return SymbolInformation.Kind.Class; + case INTERFACE: + case ANNOTATION_TYPE: + return SymbolInformation.Kind.Interface; + case CONSTRUCTOR: + return SymbolInformation.Kind.Constructor; + case METHOD: + if (isStatic) return SymbolInformation.Kind.StaticMethod; + if (isAbstract && !isDefault) return SymbolInformation.Kind.AbstractMethod; + return SymbolInformation.Kind.Method; + case FIELD: + return isStatic ? SymbolInformation.Kind.StaticField : SymbolInformation.Kind.Field; + case LOCAL_VARIABLE: + return SymbolInformation.Kind.Variable; + case TYPE_PARAMETER: + return SymbolInformation.Kind.TypeParameter; + default: + return SymbolInformation.Kind.UnspecifiedKind; + } + } + + // ======================================= + // Source position / range computation + // ======================================= + private int[] computeRange(Tree tree, CompilerRange kind, Element sym, String name) { + if (sym == null) return null; + SourcePositions sourcePositions = trees.getSourcePositions(); + int start = (int) sourcePositions.getStartPosition(compUnitTree, tree); + int end = (int) sourcePositions.getEndPosition(compUnitTree, tree); + if (kind.isPlusOne()) start++; + + if (name != null) { + if (kind.isFromTextSearch() && name.length() > 0) { + Optional startEndRange = + RangeFinder.findRange(sym, name, start, end, this.source, kind.isFromEnd()); + if (startEndRange.isPresent()) { + start = startEndRange.get().start; + end = startEndRange.get().end; + } + } else if (kind.isFromPoint()) { + if (start != Diagnostic.NOPOS) { + int testEnd = start + name.length(); + if (source.length() > testEnd && source.substring(start, testEnd).equals(name)) + end = testEnd; + } + } else if (kind.isFromEndPoint()) { + if (end != Diagnostic.NOPOS) { + int testStart = end - name.length(); + if (testStart >= 0 + && source.length() > end + && source.substring(testStart, end).equals(name)) start = testStart; + } + } + } + + return lineMapRange(start, end); + } + + private int[] computeEnclosingRange(Tree tree) { + if (tree == null) return null; + TreePath path = nodes.get(tree); + if (path == null) return null; + Tree enclosingTree = tree; + if (!(tree instanceof MethodTree + || tree instanceof ClassTree + || tree instanceof VariableTree)) { + TreePath parentPath = path.getParentPath(); + if (parentPath == null) return null; + enclosingTree = parentPath.getLeaf(); + if (enclosingTree == null || enclosingTree == compUnitTree) return null; + } + SourcePositions sourcePositions = trees.getSourcePositions(); + int start = (int) sourcePositions.getStartPosition(compUnitTree, enclosingTree); + int end = (int) sourcePositions.getEndPosition(compUnitTree, enclosingTree); + return lineMapRange(start, end); + } + + private int[] lineMapRange(int start, int end) { + if (start == Diagnostic.NOPOS || end == Diagnostic.NOPOS || end <= start) return null; + LineMap lineMap = compUnitTree.getLineMap(); + int startLine = (int) lineMap.getLineNumber(start) - 1; + int startChar = (int) lineMap.getColumnNumber(start) - 1; + int endLine = (int) lineMap.getLineNumber(end) - 1; + int endChar = (int) lineMap.getColumnNumber(end) - 1; + + // javac reports tab columns as if every tab were 8 spaces; correct that when + // the line is actually tab-indented. + int linePos = (int) lineMap.getPosition(lineMap.getLineNumber(start), 0); + if (linePos >= 0 && linePos < source.length() && source.charAt(linePos) == '\t') { + int count = 1; + while (++linePos < source.length() && source.charAt(linePos) == '\t') count++; + startChar -= count * 7; + endChar -= count * 7; + } + + if (startLine == endLine) return new int[] {startLine, startChar, endChar}; + return new int[] {startLine, startChar, endLine, endChar}; + } + + // ======================================= + // Symbol resolution + // ======================================= + private String scipSymbol(Element sym) { + return globals.semanticdbSymbol(sym, locals); + } + + private List parentTypeElements(TypeElement typeElement) { + List result = new ArrayList<>(); + Set seen = new HashSet<>(); + collectParents(typeElement, seen, result); + return result; + } + + private void collectParents( + TypeElement typeElement, Set seen, List out) { + addParent(typeElement.getSuperclass(), seen, out); + for (TypeMirror itf : typeElement.getInterfaces()) addParent(itf, seen, out); + } + + private void addParent(TypeMirror parent, Set seen, List out) { + if (parent instanceof NoType) return; + Element parentElement = types.asElement(parent); + if (!(parentElement instanceof TypeElement)) return; + if (!seen.add((TypeElement) parentElement)) return; + // Skip java.lang.Object: it is the implicit superclass of every class and would + // dominate find-implementations searches in downstream tooling. + if (!isJavaLangObject((TypeElement) parentElement)) { + out.add((TypeElement) parentElement); + } + collectParents((TypeElement) parentElement, seen, out); + } + + private static boolean isJavaLangObject(TypeElement element) { + return "java.lang.Object".contentEquals(element.getQualifiedName()); + } + + private Set overriddenSymbols( + ExecutableElement sym, Element enclosingElement, Set out) { + if (!(enclosingElement instanceof TypeElement)) return out; + for (TypeMirror superType : types.directSupertypes(enclosingElement.asType())) { + if (!(superType instanceof DeclaredType)) continue; + Element superElement = ((DeclaredType) superType).asElement(); + if (!(superElement instanceof TypeElement)) continue; + boolean methodFound = false; + for (Element enclosed : superElement.getEnclosedElements()) { + if (!(enclosed instanceof ExecutableElement)) continue; + ExecutableElement candidate = (ExecutableElement) enclosed; + if (!elements.overrides(sym, candidate, (TypeElement) sym.getEnclosingElement())) continue; + String symbol = scipSymbol(candidate); + if (!symbol.isEmpty()) out.add(symbol); + methodFound = true; + overriddenSymbols(candidate, superElement, out); + } + if (!methodFound) overriddenSymbols(sym, superElement, out); + } + return out; + } + + // ======================================= + // Misc + // ======================================= + private String readSource() { + try { + return compUnitTree.getSourceFile().getCharContent(true).toString(); + } catch (IOException e) { + return ""; + } + } + + String getSource() { + return source; + } + + private String docComment(Tree tree) { + try { + TreePath treePath = nodes.get(tree); + return trees.getDocComment(treePath); + } catch (NullPointerException e) { + // javac's JavacElements#getDocComment occasionally NPEs on synthetic trees. + return null; + } + } + + private static boolean isAnonymous(Element sym) { + return sym != null && sym.getSimpleName().length() == 0; + } +} diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbSignatures.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbSignatures.java deleted file mode 100644 index b49cac78f..000000000 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbSignatures.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.sourcegraph.semanticdb_javac; - -import com.sourcegraph.semanticdb.LocalSymbolsCache; -import com.sourcegraph.semanticdb.Semanticdb; - -import com.sourcegraph.semanticdb.Semanticdb.*; - -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import java.util.List; -import java.util.stream.Collectors; - -import static com.sourcegraph.semanticdb.SemanticdbBuilders.*; -import static com.sourcegraph.semanticdb_javac.SemanticdbTypeVisitor.UNRESOLVED_TYPE_REF; - -public final class SemanticdbSignatures { - private final GlobalSymbolsCache cache; - private final LocalSymbolsCache locals; - private final Types types; - - public SemanticdbSignatures( - GlobalSymbolsCache cache, LocalSymbolsCache locals, Types types) { - this.cache = cache; - this.locals = locals; - this.types = types; - } - - public Signature generateSignature(Element sym) { - if (sym instanceof TypeElement) { - return generateClassSignature((TypeElement) sym); - } else if (sym instanceof ExecutableElement) { - return generateMethodSignature((ExecutableElement) sym); - } else if (sym instanceof VariableElement) { - return generateFieldSignature((VariableElement) sym); - } else if (sym instanceof TypeParameterElement) { - return generateTypeSignature((TypeParameterElement) sym); - } - return null; - } - - private Signature generateClassSignature(TypeElement sym) { - ClassSignature.Builder builder = ClassSignature.newBuilder(); - - builder.setTypeParameters(generateScope(sym.getTypeParameters())); - - for (TypeMirror superType : types.directSupertypes(sym.asType())) { - Semanticdb.Type semanticdbType = generateType(superType); - if (semanticdbType == null) { - semanticdbType = UNRESOLVED_TYPE_REF; - } - builder.addParents(semanticdbType); - } - - builder.setDeclarations(generateScope(sym.getEnclosedElements())); - - return signature(builder); - } - - private Signature generateMethodSignature(ExecutableElement sym) { - MethodSignature.Builder builder = MethodSignature.newBuilder(); - - builder.setTypeParameters(generateScope(sym.getTypeParameters())); - - builder.addParameterLists(generateScope(sym.getParameters())); - - Semanticdb.Type returnType = generateType(sym.getReturnType()); - if (returnType != null) { - builder.setReturnType(returnType); - } - - List thrownTypes = - sym.getThrownTypes().stream().map(this::generateType).collect(Collectors.toList()); - builder.addAllThrows(thrownTypes); - - return signature(builder); - } - - private Signature generateFieldSignature(VariableElement sym) { - Semanticdb.Type generateType = generateType(sym.asType()); - if (generateType == null) { - generateType = UNRESOLVED_TYPE_REF; - } - return signature(ValueSignature.newBuilder().setTpe(generateType)); - } - - private Signature generateTypeSignature(TypeParameterElement sym) { - TypeSignature.Builder builder = TypeSignature.newBuilder(); - - if (sym instanceof TypeElement) { - builder.setTypeParameters(generateScope(((TypeElement) sym).getTypeParameters())); - } - - TypeMirror varType = sym.asType(); - if (varType instanceof TypeVariable) { - Semanticdb.Type upperBound = generateType(((TypeVariable) varType).getUpperBound()); - if (upperBound != null) builder.setUpperBound(upperBound); - else builder.setUpperBound(UNRESOLVED_TYPE_REF); - } else builder.setUpperBound(UNRESOLVED_TYPE_REF); - - return signature(builder); - } - - private Scope generateScope(List elements) { - Scope.Builder scope = Scope.newBuilder(); - for (Element typeVar : elements) { - scope.addSymlinks(cache.semanticdbSymbol(typeVar, locals)); - } - return scope.build(); - } - - private Semanticdb.Type generateType(TypeMirror mirror) { - return new SemanticdbTypeVisitor(cache, locals, types).semanticdbType(mirror); - } -} diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java index c134dffad..c6a6fff2d 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java @@ -2,22 +2,15 @@ import com.sourcegraph.semanticdb.LocalSymbolsCache; import com.sourcegraph.semanticdb.NoRelativePathMode; -import com.sourcegraph.semanticdb.Semanticdb; -import com.sourcegraph.semanticdb.SemanticdbDocumentBuilder; -import com.sourcegraph.semanticdb.SemanticdbPaths; +import com.sourcegraph.semanticdb.ScipDocumentBuilder; +import com.sourcegraph.semanticdb.ScipShardPaths; +import com.sourcegraph.semanticdb.ScipShardWriter; import com.sourcegraph.semanticdb.SemanticdbSymbols; -import com.sourcegraph.semanticdb.SemanticdbWriter; import com.sourcegraph.semanticdb.UriScheme; - import com.sun.source.util.JavacTask; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; import com.sun.source.util.Trees; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -import javax.lang.model.element.Element; -import javax.tools.JavaFileObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -29,10 +22,15 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.Optional; +import javax.lang.model.element.Element; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.JavaFileObject; +import org.scip_code.scip.Document; /** - * Callback hook that generates SemanticDB when the compiler has completed typechecking a Java - * source file. + * Callback hook that emits a per-source SCIP shard once the compiler has finished typechecking a + * Java compilation unit. Shards are merged into the final SCIP index by the aggregator. */ public final class SemanticdbTaskListener implements TaskListener { private final SemanticdbJavacOptions options; @@ -61,21 +59,16 @@ public SemanticdbTaskListener( @Override public void started(TaskEvent e) { - // Upon first encounter with a file (before any other tasks are run) - // we remove the semanticdb file for this source file to ensure - // stale data doesn't cause problems - if (e.getKind() == TaskEvent.Kind.ENTER) { - inferBazelSourceroot(e.getSourceFile()); - Result semanticdbPath = semanticdbOutputPath(options, e); - if (semanticdbPath.isOk()) { - Path output = semanticdbPath.getOrThrow(); - perSourceState.remove(output); - try { - Files.deleteIfExists(output); - } catch (IOException ex) { - this.reportException(ex, e); - } - } + if (e.getKind() != TaskEvent.Kind.ENTER) return; + inferBazelSourceroot(e.getSourceFile()); + Result shardPath = scipShardOutputPath(e); + if (!shardPath.isOk()) return; + Path output = shardPath.getOrThrow(); + perSourceState.remove(output); + try { + Files.deleteIfExists(output); + } catch (IOException ex) { + reportException(ex, e); } } @@ -85,9 +78,7 @@ public void finished(TaskEvent e) { if (!options.errors.isEmpty()) { if (!options.alreadyReportedErrors) { options.alreadyReportedErrors = true; - for (String error : options.errors) { - reporter.error(error, e); - } + for (String error : options.errors) reporter.error(error, e); } return; } @@ -96,26 +87,21 @@ public void finished(TaskEvent e) { onFinishedAnalyze(e); } catch (Throwable ex) { // Catch exceptions because we don't want to stop the compilation even if this - // plugin has a - // bug. We report the full stack trace because it's helpful for bug reports. - // Exceptions - // should only happen in *exceptional* situations and they should be reported - // upstream. + // plugin has a bug. We report the full stack trace because it's helpful for bug + // reports. Exceptions should only happen in *exceptional* situations and they + // should be reported upstream. Throwable throwable = ex; if (e.getSourceFile() != null) { throwable = new CompilationUnitException( String.valueOf(e.getSourceFile().toUri().toString()), throwable); } - this.reportException(throwable, e); + reportException(throwable, e); } } - // Uses reporter.error with the full stack trace of the exception instead of - // reporter.exception - // because reporter.exception doesn't seem to print any meaningful information - // about the - // exception, it just prints the location with an empty message. + // Uses reporter.error with the full stack trace instead of reporter.exception because + // the latter doesn't print any meaningful information about the exception. private void reportException(Throwable exception, TaskEvent e) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(baos); @@ -125,39 +111,45 @@ private void reportException(Throwable exception, TaskEvent e) { } private void onFinishedAnalyze(TaskEvent e) { - Result path = semanticdbOutputPath(options, e); - if (path != null) { - if (path.isOk()) { - Path output = path.getOrThrow(); - PerSourceState state = perSourceState.computeIfAbsent(output, k -> new PerSourceState()); - Semanticdb.TextDocument textDocument = - new SemanticdbVisitor( - globals, - state.locals, - e.getCompilationUnit(), - options, - types, - trees, - elements, - state.documentBuilder) - .buildTextDocument(e.getCompilationUnit()); - writeSemanticdb(e, output, textDocument); - } else { - reporter.error(path.getErrorOrThrow(), e); - } + Result path = scipShardOutputPath(e); + if (path == null) return; + if (!path.isOk()) { + reporter.error(path.getErrorOrThrow(), e); + return; } + Path output = path.getOrThrow(); + PerSourceState state = perSourceState.computeIfAbsent(output, k -> new PerSourceState()); + ScipVisitor visitor = + new ScipVisitor( + globals, + state.locals, + e.getCompilationUnit(), + types, + trees, + elements, + state.documentBuilder); + visitor.visitCompilationUnit(); + String relativePath = scipRelativePath(e); + String text = options.includeText ? visitor.getSource() : ""; + Document document = state.documentBuilder.build("java", relativePath, text); + writeShard(e, output, document); } - private void writeSemanticdb(TaskEvent event, Path output, Semanticdb.TextDocument textDocument) { + private String scipRelativePath(TaskEvent e) { + Path absolutePath = absolutePathFromUri(options, e.getSourceFile()); + return ScipShardPaths.relativePath(options.sourceroot, absolutePath); + } + + private void writeShard(TaskEvent event, Path output, Document document) { try { - SemanticdbWriter.writeTextDocument(output, textDocument); + ScipShardWriter.writeShard(output, document); } catch (IOException e) { - this.reportException(e, event); + reportException(e, event); } } private static final class PerSourceState { - final SemanticdbDocumentBuilder documentBuilder = new SemanticdbDocumentBuilder(); + final ScipDocumentBuilder documentBuilder = new ScipDocumentBuilder(); final LocalSymbolsCache locals = new LocalSymbolsCache<>(new IdentityHashMap<>(), SemanticdbSymbols::local); } @@ -166,64 +158,41 @@ public static Path absolutePathFromUri(SemanticdbJavacOptions options, JavaFileO URI uri = file.toUri(); if (options.uriScheme == UriScheme.BAZEL) { String toString = file.toString().replace(":", "/"); - // This solution is hacky, and it would be very nice to use a dedicated API - // instead. - // The Bazel Java compiler constructs `SimpleFileObject/DirectoryFileObject` - // with a - // "user-friendly" name that points to the original source file and an - // underlying/actual + // Bazel's Java compiler constructs `SimpleFileObject/DirectoryFileObject` with a + // "user-friendly" name that points to the original source file and an underlying // file path in a temporary directory. We're constrained by having to use only - // public APIs of - // the Java compiler and `toString()` seems to be the only way to access the - // user-friendly - // path. + // public APIs of the Java compiler and `toString()` seems to be the only way to + // access the user-friendly path. String[] knownBazelToStringPatterns = new String[] {"SimpleFileObject[", "DirectoryFileObject["}; for (String pattern : knownBazelToStringPatterns) { if (toString.startsWith(pattern) && toString.endsWith("]")) { Path path = Paths.get(toString.substring(pattern.length(), toString.length() - 1)); - if (path.isAbsolute()) { - return path; - } + if (path.isAbsolute()) return path; return options.sourceroot.resolve(path); } } - // Fallback to default behavior. } - return Paths.get(uri); } // Infers the `-sourceroot:` flag from the provided file. // FIXME: add unit tests https://github.com/sourcegraph/scip-java/issues/444 private void inferBazelSourceroot(JavaFileObject file) { - if (options.uriScheme != UriScheme.BAZEL || options.sourceroot != null) { - return; - } + if (options.uriScheme != UriScheme.BAZEL || options.sourceroot != null) return; Path absolutePath = absolutePathFromUri(options, file); Path uriPath = Paths.get(file.toUri()); - // absolutePath is the "human-readable" original path, for example - // /home/repo/com/example/Hello.java - // uriPath is the sandbox/temporary file path, for example - // /private/var/tmp/com/example/Hello.java - // - // We infer sourceroot by iterating the names of both files in reverse order - // and stop at the first entry where the two paths are different. For the - // example above, we compare "Hello.java", then "example", then "com", and - // when we reach "repo" != "tmp" then we guess that "/home/repo" is the - // sourceroot. This logic is brittle and it would be nice to use more - // dedicated APIs, but Bazel actively makes an effort to sandbox - // compilation and hide access to the original workspace, which is why we - // resort to solutions like this. + // absolutePath is the "human-readable" original path, e.g. /home/repo/com/example/Hello.java. + // uriPath is the sandbox/temporary file path, e.g. /private/var/tmp/com/example/Hello.java. + // Infer sourceroot by iterating the names of both files in reverse order and stopping at + // the first entry where the two paths differ. int relativePathDepth = 0; int uriPathDepth = uriPath.getNameCount(); int absolutePathDepth = absolutePath.getNameCount(); while (relativePathDepth < uriPathDepth && relativePathDepth < absolutePathDepth) { String uriName = uriPath.getName(uriPathDepth - relativePathDepth - 1).toString(); String pathName = absolutePath.getName(absolutePathDepth - relativePathDepth - 1).toString(); - if (!uriName.equals(pathName)) { - break; - } + if (!uriName.equals(pathName)) break; relativePathDepth++; } options.sourceroot = @@ -232,53 +201,41 @@ private void inferBazelSourceroot(JavaFileObject file) { .resolve(absolutePath.subpath(0, absolutePathDepth - relativePathDepth)); } - private Result semanticdbOutputPath(SemanticdbJavacOptions options, TaskEvent e) { + private Result scipShardOutputPath(TaskEvent e) { Path absolutePath = absolutePathFromUri(options, e.getSourceFile()); Optional happyPath = - SemanticdbPaths.semanticdbPath(options.targetroot, options.sourceroot, absolutePath); - if (happyPath.isPresent()) { - return Result.ok(happyPath.get()); - } + ScipShardPaths.shardPath(options.targetroot, options.sourceroot, absolutePath); + if (happyPath.isPresent()) return Result.ok(happyPath.get()); switch (options.noRelativePath) { case INDEX_ANYWAY: - // Come up with a unique relative path for this file even if it's not under the - // sourceroot. - // By indexing auto-generated files, we collect SymbolInformation for - // auto-generated symbol, - // which results in more useful hover tooltips in the editor. - // In the future, we may want to additionally embed the full text contents of - // these files - // so that it's possible to browse generated files with precise code navigation. String uniqueFilename = - String.format("%d.%s.semanticdb", ++noRelativePathCounter, absolutePath.getFileName()); - Path semanticdbOutputPath = + String.format("%d.%s.scip", ++noRelativePathCounter, absolutePath.getFileName()); + Path output = options .targetroot .resolve("META-INF") - .resolve("semanticdb") + .resolve("scip") .resolve("no-relative-path") .resolve(uniqueFilename); - return Result.ok(semanticdbOutputPath); + return Result.ok(output); case WARNING: reporter.info( String.format( "Skipping file '%s' because it is not under the sourceroot '%s'", absolutePath, options.sourceroot), e); + // fall through case SKIP: return null; case ERROR: default: String baseMessage = String.format( - "Unable to detect the relative path of '%s'. A common reason for this error is that the file is that this file is auto-generated. " + "Unable to detect the relative path of '%s'. A common reason for this error is that the file is auto-generated. " + "To fix this problem update the flag -no-relative-path:VALUE to have one of the following values: %s.", absolutePath, NoRelativePathMode.validStringValuesWithoutError()); - if (options.uriScheme == UriScheme.BAZEL) { - return Result.error(baseMessage); - } - + if (options.uriScheme == UriScheme.BAZEL) return Result.error(baseMessage); return Result.error( baseMessage + " Alternatively, configure the -sourceroot:PATH flag to point to a directory path that is the parent of all indexed files."); diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTrees.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTrees.java deleted file mode 100644 index e143c4596..000000000 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTrees.java +++ /dev/null @@ -1,261 +0,0 @@ -package com.sourcegraph.semanticdb_javac; - -import com.sourcegraph.semanticdb.LocalSymbolsCache; -import com.sourcegraph.semanticdb.Semanticdb; - -import com.sun.source.tree.*; -import com.sun.source.util.Trees; -import javax.lang.model.element.Element; -import javax.lang.model.util.Types; -import javax.lang.model.type.TypeMirror; -import com.sun.source.util.TreePath; -import com.sun.source.tree.Tree; -import com.sun.source.tree.Tree.Kind; - -import java.util.HashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import static com.sourcegraph.semanticdb.SemanticdbBuilders.*; -import static com.sourcegraph.semanticdb_javac.SemanticdbTypeVisitor.ARRAY_SYMBOL; - -public class SemanticdbTrees { - public SemanticdbTrees( - GlobalSymbolsCache globals, - LocalSymbolsCache locals, - String semanticdbUri, - Types types, - Trees trees, - HashMap nodes) { - this.globals = globals; - this.locals = locals; - this.semanticdbUri = semanticdbUri; - this.types = types; - this.trees = trees; - this.nodes = nodes; - this.typeVisitor = new SemanticdbTypeVisitor(globals, locals, types); - } - - private final GlobalSymbolsCache globals; - private final LocalSymbolsCache locals; - private final String semanticdbUri; - private final Types types; - private final Trees trees; - private final HashMap nodes; - private final SemanticdbTypeVisitor typeVisitor; - - public List annotations(Tree node) { - if (!(node instanceof ClassTree) - && !(node instanceof MethodTree) - && !(node instanceof VariableTree)) return null; - - List annotations = new ArrayList<>(); - - ModifiersTree mods; - if (node instanceof ClassTree) { - mods = ((ClassTree) node).getModifiers(); - } else if (node instanceof MethodTree) { - mods = ((MethodTree) node).getModifiers(); - } else { - mods = ((VariableTree) node).getModifiers(); - } - - for (AnnotationTree annotation : mods.getAnnotations()) { - annotations.add(annotationBuilder(annotation)); - } - - return annotations; - } - - public Semanticdb.AnnotationTree annotationBuilder(AnnotationTree annotation) { - ArrayList params = new ArrayList<>(annotation.getArguments().size()); - - for (ExpressionTree param : annotation.getArguments()) { - // anecdotally not always AssignmentTree in some situations when a compilation - // unit can't - // resolve symbols fully - if (param instanceof AssignmentTree) { - AssignmentTree assign = (AssignmentTree) param; - ExpressionTree assignValue = assign.getExpression(); - TreePath variableTreePath = nodes.get(assign.getVariable()); - if (variableTreePath != null) { - Element variableSym = trees.getElement(variableTreePath); - String symbol = globals.semanticdbSymbol(variableSym, locals); - params.add(tree(assignTree(tree(idTree(symbol)), annotationParameter(assignValue)))); - } - } else { - params.add(annotationParameter(param)); - } - } - - TreePath annotationTreePath = nodes.get(annotation); - Element annotationSym = trees.getElement(annotationTreePath); - - Semanticdb.Type type = typeVisitor.semanticdbType(annotationSym.asType()); - return annotationTree(type, params); - } - - private TypeMirror getTreeType(Tree tree) { - TreePath path = nodes.get(tree); - return trees.getTypeMirror(path); - } - - private Semanticdb.Tree annotationParameter(ExpressionTree expr) { - if (expr instanceof MemberSelectTree) { - TreePath expressionTreePath = nodes.get(expr); - Element expressionSym = trees.getElement(expressionTreePath); - return tree( - selectTree( - tree(idTree(globals.semanticdbSymbol(expressionSym.getEnclosingElement(), locals))), - idTree(globals.semanticdbSymbol(expressionSym, locals)))); - } else if (expr instanceof NewArrayTree) { - NewArrayTree rhs = (NewArrayTree) expr; - return tree( - applyTree( - tree(idTree(ARRAY_SYMBOL)), - rhs.getInitializers().stream() - .map(this::annotationParameter) - .collect(Collectors.toList()))); - } else if (expr instanceof LiteralTree) { - // Literals can either be a primitive or String - Object value = ((LiteralTree) expr).getValue(); - final Semanticdb.Constant constant; - // Technically, annotation parameter values cannot be null, - // according to JLS: https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.1 - // But this codepath is still possible to hit when compiling invalid code - and - // we should handle the null const case in order to fail more gracefully - if (value == null) constant = nullConst(); - else if (value instanceof String) constant = stringConst((String) value); - else if (value instanceof Boolean) constant = booleanConst((Boolean) value); - else if (value instanceof Byte) constant = byteConst((Byte) value); - else if (value instanceof Short) constant = shortConst((Short) value); - else if (value instanceof Integer) constant = intConst((Integer) value); - else if (value instanceof Long) constant = longConst((Long) value); - else if (value instanceof Character) constant = charConst((Character) value); - else if (value instanceof Float) constant = floatConst((Float) value); - else if (value instanceof Double) constant = doubleConst((Double) value); - else - throw new IllegalStateException( - semanticdbUri - + ": annotation parameter rhs was of unexpected class type " - + value.getClass() - + "\n" - + value); - return tree(literalTree(constant)); - } else if (expr instanceof AnnotationTree) { - return tree(annotationBuilder((AnnotationTree) expr)); - } else if (expr instanceof IdentifierTree) { - TreePath expressionTreePath = nodes.get(expr); - Element expressionSym = trees.getElement(expressionTreePath); - return tree(idTree(globals.semanticdbSymbol(expressionSym, locals))); - } else if (expr instanceof BinaryTree) { - BinaryTree binExpr = (BinaryTree) expr; - return tree( - binopTree( - annotationParameter(binExpr.getLeftOperand()), - semanticdbBinaryOperator(expr.getKind()), - annotationParameter(binExpr.getRightOperand()))); - } else if (expr instanceof UnaryTree) { - UnaryTree unaryExpr = (UnaryTree) expr; - return tree( - unaryOpTree( - semanticdbUnaryOperator(unaryExpr.getKind()), - annotationParameter(unaryExpr.getExpression()))); - } else if (expr instanceof ParenthesizedTree) { - ParenthesizedTree parenExpr = (ParenthesizedTree) expr; - return annotationParameter(parenExpr.getExpression()); - } else if (expr instanceof TypeCastTree) { - TypeCastTree tree = (TypeCastTree) expr; - return tree( - castTree( - typeVisitor.semanticdbType(getTreeType(tree.getType())), - annotationParameter(tree.getExpression()))); - } else { - throw new IllegalArgumentException( - semanticdbUri - + ": annotation parameter rhs was of unexpected tree node type " - + expr.getClass() - + "\n" - + expr); - } - } - - private Semanticdb.BinaryOperator semanticdbBinaryOperator(Tree.Kind kind) { - switch (kind) { - case PLUS: - return Semanticdb.BinaryOperator.PLUS; - case MINUS: - return Semanticdb.BinaryOperator.MINUS; - case MULTIPLY: - return Semanticdb.BinaryOperator.MULTIPLY; - case DIVIDE: - return Semanticdb.BinaryOperator.DIVIDE; - case REMAINDER: - return Semanticdb.BinaryOperator.REMAINDER; - case LESS_THAN: - return Semanticdb.BinaryOperator.LESS_THAN; - case GREATER_THAN: - return Semanticdb.BinaryOperator.GREATER_THAN; - case LEFT_SHIFT: - return Semanticdb.BinaryOperator.SHIFT_LEFT; - case RIGHT_SHIFT: - return Semanticdb.BinaryOperator.SHIFT_RIGHT; - case UNSIGNED_RIGHT_SHIFT: - return Semanticdb.BinaryOperator.SHIFT_RIGHT_UNSIGNED; - case EQUAL_TO: - return Semanticdb.BinaryOperator.EQUAL_TO; - case NOT_EQUAL_TO: - return Semanticdb.BinaryOperator.NOT_EQUAL_TO; - case LESS_THAN_EQUAL: - return Semanticdb.BinaryOperator.LESS_THAN_EQUAL; - case GREATER_THAN_EQUAL: - return Semanticdb.BinaryOperator.GREATER_THAN_EQUAL; - case CONDITIONAL_AND: - return Semanticdb.BinaryOperator.CONDITIONAL_AND; - case CONDITIONAL_OR: - return Semanticdb.BinaryOperator.CONDITIONAL_OR; - case AND: - return Semanticdb.BinaryOperator.AND; - case OR: - return Semanticdb.BinaryOperator.OR; - case XOR: - return Semanticdb.BinaryOperator.XOR; - default: - throw new IllegalStateException( - semanticdbUri + ": unexpected binary expression operator kind " + kind); - } - } - - private Semanticdb.UnaryOperator semanticdbUnaryOperator(Tree.Kind kind) { - switch (kind) { - case UNARY_MINUS: - return Semanticdb.UnaryOperator.UNARY_MINUS; - - case UNARY_PLUS: - return Semanticdb.UnaryOperator.UNARY_PLUS; - - case POSTFIX_INCREMENT: - return Semanticdb.UnaryOperator.UNARY_POSTFIX_INCREMENT; - - case POSTFIX_DECREMENT: - return Semanticdb.UnaryOperator.UNARY_POSTFIX_DECREMENT; - - case PREFIX_INCREMENT: - return Semanticdb.UnaryOperator.UNARY_PREFIX_INCREMENT; - - case PREFIX_DECREMENT: - return Semanticdb.UnaryOperator.UNARY_PREFIX_DECREMENT; - - case BITWISE_COMPLEMENT: - return Semanticdb.UnaryOperator.UNARY_BITWISE_COMPLEMENT; - - case LOGICAL_COMPLEMENT: - return Semanticdb.UnaryOperator.UNARY_LOGICAL_COMPLEMENT; - - default: - throw new IllegalStateException( - semanticdbUri + ": unexpected unary expression operator kind " + kind); - } - } -} diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTypeVisitor.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTypeVisitor.java deleted file mode 100644 index ba458ab9d..000000000 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTypeVisitor.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.sourcegraph.semanticdb_javac; - -import com.sourcegraph.semanticdb.LocalSymbolsCache; -import com.sourcegraph.semanticdb.Semanticdb; - -import javax.lang.model.element.Element; -import javax.lang.model.util.SimpleTypeVisitor8; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.WildcardType; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.IntersectionType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.NoType; -import javax.lang.model.util.Types; -import java.util.ArrayList; - -import static com.sourcegraph.semanticdb.SemanticdbBuilders.*; - -/** A TypeMirror tree visitor that constructs a recursive SemanticDB Type structure. */ -class SemanticdbTypeVisitor extends SimpleTypeVisitor8 { - static final Semanticdb.Type UNRESOLVED_TYPE_REF = typeRef("unresolved_type#"); - - static final String ARRAY_SYMBOL = "scala/Array#"; - - private final GlobalSymbolsCache cache; - private final LocalSymbolsCache locals; - private final Types types; - - SemanticdbTypeVisitor( - GlobalSymbolsCache cache, LocalSymbolsCache locals, Types types) { - this.cache = cache; - this.locals = locals; - this.types = types; - } - - public Semanticdb.Type semanticdbType(TypeMirror tpe) { - Semanticdb.Type result = super.visit(tpe); - return result == null ? UNRESOLVED_TYPE_REF : result; - } - - @Override - public Semanticdb.Type visitDeclared(DeclaredType t, Void unused) { - boolean isExistential = - t.getTypeArguments().stream().anyMatch((type) -> type instanceof WildcardType); - - ArrayList typeParams = new ArrayList<>(); - Semanticdb.Scope.Builder declarations = Semanticdb.Scope.newBuilder(); - for (TypeMirror type : t.getTypeArguments()) { - typeParams.add(semanticdbType(type)); - - if (type instanceof WildcardType) { - Semanticdb.TypeSignature.Builder typeSig = Semanticdb.TypeSignature.newBuilder(); - WildcardType wildcardType = (WildcardType) type; - - // semanticdb spec asks for List() not None for type_parameters field - typeSig.setTypeParameters(Semanticdb.Scope.newBuilder()); - - if (wildcardType.getExtendsBound() != null) { - typeSig.setUpperBound(super.visit(wildcardType.getExtendsBound())); - } else if (wildcardType.getSuperBound() != null) { - typeSig.setLowerBound(super.visit(wildcardType.getSuperBound())); - } - - declarations.addHardlinks( - Semanticdb.SymbolInformation.newBuilder() - .setSymbol("local_wildcard") - .setSignature(Semanticdb.Signature.newBuilder().setTypeSignature(typeSig))); - } else { - Element element = types.asElement(type); - declarations.addSymlinks(cache.semanticdbSymbol(element, locals)); - } - } - - if (!isExistential) { - return typeRef(cache.semanticdbSymbol(t.asElement(), locals), typeParams); - } else { - return existentialType( - typeRef(cache.semanticdbSymbol(t.asElement(), locals), typeParams), declarations.build()); - } - } - - @Override - public Semanticdb.Type visitArray(ArrayType t, Void unused) { - ArrayList types = new ArrayList(); - types.add(semanticdbType(t.getComponentType())); - return typeRef(ARRAY_SYMBOL, types); - } - - @Override - public Semanticdb.Type visitPrimitive(PrimitiveType t, Void unused) { - return typeRef(primitiveSymbol(t.getKind())); - } - - @Override - public Semanticdb.Type visitTypeVariable(TypeVariable t, Void unused) { - return typeRef(cache.semanticdbSymbol(t.asElement(), locals)); - } - - @Override - public Semanticdb.Type visitIntersection(IntersectionType t, Void unused) { - ArrayList types = new ArrayList<>(); - for (TypeMirror type : t.getBounds()) { - types.add(super.visit(type)); - } - - return intersectionType(types); - } - - @Override - public Semanticdb.Type visitWildcard(WildcardType t, Void unused) { - // https://github.com/scalameta/scalameta/issues/1703 - // https://sourcegraph.com/github.com/scalameta/scalameta/-/blob/semanticdb/metacp/src/main/scala/scala/meta/internal/javacp/Javacp.scala#L452:19 - return typeRef("local_wildcard"); - } - - @Override - public Semanticdb.Type visitNoType(NoType t, Void unused) { - return typeRef(primitiveSymbol(t.getKind())); - } - - public String primitiveSymbol(TypeKind kind) { - switch (kind) { - case BOOLEAN: - return "scala/Boolean#"; - case BYTE: - return "scala/Byte#"; - case SHORT: - return "scala/Short#"; - case INT: - return "scala/Int#"; - case LONG: - return "scala/Long#"; - case CHAR: - return "scala/Char#"; - case FLOAT: - return "scala/Float#"; - case DOUBLE: - return "scala/Double#"; - case VOID: - return "scala/Unit#"; - default: - throw new IllegalArgumentException("got " + kind.name()); - } - } -} diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java deleted file mode 100644 index 6f0207bd1..000000000 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java +++ /dev/null @@ -1,700 +0,0 @@ -package com.sourcegraph.semanticdb_javac; - -import com.sourcegraph.semanticdb.LocalSymbolsCache; -import com.sourcegraph.semanticdb.Semanticdb; - -import com.sourcegraph.semanticdb.SemanticdbDocumentBuilder; -import com.sourcegraph.semanticdb.SemanticdbPaths; -import com.sourcegraph.semanticdb.SemanticdbSymbols; - -import com.sun.source.util.SourcePositions; -import com.sun.source.util.Trees; -import com.sun.source.util.TreePathScanner; -import com.sun.source.util.TreePath; -import com.sun.source.tree.CompilationUnitTree; -import com.sun.source.tree.MemberReferenceTree; -import com.sun.source.tree.IdentifierTree; -import com.sun.source.tree.VariableTree; -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.Tree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.LineMap; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.NewClassTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.PackageTree; -import com.sun.source.tree.TypeCastTree; -import com.sun.source.tree.TypeParameterTree; -import com.sun.source.tree.ParameterizedTypeTree; -import com.sun.source.tree.AnnotatedTypeTree; - -import javax.tools.Diagnostic; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.NoType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.util.Types; -import javax.lang.model.util.Elements; -import com.sourcegraph.semanticdb.Semanticdb.SymbolInformation.Kind; -import com.sourcegraph.semanticdb.Semanticdb.SymbolInformation.Property; -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence.Role; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.HashSet; -import java.util.Set; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import static com.sourcegraph.semanticdb.SemanticdbBuilders.*; - -/** Walks the AST of a typechecked compilation unit and generates a SemanticDB TextDocument. */ -public class SemanticdbVisitor extends TreePathScanner { - - private final GlobalSymbolsCache globals; - private final LocalSymbolsCache locals; - private final Types types; - private final Trees trees; - private final CompilationUnitTree compUnitTree; - private final Elements elements; - private final SemanticdbJavacOptions options; - private final SemanticdbDocumentBuilder documentBuilder; - private String source; - private String uri; - - private final LinkedHashMap nodes; - - public SemanticdbVisitor( - GlobalSymbolsCache globals, - LocalSymbolsCache locals, - CompilationUnitTree compUnitTree, - SemanticdbJavacOptions options, - Types types, - Trees trees, - Elements elements, - SemanticdbDocumentBuilder documentBuilder) { - this.globals = globals; - this.locals = locals; - this.options = options; - this.types = types; - this.elements = elements; - this.trees = trees; - this.compUnitTree = compUnitTree; - this.documentBuilder = documentBuilder; - this.source = semanticdbText(); - this.uri = semanticdbUri(compUnitTree, options); - this.nodes = new LinkedHashMap<>(); - } - - public Semanticdb.TextDocument buildTextDocument(CompilationUnitTree tree) { - this.scan(tree, null); // Trigger recursive AST traversal to collect SemanticDB information. - - resolveNodes(); - - return documentBuilder.build( - Semanticdb.Language.JAVA, uri, options.includeText ? this.source : "", semanticdbMd5()); - } - - private Optional emitSymbolOccurrence( - Element sym, Tree tree, Name name, Role role, CompilerRange kind) { - if (sym == null || name == null) return Optional.empty(); - Optional range = semanticdbRange(tree, kind, sym, name.toString()); - if (role == Role.DEFINITION) { - emitSymbolOccurrence(sym, range, role, computeEnclosingRange(tree)); - // Only emit SymbolInformation for symbols that are defined in this compilation unit. - emitSymbolInformation(sym, tree); - return range; - } - emitSymbolOccurrence(sym, range, role, Optional.empty()); - return range; - } - - private void emitSymbolOccurrence( - Element sym, - Optional range, - Role role, - Optional enclosingRange) { - if (sym == null) return; - Optional occ = - semanticdbOccurrence(sym, range, role, enclosingRange); - occ.ifPresent(documentBuilder::addOccurrence); - } - - private void emitSymbolInformation(Element sym, Tree tree) { - String symbol = semanticdbSymbol(sym); - Semanticdb.SymbolInformation.Builder builder = symbolInformation(symbol); - Semanticdb.Documentation documentation = semanticdbDocumentation(tree); - if (documentation != null) builder.setDocumentation(documentation); - Semanticdb.Signature signature = semanticdbSignature(sym); - if (signature != null) builder.setSignature(signature); - if (SemanticdbSymbols.isLocal(symbol)) { - String enclosingSymbol = semanticdbSymbol(sym.getEnclosingElement()); - if (enclosingSymbol != null) builder.setEnclosingSymbol(enclosingSymbol); - } - - List annotations = - new SemanticdbTrees(globals, locals, uri, types, trees, nodes).annotations(tree); - if (annotations != null) builder.addAllAnnotations(annotations); - - builder - .setProperties(semanticdbSymbolInfoProperties(sym)) - .setDisplayName(sym.getSimpleName().toString()) - .setAccess(semanticdbAccess(sym)); - - switch (sym.getKind()) { - case ENUM: - case CLASS: - builder.setKind(Kind.CLASS); - builder.addAllOverriddenSymbols(semanticdbParentSymbols((TypeElement) sym)); - break; - case INTERFACE: - case ANNOTATION_TYPE: - builder.setKind(Kind.INTERFACE); - builder.addAllOverriddenSymbols(semanticdbParentSymbols((TypeElement) sym)); - break; - case FIELD: - builder.setKind(Kind.FIELD); - break; - case METHOD: - builder.setKind(Kind.METHOD); - builder.addAllOverriddenSymbols( - semanticdbOverrides( - (ExecutableElement) sym, sym.getEnclosingElement(), new HashSet<>())); - break; - case CONSTRUCTOR: - builder.setKind(Kind.CONSTRUCTOR); - break; - case TYPE_PARAMETER: - builder.setKind(Kind.TYPE_PARAMETER); - break; - case ENUM_CONSTANT: // overwrite previous value here - String args = - ((NewClassTree) ((VariableTree) tree).getInitializer()) - .getArguments().stream() - .map(ExpressionTree::toString) - .collect(Collectors.joining(", ")); - if (!args.isEmpty()) - builder.setDisplayName(sym.getSimpleName().toString() + "(" + args + ")"); - break; - case LOCAL_VARIABLE: - builder.setKind(Kind.LOCAL); - break; - } - - Semanticdb.SymbolInformation info = builder.build(); - - documentBuilder.addSymbol(info); - } - - void resolveNodes() { - // ignore parts of NewClassTree. It would cause references to classes in addition to references - // to constructors. In these cases, the references to classes aren't wanted - HashSet ignoreNodes = new HashSet<>(); - for (Tree node : nodes.keySet()) - if (node instanceof NewClassTree) { - NewClassTree newClassTree = (NewClassTree) node; - if (newClassTree.getClassBody() == null) { - if (newClassTree.getIdentifier() instanceof ParameterizedTypeTree) { - ParameterizedTypeTree paramNode = (ParameterizedTypeTree) newClassTree.getIdentifier(); - ignoreNodes.add(paramNode.getType()); - } - ignoreNodes.add(newClassTree.getIdentifier()); - } - } - - for (Map.Entry entry : nodes.entrySet()) { - Tree node = entry.getKey(); - if (!ignoreNodes.contains(node)) { - if (node instanceof TypeParameterTree) { - resolveTypeParameterTree((TypeParameterTree) node, entry.getValue()); - } else if (node instanceof ClassTree) { - resolveClassTree((ClassTree) node, entry.getValue()); - } else if (node instanceof MethodTree) { - resolveMethodTree((MethodTree) node, entry.getValue()); - } else if (node instanceof VariableTree) { - resolveVariableTree((VariableTree) node, entry.getValue()); - } else if (node instanceof IdentifierTree) { - resolveIdentifierTree((IdentifierTree) node, entry.getValue()); - } else if (node instanceof MemberReferenceTree) { - resolveMemberReferenceTree((MemberReferenceTree) node, entry.getValue()); - } else if (node instanceof MemberSelectTree) { - resolveMemberSelectTree((MemberSelectTree) node, entry.getValue()); - } else if (node instanceof NewClassTree) { - resolveNewClassTree((NewClassTree) node, entry.getValue()); - } - } - } - } - - // ======================================= - // Overridden methods from TreePathScanner - // ======================================= - @Override - public Void scan(Tree tree, Void unused) { - if (tree != null) { - TreePath path = new TreePath(getCurrentPath(), tree); - nodes.put(tree, path); - } - return super.scan(tree, unused); - } - - @Override - public Void visitPackage(PackageTree node, Void unused) { - // Stop traversal at the package declaration. JDK 17+ TreePathScanner - // recurses into the package name's identifiers and would emit a - // self-reference for `package X.Y;`; JDK 11 does not. Skipping the - // whole package subtree keeps semanticdb output stable across JDKs by - // matching the JDK 11 behavior of not emitting a reference for the - // package declaration itself. - return null; - } - - private boolean isAnonymous(Element sym) { - return sym.getSimpleName().length() == 0; - } - - public static B bar(A paramA, B paramB) { - return paramB; - } - - private void resolveClassTree(ClassTree node, TreePath treePath) { - Element sym = trees.getElement(treePath); - if (sym != null && sym.getSimpleName().length() > 0) { - emitSymbolOccurrence( - sym, - node, - sym.getSimpleName(), - Role.DEFINITION, - CompilerRange.FROM_POINT_WITH_TEXT_SEARCH); - } - } - - private void resolveTypeParameterTree(TypeParameterTree node, TreePath treePath) { - Element sym = trees.getElement(treePath); - if (sym != null && sym.getSimpleName().length() > 0) { - emitSymbolOccurrence( - sym, node, sym.getSimpleName(), Role.DEFINITION, CompilerRange.FROM_POINT_TO_SYMBOL_NAME); - } - } - - private void resolveMethodTree(MethodTree node, TreePath treePath) { - Element sym = trees.getElement(treePath); - if (sym != null) { - Element enclosingElement = sym.getEnclosingElement(); - if (sym.getKind() != ElementKind.CONSTRUCTOR || !isAnonymous(enclosingElement)) { - Name name; - if (sym.getKind() == ElementKind.CONSTRUCTOR) name = enclosingElement.getSimpleName(); - else name = sym.getSimpleName(); - - emitSymbolOccurrence( - sym, node, name, Role.DEFINITION, CompilerRange.FROM_POINT_WITH_TEXT_SEARCH); - } - } - } - - private void resolveVariableTree(VariableTree node, TreePath treePath) { - Element sym = trees.getElement(treePath); - if (sym != null) { - Optional range = - emitSymbolOccurrence( - sym, - node, - sym.getSimpleName(), - Role.DEFINITION, - CompilerRange.FROM_POINT_WITH_TEXT_SEARCH); - if (sym.getKind() == ElementKind.ENUM_CONSTANT) { - TreePath typeTreePath = nodes.get(node.getInitializer()); - Element typeSym = trees.getElement(typeTreePath); - if (typeSym != null) emitSymbolOccurrence(typeSym, range, Role.REFERENCE, Optional.empty()); - } - } - } - - private void resolveIdentifierTree(IdentifierTree node, TreePath treePath) { - Name nodeName = node.getName(); - if (nodeName != null) { - Element sym = trees.getElement(treePath); - if (sym != null) { - boolean isThis = nodeName.toString().equals("this"); - boolean isSuper = !isThis && nodeName.toString().equals("super"); - // exclude `this.` references but include `this(` and `super(` references - if (((sym.getKind() == ElementKind.CONSTRUCTOR) == isThis) || (isSuper)) { - TreePath parentPath = treePath.getParentPath(); - Element parentSym = trees.getElement(parentPath); - if (parentSym == null || parentSym.getKind() != null) { - emitSymbolOccurrence( - sym, node, sym.getSimpleName(), Role.REFERENCE, CompilerRange.FROM_START_TO_END); - } - } - } - } - } - - private void resolveMemberReferenceTree(MemberReferenceTree node, TreePath treePath) { - Element sym = trees.getElement(treePath); - if (sym != null) { - emitSymbolOccurrence( - sym, node, sym.getSimpleName(), Role.REFERENCE, CompilerRange.FROM_END_TO_SYMBOL_NAME); - } - } - - private void resolveMemberSelectTree(MemberSelectTree node, TreePath treePath) { - Element sym = trees.getElement(treePath); - if (sym != null) { - emitSymbolOccurrence( - sym, node, sym.getSimpleName(), Role.REFERENCE, CompilerRange.FROM_END_TO_SYMBOL_NAME); - } - } - - private void resolveNewClassTree(NewClassTree node, TreePath treePath) { - // ignore anonymous classes - otherwise there will be a local reference to itself - if (node.getIdentifier() != null && node.getClassBody() == null) { - Element sym = trees.getElement(treePath); - if (sym != null) { - TreePath parentPath = treePath.getParentPath(); - Element parentSym = trees.getElement(parentPath); - - if (parentSym == null || parentSym.getKind() != ElementKind.ENUM_CONSTANT) { - TreePath identifierTreePath = nodes.get(node.getIdentifier()); - Element identifierSym = trees.getElement(identifierTreePath); - // Simplest case, e.g. `new String()` - if (identifierSym != null) { - emitSymbolOccurrence( - sym, - node, - identifierSym.getSimpleName(), - Role.REFERENCE, - CompilerRange.FROM_TEXT_SEARCH); - } - // More complex case, where the type is annotated: `new @TypeParameters String()` - else if (node.getIdentifier().getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node.getIdentifier(); - if (annotatedTypeTree.getUnderlyingType() != null - && annotatedTypeTree.getUnderlyingType().getKind() == Tree.Kind.IDENTIFIER) { - IdentifierTree ident = (IdentifierTree) annotatedTypeTree.getUnderlyingType(); - emitSymbolOccurrence( - sym, ident, ident.getName(), Role.REFERENCE, CompilerRange.FROM_TEXT_SEARCH); - } - } - } - } - } - } - - // ================================================= - // Utilities to generate SemanticDB data structures. - // ================================================= - - private Semanticdb.Signature semanticdbSignature(Element sym) { - - return new SemanticdbSignatures(globals, locals, types).generateSignature(sym); - } - - private String semanticdbSymbol(Element sym) { - return globals.semanticdbSymbol(sym, locals); - } - - private Optional semanticdbRange( - Tree tree, CompilerRange kind, Element sym, String name) { - if (sym == null) return Optional.empty(); - - SourcePositions sourcePositions = trees.getSourcePositions(); - int start = (int) sourcePositions.getStartPosition(compUnitTree, tree); - int end = (int) sourcePositions.getEndPosition(compUnitTree, tree); - if (kind.isPlusOne()) start++; - - if (name != null) { - if (kind.isFromTextSearch() && name.length() > 0) { - Optional startEndRange = - RangeFinder.findRange(sym, name, start, end, this.source, kind.isFromEnd()); - if (startEndRange.isPresent()) { - start = startEndRange.get().start; - end = startEndRange.get().end; - } - } else if (kind.isFromPoint()) { - if (start != Diagnostic.NOPOS) { - // text may not exist or may be out of bounds (e.g. generated source like Lombok) - int testEnd = start + name.length(); - if (source.length() > testEnd && source.substring(start, testEnd).equals(name)) - end = testEnd; - } - } else if (kind.isFromEndPoint()) { - if (end != Diagnostic.NOPOS) { - // text may not exist or may be out of bounds (e.g. generated source like Lombok) - int testStart = end - name.length(); - if (testStart >= 0 - && source.length() > end - && source.substring(testStart, end).equals(name)) start = testStart; - } - } - } - - if (start != Diagnostic.NOPOS && end != Diagnostic.NOPOS && end > start) { - LineMap lineMap = compUnitTree.getLineMap(); - Semanticdb.Range range = - Semanticdb.Range.newBuilder() - .setStartLine((int) lineMap.getLineNumber(start) - 1) - .setStartCharacter((int) lineMap.getColumnNumber(start) - 1) - .setEndLine((int) lineMap.getLineNumber(end) - 1) - .setEndCharacter((int) lineMap.getColumnNumber(end) - 1) - .build(); - - range = correctForTabs(range, lineMap, start); - - return Optional.of(range); - } - return Optional.empty(); - } - - private Semanticdb.Range correctForTabs(Semanticdb.Range range, LineMap lineMap, int start) { - int startLinePos = (int) lineMap.getPosition(lineMap.getLineNumber(start), 0); - - // javac replaces every tab with 8 spaces in the linemap. As this is potentially inconsistent - // with the source file itself, we adjust for that here if the line is actually indented with - // tabs. - // As for every tab there are 8 spaces, we remove 7 spaces for every tab to get the correct - // char offset (note: different to _column_ offset your editor shows) - if (this.source.charAt(startLinePos) == '\t') { - int count = 1; - while (this.source.charAt(++startLinePos) == '\t') count++; - range = - range - .toBuilder() - .setStartCharacter(range.getStartCharacter() - (count * 7)) - .setEndCharacter(range.getEndCharacter() - (count * 7)) - .build(); - } - - return range; - } - - private Optional semanticdbOccurrence( - Element sym, - Optional range, - Role role, - Optional enclosingRange) { - if (range.isPresent()) { - String ssym = semanticdbSymbol(sym); - if (!ssym.equals(SemanticdbSymbols.NONE)) { - Semanticdb.SymbolOccurrence occ = symbolOccurrence(ssym, range.get(), role, enclosingRange); - return Optional.of(occ); - } else { - return Optional.empty(); - } - } else { - return Optional.empty(); - } - } - - /** - * Computes the enclosing range for the given tree node. Returns the range of the nearest - * non-trivial enclosing AST node. For definition occurrences, this includes the entire definition - * including documentation. For reference occurrences, this includes the parent expression bounds. - */ - private Optional computeEnclosingRange(Tree tree) { - if (tree == null) return Optional.empty(); - - TreePath path = nodes.get(tree); - if (path == null) return Optional.empty(); - - // For method, class, and variable definitions, use the tree itself as the enclosing range - // since we're processing the definition node - Tree enclosingTree = tree; - if (!(tree instanceof MethodTree - || tree instanceof ClassTree - || tree instanceof VariableTree)) { - // For non-definition nodes (like references), use the parent - TreePath parentPath = path.getParentPath(); - if (parentPath == null) return Optional.empty(); - enclosingTree = parentPath.getLeaf(); - if (enclosingTree == null || enclosingTree == compUnitTree) return Optional.empty(); - } - - SourcePositions sourcePositions = trees.getSourcePositions(); - int start = (int) sourcePositions.getStartPosition(compUnitTree, enclosingTree); - int end = (int) sourcePositions.getEndPosition(compUnitTree, enclosingTree); - - if (start != Diagnostic.NOPOS && end != Diagnostic.NOPOS && end > start) { - LineMap lineMap = compUnitTree.getLineMap(); - Semanticdb.Range range = - Semanticdb.Range.newBuilder() - .setStartLine((int) lineMap.getLineNumber(start) - 1) - .setStartCharacter((int) lineMap.getColumnNumber(start) - 1) - .setEndLine((int) lineMap.getLineNumber(end) - 1) - .setEndCharacter((int) lineMap.getColumnNumber(end) - 1) - .build(); - - range = correctForTabs(range, lineMap, start); - - return Optional.of(range); - } - - return Optional.empty(); - } - - private String semanticdbText() { - if (source != null) return source; - try { - source = compUnitTree.getSourceFile().getCharContent(true).toString(); - } catch (IOException e) { - source = ""; - } - return source; - } - - private String semanticdbMd5() { - try { - byte[] bytes = - compUnitTree - .getSourceFile() - .getCharContent(true) - .toString() - .getBytes(StandardCharsets.UTF_8); - byte[] digest = MessageDigest.getInstance("MD5").digest(bytes); - StringBuilder sb = new StringBuilder(digest.length * 2); - for (byte b : digest) sb.append(String.format("%02X", b)); - return sb.toString(); - } catch (IOException | NoSuchAlgorithmException e) { - return ""; - } - } - - private int semanticdbSymbolInfoProperties(Element sym) { - int properties = 0; - properties |= - sym.getKind() == ElementKind.ENUM || sym.getKind() == ElementKind.ENUM_CONSTANT - ? Property.ENUM_VALUE - : 0; - for (Modifier modifier : sym.getModifiers()) { - if (modifier == Modifier.STATIC) properties |= Property.STATIC_VALUE; - else if (modifier == Modifier.DEFAULT) properties |= Property.DEFAULT_VALUE; - else if (modifier == Modifier.FINAL) properties |= Property.FINAL_VALUE; - else if (modifier == Modifier.ABSTRACT) properties |= Property.ABSTRACT_VALUE; - } - // for default interface methods, Modifier.ABSTRACT is also set... - if (((properties & Property.ABSTRACT_VALUE) > 0) && ((properties & Property.DEFAULT_VALUE) > 0)) - properties ^= Property.ABSTRACT_VALUE; - return properties; - } - - private List semanticdbParentSymbols(TypeElement typeElement) { - ArrayList parentSymbols = new ArrayList<>(); - Set parentElements = semanticdbParentTypeElements(typeElement, new HashSet<>()); - for (TypeElement parentElement : parentElements) { - String ssym = semanticdbSymbol(parentElement); - if (!Objects.equals(ssym, SemanticdbSymbols.NONE)) { - parentSymbols.add(ssym); - } - } - return parentSymbols; - } - - private Set semanticdbParentTypeElements( - TypeElement typeElement, Set result) { - TypeMirror superType = typeElement.getSuperclass(); - semanticdbParentSymbol(superType, result); - for (TypeMirror interfaceType : typeElement.getInterfaces()) { - semanticdbParentSymbol(interfaceType, result); - } - - return result; - } - - private void semanticdbParentSymbol(TypeMirror elementType, Set result) { - if (!(elementType instanceof NoType)) { - Element superElement = types.asElement(elementType); - if (superElement != null && superElement instanceof TypeElement) { - result.add((TypeElement) superElement); - semanticdbParentTypeElements((TypeElement) superElement, result); - } - } - } - - private Set semanticdbOverrides( - ExecutableElement sym, Element enclosingElement, HashSet overriddenSymbols) { - if (enclosingElement instanceof TypeElement) { - List superTypes = types.directSupertypes(enclosingElement.asType()); - // iterate through all super types - for (TypeMirror superType : superTypes) { - if (superType instanceof DeclaredType) { - Element superElement = ((DeclaredType) superType).asElement(); - // find all elements of super class - if (superElement instanceof TypeElement) { - boolean methodFound = false; - List enclosedElements = - ((TypeElement) superElement).getEnclosedElements(); - for (Element enclosedElement : enclosedElements) { - // check the element is a method - if (enclosedElement instanceof ExecutableElement) { - ExecutableElement enclosedExecutableElement = (ExecutableElement) enclosedElement; - // check the method overrides the original method - if (elements.overrides( - sym, enclosedExecutableElement, (TypeElement) sym.getEnclosingElement())) { - String symbol = semanticdbSymbol(enclosedExecutableElement); - overriddenSymbols.add(symbol); - methodFound = true; - semanticdbOverrides(enclosedExecutableElement, superElement, overriddenSymbols); - } - } - } - if (!methodFound) { - semanticdbOverrides(sym, superElement, overriddenSymbols); - } - } - } - } - } - return overriddenSymbols; - } - - private Semanticdb.Access semanticdbAccess(Element sym) { - for (Modifier modifier : sym.getModifiers()) { - if (modifier == Modifier.PRIVATE) return privateAccess(); - if (modifier == Modifier.PUBLIC) return publicAccess(); - if (modifier == Modifier.PROTECTED) return protectedAccess(); - } - return privateWithinAccess(semanticdbSymbol(sym.getEnclosingElement())); - } - - private static String semanticdbUri( - CompilationUnitTree compUnitTree, SemanticdbJavacOptions options) { - Path absolutePath = - SemanticdbTaskListener.absolutePathFromUri(options, compUnitTree.getSourceFile()); - return SemanticdbPaths.semanticdbUri(options.sourceroot, absolutePath); - } - - private Semanticdb.Documentation semanticdbDocumentation(Tree tree) { - try { - TreePath treePath = nodes.get(tree); - String doc = trees.getDocComment(treePath); - if (doc == null) return null; - - return Semanticdb.Documentation.newBuilder() - .setFormat(Semanticdb.Documentation.Format.JAVADOC) - .setMessage(doc) - .build(); - } catch (NullPointerException e) { - // Can happen in `getDocComment()` - // Caused by: java.lang.NullPointerException - // at com.sun.tools.javac.model.JavacElements.cast(JavacElements.java:605) - // at com.sun.tools.javac.model.JavacElements.getTreeAndTopLevel(JavacElements.java:543) - // at com.sun.tools.javac.model.JavacElements.getDocComment(JavacElements.java:321) - // at - // com.sourcegraph.semanticdb_javac.SemanticdbVisitor.semanticdbDocumentation(SemanticdbVisitor.java:233) - return null; - } - } -} diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Class.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Class.kt index 57939c894..ee0428e9c 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Class.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Class.kt @@ -12,31 +12,31 @@ class Class constructor(private var banana: Int, apple: String) : // ^^^^^ definition semanticdb maven . . snapshots/Class# // display_name Class -// documentation ```kotlin\npublic final class Class : Throwable\n``` -// relationship is_reference is_implementation semanticdb maven . . kotlin/Throwable# +// signature_documentation kotlin public final class Class : Throwable +// relationship is_implementation semanticdb maven . . kotlin/Throwable# // ^^^^^^^^^^^ definition semanticdb maven . . snapshots/Class#``(). // display_name Class -// documentation ```kotlin\npublic constructor(banana: Int, apple: String): Class\n``` +// signature_documentation kotlin public constructor(banana: Int, apple: String): Class // ^^^^^^ definition semanticdb maven . . snapshots/Class#``().(banana) // display_name banana -// documentation ```kotlin\nbanana: Int\n``` +// signature_documentation kotlin banana: Int // ^^^^^^ reference semanticdb maven . . snapshots/Class#``().(banana) // ^^^^^^ definition semanticdb maven . . snapshots/Class#banana. // display_name banana -// documentation ```kotlin\nprivate final var banana: Int\n``` +// signature_documentation kotlin private final var banana: Int // ^^^^^^ definition semanticdb maven . . snapshots/Class#getBanana(). // display_name banana -// documentation ```kotlin\nprivate get(): Int\n``` +// signature_documentation kotlin private get(): Int // ^^^^^^ definition semanticdb maven . . snapshots/Class#setBanana(). // display_name banana -// documentation ```kotlin\nprivate set(value: Int): Unit\n``` +// signature_documentation kotlin private set(value: Int): Unit // ^^^^^^ definition semanticdb maven . . snapshots/Class#setBanana().(value) // display_name value -// documentation ```kotlin\nvalue: Int\n``` +// signature_documentation kotlin value: Int // ^^^ reference semanticdb maven . . kotlin/Int# // ^^^^^ definition semanticdb maven . . snapshots/Class#``().(apple) // display_name apple -// documentation ```kotlin\napple: String\n``` +// signature_documentation kotlin apple: String // ^^^^^^ reference semanticdb maven . . kotlin/String# // ⌃ enclosing_range_end semanticdb maven . . snapshots/Class#``().(banana) // ⌃ enclosing_range_end semanticdb maven . . snapshots/Class#banana. @@ -61,24 +61,24 @@ val asdf = // ^^^^ definition semanticdb maven . . snapshots/Class#asdf. // display_name asdf -// documentation ```kotlin\npublic final val asdf: Any\n``` +// signature_documentation kotlin public final val asdf: Any // ^^^^ definition semanticdb maven . . snapshots/Class#getAsdf(). // display_name asdf -// documentation ```kotlin\npublic get(): Any\n``` +// signature_documentation kotlin public get(): Any // ⌄ enclosing_range_start semanticdb maven . . snapshots/``# // ⌄ enclosing_range_start semanticdb maven . . snapshots/``#``(). object { // ^^^^^^ definition semanticdb maven . . snapshots/``# // display_name -// documentation ```kotlin\nobject : Any\n``` +// signature_documentation kotlin object : Any // ^^^^^^ definition semanticdb maven . . snapshots/``#``(). // display_name -// documentation ```kotlin\nprivate constructor(): \n``` +// signature_documentation kotlin private constructor(): // ⌄ enclosing_range_start semanticdb maven . . snapshots/``#doStuff(). fun doStuff() = Unit // ^^^^^^^ definition semanticdb maven . . snapshots/``#doStuff(). // display_name doStuff -// documentation ```kotlin\npublic final fun doStuff(): Unit\n``` +// signature_documentation kotlin public final fun doStuff(): Unit // ⌃ enclosing_range_end semanticdb maven . . snapshots/``#doStuff(). } // ⌃ enclosing_range_end semanticdb maven . . snapshots/Class#asdf. @@ -90,7 +90,7 @@ constructor() : this(1, "") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/Class#``(+1). // display_name Class -// documentation ```kotlin\npublic constructor(): Class\n``` +// signature_documentation kotlin public constructor(): Class // ⌃ enclosing_range_end semanticdb maven . . snapshots/Class#``(+1). // ⌄ enclosing_range_start semanticdb maven . . snapshots/Class#``(+2). @@ -98,10 +98,10 @@ constructor(banana: Int) : this(banana, "") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/Class#``(+2). // display_name Class -// documentation ```kotlin\npublic constructor(banana: Int): Class\n``` +// signature_documentation kotlin public constructor(banana: Int): Class // ^^^^^^ definition semanticdb maven . . snapshots/Class#``(+2).(banana) // display_name banana -// documentation ```kotlin\nbanana: Int\n``` +// signature_documentation kotlin banana: Int // ^^^ reference semanticdb maven . . kotlin/Int# // ^^^^^^ reference semanticdb maven . . snapshots/Class#``(+2).(banana) // ⌃ enclosing_range_end semanticdb maven . . snapshots/Class#``(+2).(banana) @@ -111,7 +111,7 @@ fun run() { // ^^^ definition semanticdb maven . . snapshots/Class#run(). // display_name run -// documentation ```kotlin\npublic final fun run(): Unit\n``` +// signature_documentation kotlin public final fun run(): Unit println(Class::class) // ^^^^^^^ reference semanticdb maven . . kotlin/io/println(). println("I eat $banana for lunch") diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/CompanionOwner.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/CompanionOwner.kt index a5a386101..2d68d4638 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/CompanionOwner.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/CompanionOwner.kt @@ -6,24 +6,24 @@ class CompanionOwner { // ^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/CompanionOwner# // display_name CompanionOwner -// documentation ```kotlin\npublic final class CompanionOwner : Any\n``` +// signature_documentation kotlin public final class CompanionOwner : Any // ^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/CompanionOwner#``(). // display_name CompanionOwner -// documentation ```kotlin\npublic constructor(): CompanionOwner\n``` +// signature_documentation kotlin public constructor(): CompanionOwner // ⌄ enclosing_range_start semanticdb maven . . snapshots/CompanionOwner#Companion# // ⌄ enclosing_range_start semanticdb maven . . snapshots/CompanionOwner#Companion#``(). companion object { // ^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/CompanionOwner#Companion# 2:3 // display_name Companion -// documentation ```kotlin\npublic final companion object Companion : Any\n``` +// signature_documentation kotlin public final companion object Companion : Any // ^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/CompanionOwner#Companion#``(). 2:3 // display_name Companion -// documentation ```kotlin\nprivate constructor(): CompanionOwner.Companion\n``` +// signature_documentation kotlin private constructor(): CompanionOwner.Companion // ⌄ enclosing_range_start semanticdb maven . . snapshots/CompanionOwner#Companion#create(). fun create(): CompanionOwner = CompanionOwner() // ^^^^^^ definition semanticdb maven . . snapshots/CompanionOwner#Companion#create(). // display_name create -// documentation ```kotlin\npublic final fun create(): CompanionOwner\n``` +// signature_documentation kotlin public final fun create(): CompanionOwner // ^^^^^^^^^^^^^^ reference semanticdb maven . . snapshots/CompanionOwner# // ^^^^^^^^^^^^^^ reference semanticdb maven . . snapshots/CompanionOwner#``(). // ⌃ enclosing_range_end semanticdb maven . . snapshots/CompanionOwner#Companion#create(). @@ -34,7 +34,7 @@ fun create(): Int = CompanionOwner.create().hashCode() // ^^^^^^ definition semanticdb maven . . snapshots/CompanionOwner#create(). // display_name create -// documentation ```kotlin\npublic final fun create(): Int\n``` +// signature_documentation kotlin public final fun create(): Int // ^^^ reference semanticdb maven . . kotlin/Int# // ^^^^^^ reference semanticdb maven . . snapshots/CompanionOwner#Companion#create(). // ^^^^^^^^ reference semanticdb maven . . kotlin/Any#hashCode(). diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Docstrings.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Docstrings.kt index cf219ac01..12facbd39 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Docstrings.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Docstrings.kt @@ -11,10 +11,10 @@ abstract class DocstringSuperclass // ^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/DocstringSuperclass# // display_name DocstringSuperclass -// documentation ```kotlin\npublic abstract class DocstringSuperclass : Any\n``` +// signature_documentation kotlin public abstract class DocstringSuperclass : Any // ^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/DocstringSuperclass#``(). // display_name DocstringSuperclass -// documentation ```kotlin\npublic constructor(): DocstringSuperclass\n``` +// signature_documentation kotlin public constructor(): DocstringSuperclass // ⌃ enclosing_range_end semanticdb maven . . snapshots/DocstringSuperclass# // ⌃ enclosing_range_end semanticdb maven . . snapshots/DocstringSuperclass#``(). //⌄ enclosing_range_start semanticdb maven . . snapshots/Docstrings# @@ -23,12 +23,14 @@ class Docstrings : DocstringSuperclass(), Serializable { // ^^^^^^^^^^ definition semanticdb maven . . snapshots/Docstrings# // display_name Docstrings -// documentation ```kotlin\npublic final class Docstrings : DocstringSuperclass, Serializable\n```\n\n----\n\n Example class docstring. -// relationship is_reference is_implementation semanticdb maven . . snapshots/DocstringSuperclass# -// relationship is_reference is_implementation semanticdb maven jdk 11 java/io/Serializable# +// signature_documentation kotlin public final class Docstrings : DocstringSuperclass, Serializable +// documentation Example class docstring. +// relationship is_implementation semanticdb maven . . snapshots/DocstringSuperclass# +// relationship is_implementation semanticdb maven jdk 11 java/io/Serializable# // ^^^^^^^^^^ definition semanticdb maven . . snapshots/Docstrings#``(). // display_name Docstrings -// documentation ```kotlin\npublic constructor(): Docstrings\n```\n\n----\n\n Example class docstring. +// signature_documentation kotlin public constructor(): Docstrings +// documentation Example class docstring. // ^^^^^^^^^^^^^^^^^^^ reference semanticdb maven . . snapshots/DocstringSuperclass# // ^^^^^^^^^^^^ reference semanticdb maven jdk 11 java/io/Serializable# } @@ -40,5 +42,6 @@ fun docstrings() { } // ^^^^^^^^^^ definition semanticdb maven . . snapshots/docstrings(). // display_name docstrings -// documentation ```kotlin\npublic final fun docstrings(): Unit\n```\n\n----\n\n Example method docstring. +// signature_documentation kotlin public final fun docstrings(): Unit +// documentation Example method docstring. // ⌃ enclosing_range_end semanticdb maven . . snapshots/docstrings(). diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Functions.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Functions.kt index 30f646798..85e5fb679 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Functions.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Functions.kt @@ -6,10 +6,10 @@ fun sampleText(x: String = "") { // ^^^^^^^^^^ definition semanticdb maven . . snapshots/sampleText(). // display_name sampleText -// documentation ```kotlin\npublic final fun sampleText(x: String = ...): Unit\n``` +// signature_documentation kotlin public final fun sampleText(x: String = ...): Unit // ^ definition semanticdb maven . . snapshots/sampleText().(x) // display_name x -// documentation ```kotlin\nx: String = ...\n``` +// signature_documentation kotlin x: String = ... // ^^^^^^ reference semanticdb maven . . kotlin/String# // ⌃ enclosing_range_end semanticdb maven . . snapshots/sampleText().(x) println(x) diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Implementations.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Implementations.kt index ebd286d31..0a3abb81c 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Implementations.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Implementations.kt @@ -6,18 +6,18 @@ class Overrides : AutoCloseable { // ^^^^^^^^^ definition semanticdb maven . . snapshots/Overrides# // display_name Overrides -// documentation ```kotlin\npublic final class Overrides : {kotlin/AutoCloseable=} AutoCloseable\n``` -// relationship is_reference is_implementation semanticdb maven jdk 11 java/lang/AutoCloseable# +// signature_documentation kotlin public final class Overrides : {kotlin/AutoCloseable=} AutoCloseable +// relationship is_implementation semanticdb maven jdk 11 java/lang/AutoCloseable# // ^^^^^^^^^ definition semanticdb maven . . snapshots/Overrides#``(). // display_name Overrides -// documentation ```kotlin\npublic constructor(): Overrides\n``` +// signature_documentation kotlin public constructor(): Overrides // ^^^^^^^^^^^^^ reference semanticdb maven jdk 11 java/lang/AutoCloseable# // ⌄ enclosing_range_start semanticdb maven . . snapshots/Overrides#close(). override fun close() { // ^^^^^ definition semanticdb maven . . snapshots/Overrides#close(). // display_name close -// documentation ```kotlin\npublic open override fun close(): Unit\n``` -// relationship is_reference is_implementation semanticdb maven jdk 11 java/lang/AutoCloseable#close(). +// signature_documentation kotlin public open override fun close(): Unit +// relationship is_implementation semanticdb maven jdk 11 java/lang/AutoCloseable#close(). TODO("Not yet implemented") // ^^^^ reference semanticdb maven . . kotlin/TODO(+1). } @@ -30,16 +30,16 @@ interface Animal { // ^^^^^^ definition semanticdb maven . . snapshots/Animal# // display_name Animal -// documentation ```kotlin\npublic abstract interface Animal : Any\n``` +// signature_documentation kotlin public abstract interface Animal : Any // ⌄ enclosing_range_start semanticdb maven . . snapshots/Animal#favoriteNumber. // ⌄ enclosing_range_start semanticdb maven . . snapshots/Animal#getFavoriteNumber(). val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/Animal#favoriteNumber. // display_name favoriteNumber -// documentation ```kotlin\npublic abstract val favoriteNumber: Int\n``` +// signature_documentation kotlin public abstract val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/Animal#getFavoriteNumber(). // display_name favoriteNumber -// documentation ```kotlin\npublic get(): Int\n``` +// signature_documentation kotlin public get(): Int // ^^^ reference semanticdb maven . . kotlin/Int# // ⌃ enclosing_range_end semanticdb maven . . snapshots/Animal#favoriteNumber. // ⌃ enclosing_range_end semanticdb maven . . snapshots/Animal#getFavoriteNumber(). @@ -47,7 +47,7 @@ fun sound(): String // ^^^^^ definition semanticdb maven . . snapshots/Animal#sound(). // display_name sound -// documentation ```kotlin\npublic abstract fun sound(): String\n\n``` +// signature_documentation kotlin public abstract fun sound(): String\n // ^^^^^^ reference semanticdb maven . . kotlin/String# // ⌃ enclosing_range_end semanticdb maven . . snapshots/Animal#sound(). } @@ -57,23 +57,23 @@ open class Bird : Animal { // ^^^^ definition semanticdb maven . . snapshots/Bird# // display_name Bird -// documentation ```kotlin\npublic open class Bird : Animal\n``` -// relationship is_reference is_implementation semanticdb maven . . snapshots/Animal# +// signature_documentation kotlin public open class Bird : Animal +// relationship is_implementation semanticdb maven . . snapshots/Animal# // ^^^^ definition semanticdb maven . . snapshots/Bird#``(). // display_name Bird -// documentation ```kotlin\npublic constructor(): Bird\n``` +// signature_documentation kotlin public constructor(): Bird // ^^^^^^ reference semanticdb maven . . snapshots/Animal# // ⌄ enclosing_range_start semanticdb maven . . snapshots/Bird#favoriteNumber. override val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/Bird#favoriteNumber. // display_name favoriteNumber -// documentation ```kotlin\npublic open override val favoriteNumber: Int\n``` +// signature_documentation kotlin public open override val favoriteNumber: Int // ^^^ reference semanticdb maven . . kotlin/Int# // ⌄ enclosing_range_start semanticdb maven . . snapshots/Bird#getFavoriteNumber(). get() = 42 // ^^^ definition semanticdb maven . . snapshots/Bird#getFavoriteNumber(). // display_name favoriteNumber -// documentation ```kotlin\npublic get(): Int\n``` +// signature_documentation kotlin public get(): Int // ⌃ enclosing_range_end semanticdb maven . . snapshots/Bird#favoriteNumber. // ⌃ enclosing_range_end semanticdb maven . . snapshots/Bird#getFavoriteNumber(). @@ -81,8 +81,8 @@ override fun sound(): String { // ^^^^^ definition semanticdb maven . . snapshots/Bird#sound(). // display_name sound -// documentation ```kotlin\npublic open override fun sound(): String\n``` -// relationship is_reference is_implementation semanticdb maven . . snapshots/Animal#sound(). +// signature_documentation kotlin public open override fun sound(): String +// relationship is_implementation semanticdb maven . . snapshots/Animal#sound(). // ^^^^^^ reference semanticdb maven . . kotlin/String# return "tweet" } @@ -95,31 +95,31 @@ class Seagull : Bird() { // ^^^^^^^ definition semanticdb maven . . snapshots/Seagull# // display_name Seagull -// documentation ```kotlin\npublic final class Seagull : Bird\n``` -// relationship is_reference is_implementation semanticdb maven . . snapshots/Bird# +// signature_documentation kotlin public final class Seagull : Bird +// relationship is_implementation semanticdb maven . . snapshots/Bird# // ^^^^^^^ definition semanticdb maven . . snapshots/Seagull#``(). // display_name Seagull -// documentation ```kotlin\npublic constructor(): Seagull\n``` +// signature_documentation kotlin public constructor(): Seagull // ^^^^ reference semanticdb maven . . snapshots/Bird# // ⌄ enclosing_range_start semanticdb maven . . snapshots/Seagull#favoriteNumber. override val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition semanticdb maven . . snapshots/Seagull#favoriteNumber. // display_name favoriteNumber -// documentation ```kotlin\npublic open override val favoriteNumber: Int\n``` +// signature_documentation kotlin public open override val favoriteNumber: Int // ^^^ reference semanticdb maven . . kotlin/Int# // ⌄ enclosing_range_start semanticdb maven . . snapshots/Seagull#getFavoriteNumber(). get() = 1337 // ^^^ definition semanticdb maven . . snapshots/Seagull#getFavoriteNumber(). // display_name favoriteNumber -// documentation ```kotlin\npublic get(): Int\n``` +// signature_documentation kotlin public get(): Int // ⌃ enclosing_range_end semanticdb maven . . snapshots/Seagull#favoriteNumber. // ⌃ enclosing_range_end semanticdb maven . . snapshots/Seagull#getFavoriteNumber(). // ⌄ enclosing_range_start semanticdb maven . . snapshots/Seagull#sound(). override fun sound(): String { // ^^^^^ definition semanticdb maven . . snapshots/Seagull#sound(). // display_name sound -// documentation ```kotlin\npublic open override fun sound(): String\n``` -// relationship is_reference is_implementation semanticdb maven . . snapshots/Bird#sound(). +// signature_documentation kotlin public open override fun sound(): String +// relationship is_implementation semanticdb maven . . snapshots/Bird#sound(). // ^^^^^^ reference semanticdb maven . . kotlin/String# return "squawk" } diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Lambdas.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Lambdas.kt index d531349ba..7d477b829 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Lambdas.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/Lambdas.kt @@ -8,18 +8,18 @@ val x = arrayListOf().forEachIndexed { i, s -> println("$i $s") } // ^ definition semanticdb maven . . snapshots/getX(). // display_name x -// documentation ```kotlin\npublic get(): Unit\n``` +// signature_documentation kotlin public get(): Unit // ^ definition semanticdb maven . . snapshots/x. // display_name x -// documentation ```kotlin\npublic final val x: Unit\n``` +// signature_documentation kotlin public final val x: Unit // ^^^^^^^^^^^ reference semanticdb maven . . kotlin/collections/arrayListOf(). // ^^^^^^^^^^^^^^ reference semanticdb maven . . kotlin/collections/forEachIndexed(+9). // ^ definition local 0 // display_name i -// documentation ```kotlin\ni: Int\n``` +// signature_documentation kotlin i: Int // ^ definition local 1 // display_name s -// documentation ```kotlin\ns: String\n``` +// signature_documentation kotlin s: String // ^^^^^^^ reference semanticdb maven . . kotlin/io/println(). // ^ reference local 0 // ^ reference local 1 @@ -33,10 +33,10 @@ val y = "fdsa".run { this.toByteArray() } // ^ definition semanticdb maven . . snapshots/getY(). // display_name y -// documentation ```kotlin\npublic get(): ByteArray\n``` +// signature_documentation kotlin public get(): ByteArray // ^ definition semanticdb maven . . snapshots/y. // display_name y -// documentation ```kotlin\npublic final val y: ByteArray\n``` +// signature_documentation kotlin public final val y: ByteArray // ^^^ reference semanticdb maven . . kotlin/run(+1). // ^^^^^^^^^^^ reference semanticdb maven . . kotlin/text/toByteArray(). // ⌃ enclosing_range_end semanticdb maven . . snapshots/y. @@ -48,16 +48,16 @@ val z = y.let { it.size } // ^ definition semanticdb maven . . snapshots/getZ(). // display_name z -// documentation ```kotlin\npublic get(): Int\n``` +// signature_documentation kotlin public get(): Int // ^ definition semanticdb maven . . snapshots/z. // display_name z -// documentation ```kotlin\npublic final val z: Int\n``` +// signature_documentation kotlin public final val z: Int // ^ reference semanticdb maven . . snapshots/getY(). // ^ reference semanticdb maven . . snapshots/y. // ^^^ reference semanticdb maven . . kotlin/let(). // ^^^^^^^^^^^ definition local 2 // display_name it -// documentation ```kotlin\nit: ByteArray\n``` +// signature_documentation kotlin it: ByteArray // ^^ reference local 2 // ^^^^ reference semanticdb maven . . kotlin/ByteArray#getSize(). // ^^^^ reference semanticdb maven . . kotlin/ByteArray#size. diff --git a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/ObjectKt.kt b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/ObjectKt.kt index 5a4b21f3c..558123d23 100644 --- a/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/ObjectKt.kt +++ b/semanticdb-kotlinc/minimized/src/generatedSnapshots/resources/semanticdb-kotlinc/minimized/src/main/kotlin/snapshots/ObjectKt.kt @@ -11,19 +11,19 @@ object ObjectKt { // ^^^^^^^^ definition semanticdb maven . . snapshots/ObjectKt# // display_name ObjectKt -// documentation ```kotlin\npublic final object ObjectKt : Any\n``` +// signature_documentation kotlin public final object ObjectKt : Any // ^^^^^^^^ definition semanticdb maven . . snapshots/ObjectKt#``(). // display_name ObjectKt -// documentation ```kotlin\nprivate constructor(): ObjectKt\n``` +// signature_documentation kotlin private constructor(): ObjectKt // ⌄ enclosing_range_start semanticdb maven . . snapshots/ObjectKt#fail(). // ⌄ enclosing_range_start semanticdb maven . . snapshots/ObjectKt#fail().(message) fun fail(message: String?): Nothing { // ^^^^ definition semanticdb maven . . snapshots/ObjectKt#fail(). // display_name fail -// documentation ```kotlin\npublic final fun fail(message: String?): Nothing\n``` +// signature_documentation kotlin public final fun fail(message: String?): Nothing // ^^^^^^^ definition semanticdb maven . . snapshots/ObjectKt#fail().(message) // display_name message -// documentation ```kotlin\nmessage: String?\n``` +// signature_documentation kotlin message: String? // ^^^^^^^ reference semanticdb maven . . kotlin/String# // ^^^^^^^ reference semanticdb maven . . kotlin/Nothing# // ⌃ enclosing_range_end semanticdb maven . . snapshots/ObjectKt#fail().(message) diff --git a/semanticdb-kotlinc/src/main/java/com/sourcegraph/semanticdb_kotlinc/SemanticdbBuilders.kt b/semanticdb-kotlinc/src/main/java/com/sourcegraph/semanticdb_kotlinc/SemanticdbBuilders.kt deleted file mode 100644 index d1e85c809..000000000 --- a/semanticdb-kotlinc/src/main/java/com/sourcegraph/semanticdb_kotlinc/SemanticdbBuilders.kt +++ /dev/null @@ -1,409 +0,0 @@ -// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -@file:JvmName("-SemanticdbBuilders") - -package com.sourcegraph.semanticdb_kotlinc - -import com.sourcegraph.semanticdb.Semanticdb - -import kotlin.DslMarker -import kotlin.Unit -import kotlin.annotation.AnnotationRetention -import kotlin.annotation.AnnotationTarget -import kotlin.annotation.Retention -import kotlin.annotation.Target -import kotlin.jvm.JvmName - -inline fun Semanticdb.TextDocuments.copy(block: Semanticdb.TextDocuments.Builder.() -> Unit): - Semanticdb.TextDocuments = this.toBuilder().apply(block).build() - -operator fun Semanticdb.TextDocuments.plus(other: Semanticdb.TextDocuments): - Semanticdb.TextDocuments = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.TextDocuments?.orDefault(): Semanticdb.TextDocuments = this ?: - Semanticdb.TextDocuments.getDefaultInstance() - -inline fun Semanticdb.TextDocuments.Builder.addDocuments(block: - Semanticdb.TextDocument.Builder.() -> Unit): Semanticdb.TextDocuments.Builder = - this.addDocuments(Semanticdb.TextDocument.newBuilder().apply(block).build()) - -inline fun Semanticdb.TextDocument.copy(block: Semanticdb.TextDocument.Builder.() -> Unit): - Semanticdb.TextDocument = this.toBuilder().apply(block).build() - -operator fun Semanticdb.TextDocument.plus(other: Semanticdb.TextDocument): Semanticdb.TextDocument = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.TextDocument?.orDefault(): Semanticdb.TextDocument = this ?: - Semanticdb.TextDocument.getDefaultInstance() - -inline fun Semanticdb.TextDocument.Builder.addSymbols(block: - Semanticdb.SymbolInformation.Builder.() -> Unit): Semanticdb.TextDocument.Builder = - this.addSymbols(Semanticdb.SymbolInformation.newBuilder().apply(block).build()) - -inline fun Semanticdb.TextDocument.Builder.addOccurrences(block: - Semanticdb.SymbolOccurrence.Builder.() -> Unit): Semanticdb.TextDocument.Builder = - this.addOccurrences(Semanticdb.SymbolOccurrence.newBuilder().apply(block).build()) - -inline fun Semanticdb.Range.copy(block: Semanticdb.Range.Builder.() -> Unit): Semanticdb.Range = - this.toBuilder().apply(block).build() - -operator fun Semanticdb.Range.plus(other: Semanticdb.Range): Semanticdb.Range = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.Range?.orDefault(): Semanticdb.Range = this ?: Semanticdb.Range.getDefaultInstance() - -inline fun Semanticdb.Signature.copy(block: Semanticdb.Signature.Builder.() -> Unit): - Semanticdb.Signature = this.toBuilder().apply(block).build() - -operator fun Semanticdb.Signature.plus(other: Semanticdb.Signature): Semanticdb.Signature = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.Signature?.orDefault(): Semanticdb.Signature = this ?: - Semanticdb.Signature.getDefaultInstance() - -inline fun Semanticdb.Signature.Builder.classSignature(block: - Semanticdb.ClassSignature.Builder.() -> Unit): Semanticdb.Signature.Builder = - this.setClassSignature(Semanticdb.ClassSignature.newBuilder().apply(block).build()) - -inline fun Semanticdb.Signature.Builder.methodSignature(block: - Semanticdb.MethodSignature.Builder.() -> Unit): Semanticdb.Signature.Builder = - this.setMethodSignature(Semanticdb.MethodSignature.newBuilder().apply(block).build()) - -inline fun Semanticdb.Signature.Builder.typeSignature(block: Semanticdb.TypeSignature.Builder.() -> - Unit): Semanticdb.Signature.Builder = - this.setTypeSignature(Semanticdb.TypeSignature.newBuilder().apply(block).build()) - -inline fun Semanticdb.Signature.Builder.valueSignature(block: - Semanticdb.ValueSignature.Builder.() -> Unit): Semanticdb.Signature.Builder = - this.setValueSignature(Semanticdb.ValueSignature.newBuilder().apply(block).build()) - -inline fun Semanticdb.ClassSignature.copy(block: Semanticdb.ClassSignature.Builder.() -> Unit): - Semanticdb.ClassSignature = this.toBuilder().apply(block).build() - -operator fun Semanticdb.ClassSignature.plus(other: Semanticdb.ClassSignature): - Semanticdb.ClassSignature = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.ClassSignature?.orDefault(): Semanticdb.ClassSignature = this ?: - Semanticdb.ClassSignature.getDefaultInstance() - -inline fun Semanticdb.ClassSignature.Builder.typeParameters(block: Semanticdb.Scope.Builder.() -> - Unit): Semanticdb.ClassSignature.Builder = - this.setTypeParameters(Semanticdb.Scope.newBuilder().apply(block).build()) - -inline fun Semanticdb.ClassSignature.Builder.addParents(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.ClassSignature.Builder = - this.addParents(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.ClassSignature.Builder.declarations(block: Semanticdb.Scope.Builder.() -> - Unit): Semanticdb.ClassSignature.Builder = - this.setDeclarations(Semanticdb.Scope.newBuilder().apply(block).build()) - -inline fun Semanticdb.MethodSignature.copy(block: Semanticdb.MethodSignature.Builder.() -> Unit): - Semanticdb.MethodSignature = this.toBuilder().apply(block).build() - -operator fun Semanticdb.MethodSignature.plus(other: Semanticdb.MethodSignature): - Semanticdb.MethodSignature = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.MethodSignature?.orDefault(): Semanticdb.MethodSignature = this ?: - Semanticdb.MethodSignature.getDefaultInstance() - -inline fun Semanticdb.MethodSignature.Builder.typeParameters(block: Semanticdb.Scope.Builder.() -> - Unit): Semanticdb.MethodSignature.Builder = - this.setTypeParameters(Semanticdb.Scope.newBuilder().apply(block).build()) - -inline fun Semanticdb.MethodSignature.Builder.addParameterLists(block: - Semanticdb.Scope.Builder.() -> Unit): Semanticdb.MethodSignature.Builder = - this.addParameterLists(Semanticdb.Scope.newBuilder().apply(block).build()) - -inline fun Semanticdb.MethodSignature.Builder.returnType(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.MethodSignature.Builder = - this.setReturnType(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.TypeSignature.copy(block: Semanticdb.TypeSignature.Builder.() -> Unit): - Semanticdb.TypeSignature = this.toBuilder().apply(block).build() - -operator fun Semanticdb.TypeSignature.plus(other: Semanticdb.TypeSignature): - Semanticdb.TypeSignature = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.TypeSignature?.orDefault(): Semanticdb.TypeSignature = this ?: - Semanticdb.TypeSignature.getDefaultInstance() - -inline fun Semanticdb.TypeSignature.Builder.typeParameters(block: Semanticdb.Scope.Builder.() -> - Unit): Semanticdb.TypeSignature.Builder = - this.setTypeParameters(Semanticdb.Scope.newBuilder().apply(block).build()) - -inline fun Semanticdb.TypeSignature.Builder.lowerBound(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.TypeSignature.Builder = - this.setLowerBound(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.TypeSignature.Builder.upperBound(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.TypeSignature.Builder = - this.setUpperBound(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.ValueSignature.copy(block: Semanticdb.ValueSignature.Builder.() -> Unit): - Semanticdb.ValueSignature = this.toBuilder().apply(block).build() - -operator fun Semanticdb.ValueSignature.plus(other: Semanticdb.ValueSignature): - Semanticdb.ValueSignature = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.ValueSignature?.orDefault(): Semanticdb.ValueSignature = this ?: - Semanticdb.ValueSignature.getDefaultInstance() - -inline fun Semanticdb.ValueSignature.Builder.tpe(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.ValueSignature.Builder = - this.setTpe(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.SymbolInformation.copy(block: Semanticdb.SymbolInformation.Builder.() -> - Unit): Semanticdb.SymbolInformation = this.toBuilder().apply(block).build() - -operator fun Semanticdb.SymbolInformation.plus(other: Semanticdb.SymbolInformation): - Semanticdb.SymbolInformation = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.SymbolInformation?.orDefault(): Semanticdb.SymbolInformation = this ?: - Semanticdb.SymbolInformation.getDefaultInstance() - -inline fun Semanticdb.SymbolInformation.Builder.signature(block: Semanticdb.Signature.Builder.() -> - Unit): Semanticdb.SymbolInformation.Builder = - this.setSignature(Semanticdb.Signature.newBuilder().apply(block).build()) - -inline fun Semanticdb.SymbolInformation.Builder.access(block: Semanticdb.Access.Builder.() -> Unit): - Semanticdb.SymbolInformation.Builder = - this.setAccess(Semanticdb.Access.newBuilder().apply(block).build()) - -inline fun Semanticdb.SymbolInformation.Builder.documentation(block: - Semanticdb.Documentation.Builder.() -> Unit): Semanticdb.SymbolInformation.Builder = - this.setDocumentation(Semanticdb.Documentation.newBuilder().apply(block).build()) - -inline fun Semanticdb.Access.copy(block: Semanticdb.Access.Builder.() -> Unit): Semanticdb.Access = - this.toBuilder().apply(block).build() - -operator fun Semanticdb.Access.plus(other: Semanticdb.Access): Semanticdb.Access = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.Access?.orDefault(): Semanticdb.Access = this ?: - Semanticdb.Access.getDefaultInstance() - -inline fun Semanticdb.Access.Builder.privateAccess(block: Semanticdb.PrivateAccess.Builder.() -> - Unit): Semanticdb.Access.Builder = - this.setPrivateAccess(Semanticdb.PrivateAccess.newBuilder().apply(block).build()) - -inline fun Semanticdb.Access.Builder.privateWithinAccess(block: - Semanticdb.PrivateWithinAccess.Builder.() -> Unit): Semanticdb.Access.Builder = - this.setPrivateWithinAccess(Semanticdb.PrivateWithinAccess.newBuilder().apply(block).build()) - -inline fun Semanticdb.Access.Builder.protectedAccess(block: Semanticdb.ProtectedAccess.Builder.() -> - Unit): Semanticdb.Access.Builder = - this.setProtectedAccess(Semanticdb.ProtectedAccess.newBuilder().apply(block).build()) - -inline fun Semanticdb.Access.Builder.publicAccess(block: Semanticdb.PublicAccess.Builder.() -> - Unit): Semanticdb.Access.Builder = - this.setPublicAccess(Semanticdb.PublicAccess.newBuilder().apply(block).build()) - -inline fun Semanticdb.PrivateAccess.copy(block: Semanticdb.PrivateAccess.Builder.() -> Unit): - Semanticdb.PrivateAccess = this.toBuilder().apply(block).build() - -operator fun Semanticdb.PrivateAccess.plus(other: Semanticdb.PrivateAccess): - Semanticdb.PrivateAccess = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.PrivateAccess?.orDefault(): Semanticdb.PrivateAccess = this ?: - Semanticdb.PrivateAccess.getDefaultInstance() - -inline fun Semanticdb.PrivateWithinAccess.copy(block: Semanticdb.PrivateWithinAccess.Builder.() -> - Unit): Semanticdb.PrivateWithinAccess = this.toBuilder().apply(block).build() - -operator fun Semanticdb.PrivateWithinAccess.plus(other: Semanticdb.PrivateWithinAccess): - Semanticdb.PrivateWithinAccess = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.PrivateWithinAccess?.orDefault(): Semanticdb.PrivateWithinAccess = this ?: - Semanticdb.PrivateWithinAccess.getDefaultInstance() - -inline fun Semanticdb.ProtectedAccess.copy(block: Semanticdb.ProtectedAccess.Builder.() -> Unit): - Semanticdb.ProtectedAccess = this.toBuilder().apply(block).build() - -operator fun Semanticdb.ProtectedAccess.plus(other: Semanticdb.ProtectedAccess): - Semanticdb.ProtectedAccess = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.ProtectedAccess?.orDefault(): Semanticdb.ProtectedAccess = this ?: - Semanticdb.ProtectedAccess.getDefaultInstance() - -inline fun Semanticdb.PublicAccess.copy(block: Semanticdb.PublicAccess.Builder.() -> Unit): - Semanticdb.PublicAccess = this.toBuilder().apply(block).build() - -operator fun Semanticdb.PublicAccess.plus(other: Semanticdb.PublicAccess): Semanticdb.PublicAccess = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.PublicAccess?.orDefault(): Semanticdb.PublicAccess = this ?: - Semanticdb.PublicAccess.getDefaultInstance() - -inline fun Semanticdb.Documentation.copy(block: Semanticdb.Documentation.Builder.() -> Unit): - Semanticdb.Documentation = this.toBuilder().apply(block).build() - -operator fun Semanticdb.Documentation.plus(other: Semanticdb.Documentation): - Semanticdb.Documentation = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.Documentation?.orDefault(): Semanticdb.Documentation = this ?: - Semanticdb.Documentation.getDefaultInstance() - -inline fun Semanticdb.SymbolOccurrence.copy(block: Semanticdb.SymbolOccurrence.Builder.() -> Unit): - Semanticdb.SymbolOccurrence = this.toBuilder().apply(block).build() - -operator fun Semanticdb.SymbolOccurrence.plus(other: Semanticdb.SymbolOccurrence): - Semanticdb.SymbolOccurrence = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.SymbolOccurrence?.orDefault(): Semanticdb.SymbolOccurrence = this ?: - Semanticdb.SymbolOccurrence.getDefaultInstance() - -inline fun Semanticdb.SymbolOccurrence.Builder.range(block: Semanticdb.Range.Builder.() -> Unit): - Semanticdb.SymbolOccurrence.Builder = - this.setRange(Semanticdb.Range.newBuilder().apply(block).build()) - -inline fun Semanticdb.SymbolOccurrence.Builder.enclosingRange(block: Semanticdb.Range.Builder.() -> Unit): - Semanticdb.SymbolOccurrence.Builder = - this.setEnclosingRange(Semanticdb.Range.newBuilder().apply(block).build()) - -inline fun Semanticdb.Scope.copy(block: Semanticdb.Scope.Builder.() -> Unit): Semanticdb.Scope = - this.toBuilder().apply(block).build() - -operator fun Semanticdb.Scope.plus(other: Semanticdb.Scope): Semanticdb.Scope = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.Scope?.orDefault(): Semanticdb.Scope = this ?: Semanticdb.Scope.getDefaultInstance() - -inline fun Semanticdb.Scope.Builder.addHardlinks(block: Semanticdb.SymbolInformation.Builder.() -> - Unit): Semanticdb.Scope.Builder = - this.addHardlinks(Semanticdb.SymbolInformation.newBuilder().apply(block).build()) - -inline fun Semanticdb.Type.copy(block: Semanticdb.Type.Builder.() -> Unit): Semanticdb.Type = - this.toBuilder().apply(block).build() - -operator fun Semanticdb.Type.plus(other: Semanticdb.Type): Semanticdb.Type = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.Type?.orDefault(): Semanticdb.Type = this ?: Semanticdb.Type.getDefaultInstance() - -inline fun Semanticdb.Type.Builder.typeRef(block: Semanticdb.TypeRef.Builder.() -> Unit): - Semanticdb.Type.Builder = - this.setTypeRef(Semanticdb.TypeRef.newBuilder().apply(block).build()) - -inline fun Semanticdb.Type.Builder.existentialType(block: Semanticdb.ExistentialType.Builder.() -> - Unit): Semanticdb.Type.Builder = - this.setExistentialType(Semanticdb.ExistentialType.newBuilder().apply(block).build()) - -inline fun Semanticdb.Type.Builder.intersectionType(block: Semanticdb.IntersectionType.Builder.() -> - Unit): Semanticdb.Type.Builder = - this.setIntersectionType(Semanticdb.IntersectionType.newBuilder().apply(block).build()) - -inline fun Semanticdb.TypeRef.copy(block: Semanticdb.TypeRef.Builder.() -> Unit): Semanticdb.TypeRef - = this.toBuilder().apply(block).build() - -operator fun Semanticdb.TypeRef.plus(other: Semanticdb.TypeRef): Semanticdb.TypeRef = - this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.TypeRef?.orDefault(): Semanticdb.TypeRef = this ?: - Semanticdb.TypeRef.getDefaultInstance() - -inline fun Semanticdb.TypeRef.Builder.addTypeArguments(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.TypeRef.Builder = - this.addTypeArguments(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.IntersectionType.copy(block: Semanticdb.IntersectionType.Builder.() -> Unit): - Semanticdb.IntersectionType = this.toBuilder().apply(block).build() - -operator fun Semanticdb.IntersectionType.plus(other: Semanticdb.IntersectionType): - Semanticdb.IntersectionType = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.IntersectionType?.orDefault(): Semanticdb.IntersectionType = this ?: - Semanticdb.IntersectionType.getDefaultInstance() - -inline fun Semanticdb.IntersectionType.Builder.addTypes(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.IntersectionType.Builder = - this.addTypes(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.ExistentialType.copy(block: Semanticdb.ExistentialType.Builder.() -> Unit): - Semanticdb.ExistentialType = this.toBuilder().apply(block).build() - -operator fun Semanticdb.ExistentialType.plus(other: Semanticdb.ExistentialType): - Semanticdb.ExistentialType = this.toBuilder().mergeFrom(other).build() - -fun Semanticdb.ExistentialType?.orDefault(): Semanticdb.ExistentialType = this ?: - Semanticdb.ExistentialType.getDefaultInstance() - -inline fun Semanticdb.ExistentialType.Builder.tpe(block: Semanticdb.Type.Builder.() -> Unit): - Semanticdb.ExistentialType.Builder = - this.setTpe(Semanticdb.Type.newBuilder().apply(block).build()) - -inline fun Semanticdb.ExistentialType.Builder.declarations(block: Semanticdb.Scope.Builder.() -> - Unit): Semanticdb.ExistentialType.Builder = - this.setDeclarations(Semanticdb.Scope.newBuilder().apply(block).build()) - -inline fun TextDocuments(block: Semanticdb.TextDocuments.Builder.() -> Unit): - Semanticdb.TextDocuments = Semanticdb.TextDocuments.newBuilder().apply(block).build() - -inline fun TextDocument(block: Semanticdb.TextDocument.Builder.() -> Unit): Semanticdb.TextDocument - = Semanticdb.TextDocument.newBuilder().apply(block).build() - -inline fun Range(block: Semanticdb.Range.Builder.() -> Unit): Semanticdb.Range = - Semanticdb.Range.newBuilder().apply(block).build() - -inline fun Signature(block: Semanticdb.Signature.Builder.() -> Unit): Semanticdb.Signature = - Semanticdb.Signature.newBuilder().apply(block).build() - -inline fun ClassSignature(block: Semanticdb.ClassSignature.Builder.() -> Unit): - Semanticdb.ClassSignature = Semanticdb.ClassSignature.newBuilder().apply(block).build() - -inline fun MethodSignature(block: Semanticdb.MethodSignature.Builder.() -> Unit): - Semanticdb.MethodSignature = Semanticdb.MethodSignature.newBuilder().apply(block).build() - -inline fun TypeSignature(block: Semanticdb.TypeSignature.Builder.() -> Unit): - Semanticdb.TypeSignature = Semanticdb.TypeSignature.newBuilder().apply(block).build() - -inline fun ValueSignature(block: Semanticdb.ValueSignature.Builder.() -> Unit): - Semanticdb.ValueSignature = Semanticdb.ValueSignature.newBuilder().apply(block).build() - -inline fun SymbolInformation(block: Semanticdb.SymbolInformation.Builder.() -> Unit): - Semanticdb.SymbolInformation = - Semanticdb.SymbolInformation.newBuilder().apply(block).build() - -inline fun Access(block: Semanticdb.Access.Builder.() -> Unit): Semanticdb.Access = - Semanticdb.Access.newBuilder().apply(block).build() - -inline fun PrivateAccess(block: Semanticdb.PrivateAccess.Builder.() -> Unit): - Semanticdb.PrivateAccess = Semanticdb.PrivateAccess.newBuilder().apply(block).build() - -inline fun PrivateWithinAccess(block: Semanticdb.PrivateWithinAccess.Builder.() -> Unit): - Semanticdb.PrivateWithinAccess = - Semanticdb.PrivateWithinAccess.newBuilder().apply(block).build() - -inline fun ProtectedAccess(block: Semanticdb.ProtectedAccess.Builder.() -> Unit): - Semanticdb.ProtectedAccess = Semanticdb.ProtectedAccess.newBuilder().apply(block).build() - -inline fun PublicAccess(block: Semanticdb.PublicAccess.Builder.() -> Unit): Semanticdb.PublicAccess - = Semanticdb.PublicAccess.newBuilder().apply(block).build() - -inline fun Documentation(block: Semanticdb.Documentation.Builder.() -> Unit): - Semanticdb.Documentation = Semanticdb.Documentation.newBuilder().apply(block).build() - -inline fun SymbolOccurrence(block: Semanticdb.SymbolOccurrence.Builder.() -> Unit): - Semanticdb.SymbolOccurrence = Semanticdb.SymbolOccurrence.newBuilder().apply(block).build() - -inline fun Scope(block: Semanticdb.Scope.Builder.() -> Unit): Semanticdb.Scope = - Semanticdb.Scope.newBuilder().apply(block).build() - -inline fun Type(block: Semanticdb.Type.Builder.() -> Unit): Semanticdb.Type = - Semanticdb.Type.newBuilder().apply(block).build() - -inline fun TypeRef(block: Semanticdb.TypeRef.Builder.() -> Unit): Semanticdb.TypeRef = - Semanticdb.TypeRef.newBuilder().apply(block).build() - -inline fun IntersectionType(block: Semanticdb.IntersectionType.Builder.() -> Unit): - Semanticdb.IntersectionType = Semanticdb.IntersectionType.newBuilder().apply(block).build() - -inline fun ExistentialType(block: Semanticdb.ExistentialType.Builder.() -> Unit): - Semanticdb.ExistentialType = Semanticdb.ExistentialType.newBuilder().apply(block).build() - -@DslMarker -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.BINARY) -annotation class SemanticdbDslMarker - -@SemanticdbDslMarker -interface SemanticdbDslBuilder diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerRegistrar.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerRegistrar.kt index 9f33ca704..278ef870f 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerRegistrar.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerRegistrar.kt @@ -1,18 +1,17 @@ package com.sourcegraph.semanticdb_kotlinc -import com.sourcegraph.semanticdb.Semanticdb import com.sourcegraph.semanticdb.SemanticdbOptions - import kotlin.contracts.ExperimentalContracts import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter +import org.scip_code.scip.Document @OptIn(ExperimentalCompilerApi::class) @ExperimentalContracts -class AnalyzerRegistrar(private val callback: (Semanticdb.TextDocument) -> Unit = {}) : +class AnalyzerRegistrar(private val callback: (Document) -> Unit = {}) : CompilerPluginRegistrar() { override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { val options = diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/PostAnalysisExtension.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/PostAnalysisExtension.kt index fca98c91c..15dab03db 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/PostAnalysisExtension.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/PostAnalysisExtension.kt @@ -1,9 +1,7 @@ package com.sourcegraph.semanticdb_kotlinc -import com.sourcegraph.semanticdb.Semanticdb -import com.sourcegraph.semanticdb.SemanticdbPaths -import com.sourcegraph.semanticdb.SemanticdbWriter - +import com.sourcegraph.semanticdb.ScipShardPaths +import com.sourcegraph.semanticdb.ScipShardWriter import java.io.PrintWriter import java.io.Writer import java.nio.file.Path @@ -18,20 +16,28 @@ import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.scip_code.scip.Document +/** + * Writes per-source SCIP shards once the FIR checkers have finished and the IR phase begins. + * + *

For each source file [AnalyzerCheckers] registered a [SemanticdbVisitor] for, this builds the + * file's [Document] and serializes it under `/META-INF/scip/.scip`. + * Files outside the source root are skipped with a stderr warning. + */ +@ExperimentalContracts class PostAnalysisExtension( private val sourceRoot: Path, private val targetRoot: Path, - private val callback: (Semanticdb.TextDocument) -> Unit + private val callback: (Document) -> Unit ) : IrGenerationExtension { - @OptIn(ExperimentalContracts::class) override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { try { for ((ktSourceFile, visitor) in AnalyzerCheckers.visitors) { try { val document = visitor.build() - semanticdbOutPathForFile(ktSourceFile)?.let { outPath -> - SemanticdbWriter.writeTextDocument(outPath, document) + scipShardPathForFile(ktSourceFile)?.let { outPath -> + ScipShardWriter.writeShard(outPath, document) } callback(document) } catch (e: Exception) { @@ -43,9 +49,9 @@ class PostAnalysisExtension( } } - private fun semanticdbOutPathForFile(file: KtSourceFile): Path? { + private fun scipShardPathForFile(file: KtSourceFile): Path? { val normalizedPath = Paths.get(file.path).normalize() - val outPath = SemanticdbPaths.semanticdbPath(targetRoot, sourceRoot, normalizedPath) + val outPath = ScipShardPaths.shardPath(targetRoot, sourceRoot, normalizedPath) if (outPath.isPresent) { return outPath.get() } diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt index c9a348b62..601908233 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt @@ -1,17 +1,12 @@ package com.sourcegraph.semanticdb_kotlinc -import com.sourcegraph.semanticdb.Semanticdb - -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence.Role -import com.sourcegraph.semanticdb.SemanticdbDocumentBuilder -import com.sourcegraph.semanticdb.SemanticdbPaths +import com.sourcegraph.semanticdb.ScipDocumentBuilder +import com.sourcegraph.semanticdb.ScipShardPaths import java.nio.file.Path import java.nio.file.Paths -import java.security.MessageDigest import kotlin.contracts.ExperimentalContracts import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceFile -import org.jetbrains.kotlin.com.intellij.lang.java.JavaLanguage import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.directOverriddenSymbolsSafe @@ -22,11 +17,18 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.types.impl.FirImplicitAnyTypeRef -import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi import org.jetbrains.kotlin.text +import org.scip_code.scip.Document +import org.scip_code.scip.Occurrence +import org.scip_code.scip.SymbolInformation +import org.scip_code.scip.SymbolRole +import org.scip_code.scip.occurrence +import org.scip_code.scip.relationship +import org.scip_code.scip.signature +import org.scip_code.scip.symbolInformation +/** Builds a SCIP [Document] for a single Kotlin source file. */ @ExperimentalContracts class SemanticdbTextDocumentBuilder( private val sourceroot: Path, @@ -34,23 +36,22 @@ class SemanticdbTextDocumentBuilder( private val lineMap: LineMap, private val cache: SymbolsCache, ) { - private val documentBuilder = SemanticdbDocumentBuilder() + private val documentBuilder = ScipDocumentBuilder() private val fileText = file.getContentsAsStream().reader().readText() - private val semanticMd5 = semanticdbMD5() - fun build(): Semanticdb.TextDocument = - documentBuilder.build(Semanticdb.Language.KOTLIN, semanticdbURI(), fileText, semanticMd5) + fun build(): Document = + documentBuilder.build("kotlin", relativePath(), fileText) fun emitSemanticdbData( firBasedSymbol: FirBasedSymbol<*>?, symbol: Symbol, element: KtSourceElement, - role: Role, + isDefinition: Boolean, context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { - documentBuilder.addOccurrence(symbolOccurrence(symbol, element, role, enclosingSource)) - if (role == Role.DEFINITION) { + documentBuilder.addOccurrence(occurrence(symbol, element, isDefinition, enclosingSource)) + if (isDefinition) { documentBuilder.addSymbol(symbolInformation(firBasedSymbol, symbol, element, context)) } } @@ -61,143 +62,122 @@ class SemanticdbTextDocumentBuilder( symbol: Symbol, element: KtSourceElement, context: CheckerContext, - ): Semanticdb.SymbolInformation { + ): SymbolInformation { val supers = when (firBasedSymbol) { is FirClassSymbol -> firBasedSymbol .resolvedSuperTypeRefs .filter { it !is FirImplicitAnyTypeRef } - .map { it.toClassLikeSymbol(firBasedSymbol.moduleData.session) } - .filterNotNull() + .mapNotNull { it.toClassLikeSymbol(firBasedSymbol.moduleData.session) } .flatMap { cache[it] } is FirFunctionSymbol<*> -> firBasedSymbol.directOverriddenSymbolsSafe(context).flatMap { cache[it] } - else -> emptyList().asIterable() + else -> emptyList() } - return SymbolInformation { + return symbolInformation { this.symbol = symbol.toString() this.displayName = - if (firBasedSymbol != null) { - displayName(firBasedSymbol) - } else { - element.text.toString() - } - this.documentation = - if (firBasedSymbol != null) { - semanticdbDocumentation(firBasedSymbol.fir) - } else { - Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - message = "" + if (firBasedSymbol != null) displayName(firBasedSymbol) + else element.text.toString() + if (firBasedSymbol != null) { + renderSignature(firBasedSymbol.fir)?.let { rendered -> + signatureDocumentation = signature { + language = "kotlin" + text = rendered } } - this.addAllOverriddenSymbols(supers.map { it.toString() }) - this.language = - when (element.psi?.language ?: KotlinLanguage.INSTANCE) { - is KotlinLanguage -> Semanticdb.Language.KOTLIN - is JavaLanguage -> Semanticdb.Language.JAVA - else -> throw IllegalArgumentException("unexpected language") + docComment(firBasedSymbol.fir)?.let { documentation += it } + } + for (parent in supers) { + relationships += relationship { + this.symbol = parent.toString() + isImplementation = true } + } } } - private fun symbolOccurrence( + private fun occurrence( symbol: Symbol, element: KtSourceElement, - role: Role, - enclosingSource: KtSourceElement? = null, - ): Semanticdb.SymbolOccurrence { - return SymbolOccurrence { - this.symbol = symbol.toString() - this.role = role - this.range = semanticdbRange(element) - if (enclosingSource != null) { - this.enclosingRange = semanticdbEnclosingRange(enclosingSource) - } + isDefinition: Boolean, + enclosingSource: KtSourceElement?, + ): Occurrence = occurrence { + this.symbol = symbol.toString() + if (isDefinition) symbolRoles = SymbolRole.Definition.number + range += range(element).asIterable() + if (enclosingSource != null) { + enclosingRange += enclosingRange(enclosingSource).asIterable() } } - private fun semanticdbRange(element: KtSourceElement): Semanticdb.Range { - return Range { - startCharacter = lineMap.startCharacter(element) - startLine = lineMap.lineNumber(element) - 1 - endCharacter = lineMap.endCharacter(element) - endLine = lineMap.lineNumber(element) - 1 - } + private fun range(element: KtSourceElement): IntArray { + val line = lineMap.lineNumber(element) - 1 + val startCol = lineMap.startCharacter(element) + val endCol = lineMap.endCharacter(element) + return intArrayOf(line, startCol, endCol) } - private fun semanticdbEnclosingRange(element: KtSourceElement): Semanticdb.Range { - return Range { - startLine = lineMap.lineNumber(element) - 1 - startCharacter = lineMap.startCharacter(element) - endLine = lineMap.lineNumberForOffset(element.endOffset) - 1 - endCharacter = lineMap.columnForOffset(element.endOffset) - } + private fun enclosingRange(element: KtSourceElement): IntArray { + val startLine = lineMap.lineNumber(element) - 1 + val startCol = lineMap.startCharacter(element) + val endLine = lineMap.lineNumberForOffset(element.endOffset) - 1 + val endCol = lineMap.columnForOffset(element.endOffset) + return if (startLine == endLine) intArrayOf(startLine, startCol, endCol) + else intArrayOf(startLine, startCol, endLine, endCol) } - private fun semanticdbURI(): String = - SemanticdbPaths.semanticdbUri(sourceroot, Paths.get(file.path)) + private fun relativePath(): String = + ScipShardPaths.relativePath(sourceroot, Paths.get(file.path)) - private fun semanticdbMD5(): String = - MessageDigest.getInstance("MD5") - .digest(file.getContentsAsStream().readBytes()) - .joinToString("") { "%02X".format(it) } + /** + * Renders [element] as a Kotlin signature using [FirRenderer]'s readability preset, with kdoc + * stripped (kdoc is exposed separately via [SymbolInformation.documentation]). + */ + private fun renderSignature(element: FirElement): String? { + val renderer = + FirRenderer( + typeRenderer = ConeTypeRenderer(), + idRenderer = ConeIdShortRenderer(), + classMemberRenderer = FirNoClassMemberRenderer(), + bodyRenderer = null, + propertyAccessorRenderer = null, + callArgumentsRenderer = FirCallNoArgumentsRenderer(), + modifierRenderer = FirAllModifierRenderer(), + callableSignatureRenderer = FirCallableSignatureRendererForReadability(), + declarationRenderer = FirDeclarationRenderer("local ")) + val rendered = renderer.renderElementAsString(element) + return if (rendered.isEmpty()) null else rendered + } - private fun semanticdbDocumentation(element: FirElement): Semanticdb.Documentation = Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - // Like FirRenderer().forReadability, but using FirAllModifierRenderer instead of FirPartialModifierRenderer - val renderer = FirRenderer( - typeRenderer = ConeTypeRenderer(), - idRenderer = ConeIdShortRenderer(), - classMemberRenderer = FirNoClassMemberRenderer(), - bodyRenderer = null, - propertyAccessorRenderer = null, - callArgumentsRenderer = FirCallNoArgumentsRenderer(), - modifierRenderer = FirAllModifierRenderer(), - callableSignatureRenderer = FirCallableSignatureRendererForReadability(), - declarationRenderer = FirDeclarationRenderer("local "), - ) - val renderOutput = renderer.renderElementAsString(element) - val kdoc = element.source?.getChild(KtTokens.DOC_COMMENT)?.text?.toString() ?: "" - message = "```kotlin\n$renderOutput\n```${stripKDocAsterisks(kdoc)}" + private fun docComment(element: FirElement): String? { + val kdoc = element.source?.getChild(KtTokens.DOC_COMMENT)?.text?.toString() ?: return null + return stripKdoc(kdoc).ifEmpty { null } } - // Returns the kdoc string with all leading and trailing "/*" tokens removed. Naive - // implementation that can - // be replaced with a utility method from the compiler in the future, if one exists. - private fun stripKDocAsterisks(kdoc: String): String { + /** Strips the `/**`, leading `*`s, and `*/` from a kdoc block, returning just the body text. */ + private fun stripKdoc(kdoc: String): String { if (kdoc.isEmpty()) return kdoc - val out = StringBuilder().append("\n\n").append("----").append("\n") + val out = StringBuilder() + var first = true kdoc.lineSequence().forEach { line -> if (line.isEmpty()) return@forEach var start = 0 - while (start < line.length && line[start].isWhitespace()) { - start++ - } - if (start < line.length && line[start] == '/') { - start++ - } - while (start < line.length && line[start] == '*') { - start++ - } + while (start < line.length && line[start].isWhitespace()) start++ + if (start < line.length && line[start] == '/') start++ + while (start < line.length && line[start] == '*') start++ var end = line.length - 1 - if (end > start && line[end] == '/') { - end-- - } - while (end > start && line[end] == '*') { - end-- - } - while (end > start && line[end].isWhitespace()) { - end-- - } + if (end > start && line[end] == '/') end-- + while (end > start && line[end] == '*') end-- + while (end > start && line[end].isWhitespace()) end-- start = minOf(start, line.length - 1) - if (end > start) { - end++ - } - out.append("\n").append(line, start, end) + if (end > start) end++ + if (!first) out.append('\n') + out.append(line, start, end) + first = false } - return out.toString() + return out.toString().trim() } companion object { diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbVisitor.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbVisitor.kt index 268d2b480..0ba792ff3 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbVisitor.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbVisitor.kt @@ -1,8 +1,5 @@ package com.sourcegraph.semanticdb_kotlinc -import com.sourcegraph.semanticdb.Semanticdb - -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence.Role import java.nio.file.Path import kotlin.contracts.ExperimentalContracts import org.jetbrains.kotlin.KtSourceElement @@ -14,7 +11,13 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.name.FqName +import org.scip_code.scip.Document +/** + * Per-file accumulator of SCIP occurrences and symbols. The FIR checkers in + * [AnalyzerCheckers] call into this and the resulting [Document] is written as a + * `.scip` shard at the end of compilation. + */ @ExperimentalContracts class SemanticdbVisitor( sourceroot: Path, @@ -31,18 +34,16 @@ class SemanticdbVisitor( val symbol: Symbol ) - fun build(): Semanticdb.TextDocument { - return documentBuilder.build() - } + fun build(): Document = documentBuilder.build() private fun Sequence?.emitAll( element: KtSourceElement, - role: Role, + isDefinition: Boolean, context: CheckerContext, enclosingSource: KtSourceElement? = null, ): List? = this?.onEach { (firBasedSymbol, symbol) -> - documentBuilder.emitSemanticdbData(firBasedSymbol, symbol, element, role, context, enclosingSource) + documentBuilder.emitSemanticdbData(firBasedSymbol, symbol, element, isDefinition, context, enclosingSource) } ?.map { it.symbol } ?.toList() @@ -51,57 +52,55 @@ class SemanticdbVisitor( this.map { SymbolDescriptorPair(firBasedSymbol, it) } fun visitPackage(pkg: FqName, element: KtSourceElement, context: CheckerContext) { - cache[pkg].with(null).emitAll(element, Role.REFERENCE, context) + cache[pkg].with(null).emitAll(element, isDefinition = false, context) } fun visitClassReference(firClassSymbol: FirClassLikeSymbol<*>, element: KtSourceElement, context: CheckerContext) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, Role.REFERENCE, context) + cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false, context) } fun visitCallableReference(firClassSymbol: FirCallableSymbol<*>, element: KtSourceElement, context: CheckerContext) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, Role.REFERENCE, context) + cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false, context) } fun visitClassOrObject(firClass: FirClassLikeDeclaration, element: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - cache[firClass.symbol].with(firClass.symbol).emitAll(element, Role.DEFINITION, context, enclosingSource) + cache[firClass.symbol].with(firClass.symbol).emitAll(element, isDefinition = true, context, enclosingSource) } fun visitPrimaryConstructor(firConstructor: FirConstructor, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - // if the constructor is not denoted by the 'constructor' keyword, we want to link it to the - // class ident - cache[firConstructor.symbol].with(firConstructor.symbol).emitAll(source, Role.DEFINITION, context, enclosingSource) + cache[firConstructor.symbol].with(firConstructor.symbol).emitAll(source, isDefinition = true, context, enclosingSource) } fun visitSecondaryConstructor(firConstructor: FirConstructor, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - cache[firConstructor.symbol].with(firConstructor.symbol).emitAll(source, Role.DEFINITION, context, enclosingSource) + cache[firConstructor.symbol].with(firConstructor.symbol).emitAll(source, isDefinition = true, context, enclosingSource) } fun visitNamedFunction(firFunction: FirFunction, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - cache[firFunction.symbol].with(firFunction.symbol).emitAll(source, Role.DEFINITION, context, enclosingSource) + cache[firFunction.symbol].with(firFunction.symbol).emitAll(source, isDefinition = true, context, enclosingSource) } fun visitProperty(firProperty: FirProperty, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - cache[firProperty.symbol].with(firProperty.symbol).emitAll(source, Role.DEFINITION, context, enclosingSource) + cache[firProperty.symbol].with(firProperty.symbol).emitAll(source, isDefinition = true, context, enclosingSource) } fun visitParameter(firParameter: FirValueParameter, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - cache[firParameter.symbol].with(firParameter.symbol).emitAll(source, Role.DEFINITION, context, enclosingSource) + cache[firParameter.symbol].with(firParameter.symbol).emitAll(source, isDefinition = true, context, enclosingSource) } fun visitTypeParameter(firTypeParameter: FirTypeParameter, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { cache[firTypeParameter.symbol] .with(firTypeParameter.symbol) - .emitAll(source, Role.DEFINITION, context, enclosingSource) + .emitAll(source, isDefinition = true, context, enclosingSource) } fun visitTypeAlias(firTypeAlias: FirTypeAlias, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { - cache[firTypeAlias.symbol].with(firTypeAlias.symbol).emitAll(source, Role.DEFINITION, context, enclosingSource) + cache[firTypeAlias.symbol].with(firTypeAlias.symbol).emitAll(source, isDefinition = true, context, enclosingSource) } fun visitPropertyAccessor(firPropertyAccessor: FirPropertyAccessor, source: KtSourceElement, context: CheckerContext, enclosingSource: KtSourceElement? = null) { cache[firPropertyAccessor.symbol] .with(firPropertyAccessor.symbol) - .emitAll(source, Role.DEFINITION, context, enclosingSource) + .emitAll(source, isDefinition = true, context, enclosingSource) } fun visitSimpleNameExpression( @@ -110,7 +109,6 @@ class SemanticdbVisitor( ) { cache[firResolvedNamedReference.resolvedSymbol] .with(firResolvedNamedReference.resolvedSymbol) - .emitAll(source, Role.REFERENCE, context) + .emitAll(source, isDefinition = false, context) } } - diff --git a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt index 9ce6d24e6..48e1c737b 100644 --- a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt +++ b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt @@ -1,11 +1,6 @@ package com.sourcegraph.semanticdb_kotlinc.test -import com.sourcegraph.semanticdb.Semanticdb - import com.sourcegraph.semanticdb_kotlinc.* -import com.sourcegraph.semanticdb.Semanticdb.Language.KOTLIN -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence.Role -import com.sourcegraph.semanticdb.Semanticdb.TextDocument import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.PluginOption import com.tschuchort.compiletesting.SourceFile @@ -17,21 +12,24 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import java.io.File import java.nio.file.Path +import java.nio.file.Paths import kotlin.contracts.ExperimentalContracts import kotlin.test.Test import kotlin.test.assertEquals import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.io.TempDir -import java.nio.file.Paths +import org.scip_code.scip.Document +import org.scip_code.scip.Occurrence +import org.scip_code.scip.SymbolInformation @OptIn(ExperimentalCompilerApi::class) @ExperimentalContracts class AnalyzerTest { - fun compileSemanticdb(path: Path, @Language("kotlin") code: String): TextDocument { + fun compileSemanticdb(path: Path, @Language("kotlin") code: String): Document { val buildPath = File(path.resolve("build").toString()).apply { mkdir() } val source = SourceFile.testKt(code) - lateinit var document: TextDocument + lateinit var document: Document val result = KotlinCompilation() @@ -68,8 +66,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/" range { startLine = 0 @@ -78,8 +76,8 @@ class AnalyzerTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Banana#" range { startLine = 1 @@ -93,8 +91,8 @@ class AnalyzerTest { endCharacter = 1 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Banana#foo()." range { startLine = 2 @@ -115,25 +113,15 @@ class AnalyzerTest { val symbols = arrayOf( - SymbolInformation { + scipSymbol { symbol = "sample/Banana#" - language = KOTLIN displayName = "Banana" - documentation = - Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - message = "```kotlin\npublic final class Banana : Any\n```" - } + signatureText = "public final class Banana : Any" }, - SymbolInformation { + scipSymbol { symbol = "sample/Banana#foo()." - language = KOTLIN displayName = "foo" - documentation = - Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - message = "```kotlin\npublic final fun foo(): Unit\n```" - } + signatureText = "public final fun foo(): Unit" }) assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } } } @@ -153,8 +141,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/" range { startLine = 0 @@ -163,8 +151,8 @@ class AnalyzerTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/" range { startLine = 2 @@ -173,8 +161,8 @@ class AnalyzerTest { endCharacter = 13 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/Boolean#" range { startLine = 2 @@ -183,8 +171,8 @@ class AnalyzerTest { endCharacter = 21 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/" range { startLine = 3 @@ -193,8 +181,8 @@ class AnalyzerTest { endCharacter = 13 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/Int#" range { startLine = 3 @@ -227,8 +215,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/foo()." range { startLine = 2 @@ -243,9 +231,9 @@ class AnalyzerTest { } }, // LocalClass - SymbolOccurrence { - role = Role.DEFINITION - symbol = "local0" + scipOccurrence { + role = DEFINITION + symbol = "local 0" range { startLine = 3 startCharacter = 8 @@ -260,9 +248,9 @@ class AnalyzerTest { } }, // LocalClass constructor - SymbolOccurrence { - role = Role.DEFINITION - symbol = "local1" + scipOccurrence { + role = DEFINITION + symbol = "local 1" range { startLine = 3 startCharacter = 8 @@ -277,9 +265,9 @@ class AnalyzerTest { } }, // localClassMethod - SymbolOccurrence { - role = Role.DEFINITION - symbol = "local2" + scipOccurrence { + role = DEFINITION + symbol = "local 2" range { startLine = 4 startCharacter = 8 @@ -300,41 +288,25 @@ class AnalyzerTest { val symbols = arrayOf( - SymbolInformation { + scipSymbol { symbol = "sample/foo()." displayName = "foo" - language = KOTLIN - documentation { - message = "```kotlin\npublic final fun foo(): Unit\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public final fun foo(): Unit" }, - SymbolInformation { - symbol = "local0" + scipSymbol { + symbol = "local 0" displayName = "LocalClass" - language = KOTLIN - documentation { - message = "```kotlin\nlocal final class LocalClass : Any\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "local final class LocalClass : Any" }, - SymbolInformation { - symbol = "local1" + scipSymbol { + symbol = "local 1" displayName = "LocalClass" - language = KOTLIN - documentation { - message = "```kotlin\npublic constructor(): LocalClass\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public constructor(): LocalClass" }, - SymbolInformation { - symbol = "local2" + scipSymbol { + symbol = "local 2" displayName = "localClassMethod" - language = KOTLIN - documentation { - message = "```kotlin\npublic final fun localClassMethod(): Unit\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public final fun localClassMethod(): Unit" }, ) assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } } @@ -360,8 +332,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/" range { startLine = 0 @@ -370,8 +342,8 @@ class AnalyzerTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Interface#" range { startLine = 2 @@ -385,8 +357,8 @@ class AnalyzerTest { endCharacter = 1 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Interface#foo()." range { startLine = 3 @@ -401,8 +373,8 @@ class AnalyzerTest { endCharacter = 13 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Class#" range { startLine = 6 @@ -416,8 +388,8 @@ class AnalyzerTest { endCharacter = 1 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/Interface#" range { startLine = 6 @@ -426,8 +398,8 @@ class AnalyzerTest { endCharacter = 23 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Class#foo()." range { startLine = 7 @@ -449,42 +421,26 @@ class AnalyzerTest { val symbols = arrayOf( - SymbolInformation { + scipSymbol { symbol = "sample/Interface#" displayName = "Interface" - language = KOTLIN - documentation { - message = "```kotlin\npublic abstract interface Interface : Any\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public abstract interface Interface : Any" }, - SymbolInformation { + scipSymbol { symbol = "sample/Interface#foo()." displayName = "foo" - language = KOTLIN - documentation { - message = "```kotlin\npublic abstract fun foo(): Unit\n\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public abstract fun foo(): Unit\n" }, - SymbolInformation { + scipSymbol { symbol = "sample/Class#" displayName = "Class" - language = KOTLIN - documentation { - message = "```kotlin\npublic final class Class : Interface\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public final class Class : Interface" addOverriddenSymbols("sample/Interface#") }, - SymbolInformation { + scipSymbol { symbol = "sample/Class#foo()." displayName = "foo" - language = KOTLIN - documentation { - message = "```kotlin\npublic open override fun foo(): Unit\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") }, ) @@ -516,8 +472,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/" range { startLine = 0 @@ -526,8 +482,8 @@ class AnalyzerTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Interface#" range { startLine = 2 @@ -541,8 +497,8 @@ class AnalyzerTest { endCharacter = 1 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Interface#foo()." range { startLine = 3 @@ -557,8 +513,8 @@ class AnalyzerTest { endCharacter = 13 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/``#" range { startLine = 7 @@ -573,8 +529,8 @@ class AnalyzerTest { endCharacter = 5 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/``#``()." range { startLine = 7 @@ -589,8 +545,8 @@ class AnalyzerTest { endCharacter = 5 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/Interface#" range { startLine = 7 @@ -599,8 +555,8 @@ class AnalyzerTest { endCharacter = 30 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/``#foo()." range { startLine = 8 @@ -615,8 +571,8 @@ class AnalyzerTest { endCharacter = 29 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/``#" range { startLine = 10 @@ -631,8 +587,8 @@ class AnalyzerTest { endCharacter = 5 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/``#``()." range { startLine = 10 @@ -647,8 +603,8 @@ class AnalyzerTest { endCharacter = 5 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/Interface#" range { startLine = 10 @@ -657,8 +613,8 @@ class AnalyzerTest { endCharacter = 30 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/``#foo()." range { startLine = 11 @@ -680,53 +636,33 @@ class AnalyzerTest { val symbols = arrayOf( - SymbolInformation { + scipSymbol { symbol = "sample/Interface#" displayName = "Interface" - language = KOTLIN - documentation { - message = "```kotlin\npublic abstract interface Interface : Any\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public abstract interface Interface : Any" }, - SymbolInformation { + scipSymbol { symbol = "sample/``#" displayName = "" - language = KOTLIN - documentation { - message = "```kotlin\nobject : Interface\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") }, - SymbolInformation { + scipSymbol { symbol = "sample/``#foo()." displayName = "foo" - language = KOTLIN - documentation { - message = "```kotlin\npublic open override fun foo(): Unit\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") }, - SymbolInformation { + scipSymbol { symbol = "sample/``#" displayName = "" - language = KOTLIN - documentation { - message = "```kotlin\nobject : Interface\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") }, - SymbolInformation { + scipSymbol { symbol = "sample/``#foo()." displayName = "foo" - language = KOTLIN - documentation { - message = "```kotlin\npublic open override fun foo(): Unit\n```" - format = Semanticdb.Documentation.Format.MARKDOWN - } + signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") }, ) @@ -747,8 +683,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/foo()." range { startLine = 2 @@ -762,8 +698,8 @@ class AnalyzerTest { endCharacter = 33 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/foo().(arg)" range { startLine = 2 @@ -778,8 +714,8 @@ class AnalyzerTest { endCharacter = 16 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/Int#" range { startLine = 2 @@ -788,8 +724,8 @@ class AnalyzerTest { endCharacter = 16 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/Boolean#" range { startLine = 2 @@ -822,8 +758,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/Int#" range { startLine = 4 @@ -832,8 +768,8 @@ class AnalyzerTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "kotlin/Float#" range { startLine = 5 @@ -1323,8 +1259,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "hello/" range { startLine = 0 @@ -1333,8 +1269,8 @@ class AnalyzerTest { endCharacter = 13 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "hello/sample/" range { startLine = 0 @@ -1343,8 +1279,8 @@ class AnalyzerTest { endCharacter = 20 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "hello/sample/Apple#" range { startLine = 1 @@ -1358,8 +1294,8 @@ class AnalyzerTest { endCharacter = 11 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "hello/sample/Apple#``()." range { startLine = 1 @@ -1381,15 +1317,10 @@ class AnalyzerTest { val symbols = arrayOf( - SymbolInformation { + scipSymbol { symbol = "hello/sample/Apple#" - language = KOTLIN displayName = "Apple" - documentation = - Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - message = "```kotlin\npublic final class Apple : Any\n```" - } + signatureText = "public final class Apple : Any" }) assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } } @@ -1410,8 +1341,8 @@ class AnalyzerTest { val occurrences = arrayOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "sample/" range { startLine = 0 @@ -1420,8 +1351,8 @@ class AnalyzerTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Banana#" range { startLine = 1 @@ -1435,8 +1366,8 @@ class AnalyzerTest { endCharacter = 1 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Banana#foo()." range { startLine = 2 @@ -1451,8 +1382,8 @@ class AnalyzerTest { endCharacter = 17 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "sample/Banana#" range { startLine = 1 @@ -1473,25 +1404,15 @@ class AnalyzerTest { val symbols = arrayOf( - SymbolInformation { + scipSymbol { symbol = "sample/Banana#" - language = KOTLIN displayName = "Banana" - documentation = - Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - message = "```kotlin\npublic final class Banana : Any\n```" - } + signatureText = "public final class Banana : Any" }, - SymbolInformation { + scipSymbol { symbol = "sample/Banana#foo()." - language = KOTLIN displayName = "foo" - documentation = - Documentation { - format = Semanticdb.Documentation.Format.MARKDOWN - message = "```kotlin\npublic final fun foo(): Unit\n```" - } + signatureText = "public final fun foo(): Unit" }) assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } } } @@ -1520,11 +1441,11 @@ class AnalyzerTest { document.assertDocumentation("sample/docstrings().", "Example method docstring") } - private fun TextDocument.assertDocumentation(symbol: String, expectedDocumentation: String) { - val markdown = - this.symbolsList.find { it.symbol == symbol }?.documentation?.message - ?: fail("no documentation for symbol $symbol") - val obtainedDocumentation = markdown.split("----").last().trim() + private fun Document.assertDocumentation(symbol: String, expectedDocumentation: String) { + val info = + this.symbolsList.find { it.symbol == symbol } + ?: fail("no SymbolInformation for symbol $symbol") + val obtainedDocumentation = info.documentationList.joinToString("\n").trim() assertEquals(expectedDocumentation, obtainedDocumentation) } } diff --git a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/ScipBuilders.kt b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/ScipBuilders.kt new file mode 100644 index 000000000..0b5fea89a --- /dev/null +++ b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/ScipBuilders.kt @@ -0,0 +1,117 @@ +package com.sourcegraph.semanticdb_kotlinc.test + +import org.scip_code.scip.Occurrence +import org.scip_code.scip.SymbolInformation +import org.scip_code.scip.SymbolRole +import org.scip_code.scip.occurrence +import org.scip_code.scip.relationship +import org.scip_code.scip.signature +import org.scip_code.scip.symbolInformation + +/** + * Tiny DSL for building SCIP [Occurrence] / [SymbolInformation] test fixtures with the same shape + * as the original SemanticDB-based one used by the Kotlin tests. + * + *

Example: + * ``` + * scipOccurrence { + * role = DEFINITION + * symbol = "sample/Banana#" + * range { startLine = 1; startCharacter = 6; endLine = 1; endCharacter = 12 } + * enclosingRange { startLine = 1; endLine = 3; endCharacter = 1 } + * } + * ``` + */ + +internal val REFERENCE: Int = SymbolRole.UnspecifiedSymbolRole.number +internal val DEFINITION: Int = SymbolRole.Definition.number + +@DslMarker annotation class ScipBuilderDsl + +@ScipBuilderDsl +class ScipRangeBuilder { + var startLine: Int = 0 + var startCharacter: Int = 0 + /** + * Default sentinel: when [endLine] is left untouched, the produced range is + * single-line at [startLine]. + */ + var endLine: Int = -1 + var endCharacter: Int = 0 + + internal fun toIntList(): List { + val line = if (endLine < 0) startLine else endLine + return if (line == startLine) listOf(startLine, startCharacter, endCharacter) + else listOf(startLine, startCharacter, line, endCharacter) + } +} + +@ScipBuilderDsl +class ScipOccurrenceBuilder { + var role: Int = REFERENCE + var symbol: String = "" + private var range: List? = null + private var enclosingRange: List? = null + + fun range(block: ScipRangeBuilder.() -> Unit) { + range = ScipRangeBuilder().apply(block).toIntList() + } + + fun enclosingRange(block: ScipRangeBuilder.() -> Unit) { + enclosingRange = ScipRangeBuilder().apply(block).toIntList() + } + + internal fun build(): Occurrence = occurrence { + symbol = this@ScipOccurrenceBuilder.symbol + symbolRoles = role + this@ScipOccurrenceBuilder.range?.let { range += it } + this@ScipOccurrenceBuilder.enclosingRange?.let { enclosingRange += it } + } +} + +@ScipBuilderDsl +class ScipSymbolInformationBuilder { + var symbol: String = "" + var displayName: String = "" + var signatureText: String? = null + private val docs = mutableListOf() + private val overrides = mutableListOf() + + fun documentation(text: String) { + docs += text + } + + /** + * Appends an `is_implementation` [Relationship]. Mirrors the old SemanticDB-flavored + * `addOverriddenSymbols` so existing test fixtures port over with minimal diff. + */ + fun addOverriddenSymbols(vararg symbols: String) { + overrides.addAll(symbols) + } + + internal fun build(): SymbolInformation = symbolInformation { + symbol = this@ScipSymbolInformationBuilder.symbol + if (this@ScipSymbolInformationBuilder.displayName.isNotEmpty()) { + displayName = this@ScipSymbolInformationBuilder.displayName + } + signatureText?.let { sigText -> + signatureDocumentation = signature { + language = "kotlin" + text = sigText + } + } + for (d in docs) documentation += d + for (s in overrides) { + relationships += relationship { + symbol = s + isImplementation = true + } + } + } +} + +internal fun scipOccurrence(block: ScipOccurrenceBuilder.() -> Unit): Occurrence = + ScipOccurrenceBuilder().apply(block).build() + +internal fun scipSymbol(block: ScipSymbolInformationBuilder.() -> Unit): SymbolInformation = + ScipSymbolInformationBuilder().apply(block).build() diff --git a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/SemanticdbSymbolsTest.kt b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/SemanticdbSymbolsTest.kt index 90f451dda..dd3a0e23a 100644 --- a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/SemanticdbSymbolsTest.kt +++ b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/SemanticdbSymbolsTest.kt @@ -1,17 +1,14 @@ package com.sourcegraph.semanticdb_kotlinc.test -import com.sourcegraph.semanticdb.Semanticdb - import com.sourcegraph.semanticdb_kotlinc.* -import com.sourcegraph.semanticdb.Semanticdb.Documentation.Format -import com.sourcegraph.semanticdb.Semanticdb.Language -import com.sourcegraph.semanticdb.Semanticdb.SymbolOccurrence.Role -import com.sourcegraph.semanticdb_kotlinc.test.ExpectedSymbols.SemanticdbData +import com.sourcegraph.semanticdb_kotlinc.test.ExpectedSymbols.ScipData import com.sourcegraph.semanticdb_kotlinc.test.ExpectedSymbols.SymbolCacheData import com.tschuchort.compiletesting.SourceFile import kotlin.contracts.ExperimentalContracts import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.TestFactory +import org.scip_code.scip.Occurrence +import org.scip_code.scip.SymbolInformation @ExperimentalCompilerApi @ExperimentalContracts @@ -215,12 +212,12 @@ class SemanticdbSymbolsTest { """ |var x: Int = 5 |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "x." range { startLine = 0 @@ -232,8 +229,8 @@ class SemanticdbSymbolsTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "getX()." range { startLine = 0 @@ -245,8 +242,8 @@ class SemanticdbSymbolsTest { endCharacter = 14 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "setX()." range { startLine = 0 @@ -266,12 +263,12 @@ class SemanticdbSymbolsTest { |var x: Int = 5 | get() = field + 10 |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "x." range { startLine = 0 @@ -284,8 +281,8 @@ class SemanticdbSymbolsTest { endCharacter = 22 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "setX()." range { startLine = 0 @@ -298,8 +295,8 @@ class SemanticdbSymbolsTest { endCharacter = 22 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "getX()." range { startLine = 1 @@ -322,12 +319,12 @@ class SemanticdbSymbolsTest { |var x: Int = 5 | set(value) { field = value + 5 } |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "x." range { startLine = 0 @@ -340,8 +337,8 @@ class SemanticdbSymbolsTest { endCharacter = 36 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "getX()." range { startLine = 0 @@ -354,8 +351,8 @@ class SemanticdbSymbolsTest { endCharacter = 36 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "setX()." range { startLine = 1 @@ -379,12 +376,12 @@ class SemanticdbSymbolsTest { | get() = field + 10 | set(value) { field = value + 10 } |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "x." range { startLine = 0 @@ -397,8 +394,8 @@ class SemanticdbSymbolsTest { endCharacter = 37 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "getX()." range { startLine = 1 @@ -413,8 +410,8 @@ class SemanticdbSymbolsTest { endCharacter = 22 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "setX()." range { startLine = 2 @@ -440,12 +437,12 @@ class SemanticdbSymbolsTest { | } |} |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Test#``().(sample)" range { startLine = 0 @@ -458,8 +455,8 @@ class SemanticdbSymbolsTest { endCharacter = 26 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Test#sample." range { startLine = 0 @@ -472,8 +469,8 @@ class SemanticdbSymbolsTest { endCharacter = 26 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Test#getSample()." range { startLine = 0 @@ -486,8 +483,8 @@ class SemanticdbSymbolsTest { endCharacter = 26 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Test#setSample()." range { startLine = 0 @@ -500,8 +497,8 @@ class SemanticdbSymbolsTest { endCharacter = 26 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "Test#``().(sample)" range { startLine = 0 @@ -510,8 +507,8 @@ class SemanticdbSymbolsTest { endCharacter = 59 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "Test#sample." range { startLine = 2 @@ -520,8 +517,8 @@ class SemanticdbSymbolsTest { endCharacter = 22 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "Test#getSample()." range { startLine = 2 @@ -542,12 +539,12 @@ class SemanticdbSymbolsTest { """ |class Banana |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Banana#" range { startLine = 0 @@ -559,8 +556,8 @@ class SemanticdbSymbolsTest { endCharacter = 12 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Banana#``()." range { startLine = 0 @@ -579,12 +576,12 @@ class SemanticdbSymbolsTest { """ |class Banana(size: Int) |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Banana#" range { startLine = 0 @@ -596,8 +593,8 @@ class SemanticdbSymbolsTest { endCharacter = 23 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Banana#``()." range { startLine = 0 @@ -617,12 +614,12 @@ class SemanticdbSymbolsTest { """ |class Banana constructor(size: Int) |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Banana#" range { startLine = 0 @@ -634,8 +631,8 @@ class SemanticdbSymbolsTest { endCharacter = 35 } }, - SymbolOccurrence { - role = Role.DEFINITION + scipOccurrence { + role = DEFINITION symbol = "Banana#``()." range { startLine = 0 @@ -660,12 +657,12 @@ class SemanticdbSymbolsTest { """ |val x = Runnable { }.run() |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedOccurrences = listOf( - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "java/lang/Runnable#" range { startLine = 0 @@ -674,8 +671,8 @@ class SemanticdbSymbolsTest { endCharacter = 16 } }, - SymbolOccurrence { - role = Role.REFERENCE + scipOccurrence { + role = REFERENCE symbol = "java/lang/Runnable#run()." range { startLine = 0 @@ -700,29 +697,21 @@ class SemanticdbSymbolsTest { |*/ |val x = "" |""".trimMargin()), - semanticdb = - SemanticdbData( + scip = + ScipData( expectedSymbols = listOf( - SymbolInformation { + scipSymbol { symbol = "x." displayName = "x" - language = Language.KOTLIN - documentation { - message = - "```kotlin\npublic final val x: String\n```\n\n----\n\n\nhello world\n test content\n" - format = Format.MARKDOWN - } + signatureText = "public final val x: String" + documentation("hello world\n test content") }, - SymbolInformation { + scipSymbol { symbol = "getX()." displayName = "x" - language = Language.KOTLIN - documentation { - message = - "```kotlin\npublic get(): String\n```\n\n----\n\n\nhello world\n test content\n" - format = Format.MARKDOWN - } + signatureText = "public get(): String" + documentation("hello world\n test content") })))) .mapCheckExpectedSymbols() } diff --git a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt index 84b9b82ce..908c14294 100644 --- a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt +++ b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt @@ -1,8 +1,6 @@ package com.sourcegraph.semanticdb_kotlinc.test -import com.sourcegraph.semanticdb.Semanticdb import com.sourcegraph.semanticdb.SemanticdbOptions - import com.sourcegraph.semanticdb_kotlinc.* import com.sourcegraph.semanticdb_kotlinc.AnalyzerCheckers.Companion.visitors import com.tschuchort.compiletesting.KotlinCompilation @@ -31,16 +29,20 @@ import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.junit.jupiter.api.Assumptions.assumeFalse import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.scip_code.scip.Document +import org.scip_code.scip.Occurrence +import org.scip_code.scip.SymbolInformation data class ExpectedSymbols( val testName: String, val source: SourceFile, val symbolsCacheData: SymbolCacheData? = null, - val semanticdb: SemanticdbData? = null + val scip: ScipData? = null ) { - data class SemanticdbData( - val expectedOccurrences: List? = null, - val expectedSymbols: List? = null + /** Subset of a SCIP [Document] that a single test wants to assert on. */ + data class ScipData( + val expectedOccurrences: List? = null, + val expectedSymbols: List? = null ) data class SymbolCacheData( @@ -55,10 +57,10 @@ fun SourceFile.Companion.testKt(@Language("kotlin") contents: String): SourceFil @ExperimentalCompilerApi @ExperimentalContracts fun List.mapCheckExpectedSymbols(): List = - this.flatMap { (testName, source, symbolsData, semanticdbData) -> + this.flatMap { (testName, source, symbolsData, scipData) -> val globals = GlobalSymbolsCache(testing = true) val locals = LocalSymbolsCache() - lateinit var document: Semanticdb.TextDocument + lateinit var document: Document val compilation = configureTestCompiler(source, globals, locals) { document = it } listOf( dynamicTest("$testName - compilation") { @@ -73,11 +75,11 @@ fun List.mapCheckExpectedSymbols(): List = } ?: assumeFalse(true) }, - dynamicTest("$testName - semanticdb") { - semanticdbData?.apply { + dynamicTest("$testName - scip") { + scipData?.apply { println( - "checking semanticdb: ${expectedOccurrences?.size ?: 0} occurrences and ${expectedSymbols?.size ?: 0} symbols") - checkContainsExpectedSemanticdb(document, expectedOccurrences, expectedSymbols) + "checking scip: ${expectedOccurrences?.size ?: 0} occurrences and ${expectedSymbols?.size ?: 0} symbols") + checkContainsExpectedScip(document, expectedOccurrences, expectedSymbols) } ?: assumeFalse(true) }) @@ -94,11 +96,10 @@ fun checkContainsExpectedSymbols( localsCount?.also { locals.size shouldBe it } } -@ExperimentalContracts -fun checkContainsExpectedSemanticdb( - document: Semanticdb.TextDocument, - expectedOccurrences: List?, - expectedSymbols: List? +fun checkContainsExpectedScip( + document: Document, + expectedOccurrences: List?, + expectedSymbols: List? ) { assertSoftly(document.occurrencesList) { expectedOccurrences?.let { this.shouldContainInOrder(it) } @@ -112,7 +113,7 @@ private fun configureTestCompiler( source: SourceFile, globals: GlobalSymbolsCache, locals: LocalSymbolsCache, - hook: (Semanticdb.TextDocument) -> Unit = {} + hook: (Document) -> Unit = {} ): KotlinCompilation { val compilation = KotlinCompilation().apply { @@ -184,7 +185,7 @@ fun semanticdbVisitorAnalyzer( globals: GlobalSymbolsCache, locals: LocalSymbolsCache, sourceroot: Path, - hook: (Semanticdb.TextDocument) -> Unit = {} + hook: (Document) -> Unit = {} ): CompilerPluginRegistrar { return object : CompilerPluginRegistrar() { override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { diff --git a/semanticdb-shared/BUILD b/semanticdb-shared/BUILD index 099906958..58c22abe9 100644 --- a/semanticdb-shared/BUILD +++ b/semanticdb-shared/BUILD @@ -1,24 +1,16 @@ -load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") -load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("@rules_java//java:defs.bzl", "java_library") package( default_visibility = ["//visibility:public"], ) -proto_library( - name = "semanticdb_proto", - srcs = ["src/main/protobuf/semanticdb.proto"], -) - -java_proto_library( - name = "semanticdb_java_proto", - deps = [":semanticdb_proto"], -) - java_library( name = "semanticdb-shared", srcs = glob(["src/main/java/**/*.java"]), - exports = [":semanticdb_java_proto"], - deps = [":semanticdb_java_proto"], + exports = [ + "@maven//:org_scip_code_scip_java_bindings", + ], + deps = [ + "@maven//:org_scip_code_scip_java_bindings", + ], ) diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipDocumentBuilder.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipDocumentBuilder.java new file mode 100644 index 000000000..fc88024fd --- /dev/null +++ b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipDocumentBuilder.java @@ -0,0 +1,86 @@ +package com.sourcegraph.semanticdb; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.scip_code.scip.Document; +import org.scip_code.scip.Occurrence; +import org.scip_code.scip.SymbolInformation; + +/** + * Accumulator for SCIP {@link Occurrence}/{@link SymbolInformation} that assembles a final SCIP + * {@link Document}. + * + *

First emission wins: occurrences are deduplicated by {@code (range, symbol, roles)}, symbols + * by {@code symbol}; occurrences are sorted by start position. Contains no compiler-API + * dependencies. + */ +public final class ScipDocumentBuilder { + private static final Comparator OCCURRENCE_ORDER = + Comparator.comparingInt(o -> rangeStartLine(o)) + .thenComparingInt(o -> rangeStartCharacter(o)); + + private final Map occurrences = new LinkedHashMap<>(); + private final Map symbols = new LinkedHashMap<>(); + + public void addOccurrence(Occurrence occurrence) { + if (occurrence.getSymbol().isEmpty()) return; + occurrences.putIfAbsent(new OccurrenceKey(occurrence), occurrence); + } + + public void addSymbol(SymbolInformation symbol) { + if (symbol.getSymbol().isEmpty()) return; + symbols.putIfAbsent(symbol.getSymbol(), symbol); + } + + public Document build(String language, String relativePath, String text) { + List sortedOccurrences = new ArrayList<>(occurrences.values()); + sortedOccurrences.sort(OCCURRENCE_ORDER); + return Document.newBuilder() + .setLanguage(language) + .setRelativePath(relativePath) + .setText(text == null ? "" : text) + .addAllOccurrences(sortedOccurrences) + .addAllSymbols(symbols.values()) + .build(); + } + + private static int rangeStartLine(Occurrence occurrence) { + return occurrence.getRangeCount() > 0 ? occurrence.getRange(0) : 0; + } + + private static int rangeStartCharacter(Occurrence occurrence) { + return occurrence.getRangeCount() > 1 ? occurrence.getRange(1) : 0; + } + + private static final class OccurrenceKey { + private final List range; + private final String symbol; + private final int roles; + + OccurrenceKey(Occurrence occurrence) { + this.range = Collections.unmodifiableList(new ArrayList<>(occurrence.getRangeList())); + this.symbol = occurrence.getSymbol(); + this.roles = occurrence.getSymbolRoles(); + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof OccurrenceKey)) return false; + OccurrenceKey that = (OccurrenceKey) other; + return roles == that.roles + && Objects.equals(range, that.range) + && Objects.equals(symbol, that.symbol); + } + + @Override + public int hashCode() { + return Objects.hash(range, symbol, roles); + } + } +} diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbPaths.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipShardPaths.java similarity index 50% rename from semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbPaths.java rename to semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipShardPaths.java index 2181ca4d1..6b11fb12b 100644 --- a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbPaths.java +++ b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipShardPaths.java @@ -5,55 +5,55 @@ import java.util.Optional; /** - * Shared helpers for the canonical SemanticDB output-path and URI layout. + * Shared helpers for the canonical SCIP shard output-path and relative-path layout. * - *

Every SemanticDB producer writes payloads under {@code - * /META-INF/semanticdb/.semanticdb} and labels documents with a - * unix-style {@code } URI. Both {@code semanticdb-javac} and {@code - * semanticdb-kotlinc} converged on the same layout, so the policy lives here. + *

Every compiler plugin writes one SCIP shard per source file under {@code + * /META-INF/scip/.scip} and labels documents with a unix-style + * {@code }. Both {@code semanticdb-javac} and {@code semanticdb-kotlinc} + * share this layout. * *

Plugin-specific behavior for files that are not under the sourceroot (e.g. javac's * {@code NoRelativePathMode} fallbacks or kotlinc's "skip with warning") stays in the plugins; this * class only covers the in-sourceroot happy path. */ -public final class SemanticdbPaths { - /** Subdirectory of the target root that holds the generated {@code .semanticdb} payloads. */ - public static final String SEMANTICDB_ROOT = "META-INF/semanticdb"; +public final class ScipShardPaths { + /** Subdirectory of the target root that holds the generated {@code .scip} shards. */ + public static final String SCIP_ROOT = "META-INF/scip"; - private SemanticdbPaths() {} + private ScipShardPaths() {} /** - * Returns the on-disk {@code .semanticdb} payload path for an absolute source file under {@code + * Returns the on-disk {@code .scip} shard path for an absolute source file under {@code * sourceRoot}, or {@link Optional#empty()} when the source file is not under {@code sourceRoot}. */ - public static Optional semanticdbPath( + public static Optional shardPath( Path targetRoot, Path sourceRoot, Path absoluteSourcePath) { if (!absoluteSourcePath.startsWith(sourceRoot)) { return Optional.empty(); } Path relative = sourceRoot.relativize(absoluteSourcePath); - return Optional.of(semanticdbPathForRelativeSource(targetRoot, relative)); + return Optional.of(shardPathForRelativeSource(targetRoot, relative)); } /** - * Returns the on-disk {@code .semanticdb} payload path for a source file expressed as a path - * relative to the source root. + * Returns the on-disk {@code .scip} shard path for a source file expressed as a path relative to + * the source root. */ - public static Path semanticdbPathForRelativeSource(Path targetRoot, Path relativeSourcePath) { - String filename = relativeSourcePath.getFileName().toString() + ".semanticdb"; + public static Path shardPathForRelativeSource(Path targetRoot, Path relativeSourcePath) { + String filename = relativeSourcePath.getFileName().toString() + ".scip"; return targetRoot .resolve("META-INF") - .resolve("semanticdb") + .resolve("scip") .resolve(relativeSourcePath) .resolveSibling(filename); } /** - * Returns the unix-style {@code TextDocument.uri} for the given absolute source file. When the - * file lives under {@code sourceRoot}, the URI is the source-root-relative path; otherwise it is - * the file's absolute path. Both cases are joined with {@code '/'} regardless of platform. + * Returns the unix-style {@code Document.relative_path} for the given absolute source file. When + * the file lives under {@code sourceRoot}, the path is source-root-relative; otherwise it is the + * file's absolute path. Both cases are joined with {@code '/'} regardless of platform. */ - public static String semanticdbUri(Path sourceRoot, Path absoluteSourcePath) { + public static String relativePath(Path sourceRoot, Path absoluteSourcePath) { Path uriPath = absoluteSourcePath.startsWith(sourceRoot) ? sourceRoot.relativize(absoluteSourcePath) diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipShardWriter.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipShardWriter.java new file mode 100644 index 000000000..c258a38f4 --- /dev/null +++ b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/ScipShardWriter.java @@ -0,0 +1,23 @@ +package com.sourcegraph.semanticdb; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.scip_code.scip.Document; +import org.scip_code.scip.Index; + +/** Serializes a single SCIP {@link Document} as a singleton {@link Index} on disk. */ +public final class ScipShardWriter { + private ScipShardWriter() {} + + /** + * Wraps {@code document} in a singleton {@link Index} message (no {@code Metadata}) and writes it + * to {@code output}, creating parent directories as needed. The aggregator owns the per-index + * metadata, so per-source shards intentionally omit it. + */ + public static void writeShard(Path output, Document document) throws IOException { + byte[] bytes = Index.newBuilder().addDocuments(document).build().toByteArray(); + Files.createDirectories(output.getParent()); + Files.write(output, bytes); + } +} diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbBuilders.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbBuilders.java deleted file mode 100644 index 2a238bbbf..000000000 --- a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbBuilders.java +++ /dev/null @@ -1,242 +0,0 @@ -package com.sourcegraph.semanticdb; - -import java.util.List; - -public class SemanticdbBuilders { - // SemanticDB Types - public static Semanticdb.Type typeRef(String symbol) { - return Semanticdb.Type.newBuilder() - .setTypeRef(Semanticdb.TypeRef.newBuilder().setSymbol(symbol)) - .build(); - } - - public static Semanticdb.Type typeRef(String symbol, List typeArguments) { - return Semanticdb.Type.newBuilder() - .setTypeRef( - Semanticdb.TypeRef.newBuilder().setSymbol(symbol).addAllTypeArguments(typeArguments)) - .build(); - } - - public static Semanticdb.Type existentialType( - Semanticdb.Type type, Semanticdb.Scope declarations) { - return Semanticdb.Type.newBuilder() - .setExistentialType( - Semanticdb.ExistentialType.newBuilder().setTpe(type).setDeclarations(declarations)) - .build(); - } - - public static Semanticdb.Type intersectionType(List types) { - return Semanticdb.Type.newBuilder() - .setIntersectionType(Semanticdb.IntersectionType.newBuilder().addAllTypes(types)) - .build(); - } - - // SemanticDB Signatures - - public static Semanticdb.Signature signature(Semanticdb.ClassSignature.Builder signature) { - return Semanticdb.Signature.newBuilder().setClassSignature(signature).build(); - } - - public static Semanticdb.Signature signature(Semanticdb.MethodSignature.Builder signature) { - return Semanticdb.Signature.newBuilder().setMethodSignature(signature).build(); - } - - public static Semanticdb.Signature signature(Semanticdb.ValueSignature.Builder signature) { - return Semanticdb.Signature.newBuilder().setValueSignature(signature).build(); - } - - public static Semanticdb.Signature signature(Semanticdb.TypeSignature.Builder signature) { - return Semanticdb.Signature.newBuilder().setTypeSignature(signature).build(); - } - - // SemanticDB Symbols - - public static Semanticdb.SymbolOccurrence symbolOccurrence( - String symbol, - Semanticdb.Range range, - Semanticdb.SymbolOccurrence.Role role, - java.util.Optional enclosingRange) { - Semanticdb.SymbolOccurrence.Builder builder = - Semanticdb.SymbolOccurrence.newBuilder().setSymbol(symbol).setRange(range).setRole(role); - enclosingRange.ifPresent(builder::setEnclosingRange); - return builder.build(); - } - - public static Semanticdb.SymbolInformation.Builder symbolInformation(String symbol) { - return Semanticdb.SymbolInformation.newBuilder().setSymbol(symbol); - } - - // SemanticDB Access - - public static Semanticdb.Access privateAccess() { - return Semanticdb.Access.newBuilder() - .setPrivateAccess(Semanticdb.PrivateAccess.newBuilder()) - .build(); - } - - public static Semanticdb.Access publicAccess() { - return Semanticdb.Access.newBuilder() - .setPublicAccess(Semanticdb.PublicAccess.newBuilder()) - .build(); - } - - public static Semanticdb.Access protectedAccess() { - return Semanticdb.Access.newBuilder() - .setProtectedAccess(Semanticdb.ProtectedAccess.newBuilder()) - .build(); - } - - public static Semanticdb.Access privateWithinAccess(String symbol) { - return Semanticdb.Access.newBuilder() - .setPrivateWithinAccess(Semanticdb.PrivateWithinAccess.newBuilder().setSymbol(symbol)) - .build(); - } - - // SemanticDB Trees - - public static Semanticdb.Tree tree(Semanticdb.IdTree idTree) { - return Semanticdb.Tree.newBuilder().setIdTree(idTree).build(); - } - - public static Semanticdb.IdTree idTree(String symbol) { - return Semanticdb.IdTree.newBuilder().setSymbol(symbol).build(); - } - - public static Semanticdb.Tree tree(Semanticdb.ApplyTree applyTree) { - return Semanticdb.Tree.newBuilder().setApplyTree(applyTree).build(); - } - - public static Semanticdb.ApplyTree applyTree( - Semanticdb.Tree function, Iterable arguments) { - return Semanticdb.ApplyTree.newBuilder() - .setFunction(function) - .addAllArguments(arguments) - .build(); - } - - public static Semanticdb.Tree tree(Semanticdb.SelectTree selectTree) { - return Semanticdb.Tree.newBuilder().setSelectTree(selectTree).build(); - } - - public static Semanticdb.SelectTree selectTree( - Semanticdb.Tree qualifier, Semanticdb.IdTree idTree) { - return Semanticdb.SelectTree.newBuilder().setQualifier(qualifier).setId(idTree).build(); - } - - public static Semanticdb.Tree tree(Semanticdb.LiteralTree literalTree) { - return Semanticdb.Tree.newBuilder().setLiteralTree(literalTree).build(); - } - - public static Semanticdb.LiteralTree literalTree(Semanticdb.Constant constant) { - return Semanticdb.LiteralTree.newBuilder().setConstant(constant).build(); - } - - public static Semanticdb.Tree tree(Semanticdb.AnnotationTree annotationTree) { - return Semanticdb.Tree.newBuilder().setAnnotationTree(annotationTree).build(); - } - - public static Semanticdb.Tree tree(Semanticdb.BinaryOperatorTree binaryOperatorTree) { - return Semanticdb.Tree.newBuilder().setBinopTree(binaryOperatorTree).build(); - } - - public static Semanticdb.BinaryOperatorTree binopTree( - Semanticdb.Tree lhs, Semanticdb.BinaryOperator operator, Semanticdb.Tree rhs) { - return Semanticdb.BinaryOperatorTree.newBuilder() - .setLhs(lhs) - .setOp(operator) - .setRhs(rhs) - .build(); - } - - public static Semanticdb.Tree tree(Semanticdb.UnaryOperatorTree unaryOperatorTree) { - return Semanticdb.Tree.newBuilder().setUnaryopTree(unaryOperatorTree).build(); - } - - public static Semanticdb.Tree tree(Semanticdb.CastTree castTree) { - return Semanticdb.Tree.newBuilder().setCastTree(castTree).build(); - } - - public static Semanticdb.UnaryOperatorTree unaryOpTree( - Semanticdb.UnaryOperator operator, Semanticdb.Tree rhs) { - return Semanticdb.UnaryOperatorTree.newBuilder().setOp(operator).setTree(rhs).build(); - } - - public static Semanticdb.Tree tree(Semanticdb.AssignTree assignTree) { - return Semanticdb.Tree.newBuilder().setAssignTree(assignTree).build(); - } - - public static Semanticdb.AssignTree assignTree(Semanticdb.Tree lhs, Semanticdb.Tree rhs) { - return Semanticdb.AssignTree.newBuilder().setLhs(lhs).setRhs(rhs).build(); - } - - public static Semanticdb.CastTree castTree(Semanticdb.Type type, Semanticdb.Tree value) { - return Semanticdb.CastTree.newBuilder().setTpe(type).setValue(value).build(); - } - - public static Semanticdb.AnnotationTree annotationTree( - Semanticdb.Type type, Iterable parameters) { - return Semanticdb.AnnotationTree.newBuilder().setTpe(type).addAllParameters(parameters).build(); - } - - // SemanticDB Constants - - public static Semanticdb.Constant stringConst(String value) { - return Semanticdb.Constant.newBuilder() - .setStringConstant(Semanticdb.StringConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant doubleConst(Double value) { - return Semanticdb.Constant.newBuilder() - .setDoubleConstant(Semanticdb.DoubleConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant nullConst() { - return Semanticdb.Constant.newBuilder() - .setNullConstant(Semanticdb.NullConstant.newBuilder()) - .build(); - } - - public static Semanticdb.Constant floatConst(Float value) { - return Semanticdb.Constant.newBuilder() - .setFloatConstant(Semanticdb.FloatConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant longConst(Long value) { - return Semanticdb.Constant.newBuilder() - .setLongConstant(Semanticdb.LongConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant intConst(Integer value) { - return Semanticdb.Constant.newBuilder() - .setIntConstant(Semanticdb.IntConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant charConst(Character value) { - return Semanticdb.Constant.newBuilder() - .setCharConstant(Semanticdb.CharConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant shortConst(Short value) { - return Semanticdb.Constant.newBuilder() - .setShortConstant(Semanticdb.ShortConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant byteConst(Byte value) { - return Semanticdb.Constant.newBuilder() - .setByteConstant(Semanticdb.ByteConstant.newBuilder().setValue(value)) - .build(); - } - - public static Semanticdb.Constant booleanConst(Boolean value) { - return Semanticdb.Constant.newBuilder() - .setBooleanConstant(Semanticdb.BooleanConstant.newBuilder().setValue(value)) - .build(); - } -} diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbDocumentBuilder.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbDocumentBuilder.java deleted file mode 100644 index e9d5c6be6..000000000 --- a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbDocumentBuilder.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.sourcegraph.semanticdb; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Accumulator for {@link Semanticdb.SymbolOccurrence}/{@link Semanticdb.SymbolInformation} that - * assembles a final {@link Semanticdb.TextDocument}. First emission wins: occurrences are - * deduplicated by {@code (range, symbol, role)}, symbols by {@code symbol}; occurrences are sorted - * by start position. Contains no compiler-API dependencies. - */ -public final class SemanticdbDocumentBuilder { - private static final Comparator OCCURRENCE_ORDER = - Comparator.comparingInt(o -> o.getRange().getStartLine()) - .thenComparingInt(o -> o.getRange().getStartCharacter()); - - private final Map occurrences = new LinkedHashMap<>(); - private final Map symbols = new LinkedHashMap<>(); - - public void addOccurrence(Semanticdb.SymbolOccurrence occurrence) { - occurrences.putIfAbsent(new OccurrenceKey(occurrence), occurrence); - } - - public void addSymbol(Semanticdb.SymbolInformation symbol) { - symbols.putIfAbsent(symbol.getSymbol(), symbol); - } - - public Semanticdb.TextDocument build( - Semanticdb.Language language, String uri, String text, String md5) { - List sortedOccurrences = new ArrayList<>(occurrences.values()); - sortedOccurrences.sort(OCCURRENCE_ORDER); - return Semanticdb.TextDocument.newBuilder() - .setSchema(Semanticdb.Schema.SEMANTICDB4) - .setLanguage(language) - .setUri(uri) - .setText(text) - .setMd5(md5) - .addAllOccurrences(sortedOccurrences) - .addAllSymbols(symbols.values()) - .build(); - } - - private static final class OccurrenceKey { - private final Semanticdb.Range range; - private final String symbol; - private final Semanticdb.SymbolOccurrence.Role role; - - OccurrenceKey(Semanticdb.SymbolOccurrence occurrence) { - this.range = occurrence.hasRange() ? occurrence.getRange() : null; - this.symbol = occurrence.getSymbol(); - this.role = occurrence.getRole(); - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (!(other instanceof OccurrenceKey)) return false; - OccurrenceKey that = (OccurrenceKey) other; - return role == that.role - && Objects.equals(range, that.range) - && Objects.equals(symbol, that.symbol); - } - - @Override - public int hashCode() { - return Objects.hash(range, symbol, role); - } - } -} diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbSymbols.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbSymbols.java index 8f1ec4f3f..c88d5932e 100644 --- a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbSymbols.java +++ b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbSymbols.java @@ -21,13 +21,16 @@ public static String global(String owner, Descriptor desc) { else return desc.encode(); } - /** Creates a new local SemanticDB symbol. */ + /** + * Creates a new local SCIP symbol. The space matches the SCIP wire format and lets {@link + * #isLocal} disambiguate from a bare descriptor that happens to start with {@code local}. + */ public static String local(int suffix) { - return "local" + suffix; + return "local " + suffix; } public static boolean isLocal(String symbol) { - return symbol.startsWith("local"); + return symbol.startsWith("local "); } public static boolean isGlobal(String symbol) { diff --git a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbWriter.java b/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbWriter.java deleted file mode 100644 index 691252695..000000000 --- a/semanticdb-shared/src/main/java/com/sourcegraph/semanticdb/SemanticdbWriter.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sourcegraph.semanticdb; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -/** Shared helper for serializing a single SemanticDB {@link Semanticdb.TextDocument} to disk. */ -public final class SemanticdbWriter { - private SemanticdbWriter() {} - - /** - * Wraps {@code document} in a singleton {@link Semanticdb.TextDocuments} message and writes it to - * {@code output}, creating parent directories as needed. - */ - public static void writeTextDocument(Path output, Semanticdb.TextDocument document) - throws IOException { - byte[] bytes = - Semanticdb.TextDocuments.newBuilder().addDocuments(document).build().toByteArray(); - Files.createDirectories(output.getParent()); - Files.write(output, bytes); - } -} diff --git a/semanticdb-shared/src/main/protobuf/semanticdb.proto b/semanticdb-shared/src/main/protobuf/semanticdb.proto deleted file mode 100644 index bfc289c5e..000000000 --- a/semanticdb-shared/src/main/protobuf/semanticdb.proto +++ /dev/null @@ -1,473 +0,0 @@ -// Original source: https://github.com/scalameta/scalameta/blob/cf796cf2436b40494baf2bdc266623dc65264ad5/semanticdb/semanticdb/semanticdb.proto -// Local modifications: -// - Removes unused fields to minimize the amount of generated code. -// - Adds SymbolInformation.documentation that is pending upstream approval. -// - Adds SymbolOccurrence.enclosing_range to support SCIP's enclosing_range field. - -syntax = "proto3"; - -package com.sourcegraph.semanticdb; - -option java_package = "com.sourcegraph.semanticdb"; -option java_multiple_files = false; -option java_outer_classname = "Semanticdb"; - -enum Schema { - LEGACY = 0; - SEMANTICDB3 = 3; - SEMANTICDB4 = 4; -} - -message TextDocuments { - repeated TextDocument documents = 1; -} - -message TextDocument { - reserved 4, 8, 9; - Schema schema = 1; - string uri = 2; - string text = 3; - string md5 = 11; - Language language = 10; - repeated SymbolInformation symbols = 5; - repeated SymbolOccurrence occurrences = 6; - repeated Synthetic synthetics = 12; -} - -enum Language { - UNKNOWN_LANGUAGE = 0; - SCALA = 1; - JAVA = 2; - KOTLIN = 3; -} - -message Range { - int32 start_line = 1; - int32 start_character = 2; - int32 end_line = 3; - int32 end_character = 4; -} - -message Signature { - oneof sealed_value { - ClassSignature class_signature = 1; - MethodSignature method_signature = 2; - TypeSignature type_signature = 3; - ValueSignature value_signature = 4; - } -} - -message ClassSignature { - Scope type_parameters = 1; - repeated Type parents = 2; - Scope declarations = 4; -} - -message MethodSignature { - Scope type_parameters = 1; - repeated Scope parameter_lists = 2; - Type return_type = 3; - repeated Type throws = 4; -} - -message TypeSignature { - Scope type_parameters = 1; - Type lower_bound = 2; - Type upper_bound = 3; -} - -message ValueSignature { - Type tpe = 1; -} - -message SymbolInformation { - enum Kind { - reserved 1, 2, 4, 5, 15, 16; - UNKNOWN_KIND = 0; - LOCAL = 19; - FIELD = 20; - METHOD = 3; - CONSTRUCTOR = 21; - MACRO = 6; - TYPE = 7; - PARAMETER = 8; - SELF_PARAMETER = 17; - TYPE_PARAMETER = 9; - OBJECT = 10; - PACKAGE = 11; - PACKAGE_OBJECT = 12; - CLASS = 13; - TRAIT = 14; - INTERFACE = 18; - } - enum Property { - UNKNOWN_PROPERTY = 0; - reserved 0x1; - reserved 0x2; - ABSTRACT = 0x4; - FINAL = 0x8; - SEALED = 0x10; - IMPLICIT = 0x20; - LAZY = 0x40; - CASE = 0x80; - COVARIANT = 0x100; - CONTRAVARIANT = 0x200; - VAL = 0x400; - VAR = 0x800; - STATIC = 0x1000; - PRIMARY = 0x2000; - ENUM = 0x4000; - DEFAULT = 0x8000; - } - reserved 2, 6, 7, 8, 9, 10, 11, 12, 14; - string symbol = 1; - Language language = 16; - Kind kind = 3; - int32 properties = 4; - string display_name = 5; - // -- OUT OF SPEC -- // - repeated AnnotationTree annotations = 13; - // -- OUT OF SPEC -- // - Signature signature = 17; - Access access = 18; - repeated string overridden_symbols = 19; - Documentation documentation = 20; - string enclosing_symbol = 15; - - // -- OUT OF SPEC -- // - repeated string definition_relationships = 21; - // -- OUT OF SPEC -- // -} - -message Access { - oneof sealed_value { - PrivateAccess private_access = 1; - PrivateThisAccess private_this_access = 2; - PrivateWithinAccess private_within_access = 3; - ProtectedAccess protected_access = 4; - PublicAccess public_access = 7; - } -} - -message PrivateAccess {} - -message PrivateWithinAccess { - string symbol = 1; -} - -message PrivateThisAccess { -} - -message ProtectedAccess {} - -message PublicAccess {} - -message Documentation { - enum Format { - HTML = 0; - MARKDOWN = 1; - JAVADOC = 2; - SCALADOC = 3; - KDOC = 4; - } - string message = 1; - Format format = 2; -} - -message SymbolOccurrence { - enum Role { - UNKNOWN_ROLE = 0; - REFERENCE = 1; - DEFINITION = 2; - // NOTE: this role does not exist in the upstream SemanticDB spec. - // WE added SYNTHETIC_DEFINITION fix the following scip-java issue: - // https://github.com/sourcegraph/scip-java/issues/399 - SYNTHETIC_DEFINITION = 3; - } - Range range = 1; - string symbol = 2; - Role role = 3; - // NOTE: this field does not exist in the upstream SemanticDB spec. - // It is added to support SCIP's enclosing_range field. - // This is the range of the nearest non-trivial enclosing AST node. - optional Range enclosing_range = 4; -} - -message Scope { - repeated string symlinks = 1; - repeated SymbolInformation hardlinks = 2; -} - -message Type { - reserved 1, 3, 4, 5, 6, 11, 12, 15, 16; - oneof sealed_value { - TypeRef type_ref = 2; - SingleType single_type = 20; - ThisType this_type = 21; - SuperType super_type = 22; - ConstantType constant_type = 23; - IntersectionType intersection_type = 17; - UnionType union_type = 18; - WithType with_type = 19; - StructuralType structural_type = 7; - AnnotatedType annotated_type = 8; - ExistentialType existential_type = 9; - UniversalType universal_type = 10; - ByNameType by_name_type = 13; - RepeatedType repeated_type = 14; - } -} - - -message TypeRef { - Type prefix = 1; - string symbol = 2; - repeated Type type_arguments = 3; -} - -message SingleType { - Type prefix = 1; - string symbol = 2; -} - -message ThisType { - string symbol = 1; -} - -message SuperType { - Type prefix = 1; - string symbol = 2; -} - -message ConstantType { - Constant constant = 1; -} - -message IntersectionType { - repeated Type types = 1; -} - -message UnionType { - repeated Type types = 1; -} - -message WithType { - repeated Type types = 1; -} - -message StructuralType { - reserved 1, 2, 3; - Type tpe = 4; - Scope declarations = 5; -} - -message AnnotatedType { - reserved 2; - repeated AnnotationTree annotations = 3; - Type tpe = 1; -} - -message ExistentialType { - reserved 2; - Type tpe = 1; - Scope declarations = 3; -} - -message UniversalType { - reserved 1; - Scope type_parameters = 3; - Type tpe = 2; -} - -message ByNameType { - Type tpe = 1; -} - -message RepeatedType { - Type tpe = 1; -} - -message Synthetic { - Range range = 1; - Tree tree = 2; -} - -message Tree { - oneof sealed_value { - ApplyTree apply_tree = 1; - FunctionTree function_tree = 2; - IdTree id_tree = 3; - LiteralTree literal_tree = 4; - MacroExpansionTree macro_expansion_tree = 5; - OriginalTree original_tree = 6; - SelectTree select_tree = 7; - TypeApplyTree type_apply_tree = 8; - // -- OUT OF SPEC -- // - AnnotationTree annotation_tree = 9; - AssignTree assign_tree = 10; - BinaryOperatorTree binop_tree = 11; - UnaryOperatorTree unaryop_tree = 12; - CastTree cast_tree = 13; - // -- OUT OF SPEC -- // - } -} - -message ApplyTree { - Tree function = 1; - repeated Tree arguments = 2; -} - - -message FunctionTree { - repeated IdTree parameters = 1; - Tree body = 2; -} - -message IdTree { - string symbol = 1; -} - - -message LiteralTree { - Constant constant = 1; -} - -message MacroExpansionTree { - Tree before_expansion = 1; - Type tpe = 2; -} - -message OriginalTree { - Range range = 1; -} - -message SelectTree { - Tree qualifier = 1; - IdTree id = 2; -} - -message TypeApplyTree { - Tree function = 1; - repeated Type type_arguments = 2; -} - -// -- OUT OF SPEC -- // -message AnnotationTree { - Type tpe = 1; - repeated Tree parameters = 2; -} - - -message CastTree { - Type tpe = 1; - Tree value = 2; -} - -message AssignTree { - Tree lhs = 1; - Tree rhs = 2; -} - -message BinaryOperatorTree { - Tree lhs = 1; - BinaryOperator op = 2; - Tree rhs = 3; -} - -enum BinaryOperator { - PLUS = 0; - MINUS = 1; - MULTIPLY = 2; - DIVIDE = 3; - REMAINDER = 4; - GREATER_THAN = 5; - LESS_THAN = 6; - AND = 7; - XOR = 8; - OR = 9; - CONDITIONAL_AND = 10; - CONDITIONAL_OR = 11; - SHIFT_LEFT = 12; - SHIFT_RIGHT = 13; - SHIFT_RIGHT_UNSIGNED = 14; - EQUAL_TO = 15; - NOT_EQUAL_TO = 16; - GREATER_THAN_EQUAL = 17; - LESS_THAN_EQUAL = 18; -} - -// https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.15 -message UnaryOperatorTree { - UnaryOperator op = 1; - Tree tree = 2; -} - -enum UnaryOperator { - UNARY_MINUS = 0; - UNARY_PLUS = 1; - UNARY_POSTFIX_INCREMENT = 2; - UNARY_POSTFIX_DECREMENT = 3; - UNARY_PREFIX_DECREMENT = 4; - UNARY_PREFIX_INCREMENT = 5; - UNARY_BITWISE_COMPLEMENT = 6; - UNARY_LOGICAL_COMPLEMENT = 7; -} -// -- OUT OF SPEC -- // - -message Constant { - oneof sealed_value { - UnitConstant unit_constant = 1; - BooleanConstant boolean_constant = 2; - ByteConstant byte_constant = 3; - ShortConstant short_constant = 4; - CharConstant char_constant = 5; - IntConstant int_constant = 6; - LongConstant long_constant = 7; - FloatConstant float_constant = 8; - DoubleConstant double_constant = 9; - StringConstant string_constant = 10; - NullConstant null_constant = 11; - } -} - -message UnitConstant { -} - -message BooleanConstant { - bool value = 1; -} - -message ByteConstant { - int32 value = 1; -} - -message ShortConstant { - int32 value = 1; -} - -message CharConstant { - int32 value = 1; -} - -message IntConstant { - int32 value = 1; -} - -message LongConstant { - int64 value = 1; -} - -message FloatConstant { - float value = 1; -} - -message DoubleConstant { - double value = 1; -} - -message StringConstant { - string value = 1; -} - -message NullConstant { -} diff --git a/tests/buildTools/src/test/scala/tests/BaseBuildToolSuite.scala b/tests/buildTools/src/test/scala/tests/BaseBuildToolSuite.scala index 31aca108a..d7df9aae3 100644 --- a/tests/buildTools/src/test/scala/tests/BaseBuildToolSuite.scala +++ b/tests/buildTools/src/test/scala/tests/BaseBuildToolSuite.scala @@ -54,9 +54,9 @@ abstract class BaseBuildToolSuite extends MopedSuite(ScipJava.app) { } } - private val semanticdbPattern = FileSystems + private val scipShardPattern = FileSystems .getDefault - .getPathMatcher("glob:**.semanticdb") + .getPathMatcher("glob:**.scip") def checkBuild( options: TestOptions, @@ -120,17 +120,17 @@ abstract class BaseBuildToolSuite extends MopedSuite(ScipJava.app) { case None => assertEquals(exit, 0, clues(app.capturedOutput)) } - val semanticdbFiles = + val scipShardFiles = if (!Files.isDirectory(targetroot)) Nil else FileIO .listAllFilesRecursively(AbsolutePath(targetroot)) - .filter(p => semanticdbPattern.matches(p.toNIO)) - if (semanticdbFiles.length != expectedSemanticdbFiles) { + .filter(p => scipShardPattern.matches(p.toNIO)) + if (scipShardFiles.length != expectedSemanticdbFiles) { fail( - s"Expected $expectedSemanticdbFiles SemanticDB file(s) to be generated.", - clues(semanticdbFiles, app.capturedOutput) + s"Expected $expectedSemanticdbFiles SCIP shard(s) to be generated.", + clues(scipShardFiles, app.capturedOutput) ) } if (expectedPackages.nonEmpty) { diff --git a/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala b/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala index 12350ee22..7cca65f28 100644 --- a/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala +++ b/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala @@ -33,8 +33,8 @@ abstract class GradleBuildToolSuite(gradle: Tool.Gradle) """.stripMargin, /* An immutable version will be generated along with the original class: - - build/generated/sources/annotationProcessor/java/main/test/ImmutableWorkflowOptions.java.semanticdb - - /META-INF/semanticdb/src/main/java/WorkflowOptions.java.semanticdb + - build/generated/sources/annotationProcessor/java/main/test/ImmutableWorkflowOptions.java.scip + - /META-INF/scip/src/main/java/WorkflowOptions.java.scip */ expectedSemanticdbFiles = 2 ) diff --git a/tests/minimized/src/main/java/minimized/EnumImplementsInterface.java b/tests/minimized/src/main/java/minimized/EnumImplementsInterface.java new file mode 100644 index 000000000..efd29dde0 --- /dev/null +++ b/tests/minimized/src/main/java/minimized/EnumImplementsInterface.java @@ -0,0 +1,8 @@ +package minimized; + +import java.io.Serializable; + +enum EnumImplementsInterface implements Serializable { + A, + B +} diff --git a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/AnnotationsOnParameterizedTypes.java b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/AnnotationsOnParameterizedTypes.java index 926380b42..7026d6e9b 100644 --- a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/AnnotationsOnParameterizedTypes.java +++ b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/AnnotationsOnParameterizedTypes.java @@ -92,7 +92,7 @@ public static AnnotationsOnParameterizedTypes getInstance() { class AnnotationsOnParameterizedTypesImpl implements AnnotationsOnParameterizedTypes { // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl# // display_name AnnotationsOnParameterizedTypesImpl -// signature_documentation java class AnnotationsOnParameterizedTypesImpl +// signature_documentation java class AnnotationsOnParameterizedTypesImpl implements AnnotationsOnParameterizedTypes // kind Class // relationship is_implementation semanticdb maven . . minimized/AnnotationsOnParameterizedTypes# // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#``(). @@ -208,7 +208,7 @@ private Function getConstructor(Class contract) { // ^ reference semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor().[T] // ^^^^^^^^^^^ definition local 6 // display_name constructor -// signature_documentation java @SuppressWarnings\nConstructor constructor +// signature_documentation java @SuppressWarnings("unchecked")\nConstructor constructor // enclosing_symbol semanticdb maven . . minimized/AnnotationsOnParameterizedTypesImpl#getConstructor(). // kind Variable // ^^^^^^^^^^^ reference semanticdb maven jdk 11 java/lang/reflect/Constructor# diff --git a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/EnumImplementsInterface.java b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/EnumImplementsInterface.java new file mode 100644 index 000000000..27cd80af6 --- /dev/null +++ b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/EnumImplementsInterface.java @@ -0,0 +1,37 @@ + package minimized; + + import java.io.Serializable; +// ^^^^ reference semanticdb maven . . java/ +// ^^ reference semanticdb maven . . java/io/ +// ^^^^^^^^^^^^ reference semanticdb maven jdk 11 java/io/Serializable# + +//⌄ enclosing_range_start semanticdb maven . . minimized/EnumImplementsInterface# + enum EnumImplementsInterface implements Serializable { +// ^^^^^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . minimized/EnumImplementsInterface# +// display_name EnumImplementsInterface +// signature_documentation java enum EnumImplementsInterface implements Serializable +// kind Enum +// relationship is_implementation semanticdb maven jdk 11 java/io/Serializable# +// relationship is_implementation semanticdb maven jdk 11 java/lang/Comparable# +// relationship is_implementation semanticdb maven jdk 11 java/lang/Enum# +// ^^^^^^^^^^^^^^^^^^^^^^^ definition semanticdb maven . . minimized/EnumImplementsInterface#``(). +// display_name +// signature_documentation java private EnumImplementsInterface() +// kind Constructor +// ^^^^^^^^^^^^ reference semanticdb maven jdk 11 java/io/Serializable# +// ⌄ enclosing_range_start semanticdb maven . . minimized/EnumImplementsInterface#A. + A, +// ^ definition semanticdb maven . . minimized/EnumImplementsInterface#A. +// display_name A +// signature_documentation java EnumImplementsInterface.A /* ordinal 0 */ +// ^ reference semanticdb maven . . minimized/EnumImplementsInterface#``(). +// ⌃ enclosing_range_end semanticdb maven . . minimized/EnumImplementsInterface#A. +// ⌄ enclosing_range_start semanticdb maven . . minimized/EnumImplementsInterface#B. + B +// ^ definition semanticdb maven . . minimized/EnumImplementsInterface#B. +// display_name B +// signature_documentation java EnumImplementsInterface.B /* ordinal 1 */ +// ^ reference semanticdb maven . . minimized/EnumImplementsInterface#``(). +// ⌃ enclosing_range_end semanticdb maven . . minimized/EnumImplementsInterface#B. + } +//⌃ enclosing_range_end semanticdb maven . . minimized/EnumImplementsInterface# diff --git a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/InnerClasses.java b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/InnerClasses.java index 90779baed..b622416de 100644 --- a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/InnerClasses.java +++ b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/InnerClasses.java @@ -180,7 +180,7 @@ public static void innerStaticMethod() {} public class InnerClass implements InnerInterface { // ^^^^^^^^^^ definition semanticdb maven . . minimized/InnerClasses#InnerClass# // display_name InnerClass -// signature_documentation java public class InnerClass +// signature_documentation java public class InnerClass implements InnerInterface // kind Class // relationship is_implementation semanticdb maven . . minimized/InnerClasses#InnerInterface# // ^^^^^^^^^^^^^^ reference semanticdb maven . . minimized/InnerClasses#InnerInterface# diff --git a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/LombokBuilder.java b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/LombokBuilder.java index 8bb0cff51..80c75b809 100644 --- a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/LombokBuilder.java +++ b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/LombokBuilder.java @@ -1,10 +1,10 @@ package minimized; -//⌄ enclosing_range_start semanticdb maven . . minimized/Hello#HelloBuilder#build(). -//⌄ enclosing_range_start semanticdb maven . . minimized/Hello#builder(). -//⌄ enclosing_range_start semanticdb maven . . minimized/Hello#HelloBuilder#toString(). //⌄ enclosing_range_start semanticdb maven . . minimized/Hello#HelloBuilder# //⌄ enclosing_range_start semanticdb maven . . minimized/Hello#HelloBuilder#``(). +//⌄ enclosing_range_start semanticdb maven . . minimized/Hello#HelloBuilder#build(). +//⌄ enclosing_range_start semanticdb maven . . minimized/Hello#HelloBuilder#toString(). +//⌄ enclosing_range_start semanticdb maven . . minimized/Hello#builder(). //⌄ enclosing_range_start semanticdb maven . . minimized/Hello# //⌄ enclosing_range_start semanticdb maven . . minimized/Hello#``(). //⌄ enclosing_range_start local 0 @@ -48,11 +48,11 @@ // signature_documentation java @SuppressWarnings("all")\nHelloBuilder() // kind Constructor // reference semanticdb maven . . minimized/Hello#HelloBuilder#``(). 1:11 -// ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder#build(). -// ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#builder(). -// ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder#toString(). // ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder# // ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder#``(). +// ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder#build(). +// ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder#toString(). +// ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#builder(). // ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#``(). // ⌃ enclosing_range_end local 0 // ⌃ enclosing_range_end semanticdb maven . . minimized/Hello#HelloBuilder#message. diff --git a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/SubClasses.java b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/SubClasses.java index 4e99f292f..b701b2fc3 100644 --- a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/SubClasses.java +++ b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/SubClasses.java @@ -4,7 +4,7 @@ public class SubClasses extends AbstractClasses implements Interfaces { // ^^^^^^^^^^ definition semanticdb maven . . minimized/SubClasses# // display_name SubClasses -// signature_documentation java public class SubClasses +// signature_documentation java public class SubClasses extends AbstractClasses implements Interfaces // kind Class // relationship is_implementation semanticdb maven . . minimized/AbstractClasses# // relationship is_implementation semanticdb maven . . minimized/Interfaces# diff --git a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/TypeVariables.java b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/TypeVariables.java index bda109668..cf7ae9434 100644 --- a/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/TypeVariables.java +++ b/tests/snapshots/src/main/generated/tests/minimized/src/main/java/minimized/TypeVariables.java @@ -69,7 +69,7 @@ interface I { static class CT extends C implements I { // ^^ definition semanticdb maven . . minimized/TypeVariables#CT# // display_name CT -// signature_documentation java static class CT +// signature_documentation java static class CT extends C implements I // kind Class // relationship is_implementation semanticdb maven . . minimized/TypeVariables#C# // relationship is_implementation semanticdb maven . . minimized/TypeVariables#I# diff --git a/tests/snapshots/src/main/scala/tests/SemanticdbFile.scala b/tests/snapshots/src/main/scala/tests/SemanticdbFile.scala deleted file mode 100644 index c73c4c919..000000000 --- a/tests/snapshots/src/main/scala/tests/SemanticdbFile.scala +++ /dev/null @@ -1,49 +0,0 @@ -package tests - -import java.nio.file.Files - -import scala.meta.internal.io.FileIO -import scala.meta.io.AbsolutePath -import scala.meta.io.RelativePath - -import com.sourcegraph.semanticdb.Semanticdb.TextDocument -import com.sourcegraph.semanticdb.Semanticdb.TextDocuments - -case class SemanticdbFile( - sourceroot: AbsolutePath, - relativePath: RelativePath, - sourceDirectory: AbsolutePath, - targetroot: AbsolutePath -) { - def javaPath: AbsolutePath = sourceroot.resolve(relativePath) - def semanticdbPath: AbsolutePath = targetroot - .resolve("META-INF") - .resolve("semanticdb") - .resolve(relativePath.toString() + ".semanticdb") - def textDocument: TextDocument = { - val docs = TextDocuments.parseFrom(Files.readAllBytes(semanticdbPath.toNIO)) - if (docs.getDocumentsCount == 0) - TextDocument.newBuilder().build() - else - docs.getDocuments(0) - } -} - -object SemanticdbFile { - def fromDirectory( - sourceDirectory: AbsolutePath, - sourceroot: AbsolutePath, - targetroot: AbsolutePath - ): Seq[SemanticdbFile] = { - FileIO - .listAllFilesRecursively(sourceDirectory) - .map { file => - SemanticdbFile( - sourceroot, - file.toRelative(sourceroot), - sourceDirectory, - targetroot - ) - } - } -} diff --git a/tests/unit/src/main/scala/tests/CompileResult.scala b/tests/unit/src/main/scala/tests/CompileResult.scala index 21e76f470..6ddda14b6 100644 --- a/tests/unit/src/main/scala/tests/CompileResult.scala +++ b/tests/unit/src/main/scala/tests/CompileResult.scala @@ -1,38 +1,40 @@ package tests -import com.sourcegraph.semanticdb.Semanticdb +import org.scip_code.scip.Document +import org.scip_code.scip.Index case class CompileResult( byteCode: Array[Byte], stdout: String, - textDocuments: Semanticdb.TextDocuments, + documents: Seq[Document], isSuccess: Boolean ) { - def textDocument: Option[Semanticdb.TextDocument] = { - Option.when(textDocuments.getDocumentsCount() > 0) { - textDocuments.getDocuments(0) - } - } + def document: Option[Document] = documents.headOption - def merge(other: CompileResult): CompileResult = { - copy( - byteCode = this.byteCode ++ other.byteCode, - stdout = this.stdout ++ other.stdout, - textDocuments = this - .textDocuments - .toBuilder - .addAllDocuments(other.textDocuments.getDocumentsList) - .build(), - isSuccess = this.isSuccess && other.isSuccess - ) - } + def merge(other: CompileResult): CompileResult = copy( + byteCode = this.byteCode ++ other.byteCode, + stdout = this.stdout ++ other.stdout, + documents = this.documents ++ other.documents, + isSuccess = this.isSuccess && other.isSuccess + ) } object CompileResult { val empty: CompileResult = CompileResult( Array.emptyByteArray, "", - Semanticdb.TextDocuments.getDefaultInstance, + Seq.empty, isSuccess = true ) + + /** + * Convenience: builds an empty SCIP {@link Index} from this result's + * documents. + */ + def index(documents: Seq[Document]): Index = Index + .newBuilder() + .addAllDocuments( + scala.jdk.CollectionConverters.SeqHasAsJava(documents).asJava + ) + .build() } diff --git a/tests/unit/src/main/scala/tests/TestCompiler.scala b/tests/unit/src/main/scala/tests/TestCompiler.scala index a4ea44db7..13f9bed23 100644 --- a/tests/unit/src/main/scala/tests/TestCompiler.scala +++ b/tests/unit/src/main/scala/tests/TestCompiler.scala @@ -13,7 +13,7 @@ import scala.meta.Input import scala.meta.internal.io.FileIO import scala.meta.io.AbsolutePath -import com.sourcegraph.semanticdb.Semanticdb +import org.scip_code.scip.Index object TestCompiler { val PROCESSOR_PATH = System.getProperty("java.class.path") @@ -23,7 +23,7 @@ class TestCompiler( val classpath: String, val javacOptions: List[String], val targetroot: Path, - val sourceroot: Path = Files.createTempDirectory("semanticdb-javac") + val sourceroot: Path = Files.createTempDirectory("scip-javac") ) { private val compiler = ToolProvider.getSystemJavaCompiler @@ -37,11 +37,11 @@ class TestCompiler( this(TestCompiler.PROCESSOR_PATH, Nil, targetroot) } - def compileSemanticdbDirectory(dir: Path): CompileResult = { - compileSemanticdb(inputsFromDirectory(dir)) - } + def compileSemanticdbDirectory(dir: Path): CompileResult = compileSemanticdb( + inputsFromDirectory(dir) + ) - def compileSemanticdb(inputs: Seq[Input.VirtualFile]): CompileResult = { + def compileSemanticdb(inputs: Seq[Input.VirtualFile]): CompileResult = compile( inputs, List( @@ -52,7 +52,6 @@ class TestCompiler( ) ) ) - } def compile( inputs: Seq[Input.VirtualFile], @@ -60,9 +59,8 @@ class TestCompiler( ): CompileResult = { val javacInputs = inputs.filter(_.path.endsWith(".java")) val results = ListBuffer.empty[CompileResult] - if (javacInputs.nonEmpty) { + if (javacInputs.nonEmpty) results += compileJavac(javacInputs, extraJavacOptions) - } results.foldLeft(CompileResult.empty)(_ merge _) } @@ -94,23 +92,19 @@ class TestCompiler( var bytecode = new Array[Byte](0) if (!fileManager.compiled.isEmpty) bytecode = fileManager.compiled.iterator.next.getCompiledBinaries - val textDocuments = Semanticdb.TextDocuments.newBuilder - inputs.map { input => - val outputPath = targetroot + val docs = ListBuffer.empty[org.scip_code.scip.Document] + inputs.foreach { input => + val shardPath = targetroot .resolve("META-INF") - .resolve("semanticdb") - .resolve(input.path + ".semanticdb") - if (Files.isRegularFile(outputPath)) { - textDocuments.addAllDocuments( - Semanticdb - .TextDocuments - .parseFrom(Files.readAllBytes(outputPath)) - .getDocumentsList - ) + .resolve("scip") + .resolve(input.path + ".scip") + if (Files.isRegularFile(shardPath)) { + val shard = Index.parseFrom(Files.readAllBytes(shardPath)) + docs ++= shard.getDocumentsList.asScala } } val stdout = output.toString - CompileResult(bytecode, stdout, textDocuments.build(), isSuccess) + CompileResult(bytecode, stdout, docs.toSeq, isSuccess) } private def inputsFromDirectory(dir: Path): Seq[Input.VirtualFile] = { diff --git a/tests/unit/src/test/scala/tests/GeneratedConstructorSuite.scala b/tests/unit/src/test/scala/tests/GeneratedConstructorSuite.scala index d371838e5..408bd4491 100644 --- a/tests/unit/src/test/scala/tests/GeneratedConstructorSuite.scala +++ b/tests/unit/src/test/scala/tests/GeneratedConstructorSuite.scala @@ -2,9 +2,9 @@ package tests import scala.meta.inputs.Input -import com.sourcegraph.semanticdb.Semanticdb.TextDocument import munit.FunSuite import munit.TestOptions +import org.scip_code.scip.Document class GeneratedConstructorSuite extends FunSuite with TempDirectories { val targetroot = new DirectoryFixture() @@ -15,16 +15,15 @@ class GeneratedConstructorSuite extends FunSuite with TempDirectories { def doSomething( options: TestOptions, original: String, - fn: (TextDocument, List[String]) => Unit, + fn: (Document, List[String]) => Unit, qualifiedClassName: String = "example.Test" - )(implicit loc: munit.Location): Unit = { + )(implicit loc: munit.Location): Unit = test(options) { val groups = "<<([0-9a-zA-Z]+)>>".r val compiler = new TestCompiler(targetroot()) val trimmedText = groups.replaceAllIn(original, "$1") val relativePath = qualifiedClassName.replace('.', '/') + ".java" val input = Input.VirtualFile(relativePath, trimmedText) - + val _ = (compiler, input, fn) } - } } diff --git a/tests/unit/src/test/scala/tests/JavacClassesDirectorySuite.scala b/tests/unit/src/test/scala/tests/JavacClassesDirectorySuite.scala index 4a3d51155..6674ba015 100644 --- a/tests/unit/src/test/scala/tests/JavacClassesDirectorySuite.scala +++ b/tests/unit/src/test/scala/tests/JavacClassesDirectorySuite.scala @@ -36,12 +36,12 @@ class JavacClassesDirectorySuite extends FunSuite with TempDirectories { ) ) assert(clue(compileResult).isSuccess) - val semanticdbPath = Paths + val shardPath = Paths .get("META-INF") - .resolve("semanticdb") + .resolve("scip") .resolve("example") - .resolve("Example.java.semanticdb") - assert(Files.isRegularFile(clue(sourceroot().resolve(semanticdbPath)))) + .resolve("Example.java.scip") + assert(Files.isRegularFile(clue(sourceroot().resolve(shardPath)))) } } diff --git a/tests/unit/src/test/scala/tests/OverridesSuite.scala b/tests/unit/src/test/scala/tests/OverridesSuite.scala index 15b069aff..3e6042151 100644 --- a/tests/unit/src/test/scala/tests/OverridesSuite.scala +++ b/tests/unit/src/test/scala/tests/OverridesSuite.scala @@ -1,10 +1,9 @@ package tests -import java.util.stream.Collectors +import scala.jdk.CollectionConverters._ import scala.meta.Input -import com.sourcegraph.scip_semanticdb.Symtab import munit.FunSuite import munit.TestOptions @@ -20,24 +19,31 @@ class OverridesSuite extends FunSuite with TempDirectories { source: String, extractSymbol: String, expectedSymbols: String* - ): Unit = { + ): Unit = test(options) { val compiler = new TestCompiler(targetroot()) val relativePath = "example.Parent".replace('.', '/') + ".java" val input = Input.VirtualFile(relativePath, source) val result = compiler.compileSemanticdb(List(input)) - val symtab = new Symtab(result.textDocument.orNull) - - val expectedSyms = expectedSymbols.mkString("\n") - val syms = symtab - .symbols - .get(extractSymbol) - .getOverriddenSymbolsList - .stream - .collect(Collectors.joining("\n")) - assertNoDiff(syms, expectedSyms) + val document = result.document.orNull + val info = document + .getSymbolsList + .asScala + .find(_.getSymbol == extractSymbol) + .getOrElse( + fail( + s"no SymbolInformation for $extractSymbol", + clues(document.getSymbolsList.asScala.map(_.getSymbol)) + ) + ) + val implementations = info + .getRelationshipsList + .asScala + .filter(_.getIsImplementation) + .map(_.getSymbol) + .mkString("\n") + assertNoDiff(implementations, expectedSymbols.mkString("\n")) } - } checkOverrides( "same file", diff --git a/tests/unit/src/test/scala/tests/TargetedSuite.scala b/tests/unit/src/test/scala/tests/TargetedSuite.scala index 284804738..c54600b7e 100644 --- a/tests/unit/src/test/scala/tests/TargetedSuite.scala +++ b/tests/unit/src/test/scala/tests/TargetedSuite.scala @@ -7,10 +7,9 @@ import scala.meta.Input import scala.meta.Position import scala.meta.internal.inputs._ -import com.sourcegraph.semanticdb.Semanticdb -import com.sourcegraph.semanticdb.Semanticdb.TextDocument import munit.FunSuite import munit.TestOptions +import org.scip_code.scip.Document @nowarn("msg=match may not be exhaustive") class TargetedSuite extends FunSuite with TempDirectories { @@ -23,7 +22,7 @@ class TargetedSuite extends FunSuite with TempDirectories { def checkDoc( options: TestOptions, original: String, - fn: (TextDocument, List[String]) => Unit, + fn: (Document, List[String]) => Unit, qualifiedClassName: String = "example.Test" )(implicit loc: munit.Location): Unit = { test(options) { @@ -44,18 +43,13 @@ class TargetedSuite extends FunSuite with TempDirectories { }) .toList val result = compiler.compileSemanticdb(List(input)) - val textDocument = result.textDocument.orNull - val occurrences = textDocument.getOccurrencesList.asScala.toList + val document = result.document.orNull + val occurrences = document.getOccurrencesList.asScala.toList val symbols: List[String] = positions.map { pos => - val posRange = Semanticdb - .Range - .newBuilder() - .setStartLine(pos.startLine) - .setStartCharacter(pos.startColumn) - .setEndLine(pos.endLine) - .setEndCharacter(pos.endColumn) - .build() - val matchingOccurrences = occurrences.filter(_.getRange == posRange) + val expected = rangeOf(pos) + val matchingOccurrences = occurrences.filter(occ => + occ.getRangeList.asScala.toList == expected + ) matchingOccurrences match { case Nil => fail( @@ -63,7 +57,7 @@ class TargetedSuite extends FunSuite with TempDirectories { "error", s"no symbol occurrence for this position." ), - clues(occurrences, posRange) + clues(occurrences, expected) ) case sym :: Nil => sym.getSymbol @@ -73,14 +67,20 @@ class TargetedSuite extends FunSuite with TempDirectories { "error", s"ambiguous symbols for this position" ), - clues(many, occurrences, posRange) + clues(many, occurrences, expected) ) } } - fn(textDocument, symbols) + fn(document, symbols) } } + private def rangeOf(pos: Position): List[Int] = + if (pos.startLine == pos.endLine) + List(pos.startLine, pos.startColumn, pos.endColumn) + else + List(pos.startLine, pos.startColumn, pos.endLine, pos.endColumn) + checkDoc( "issue-24", """package example;