Skip to content

Commit

Permalink
Move external reference codelens to subroutine in external subroutines (
Browse files Browse the repository at this point in the history
#436)

closes #431
  • Loading branch information
MarkusAmshove authored Nov 29, 2023
2 parents 62ec70d + 62d6ecf commit 97ee15c
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import org.amshove.natls.languageserver.LspUtil;
import org.amshove.natls.project.LanguageServerFile;
import org.amshove.natparse.natural.IModuleWithBody;
import org.amshove.natparse.natural.IStatementListNode;
import org.amshove.natparse.natural.ISubroutineNode;
import org.amshove.natparse.natural.project.NaturalFileType;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.Command;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class InternalSubroutineReferencesCodeLens implements ICodeLensProvider
{
Expand All @@ -27,6 +30,11 @@ public List<CodeLens> provideCodeLens(LanguageServerFile file)
.map(ISubroutineNode.class::cast)
.map(s ->
{
if (file.getType() == NaturalFileType.SUBROUTINE && isTopLevelSubroutine(s))
{
return null;
}

var references = s.references().size();
var declarationRange = LspUtil.toRange(s.declaration());

Expand All @@ -46,9 +54,26 @@ public List<CodeLens> provideCodeLens(LanguageServerFile file)
null
);
})
.filter(Objects::nonNull)
.forEach(codelens::add);
}

return codelens;
}

private boolean isTopLevelSubroutine(ISubroutineNode node)
{
/*
Module
StatementList
Subroutine <- top level
Module
StatementList
Subroutine
StatementList
Subroutine <- not top level
*/
return !(node.parent()instanceof IStatementListNode statementList && statementList.parent() instanceof ISubroutineNode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import org.amshove.natls.languageserver.LspUtil;
import org.amshove.natls.project.LanguageServerFile;
import org.amshove.natls.project.ModuleReferenceCache;
import org.amshove.natparse.NodeUtil;
import org.amshove.natparse.natural.ISubroutineNode;
import org.amshove.natparse.natural.project.NaturalFileType;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Range;

import java.util.Arrays;
import java.util.List;
Expand All @@ -17,24 +21,41 @@ public List<CodeLens> provideCodeLens(LanguageServerFile file)
{
var references = file.module().callers().size() + ModuleReferenceCache.retrieveCachedPositions(file).size();

var firstNodeRange = LspUtil.toRange(file.module().syntaxTree().descendants().first().position());
var codeLensRange = findRangeForCodeLens(file);

if (references == 0)
{
return List.of(
codeLensWithoutCommand("No references", firstNodeRange)
codeLensWithoutCommand("No references", codeLensRange)
);
}

return List.of(
new CodeLens(
firstNodeRange,
codeLensRange,
new Command(
"%d references".formatted(references),
CustomCommands.CODELENS_SHOW_REFERENCES,
Arrays.asList(file.getUri(), firstNodeRange)
Arrays.asList(file.getUri(), codeLensRange)
),
null
)
);
}

private Range findRangeForCodeLens(LanguageServerFile file)
{
var firstRangeInFile = LspUtil.toRange(file.module().syntaxTree().descendants().first().position());

if (file.getType() == NaturalFileType.SUBROUTINE)
{
var topLevelSubroutine = NodeUtil.findFirstStatementOfType(ISubroutineNode.class, file.module().syntaxTree());
return topLevelSubroutine != null
? LspUtil.toRange(topLevelSubroutine.position())
: firstRangeInFile;
}

return firstRangeInFile;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void provideCodeLensWithNoReferencesIfAModuleIsUnused()
var identifier = createOrSaveFile("LIBONE", "SUB.NSN", """
DEFINE DATA LOCAL
END-DEFINE
END
""");

Expand All @@ -36,6 +36,31 @@ void provideCodeLensWithNoReferencesIfAModuleIsUnused()
});
}

@Test
void provideCodeLensWithNoReferencesIfAnExternalSubroutineIsUnused()
{
var identifier = createOrSaveFile("LIBONE", "SUB.NSS", """
DEFINE DATA LOCAL
END-DEFINE
DEFINE SUBROUTINE EXT-SUB
IGNORE
END-SUBROUTINE
END
""");

testCodeLens(identifier, lenses ->
{
assertThat(lenses).hasSize(1);
var lens = lenses.get(0);
var soft = new SoftAssertions();
soft.assertThat(lens.getRange()).as("Range").isEqualTo(LspUtil.newLineRange(2, 18, 25));
soft.assertThat(lens.getCommand().getTitle()).isEqualTo("No references");
soft.assertThat(lens.getCommand().getCommand()).as("Command").isEqualTo(CustomCommands.CODELENS_NON_INTERACTIVE);
soft.assertThat(lens.getCommand().getArguments()).as("Arguments").isNull();
soft.assertAll();
});
}

@Test
void provideCodeLensForAModuleWhichHasReferrers()
{
Expand Down Expand Up @@ -65,4 +90,37 @@ void provideCodeLensForAModuleWhichHasReferrers()
soft.assertAll();
});
}

@Test
void provideCodeLensForAnExternalSubroutineWhichHasReferrers()
{
var identifier = createOrSaveFile("LIBONE", "SUBR.NSS", """
DEFINE DATA LOCAL
END-DEFINE
DEFINE SUBROUTINE EXTERNAL-SUB
IGNORE
END-SUBROUTINE
END
""");

createOrSaveFile("LIBONE", "SUBCALL.NSN", """
DEFINE DATA LOCAL
END-DEFINE
PERFORM EXTERNAL-SUB
END
""");

testCodeLens(identifier, lenses ->
{
assertThat(lenses).hasSize(1);
var lens = lenses.get(0);
var soft = new SoftAssertions();
soft.assertThat(lens.getCommand().getTitle()).isEqualTo("1 references");
var expectedRange = LspUtil.newLineRange(2, 18, 30);
soft.assertThat(lens.getRange()).isEqualTo(expectedRange);
soft.assertThat(lens.getCommand().getCommand()).as("Command").isEqualTo(CustomCommands.CODELENS_SHOW_REFERENCES);
soft.assertThat(lens.getCommand().getArguments()).as("Arguments").isEqualTo(Arrays.asList(identifier.getUri(), expectedRange));
soft.assertAll();
});
}
}
19 changes: 19 additions & 0 deletions libs/natparse/src/main/java/org/amshove/natparse/NodeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,23 @@ public static boolean containsTokenWithKind(ISyntaxNode node, SyntaxKind kind)

return false;
}

public static <T extends IStatementNode> T findFirstStatementOfType(Class<T> statementType, ISyntaxTree tree)
{
if (statementType.isInstance(tree))
{
return statementType.cast(tree);
}

for (var descendant : tree.descendants())
{
var foundDescendant = findFirstStatementOfType(statementType, descendant);
if (foundDescendant != null)
{
return foundDescendant;
}
}

return null;
}
}

0 comments on commit 97ee15c

Please sign in to comment.