Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/edge-bundler/node/utils/import_attributes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,30 @@ import data2 from './data.json' with { type: 'json' };
expect(result).toEqual(expectedResult)
})

test('handles TsTypeAssertion despite no support in acorn-walk', () => {
// edge case, because "<Params> inputs" is valid JSX syntax (a component with name "Params" and children "inputs"),
// but also valid TypeScript syntax (type assertion). In this case, the acorn-jsx parser will parse it as JSX,
// but then throw an error when it encounters the "assert" keyword in the import assertion.
// We want to make sure we can handle this case and still rewrite the import assertions correctly.
const source = `
import data3 from './data.json' assert { type: 'json' };
const params = <Params> inputs;
Copy link
Copy Markdown
Contributor Author

@pieh pieh May 8, 2026

Choose a reason for hiding this comment

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

This is valid and documented syntax - https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions - it just conflicts in JSX mode because angled brackets become ambiguous so parsers intentionally disable handling of this syntax for type casting in JSX mode

const [,foo]=[1,2]
import data2 from './data.json' assert { type: 'json' };
`
const expectedResult = `
import data3 from './data.json' with { type: 'json' };
const params = <Params> inputs;
const [,foo]=[1,2]
import data2 from './data.json' with { type: 'json' };
`

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const result = rewriteSourceImportAssertions(source)

expect(result).toEqual(expectedResult)
})

test('complex JSX import assertion case', () => {
const source = `<><Component prop={() => import('./foo.json', { assert: { type: 'json' } })} /></>`
const expectedResult = `<><Component prop={() => import('./foo.json', { with: { type: 'json' } })} /></>`
Expand Down
37 changes: 30 additions & 7 deletions packages/edge-bundler/node/utils/import_attributes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
import { Parser, Node } from 'acorn'
import type { ExportAllDeclaration, ExportNamedDeclaration, ImportDeclaration, ImportExpression } from 'acorn'
import type {
ExportAllDeclaration,
ExportNamedDeclaration,
ImportDeclaration,
ImportExpression,
Options as AcornOptions,
Program,
} from 'acorn'
import { tsPlugin } from '@sveltejs/acorn-typescript'

const acorn = Parser.extend(tsPlugin({ jsx: true }))
const acornNoJSX = Parser.extend(tsPlugin({ jsx: false }))
const acornJSX = Parser.extend(tsPlugin({ jsx: true }))

const parseOptions: AcornOptions = {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
}

const parseAST = (source: string): Program => {
try {
return acornJSX.parse(source, parseOptions)
} catch (error) {
// for non-jsx typescript casting to type via "<type> value" (normally done with "value as type") will throw an "Unexpected token" error in acorn-jsx,
// but is valid syntax in TypeScript. In this case, we can retry parsing with the non-jsx parser.
if (error instanceof SyntaxError) {
return acornNoJSX.parse(source, parseOptions)
}
throw error
}
}
Comment on lines +21 to +32
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Potential alternative I considered is picking parser based on extension (with .ts specifically using NoJSX mode), but fallback felt like more sure thing that is potentially not susceptible to wild and unexpected configurations


/**
* Given source code rewrites import assert into import with
Expand All @@ -15,11 +42,7 @@ export function rewriteSourceImportAssertions(source: string): string {
let modified = source

try {
const parsedAST = acorn.parse(source, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
})
const parsedAST = parseAST(source)

const statements = collectImportAssertions(source, parsedAST.body)

Expand Down
Loading