diff --git a/packages/parsers/src/hfIds.test.ts b/packages/parsers/src/hfIds.test.ts
index 5f120f49d5..662aec881f 100644
--- a/packages/parsers/src/hfIds.test.ts
+++ b/packages/parsers/src/hfIds.test.ts
@@ -44,6 +44,14 @@ describe("ensureHfIds fragment detection", () => {
});
describe("stampHfIdsInPlace keeps the source's own formatting", () => {
+ it.each(["script", "style"])("keeps source offsets after expanding Unicode near %s", (tag) => {
+ const html = `
${"İ".repeat(20)}<${tag}>/* İ
*/${tag.toUpperCase()} \n>after
`;
+ const out = stampHfIdsInPlace(html);
+ expect(out.replace(/ data-hf-id="hf-[a-z0-9]{4}"/g, "")).toBe(html);
+ expect(ids(out)).toEqual(ids(ensureHfIds(html)));
+ expect(stampHfIdsInPlace(out)).toBe(out);
+ });
+
it("only inserts ids into a composition written without whitespace", () => {
const html =
'';
diff --git a/packages/parsers/src/hfIds.ts b/packages/parsers/src/hfIds.ts
index 6aa2f30645..52e23db83d 100644
--- a/packages/parsers/src/hfIds.ts
+++ b/packages/parsers/src/hfIds.ts
@@ -62,7 +62,6 @@ const IMPLIED = new Set(["html", "head", "body", "tbody"]);
type StartTag = { name: string; nameEnd: number; bracket: number };
function startTags(html: string): StartTag[] | null {
- const lower = html.toLowerCase();
const tags: StartTag[] = [];
let i = html.indexOf("<");
while (i >= 0) {
@@ -77,23 +76,16 @@ function startTags(html: string): StartTag[] | null {
if (end < 0) return null;
const tag = name[1]!.toLowerCase();
tags.push({ name: tag, nameEnd: i + name[0].length, bracket: end });
- const after = RAW_TEXT.has(tag) ? rawTextEnd(lower, tag, end) : end;
+ const after = RAW_TEXT.has(tag) ? rawTextEnd(html, tag, end) : end;
i = after < 0 ? -1 : html.indexOf("<", after + 1);
}
return tags;
}
-function rawTextEnd(lower: string, tag: string, from: number): number {
- const marker = `${tag}`;
- for (
- let at = lower.indexOf(marker, from);
- at >= 0;
- at = lower.indexOf(marker, at + marker.length)
- ) {
- const next = lower[at + marker.length];
- if (next !== undefined && /[\s/>]/.test(next)) return at;
- }
- return -1;
+function rawTextEnd(html: string, tag: string, from: number): number {
+ const endTag = new RegExp(`${tag}(?=[\\s/>]|$)`, "gi");
+ endTag.lastIndex = from;
+ return endTag.exec(html)?.index ?? -1;
}
function nextTagAfter(html: string, lt: number): number {