fix: avoid lookbehind in application regexes for older WebKit

Application regex lookbehind prevented older WebKit from loading the startup module graph. Replace lookbehind with boundary checks across run/terminal links, SQL aliases, debugger targets and refactoring; preserve Unicode boundaries, UTF-16 offsets and empty-match progress. Add compatibility and regression tests.

Fixes #224
Refs #208
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-29 15:01:01 +08:00
1 джерело 364bb5ef50
коміт d37b96002b
10 files changed
+87 -10

No files matched your search

+10
Переглянути файл
@@ -56,6 +56,16 @@ describe("locating Smart Step Into targets", () => {
expect(locateStepTargets(line, 3, [target(1, "missing()")])).toBeNull();
});
it("rejects identifier suffixes, preserving UTF-16 offsets and nested calls", () => {
const line = "𐐀foo() αfoo() $foo() _foo() foo(foo())";
const from = line.indexOf("foo(foo");
expect(locateStepTargets(line, 0, [target(1, "foo()"), target(2, "foo()")])).toEqual([
{ id: 1, from, to: from + 3 },
{ id: 2, from: from + 4, to: from + 7 },
]);
expect(targetsOnLine("𐐀foo() bar()", 0, [target(1, "foo()"), target(2, "bar()")]).map((t) => t.id)).toEqual([2]);
});
it("keeps only the paused line's calls when the adapter lists the whole function", () => {
const line = " total = add(total, square(i))";
const all = [target(0, "square(i)"), target(1, "add(total, square(i))"), target(2, 'print("total", total)')];
+7 -3
Переглянути файл
@@ -25,7 +25,11 @@ export function calleeName(label: string): string | null {
/** A call of `name`: `name(` not preceded by an identifier character. */
function callPattern(name: string, flags: string): RegExp {
return new RegExp(`(?<![\\p{L}\\p{N}_$])${name.replace(/\$/g, "\\$")}\\s*\\(`, flags);
return new RegExp(`${name.replace(/\$/g, "\\$")}\\s*\\(`, flags);
}
function isCallBoundary(text: string, from: number): boolean {
return !/[\p{L}\p{N}_$]$/u.test(text.slice(Math.max(0, from - 2), from));
}
function identifierEnd(text: string, from: number): number {
@@ -55,7 +59,7 @@ export function locateStepTargets(lineText: string, line: number, targets: StepI
const name = calleeName(t.label);
if (!name) return null;
for (const m of lineText.matchAll(callPattern(name, "gu"))) {
if (used.has(m.index!)) continue;
if (used.has(m.index!) || !isCallBoundary(lineText, m.index!)) continue;
from = m.index!;
to = from + name.length;
break;
@@ -78,7 +82,7 @@ export function targetsOnLine(lineText: string, line: number, targets: StepInTar
if (t.line != null) return t.line === line;
if (t.column != null) return true;
const name = calleeName(t.label);
return !name || callPattern(name, "u").test(lineText);
return !name || [...lineText.matchAll(callPattern(name, "gu"))].some((m) => isCallBoundary(lineText, m.index));
});
return kept.length ? kept : targets;
}