@@ -56,14 +56,6 @@ interface IAstModuleReference {
5656 * generating .d.ts rollups.
5757 */
5858export class ExportAnalyzer {
59- // Captures "@a/b" or "d" from these examples:
60- // @a /b
61- // @a /b/c
62- // d
63- // d/
64- // d/e
65- private static _modulePathRegExp : RegExp = / ^ ( (?: @ [ ^ @ \/ \s ] + \/ ) ? [ ^ @ \/ \s ] + ) (?: .* ) $ / ;
66-
6759 private readonly _program : ts . Program ;
6860 private readonly _typeChecker : ts . TypeChecker ;
6961 private readonly _bundledPackageNames : ReadonlySet < string > ;
@@ -94,10 +86,12 @@ export class ExportAnalyzer {
9486 *
9587 * @param moduleReference - contextual information about the import statement that took us to this source file.
9688 * or `undefined` if this source file is the initial entry point
89+ * @param isExternal - whether the given `moduleReference` is external.
9790 */
9891 public fetchAstModuleFromSourceFile (
9992 sourceFile : ts . SourceFile ,
100- moduleReference : IAstModuleReference | undefined
93+ moduleReference : IAstModuleReference | undefined ,
94+ isExternal : boolean
10195 ) : AstModule {
10296 const moduleSymbol : ts . Symbol = this . _getModuleSymbolFromSourceFile ( sourceFile , moduleReference ) ;
10397
@@ -107,14 +101,8 @@ export class ExportAnalyzer {
107101 let astModule : AstModule | undefined = this . _astModulesByModuleSymbol . get ( moduleSymbol ) ;
108102 if ( ! astModule ) {
109103 // (If moduleReference === undefined, then this is the entry point of the local project being analyzed.)
110- let externalModulePath : string | undefined = undefined ;
111- if ( moduleReference !== undefined ) {
112- // Match: "@microsoft/sp-lodash-subset" or "lodash/has"
113- // but ignore: "../folder/LocalFile"
114- if ( this . _isExternalModulePath ( moduleReference . moduleSpecifier ) ) {
115- externalModulePath = moduleReference . moduleSpecifier ;
116- }
117- }
104+ const externalModulePath : string | undefined =
105+ moduleReference !== undefined && isExternal ? moduleReference . moduleSpecifier : undefined ;
118106
119107 astModule = new AstModule ( { sourceFile, moduleSymbol, externalModulePath } ) ;
120108
@@ -266,29 +254,32 @@ export class ExportAnalyzer {
266254 /**
267255 * Returns true if the module specifier refers to an external package. Ignores packages listed in the
268256 * "bundledPackages" setting from the api-extractor.json config file.
269- *
270- * @remarks
271- * Examples:
272- *
273- * - NO: `./file1`
274- * - YES: `library1/path/path`
275- * - YES: `@my-scope/my-package`
276257 */
277- private _isExternalModulePath ( moduleSpecifier : string ) : boolean {
278- if ( ts . isExternalModuleNameRelative ( moduleSpecifier ) ) {
258+ private _isExternalModulePath (
259+ importOrExportDeclaration : ts . ImportDeclaration | ts . ExportDeclaration | ts . ImportTypeNode ,
260+ moduleSpecifier : string
261+ ) : boolean {
262+ const resolvedModule : ts . ResolvedModuleFull = this . _getResolvedModule (
263+ importOrExportDeclaration ,
264+ moduleSpecifier
265+ ) ;
266+
267+ // Either something like `jquery` or `@microsoft/api-extractor`.
268+ const packageName : string | undefined = resolvedModule . packageId ?. name ;
269+ if ( packageName !== undefined && this . _bundledPackageNames . has ( packageName ) ) {
279270 return false ;
280271 }
281272
282- const match : RegExpExecArray | null = ExportAnalyzer . _modulePathRegExp . exec ( moduleSpecifier ) ;
283- if ( match ) {
284- // Extract "@my-scope/my-package" from "@my-scope/my-package/path/module"
285- const packageName : string = match [ 1 ] ;
286- if ( this . _bundledPackageNames . has ( packageName ) ) {
287- return false ;
288- }
273+ if ( resolvedModule . isExternalLibraryImport === undefined ) {
274+ // This presumably means the compiler couldn't figure out whether the module was external, but we're not
275+ // sure how this can happen.
276+ throw new InternalError (
277+ `Cannot determine whether the module ${ JSON . stringify ( moduleSpecifier ) } is external\n` +
278+ SourceFileLocationFormatter . formatDeclaration ( importOrExportDeclaration )
279+ ) ;
289280 }
290281
291- return true ;
282+ return resolvedModule . isExternalLibraryImport ;
292283 }
293284
294285 /**
@@ -568,10 +559,7 @@ export class ExportAnalyzer {
568559
569560 // Ignore "export { A }" without a module specifier
570561 if ( exportDeclaration . moduleSpecifier ) {
571- const externalModulePath : string | undefined = this . _tryGetExternalModulePath (
572- exportDeclaration ,
573- declarationSymbol
574- ) ;
562+ const externalModulePath : string | undefined = this . _tryGetExternalModulePath ( exportDeclaration ) ;
575563
576564 if ( externalModulePath !== undefined ) {
577565 return this . _fetchAstImport ( declarationSymbol , {
@@ -597,10 +585,7 @@ export class ExportAnalyzer {
597585 TypeScriptHelpers . findFirstParent < ts . ImportDeclaration > ( declaration , ts . SyntaxKind . ImportDeclaration ) ;
598586
599587 if ( importDeclaration ) {
600- const externalModulePath : string | undefined = this . _tryGetExternalModulePath (
601- importDeclaration ,
602- declarationSymbol
603- ) ;
588+ const externalModulePath : string | undefined = this . _tryGetExternalModulePath ( importDeclaration ) ;
604589
605590 if ( declaration . kind === ts . SyntaxKind . NamespaceImport ) {
606591 // EXAMPLE:
@@ -852,22 +837,10 @@ export class ExportAnalyzer {
852837 }
853838
854839 private _tryGetExternalModulePath (
855- importOrExportDeclaration : ts . ImportDeclaration | ts . ExportDeclaration | ts . ImportTypeNode ,
856- exportSymbol ?: ts . Symbol
840+ importOrExportDeclaration : ts . ImportDeclaration | ts . ExportDeclaration | ts . ImportTypeNode
857841 ) : string | undefined {
858- // The name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point'
859- const moduleSpecifier : string | undefined =
860- TypeScriptHelpers . getModuleSpecifier ( importOrExportDeclaration ) ;
861- if ( ! moduleSpecifier ) {
862- throw new InternalError (
863- 'Unable to parse module specifier\n' +
864- SourceFileLocationFormatter . formatDeclaration ( importOrExportDeclaration )
865- ) ;
866- }
867-
868- // Match: "@microsoft/sp-lodash-subset" or "lodash/has"
869- // but ignore: "../folder/LocalFile"
870- if ( this . _isExternalModulePath ( moduleSpecifier ) ) {
842+ const moduleSpecifier : string = this . _getModuleSpecifier ( importOrExportDeclaration ) ;
843+ if ( this . _isExternalModulePath ( importOrExportDeclaration , moduleSpecifier ) ) {
871844 return moduleSpecifier ;
872845 }
873846
@@ -882,32 +855,12 @@ export class ExportAnalyzer {
882855 importOrExportDeclaration : ts . ImportDeclaration | ts . ExportDeclaration ,
883856 exportSymbol : ts . Symbol
884857 ) : AstModule {
885- // The name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point'
886- const moduleSpecifier : string | undefined =
887- TypeScriptHelpers . getModuleSpecifier ( importOrExportDeclaration ) ;
888- if ( ! moduleSpecifier ) {
889- throw new InternalError (
890- 'Unable to parse module specifier\n' +
891- SourceFileLocationFormatter . formatDeclaration ( importOrExportDeclaration )
892- ) ;
893- }
894-
895- const resolvedModule : ts . ResolvedModuleFull | undefined = TypeScriptInternals . getResolvedModule (
896- importOrExportDeclaration . getSourceFile ( ) ,
858+ const moduleSpecifier : string = this . _getModuleSpecifier ( importOrExportDeclaration ) ;
859+ const resolvedModule : ts . ResolvedModuleFull = this . _getResolvedModule (
860+ importOrExportDeclaration ,
897861 moduleSpecifier
898862 ) ;
899863
900- if ( resolvedModule === undefined ) {
901- // This should not happen, since getResolvedModule() specifically looks up names that the compiler
902- // found in export declarations for this source file
903- //
904- // Encountered in https://github.com/microsoft/rushstack/issues/1914
905- throw new InternalError (
906- `getResolvedModule() could not resolve module name ${ JSON . stringify ( moduleSpecifier ) } \n` +
907- SourceFileLocationFormatter . formatDeclaration ( importOrExportDeclaration )
908- ) ;
909- }
910-
911864 // Map the filename back to the corresponding SourceFile. This circuitous approach is needed because
912865 // we have no way to access the compiler's internal resolveExternalModuleName() function
913866 const moduleSourceFile : ts . SourceFile | undefined = this . _program . getSourceFile (
@@ -922,13 +875,15 @@ export class ExportAnalyzer {
922875 ) ;
923876 }
924877
878+ const isExternal : boolean = this . _isExternalModulePath ( importOrExportDeclaration , moduleSpecifier ) ;
925879 const moduleReference : IAstModuleReference = {
926880 moduleSpecifier : moduleSpecifier ,
927881 moduleSpecifierSymbol : exportSymbol
928882 } ;
929883 const specifierAstModule : AstModule = this . fetchAstModuleFromSourceFile (
930884 moduleSourceFile ,
931- moduleReference
885+ moduleReference ,
886+ isExternal
932887 ) ;
933888
934889 return specifierAstModule ;
@@ -963,4 +918,44 @@ export class ExportAnalyzer {
963918
964919 return astImport ;
965920 }
921+
922+ private _getResolvedModule (
923+ importOrExportDeclaration : ts . ImportDeclaration | ts . ExportDeclaration | ts . ImportTypeNode ,
924+ moduleSpecifier : string
925+ ) : ts . ResolvedModuleFull {
926+ const resolvedModule : ts . ResolvedModuleFull | undefined = TypeScriptInternals . getResolvedModule (
927+ importOrExportDeclaration . getSourceFile ( ) ,
928+ moduleSpecifier
929+ ) ;
930+
931+ if ( resolvedModule === undefined ) {
932+ // This should not happen, since getResolvedModule() specifically looks up names that the compiler
933+ // found in export declarations for this source file
934+ //
935+ // Encountered in https://github.com/microsoft/rushstack/issues/1914
936+ throw new InternalError (
937+ `getResolvedModule() could not resolve module name ${ JSON . stringify ( moduleSpecifier ) } \n` +
938+ SourceFileLocationFormatter . formatDeclaration ( importOrExportDeclaration )
939+ ) ;
940+ }
941+
942+ return resolvedModule ;
943+ }
944+
945+ private _getModuleSpecifier (
946+ importOrExportDeclaration : ts . ImportDeclaration | ts . ExportDeclaration | ts . ImportTypeNode
947+ ) : string {
948+ // The name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point'
949+ const moduleSpecifier : string | undefined =
950+ TypeScriptHelpers . getModuleSpecifier ( importOrExportDeclaration ) ;
951+
952+ if ( ! moduleSpecifier ) {
953+ throw new InternalError (
954+ 'Unable to parse module specifier\n' +
955+ SourceFileLocationFormatter . formatDeclaration ( importOrExportDeclaration )
956+ ) ;
957+ }
958+
959+ return moduleSpecifier ;
960+ }
966961}
0 commit comments