Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import moped.reporters.Position

object ScipPrinters {

/**
* Indent prefix prepended to each source line so that caret-based indicators
* in snapshot comments can point at arbitrary columns.
*/
val sourceIndent = " "

def printTextDocument(
doc: Scip.Document,
text: String,
Expand Down Expand Up @@ -51,11 +57,58 @@ object ScipPrinters {
val extension = doc.getRelativePath.split("\\.").lastOption.getOrElse("")
val commentSyntax = comments.extensionSyntax(extension)
val input = Input.filename(doc.getRelativePath, text)

// Collect enclosing ranges from all occurrences, grouped by start/end line.
case class EnclosingRange(
startLine: Int,
startChar: Int,
endLine: Int,
endChar: Int,
symbol: String
)
val allEnclosingRanges =
doc
.getOccurrencesList
.asScala
.flatMap { occ =>
occ.getEnclosingRangeList.asScala.map(_.toInt).toList match {
case List(sl, sc, ec) =>
Some(EnclosingRange(sl, sc, sl, ec, occ.getSymbol))
case List(sl, sc, el, ec) =>
Some(EnclosingRange(sl, sc, el, ec, occ.getSymbol))
case _ =>
None
}
}
.toList
val enclosingByStartLine =
allEnclosingRanges
.groupBy(_.startLine)
.view
.mapValues(_.sortBy(_.startChar))
.toMap
val enclosingByEndLine =
allEnclosingRanges
.groupBy(_.endLine)
.view
.mapValues(_.sortBy(_.endChar))
.toMap

text
.linesWithSeparators
.zipWithIndex
.foreach { case (line, i) =>
out.append(line.replace("\t", "→"))
enclosingByStartLine
.getOrElse(i, Nil)
.foreach { er =>
out
.append(commentSyntax)
.append(" " * er.startChar)
.append("⌄ enclosing_range_start ")
.append(er.symbol)
.append("\n")
}
out.append(sourceIndent).append(line.replace("\t", " "))
val occurrences = occurrencesByLine
.getOrElse(i, Nil)
.toSeq
Expand All @@ -80,6 +133,17 @@ object ScipPrinters {
}
}
}
enclosingByEndLine
.getOrElse(i, Nil)
.foreach { er =>
val indent = math.max(0, er.endChar - 1)
out
.append(commentSyntax)
.append(" " * indent)
.append("⌃ enclosing_range_end ")
.append(er.symbol)
.append("\n")
}
}
out.toString()
}
Expand Down Expand Up @@ -133,8 +197,8 @@ object ScipPrinters {
else
"reference"
val indent =
if (pos.startColumn > comment.length)
" " * (pos.startColumn - comment.length)
if (pos.startColumn + sourceIndent.length > comment.length)
" " * (pos.startColumn + sourceIndent.length - comment.length)
else
""
val caretCharacter =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,30 @@ private void processTypedDocument(
occ.getRange().getEndLine(),
occ.getRange().getEndCharacter());
Package pkg = packages.packageForSymbol(occ.getSymbol()).orElse(Package.EMPTY);
tdoc.addOccurrences(
Scip.Occurrence.Builder occBuilder =
Scip.Occurrence.newBuilder()
.addAllRange(range)
.setSymbol(typedSymbol(occ.getSymbol(), pkg))
.setSymbolRoles(role));
.setSymbolRoles(role);
// Add enclosing_range if it exists
if (occ.hasEnclosingRange()) {
Semanticdb.Range enclosingRange = occ.getEnclosingRange();
boolean isEnclosingSingleLine =
enclosingRange.getStartLine() == enclosingRange.getEndLine();
Iterable<Integer> 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 (SymbolInformation info : doc.semanticdb.getSymbolsList()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ public static Semanticdb.Signature signature(Semanticdb.TypeSignature.Builder si
// SemanticDB Symbols

public static Semanticdb.SymbolOccurrence symbolOccurrence(
String symbol, Semanticdb.Range range, Semanticdb.SymbolOccurrence.Role role) {
return Semanticdb.SymbolOccurrence.newBuilder()
.setSymbol(symbol)
.setRange(range)
.setRole(role)
.build();
String symbol,
Semanticdb.Range range,
Semanticdb.SymbolOccurrence.Role role,
java.util.Optional<Semanticdb.Range> 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) {
Expand Down
4 changes: 4 additions & 0 deletions semanticdb-java/src/main/protobuf/semanticdb.proto
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ message SymbolOccurrence {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,24 @@ private Optional<Semanticdb.Range> emitSymbolOccurrence(
Element sym, Tree tree, Name name, Role role, CompilerRange kind) {
if (sym == null || name == null) return Optional.empty();
Optional<Semanticdb.Range> range = semanticdbRange(tree, kind, sym, name.toString());
emitSymbolOccurrence(sym, range, role);
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<Semanticdb.Range> range, Role role) {
private void emitSymbolOccurrence(
Element sym,
Optional<Semanticdb.Range> range,
Role role,
Optional<Semanticdb.Range> enclosingRange) {
if (sym == null) return;
Optional<Semanticdb.SymbolOccurrence> occ = semanticdbOccurrence(sym, range, role);
Optional<Semanticdb.SymbolOccurrence> occ =
semanticdbOccurrence(sym, range, role, enclosingRange);
occ.ifPresent(occurrences::add);
}

Expand Down Expand Up @@ -298,7 +305,7 @@ private void resolveVariableTree(VariableTree node, TreePath treePath) {
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);
if (typeSym != null) emitSymbolOccurrence(typeSym, range, Role.REFERENCE, Optional.empty());
}
}
}
Expand Down Expand Up @@ -462,11 +469,14 @@ private Semanticdb.Range correctForTabs(Semanticdb.Range range, LineMap lineMap,
}

private Optional<Semanticdb.SymbolOccurrence> semanticdbOccurrence(
Element sym, Optional<Semanticdb.Range> range, Role role) {
Element sym,
Optional<Semanticdb.Range> range,
Role role,
Optional<Semanticdb.Range> enclosingRange) {
if (range.isPresent()) {
String ssym = semanticdbSymbol(sym);
if (!ssym.equals(SemanticdbSymbols.NONE)) {
Semanticdb.SymbolOccurrence occ = symbolOccurrence(ssym, range.get(), role);
Semanticdb.SymbolOccurrence occ = symbolOccurrence(ssym, range.get(), role, enclosingRange);
return Optional.of(occ);
} else {
return Optional.empty();
Expand All @@ -476,6 +486,52 @@ private Optional<Semanticdb.SymbolOccurrence> semanticdbOccurrence(
}
}

/**
* 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<Semanticdb.Range> 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 {
Expand Down
34 changes: 17 additions & 17 deletions tests/minimized/src/main/java/minimized/AnnotationParameters.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
package minimized;

@interface Bar {
double value();
double value();
}

@interface BarB {
boolean value();
boolean value();
}

@interface Nullable {
String value() default "";
String value() default "";
}


@interface BarRef{
SuppressWarnings value();
SuppressWarnings value();
}

interface Foo {
@Bar(-1d)
double test();
@Bar(-1d)
double test();

@Bar(~5)
@SuppressWarnings(value = "unchecked")
double test2();
@Bar(~5)
@SuppressWarnings(value = "unchecked")
double test2();

@BarB(!true)
double test3();
@BarB(!true)
double test3();

@Nullable(("what"))
Foo test4();
@Nullable(("what"))
Foo test4();

@Bar((double) -1)
double testCast();
@Bar((double) -1)
double testCast();
}

interface TestRef {
@BarRef(@SuppressWarnings(value = "unchecked"))
abstract double testCase();
@BarRef(@SuppressWarnings(value = "unchecked"))
abstract double testCase();
}
2 changes: 1 addition & 1 deletion tests/minimized/src/main/java/minimized/LombokBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

@lombok.Builder
class Hello {
private String message;
private String message;
}
32 changes: 16 additions & 16 deletions tests/minimized/src/main/java/minimized/TabIndented.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package minimized;

public class TabIndented {
public void app() {
Object o = new Object() {
@Override
public boolean equals(Object other) {
return false;
}
public void app() {
Object o = new Object() {
@Override
public boolean equals(Object other) {
return false;
}

@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}

@Override
public String toString() {
return "";
}
};
}
@Override
public String toString() {
return "";
}
};
}
}
Loading