Skip to content

Commit 749882e

Browse files
committed
Add support for custom transformers
1 parent 9ff68c6 commit 749882e

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
@@ -1334,6 +1334,7 @@ function emitUsingBuildInfoWorker(
13341334
getFileIncludeReasons: notImplemented,
13351335
createHash: maybeBind(host, host.createHash),
13361336
};
1337+
// TODO(jakebailey): customTransformers
13371338
emitFiles(
13381339
notImplementedResolver,
13391340
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
HasChangedAutomaticTypeDirectiveNames,
157159
hasChangesInResolutions,
158160
hasExtension,
@@ -218,6 +220,7 @@ import {
218220
mapDefinedIterator,
219221
maybeBind,
220222
memoize,
223+
mergeCustomTransformers,
221224
MethodDeclaration,
222225
ModeAwareCache,
223226
ModeAwareCacheKey,
@@ -499,7 +502,8 @@ export function createCompilerHostWorker(options: CompilerOptions, setParentNode
499502
realpath,
500503
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth),
501504
createDirectory: d => system.createDirectory(d),
502-
createHash: maybeBind(system, system.createHash)
505+
createHash: maybeBind(system, system.createHash),
506+
require: maybeBind(system, system.require),
503507
};
504508
return compilerHost;
505509
}
@@ -2670,6 +2674,30 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26702674
return hasEmitBlockingDiagnostics.has(toPath(emitFileName));
26712675
}
26722676

