Skip to content

Commit d4b7889

Browse files
authored
Fix syntax highlighting multiline Swift string literals with backslashes (#723)
Fixes an issue where syntax highlighting looks broken for Swift code listings with multi-line string literals that have backslash newline delimiters. Resolves: rdar://102993199
1 parent 6f9f638 commit d4b7889

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

src/utils/custom-highlight-lang/swift.js

+35
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,40 @@ export default function swiftOverride(hljs) {
4141
};
4242
}
4343

44+
// Checks if a given language sub-mode matches the "ESCAPED_NEWLINE" from the
45+
// built-in Swift parser from hljs
46+
const isEscapedNewlineMode = (mode) => {
47+
const { className, match } = mode;
48+
if (className !== 'subst' || !match) {
49+
return false;
50+
}
51+
52+
const matchStr = match.toString();
53+
return matchStr.startsWith('\\') && matchStr.endsWith('[\\t ]*(?:[\\r\\n]|\\r\\n)');
54+
};
55+
56+
// replace the "ESCAPED_NEWLINE" sub-mode in the multiline string literal mode
57+
// variants so that it doesn't include the actual newline characters in the
58+
// span token that it generates, because this causes issues with our
59+
// line-number + multi-line string literal logic when the span for the
60+
// backslash token is split across multiple lines
61+
const strIndex = language.contains.findIndex(({ className }) => className === 'string');
62+
language.contains[strIndex] = {
63+
...language.contains[strIndex],
64+
variants: language.contains[strIndex].variants.map(variant => ({
65+
...variant,
66+
contains: variant.contains.map(mode => (isEscapedNewlineMode(mode) ? ({
67+
className: 'subst',
68+
begin: /\\#{0,3}/,
69+
end: /[\t ]*(?:[\r\n]|\r\n)/,
70+
// same match as the original one but with an explicit start/end match so
71+
// that the end one can be excluded from the resulting span
72+
excludeEnd: true,
73+
}) : (
74+
mode
75+
))),
76+
})),
77+
};
78+
4479
return language;
4580
}

tests/unit/utils/syntax-highlight.spec.js

+23
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,29 @@ describe("syntax-highlight", () => {
136136
`);
137137
});
138138

139+
it('keeps escaped newline tokens on the same line for multiline string literals', async () => {
140+
const content = [
141+
'let multiline = """',
142+
'a \\',
143+
'b',
144+
'',
145+
'c \\',
146+
'd',
147+
'"""',
148+
];
149+
const { highlightedCode, sanitizedCode } = await prepare(content, 'swift');
150+
expect(sanitizedCode).not.toEqual(highlightedCode);
151+
expect(sanitizedCode).toMatchInlineSnapshot(`
152+
<span class="syntax-keyword">let</span> multiline <span class="syntax-operator">=</span> <span class="syntax-string">"""</span>
153+
<span class="syntax-string">a <span class="syntax-subst">\\</span></span>
154+
<span class="syntax-string">b</span>
155+
<span class="syntax-string"></span>
156+
<span class="syntax-string">c <span class="syntax-subst">\\</span></span>
157+
<span class="syntax-string">d</span>
158+
<span class="syntax-string">"""</span>
159+
`);
160+
});
161+
139162
it("wraps multiline nested html elements", () => {
140163
const code = document.createElement("CODE");
141164
code.innerHTML = `<span class="syntax-function">function <span class="syntax-title function_">someName</span>(<span class="syntax-params">foo,

0 commit comments

Comments
 (0)