Skip to content

Commit 5702b70

Browse files
authored
Merge pull request #424 from 1c-syntax/copilot/fix-scanner-parsing-error
Fix BSLHighlighter crash on multiline SDBL tokens (GROUP BY on separate lines)
2 parents 796cf6d + 02ad1ae commit 5702b70

7 files changed

Lines changed: 3052 additions & 9 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
implementation("org.commonmark", "commonmark-ext-heading-anchor", commonmarkVersion)
4343

4444
testImplementation("org.junit.jupiter", "junit-jupiter-api", "6.0.3")
45+
testImplementation("org.junit.jupiter", "junit-jupiter-params", "6.0.3")
4546
testImplementation("org.assertj", "assertj-core", "3.27.7")
4647
testImplementation("org.mockito", "mockito-core", "5.21.0")
4748
testImplementation("org.sonarsource.sonarqube", "sonar-testing-harness", sonarQubeVersion) {

src/main/java/com/github/_1c_syntax/bsl/sonar/BSLHighlighter.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import lombok.Data;
2929
import lombok.EqualsAndHashCode;
3030
import lombok.RequiredArgsConstructor;
31+
import lombok.extern.slf4j.Slf4j;
3132
import org.antlr.v4.runtime.Token;
3233
import org.antlr.v4.runtime.Tokenizer;
3334
import org.eclipse.lsp4j.Range;
3435
import org.sonar.api.batch.fs.InputFile;
3536
import org.sonar.api.batch.sensor.SensorContext;
37+
import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
3638
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
3739

3840
import javax.annotation.Nullable;
@@ -45,6 +47,7 @@
4547
import java.util.Set;
4648
import java.util.stream.Collectors;
4749

50+
@Slf4j
4851
@RequiredArgsConstructor
4952
public class BSLHighlighter {
5053

@@ -158,19 +161,29 @@ public void saveHighlighting(InputFile inputFile, DocumentContext documentContex
158161

159162
highlightingData.stream()
160163
.filter(HighlightingData::isActive)
161-
.forEach(data ->
162-
highlighting.highlight(
163-
data.getRange().getStart().getLine(),
164-
data.getRange().getStart().getCharacter(),
165-
data.getRange().getEnd().getLine(),
166-
data.getRange().getEnd().getCharacter(),
167-
data.getType()
168-
)
169-
);
164+
.forEach(data -> applyHighlighting(highlighting, data, inputFile));
170165

171166
highlighting.save();
172167
}
173168

169+
private static void applyHighlighting(
170+
NewHighlighting highlighting,
171+
HighlightingData data,
172+
InputFile inputFile
173+
) {
174+
try {
175+
highlighting.highlight(
176+
data.getRange().getStart().getLine(),
177+
data.getRange().getStart().getCharacter(),
178+
data.getRange().getEnd().getLine(),
179+
data.getRange().getEnd().getCharacter(),
180+
data.getType()
181+
);
182+
} catch (IllegalArgumentException e) {
183+
LOGGER.error("Unable to highlight file {}", inputFile, e);
184+
}
185+
}
186+
174187
public void highlightToken(
175188
Token token,
176189
Collection<HighlightingData> highlightingData,
@@ -184,6 +197,11 @@ public void highlightToken(
184197
var charPositionInLine = token.getCharPositionInLine();
185198
var tokenText = token.getText().stripTrailing();
186199

200+
var newlineIndex = tokenText.indexOf('\n');
201+
if (newlineIndex >= 0) {
202+
tokenText = tokenText.substring(0, newlineIndex).stripTrailing();
203+
}
204+
187205
var range = Ranges.create(
188206
line,
189207
charPositionInLine,

src/test/java/com/github/_1c_syntax/bsl/sonar/BSLHighlighterTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.antlr.v4.runtime.Token;
3131
import org.antlr.v4.runtime.Vocabulary;
3232
import org.junit.jupiter.api.Test;
33+
import org.junit.jupiter.params.ParameterizedTest;
34+
import org.junit.jupiter.params.provider.ValueSource;
3335
import org.sonar.api.batch.fs.InputFile;
3436
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
3537
import org.sonar.api.batch.sensor.internal.SensorContextTester;
@@ -45,6 +47,7 @@
4547
import java.util.stream.IntStream;
4648

4749
import static org.assertj.core.api.Assertions.assertThat;
50+
import static org.assertj.core.api.Assertions.assertThatNoException;
4851
import static org.mockito.Mockito.mock;
4952
import static org.mockito.Mockito.when;
5053

@@ -130,6 +133,56 @@ void testMergeHighlightingTokens() {
130133

131134
}
132135

136+
@ParameterizedTest(name = "{0}")
137+
@ValueSource(strings = {
138+
// Tab-indented query: ensures tabs producing position differences do not crash highlighting.
139+
"highlightLongQuery.bsl",
140+
// СГРУППИРОВАТЬ ПО on separate BSL continuation lines -> single multiline SDBL token.
141+
"highlightCrmQuery.bsl",
142+
// ИНДЕКСИРОВАТЬ ПО on separate lines (reproduces PR #424 comment from ERP 2.5 module).
143+
"highlightErpIndexByQuery.bsl",
144+
// Real-world reproducer from issue #318 (CRM_КлиентыСервер.bsl attachment).
145+
"CRM_КлиентыСервер.bsl"
146+
})
147+
void testHighlightingDoesNotThrowOnFile(String fileName) {
148+
// given
149+
context = SensorContextTester.create(Path.of("."));
150+
highlighter = new BSLHighlighter(context);
151+
var baseDirName = "src/test/resources/examples";
152+
var path = Path.of(baseDirName, fileName);
153+
documentContext = BSLLSBinding.getServerContext().addDocument(path.toUri());
154+
BSLLSBinding.getServerContext().rebuildDocument(documentContext);
155+
inputFile = Tools.inputFileBSL(fileName, Path.of(baseDirName).toFile());
156+
157+
// when/then - should not throw, even for multiline SDBL tokens or tab-indented queries
158+
assertThatNoException().isThrownBy(() ->
159+
highlighter.saveHighlighting(inputFile, documentContext)
160+
);
161+
}
162+
163+
@Test
164+
void testSaveHighlightingWithInvalidTokenPosition() {
165+
// given
166+
context = SensorContextTester.create(Path.of("."));
167+
highlighter = new BSLHighlighter(context);
168+
documentContext = mock(DocumentContext.class);
169+
170+
// Create a token with position exceeding line length
171+
var token = new CommonToken(BSLLexer.IF_KEYWORD, "Если");
172+
token.setLine(1);
173+
token.setCharPositionInLine(20);
174+
175+
when(documentContext.getTokens()).thenReturn(List.of(token));
176+
177+
// Create InputFile with short content (line has less than 20 characters)
178+
inputFile = Tools.inputFileBSL(FILE_NAME, BASE_DIR, "А = 1;");
179+
180+
// when/then - should not throw
181+
assertThatNoException().isThrownBy(() ->
182+
highlighter.saveHighlighting(inputFile, documentContext)
183+
);
184+
}
185+
133186
private void testHighlighting(Vocabulary vocabulary, Map<String, TypeOfText> highlightingMap) {
134187
// given
135188
initContext(vocabulary);

0 commit comments

Comments
 (0)