Skip to content

Commit ebb13a1

Browse files
committed
Add support for LSIF Typed
This commit adds the ability to emit LSIF Typed using the following command: ``` lsif-java index --output dump.lsif-typed ``` LSIF Typed is used as the output format as long as the output filename ends with `*.lsif-typed`.
1 parent a96b7eb commit ebb13a1

File tree

12 files changed

+620
-47
lines changed

12 files changed

+620
-47
lines changed

lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/IndexSemanticdbCommand.scala

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,19 @@ final case class IndexSemanticdbCommand(
4444
@Inline() app: Application = Application.default
4545
) extends Command {
4646
def sourceroot: Path = AbsolutePath.of(app.env.workingDirectory)
47-
def isProtobufFormat: Boolean =
48-
IndexSemanticdbCommand.isProtobufFormat(output)
4947
def absoluteTargetroots: List[Path] =
5048
targetroot.map(AbsolutePath.of(_, app.env.workingDirectory))
5149
def run(): Int = {
5250
val reporter = new ConsoleLsifSemanticdbReporter(app)
53-
val format =
54-
if (isProtobufFormat)
55-
LsifOutputFormat.PROTOBUF
56-
else
57-
LsifOutputFormat.JSON
51+
val outputFilename = output.getFileName.toString
52+
val format = LsifOutputFormat.fromFilename(outputFilename)
53+
if (format == LsifOutputFormat.UNKNOWN) {
54+
app.error(
55+
s"unknown output format for filename '$outputFilename'. " +
56+
s"Supported file extension are `*.lsif`, `*.lsif-typed'"
57+
)
58+
return 1
59+
}
5860
val packages =
5961
absoluteTargetroots
6062
.iterator

lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/SnapshotLsifCommand.scala

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import moped.cli.Command
4242
import moped.cli.CommandParser
4343
import moped.reporters.Input
4444
import moped.reporters.Position
45+
import moped.reporters.Reporter
4546
import org.scalameta.ascii.layout.prefs.LayoutPrefsImpl
4647

4748
@CommandName("snapshot-lsif")
@@ -65,7 +66,11 @@ case class SnapshotLsifCommand(
6566
for {
6667
inputPath <- input
6768
in = AbsolutePath.of(inputPath, sourceroot)
68-
doc <- SnapshotLsifCommand.parseTextDocument(in, sourceroot)
69+
if Files.isRegularFile(in) || {
70+
app.error(s"no such file: $in")
71+
false
72+
}
73+
doc <- SnapshotLsifCommand.parseTextDocument(in, sourceroot, app.reporter)
6974
} {
7075
val docPath = AbsolutePath
7176
.of(Paths.get(doc.getUri), sourceroot)
@@ -86,16 +91,21 @@ case class SnapshotLsifCommand(
8691

8792
object SnapshotLsifCommand {
8893
private val jsonParser = JsonFormat.parser().ignoringUnknownFields()
89-
def parseTextDocument(input: Path, sourceroot: Path): List[TextDocument] = {
90-
parseSemanticdb(input, parseInput(input), sourceroot)
94+
def parseTextDocument(
95+
input: Path,
96+
sourceroot: Path,
97+
reporter: Reporter
98+
): List[TextDocument] = {
99+
parseSemanticdb(input, parseInput(input), sourceroot, reporter)
91100
}
92101

93102
def parseSemanticdb(
94103
input: Path,
95104
objects: mutable.Buffer[LsifObject],
96-
sourceroot: Path
105+
sourceroot: Path,
106+
reporter: Reporter
97107
): List[TextDocument] = {
98-
val lsif = new IndexedLsif(input, objects, sourceroot)
108+
val lsif = new IndexedLsif(input, objects, sourceroot, reporter)
99109
lsif
100110
.ranges
101111
.iterator
@@ -169,7 +179,8 @@ object SnapshotLsifCommand {
169179
class IndexedLsif(
170180
val path: Path,
171181
val objects: mutable.Buffer[LsifObject],
172-
val sourceroot: Path
182+
val sourceroot: Path,
183+
val reporter: Reporter
173184
) {
174185
val documents = mutable.Map.empty[Int, TextDocument.Builder]
175186
val next = mutable.Map.empty[Int, Int]
@@ -432,24 +443,28 @@ object SnapshotLsifCommand {
432443
case "document" =>
433444
val relativeFile = Paths.get(URI.create(o.getUri))
434445
val absoluteFile = sourceroot.resolve(relativeFile)
435-
val text =
436-
new String(
437-
Files.readAllBytes(absoluteFile),
438-
StandardCharsets.UTF_8
439-
)
440-
val relativeUri = sourceroot
441-
.relativize(absoluteFile)
442-
.iterator()
443-
.asScala
444-
.mkString("/")
445-
val language = Language
446-
.values()
447-
.find(_.name().compareToIgnoreCase(o.getLanguage) == 0)
448-
.getOrElse(Language.UNKNOWN_LANGUAGE)
449-
textDocument(o.getId)
450-
.setUri(relativeUri)
451-
.setLanguage(language)
452-
.setText(text)
446+
if (!Files.isRegularFile(absoluteFile)) {
447+
reporter.warning(s"no such file: $absoluteFile")
448+
} else {
449+
val text =
450+
new String(
451+
Files.readAllBytes(absoluteFile),
452+
StandardCharsets.UTF_8
453+
)
454+
val relativeUri = sourceroot
455+
.relativize(absoluteFile)
456+
.iterator()
457+
.asScala
458+
.mkString("/")
459+
val language = Language
460+
.values()
461+
.find(_.name().compareToIgnoreCase(o.getLanguage) == 0)
462+
.getOrElse(Language.UNKNOWN_LANGUAGE)
463+
textDocument(o.getId)
464+
.setUri(relativeUri)
465+
.setLanguage(language)
466+
.setText(text)
467+
}
453468
case "definitionResult" =>
454469
isDefinitionResult += o.getId()
455470
case "hoverResult" =>

lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifOutputFormat.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@
66
* <p>The Protobuf format is experimental and currently only exists as a proof-of-concept.
77
*/
88
public enum LsifOutputFormat {
9-
JSON,
10-
PROTOBUF
9+
GRAPH_NDJSON,
10+
GRAPH_PROTOBUF,
11+
TYPED_PROTOBUF,
12+
TYPED_NDJSON,
13+
UNKNOWN;
14+
15+
public boolean isTyped() {
16+
return this == TYPED_NDJSON || this == TYPED_PROTOBUF;
17+
}
18+
19+
public boolean isNewlineDelimitedJSON() {
20+
return this == GRAPH_NDJSON || this == TYPED_NDJSON;
21+
}
22+
23+
public static LsifOutputFormat fromFilename(String name) {
24+
if (name.endsWith(".lsif")) return GRAPH_NDJSON;
25+
if (name.endsWith(".lsif-protobuf")) return GRAPH_PROTOBUF;
26+
if (name.endsWith(".lsif-typed")) return TYPED_PROTOBUF;
27+
if (name.endsWith(".lsif-typed.ndjson")) return TYPED_NDJSON;
28+
return UNKNOWN;
29+
}
1130
}

lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifOutputStream.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ public void writeLsifObject(LsifObject.Builder object) {
4949
b.output.reset();
5050
try {
5151
switch (options.format) {
52-
case PROTOBUF:
52+
case GRAPH_PROTOBUF:
5353
object.buildPartial().writeTo(b.output);
5454
break;
55-
case JSON:
55+
case GRAPH_NDJSON:
5656
default:
5757
jsonPrinter.appendTo(object, b.writer);
5858
b.writer.flush();
@@ -69,7 +69,9 @@ public void flush() throws IOException {
6969
byte[] bytes = buffer.poll();
7070
while (bytes != null) {
7171
out.write(bytes);
72-
out.write(NEWLINE);
72+
if (options.format.isNewlineDelimitedJSON()) {
73+
out.write(NEWLINE);
74+
}
7375
bytes = buffer.poll();
7476
}
7577
out.flush();

lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
import com.sourcegraph.semanticdb_javac.Semanticdb.SymbolOccurrence;
88
import com.sourcegraph.semanticdb_javac.Semanticdb.SymbolOccurrence.Role;
99
import com.sourcegraph.semanticdb_javac.SemanticdbSymbols;
10+
import lib.codeintel.lsif_typed.LsifTyped;
1011

1112
import java.io.IOException;
13+
import java.net.URI;
1214
import java.nio.file.Files;
1315
import java.nio.file.Path;
16+
import java.nio.file.Paths;
1417
import java.util.*;
1518
import java.util.concurrent.ConcurrentHashMap;
1619
import java.util.stream.Collectors;
1720
import java.util.stream.Stream;
21+
import java.util.stream.StreamSupport;
1822

1923
/** The core logic that converts SemanticDB into LSIF. */
2024
public class LsifSemanticdb {
@@ -49,6 +53,116 @@ private void run() throws IOException {
4953
return;
5054
}
5155
options.reporter.startProcessing(files.size());
56+
if (options.format.isTyped()) {
57+
runTyped(files, packages);
58+
} else {
59+
runGraph(files, packages);
60+
}
61+
writer.build();
62+
options.reporter.endProcessing();
63+
}
64+
65+
private void runTyped(List<Path> files, PackageTable packages) {
66+
writer.emitTyped(typedMetadata());
67+
filesStream(files).forEach(document -> processTypedDocument(document, packages));
68+
}
69+
70+
private String typedSymbol(String symbol, Package pkg) {
71+
if (symbol.startsWith("local")) {
72+
return "local " + symbol.substring("local".length());
73+
}
74+
return "semanticdb maven " + pkg.repoName() + " " + pkg.version() + " " + symbol;
75+
}
76+
77+
private void processTypedDocument(Path path, PackageTable packages) {
78+
for (LsifTextDocument doc : parseTextDocument(path).collect(Collectors.toList())) {
79+
if (doc.semanticdb.getOccurrencesCount() == 0) {
80+
continue;
81+
}
82+
83+
Path absolutePath = Paths.get(URI.create(doc.semanticdb.getUri()));
84+
String relativePath =
85+
StreamSupport.stream(options.sourceroot.relativize(absolutePath).spliterator(), false)
86+
.map(p -> p.getFileName().toString())
87+
.collect(Collectors.joining("/"));
88+
LsifTyped.Document.Builder tdoc =
89+
LsifTyped.Document.newBuilder().setRelativePath(relativePath);
90+
for (SymbolOccurrence occ : doc.sortedSymbolOccurrences()) {
91+
int role = 0;
92+
if (occ.getRole() == Role.DEFINITION) {
93+
role |= LsifTyped.SymbolRole.Definition_VALUE;
94+
}
95+
boolean isSingleLineRange = occ.getRange().getStartLine() == occ.getRange().getEndLine();
96+
Iterable<Integer> range =
97+
isSingleLineRange
98+
? Arrays.asList(
99+
occ.getRange().getStartLine(),
100+
occ.getRange().getStartCharacter(),
101+
occ.getRange().getEndCharacter())
102+
: Arrays.asList(
103+
occ.getRange().getStartLine(),
104+
occ.getRange().getStartCharacter(),
105+
occ.getRange().getEndLine(),
106+
occ.getRange().getEndCharacter());
107+
Package pkg = packages.packageForSymbol(occ.getSymbol()).orElse(Package.EMPTY);
108+
tdoc.addOccurrences(
109+
LsifTyped.Occurrence.newBuilder()
110+
.addAllRange(range)
111+
.setSymbol(typedSymbol(occ.getSymbol(), pkg))
112+
.setSymbolRoles(role));
113+
}
114+
Symtab symtab = new Symtab(doc.semanticdb);
115+
for (SymbolInformation info : doc.semanticdb.getSymbolsList()) {
116+
Package pkg = packages.packageForSymbol(info.getSymbol()).orElse(Package.EMPTY);
117+
LsifTyped.SymbolInformation.Builder tinfo =
118+
LsifTyped.SymbolInformation.newBuilder().setSymbol(typedSymbol(info.getSymbol(), pkg));
119+
120+
for (String overriddenSymbol : info.getOverriddenSymbolsList()) {
121+
if (overriddenSymbol.equals("java/lang/Object#")) {
122+
// Skip java/lang/Object# since it's the parent of all classes
123+
// making it noisy for "find implementations" results.
124+
continue;
125+
}
126+
Package overriddenSymbolPkg =
127+
packages.packageForSymbol(overriddenSymbol).orElse(Package.EMPTY);
128+
tinfo.addRelationships(
129+
LsifTyped.Relationship.newBuilder()
130+
.setSymbol(typedSymbol(overriddenSymbol, overriddenSymbolPkg))
131+
.setIsImplementation(true)
132+
.setIsReference(SemanticdbSymbols.isMethod(info.getSymbol())));
133+
}
134+
if (info.hasSignature()) {
135+
String language =
136+
doc.semanticdb.getLanguage().toString().toLowerCase(Locale.ROOT).intern();
137+
String signature = new SignatureFormatter(info, symtab).formatSymbol();
138+
tinfo.addDocumentation("```" + language + "\n" + signature + "\n```");
139+
}
140+
String documentation = info.getDocumentation().getMessage();
141+
if (!documentation.isEmpty()) {
142+
tinfo.addDocumentation(documentation);
143+
}
144+
tdoc.addSymbols(tinfo);
145+
}
146+
writer.emitTyped(LsifTyped.Index.newBuilder().addDocuments(tdoc).build());
147+
}
148+
}
149+
150+
private LsifTyped.Index typedMetadata() {
151+
return LsifTyped.Index.newBuilder()
152+
.setMetadata(
153+
LsifTyped.Metadata.newBuilder()
154+
.setVersion(LsifTyped.ProtocolVersion.UnspecifiedProtocolVersion)
155+
.setProjectRoot(options.sourceroot.toUri().toString())
156+
.setTextDocumentEncoding(LsifTyped.TextEncoding.UTF8)
157+
.setToolInfo(
158+
LsifTyped.ToolInfo.newBuilder()
159+
.setName(options.toolInfo.getName())
160+
.setVersion(options.toolInfo.getVersion())
161+
.addAllArguments(options.toolInfo.getArgsList())))
162+
.build();
163+
}
164+
165+
private void runGraph(List<Path> files, PackageTable packages) {
52166
writer.emitMetaData();
53167
int projectId = writer.emitProject(options.language);
54168

@@ -57,11 +171,7 @@ private void run() throws IOException {
57171
filesStream(files)
58172
.flatMap(d -> processPath(d, isExportedSymbol, packages))
59173
.collect(Collectors.toList());
60-
61174
writer.emitContains(projectId, documentIds);
62-
63-
writer.build();
64-
options.reporter.endProcessing();
65175
}
66176

67177
private Stream<Path> filesStream(List<Path> files) {

lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifWriter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.sourcegraph.lsif_protocol.LsifPosition;
77
import com.sourcegraph.semanticdb_javac.Semanticdb;
88
import com.sourcegraph.semanticdb_javac.SemanticdbSymbols;
9+
import lib.codeintel.lsif_typed.LsifTyped;
10+
911
import java.io.BufferedOutputStream;
1012
import java.io.IOException;
1113
import java.nio.file.Files;
@@ -15,6 +17,7 @@
1517
import java.util.Arrays;
1618
import java.util.List;
1719
import java.util.concurrent.atomic.AtomicInteger;
20+
import java.util.regex.Pattern;
1821
import java.util.stream.Collectors;
1922

2023
/** High-level utility methods to write LSIF vertex/edge objects into the LSIF output stream. */
@@ -36,6 +39,10 @@ public LsifWriter(LsifSemanticdbOptions options) throws IOException {
3639
this.options = options;
3740
}
3841

42+
public void emitTyped(LsifTyped.Index index) {
43+
this.output.write(index.toByteArray());
44+
}
45+
3946
public void emitMetaData() {
4047
emitObject(
4148
lsifVertex("metaData")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
11
package com.sourcegraph.lsif_semanticdb;
22

33
public abstract class Package {
4+
public static final Package EMPTY =
5+
new Package() {
6+
@Override
7+
public String repoName() {
8+
return ".";
9+
}
10+
11+
@Override
12+
public String version() {
13+
return ".";
14+
}
15+
};
416

517
public abstract String repoName();
618

719
public abstract String version();
20+
21+
public final String lsifTypedEncoding() {
22+
return "maven " + encode(repoName()) + " " + encode(version());
23+
}
24+
25+
private String encode(String value) {
26+
if (value.contains(" ")) {
27+
return "`" + value + "`";
28+
}
29+
return value;
30+
}
831
}

0 commit comments

Comments
 (0)