@@ -81,7 +81,7 @@ export class ClassLoader {
81
81
importedElements,
82
82
exportedImportedAll,
83
83
exportedImportedElements,
84
- } = this . getClassElements ( classReference . fileName , ast ) ;
84
+ } = this . getClassElements ( classReference . packageName , classReference . fileName , ast ) ;
85
85
86
86
// If the class has been exported in this file, return directly
87
87
if ( classReference . localName in exportedClasses ) {
@@ -149,7 +149,7 @@ export class ClassLoader {
149
149
// If we still haven't found the class, iterate over all export all's
150
150
for ( const subFile of exportedImportedAll ) {
151
151
try {
152
- return await this . loadClassDeclaration ( { localName : classReference . localName , fileName : subFile } ,
152
+ return await this . loadClassDeclaration ( { localName : classReference . localName , ... subFile } ,
153
153
considerInterfaces ) ;
154
154
} catch {
155
155
// Ignore class not found errors
@@ -188,37 +188,88 @@ export class ClassLoader {
188
188
189
189
/**
190
190
* Load a class, and get all class elements from it.
191
+ * @param packageName Package name we are importing from.
191
192
* @param fileName A file path.
192
193
*/
193
- public async loadClassElements ( fileName : string ) : Promise < ClassElements > {
194
+ public async loadClassElements ( packageName : string , fileName : string ) : Promise < ClassElements > {
194
195
const ast = await this . resolutionContext . parseTypescriptFile ( fileName ) ;
195
- return this . getClassElements ( fileName , ast ) ;
196
+ return this . getClassElements ( packageName , fileName , ast ) ;
196
197
}
197
198
198
199
/**
199
- * Convert the given import path to an absolute file path.
200
+ * Convert the given import path to an absolute file path, coupled with the module it is part of.
201
+ * @param currentPackageName Package name we are importing from.
200
202
* @param currentFilePath Absolute path to a file in which the import path occurs.
201
203
* @param importPath Possibly relative path that is being imported.
202
204
*/
203
- public importTargetToAbsolutePath ( currentFilePath : string , importPath : string ) : string {
204
- // TODO: Add support for imports from other packages (#39)
205
- return Path . join ( Path . dirname ( currentFilePath ) , importPath ) ;
205
+ public importTargetToAbsolutePath (
206
+ currentPackageName : string ,
207
+ currentFilePath : string ,
208
+ importPath : string ,
209
+ ) : { packageName : string ; fileName : string } {
210
+ // Handle import paths within the current package
211
+ if ( importPath . startsWith ( '.' ) ) {
212
+ return {
213
+ packageName : currentPackageName ,
214
+ fileName : Path . join ( Path . dirname ( currentFilePath ) , importPath ) ,
215
+ } ;
216
+ }
217
+
218
+ // Handle import paths to other packages
219
+ let packageName : string ;
220
+ let packagePath : string | undefined ;
221
+ if ( importPath . startsWith ( '@' ) ) {
222
+ const slashIndexFirst = importPath . indexOf ( '/' ) ;
223
+ if ( slashIndexFirst < 0 ) {
224
+ throw new Error ( `Invalid scoped package name for import path '${ importPath } ' in '${ currentFilePath } '` ) ;
225
+ }
226
+ const slashIndexSecond = importPath . indexOf ( '/' , slashIndexFirst + 1 ) ;
227
+ if ( slashIndexSecond < 0 ) {
228
+ // Import form: "@scope/package"
229
+ packageName = importPath ;
230
+ } else {
231
+ // Import form: "@scope/package/path"
232
+ packageName = importPath . slice ( 0 , Math . max ( 0 , slashIndexSecond ) ) ;
233
+ packagePath = importPath . slice ( slashIndexSecond + 1 ) ;
234
+ }
235
+ } else {
236
+ const slashIndex = importPath . indexOf ( '/' ) ;
237
+ if ( slashIndex < 0 ) {
238
+ // Import form: "package"
239
+ packageName = importPath ;
240
+ } else {
241
+ // Import form: "package/path"
242
+ packageName = importPath . slice ( 0 , Math . max ( 0 , slashIndex ) ) ;
243
+ packagePath = importPath . slice ( slashIndex + 1 ) ;
244
+ }
245
+ }
246
+
247
+ // Resolve paths
248
+ const packageRoot = this . resolutionContext . resolvePackageIndex ( packageName , currentFilePath ) ;
249
+ const remoteFilePath = packagePath ?
250
+ Path . resolve ( Path . dirname ( packageRoot ) , packagePath ) :
251
+ packageRoot . slice ( 0 , packageRoot . lastIndexOf ( '.' ) ) ;
252
+ return {
253
+ packageName,
254
+ fileName : remoteFilePath ,
255
+ } ;
206
256
}
207
257
208
258
/**
209
259
* Get all class elements in a file.
260
+ * @param packageName Package name we are importing from.
210
261
* @param fileName A file path.
211
262
* @param ast The parsed file.
212
263
*/
213
- public getClassElements ( fileName : string , ast : AST < TSESTreeOptions > ) : ClassElements {
264
+ public getClassElements ( packageName : string , fileName : string , ast : AST < TSESTreeOptions > ) : ClassElements {
214
265
const exportedClasses : Record < string , ClassDeclaration > = { } ;
215
266
const exportedInterfaces : Record < string , TSInterfaceDeclaration > = { } ;
216
- const exportedImportedElements : Record < string , { localName : string ; fileName : string } > = { } ;
217
- const exportedImportedAll : string [ ] = [ ] ;
267
+ const exportedImportedElements : Record < string , ClassReference > = { } ;
268
+ const exportedImportedAll : { packageName : string ; fileName : string } [ ] = [ ] ;
218
269
const exportedUnknowns : Record < string , string > = { } ;
219
270
const declaredClasses : Record < string , ClassDeclaration > = { } ;
220
271
const declaredInterfaces : Record < string , TSInterfaceDeclaration > = { } ;
221
- const importedElements : Record < string , { localName : string ; fileName : string } > = { } ;
272
+ const importedElements : Record < string , ClassReference > = { } ;
222
273
223
274
for ( const statement of ast . body ) {
224
275
if ( statement . type === AST_NODE_TYPES . ExportNamedDeclaration ) {
@@ -240,7 +291,7 @@ export class ClassLoader {
240
291
for ( const specifier of statement . specifiers ) {
241
292
exportedImportedElements [ specifier . exported . name ] = {
242
293
localName : specifier . local . name ,
243
- fileName : this . importTargetToAbsolutePath ( fileName , statement . source . value ) ,
294
+ ... this . importTargetToAbsolutePath ( packageName , fileName , statement . source . value ) ,
244
295
} ;
245
296
}
246
297
} else {
@@ -254,7 +305,7 @@ export class ClassLoader {
254
305
if ( statement . source &&
255
306
statement . source . type === AST_NODE_TYPES . Literal &&
256
307
typeof statement . source . value === 'string' ) {
257
- exportedImportedAll . push ( this . importTargetToAbsolutePath ( fileName , statement . source . value ) ) ;
308
+ exportedImportedAll . push ( this . importTargetToAbsolutePath ( packageName , fileName , statement . source . value ) ) ;
258
309
}
259
310
} else if ( statement . type === AST_NODE_TYPES . ClassDeclaration && statement . id ) {
260
311
// Form: `declare class A {}`
@@ -270,7 +321,7 @@ export class ClassLoader {
270
321
if ( specifier . type === AST_NODE_TYPES . ImportSpecifier ) {
271
322
importedElements [ specifier . local . name ] = {
272
323
localName : specifier . imported . name ,
273
- fileName : this . importTargetToAbsolutePath ( fileName , statement . source . value ) ,
324
+ ... this . importTargetToAbsolutePath ( packageName , fileName , statement . source . value ) ,
274
325
} ;
275
326
}
276
327
}
@@ -303,15 +354,15 @@ export interface ClassElements {
303
354
// Interfaces that have been declared in a file via `export interface A`
304
355
exportedInterfaces : Record < string , TSInterfaceDeclaration > ;
305
356
// Elements that have been exported via `export { A as B } from "b"`
306
- exportedImportedElements : Record < string , { localName : string ; fileName : string } > ;
357
+ exportedImportedElements : Record < string , ClassReference > ;
307
358
// Exports via `export * from "b"`
308
- exportedImportedAll : string [ ] ;
359
+ exportedImportedAll : { packageName : string ; fileName : string } [ ] ;
309
360
// Things that have been exported via `export {A as B}`, where the target is not known
310
361
exportedUnknowns : Record < string , string > ;
311
362
// Classes that have been declared in a file via `declare class A`
312
363
declaredClasses : Record < string , ClassDeclaration > ;
313
364
// Interfaces that have been declared in a file via `declare interface A`
314
365
declaredInterfaces : Record < string , TSInterfaceDeclaration > ;
315
366
// Elements that are imported from elsewhere via `import {A} from ''`
316
- importedElements : Record < string , { localName : string ; fileName : string } > ;
367
+ importedElements : Record < string , ClassReference > ;
317
368
}
0 commit comments