Skip to content

Commit

Permalink
Add defintion functionality to magik-language-server
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenLooman committed Sep 9, 2023
1 parent 257bffb commit 79ebd0d
Show file tree
Hide file tree
Showing 22 changed files with 298 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- HidesVariableCheck now allows for variable definition in ancestor scope, when defined at lower line.
- Add ForbiddenInheritanceCheck.
- TrailingWhitespaceCheck now marks the actual whitespace.
- Add defintion functionality to magik-language-server.
- Various small fixes.

0.7.1 (2023-02-21)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private void checkIdentifier(final Scope scope, final AstNode identifierNode) {
final ScopeEntry scopeEntry = ancestorScope.getScopeEntry(identifier);
if (scopeEntry != null
&& scopeEntry.isType(ScopeEntry.Type.LOCAL)
&& scopeEntry.getNode().getTokenLine() < identifierNode.getTokenLine()) {
&& scopeEntry.getDefinitionNode().getTokenLine() < identifierNode.getTokenLine()) {
this.addIssue(identifierNode, MESSAGE);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private void checkScope(final Scope scope, final List<Scope> parentScopes) {
final String identifier = scopeEntry.getIdentifier();
for (final Scope parentScope : parentScopes) {
if (parentScope.getScopeEntry(identifier) != null) {
this.addIssue(scopeEntry.getNode(), MESSAGE);
this.addIssue(scopeEntry.getDefinitionNode(), MESSAGE);
found = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected void walkPostMagik(final AstNode node) {
.filter(scopeEntry -> scopeEntry.isType(ScopeEntry.Type.GLOBAL))
.filter(scopeEntry -> this.isPrefixed(scopeEntry.getIdentifier()))
.forEach(scopeEntry -> {
AstNode scopeEntryNode = scopeEntry.getNode();
AstNode scopeEntryNode = scopeEntry.getDefinitionNode();
String identifier = scopeEntry.getIdentifier();
String message = String.format(MESSAGE, identifier);
this.addIssue(scopeEntryNode, message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ protected void walkPostMagik(final AstNode node) {
// - any later identifier(s) of it is used
// - but this one is not
for (final ScopeEntry entry : new ArrayList<>(scopeEntries)) {
final AstNode entryNode = entry.getNode();
final AstNode entryNode = entry.getDefinitionNode();
if (this.isPartOfMultiVariableDefinition(entryNode)
&& this.anyNextSiblingUsed(entryNode)
|| this.isPartOfMultiAssignment(entryNode)) {
Expand All @@ -129,7 +129,7 @@ protected void walkPostMagik(final AstNode node) {
continue;
}

final AstNode entryNode = entry.getNode();
final AstNode entryNode = entry.getDefinitionNode();
final String name = entryNode.getTokenValue();
final String message = String.format(MESSAGE, name);
this.addIssue(entryNode, message);
Expand All @@ -141,7 +141,7 @@ private List<ScopeEntry> getCheckableScopeEntries() {
final GlobalScope globalScope = this.getMagikFile().getGlobalScope();
for (final Scope scope : globalScope.getSelfAndDescendantScopes()) {
for (final ScopeEntry scopeEntry : scope.getScopeEntriesInScope()) { // NOSONAR
final AstNode scopeEntryNode = scopeEntry.getNode();
final AstNode scopeEntryNode = scopeEntry.getDefinitionNode();

// But not globals/dynamics which are assigned to directly
if ((scopeEntry.isType(ScopeEntry.Type.GLOBAL) || scopeEntry.isType(ScopeEntry.Type.DYNAMIC))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ protected void walkPreIdentifier(final AstNode node) {
}

// Only test the first use.
final AstNode declarationNode = entry.getNode();
final AstNode declarationNode = entry.getDefinitionNode();
if (this.seenNodes.contains(declarationNode)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected void walkPostMagik(final AstNode node) {

if (!this.isValidName(identifier)) {
final String message = String.format(MESSAGE, identifier);
final AstNode identifierNode = scopeEntry.getNode();
final AstNode identifierNode = scopeEntry.getDefinitionNode();
this.addIssue(identifierNode, message);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import nl.ramsolutions.sw.magik.analysis.typing.ReadOnlyTypeKeeperAdapter;
import nl.ramsolutions.sw.magik.languageserver.codeactions.CodeActionProvider;
import nl.ramsolutions.sw.magik.languageserver.completion.CompletionProvider;
import nl.ramsolutions.sw.magik.languageserver.definitions.DefinitionsProvider;
import nl.ramsolutions.sw.magik.languageserver.diagnostics.MagikLintDiagnosticsProvider;
import nl.ramsolutions.sw.magik.languageserver.diagnostics.MagikTypeDiagnosticsProvider;
import nl.ramsolutions.sw.magik.languageserver.documentsymbols.DocumentSymbolProvider;
Expand All @@ -34,6 +35,7 @@
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
Expand Down Expand Up @@ -99,6 +101,7 @@ public class MagikTextDocumentService implements TextDocumentService {
private final HoverProvider hoverProvider;
private final ImplementationProvider implementationProvider;
private final SignatureHelpProvider signatureHelpProvider;
private final DefinitionsProvider definitionsProvider;
private final ReferencesProvider referencesProvider;
private final CompletionProvider completionProvider;
private final FormattingProvider formattingProvider;
Expand All @@ -123,6 +126,7 @@ public MagikTextDocumentService(final MagikLanguageServer languageServer, final
this.hoverProvider = new HoverProvider();
this.implementationProvider = new ImplementationProvider();
this.signatureHelpProvider = new SignatureHelpProvider();
this.definitionsProvider = new DefinitionsProvider();
this.referencesProvider = new ReferencesProvider();
this.completionProvider = new CompletionProvider();
this.formattingProvider = new FormattingProvider();
Expand All @@ -146,6 +150,7 @@ public void setCapabilities(final ServerCapabilities capabilities) {
this.hoverProvider.setCapabilities(capabilities);
this.implementationProvider.setCapabilities(capabilities);
this.signatureHelpProvider.setCapabilities(capabilities);
this.definitionsProvider.setCapabilities(capabilities);
this.referencesProvider.setCapabilities(capabilities);
this.completionProvider.setCapabilities(capabilities);
this.formattingProvider.setCapabilities(capabilities);
Expand Down Expand Up @@ -320,17 +325,42 @@ public CompletableFuture<List<FoldingRange>> foldingRange(final FoldingRangeRequ
});
}

@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
final DefinitionParams params) {
final TextDocumentIdentifier textDocument = params.getTextDocument();
LOGGER.trace("definitions, uri: {}", textDocument.getUri());

final MagikTypedFile magikFile = this.openFiles.get(textDocument);
final Position lsp4jPosition = params.getPosition();
final nl.ramsolutions.sw.magik.Position position = Lsp4jConversion.positionFromLsp4j(lsp4jPosition);
return CompletableFuture.supplyAsync(() -> {
final List<nl.ramsolutions.sw.magik.Location> locations =
this.definitionsProvider.provideDefinitions(magikFile, position);
LOGGER.debug("Definitions found: {}", locations.size());

final List<Location> lsp4jLocations = locations.stream()
.map(location -> Lsp4jConversion.locationToLsp4j(location))
.collect(Collectors.toList());
return Either.forLeft(lsp4jLocations);
});
}

@Override
public CompletableFuture<List<? extends Location>> references(final ReferenceParams params) {
final TextDocumentIdentifier textDocument = params.getTextDocument();
LOGGER.trace("references, uri: {}", textDocument.getUri());

final MagikTypedFile magikFile = this.openFiles.get(textDocument);
final Position position = params.getPosition();
final Position lsp4jPosition = params.getPosition();
final nl.ramsolutions.sw.magik.Position position = Lsp4jConversion.positionFromLsp4j(lsp4jPosition);
return CompletableFuture.supplyAsync(() -> {
final List<Location> locations = this.referencesProvider.provideReferences(magikFile, position);
final List<nl.ramsolutions.sw.magik.Location> locations =
this.referencesProvider.provideReferences(magikFile, position);
LOGGER.debug("References found: {}", locations.size());
return locations;
return locations.stream()
.map(location -> Lsp4jConversion.locationToLsp4j(location))
.collect(Collectors.toList());
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ private List<CompletionItem> provideGlobalCompletion(
scopeForNode.getSelfAndAncestorScopes().stream()
.flatMap(scope -> scope.getScopeEntriesInScope().stream())
.filter(scopeEntry -> {
final AstNode definingNode = scopeEntry.getNode();
final AstNode definingNode = scopeEntry.getDefinitionNode();
final Range range = new Range(definingNode);
return Lsp4jConversion.positionFromLsp4j(position).isAfterRange(range);
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package nl.ramsolutions.sw.magik.languageserver.definitions;

import com.sonar.sslr.api.AstNode;
import java.util.Collections;
import java.util.List;
import nl.ramsolutions.sw.magik.Location;
import nl.ramsolutions.sw.magik.MagikTypedFile;
import nl.ramsolutions.sw.magik.Position;
import nl.ramsolutions.sw.magik.analysis.AstQuery;
import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper;
import nl.ramsolutions.sw.magik.analysis.scope.Scope;
import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry;
import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper;
import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType;
import nl.ramsolutions.sw.magik.analysis.typing.types.Condition;
import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString;
import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType;
import nl.ramsolutions.sw.magik.api.MagikGrammar;
import org.eclipse.lsp4j.ServerCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Definitions provider.
*/
public class DefinitionsProvider {

private static final Logger LOGGER = LoggerFactory.getLogger(DefinitionsProvider.class);

/**
* Set server capabilities.
* @param capabilities Server capabilities.
*/
public void setCapabilities(final ServerCapabilities capabilities) {
capabilities.setDefinitionProvider(true);
}

/**
* Provide definitions.
* @param magikFile Magik file.
* @param position Position.
* @return Definitions.
*/
public List<Location> provideDefinitions(final MagikTypedFile magikFile, final Position position) {
// Parse magik.
final AstNode node = magikFile.getTopNode();
final ITypeKeeper typeKeeper = magikFile.getTypeKeeper();

// Should always be on an identifier.
final AstNode currentNode = AstQuery.nodeAt(node, position, MagikGrammar.IDENTIFIER);
if (currentNode == null) {
return Collections.emptyList();
}

final AstNode wantedNode = currentNode.getFirstAncestor(
MagikGrammar.METHOD_INVOCATION,
MagikGrammar.METHOD_DEFINITION,
MagikGrammar.ATOM,
MagikGrammar.CONDITION_NAME);
LOGGER.trace("Wanted node: {}", wantedNode);
final PackageNodeHelper packageHelper = new PackageNodeHelper(wantedNode);
if (wantedNode == null) {
return Collections.emptyList();
} else if (wantedNode.is(MagikGrammar.ATOM)
&& wantedNode.getFirstChild().is(MagikGrammar.IDENTIFIER)) {
final Scope scope = magikFile.getGlobalScope().getScopeForNode(wantedNode);
final String identifier = wantedNode.getTokenValue();
final ScopeEntry scopeEntry = scope.getScopeEntry(identifier);
if (scopeEntry == null) {
return Collections.emptyList();
}

if (scopeEntry.isType(
ScopeEntry.Type.DEFINITION,
ScopeEntry.Type.LOCAL,
ScopeEntry.Type.IMPORT,
ScopeEntry.Type.CONSTANT,
ScopeEntry.Type.PARAMETER)) {
final AstNode definitionNode = scopeEntry.getDefinitionNode();
final Location definitionLocation = new Location(magikFile.getUri(), definitionNode);
return List.of(definitionLocation);
}

// Assume type.
final String pakkage = packageHelper.getCurrentPackage();
final TypeString typeString = TypeString.ofIdentifier(identifier, pakkage);
final AbstractType type = typeKeeper.getType(typeString);
if (type != UndefinedType.INSTANCE
&& type.getLocation() != null) {
final Location typeLocation = type.getLocation();
return List.of(typeLocation);
}
} else if (wantedNode.is(MagikGrammar.CONDITION_NAME)) {
final String conditionName = currentNode.getTokenValue();
final Condition condition = typeKeeper.getCondition(conditionName);
if (condition != null
&& condition.getLocation() != null) {
final Location conditionLocation = condition.getLocation();
return List.of(conditionLocation);
}
}

return Collections.emptyList();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Defaults.
*/
@javax.annotation.ParametersAreNonnullByDefault
package nl.ramsolutions.sw.magik.languageserver.definitions;
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,21 @@
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import nl.ramsolutions.sw.magik.Location;
import nl.ramsolutions.sw.magik.MagikTypedFile;
import nl.ramsolutions.sw.magik.Position;
import nl.ramsolutions.sw.magik.analysis.AstQuery;
import nl.ramsolutions.sw.magik.analysis.helpers.MethodDefinitionNodeHelper;
import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper;
import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper;
import nl.ramsolutions.sw.magik.analysis.scope.Scope;
import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry;
import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper;
import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType;
import nl.ramsolutions.sw.magik.analysis.typing.types.Method;
import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString;
import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType;
import nl.ramsolutions.sw.magik.api.MagikGrammar;
import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.ServerCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -55,8 +56,7 @@ public List<Location> provideReferences(final MagikTypedFile magikFile, final Po
final ITypeKeeper typeKeeper = magikFile.getTypeKeeper();

// Should always be on an identifier.
final AstNode currentNode =
AstQuery.nodeAt(node, Lsp4jConversion.positionFromLsp4j(position), MagikGrammar.IDENTIFIER);
final AstNode currentNode = AstQuery.nodeAt(node, position, MagikGrammar.IDENTIFIER);
if (currentNode == null) {
return Collections.emptyList();
}
Expand Down Expand Up @@ -97,15 +97,32 @@ public List<Location> provideReferences(final MagikTypedFile magikFile, final Po
}
} else if (wantedNode.is(MagikGrammar.ATOM)
&& wantedNode.getFirstChild().is(MagikGrammar.IDENTIFIER)) {
// A random identifier, regard it as a type.
final String pakkage = packageHelper.getCurrentPackage();
// TODO: If variable, find references to variable.
final Scope scope = magikFile.getGlobalScope().getScopeForNode(wantedNode);
final String identifier = currentNode.getTokenValue();
final TypeString typeString = TypeString.ofIdentifier(identifier, pakkage);
final AbstractType type = typeKeeper.getType(typeString);
if (type != UndefinedType.INSTANCE) {
final String typeName = type.getFullName();
LOGGER.debug("Getting references to type: {}", typeName);
return this.referencesToType(typeKeeper, typeName);
final ScopeEntry scopeEntry = scope.getScopeEntry(identifier);
if (scopeEntry == null) {
return Collections.emptyList();
} else if (scopeEntry.isType(
ScopeEntry.Type.DEFINITION,
ScopeEntry.Type.LOCAL,
ScopeEntry.Type.IMPORT,
ScopeEntry.Type.CONSTANT,
ScopeEntry.Type.PARAMETER)) {
final List<AstNode> usages = scopeEntry.getUsages();
return usages.stream()
.map(usageNode -> new Location(magikFile.getUri(), usageNode))
.collect(Collectors.toList());
} else {
// A random identifier, regard it as a type.
final String pakkage = packageHelper.getCurrentPackage();
final TypeString typeString = TypeString.ofIdentifier(identifier, pakkage);
final AbstractType type = typeKeeper.getType(typeString);
if (type != UndefinedType.INSTANCE) {
final String typeName = type.getFullName();
LOGGER.debug("Getting references to type: {}", typeName);
return this.referencesToType(typeKeeper, typeName);
}
}
} else if (wantedNode.is(MagikGrammar.CONDITION_NAME)) {
final String conditionName = currentNode.getTokenValue();
Expand Down Expand Up @@ -145,7 +162,6 @@ private List<Location> referencesToMethod(
.filter(filterPredicate::test)
.map(Method.MethodUsage::getLocation)
.filter(Objects::nonNull)
.map(Lsp4jConversion::locationToLsp4j)
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -174,7 +190,6 @@ private List<Location> referencesToType(final ITypeKeeper typeKeeper, final Stri
.filter(filterPredicate::test)
.map(Method.GlobalUsage::getLocation)
.filter(Objects::nonNull)
.map(Lsp4jConversion::locationToLsp4j)
.collect(Collectors.toList());
}

Expand All @@ -186,7 +201,6 @@ private List<Location> referencesToCondition(final ITypeKeeper typeKeeper, final
.flatMap(method -> method.getConditionUsages().stream())
.filter(conditionUsage -> conditionUsage.getConditionName().equals(conditionName))
.map(Method.ConditionUsage::getLocation)
.map(Lsp4jConversion::locationToLsp4j)
.collect(Collectors.toList());
}

Expand Down
Loading

0 comments on commit 79ebd0d

Please sign in to comment.