55import type { Declaration , Expression , Program } from 'estree' ;
66import type { AstNode } from 'rollup' ;
77
8- export interface BackendFunction {
9- /** Relative path from project root to the .backend.ts file (without extension) */
10- relativePath : string ;
11- /** Exported function name */
12- name : string ;
13- /** Absolute path to the .backend.ts source file */
14- absolutePath : string ;
15- /** Connection IDs this backend function is allowed to use. */
16- allowedConnectionIds : string [ ] ;
17- }
8+ import { isProgramNode } from './type-guards' ;
9+ import type { BackendExport } from './types' ;
1810
1911/**
2012 * Extract exported value (non-type) symbols from an ESTree AST.
@@ -27,11 +19,11 @@ export interface BackendFunction {
2719 * @param ast - AstNode from `this.parse()` in unplugin's transform hook
2820 * @param filePath - Path to the source file (used in error messages)
2921 */
30- function isProgramNode ( node : AstNode ) : node is AstNode & Program {
31- return node . type === 'Program' ;
22+ export function extractExportedFunctions ( ast : AstNode , filePath : string ) : string [ ] {
23+ return enumerateBackendExports ( ast , filePath ) . map ( ( backendExport ) => backendExport . name ) ;
3224}
3325
34- export function extractExportedFunctions ( ast : AstNode , filePath : string ) : string [ ] {
26+ export function enumerateBackendExports ( ast : AstNode , filePath : string ) : BackendExport [ ] {
3527 if ( ! isProgramNode ( ast ) ) {
3628 throw new Error (
3729 `Expected a Program node from this.parse() for ${ filePath } , got ${ ast . type } ` ,
@@ -41,7 +33,7 @@ export function extractExportedFunctions(ast: AstNode, filePath: string): string
4133 // Build a map of top-level declarations so we can validate export specifiers.
4234 const declarations = buildDeclarationMap ( ast ) ;
4335
44- const names : string [ ] = [ ] ;
36+ const backendExports : BackendExport [ ] = [ ] ;
4537 for ( const node of ast . body ) {
4638 // handles: export default ...
4739 if ( node . type === 'ExportDefaultDeclaration' ) {
@@ -61,9 +53,16 @@ export function extractExportedFunctions(ast: AstNode, filePath: string): string
6153
6254 // handles: export function add() {} / export const add = ...
6355 if ( node . declaration ) {
64- names . push ( ...namesFromDeclaration ( node . declaration , filePath ) ) ;
56+ backendExports . push (
57+ ...namesFromDeclaration ( node . declaration , filePath ) . map ( ( name ) => ( {
58+ kind : 'local' as const ,
59+ name,
60+ localName : name ,
61+ } ) ) ,
62+ ) ;
6563 }
6664
65+ const source = typeof node . source ?. value === 'string' ? node . source . value : null ;
6766 for ( const spec of node . specifiers ) {
6867 if ( spec . exported . type !== 'Identifier' ) {
6968 continue ;
@@ -77,14 +76,37 @@ export function extractExportedFunctions(ast: AstNode, filePath: string): string
7776 // Validate specifier binding is callable when we can resolve it.
7877 // e.g. `const VERSION = '1.0'; export { VERSION };` — rejected
7978 // e.g. `function add() {}; export { add };` — allowed
79+ const localName =
80+ spec . local . type === 'Identifier'
81+ ? spec . local . name
82+ : typeof spec . local . value === 'string'
83+ ? spec . local . value
84+ : null ;
85+ if ( ! localName ) {
86+ continue ;
87+ }
88+
89+ if ( source ) {
90+ backendExports . push ( {
91+ kind : 're-export' ,
92+ name : spec . exported . name ,
93+ localName,
94+ source,
95+ } ) ;
96+ continue ;
97+ }
98+
8099 if ( spec . local . type === 'Identifier' ) {
81- validateSpecifierBinding ( spec . local . name , declarations , filePath ) ;
100+ validateSpecifierBinding ( localName , declarations , filePath ) ;
82101 }
83- // handles: export { add, multiply }
84- names . push ( spec . exported . name ) ;
102+ backendExports . push ( {
103+ kind : 'local' ,
104+ name : spec . exported . name ,
105+ localName,
106+ } ) ;
85107 }
86108 }
87- return names ;
109+ return backendExports ;
88110}
89111
90112/** Init types that are definitively non-callable at runtime. */
0 commit comments