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
7 changes: 7 additions & 0 deletions .changeset/fix-lookbehind-safari-16-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"remend": patch
---

Fix crash on iOS 16.0-16.2 / Safari < 16.3 by removing the lookbehind assertion from the single-tilde escape pattern (#519).

JSCore on those versions doesn't support lookbehind (`(?<=...)`) and throws a `SyntaxError` while the module is being evaluated, before any user code runs, so there is no way to catch it. The preceding word character is now captured and written back in the replacement instead.
14 changes: 14 additions & 0 deletions packages/remend/__tests__/single-tilde.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,18 @@ describe("single tilde escape (#445)", () => {
it("can be disabled via options", () => {
expect(remend("20~25°C", { singleTilde: false })).toBe("20~25°C");
});

it("should preserve Unicode letters around single ~", () => {
expect(remend("日本~語")).toBe("日本\\~語");
expect(remend("α~β")).toBe("α\\~β");
expect(remend("é~x")).toBe("é\\~x");
});

it("should preserve supplementary-plane Unicode letters around single ~", () => {
expect(remend("𐐀~a")).toBe("𐐀\\~a");
});

it("should escape multiple single tildes between letters", () => {
expect(remend("foo~bar~baz")).toBe("foo\\~bar\\~baz");
});
});
12 changes: 8 additions & 4 deletions packages/remend/src/single-tilde-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { isInsideCodeBlock } from "./code-block-utils";
// - NOT followed by another ~ (to avoid matching ~~)
// - followed by a word character
// Uses Unicode-aware \p{L} and \p{N} for CJK and other scripts
const SINGLE_TILDE_PATTERN = /(?<=[\p{L}\p{N}_])~(?!~)(?=[\p{L}\p{N}_])/gu;
// Captures the preceding char instead of using a lookbehind: Safari < 16.3
// throws on lookbehind, which would crash at module evaluation (#519).
const SINGLE_TILDE_PATTERN = /([\p{L}\p{N}_])~(?!~)(?=[\p{L}\p{N}_])/gu;

export const handleSingleTildeEscape = (text: string): string => {
if (!text || typeof text !== "string") {
Expand All @@ -24,12 +26,14 @@ export const handleSingleTildeEscape = (text: string): string => {
return text;
}

return text.replace(SINGLE_TILDE_PATTERN, (match, offset) => {
return text.replace(SINGLE_TILDE_PATTERN, (match, precedingChar, offset) => {
const tildeOffset = offset + precedingChar.length;

// Don't escape inside code blocks
if (isInsideCodeBlock(text, offset)) {
if (isInsideCodeBlock(text, tildeOffset)) {
return match;
}

return "\\~";
return `${precedingChar}\\~`;
});
};
Loading