partial-tsx 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/partial.ts +20 -8
package/package.json
CHANGED
package/src/partial.ts
CHANGED
|
@@ -4,7 +4,7 @@ const BLOCK_CLOSERS = new Set(Object.values(BLOCK_FOLLOWERS));
|
|
|
4
4
|
export type PartialTsxMode = "streaming" | "final";
|
|
5
5
|
export type PartialTsxOptions = { mode?: PartialTsxMode };
|
|
6
6
|
type BlockKind = "function" | "control" | "arrow" | "class" | "method" | "block" | "other";
|
|
7
|
-
type CompletionFrame = { type: "block"; closer: string; statement: boolean; open?: number; close?: number; jsxExpression?: boolean; statementStart?: number; kind?: BlockKind } | { type: "jsx"; tag: string; open?: number };
|
|
7
|
+
type CompletionFrame = { type: "block"; closer: string; statement: boolean; open?: number; close?: number; headerEnd?: number; jsxExpression?: boolean; statementStart?: number; kind?: BlockKind } | { type: "jsx"; tag: string; open?: number };
|
|
8
8
|
|
|
9
9
|
// Single source of truth for the JSX-text fast path in the four lexer loops below: only `<` (child tag) and `{` (child
|
|
10
10
|
// expression) leave text mode, so none of them reads `(`/`[`/quotes/`//` in ordinary text as unfinished JavaScript.
|
|
@@ -321,10 +321,10 @@ const isLikelyJsxTagStart = (code: string, index: number) => {
|
|
|
321
321
|
return previousChar !== ">";
|
|
322
322
|
};
|
|
323
323
|
|
|
324
|
-
const isLikelyStatementBlock = (code: string, index: number, stack: CompletionFrame[]) => {
|
|
324
|
+
const isLikelyStatementBlock = (code: string, index: number, headerEnd: number, stack: CompletionFrame[]) => {
|
|
325
325
|
// A JSX child expression stays an expression even when the preceding prose looks like a JavaScript block header.
|
|
326
326
|
if (code[index] !== "{" || isInsideJsxText(stack)) return false;
|
|
327
|
-
const before = code.slice(0,
|
|
327
|
+
const before = code.slice(0, headerEnd).trimEnd();
|
|
328
328
|
return /\bfunction(?:\s+[\w$]+)?\s*\([^)]*\)$/.test(before) || /\b(?:if|for|while|switch|with)\s*\([^)]*\)$/.test(before) || /\bcatch(?:\s*\([^)]*\))?$/.test(before) || /(?:^|[^\w$])(?:else|try|finally|do)$/.test(before) || before.endsWith("=>");
|
|
329
329
|
};
|
|
330
330
|
|
|
@@ -1240,10 +1240,13 @@ const scanStreamingSource = (code: string): StreamingScan => {
|
|
|
1240
1240
|
statementRegexBoundary = false;
|
|
1241
1241
|
const inheritedStart = currentStatementStart();
|
|
1242
1242
|
const jsxExpression = char === "{" && insideJsxText;
|
|
1243
|
-
|
|
1243
|
+
// Both classification and later JSX-shell checks must stop before trailing comments. Reuse lexical provenance
|
|
1244
|
+
// instead of rescanning the prefix or stripping comment-like text inside literals (#1814).
|
|
1245
|
+
const headerEnd = lastSignificantEnd;
|
|
1246
|
+
const kind = jsxExpression ? undefined : getBlockKind(code, headerEnd, inheritedStart);
|
|
1244
1247
|
const statement = !jsxExpression && kind !== "other";
|
|
1245
|
-
const statementStart = statement ? findKeywordStart(code,
|
|
1246
|
-
stack.push({ type: "block", closer: BLOCK_FOLLOWERS[char], statement, open: index, jsxExpression, statementStart, kind });
|
|
1248
|
+
const statementStart = statement ? findKeywordStart(code, headerEnd, inheritedStart, kind ?? "other") : inheritedStart;
|
|
1249
|
+
stack.push({ type: "block", closer: BLOCK_FOLLOWERS[char], statement, open: index, headerEnd, jsxExpression, statementStart, kind });
|
|
1247
1250
|
if (statement && char === "{") scopes.push({ statementStart: index + 1 });
|
|
1248
1251
|
lastSignificantEnd = index + 1;
|
|
1249
1252
|
index += 1;
|
|
@@ -1286,7 +1289,7 @@ const scanStreamingSource = (code: string): StreamingScan => {
|
|
|
1286
1289
|
return { stack, closedBlocks, scopes, regexLiteralStarts, lastSignificantEnd, unclosedLiteralStart, unclosedLiteralStatementStart, incompleteJsxTagStart };
|
|
1287
1290
|
};
|
|
1288
1291
|
|
|
1289
|
-
const getFrameHeader = (code: string, frame: Extract<CompletionFrame, { type: "block" }>) => code.slice(frame.statementStart ?? 0, frame.open ?? 0).trim();
|
|
1292
|
+
const getFrameHeader = (code: string, frame: Extract<CompletionFrame, { type: "block" }>) => code.slice(frame.statementStart ?? 0, frame.headerEnd ?? frame.open ?? 0).trim();
|
|
1290
1293
|
const isComponentFunctionFrame = (code: string, frame: Extract<CompletionFrame, { type: "block" }>) => /^[A-Z]/.test(FUNCTION_HEADER.exec(getFrameHeader(code, frame))?.[1] ?? "");
|
|
1291
1294
|
const isFunctionDeclarationFrame = (code: string, frame: Extract<CompletionFrame, { type: "block" }>) => FUNCTION_HEADER.test(getFrameHeader(code, frame));
|
|
1292
1295
|
const isComponentClassFrame = (code: string, frame: Extract<CompletionFrame, { type: "block" }>) => /^[A-Z]/.test(CLASS_HEADER.exec(getFrameHeader(code, frame))?.[1] ?? "");
|
|
@@ -1642,6 +1645,7 @@ export function completePartialTsx(code: string, options: PartialTsxOptions = {}
|
|
|
1642
1645
|
};
|
|
1643
1646
|
const stack: CompletionFrame[] = [];
|
|
1644
1647
|
let index = 0;
|
|
1648
|
+
let lastSignificantEnd = 0;
|
|
1645
1649
|
const push = (value: string) => {
|
|
1646
1650
|
output += value;
|
|
1647
1651
|
};
|
|
@@ -1650,6 +1654,7 @@ export function completePartialTsx(code: string, options: PartialTsxOptions = {}
|
|
|
1650
1654
|
// Keep completion and committed-prefix scanning in the same lexical mode; never append ')' or ']' to plain text.
|
|
1651
1655
|
if (isInsideJsxText(stack) && !isJsxTextBoundary(char)) {
|
|
1652
1656
|
push(char);
|
|
1657
|
+
if (!/\s/.test(char)) lastSignificantEnd = index + 1;
|
|
1653
1658
|
index += 1;
|
|
1654
1659
|
continue;
|
|
1655
1660
|
}
|
|
@@ -1674,16 +1679,22 @@ export function completePartialTsx(code: string, options: PartialTsxOptions = {}
|
|
|
1674
1679
|
index = closeIndex + 2;
|
|
1675
1680
|
continue;
|
|
1676
1681
|
}
|
|
1682
|
+
// Match scanStreamingSource's header boundary without losing comments from the completed output.
|
|
1683
|
+
const headerEnd = lastSignificantEnd;
|
|
1684
|
+
if (!/\s/.test(char)) lastSignificantEnd = index + 1;
|
|
1677
1685
|
if (regexLiteralStarts?.has(index) || isLikelyRegexLiteralStart(code, index)) {
|
|
1678
1686
|
index = consumeRegexLiteral(code, index, push);
|
|
1687
|
+
lastSignificantEnd = index;
|
|
1679
1688
|
continue;
|
|
1680
1689
|
}
|
|
1681
1690
|
if (char === '"' || char === "'") {
|
|
1682
1691
|
index = consumeStringLiteral(code, index, push);
|
|
1692
|
+
lastSignificantEnd = index;
|
|
1683
1693
|
continue;
|
|
1684
1694
|
}
|
|
1685
1695
|
if (char === "`") {
|
|
1686
1696
|
index = consumeTemplateLiteral(code, index, push);
|
|
1697
|
+
lastSignificantEnd = index;
|
|
1687
1698
|
continue;
|
|
1688
1699
|
}
|
|
1689
1700
|
if (char === "<" && isInsideJsxText(stack) && !shouldStartJsxTag(code, index, stack) && index < code.length - 1) {
|
|
@@ -1712,11 +1723,12 @@ export function completePartialTsx(code: string, options: PartialTsxOptions = {}
|
|
|
1712
1723
|
const tagName = match?.[1] ?? (tag === "<>" || tag === "</>" ? "" : null);
|
|
1713
1724
|
if (!(tagName && VOID_TAGS.has(tagName) && tag.startsWith("</"))) push(tagName && VOID_TAGS.has(tagName) && !tag.endsWith("/>") ? tag.replace(/\s*\/?\s*>$/, " />") : tag);
|
|
1714
1725
|
index = closeIndex + 1;
|
|
1726
|
+
lastSignificantEnd = index;
|
|
1715
1727
|
updateJsxStackForTag(stack, tag);
|
|
1716
1728
|
continue;
|
|
1717
1729
|
}
|
|
1718
1730
|
if (char in BLOCK_FOLLOWERS) {
|
|
1719
|
-
stack.push({ type: "block", closer: BLOCK_FOLLOWERS[char], statement: isLikelyStatementBlock(code, index, stack), open: output.length, jsxExpression: char === "{" && isInsideJsxText(stack) });
|
|
1731
|
+
stack.push({ type: "block", closer: BLOCK_FOLLOWERS[char], statement: isLikelyStatementBlock(code, index, headerEnd, stack), open: output.length, jsxExpression: char === "{" && isInsideJsxText(stack) });
|
|
1720
1732
|
push(char);
|
|
1721
1733
|
index += 1;
|
|
1722
1734
|
continue;
|