diff --git a/libs/natls/build.gradle b/libs/natls/build.gradle index 97877c3c8..cd47e18ae 100644 --- a/libs/natls/build.gradle +++ b/libs/natls/build.gradle @@ -10,6 +10,7 @@ dependencies { implementation project(':natlint') testImplementation project(':testhelpers') + testImplementation libraries.slf4j_nop } shadowJar { @@ -18,6 +19,10 @@ shadowJar { archiveVersion.set('') } +configurations.testImplementation { + exclude group: 'org.slf4j', module: libraries.slf4j_utillogging.split(':')[1] +} + jar { manifest { attributes( diff --git a/libs/natls/src/main/java/org/amshove/natls/project/LanguageServerFile.java b/libs/natls/src/main/java/org/amshove/natls/project/LanguageServerFile.java index fa0bab27d..b6d41603e 100644 --- a/libs/natls/src/main/java/org/amshove/natls/project/LanguageServerFile.java +++ b/libs/natls/src/main/java/org/amshove/natls/project/LanguageServerFile.java @@ -168,11 +168,15 @@ public void parse() private boolean hasToReparseCallers(String newSource) { + var tooManyCallers = incomingReferences.size() > 20; + if (!tooManyCallers && module.file().getFiletype() == NaturalFileType.COPYCODE) + { + return true; + } + var newDefineDataHash = hashDefineData(newSource); var defineDataChanged = !Arrays.equals(newDefineDataHash, defineDataHash); defineDataHash = newDefineDataHash; - var tooManyCallers = incomingReferences.size() > 20; - // TODO: Add trace log? return !tooManyCallers && defineDataChanged; } diff --git a/libs/natls/src/test/java/org/amshove/natls/CallerReparseTest.java b/libs/natls/src/test/java/org/amshove/natls/CallerReparseTest.java new file mode 100644 index 000000000..0bd18abed --- /dev/null +++ b/libs/natls/src/test/java/org/amshove/natls/CallerReparseTest.java @@ -0,0 +1,95 @@ +package org.amshove.natls; + +import org.amshove.natls.testlifecycle.LanguageServerTest; +import org.amshove.natls.testlifecycle.LspProjectName; +import org.amshove.natls.testlifecycle.LspTestContext; +import org.amshove.natparse.lexing.LexerError; +import org.amshove.natparse.parsing.ParserError; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class CallerReparseTest extends LanguageServerTest +{ + private static LspTestContext testContext; + + @Override + protected LspTestContext getContext() + { + return testContext; + } + + @Test + void savingACopyCodeShouldTriggerReparseOnCaller(@LspProjectName("emptyproject") LspTestContext context) + { + testContext = context; + + createOrSaveFile("LIBONE", "MYCC.NSC", """ + WRITE '' + """); + + var subprog = createOrSaveFile("LIBONE", "MYSUB.NSN", """ + DEFINE DATA LOCAL + END-DEFINE + INCLUDE MYCC + END + """); + + var subprogramFile = findLanguageServerFile(subprog); + + // Diagnostic from copy code is propagated to the subprogram + assertThat(subprogramFile.allDiagnostics()) + .as("Expected diagnostic not found") + .anyMatch(d -> d.getCode().getLeft().equals(LexerError.INVALID_STRING_LENGTH.id())); + + // Fix the diagnostic in the copy code + createOrSaveFile("LIBONE", "MYCC.NSC", """ + WRITE 'Hi' + """); + + // Diagnostic from copy code should not be present on subprogram anymore, because it was reparsed + assertThat(subprogramFile.allDiagnostics()) + .as("Diagnostic shouldn't be present anymore") + .noneMatch(d -> d.getCode().getLeft().equals(LexerError.INVALID_STRING_LENGTH.id())); + } + + @Test + void changingDefineDataShouldTriggerReparsing(@LspProjectName("emptyproject") LspTestContext context) + { + testContext = context; + + createOrSaveFile("LIBONE", "MYLDA.NSL", """ + DEFINE DATA + LOCAL + END-DEFINE + """); + + var subprog = createOrSaveFile("LIBONE", "MYSUB.NSN", """ + DEFINE DATA + LOCAL USING MYLDA + END-DEFINE + WRITE #HI + END + """); + + var subprogramFile = findLanguageServerFile(subprog); + + // Diagnostic from copy code is propagated to the subprogram + assertThat(subprogramFile.allDiagnostics()) + .as("Expected diagnostic not found") + .anyMatch(d -> d.getCode().getLeft().equals(ParserError.UNRESOLVED_REFERENCE.id())); + + // Adding the variable to the LDA to fix the diagnostic + createOrSaveFile("LIBONE", "MYLDA.NSL", """ + DEFINE DATA + LOCAL + 1 #HI (A10) + END-DEFINE + """); + + // Diagnostic from copy code should not be present on subprogram anymore, because it was reparsed + assertThat(subprogramFile.allDiagnostics()) + .as("Diagnostic shouldn't be present anymore") + .noneMatch(d -> d.getCode().getLeft().equals(ParserError.UNRESOLVED_REFERENCE.id())); + } +}