2677+
function getCustomTransformers() {
2678+
if (!host.require) {
2679+
return emptyArray;
2680+
}
2681+
2682+
const compilerOptions = program.getCompilerOptions();
2683+
const customTransformers = mapDefined(compilerOptions.plugins, config => {
2684+
if (config.type !== "transformer") return undefined;
2685+
2686+
// TODO(jakebailey): The LS plugin loader is more complicated than this; copy.
2687+
const result = host.require!(program.getCurrentDirectory(), config.path);
2688+
// TODO(jakebailey): error handling, only do this once per, etc
2689+
Debug.assertIsDefined(result.module);
2690+
2691+
const factory = result.module as CustomTransformersModuleFactory;
2692+
Debug.assert(typeof factory === "function");
2693+
2694+
const plugin = factory({ typescript: getTypeScriptNamespace() });
2695+
return plugin.create({ program, config });
2696+
});
2697+
2698+
return customTransformers ?? emptyArray;
2699+
}
2700+
26732701
function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnly?: boolean | EmitOnly, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult {
26742702
if (!forceDtsEmit) {
26752703
const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken);
@@ -2688,11 +2716,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26882716

26892717
performance.mark("beforeEmit");
26902718

2719+
const mergedCustomTransformers = mergeCustomTransformers(...getCustomTransformers(), customTransformers);
2720+
26912721
const emitResult = emitFiles(
26922722
emitResolver,
26932723
getEmitHost(writeFileCallback),
26942724
sourceFile,
2695-
getTransformers(options, customTransformers, emitOnly),
2725+
getTransformers(options, mergedCustomTransformers, emitOnly),
26962726
emitOnly,
26972727
/*onlyBuildInfo*/ false,
26982728
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
@@ -7022,8 +7023,18 @@ export enum ModuleDetectionKind {
70227023
Force = 3,
70237024
}
70247025

7026+
export type PluginConfig = PluginImport | TransformerPluginImport;
7027+
70257028
export interface PluginImport {
7029+
type?: undefined;
70267030
name: string;
7031+
/** @internal */
7032+
global?: boolean;
7033+
}
7034+
7035+
export interface TransformerPluginImport {
7036+
type: "transformer",
7037+
path: string;
70277038
}
70287039

70297040
export interface ProjectReference {
@@ -7060,7 +7071,7 @@ export enum PollingWatchKind {
70607071
FixedChunkSize,
70617072
}
70627073

7063-
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
7074+
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginConfig[] | ProjectReference[] | null | undefined;
70647075

70657076
export interface CompilerOptions {
70667077
/** @internal */ all?: boolean;
@@ -7161,7 +7172,7 @@ export interface CompilerOptions {
71617172
* @internal
71627173
*/
71637174
pathsBasePath?: string;
7164-
/** @internal */ plugins?: PluginImport[];
7175+
/** @internal */ plugins?: PluginConfig[];
71657176
preserveConstEnums?: boolean;
71667177
noImplicitOverride?: boolean;
71677178
preserveSymlinks?: boolean;
@@ -7207,6 +7218,7 @@ export interface CompilerOptions {
72077218
esModuleInterop?: boolean;
72087219
/** @internal */ showConfig?: boolean;
72097220
useDefineForClassFields?: boolean;
7221+
customTransformers?: string[];
72107222

72117223
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
72127224
}
@@ -7806,6 +7818,7 @@ export interface CompilerHost extends ModuleResolutionHost {
78067818
createHash?(data: string): string;
78077819
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
78087820
/** @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
7821+
/** @internal */ require?(baseDir: string, moduleName: string): ModuleImportResult;
78097822

78107823
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
78117824
/** @internal */createDirectory?(directory: string): void;
@@ -9973,3 +9986,19 @@ export interface Queue<T> {
99739986
dequeue(): T;
99749987
isEmpty(): boolean;
99759988
}
9989+
9990+
export type CustomTransformersModuleFactory = (mod: { typescript: typeof ts }) => CustomTransformersModule;
9991+
9992+
export interface CustomTransformersModuleWithName {
9993+
name: string;
9994+
module: CustomTransformersModule;
9995+
}
9996+
9997+
export interface CustomTransformersModule {
9998+
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
9999+
}
10000+
10001+
export interface CustomTransformersCreateInfo {
10002+
program: Program;
10003+
config: any;
10004+
}

src/server/project.ts

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

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

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

1859-
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
1859+
this.enablePlugin({ name: globalPluginName, global: true }, searchPaths);
18601860
}
18611861
}
18621862

@@ -2711,7 +2711,9 @@ export class ConfiguredProject extends Project {
27112711
// Enable tsconfig-specified plugins
27122712
if (options.plugins) {
27132713
for (const pluginConfigEntry of options.plugins) {
2714-
this.enablePlugin(pluginConfigEntry, searchPaths);
2714+
if (pluginConfigEntry.type === undefined) {
2715+
this.enablePlugin(pluginConfigEntry, searchPaths);
2716+
}
27152717
}
27162718
}
27172719

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
@@ -7127,9 +7127,15 @@ declare namespace ts {
71277127
*/
71287128
Force = 3
71297129
}
7130+
type PluginConfig = PluginImport | TransformerPluginImport;
71307131
interface PluginImport {
7132+
type?: undefined;
71317133
name: string;
71327134
}
7135+
interface TransformerPluginImport {
7136+
type: "transformer";
7137+
path: string;
7138+
}
71337139
interface ProjectReference {
71347140
/** A normalized path on disk */
71357141
path: string;
@@ -7160,7 +7166,7 @@ declare namespace ts {
71607166
DynamicPriority = 2,
71617167
FixedChunkSize = 3
71627168
}
7163-
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
7169+
type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginConfig[] | ProjectReference[] | null | undefined;
71647170
interface CompilerOptions {
71657171
allowImportingTsExtensions?: boolean;
71667172
allowJs?: boolean;
@@ -7265,6 +7271,7 @@ declare namespace ts {
72657271
verbatimModuleSyntax?: boolean;
72667272
esModuleInterop?: boolean;
72677273
useDefineForClassFields?: boolean;
7274+
customTransformers?: string[];
72687275
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
72697276
}
72707277
interface WatchOptions {
@@ -8397,6 +8404,20 @@ declare namespace ts {
83978404
negative: boolean;
83988405
base10Value: string;
83998406
}
8407+
type CustomTransformersModuleFactory = (mod: {
8408+
typescript: typeof ts;
8409+
}) => CustomTransformersModule;
8410+
interface CustomTransformersModuleWithName {
8411+
name: string;
8412+
module: CustomTransformersModule;
8413+
}
8414+
interface CustomTransformersModule {
8415+
create(createInfo: CustomTransformersCreateInfo): CustomTransformers;
8416+
}
8417+
interface CustomTransformersCreateInfo {
8418+
program: Program;
8419+
config: any;
8420+
}
84008421
enum FileWatcherEventKind {
84018422
Created = 0,
84028423
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)