@@ -3,7 +3,6 @@ import { readFileSync } from 'node:fs';
33import path from 'node:path' ;
44import { test } from 'node:test' ;
55import { listSourceFiles } from './check.ts' ;
6- import { workspaceSpecifierTargets } from './package-boundaries.ts' ;
76import {
87 fieldClassificationDrift ,
98 findSessionStateWrites ,
@@ -26,6 +25,26 @@ import {
2625 unclassifiedZones ,
2726} from './model.ts' ;
2827
28+ test ( 'parseImports rejects malformed source instead of returning an incomplete graph' , ( ) => {
29+ assert . throws (
30+ ( ) => parseImports ( "import { Broken from './broken.ts';" ) ,
31+ / C a n n o t p a r s e i m p o r t s .* E x p e c t e d / ,
32+ ) ;
33+ } ) ;
34+
35+ test ( 'parseImports ignores import-like declarations inside comments and template text' , ( ) => {
36+ const source = [
37+ 'const documentation = `' ,
38+ "import { phantom } from './phantom.ts';" ,
39+ "export { phantom } from './also-phantom.ts';" ,
40+ '`;' ,
41+ '/*' ,
42+ "import './comment.ts';" ,
43+ '*/' ,
44+ ] . join ( '\n' ) ;
45+ assert . deepEqual ( parseImports ( source ) , [ ] ) ;
46+ } ) ;
47+
2948test ( 'parseImports distinguishes value, type-only, dynamic, and value re-export edges' , ( ) => {
3049 const edges = parseImports (
3150 [
@@ -148,32 +167,90 @@ test('parseImports records TSImportType edges separately from dynamic imports',
148167 ) ;
149168} ) ;
150169
151- test ( 'the nine recorded TSImportType file pairs resolve as type edges' , ( ) => {
152- const expected = [
153- 'packages/contracts/src/platform-runtime.ts\0packages/contracts/src/device-shutdown-runtime.ts' ,
154- 'packages/platform-apple/src/runner/runner-provider.ts\0packages/platform-apple/src/runner/runner-artifact.ts' ,
155- 'src/daemon/interaction/index.ts\0src/daemon/gesture-runtime.ts' ,
156- 'src/daemon/interaction/index.ts\0src/daemon/touch-runtime.ts' ,
157- 'src/daemon/interaction/internal/interaction.ts\0packages/contracts/src/android-observation.ts' ,
158- 'src/daemon/snapshot-runtime-binding.ts\0packages/contracts/src/focus-runtime.ts' ,
159- 'src/daemon/snapshot-runtime-binding.ts\0packages/contracts/src/interactor-types.ts' ,
160- 'src/daemon/snapshot-runtime-binding.ts\0packages/contracts/src/type-text-runtime.ts' ,
161- 'src/sdk/artifacts.ts\0packages/platform-android/src/mechanics.ts' ,
162- ] . sort ( ) ;
163- const expectedPairs = new Set ( expected ) ;
164- const sources = new Map (
165- listSourceFiles ( ) . map ( ( file ) => [ file , readFileSync ( path . resolve ( file ) , 'utf8' ) ] ) ,
166- ) ;
167- const actual = [
168- ...new Set (
169- resolveImportEdges ( sources , workspaceSpecifierTargets ( process . cwd ( ) ) )
170- . filter ( ( edge ) => ! edge . dynamic && edge . typeOnly )
171- . map ( ( edge ) => `${ edge . file } \0${ edge . target } ` )
172- . filter ( ( pair ) => expectedPairs . has ( pair ) ) ,
173- ) ,
174- ] . sort ( ) ;
175-
176- assert . deepEqual ( actual , expected ) ;
170+ test ( 'the nine recorded TSImportType shapes resolve with source-side symbols' , ( ) => {
171+ const cases = [
172+ [
173+ 'packages/contracts/src/platform-runtime.ts' ,
174+ 'packages/contracts/src/device-shutdown-runtime.ts' ,
175+ './device-shutdown-runtime.ts' ,
176+ 'DeviceShutdownRuntime' ,
177+ ] ,
178+ [
179+ 'packages/platform-apple/src/runner/runner-provider.ts' ,
180+ 'packages/platform-apple/src/runner/runner-artifact.ts' ,
181+ './runner-artifact.ts' ,
182+ 'RunnerStartAdmission' ,
183+ ] ,
184+ [
185+ 'src/daemon/interaction/index.ts' ,
186+ 'src/daemon/gesture-runtime.ts' ,
187+ '../gesture-runtime.ts' ,
188+ 'BoundGestureExecutor' ,
189+ ] ,
190+ [
191+ 'src/daemon/interaction/index.ts' ,
192+ 'src/daemon/touch-runtime.ts' ,
193+ '../touch-runtime.ts' ,
194+ 'BoundTouchExecutor' ,
195+ ] ,
196+ [
197+ 'src/daemon/interaction/internal/interaction.ts' ,
198+ 'packages/contracts/src/android-observation.ts' ,
199+ '@agent-device/contracts/android-observation' ,
200+ 'AndroidObservationAdapter' ,
201+ ] ,
202+ [
203+ 'src/daemon/snapshot-runtime-binding.ts' ,
204+ 'packages/contracts/src/focus-runtime.ts' ,
205+ '@agent-device/contracts/focus-runtime' ,
206+ 'FocusPointInput' ,
207+ ] ,
208+ [
209+ 'src/daemon/snapshot-runtime-binding.ts' ,
210+ 'packages/contracts/src/interactor-types.ts' ,
211+ '@agent-device/contracts/interactor-types' ,
212+ 'TypeTextBackendResult' ,
213+ ] ,
214+ [
215+ 'src/daemon/snapshot-runtime-binding.ts' ,
216+ 'packages/contracts/src/type-text-runtime.ts' ,
217+ '@agent-device/contracts/type-text-runtime' ,
218+ 'TypeTextInput' ,
219+ ] ,
220+ [
221+ 'src/sdk/artifacts.ts' ,
222+ 'packages/platform-android/src/mechanics.ts' ,
223+ '@agent-device/platform-android/mechanics' ,
224+ 'resolveAndroidArchivePackageName' ,
225+ ] ,
226+ ] as const ;
227+ const sources = new Map < string , string > ( ) ;
228+ const exports = new Map < string , string > ( ) ;
229+ for ( const [ index , [ file , target , spec , symbol ] ] of cases . entries ( ) ) {
230+ const query = file === 'src/sdk/artifacts.ts' ? 'typeof ' : '' ;
231+ sources . set (
232+ file ,
233+ `${ sources . get ( file ) ?? '' } type Dependency${ index } = ${ query } import('${ spec } ').${ symbol } ;\n` ,
234+ ) ;
235+ sources . set ( target , `export type ${ symbol } = unknown;` ) ;
236+ if ( spec . startsWith ( '@' ) ) exports . set ( spec , target ) ;
237+ }
238+ assert . deepEqual (
239+ resolveImportEdges ( sources , exports ) . map ( ( { file, target, dynamic, typeOnly, symbols } ) => ( {
240+ file,
241+ target,
242+ dynamic,
243+ typeOnly,
244+ symbols,
245+ } ) ) ,
246+ cases . map ( ( [ file , target , , symbol ] ) => ( {
247+ file,
248+ target,
249+ dynamic : false ,
250+ typeOnly : true ,
251+ symbols : [ symbol ] ,
252+ } ) ) ,
253+ ) ;
177254} ) ;
178255
179256test ( 'parseImports detects multiline dynamic imports' , ( ) => {
0 commit comments