Skip to content
Open
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
53 changes: 53 additions & 0 deletions scip-java-proto/src/main/protobuf/scip.proto
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,59 @@ message Occurrence {
SyntaxKind syntax_kind = 5;
// Diagnostics that have been reported for this specific range.
repeated Diagnostic diagnostics = 6;
// (optional) Using the same encoding as the sibling `range` field, half-open
// source range of the nearest non-trivial enclosing AST node. This range must
// enclose the `range` field. Example applications that make use of the
// enclosing_range field:
//
// - Call hierarchies: to determine what symbols are references from the body
// of a function
// - Symbol outline: to display breadcrumbs from the cursor position to the
// root of the file
// - Expand selection: to select the nearest enclosing AST node.
// - Highlight range: to indicate the AST expression that is associated with a
// hover popover
//
// For definition occurrences, the enclosing range should indicate the
// start/end bounds of the entire definition AST node, including
// documentation.
// ```
// const n = 3
// ^ range
// ^^^^^^^^^^^ enclosing_range
//
// /** Parses the string into something */
// ^ enclosing_range start --------------------------------------|
// function parse(input string): string { |
// ^^^^^ range |
// return input.slice(n) |
// } |
// ^ enclosing_range end <---------------------------------------|
// ```
//
// Any attributes/decorators/attached macros should also be part of the
// enclosing range.
//
// ```python
// @cache
// ^ enclosing_range start---------------------|
// def factorial(n): |
// return n * factorial(n-1) if n else 1 |
// < enclosing_range end-----------------------|
//
// ```
//
// For reference occurrences, the enclosing range should indicate the start/end
// bounds of the parent expression.
// ```
// const a = a.b
// ^ range
// ^^^ enclosing_range
// const b = a.b(41).f(42).g(43)
// ^ range
// ^^^^^^^^^^^^^ enclosing_range
// ```
repeated int32 enclosing_range = 7;
}

// Represents a diagnostic, such as a compiler error or warning, which should be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,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()
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)
.build();
.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,18 @@ 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);
Optional<Semanticdb.Range> enclosingRange = computeEnclosingRange(tree);
emitSymbolOccurrence(sym, range, role, enclosingRange);
if (role == Role.DEFINITION) {
// Only emit SymbolInformation for symbols that are defined in this compilation unit.
emitSymbolInformation(sym, tree);
}
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 +299,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 +463,11 @@ 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 +477,51 @@ 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