Reputation: 2767
I have a mapped type called Commands
in file, like this:
// File: protocol.ts
import { CArgs, CRes } from '../utils';
type S = import('src/applications').Services;
export type Commands = {
-readonly [P in keyof S]: {
args: CArgs<S[P]>;
res: CRes<S[P]>;
};
};
// ... more declarations
The inference is quite complex, Services
is by itself a mapped type that is inferred using UnionToIntersection
and more fancy things, with types that come from many different files... But it works and the type as inferred by TypeScript is something like this:
type Commands = {
Com1: {
args: number;
res: void;
};
Com2: {
args: {foo: string};
res: string[];
};
// ... etc ...
};
Now I want to emit a single .d.ts
file with the resolved type, as shown in the second snippet, so clients doesn't need declarations for the entire tree of files to infer the type. In other words I'd like to "paste" the type as inferred from TypeScript in place, replacing Commands
declaration and removing all import statements.
I think I need to use the compiler API but the small documentation I could find isn't very clear. To be honest, I don't even know where to start.
How do I perform that task??
I started by creating a Program
using ts.createProgram()
giving the filename protocol.ts
. I visited every node from there so I know the file is loaded. I created a TypeChecker
and used .getTypeAtLocation()
for almost every Node
, logging checker.typeToString(type)
, checker.GetFullyQualifiedName(type.symbol)
, and many more, but there is no trace of the inferred type. Maybe the type checker or the program needs to have every file loaded? (I supposed it'd do it for itself) If so, how do I create the array from the glob pattern in the tsconfig.json
?
As you can see I'm a little lost so any help and orientation is welcome.
Thank you very much in advance.
Upvotes: 3
Views: 563
Reputation: 9192
I'm almost certain there's a better way to do this, but following the incremental example for the compiler API I was able to get the same information that VSCode displays.
Given ./src/mapped-types.ts
is:
type A = { a: 1, b: 2, c: 3 }
export type Mapped = {
readonly [K in keyof A]: A[K]
}
The following code will print out:
type Mapped = {
readonly a: 1;
readonly b: 2;
readonly c: 3;
}
import * as ts from 'typescript'
import * as fs from 'fs'
const rootFileNames = ['./src/mapped-types.ts']
const files: ts.MapLike<{ version: number }> = {};
const servicesHost: ts.LanguageServiceHost = {
getScriptFileNames: () => rootFileNames,
getScriptVersion: fileName => files[fileName] && files[fileName].version.toString(),
getScriptSnapshot: fileName => {
if (!fs.existsSync(fileName)) {
return undefined;
}
return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString());
},
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => ({}),
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
readDirectory: ts.sys.readDirectory
};
// Create the language service files
const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
const index = fs.readFileSync(rootFileNames[0], 'utf-8').indexOf('Mapped')
const info = services.getQuickInfoAtPosition(rootFileNames[0], index)!
console.log(ts.displayPartsToString(info.displayParts))
The relevant code for getting the quick info is here, unfortunately there's a lot going on there that I don't understand, so I'm not sure how to point you in the right direction.
Upvotes: 3