@@ -81,7 +81,7 @@ export class ClassLoader {
8181 importedElements,
8282 exportedImportedAll,
8383 exportedImportedElements,
84- } = this . getClassElements ( classReference . fileName , ast ) ;
84+ } = this . getClassElements ( classReference . packageName , classReference . fileName , ast ) ;
8585
8686 // If the class has been exported in this file, return directly
8787 if ( classReference . localName in exportedClasses ) {
@@ -149,7 +149,7 @@ export class ClassLoader {
149149 // If we still haven't found the class, iterate over all export all's
150150 for ( const subFile of exportedImportedAll ) {
151151 try {
152- return await this . loadClassDeclaration ( { localName : classReference . localName , fileName : subFile } ,
152+ return await this . loadClassDeclaration ( { localName : classReference . localName , ... subFile } ,
153153 considerInterfaces ) ;
154154 } catch {
155155 // Ignore class not found errors
@@ -188,37 +188,88 @@ export class ClassLoader {
188188
189189 /**
190190 * Load a class, and get all class elements from it.
191+ * @param packageName Package name we are importing from.
191192 * @param fileName A file path.
192193 */
193- public async loadClassElements ( fileName : string ) : Promise < ClassElements > {
194+ public async loadClassElements ( packageName : string , fileName : string ) : Promise < ClassElements > {
194195 const ast = await this . resolutionContext . parseTypescriptFile ( fileName ) ;
195- return this . getClassElements ( fileName , ast ) ;
196+ return this . getClassElements ( packageName , fileName , ast ) ;
196197 }
197198
198199 /**
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.
200202 * @param currentFilePath Absolute path to a file in which the import path occurs.
201203 * @param importPath Possibly relative path that is being imported.
202204 */
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+ } ;
206256 }
207257
208258 /**
209259 * Get all class elements in a file.
260+ * @param packageName Package name we are importing from.
210261 * @param fileName A file path.
211262 * @param ast The parsed file.
212263 */
213- public getClassElements ( fileName : string , ast : AST < TSESTreeOptions > ) : ClassElements {
264+ public getClassElements ( packageName : string , fileName : string , ast : AST < TSESTreeOptions > ) : ClassElements {
214265 const exportedClasses : Record < string , ClassDeclaration > = { } ;
215266 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 } [ ] = [ ] ;
218269 const exportedUnknowns : Record < string , string > = { } ;
219270 const declaredClasses : Record < string , ClassDeclaration > = { } ;
220271 const declaredInterfaces : Record < string , TSInterfaceDeclaration > = { } ;
221- const importedElements : Record < string , { localName : string ; fileName : string } > = { } ;
272+ const importedElements : Record < string , ClassReference > = { } ;
222273
223274 for ( const statement of ast . body ) {
224275 if ( statement . type === AST_NODE_TYPES . ExportNamedDeclaration ) {
@@ -240,7 +291,7 @@ export class ClassLoader {
240291 for ( const specifier of statement . specifiers ) {
241292 exportedImportedElements [ specifier . exported . name ] = {
242293 localName : specifier . local . name ,
243- fileName : this . importTargetToAbsolutePath ( fileName , statement . source . value ) ,
294+ ... this . importTargetToAbsolutePath ( packageName , fileName , statement . source . value ) ,
244295 } ;
245296 }
246297 } else {
@@ -254,7 +305,7 @@ export class ClassLoader {
254305 if ( statement . source &&
255306 statement . source . type === AST_NODE_TYPES . Literal &&
256307 typeof statement . source . value === 'string' ) {
257- exportedImportedAll . push ( this . importTargetToAbsolutePath ( fileName , statement . source . value ) ) ;
308+ exportedImportedAll . push ( this . importTargetToAbsolutePath ( packageName , fileName , statement . source . value ) ) ;
258309 }
259310 } else if ( statement . type === AST_NODE_TYPES . ClassDeclaration && statement . id ) {
260311 // Form: `declare class A {}`
@@ -270,7 +321,7 @@ export class ClassLoader {
270321 if ( specifier . type === AST_NODE_TYPES . ImportSpecifier ) {
271322 importedElements [ specifier . local . name ] = {
272323 localName : specifier . imported . name ,
273- fileName : this . importTargetToAbsolutePath ( fileName , statement . source . value ) ,
324+ ... this . importTargetToAbsolutePath ( packageName , fileName , statement . source . value ) ,
274325 } ;
275326 }
276327 }
@@ -303,15 +354,15 @@ export interface ClassElements {
303354 // Interfaces that have been declared in a file via `export interface A`
304355 exportedInterfaces : Record < string , TSInterfaceDeclaration > ;
305356 // 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 > ;
307358 // Exports via `export * from "b"`
308- exportedImportedAll : string [ ] ;
359+ exportedImportedAll : { packageName : string ; fileName : string } [ ] ;
309360 // Things that have been exported via `export {A as B}`, where the target is not known
310361 exportedUnknowns : Record < string , string > ;
311362 // Classes that have been declared in a file via `declare class A`
312363 declaredClasses : Record < string , ClassDeclaration > ;
313364 // Interfaces that have been declared in a file via `declare interface A`
314365 declaredInterfaces : Record < string , TSInterfaceDeclaration > ;
315366 // Elements that are imported from elsewhere via `import {A} from ''`
316- importedElements : Record < string , { localName : string ; fileName : string } > ;
367+ importedElements : Record < string , ClassReference > ;
317368}
0 commit comments