authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-03-20 16:57:18-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-03-20 18:52:07-07:00
log80fb1789cd75eb917d402b010325e5d446b4b997
treee4f8d9ebcbedaa3fb261e323d388d2c680e0c7af
parentc2f5400c509fa622eb21a7c227b2d180fdbbc694
signaturebadge-check Signed by SSH key SHA256:cOKiuRFOeSRxne6EWgHtdQQSlBxjOXm2hOCFnCdLQbQ

fix: html tag prediction was cooked


6 files changed, 68 insertions(+), 68 deletions(-)

src/Memoizer.ts+1-1
...@@ -182,7 +182,7 @@ export class Memoizer {...@@ -182,7 +182,7 @@ export class Memoizer {
182 i !== end;182 i !== end;
183 i += direction183 i += direction
184 ) {184 ) {
185 let previousIndex: number = -1;185 let previousIndex: number = -1;
186 if (i < blockStart) {186 if (i < blockStart) {
187 previousIndex = i;187 previousIndex = i;
188 } else if (i >= nextMiddleEnd) {188 } else if (i >= nextMiddleEnd) {
src/Predict.ts+29-53
...@@ -50,10 +50,8 @@ function parseTail(tail: string): TailState {...@@ -50,10 +50,8 @@ function parseTail(tail: string): TailState {
50 pendingDelim: null,50 pendingDelim: null,
51 pendingHtml: null,51 pendingHtml: null,
52 };52 };
53
54 for (let i = 0; i < tail.length; i++) {53 for (let i = 0; i < tail.length; i++) {
55 const char = tail[i]!;54 const char = tail[i]!;
56
57 if (char === "\n" && tail[i - 1] === "\n") {55 if (char === "\n" && tail[i - 1] === "\n") {
58 resetParagraphState(state);56 resetParagraphState(state);
59 if (!state.exclusive) state.commitIndex = i + 1;57 if (!state.exclusive) state.commitIndex = i + 1;
...@@ -65,31 +63,25 @@ function parseTail(tail: string): TailState {...@@ -65,31 +63,25 @@ function parseTail(tail: string): TailState {
65 i = exclusiveEnd - 1;63 i = exclusiveEnd - 1;
66 continue;64 continue;
67 }65 }
68
69 if (state.exclusive) continue;66 if (state.exclusive) continue;
7067
71 if (inLinkUrlMode(state)) {68 if (inLinkUrlMode(state)) {
72 updateLink(state, i, char);69 updateLink(state, i, char);
73 continue;70 continue;
74 }71 }
75
76 const htmlEnd = consumeHtml(state, tail, i);72 const htmlEnd = consumeHtml(state, tail, i);
77 if (htmlEnd > i) {73 if (htmlEnd > i) {
78 i = htmlEnd - 1;74 i = htmlEnd - 1;
79 continue;75 continue;
80 }76 }
81
82 const delimEnd = consumeDelim(state, tail, i);77 const delimEnd = consumeDelim(state, tail, i);
83 if (delimEnd > i) {78 if (delimEnd > i) {
84 i = delimEnd - 1;79 i = delimEnd - 1;
85 continue;80 continue;
86 }81 }
87
88 if (isEscapedAt(tail, i)) continue;82 if (isEscapedAt(tail, i)) continue;
89
90 updateLink(state, i, char);83 updateLink(state, i, char);
91 }84 }
92
93 return state;85 return state;
94}86}
9587
...@@ -105,7 +97,6 @@ function consumeBackticks(state: TailState, tail: string, start: number) {...@@ -105,7 +97,6 @@ function consumeBackticks(state: TailState, tail: string, start: number) {
105 const length = runLengthAt(tail, start);97 const length = runLengthAt(tail, start);
106 const token = "`".repeat(length);98 const token = "`".repeat(length);
107 const exclusive = state.exclusive;99 const exclusive = state.exclusive;
108
109 if (exclusive?.kind === "fence") {100 if (exclusive?.kind === "fence") {
110 if (101 if (
111 exclusive.token[0] === "`" &&102 exclusive.token[0] === "`" &&
...@@ -117,22 +108,18 @@ function consumeBackticks(state: TailState, tail: string, start: number) {...@@ -117,22 +108,18 @@ function consumeBackticks(state: TailState, tail: string, start: number) {
117 }108 }
118 return start + length;109 return start + length;
119 }110 }
120
121 if (exclusive?.kind === "code") {111 if (exclusive?.kind === "code") {
122 if (!isEscapedAt(tail, start) && length >= exclusive.token.length) {112 if (!isEscapedAt(tail, start) && length >= exclusive.token.length) {
123 state.exclusive = null;113 state.exclusive = null;
124 }114 }
125 return start + length;115 return start + length;
126 }116 }
127
128 if (exclusive) return start + length;117 if (exclusive) return start + length;
129 if (isEscapedAt(tail, start)) return start + length;118 if (isEscapedAt(tail, start)) return start + length;
130
131 if (length >= 3 && isLineStart(tail, start)) {119 if (length >= 3 && isLineStart(tail, start)) {
132 state.exclusive = { kind: "fence", start, token };120 state.exclusive = { kind: "fence", start, token };
133 return start + length;121 return start + length;
134 }122 }
135
136 state.exclusive = { kind: "code", start, token };123 state.exclusive = { kind: "code", start, token };
137 return start + length;124 return start + length;
138}125}
...@@ -140,17 +127,14 @@ function consumeBackticks(state: TailState, tail: string, start: number) {...@@ -140,17 +127,14 @@ function consumeBackticks(state: TailState, tail: string, start: number) {
140function consumeMath(state: TailState, tail: string, start: number) {127function consumeMath(state: TailState, tail: string, start: number) {
141 const length = runLengthAt(tail, start);128 const length = runLengthAt(tail, start);
142 const exclusive = state.exclusive;129 const exclusive = state.exclusive;
143
144 if (exclusive?.kind === "math") {130 if (exclusive?.kind === "math") {
145 if (!isEscapedAt(tail, start) && length >= exclusive.token.length) {131 if (!isEscapedAt(tail, start) && length >= exclusive.token.length) {
146 state.exclusive = null;132 state.exclusive = null;
147 }133 }
148 return start + length;134 return start + length;
149 }135 }
150
151 if (exclusive) return start + length;136 if (exclusive) return start + length;
152 if (isEscapedAt(tail, start)) return start + length;137 if (isEscapedAt(tail, start)) return start + length;
153
154 state.exclusive = {138 state.exclusive = {
155 kind: "math",139 kind: "math",
156 start,140 start,
...@@ -162,27 +146,22 @@ function consumeMath(state: TailState, tail: string, start: number) {...@@ -162,27 +146,22 @@ function consumeMath(state: TailState, tail: string, start: number) {
162function consumeTildes(state: TailState, tail: string, start: number) {146function consumeTildes(state: TailState, tail: string, start: number) {
163 const length = runLengthAt(tail, start);147 const length = runLengthAt(tail, start);
164 const exclusive = state.exclusive;148 const exclusive = state.exclusive;
165
166 if (exclusive?.kind === "fence" && exclusive.token[0] === "~") {149 if (exclusive?.kind === "fence" && exclusive.token[0] === "~") {
167 if (isLineStart(tail, start) && !isEscapedAt(tail, start) && length >= exclusive.token.length) {150 if (isLineStart(tail, start) && !isEscapedAt(tail, start) && length >= exclusive.token.length) {
168 state.exclusive = null;151 state.exclusive = null;
169 }152 }
170 return start + length;153 return start + length;
171 }154 }
172
173 if (exclusive) return start;155 if (exclusive) return start;
174 if (length < 3 || !isLineStart(tail, start) || isEscapedAt(tail, start)) return start;156 if (length < 3 || !isLineStart(tail, start) || isEscapedAt(tail, start)) return start;
175
176 state.exclusive = { kind: "fence", start, token: "~".repeat(length) };157 state.exclusive = { kind: "fence", start, token: "~".repeat(length) };
177 return start + length;158 return start + length;
178}159}
179160
180function consumeHtml(state: TailState, tail: string, start: number) {161function consumeHtml(state: TailState, tail: string, start: number) {
181 if (tail[start] !== "<" || isEscapedAt(tail, start)) return start;162 if (tail[start] !== "<" || isEscapedAt(tail, start)) return start;
182
183 const next = tail[start + 1];163 const next = tail[start + 1];
184 if (next !== undefined && !isAsciiLetter(next)) return start;164 if (next !== undefined && !isAsciiLetter(next)) return start;
185
186 state.pendingHtml = start;165 state.pendingHtml = start;
187 for (let i = start + 1; i < tail.length; i++) {166 for (let i = start + 1; i < tail.length; i++) {
188 if (tail[i] === ">" || tail[i] === "\n") {167 if (tail[i] === ">" || tail[i] === "\n") {
...@@ -190,47 +169,38 @@ function consumeHtml(state: TailState, tail: string, start: number) {...@@ -190,47 +169,38 @@ function consumeHtml(state: TailState, tail: string, start: number) {
190 return i + 1;169 return i + 1;
191 }170 }
192 }171 }
193
194 return tail.length;172 return tail.length;
195}173}
196174
197function consumeDelim(state: TailState, tail: string, start: number) {175function consumeDelim(state: TailState, tail: string, start: number) {
198 const token = matchDelim(tail, start);176 const token = matchDelim(tail, start);
199 if (!token) return start;177 if (!token) return start;
200
201 if (isEscapedAt(tail, start)) return start + token.length;178 if (isEscapedAt(tail, start)) return start + token.length;
202
203 const existingIndex = state.delims.findLastIndex((delim) => delim.token === token);179 const existingIndex = state.delims.findLastIndex((delim) => delim.token === token);
204 if (existingIndex !== -1) {180 if (existingIndex !== -1) {
205 state.delims.splice(existingIndex, 1);181 state.delims.splice(existingIndex, 1);
206 return start + token.length;182 return start + token.length;
207 }183 }
208
209 if (start + token.length === tail.length) {184 if (start + token.length === tail.length) {
210 state.pendingDelim = { start, token };185 state.pendingDelim = { start, token };
211 return start + token.length;186 return start + token.length;
212 }187 }
213
214 if (!canOpenDelim(tail, start, token)) return start + token.length;188 if (!canOpenDelim(tail, start, token)) return start + token.length;
215
216 state.delims.push({ start, token });189 state.delims.push({ start, token });
217 return start + token.length;190 return start + token.length;
218}191}
219192
220function matchDelim(tail: string, start: number): DelimToken | undefined {193function matchDelim(tail: string, start: number): DelimToken | undefined {
221 const char = tail[start];194 const char = tail[start];
222
223 if (char === "*") {195 if (char === "*") {
224 if (tail.startsWith("***", start)) return "***";196 if (tail.startsWith("***", start)) return "***";
225 if (tail.startsWith("**", start)) return "**";197 if (tail.startsWith("**", start)) return "**";
226 return "*";198 return "*";
227 }199 }
228
229 if (char === "_") {200 if (char === "_") {
230 if (tail.startsWith("__", start)) return "__";201 if (tail.startsWith("__", start)) return "__";
231 return "_";202 return "_";
232 }203 }
233
234 if (char === "~" && tail.startsWith("~~", start)) {204 if (char === "~" && tail.startsWith("~~", start)) {
235 return "~~";205 return "~~";
236 }206 }
...@@ -239,19 +209,16 @@ function matchDelim(tail: string, start: number): DelimToken | undefined {...@@ -239,19 +209,16 @@ function matchDelim(tail: string, start: number): DelimToken | undefined {
239function canOpenDelim(tail: string, start: number, token: DelimToken) {209function canOpenDelim(tail: string, start: number, token: DelimToken) {
240 const next = tail[start + token.length];210 const next = tail[start + token.length];
241 if (!next || /\s/.test(next)) return false;211 if (!next || /\s/.test(next)) return false;
242
243 const prev = tail[start - 1];212 const prev = tail[start - 1];
244 return !isWordChar(prev) || !isWordChar(next);213 return !isWordChar(prev) || !isWordChar(next);
245}214}
246215
247function updateLink(state: TailState, index: number, char: string) {216function updateLink(state: TailState, index: number, char: string) {
248 const top = state.links.at(-1);217 const top = state.links.at(-1);
249
250 if (char === "[") {218 if (char === "[") {
251 state.links.push({ phase: "text", start: index });219 state.links.push({ phase: "text", start: index });
252 return;220 return;
253 }221 }
254
255 if (char === "]" && top?.phase === "text") {222 if (char === "]" && top?.phase === "text") {
256 state.links[state.links.length - 1] = {223 state.links[state.links.length - 1] = {
257 phase: "url_wait",224 phase: "url_wait",
...@@ -260,7 +227,6 @@ function updateLink(state: TailState, index: number, char: string) {...@@ -260,7 +227,6 @@ function updateLink(state: TailState, index: number, char: string) {
260 };227 };
261 return;228 return;
262 }229 }
263
264 if (char === "(" && top?.phase === "url_wait") {230 if (char === "(" && top?.phase === "url_wait") {
265 state.links[state.links.length - 1] = {231 state.links[state.links.length - 1] = {
266 phase: "url",232 phase: "url",
...@@ -270,18 +236,15 @@ function updateLink(state: TailState, index: number, char: string) {...@@ -270,18 +236,15 @@ function updateLink(state: TailState, index: number, char: string) {
270 };236 };
271 return;237 return;
272 }238 }
273
274 if (char === "(" && top?.phase === "url") {239 if (char === "(" && top?.phase === "url") {
275 top.parenDepth += 1;240 top.parenDepth += 1;
276 return;241 return;
277 }242 }
278
279 if (char === ")" && top?.phase === "url") {243 if (char === ")" && top?.phase === "url") {
280 if (top.parenDepth > 0) {244 if (top.parenDepth > 0) {
281 top.parenDepth -= 1;245 top.parenDepth -= 1;
282 return;246 return;
283 }247 }
284
285 state.links.pop();248 state.links.pop();
286 }249 }
287}250}
...@@ -289,10 +252,8 @@ function updateLink(state: TailState, index: number, char: string) {...@@ -289,10 +252,8 @@ function updateLink(state: TailState, index: number, char: string) {
289function resetParagraphState(state: TailState) {252function resetParagraphState(state: TailState) {
290 state.delims.length = 0;253 state.delims.length = 0;
291 state.links.length = 0;254 state.links.length = 0;
292
293 if (state.exclusive?.kind === "fence") return;255 if (state.exclusive?.kind === "fence") return;
294 if (state.exclusive?.kind === "math" && state.exclusive.token === "$$") return;256 if (state.exclusive?.kind === "math" && state.exclusive.token === "$$") return;
295
296 state.exclusive = null;257 state.exclusive = null;
297}258}
298259
...@@ -302,22 +263,18 @@ function inLinkUrlMode(state: TailState) {...@@ -302,22 +263,18 @@ function inLinkUrlMode(state: TailState) {
302}263}
303264
304function renderTail(tail: string, state: TailState) {265function renderTail(tail: string, state: TailState) {
305 if (state.pendingHtml !== null) tail = tail.slice(0, state.pendingHtml);266 if (state.pendingHtml !== null)
306267 tail = tail.slice(0, findPendingHtmlCutoff(tail, state.pendingHtml));
307 const link = state.links.at(-1);268 const link = state.links.at(-1);
308 if (link) return renderOpenLink(tail, link);269 if (link) return renderOpenLink(tail, link);
309
310 const nestedClosers = renderNestedClosers(state);270 const nestedClosers = renderNestedClosers(state);
311 if (nestedClosers) return appendCloser(tail, nestedClosers);271 if (nestedClosers) return appendCloser(tail, nestedClosers);
312
313 const open = findLastOpen(state);272 const open = findLastOpen(state);
314 if (!open) return state.pendingDelim ? tail.slice(0, state.pendingDelim.start) : tail;273 if (!open) return state.pendingDelim ? tail.slice(0, state.pendingDelim.start) : tail;
315
316 if (open.kind === "delim") {274 if (open.kind === "delim") {
317 if (!hasContentAfter(tail, open.start, open.token.length)) return tail.slice(0, open.start);275 if (!hasContentAfter(tail, open.start, open.token.length)) return tail.slice(0, open.start);
318 return appendCloserBeforeTrailingInlineWhitespace(tail, open.token);276 return appendCloserBeforeTrailingInlineWhitespace(tail, open.token);
319 }277 }
320
321 if (!hasContentAfter(tail, open.start, open.token.length)) {278 if (!hasContentAfter(tail, open.start, open.token.length)) {
322 return open.kind === "fence" ? tail : tail.slice(0, open.start);279 return open.kind === "fence" ? tail : tail.slice(0, open.start);
323 }280 }
...@@ -330,12 +287,10 @@ function renderTail(tail: string, state: TailState) {...@@ -330,12 +287,10 @@ function renderTail(tail: string, state: TailState) {
330287
331function renderOpenLink(tail: string, state: LinkState) {288function renderOpenLink(tail: string, state: LinkState) {
332 const before = tail.slice(0, state.start);289 const before = tail.slice(0, state.start);
333
334 if (state.phase === "text") {290 if (state.phase === "text") {
335 if (!hasContentAfter(tail, state.start, 1)) return before;291 if (!hasContentAfter(tail, state.start, 1)) return before;
336 return before + tail.slice(state.start + 1);292 return before + tail.slice(state.start + 1);
337 }293 }
338
339 const text = tail.slice(state.start + 1, state.textEnd);294 const text = tail.slice(state.start + 1, state.textEnd);
340 if (state.phase === "url_wait") return before + text + tail.slice(state.textEnd + 1);295 if (state.phase === "url_wait") return before + text + tail.slice(state.textEnd + 1);
341 return before + text;296 return before + text;
...@@ -343,22 +298,46 @@ function renderOpenLink(tail: string, state: LinkState) {...@@ -343,22 +298,46 @@ function renderOpenLink(tail: string, state: LinkState) {
343298
344function renderNestedClosers(state: TailState) {299function renderNestedClosers(state: TailState) {
345 const closers: string[] = [];300 const closers: string[] = [];
346
347 if (state.exclusive) {301 if (state.exclusive) {
348 if (state.exclusive.kind !== "code") return undefined;302 if (state.exclusive.kind !== "code") return undefined;
349 closers.push(state.exclusive.token);303 closers.push(state.exclusive.token);
350 }304 }
351
352 for (let i = state.delims.length - 1; i >= 0; i--) {305 for (let i = state.delims.length - 1; i >= 0; i--) {
353 const token = state.delims[i]!.token;306 const token = state.delims[i]!.token;
354 if (!NESTABLE_DELIMS.has(token)) return undefined;307 if (!NESTABLE_DELIMS.has(token)) return undefined;
355 closers.push(token);308 closers.push(token);
356 }309 }
357
358 if (closers.length < 2) return undefined;310 if (closers.length < 2) return undefined;
359 return closers.join("");311 return closers.join("");
360}312}
361313
314function findPendingHtmlCutoff(tail: string, pendingStart: number) {
315 let cutoff = pendingStart;
316 let cursor = pendingStart;
317
318 while (cursor > 0) {
319 const candidateStart = tail.lastIndexOf("<", cursor - 1);
320 if (candidateStart === -1) break;
321 if (!isCompleteHtmlTag(tail, candidateStart, cursor)) break;
322 cutoff = candidateStart;
323 cursor = candidateStart;
324 }
325
326 return cutoff;
327}
328
329function isCompleteHtmlTag(tail: string, start: number, end: number) {
330 if (tail[end - 1] !== ">") return false;
331 if (isEscapedAt(tail, start)) return false;
332 const next = tail[start + 1];
333 if (next !== undefined && !isAsciiLetter(next)) return false;
334 for (let i = start + 1; i < end - 1; i++) {
335 const char = tail[i];
336 if (char === ">" || char === "\n") return false;
337 }
338 return true;
339}
340
362function appendCloser(tail: string, closer: string) {341function appendCloser(tail: string, closer: string) {
363 return tail + closer.slice(overlapLength(tail, closer));342 return tail + closer.slice(overlapLength(tail, closer));
364}343}
...@@ -366,7 +345,6 @@ function appendCloser(tail: string, closer: string) {...@@ -366,7 +345,6 @@ function appendCloser(tail: string, closer: string) {
366function appendCloserBeforeTrailingInlineWhitespace(tail: string, closer: string) {345function appendCloserBeforeTrailingInlineWhitespace(tail: string, closer: string) {
367 const trailingWhitespace = tail.match(/[^\S\n]+$/)?.[0];346 const trailingWhitespace = tail.match(/[^\S\n]+$/)?.[0];
368 if (!trailingWhitespace) return appendCloser(tail, closer);347 if (!trailingWhitespace) return appendCloser(tail, closer);
369
370 const body = tail.slice(0, -trailingWhitespace.length);348 const body = tail.slice(0, -trailingWhitespace.length);
371 return appendCloser(body, closer) + trailingWhitespace;349 return appendCloser(body, closer) + trailingWhitespace;
372}350}
...@@ -401,12 +379,10 @@ function isLineStart(text: string, index: number) {...@@ -401,12 +379,10 @@ function isLineStart(text: string, index: number) {
401379
402function isEscapedAt(text: string, index: number) {380function isEscapedAt(text: string, index: number) {
403 let count = 0;381 let count = 0;
404
405 for (let i = index - 1; i >= 0; i--) {382 for (let i = index - 1; i >= 0; i--) {
406 if (text[i] !== "\\") break;383 if (text[i] !== "\\") break;
407 count += 1;384 count += 1;
408 }385 }
409
410 return count % 2 === 1;386 return count % 2 === 1;
411}387}
412388
src/hast.ts+29-6
...@@ -61,14 +61,31 @@ function recursiveMemoRender(...@@ -61,14 +61,31 @@ function recursiveMemoRender(
61 const nextChildren: RenderState[] = [];61 const nextChildren: RenderState[] = [];
62 const children: ReactNode[] = [];62 const children: ReactNode[] = [];
63 const countsByName = new Map<string, number>();63 const countsByName = new Map<string, number>();
64 const usedKeys = new Set(previousChildren.map((child) => child.key).filter((key) => key !== undefined));64 const usedKeys = new Set(
65 previousChildren.map((child) => child.key).filter((key) => key !== undefined),
66 );
65 let prefixLength = 0;67 let prefixLength = 0;
66 for (; prefixLength < node.children.length && prefixLength < previousChildren.length; prefixLength += 1) {68 for (
69 ;
70 prefixLength < node.children.length && prefixLength < previousChildren.length;
71 prefixLength += 1
72 ) {
67 if (!nodeDeepEquals(node.children[prefixLength], previousChildren[prefixLength]?.node)) break;73 if (!nodeDeepEquals(node.children[prefixLength], previousChildren[prefixLength]?.node)) break;
68 }74 }
69 let suffixLength = 0;75 let suffixLength = 0;
70 for (; suffixLength < node.children.length - prefixLength && suffixLength < previousChildren.length - prefixLength; suffixLength += 1) {76 for (
71 if (!nodeDeepEquals(node.children[node.children.length - 1 - suffixLength], previousChildren[previousChildren.length - 1 - suffixLength]?.node)) break;77 ;
78 suffixLength < node.children.length - prefixLength &&
79 suffixLength < previousChildren.length - prefixLength;
80 suffixLength += 1
81 ) {
82 if (
83 !nodeDeepEquals(
84 node.children[node.children.length - 1 - suffixLength],
85 previousChildren[previousChildren.length - 1 - suffixLength]?.node,
86 )
87 )
88 break;
72 }89 }
7390
74 for (let i = 0; i < node.children.length; i += 1) {91 for (let i = 0; i < node.children.length; i += 1) {
...@@ -85,9 +102,15 @@ function recursiveMemoRender(...@@ -85,9 +102,15 @@ function recursiveMemoRender(
85 previousIndex = previousChildren.length - (node.children.length - i);102 previousIndex = previousChildren.length - (node.children.length - i);
86 }103 }
87 if (previousIndex >= 0) childKey = previousChildren[previousIndex]?.key ?? childKey;104 if (previousIndex >= 0) childKey = previousChildren[previousIndex]?.key ?? childKey;
88 while (childKey && usedKeys.has(childKey) && previousChildren[previousIndex]?.key !== childKey) childKey = `${childKey}+`;105 while (childKey && usedKeys.has(childKey) && previousChildren[previousIndex]?.key !== childKey)
106 childKey = `${childKey}+`;
89 if (childKey) usedKeys.add(childKey);107 if (childKey) usedKeys.add(childKey);
90 const childState = recursiveMemoRender(child, previousChildren[previousIndex] ?? null, options, childKey);108 const childState = recursiveMemoRender(
109 child,
110 previousChildren[previousIndex] ?? null,
111 options,
112 childKey,
113 );
91 nextChildren.push(childState);114 nextChildren.push(childState);
92 if (childState.react !== undefined) children.push(childState.react);115 if (childState.react !== undefined) children.push(childState.react);
93 }116 }
tests/Memoizer.test.tsx+3-1
...@@ -31,7 +31,9 @@ const documentStateProcessor = unified()...@@ -31,7 +31,9 @@ const documentStateProcessor = unified()
31 ?.slice("suffix: ".length);31 ?.slice("suffix: ".length);
32 if (!suffix) return tree;32 if (!suffix) return tree;
3333
34 const target = paragraphs.find((paragraph) => !paragraphText(paragraph).startsWith("suffix: "));34 const target = paragraphs.find(
35 (paragraph) => !paragraphText(paragraph).startsWith("suffix: "),
36 );
35 const firstChild = target?.children[0];37 const firstChild = target?.children[0];
36 if (firstChild?.type === "text") {38 if (firstChild?.type === "text") {
37 const textNode = firstChild as Literal;39 const textNode = firstChild as Literal;
tests/memoizedHastToReact.test.tsx+3-1
...@@ -185,7 +185,9 @@ it("reuses an equal subtree when a sibling of another tag is inserted above it",...@@ -185,7 +185,9 @@ it("reuses an equal subtree when a sibling of another tag is inserted above it",
185});185});
186186
187it("preserves an unchanged list item when a sibling item is inserted above it", () => {187it("preserves an unchanged list item when a sibling item is inserted above it", () => {
188 const first = renderTree(root(element("ul", [element("li", [text("stable")]), element("li", [text("tail")])])));188 const first = renderTree(
189 root(element("ul", [element("li", [text("stable")]), element("li", [text("tail")])])),
190 );
189 const firstList = childAt(first.react, 0);191 const firstList = childAt(first.react, 0);
190 const firstStable = childAt(firstList, 0);192 const firstStable = childAt(firstList, 0);
191193
tests/prediction.test.ts+3-6
...@@ -339,12 +339,9 @@ describe.each(["stateless", "stateful"] as const)("%s processing", (mode) => {...@@ -339,12 +339,9 @@ describe.each(["stateless", "stateful"] as const)("%s processing", (mode) => {
339 ["incomplete self-closing tag gets hidden", "<hello-element /", ""],339 ["incomplete self-closing tag gets hidden", "<hello-element /", ""],
340 ["incomplete attribute list gets hidden", '<hello-element attribute="meow"', ""],340 ["incomplete attribute list gets hidden", '<hello-element attribute="meow"', ""],
341 ["complete self-closing tag passes through", "<hello-element />", "<hello-element />"],341 ["complete self-closing tag passes through", "<hello-element />", "<hello-element />"],
342 ["completed tag followed by bare angle hides the tail", "<hello-element><", "<hello-element>"],342 ["completed tag followed by bare angle hides", "<hello-element><", ""],
343 [343 ["completed tag followed by partial tag hides", "<hello-element><other-not-checked", ""],
344 "completed tag followed by partial tag hides the tail",344 ["completed tag followed by partial tag hides it", "<hello-element><hello-element/", ""],
345 "<hello-element><hello-element/",
346 "<hello-element>",
347 ],
348 [345 [
349 "completed tags pass through together",346 "completed tags pass through together",
350 "<hello-element><hello-element/>",347 "<hello-element><hello-element/>",