Skip to content

Commit f610b44

Browse files
committed
Add support for custom transformers
1 parent 35fe2b5 commit f610b44

File tree

8 files changed

+151
-10
lines changed

8 files changed

+151
-10
lines changed

src/compiler/emitter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,7 @@ function emitUsingBuildInfoWorker(
13361336
getFileIncludeReasons: notImplemented,
13371337
createHash: maybeBind(host, host.createHash),
13381338
};
1339+
// TODO(jakebailey): customTransformers
13391340
emitFiles(
13401341
notImplementedResolver,
13411342
emitHost,

src/compiler/program.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
createTypeChecker,
5454
createTypeReferenceDirectiveResolutionCache,
5555
CustomTransformers,
56+
CustomTransformersModuleFactory,
5657
Debug,
5758
DeclarationWithTypeParameterChildren,
5859
Diagnostic,
@@ -153,6 +154,7 @@ import {
153154
getTsBuildInfoEmitOutputFilePath,
154155
getTsConfigObjectLiteralExpression,
155156
getTsConfigPropArrayElementValue,
157+
getTypeScriptNamespace,
156158
getTypesPackageName,
157159
HasChangedAutomaticTypeDirectiveNames,
158160
hasChangesInResolutions,
@@ -219,6 +221,7 @@ import {
219221
mapDefinedIterator,
220222
maybeBind,
221223
memoize,
224+
mergeCustomTransformers,
222225
MethodDeclaration,
223226
ModeAwareCache,
224227
ModeAwareCacheKey,
@@ -500,7 +503,8 @@ export function createCompilerHostWorker(options: CompilerOptions, setParentNode
500503
realpath,
501504
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth),
502505
createDirectory: d => system.createDirectory(d),
503-
createHash: maybeBind(system, system.createHash)
506+
createHash: maybeBind(system, system.createHash),
507+
require: maybeBind(system, system.require),
504508
};
505509
return compilerHost;
506510
}
@@ -2702,6 +2706,30 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
27022706
return hasEmitBlockingDiagnostics.has(toPath(emitFileName));
27032707
}
27042708

2709+
function getCustomTransformers() {
2710+
if (!host.require) {
2711+
return emptyArray;
2712+
}
2713+
2714+
const compilerOptions = program.getCompilerOptions();
2715+
const customTransformers = mapDefined(compilerOptions.plugins, config => {
2716+
if (config.type !== "transformer") return undefined;
2717+
2718+
// TODO(jakebailey): The LS plugin loader is more complicated than this; copy.
2719+
const result = host.require!(program.getCurrentDirectory(), config.path);
2720+
// TODO(jakebailey): error handling, only do this once per, etc
2721+
Debug.assertIsDefined(result.module);
2722+
2723+
const factory = result.module as CustomTransformersModuleFactory;
2724+
Debug.assert(typeof factory === "function");
2725+
2726+
const plugin = factory({ typescript: getTypeScriptNamespace() });
2727+
return plugin.create({ program, config });
2728+
});
2729+
2730+
return customTransformers ?? emptyArray;
2731+
}
2732+
27052733
function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnly?: boolean | EmitOnly, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult {
27062734
if (!forceDtsEmit) {
27072735
const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken);
@@ -2720,11 +2748,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
27202748

27212749
performance.mark("beforeEmit");
27222750

2751+
const mergedCustomTransformers = mergeCustomTransformers(...getCustomTransformers(), customTransformers);
2752+
27232753
const emitResult = emitFiles(
27242754
emitResolver,
27252755
getEmitHost(writeFileCallback),
27262756
sourceFile,
2727-
getTransformers(options, customTransformers, emitOnly),
2757+
getTransformers(options, mergedCustomTransformers, emitOnly),
27282758
emitOnly,
27292759
/*onlyBuildInfo*/ false,
27302760
forceDtsEmit

src/compiler/transformer.ts

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Bundle,
55
chainBundle,
66
CompilerOptions,
7+
concatenate,
78
createEmitHelperFactory,
89
CustomTransformer,
910
CustomTransformerFactory,
@@ -116,6 +117,21 @@ export function getTransformers(compilerOptions: CompilerOptions, customTransfor
116117
};
117118
}
118119

120+
/** @internal */
121+
export function mergeCustomTransformers(...customTransformers: (CustomTransformers | undefined)[]): CustomTransformers | undefined {
122+
if (!some(customTransformers)) return undefined;
123+
124+
const result: CustomTransformers = {};
125+
for (const transformer of customTransformers) {
126+
if (!transformer) continue;
127+
result.before = concatenate(result.before, transformer.before);
128+
result.after = concatenate(result.after, transformer.after);
129+
result.afterDeclarations = concatenate(result.afterDeclarations, transformer.afterDeclarations);
130+
}
131+
132+
return result;
133+
}
134+
119135
function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnly?: boolean | EmitOnly) {
120136
if (emitOnly) return emptyArray;
121137

src/compiler/types.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
SymlinkCache,
1818
ThisContainer,
1919
} from "./_namespaces/ts";
20+
import * as ts from "./_namespaces/ts";
2021

