Skip to content

Commit ff4c4aa

Browse files
authored
SONARPY-2076: Fix highlighting of single line Jupyter Notebook (#1935)
1 parent 5736897 commit ff4c4aa

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

sonar-python-plugin/src/main/java/org/sonar/plugins/python/IpynbNotebookParser.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,10 @@ public Optional<String> parseLanguage() throws IOException {
7878

7979
public GeneratedIPythonFile parseNotebook() throws IOException {
8080
String content = inputFile.wrappedFile().contents();
81+
boolean isCompressed = content.lines().count() <= 1;
8182
JsonFactory factory = new JsonFactory();
8283
try (JsonParser jParser = factory.createParser(content)) {
83-
return parseCells(jParser).map(notebookData -> {
84+
return parseCells(jParser, isCompressed).map(notebookData -> {
8485
// Account for EOF token
8586
JsonLocation location = jParser.currentTokenLocation();
8687
notebookData.addDefaultLocation(lastPythonLine, location.getLineNr(), location.getColumnNr());
@@ -90,27 +91,27 @@ public GeneratedIPythonFile parseNotebook() throws IOException {
9091

9192
}
9293

93-
private Optional<NotebookParsingData> parseCells(JsonParser parser) throws IOException {
94+
private Optional<NotebookParsingData> parseCells(JsonParser parser, boolean isCompressed) throws IOException {
9495
while (!parser.isClosed()) {
9596
parser.nextToken();
9697
String fieldName = parser.currentName();
9798
if ("cells".equals(fieldName)) {
9899
// consume array start token
99100
parser.nextToken();
100-
Optional<NotebookParsingData> data = parseCellArray(parser);
101+
Optional<NotebookParsingData> data = parseCellArray(parser, isCompressed);
101102
parser.close();
102103
return data;
103104
}
104105
}
105106
return Optional.empty();
106107
}
107108

108-
private Optional<NotebookParsingData> parseCellArray(JsonParser jParser) throws IOException {
109+
private Optional<NotebookParsingData> parseCellArray(JsonParser jParser, boolean isCompressed) throws IOException {
109110
List<NotebookParsingData> cellsData = new ArrayList<>();
110111

111112
while (jParser.nextToken() != JsonToken.END_ARRAY) {
112113
if (jParser.currentToken() == JsonToken.START_OBJECT) {
113-
processCodeCell(cellsData, jParser);
114+
processCodeCell(cellsData, jParser, isCompressed);
114115
}
115116
}
116117
Optional<NotebookParsingData> aggregatedNotebookData = cellsData.stream().reduce(NotebookParsingData::combine);
@@ -134,7 +135,7 @@ private static boolean processCodeCellType(JsonParser jParser) throws IOExceptio
134135
return false;
135136
}
136137

137-
private void processCodeCell(List<NotebookParsingData> accumulator, JsonParser jParser) throws IOException {
138+
private void processCodeCell(List<NotebookParsingData> accumulator, JsonParser jParser, boolean isCompressed) throws IOException {
138139
boolean isCodeCell = false;
139140
Optional<NotebookParsingData> notebookData = Optional.empty();
140141
while (jParser.nextToken() != JsonToken.END_OBJECT) {
@@ -154,7 +155,7 @@ private void processCodeCell(List<NotebookParsingData> accumulator, JsonParser j
154155
}
155156
switch (jParser.currentToken()) {
156157
case START_ARRAY:
157-
notebookData = Optional.of(parseSourceArray(startLine, jParser));
158+
notebookData = Optional.of(parseSourceArray(startLine, jParser, isCompressed));
158159
break;
159160
case VALUE_STRING:
160161
notebookData = Optional.of(parseSourceMultilineString(startLine, jParser));
@@ -172,7 +173,7 @@ private void processCodeCell(List<NotebookParsingData> accumulator, JsonParser j
172173
}
173174
}
174175

175-
private static NotebookParsingData parseSourceArray(int startLine, JsonParser jParser) throws IOException {
176+
private static NotebookParsingData parseSourceArray(int startLine, JsonParser jParser, boolean isCompressed) throws IOException {
176177
NotebookParsingData cellData = NotebookParsingData.fromLine(startLine);
177178
JsonLocation tokenLocation = jParser.currentTokenLocation();
178179
// In case of an empty cell, we don't add an extra line
@@ -181,7 +182,7 @@ private static NotebookParsingData parseSourceArray(int startLine, JsonParser jP
181182
String sourceLine = jParser.getValueAsString();
182183
var newTokenLocation = jParser.currentTokenLocation();
183184
var countEscapedChar = countEscapeCharacters(sourceLine, newTokenLocation.getColumnNr());
184-
cellData.addLineToSource(sourceLine, newTokenLocation.getLineNr(), newTokenLocation.getColumnNr(), countEscapedChar);
185+
cellData.addLineToSource(sourceLine, newTokenLocation.getLineNr(), newTokenLocation.getColumnNr(), countEscapedChar, isCompressed);
185186
lastSourceLine = sourceLine;
186187
tokenLocation = newTokenLocation;
187188
}

sonar-python-plugin/src/main/java/org/sonar/plugins/python/NotebookParsingData.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public void appendToSource(String str) {
7070
aggregatedSource.append(str);
7171
}
7272

73-
public void addLineToSource(String sourceLine, int lineNr, int columnNr, Map<Integer, Integer> colOffset) {
74-
addLineToSource(sourceLine, new IPythonLocation(lineNr, columnNr, colOffset));
73+
public void addLineToSource(String sourceLine, int lineNr, int columnNr, Map<Integer, Integer> colOffset, boolean isCompressed) {
74+
addLineToSource(sourceLine, new IPythonLocation(lineNr, columnNr, colOffset, isCompressed));
7575
}
7676

7777
private void appendLine(String line) {

sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonHighlighterTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ class PythonHighlighterTest {
4141

4242
private File file;
4343
private File notebookFile;
44+
private File notebookFileSingleLine;
4445
private DefaultInputFile notebookInputFile;
46+
private DefaultInputFile notebookInputFileSingleLine;
4547
private String dir = "src/test/resources/org/sonar/plugins/python";
4648

4749
@BeforeEach
@@ -56,9 +58,16 @@ void scanFile() {
5658
notebookInputFile = TestInputFileBuilder.create("moduleKey", notebookFile.getName())
5759
.initMetadata(TestUtils.fileContent(notebookFile, StandardCharsets.UTF_8))
5860
.build();
61+
62+
notebookFileSingleLine = new File(dir, "/notebookHighlighterSingleLine.ipynb");
63+
notebookInputFileSingleLine = TestInputFileBuilder.create("moduleKey", notebookFileSingleLine.getName())
64+
.initMetadata(TestUtils.fileContent(notebookFileSingleLine, StandardCharsets.UTF_8))
65+
.build();
66+
5967
context = SensorContextTester.create(new File(dir));
6068
context.fileSystem().add(inputFile);
6169
context.fileSystem().add(notebookInputFile);
70+
context.fileSystem().add(notebookInputFileSingleLine);
6271

6372
PythonHighlighter pythonHighlighter = new PythonHighlighter(context, new PythonInputFileImpl(inputFile));
6473
TestPythonVisitorRunner.scanFile(file, pythonHighlighter);
@@ -226,6 +235,26 @@ void highlightingNotebooks() {
226235
checkOnRange(13, 9, 2, notebookFile, TypeOfText.CONSTANT);
227236
}
228237

238+
@Test
239+
void highlightingNotebooksSingleLine() {
240+
String pythonContent = "def foo():\n pass\na = 2 # comment\n#SONAR_PYTHON_NOTEBOOK_CELL_DELIMITER";
241+
var locations = Map.of(
242+
1, new IPythonLocation(1, 93, Map.of(-1, 0), true),
243+
2, new IPythonLocation(1, 108, Map.of(-1, 0), true),
244+
3, new IPythonLocation(1, 121, Map.of(-1, 0), true),
245+
4, new IPythonLocation(1, 93, Map.of(-1, 0), true)); //EOF Token
246+
PythonHighlighter pythonHighlighter = new PythonHighlighter(context, new GeneratedIPythonFile(notebookInputFileSingleLine, pythonContent, locations));
247+
TestPythonVisitorRunner.scanNotebookFile(notebookFileSingleLine, locations, pythonContent, pythonHighlighter);
248+
// def
249+
checkOnRange(1, 93, 3, notebookFileSingleLine, TypeOfText.KEYWORD);
250+
// pass
251+
checkOnRange(1, 112, 4, notebookFileSingleLine, TypeOfText.KEYWORD);
252+
// 2
253+
checkOnRange(1, 125, 1, notebookFileSingleLine, TypeOfText.CONSTANT);
254+
// # comment
255+
checkOnRange(1, 127 , 9, notebookFileSingleLine, TypeOfText.COMMENT);
256+
}
257+
229258
/**
230259
* Checks the highlighting of a range of columns. The first column of a line has index 0.
231260
* The range is the columns of the token.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"cells":[{"cell_type":"code","execution_count": null,"metadata":{},"outputs":[],"source":["def foo():\n"," pass\n","a = 2 # comment"]}],"metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 }

0 commit comments

Comments
 (0)