Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 14 additions & 29 deletions lib/core/theme/parser/jsonc_preprocessor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ String stripJsonc(String input) {
final next = i + 1 < len ? input[i + 1] : '';

if (ch == '"') {
out.write(_copyStringLiteral(input, i));
i = _skipStringLiteral(input, i);
// One scan finds the closing quote; the literal is copied as a slice.
final end = _stringLiteralEnd(input, i);
out.write(input.substring(i, end));
i = end;
continue;
}

Expand Down Expand Up @@ -54,37 +56,20 @@ String stripJsonc(String input) {

bool _isWhitespace(String c) => c == ' ' || c == '\t' || c == '\n' || c == '\r';

String _copyStringLiteral(String s, int start) {
final buf = StringBuffer();
var i = start;
buf.write(s[i]);
i++;
while (i < s.length) {
final ch = s[i];
buf.write(ch);
if (ch == '\\' && i + 1 < s.length) {
i++;
buf.write(s[i]);
} else if (ch == '"') {
i++;
break;
}
i++;
}
return buf.toString();
}

int _skipStringLiteral(String s, int start) {
/// Offset after the string literal that opens at [start] (a `"`): past its
/// closing quote, or the end of [s] when it is not terminated. A backslash
/// escapes the next character.
int _stringLiteralEnd(String s, int start) {
final len = s.length;
var i = start + 1;
while (i < s.length) {
if (s[i] == '\\') {
while (i < len) {
final c = s.codeUnitAt(i);
if (c == 0x5C) {
i += 2;
continue;
}
if (s[i] == '"') {
return i + 1;
}
if (c == 0x22) return i + 1;
i++;
}
return s.length;
return len;
}
30 changes: 30 additions & 0 deletions test/core/theme/parser/jsonc_preprocessor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,35 @@ void main() {
expect(cleaned.contains(',}'), isFalse);
expect(cleaned, contains('"editor.background"'));
});

test('keeps string literals byte for byte, escapes included', () {
const input = r'{"a": "q\"uote // not a comment", "b": "back\\"}';
expect(stripJsonc(input), input);
});

test('comment markers and trailing commas inside strings survive', () {
const input = r'{"a": "/* x */", "b": "[1,]", "c": "x,}"}';
expect(stripJsonc(input), input);
});

test('an unterminated string is copied to the end', () {
expect(stripJsonc('{"a": "oops'), '{"a": "oops');
});

test('a trailing backslash at the end of input does not throw', () {
expect(stripJsonc(r'{"a": "x\'), r'{"a": "x\');
});

test('comments and trailing commas around many strings', () {
final input = StringBuffer('{\n');
for (var i = 0; i < 200; i++) {
input.write(' // token $i\n "k$i": "#${i.toRadixString(16).padLeft(6, '0')}",\n');
}
input.write('}');
final cleaned = stripJsonc(input.toString());
expect(cleaned.contains('//'), isFalse);
expect(cleaned.contains(',\n}'), isFalse);
expect(cleaned, contains('"k199": "#0000c7"'));
});
});
}
Loading