Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(type,type-compiler): inline external library imports #541

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
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
Loading