Skip to content
1,277 changes: 935 additions & 342 deletions packages/type-compiler/src/compiler.ts

Large diffs are not rendered by default.

34 changes: 25 additions & 9 deletions packages/type-compiler/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export interface TsConfigJson {
* Per default a few global .d.ts files are excluded like `lib.dom*.d.ts` and `*typedarrays.d.ts`.
*/
exclude?: string[];

/**
* External library imports to reflect
*/
inlineExternalLibraryImports?: true | Record<string, true | string[]>;
};
}

Expand Down Expand Up @@ -104,6 +109,11 @@ export interface ReflectionConfig {
* or a list of globs to match against.
*/
reflection?: string[] | Mode;

/**
* External library imports to reflect
*/
inlineExternalLibraryImports?: true | Record<string, true | string[]>;
}

export interface CurrentConfig extends ReflectionConfig {
Expand Down Expand Up @@ -217,6 +227,7 @@ function applyConfigValues(existing: CurrentConfig, parent: TsConfigJson, baseDi
export interface MatchResult {
tsConfigPath: string;
mode: (typeof reflectionModes)[number];
inlineExternalLibraryImports?: true | Record<string, true | string[]>;
}

export const defaultExcluded = [
Expand Down Expand Up @@ -300,20 +311,25 @@ export function getResolver(
exclude: config.exclude,
reflection: config.reflection,
mergeStrategy: config.mergeStrategy || defaultMergeStrategy,
inlineExternalLibraryImports: config.inlineExternalLibraryImports,
};

if (isDebug()) {
debug(
`Found config ${resolvedConfig.path}:\nreflection:`,
resolvedConfig.reflection,
`\nexclude:`,
resolvedConfig.exclude,
);
}
debug(
`Found config ${resolvedConfig.path}:\nreflection:`,
resolvedConfig.reflection,
`\nexclude:`,
resolvedConfig.exclude,
`\ninlineExternalLibraryImports:`,
resolvedConfig.inlineExternalLibraryImports,
);

const match = (path: string) => {
const mode = reflectionModeMatcher(config, path);
return { mode, tsConfigPath };
return {
mode,
tsConfigPath,
inlineExternalLibraryImports: config.inlineExternalLibraryImports,
};
};

return (cache[tsConfigPath] = { config: resolvedConfig, match });
Expand Down
128 changes: 128 additions & 0 deletions packages/type-compiler/src/external.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { EntityName, ImportDeclaration, Node, ResolvedModuleFull, SourceFile, isStringLiteral } from 'typescript';

import { ReflectionConfig } from './config.js';
import { getEntityName, getNameAsString, hasSourceFile } from './reflection-ast.js';
import { Resolver } from './resolver.js';

export interface ExternalLibraryImport {
declaration: Node;
name: EntityName;
sourceFile: SourceFile;
module: Required<ResolvedModuleFull>;
}

export class External {
protected sourceFileNames = new Set<string>();

public compileExternalLibraryImports = new Map<string, Map<string, ExternalLibraryImport>>();

protected processedEntities = new Set<string>();

public embeddedLibraryVariables = new Set<string>();

public knownGlobalTypeNames = new Set<string>();

public sourceFile?: SourceFile;

protected embeddingExternalLibraryImport?: ExternalLibraryImport;

constructor(protected resolver: Resolver) {}

startEmbeddingExternalLibraryImport(value: ExternalLibraryImport): void {
if (this.embeddingExternalLibraryImport) {
throw new Error('Already embedding external library import');
}
this.embeddingExternalLibraryImport = value;
}

getEmbeddingExternalLibraryImport(): ExternalLibraryImport {
if (!this.embeddingExternalLibraryImport) {
throw new Error('Not embedding external library import');
}
return this.embeddingExternalLibraryImport;
}

isEmbeddingExternalLibraryImport(): boolean {
return !!this.embeddingExternalLibraryImport;
}

finishEmbeddingExternalLibraryImport(): void {
delete this.embeddingExternalLibraryImport;
}

public addSourceFile(sourceFile: SourceFile): void {
this.sourceFileNames.add(sourceFile.fileName);
}

public hasSourceFile(sourceFile: SourceFile): boolean {
return this.sourceFileNames.has(sourceFile.fileName);
}

public shouldInlineExternalLibraryImport(
importDeclaration: ImportDeclaration,
entityName: EntityName,
config: ReflectionConfig,
): boolean {
if (!isStringLiteral(importDeclaration.moduleSpecifier)) return false;
if (!hasSourceFile(importDeclaration)) return false;
let resolvedModule;
try {
// throws an error if import is not an external library
resolvedModule = this.resolver.resolveExternalLibraryImport(importDeclaration);
} catch {
return false;
}
if (config.inlineExternalLibraryImports === true) return true;
const imports = config.inlineExternalLibraryImports?.[resolvedModule.packageId.name];
if (!imports) return false;
if (imports === true) return true;
if (!importDeclaration.moduleSpecifier.text.startsWith(resolvedModule.packageId.name)) {
return true;
}
const typeName = getEntityName(entityName);
return imports.includes(typeName);
}

public hasProcessedEntity(typeName: EntityName): boolean {
return this.processedEntities.has(getNameAsString(typeName));
}

public processExternalLibraryImport(
typeName: EntityName,
declaration: Node,
sourceFile: SourceFile,
importDeclaration?: ImportDeclaration,
): ExternalLibraryImport {
const module = importDeclaration
? this.resolver.resolveExternalLibraryImport(importDeclaration)
: this.getEmbeddingExternalLibraryImport().module;

const entityName = getNameAsString(typeName);
if (this.processedEntities.has(entityName)) {
return {
name: typeName,
declaration,
sourceFile,
module,
};
}

this.processedEntities.add(entityName);

const imports =
this.compileExternalLibraryImports.get(module.packageId.name) || new Map<string, ExternalLibraryImport>();
const externalLibraryImport: ExternalLibraryImport = {
name: typeName,
declaration,
sourceFile,
module,
};
this.compileExternalLibraryImports.set(module.packageId.name, imports.set(entityName, externalLibraryImport));

if (sourceFile.fileName !== this.sourceFile?.fileName) {
this.addSourceFile(sourceFile);
}

return externalLibraryImport!;
}
}
Loading