Skip to content

Commit 15e9c4c

Browse files
authored
Merge pull request microsoft#31301 from microsoft/fixCustomTransformers
Add opt-in behavior for custom transforms to support bundles
2 parents b40b542 + 0c1a283 commit 15e9c4c

File tree

8 files changed

+108
-38
lines changed

8 files changed

+108
-38
lines changed

src/compiler/emitter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ namespace ts {
222222

223223
/*@internal*/
224224
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
225-
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<Bundle | SourceFile>[], declarationTransformers?: TransformerFactory<Bundle | SourceFile>[], onlyBuildInfo?: boolean): EmitResult {
225+
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean): EmitResult {
226226
const compilerOptions = host.getCompilerOptions();
227227
const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined;
228228
const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined;
@@ -303,7 +303,7 @@ namespace ts {
303303
return;
304304
}
305305
// Transform the source files
306-
const transform = transformNodes(resolver, host, compilerOptions, [sourceFileOrBundle], transformers!, /*allowDtsFiles*/ false);
306+
const transform = transformNodes(resolver, host, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false);
307307

308308
const printerOptions: PrinterOptions = {
309309
removeComments: compilerOptions.removeComments,
@@ -349,7 +349,7 @@ namespace ts {
349349
// Do that here when emitting only dts files
350350
nonJsFiles.forEach(collectLinkedAliases);
351351
}
352-
const declarationTransform = transformNodes(resolver, host, compilerOptions, inputListOrBundle, concatenate([transformDeclarations], declarationTransformers), /*allowDtsFiles*/ false);
352+
const declarationTransform = transformNodes(resolver, host, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false);
353353
if (length(declarationTransform.diagnostics)) {
354354
for (const diagnostic of declarationTransform.diagnostics!) {
355355
emitterDiagnostics.add(diagnostic);
@@ -723,7 +723,7 @@ namespace ts {
723723
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
724724
getProgramBuildInfo: returnUndefined
725725
};
726-
emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, /*emitOnlyDtsFiles*/ false, getTransformers(config.options));
726+
emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, getTransformers(config.options), /*emitOnlyDtsFiles*/ false);
727727
return outputFiles;
728728
}
729729

src/compiler/factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2949,7 +2949,7 @@ namespace ts {
29492949
return node;
29502950
}
29512951

2952-
export function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends: ReadonlyArray<UnparsedSource> = emptyArray) {
2952+
export function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends: ReadonlyArray<UnparsedSource | InputFiles> = emptyArray) {
29532953
if (node.sourceFiles !== sourceFiles || node.prepends !== prepends) {
29542954
return createBundle(sourceFiles, prepends);
29552955
}

src/compiler/program.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,9 +1453,8 @@ namespace ts {
14531453
notImplementedResolver,
14541454
getEmitHost(writeFileCallback),
14551455
/*targetSourceFile*/ undefined,
1456+
/*transformers*/ noTransformers,
14561457
/*emitOnlyDtsFiles*/ false,
1457-
/*transformers*/ undefined,
1458-
/*declaraitonTransformers*/ undefined,
14591458
/*onlyBuildInfo*/ true
14601459
);
14611460

@@ -1574,14 +1573,12 @@ namespace ts {
15741573

15751574
performance.mark("beforeEmit");
15761575

1577-
const transformers = emitOnlyDtsFiles ? [] : getTransformers(options, customTransformers);
15781576
const emitResult = emitFiles(
15791577
emitResolver,
15801578
getEmitHost(writeFileCallback),
15811579
sourceFile,
1580+
getTransformers(options, customTransformers, emitOnlyDtsFiles),
15821581
emitOnlyDtsFiles,
1583-
transformers,
1584-
customTransformers && customTransformers.afterDeclarations
15851582
);
15861583

15871584
performance.mark("afterEmit");

src/compiler/transformer.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,24 @@ namespace ts {
2424
EmitNotifications = 1 << 1,
2525
}
2626

27-
export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers) {
27+
export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray };
28+
29+
export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers {
30+
return {
31+
scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles),
32+
declarationTransformers: getDeclarationTransformers(customTransformers),
33+
};
34+
}
35+
36+
function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) {
37+
if (emitOnlyDtsFiles) return emptyArray;
38+
2839
const jsx = compilerOptions.jsx;
2940
const languageVersion = getEmitScriptTarget(compilerOptions);
3041
const moduleKind = getEmitModuleKind(compilerOptions);
3142
const transformers: TransformerFactory<SourceFile | Bundle>[] = [];
3243

33-
addRange(transformers, customTransformers && customTransformers.before);
44+
addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory));
3445

