Skip to content

fix: prevent out-of-order mappings with decorators - #227

Merged
Rich-Harris merged 10 commits into
mainfrom
fix-decorators
Sep 25, 2026
Merged

Rich-Harris merged 10 commits into
mainfrom
fix-decorators

Conversation

@Rich-Harris

Copy link
Copy Markdown
Member

This simplifies and DRYs out the decorator handling code, and also fixes a problem with export class when it has decorators — we get a spurious mapping from the start of the decorator to the start of export

@pkg-svelte-dev

pkg-svelte-dev Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Install the latest version of esrap from f07d190:

pnpm add https://pkg.svelte.dev/esrap/c/f07d19053f397b616a91b10797dd131dd469eb47

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/esrap/pr/227

@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f07d190

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
esrap Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Nic-Polumeyv Nic-Polumeyv left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had an AI throw decorator shapes at this, it found a few that still misbehave:

  • @dec\nexport /* c */ declare class X {} now prints export declare class /* c */ X {} (d.loc = null skips the comment flush)
  • export @dec class X {} still gets the export start and the decorator start at the same column
  • constructor(@inject() private x: string) same thing, acorn puts the decorators on parameter (already the case on main)
  • export has no mapping at all once decorators are involved

Edit: just realized #228 exists and covers most of these except for the second one

Comment thread src/languages/ts/index.js Outdated
Comment on lines +1169 to +1170
// Check if declaration has decorators (ClassDeclaration, ClassExpression can have them)
const decl = /** @type {any} */ (node.declaration);
if (decl.decorators && decl.decorators.length > 0) {
for (const decorator of decl.decorators) {
context.visit(decorator);
}
token(context, 'export', node);
context.write(' ');
const d = /** @type {any} */ (node.declaration);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stale comment

Suggested change
// Check if declaration has decorators (ClassDeclaration, ClassExpression can have them)
const decl = /** @type {any} */ (node.declaration);
if (decl.decorators && decl.decorators.length > 0) {
for (const decorator of decl.decorators) {
context.visit(decorator);
}
token(context, 'export', node);
context.write(' ');
const d = /** @type {any} */ (node.declaration);
const d = /** @type {any} */ (node.declaration);

Comment thread src/languages/ts/index.js Outdated
Comment on lines +1180 to +1187
// Temporarily remove decorators so ClassDeclaration doesn't print them again
const savedDecorators = decl.decorators;
decl.decorators = [];
context.visit(node.declaration);
decl.decorators = savedDecorators;
d.decorators = [];
d.loc = null;
context.visit(d);
d.decorators = decorators;
d.loc = loc;

if (loc) context.location(loc.end.line, loc.end.column);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one's a noop I think

Suggested change
// Temporarily remove decorators so ClassDeclaration doesn't print them again
const savedDecorators = decl.decorators;
decl.decorators = [];
context.visit(node.declaration);
decl.decorators = savedDecorators;
d.decorators = [];
d.loc = null;
context.visit(d);
d.decorators = decorators;
d.loc = loc;
if (loc) context.location(loc.end.line, loc.end.column);
// Temporarily remove decorators and loc so ClassDeclaration doesn't print them again
d.decorators = [];
d.loc = null;
context.visit(d);
d.decorators = decorators;
d.loc = loc;

Rich-Harris and others added 4 commits September 24, 2026 22:02
* print decorators before export default

* fix

* fix

* fix

* fix

* DRY out

* tweak

* fix

* prettier
@Rich-Harris

Copy link
Copy Markdown
Member Author

export has no mapping at all once decorators are involved

yeah this is an unfortunate consequence of the AST shape — we just don't know where the node-minus-decorators actually starts. we could potentially solve it if we had access to the original tokens, per #213

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants