@@ -240,12 +240,68 @@ function collectSqlBindings(program: SyntaxNode): SqlBindings {
240240 }
241241 }
242242 }
243+ if ( node . type === 'VariableDeclarator' && isDrizzleImportCall ( node . init ) )
244+ bindDynamicImport ( node . id , bindings )
245+
243246 for ( const child of getChildNodes ( node ) ) visit ( child )
244247 }
245248 visit ( program )
246249 return bindings
247250}
248251
252+ /**
253+ * `import('drizzle-orm')`, with or without an `await`.
254+ *
255+ * Babel parses a dynamic import as a `CallExpression` whose callee is `Import`;
256+ * the `ImportExpression` spelling is accepted too so a parser upgrade cannot
257+ * silently reopen the hole this closes.
258+ */
259+ function isDrizzleImportCall ( node : unknown ) : boolean {
260+ if ( ! isSyntaxNode ( node ) ) return false
261+ const current = node . type === 'AwaitExpression' ? unwrapAwait ( node ) : node
262+ if ( ! isSyntaxNode ( current ) ) return false
263+ const isImport =
264+ current . type === 'ImportExpression' ||
265+ ( current . type === 'CallExpression' &&
266+ isSyntaxNode ( current . callee ) &&
267+ current . callee . type === 'Import' )
268+ if ( ! isImport ) return false
269+ const args = Array . isArray ( current . arguments ) ? current . arguments : [ ]
270+ const source = isSyntaxNode ( current . source ) ? current . source : args . find ( isSyntaxNode )
271+ const value = source ?. value
272+ return (
273+ typeof value === 'string' &&
274+ ( value === DRIZZLE_MODULE || value . startsWith ( `${ DRIZZLE_MODULE } /` ) )
275+ )
276+ }
277+
278+ const unwrapAwait = ( node : SyntaxNode ) : unknown =>
279+ isSyntaxNode ( node . argument ) ? node . argument : undefined
280+
281+ /**
282+ * Binds `const { sql } = await import('drizzle-orm')` and its namespace form.
283+ *
284+ * Without this a file importing the tag dynamically resolves no tag at all, so
285+ * the whole file is skipped rather than audited — a silent hole, not a warning.
286+ */
287+ function bindDynamicImport ( target : unknown , bindings : SqlBindings ) : void {
288+ if ( ! isSyntaxNode ( target ) ) return
289+ if ( target . type === 'Identifier' && typeof target . name === 'string' ) {
290+ bindings . namespaces . add ( target . name )
291+ return
292+ }
293+ if ( target . type !== 'ObjectPattern' || ! Array . isArray ( target . properties ) ) return
294+ for ( const property of target . properties ) {
295+ if ( ! isSyntaxNode ( property ) || property . type !== 'ObjectProperty' ) continue
296+ const key = isSyntaxNode ( property . key ) ? property . key . name : undefined
297+ if ( key !== 'sql' ) continue
298+ const raw = isSyntaxNode ( property . value ) ? property . value : undefined
299+ const local = raw ?. type === 'AssignmentPattern' && isSyntaxNode ( raw . left ) ? raw . left : raw
300+ if ( local ?. type === 'Identifier' && typeof local . name === 'string' )
301+ bindings . tags . add ( local . name )
302+ }
303+ }
304+
249305/** `sql`, an aliased import of it, or `namespace.sql`. */
250306function isSqlReference ( node : unknown , bindings : SqlBindings ) : boolean {
251307 if ( ! isSyntaxNode ( node ) ) return false
0 commit comments