3546
transformers.push(transformTypeScript);
3647

@@ -71,11 +82,44 @@ namespace ts {
7182
transformers.push(transformES5);
7283
}
7384

74-
addRange(transformers, customTransformers && customTransformers.after);
85+
addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory));
86+
return transformers;
87+
}
7588

89+
function getDeclarationTransformers(customTransformers?: CustomTransformers) {
90+
const transformers: TransformerFactory<SourceFile | Bundle>[] = [];
91+
transformers.push(transformDeclarations);
92+
addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory));
7693
return transformers;
7794
}
7895

96+
/**
97+
* Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles.
98+
*/
99+
function wrapCustomTransformer(transformer: CustomTransformer): Transformer<Bundle | SourceFile> {
100+
return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node);
101+
}
102+
103+
/**
104+
* Wrap a transformer factory that may return a custom script or declaration transformer object.
105+
*/
106+
function wrapCustomTransformerFactory<T extends SourceFile | Bundle>(transformer: TransformerFactory<T> | CustomTransformerFactory, handleDefault: (node: Transformer<T>) => Transformer<Bundle | SourceFile>): TransformerFactory<Bundle | SourceFile> {
107+
return context => {
108+
const customTransformer = transformer(context);
109+
return typeof customTransformer === "function"
110+
? handleDefault(customTransformer)
111+
: wrapCustomTransformer(customTransformer);
112+
};
113+
}
114+
115+
function wrapScriptTransformerFactory(transformer: TransformerFactory<SourceFile> | CustomTransformerFactory): TransformerFactory<Bundle | SourceFile> {
116+
return wrapCustomTransformerFactory(transformer, chainBundle);
117+
}
118+
119+
function wrapDeclarationTransformerFactory(transformer: TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory): TransformerFactory<Bundle | SourceFile> {
120+
return wrapCustomTransformerFactory(transformer, identity);
121+
}
122+
79123
export function noEmitSubstitution(_hint: EmitHint, node: Node) {
80124
return node;
81125
}

src/compiler/types.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3011,13 +3011,26 @@ namespace ts {
30113011
Completely = 1 << 1,
30123012
}
30133013

3014+
export type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;
3015+
3016+
export interface CustomTransformer {
3017+
transformSourceFile(node: SourceFile): SourceFile;
3018+
transformBundle(node: Bundle): Bundle;
3019+
}
3020+
30143021
export interface CustomTransformers {
30153022
/** Custom transformers to evaluate before built-in .js transformations. */
3016-
before?: TransformerFactory<SourceFile>[];
3023+
before?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
30173024
/** Custom transformers to evaluate after built-in .js transformations. */
3018-
after?: TransformerFactory<SourceFile>[];
3025+
after?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
30193026
/** Custom transformers to evaluate after built-in .d.ts transformations. */
3020-
afterDeclarations?: TransformerFactory<Bundle | SourceFile>[];
3027+
afterDeclarations?: (TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory)[];
3028+
}
3029+
3030+
/*@internal*/
3031+
export interface EmitTransformers {
3032+
scriptTransformers: readonly TransformerFactory<SourceFile | Bundle>[];
3033+
declarationTransformers: readonly TransformerFactory<SourceFile | Bundle>[];
30213034
}
30223035

30233036
export interface SourceMapSpan {

src/testRunner/unittests/customTransforms.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,22 +140,28 @@ namespace ts {
140140
],
141141
{
142142
before: [
143-
context => node => visitNode(node, function visitor(node: Node): Node {
144-
if (isIdentifier(node) && node.text === "original") {
145-
const newNode = createIdentifier("changed");
146-
setSourceMapRange(newNode, {
147-
pos: 0,
148-
end: 7,
149-
// Do not provide a custom skipTrivia function for `source`.
150-
source: createSourceMapSource("another.html", "changed;")
151-
});
152-
return newNode;
153-
}
154-
return visitEachChild(node, visitor, context);
155-
})
143+
context => {
144+
const transformSourceFile: Transformer<SourceFile> = node => visitNode(node, function visitor(node: Node): Node {
145+
if (isIdentifier(node) && node.text === "original") {
146+
const newNode = createIdentifier("changed");
147+
setSourceMapRange(newNode, {
148+
pos: 0,
149+
end: 7,
150+
// Do not provide a custom skipTrivia function for `source`.
151+
source: createSourceMapSource("another.html", "changed;")
152+
});
153+
return newNode;
154+
}
155+
return visitEachChild(node, visitor, context);
156+
});
157+
return {
158+
transformSourceFile,
159+
transformBundle: node => createBundle(map(node.sourceFiles, transformSourceFile), node.prepends),
160+
};
161+
}
156162
]
157163
},
158-
{ sourceMap: true }
164+
{ sourceMap: true, outFile: "source.js" }
159165
);
160166