2122
// branded string type used to store absolute, normalized and canonicalized paths
2223
// arbitrary file name can be converted to Path via toPath function
@@ -7037,8 +7038,18 @@ export enum ModuleDetectionKind {
70377038
Force = 3,
70387039
}
70397040

7041+
export type PluginConfig = PluginImport | TransformerPluginImport;
7042+
70407043
export interface PluginImport {
7044+
type?: undefined;
70417045
name: string;
7046+
/** @internal */
7047+
global?: boolean;
7048+
}
7049+
7050+
export interface TransformerPluginImport {
7051+
type: "transformer",
7052+
path: string;
70427053
}
70437054

70447055
export interface ProjectReference {
@@ -7075,7 +7086,7 @@ export enum PollingWatchKind {
70757086
FixedChunkSize,
70767087
}
70777088

7078-
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
7089+
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginConfig[] | ProjectReference[] | null | undefined;
70797090

70807091
export interface CompilerOptions {
70817092
/** @internal */ all?: boolean;
@@ -7176,7 +7187,7 @@ export interface CompilerOptions {
71767187
* @internal
71777188
*/
71787189
pathsBasePath?: string;
7179-
/** @internal */ plugins?: PluginImport[];
7190+
/** @internal */ plugins?: PluginConfig[];
71807191
preserveConstEnums?: boolean;
71817192
noImplicitOverride?: boolean;
71827193
preserveSymlinks?: boolean;
@@ -7222,6 +7233,7 @@ export interface CompilerOptions {
72227233
esModuleInterop?: boolean;
72237234
/** @internal */ showConfig?: boolean;
72247235
useDefineForClassFields?: boolean;
7236+
customTransformers?: string[];
72257237

72267238
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
72277239
}
@@ -7821,6 +7833,7 @@ export interface CompilerHost extends ModuleResolutionHost {
78217833
createHash?(data: string): string;
78227834
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
78237835
/** @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
7836+
/** @internal */ require?(baseDir: string, moduleName: string): ModuleImportResult;
78247837

78257838
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
78267839
/** @internal */createDirectory?(directory: string): void;
@@ -9988,3 +10001,19 @@ export interface Queue<T> {
998810001
dequeue(): T;
998910002
isEmpty(): boolean;
999010003
}
10004+
10005+
export type CustomTransformersModuleFactory = (mod: { typescript: typeof ts }) => CustomTransformersModule;
10006+
10007+
export interface CustomTransformersModuleWithName {
10008+
name: string;
10009+
module: CustomTransformersModule;
10010+
}
10011+
10012+
export interface CustomTransformersModule {
10013+
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
10014+
}
10015+
10016+
export interface CustomTransformersCreateInfo {
10017+
program: Program;
10018+
config: any;
10019+
}

src/server/project.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1971,12 +1971,12 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
19711971
if (!globalPluginName) continue;
19721972

19731973
// Skip already-locally-loaded plugins
1974-
if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue;
1974+
if (options.plugins && options.plugins.some(p => p.type === undefined && p.name === globalPluginName)) continue;
19751975

19761976
// Provide global: true so plugins can detect why they can't find their config
19771977
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
19781978

1979-
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
1979+
this.enablePlugin({ name: globalPluginName, global: true }, searchPaths);
19801980
}
19811981
}
19821982

@@ -2831,7 +2831,9 @@ export class ConfiguredProject extends Project {
28312831
// Enable tsconfig-specified plugins
28322832
if (options.plugins) {
28332833
for (const pluginConfigEntry of options.plugins) {
2834-
this.enablePlugin(pluginConfigEntry, searchPaths);
2834+
if (pluginConfigEntry.type === undefined) {
2835+
this.enablePlugin(pluginConfigEntry, searchPaths);
2836+
}
28352837
}
28362838
}
28372839

tests/baselines/reference/api/tsclibrary.d.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -2996,9 +2996,15 @@ declare namespace ts {
29962996
*/
29972997
Force = 3
29982998
}
2999+
type PluginConfig = PluginImport | TransformerPluginImport;
29993000
interface PluginImport {
3001+
type?: undefined;
30003002
name: string;
30013003
}
3004+
interface TransformerPluginImport {
3005+
type: "transformer";
3006+
path: string;
3007+
}
30023008
interface ProjectReference {
30033009
/** A normalized path on disk */
30043010
path: string;
@@ -3029,7 +3035,7 @@ declare namespace ts {
30293035
DynamicPriority = 2,
30303036
FixedChunkSize = 3
30313037
}
3032-
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
3038+
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginConfig[] | ProjectReference[] | null | undefined;
30333039
interface CompilerOptions {
30343040
allowImportingTsExtensions?: boolean;
30353041
allowJs?: boolean;
@@ -3134,6 +3140,7 @@ declare namespace ts {
31343140
verbatimModuleSyntax?: boolean;
31353141
esModuleInterop?: boolean;
31363142
useDefineForClassFields?: boolean;
3143+
customTransformers?: string[];
31373144
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
31383145
}
31393146
interface WatchOptions {
@@ -4263,6 +4270,20 @@ declare namespace ts {
42634270
negative: boolean;
42644271
base10Value: string;
42654272
}
4273+
type CustomTransformersModuleFactory = (mod: {
4274+
typescript: typeof ts;
4275+
}) => CustomTransformersModule;
4276+
interface CustomTransformersModuleWithName {
4277+
name: string;
4278+
module: CustomTransformersModule;
4279+
}
4280+
interface CustomTransformersModule {
4281+
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
4282+
}
4283+
interface CustomTransformersCreateInfo {
4284+
program: Program;
4285+
config: any;
4286+
}
42664287
enum FileWatcherEventKind {
42674288
Created = 0,
42684289
Changed = 1,

tests/baselines/reference/api/tsserverlibrary.d.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -7128,9 +7128,15 @@ declare namespace ts {
71287128
*/
71297129
Force = 3
71307130
}
7131+
type PluginConfig = PluginImport | TransformerPluginImport;
71317132
interface PluginImport {
7133+
type?: undefined;
71327134
name: string;
71337135
}
7136+
interface TransformerPluginImport {
7137+
type: "transformer";
7138+
path: string;
7139+
}
71347140
interface ProjectReference {
71357141
/** A normalized path on disk */
71367142
path: string;
@@ -7161,7 +7167,7 @@ declare namespace ts {
71617167
DynamicPriority = 2,
71627168
FixedChunkSize = 3
71637169
}
7164-
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
7170+
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginConfig[] | ProjectReference[] | null | undefined;
71657171
interface CompilerOptions {
71667172
allowImportingTsExtensions?: boolean;
71677173
allowJs?: boolean;
@@ -7266,6 +7272,7 @@ declare namespace ts {
72667272
verbatimModuleSyntax?: boolean;
72677273
esModuleInterop?: boolean;
72687274
useDefineForClassFields?: boolean;
7275+
customTransformers?: string[];
72697276
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
72707277
}
72717278
interface WatchOptions {
@@ -8398,6 +8405,20 @@ declare namespace ts {
83988405
negative: boolean;
83998406
base10Value: string;
84008407
}
8408+
type CustomTransformersModuleFactory = (mod: {
8409+
typescript: typeof ts;
8410+
}) => CustomTransformersModule;
8411+
interface CustomTransformersModuleWithName {
8412+
name: string;
8413+
module: CustomTransformersModule;
8414+
}
8415+
interface CustomTransformersModule {
8416+
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
8417+
}
8418+
interface CustomTransformersCreateInfo {
8419+
program: Program;
8420+
config: any;
8421+
}
84018422
enum FileWatcherEventKind {
84028423
Created = 0,
84038424
Changed = 1,

tests/baselines/reference/api/typescript.d.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -3081,9 +3081,15 @@ declare namespace ts {
30813081
*/
30823082
Force = 3
30833083
}
3084+
type PluginConfig = PluginImport | TransformerPluginImport;
30843085
interface PluginImport {
3086+
type?: undefined;
30853087
name: string;
30863088
}
3089+
interface TransformerPluginImport {
3090+
type: "transformer";
3091+
path: string;
3092+
}
30873093
interface ProjectReference {
30883094
/** A normalized path on disk */
30893095
path: string;
@@ -3114,7 +3120,7 @@ declare namespace ts {
31143120
DynamicPriority = 2,
31153121
FixedChunkSize = 3
31163122
}
3117-
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
3123+
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginConfig[] | ProjectReference[] | null | undefined;
31183124
interface CompilerOptions {
31193125
allowImportingTsExtensions?: boolean;
31203126
allowJs?: boolean;
@@ -3219,6 +3225,7 @@ declare namespace ts {
32193225
verbatimModuleSyntax?: boolean;
32203226
esModuleInterop?: boolean;
32213227
useDefineForClassFields?: boolean;
3228+
customTransformers?: string[];
32223229
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
32233230
}
32243231
interface WatchOptions {
@@ -4351,6 +4358,20 @@ declare namespace ts {
43514358
negative: boolean;
43524359
base10Value: string;
43534360
}
4361+
type CustomTransformersModuleFactory = (mod: {
4362+
typescript: typeof ts;
4363+
}) => CustomTransformersModule;
4364+
interface CustomTransformersModuleWithName {
4365+
name: string;
4366+
module: CustomTransformersModule;
4367+
}
4368+
interface CustomTransformersModule {
4369+
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
4370+
}
4371+
interface CustomTransformersCreateInfo {
4372+
program: Program;
4373+
config: any;
4374+
}
43544375
enum FileWatcherEventKind {
43554376
Created = 0,
43564377
Changed = 1,

0 commit comments

Comments
 (0)