Skip to content

Commit 1f69a7d

Browse files
fix(javascript): keep a list's item when writing it grows the list column
The column was read before the item was written, so an item whose own lists outgrew it landed in the copy the growth replaced, leaving a hole: a module's last `export { … }` went missing after a smaller tree set the size hint (Vue 3 beautified, swc's asmjs fixture with improvements). A tree whose parse throws is now released too. Claude-Session: https://claude.ai/code/session_01Tgxgyb4N35ndP6cC4KcrXC
1 parent 958022f commit 1f69a7d

3 files changed

Lines changed: 89 additions & 37 deletions

File tree

‎lib/javascript/syntax-parser.js‎

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10947,9 +10947,10 @@ class WebpackParser extends BaseParser {
1094710947
const start = tree._listStarts[list];
1094810948
for (let i = 0; i < count; i++) {
1094910949
const item = items[i];
10950-
// `_treeId` may write lists of its own, growing the column
10951-
tree._listItems[start + i] =
10952-
typeof item === "number" ? item : this._treeId(item);
10950+
// Read before the column is: `_treeId` may write lists of its own,
10951+
// replacing it with a longer copy.
10952+
const id = typeof item === "number" ? item : this._treeId(item);
10953+
tree._listItems[start + i] = id;
1095310954
}
1095410955
return list;
1095510956
}
@@ -21043,20 +21044,26 @@ const grammar = (input, visitors, writer, options) => {
2104321044
);
2104421045
}
2104521046
const tree = new SyntaxTree();
21046-
const program = /** @type {number} */ (
21047-
/** @type {unknown} */ (
21048-
new WebpackParser(
21049-
{
21050-
ecmaVersion: "latest",
21051-
sourceType: options.sourceType === "script" ? "script" : "module",
21052-
allowHashBang: true,
21053-
lazyNodes: true,
21054-
syntaxTree: tree
21055-
},
21056-
input
21057-
).parse()
21058-
)
21059-
);
21047+
let program = 0;
21048+
try {
21049+
program = /** @type {number} */ (
21050+
/** @type {unknown} */ (
21051+
new WebpackParser(
21052+
{
21053+
ecmaVersion: "latest",
21054+
sourceType: options.sourceType === "script" ? "script" : "module",
21055+
allowHashBang: true,
21056+
lazyNodes: true,
21057+
syntaxTree: tree
21058+
},
21059+
input
21060+
).parse()
21061+
)
21062+
);
21063+
} catch (error) {
21064+
tree.release();
21065+
throw error;
21066+
}
2106021067
walkTree = tree;
2106121068
walkInput = input;
2106221069
walkVisitors = visitors;

‎lib/javascript/syntax-printer.js‎

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18127,27 +18127,35 @@ const installFusedParse = (modules) => {
1812718127
const tree = new SyntaxTree();
1812818128
let program = 0;
1812918129
let module = false;
18130-
const { result: read, table } = readSource(
18131-
text,
18132-
options,
18133-
filename,
18134-
tables.nextBase(),
18135-
(parserOptions) =>
18136-
new WebpackParser({ ...parserOptions, syntaxTree: tree }, text),
18137-
(written, tokenAt, ownTable) => {
18138-
program = /** @type {number} */ (/** @type {unknown} */ (written));
18139-
const count = tree.fieldCount(Field.body, program);
18140-
for (let i = 0; i < count && !module; i++) {
18141-
const type = tree.type(tree.field(i, Field.body, program));
18142-
module =
18143-
type === NodeType.ImportDeclaration ||
18144-
type === NodeType.ExportNamedDeclaration ||
18145-
type === NodeType.ExportDefaultDeclaration ||
18146-
type === NodeType.ExportAllDeclaration;
18130+
/** @type {{ result: T, table: TokenTable }} */
18131+
let source;
18132+
try {
18133+
source = readSource(
18134+
text,
18135+
options,
18136+
filename,
18137+
tables.nextBase(),
18138+
(parserOptions) =>
18139+
new WebpackParser({ ...parserOptions, syntaxTree: tree }, text),
18140+
(written, tokenAt, ownTable) => {
18141+
program = /** @type {number} */ (/** @type {unknown} */ (written));
18142+
const count = tree.fieldCount(Field.body, program);
18143+
for (let i = 0; i < count && !module; i++) {
18144+
const type = tree.type(tree.field(i, Field.body, program));
18145+
module =
18146+
type === NodeType.ImportDeclaration ||
18147+
type === NodeType.ExportNamedDeclaration ||
18148+
type === NodeType.ExportDefaultDeclaration ||
18149+
type === NodeType.ExportAllDeclaration;
18150+
}
18151+
return readTokens(tree, program, tokenAt, ownTable);
1814718152
}
18148-
return readTokens(tree, program, tokenAt, ownTable);
18149-
}
18150-
);
18153+
);
18154+
} catch (error) {
18155+
tree.release();
18156+
throw error;
18157+
}
18158+
const { result: read, table } = source;
1815118159
tables.add(table);
1815218160
return { tree, program, tables, read, module };
1815318161
};

‎test/unitCases/JavascriptSyntaxTree.unittest.js‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,43 @@ describe("SyntaxTree", () => {
365365
next.release();
366366
});
367367

368+
it("keeps a list's items while an item written into it grows the column", () => {
369+
/**
370+
* @param {string} source a module
371+
* @returns {[SyntaxTree, number]} its tree and program
372+
*/
373+
const parseModule = (source) => {
374+
const tree = new SyntaxTree();
375+
const program = /** @type {number} */ (
376+
/** @type {unknown} */ (
377+
new WebpackParser(
378+
{
379+
ecmaVersion: "latest",
380+
sourceType: "module",
381+
lazyNodes: true,
382+
syntaxTree: tree
383+
},
384+
source
385+
).parse()
386+
)
387+
);
388+
return [tree, program];
389+
};
390+
const body = `var a, b;${"a;".repeat(40000)}`;
391+
// Large enough to be let go, it leaves the next tree its list room exactly:
392+
// the body's list fits, and the export's one more name does not.
393+
parseModule(`${body}export { a };`)[0].release();
394+
const [tree, program] = parseModule(`${body}export { a, b };`);
395+
const last = tree.field(
396+
tree.fieldCount(Field.body, program) - 1,
397+
Field.body,
398+
program
399+
);
400+
expect(tree.type(last)).toBe(NodeType.ExportNamedDeclaration);
401+
expect(tree.fieldCount(Field.specifiers, last)).toBe(2);
402+
tree.release();
403+
});
404+
368405
it("lets go of a large tree's columns, and the next grows to its size", () => {
369406
const tree = new SyntaxTree();
370407
parseInto(tree, "a;".repeat(40000));

0 commit comments

Comments
 (0)