Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions apps/api-extractor/src/analyzer/ExportAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export class ExportAnalyzer {
private readonly _astImportsByKey: Map<string, AstImport> = new Map<string, AstImport>();
private readonly _astNamespaceImportByModule: Map<AstModule, AstNamespaceImport> = new Map();

// Used and populated by `_hasPathMappingMatch`.
private readonly _exactPathMappings: Set<string> = new Set<string>();
private readonly _prefixPathMappings: string[] = [];

public constructor(
program: ts.Program,
typeChecker: ts.TypeChecker,
Expand Down Expand Up @@ -273,12 +277,18 @@ export class ExportAnalyzer {
* - NO: `./file1`
* - YES: `library1/path/path`
* - YES: `@my-scope/my-package`
* - NO: `@my-scope/my-package` (if present in tsconfig `paths` mapping).
*/
private _isExternalModulePath(moduleSpecifier: string): boolean {
if (ts.isExternalModuleNameRelative(moduleSpecifier)) {
return false;
}

// Any module specifiers that match a path mapping entry are considered part of the current package.
if (this._hasPathMappingMatch(moduleSpecifier)) {
return false;
}

const match: RegExpExecArray | null = ExportAnalyzer._modulePathRegExp.exec(moduleSpecifier);
if (match) {
// Extract "@my-scope/my-package" from "@my-scope/my-package/path/module"
Expand All @@ -291,6 +301,40 @@ export class ExportAnalyzer {
return true;
}

/**
* Returns true if the module specifier matches a path mapping entry in the tsconfig `paths` map.
*
* @remarks
* Handles two types of path mappings:
*
* - Simple path-like strings: `some/path/to/import`
* - Paths with wildcards at the end: `some/path/to/*`
*/
private _hasPathMappingMatch(moduleSpecifier: string): boolean {
// If there are no path mappings, there cannot be a match.
const pathKeys: string[] = Object.keys(this._program.getCompilerOptions().paths || {});
if (pathKeys.length === 0) {
return false;
}

// Otherwise, if we haven't already, initialize some data structures to optimize how we evaluate path
// mapping matches. This will only be run once for all calls of `_hasPathMappingMatch`.
if (this._exactPathMappings.size === 0 && this._prefixPathMappings.length === 0) {
for (const pathKey of pathKeys) {
if (pathKey.endsWith('*')) {
this._prefixPathMappings.push(pathKey.slice(0, -1));
} else {
this._exactPathMappings.add(pathKey);
}
}
}

return (
this._exactPathMappings.has(moduleSpecifier) ||
this._prefixPathMappings.some((prefix) => moduleSpecifier.startsWith(prefix))
);
}

/**
* Returns true if when we analyzed sourceFile, we found that it contains an "export=" statement that allows
* it to behave /either/ as an ambient module /or/ as a regular importable module. In this case,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"inconsistentReleaseTags",
"internationalCharacters",
"namedDefaultImport",
"pathMappings",
"preapproved",
"spanSorting",
"typeOf",
Expand Down
Loading