161167
});

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,13 +1879,18 @@ declare namespace ts {
18791879
sourceFile: SourceFile;
18801880
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
18811881
}
1882+
type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;
1883+
interface CustomTransformer {
1884+
transformSourceFile(node: SourceFile): SourceFile;
1885+
transformBundle(node: Bundle): Bundle;
1886+
}
18821887
interface CustomTransformers {
18831888
/** Custom transformers to evaluate before built-in .js transformations. */
1884-
before?: TransformerFactory<SourceFile>[];
1889+
before?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
18851890
/** Custom transformers to evaluate after built-in .js transformations. */
1886-
after?: TransformerFactory<SourceFile>[];
1891+
after?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
18871892
/** Custom transformers to evaluate after built-in .d.ts transformations. */
1888-
afterDeclarations?: TransformerFactory<Bundle | SourceFile>[];
1893+
afterDeclarations?: (TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory)[];
18891894
}
18901895
interface SourceMapSpan {
18911896
/** Line number in the .js file. */
@@ -4071,7 +4076,7 @@ declare namespace ts {
40714076
function createInputFiles(javascriptText: string, declarationText: string): InputFiles;
40724077
function createInputFiles(readFileText: (path: string) => string | undefined, javascriptPath: string, javascriptMapPath: string | undefined, declarationPath: string, declarationMapPath: string | undefined, buildInfoPath: string | undefined): InputFiles;
40734078
function createInputFiles(javascriptText: string, declarationText: string, javascriptMapPath: string | undefined, javascriptMapText: string | undefined, declarationMapPath: string | undefined, declarationMapText: string | undefined): InputFiles;
4074-
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource>): Bundle;
4079+
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource | InputFiles>): Bundle;
40754080
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>): CallExpression;
40764081
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>, param: ParameterDeclaration, paramValue: Expression): CallExpression;
40774082
function createImmediatelyInvokedArrowFunction(statements: ReadonlyArray<Statement>): CallExpression;

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,13 +1879,18 @@ declare namespace ts {
18791879
sourceFile: SourceFile;
18801880
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
18811881
}
1882+
type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;
1883+
interface CustomTransformer {
1884+
transformSourceFile(node: SourceFile): SourceFile;
1885+
transformBundle(node: Bundle): Bundle;
1886+
}
18821887
interface CustomTransformers {
18831888
/** Custom transformers to evaluate before built-in .js transformations. */
1884-
before?: TransformerFactory<SourceFile>[];
1889+
before?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
18851890
/** Custom transformers to evaluate after built-in .js transformations. */
1886-
after?: TransformerFactory<SourceFile>[];
1891+
after?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
18871892
/** Custom transformers to evaluate after built-in .d.ts transformations. */
1888-
afterDeclarations?: TransformerFactory<Bundle | SourceFile>[];
1893+
afterDeclarations?: (TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory)[];
18891894
}
18901895
interface SourceMapSpan {
18911896
/** Line number in the .js file. */
@@ -4071,7 +4076,7 @@ declare namespace ts {
40714076
function createInputFiles(javascriptText: string, declarationText: string): InputFiles;
40724077
function createInputFiles(readFileText: (path: string) => string | undefined, javascriptPath: string, javascriptMapPath: string | undefined, declarationPath: string, declarationMapPath: string | undefined, buildInfoPath: string | undefined): InputFiles;
40734078
function createInputFiles(javascriptText: string, declarationText: string, javascriptMapPath: string | undefined, javascriptMapText: string | undefined, declarationMapPath: string | undefined, declarationMapText: string | undefined): InputFiles;
4074-
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource>): Bundle;
4079+
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource | InputFiles>): Bundle;
40754080
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>): CallExpression;
40764081
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>, param: ParameterDeclaration, paramValue: Expression): CallExpression;
40774082
function createImmediatelyInvokedArrowFunction(statements: ReadonlyArray<Statement>): CallExpression;

0 commit comments

Comments
 (0)