Skip to content

Commit 3b20d82

Browse files
committed
allow 'transform()' to transform arbitrary nodes.
1 parent c22730e commit 3b20d82

File tree

6 files changed

+43
-61
lines changed

6 files changed

+43
-61
lines changed

src/compiler/emitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ts {
1010

1111
/*@internal*/
1212
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
13-
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: Transformer[]): EmitResult {
13+
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<SourceFile>[]): EmitResult {
1414
const compilerOptions = host.getCompilerOptions();
1515
const moduleKind = getEmitModuleKind(compilerOptions);
1616
const sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined;
@@ -28,7 +28,7 @@ namespace ts {
2828
const sourceFiles = getSourceFilesToEmit(host, targetSourceFile);
2929

3030
// Transform the source files
31-
const transform = transformFiles(resolver, host, sourceFiles, transformers);
31+
const transform = transformNodes(resolver, host, compilerOptions, sourceFiles, transformers, /*allowDtsFiles*/ false);
3232

3333
// Create a printer to print the nodes
3434
const printer = createPrinter(compilerOptions, {

src/compiler/transformer.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
/* @internal */
1515
namespace ts {
16-
function getModuleTransformer(moduleKind: ModuleKind): Transformer {
16+
function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory<SourceFile> {
1717
switch (moduleKind) {
1818
case ModuleKind.ES2015:
1919
return transformES2015Module;
@@ -40,7 +40,7 @@ namespace ts {
4040
const jsx = compilerOptions.jsx;
4141
const languageVersion = getEmitScriptTarget(compilerOptions);
4242
const moduleKind = getEmitModuleKind(compilerOptions);
43-
const transformers: Transformer[] = [];
43+
const transformers: TransformerFactory<SourceFile>[] = [];
4444

4545
addRange(transformers, customTransformers && customTransformers.before);
4646

@@ -84,11 +84,13 @@ namespace ts {
8484
* Transforms an array of SourceFiles by passing them through each transformer.
8585
*
8686
* @param resolver The emit resolver provided by the checker.
87-
* @param host The emit host.
88-
* @param sourceFiles An array of source files
89-
* @param transforms An array of Transformers.
87+
* @param host The emit host object used to interact with the file system.
88+
* @param options Compiler options to surface in the `TransformationContext`.
89+
* @param nodes An array of nodes to transform.
90+
* @param transforms An array of `TransformerFactory` callbacks.
91+
* @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files.
9092
*/
91-
export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult {
93+
export function transformNodes<T extends Node>(resolver: EmitResolver, host: EmitHost, options: CompilerOptions, nodes: T[], transformers: TransformerFactory<T>[], allowDtsFiles: boolean): TransformationResult<T> {
9294
const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count);
9395
let lexicalEnvironmentVariableDeclarations: VariableDeclaration[];
9496
let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[];
@@ -104,7 +106,7 @@ namespace ts {
104106
// The transformation context is provided to each transformer as part of transformer
105107
// initialization.
106108
const context: TransformationContext = {
107-
getCompilerOptions: () => host.getCompilerOptions(),
109+
getCompilerOptions: () => options,
108110
getEmitResolver: () => resolver,
109111
getEmitHost: () => host,
110112
startLexicalEnvironment,
@@ -134,7 +136,9 @@ namespace ts {
134136
};
135137

136138
// Ensure the parse tree is clean before applying transformations
137-
forEach(sourceFiles, disposeEmitNodes);
139+
for (const node of nodes) {
140+
disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node)));
141+
}
138142

139143
performance.mark("beforeTransform");
140144

@@ -144,8 +148,8 @@ namespace ts {
144148
// prevent modification of transformation hooks.
145149
state = TransformationState.Initialized;
146150

147-
// Transform each source file.
148-
const transformed = map(sourceFiles, transformSourceFile);
151+
// Transform each node.
152+
const transformed = map(nodes, allowDtsFiles ? transformation : transformRoot);
149153

150154
// prevent modification of the lexical environment.
151155
state = TransformationState.Completed;
@@ -160,17 +164,8 @@ namespace ts {
160164
dispose
161165
};
162166

163-
/**
164-
* Transforms a source file.
165-
*
166-
* @param sourceFile The source file to transform.
167-
*/
168-
function transformSourceFile(sourceFile: SourceFile) {
169-
if (isDeclarationFile(sourceFile)) {
170-
return sourceFile;
171-
}
172-
173-
return transformation(sourceFile);
167+
function transformRoot(node: T) {
168+
return node && (!isSourceFile(node) || !isDeclarationFile(node)) ? transformation(node) : node;
174169
}
175170

176171
/**
@@ -366,7 +361,9 @@ namespace ts {
366361
function dispose() {
367362
if (state < TransformationState.Disposed) {
368363
// Clean up emit nodes on parse tree
369-
forEach(sourceFiles, disposeEmitNodes);
364+
for (const node of nodes) {
365+
disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node)));
366+
}
370367

371368
// Release references to external entries for GC purposes.
372369
lexicalEnvironmentVariableDeclarations = undefined;

src/compiler/types.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,9 +2308,9 @@
23082308

23092309
export interface CustomTransformers {
23102310
/** Custom transformers to evaluate before built-in transformations. */
2311-
before?: Transformer[];
2311+
before?: TransformerFactory<SourceFile>[];
23122312
/** Custom transformers to evaluate after built-in transformations. */
2313-
after?: Transformer[];
2313+
after?: TransformerFactory<SourceFile>[];
23142314
}
23152315

23162316
export interface SourceMapSpan {
@@ -3867,7 +3867,7 @@
38673867
* are emitted by the pretty printer.
38683868
*
38693869
* NOTE: Transformation hooks should only be modified during `Transformer` initialization,
3870-
* before returning the `FileTransformer` callback.
3870+
* before returning the `NodeTransformer` callback.
38713871
*/
38723872
onSubstituteNode?: (hint: EmitHint, node: Node) => Node;
38733873

@@ -3888,14 +3888,14 @@
38883888
* the printer emits a node.
38893889
*
38903890
* NOTE: Transformation hooks should only be modified during `Transformer` initialization,
3891-
* before returning the `FileTransformer` callback.
3891+
* before returning the `NodeTransformer` callback.
38923892
*/
38933893
onEmitNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void;
38943894
}
38953895

3896-
export interface TransformationResult {
3896+
export interface TransformationResult<T extends Node> {
38973897
/** Gets the transformed source files. */
3898-
transformed: SourceFile[];
3898+
transformed: T[];
38993899

39003900
/** Gets diagnostics for the transformation. */
39013901
diagnostics?: Diagnostic[];
@@ -3925,23 +3925,23 @@
39253925
}
39263926

39273927
/**
3928-
* A function that is used to initialize and return a `FileTransformer` callback, which in turn
3929-
* will be used to transform one or more `SourceFile` objects.
3928+
* A function that is used to initialize and return a `Transformer` callback, which in turn
3929+
* will be used to transform one or more nodes.
39303930
*/
3931-
export type Transformer = (context: TransformationContext) => FileTransformer;
3931+
export type TransformerFactory<T extends Node> = (context: TransformationContext) => Transformer<T>;
39323932

39333933
/**
3934-
* A function that transforms a `SourceFile` object.
3934+
* A function that transforms a node.
39353935
*/
3936-
export type FileTransformer = (node: SourceFile) => SourceFile;
3937-
3938-
export type VisitResult<T extends Node> = T | T[];
3936+
export type Transformer<T extends Node> = (node: T) => T;
39393937

39403938
/**
39413939
* A function that accepts and possible transforms a node.
39423940
*/
39433941
export type Visitor = (node: Node) => VisitResult<Node>;
39443942

3943+
export type VisitResult<T extends Node> = T | T[];
3944+
39453945
export interface Printer {
39463946
/**
39473947
* Print a node and its subtree as-is, without any emit transformations.

src/harness/unittests/customTransforms.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ namespace ts {
4747
`
4848
}];
4949

50-
const before: Transformer = context => {
50+
const before: TransformerFactory<SourceFile> = context => {
5151
return file => visitEachChild(file, visit, context);
5252
function visit(node: Node): VisitResult<Node> {
5353
switch (node.kind) {
@@ -63,7 +63,7 @@ namespace ts {
6363
}
6464
};
6565

66-
const after: Transformer = context => {
66+
const after: TransformerFactory<SourceFile> = context => {
6767
return file => visitEachChild(file, visit, context);
6868
function visit(node: Node): VisitResult<Node> {
6969
switch (node.kind) {

src/harness/unittests/transform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace ts {
55
describe("TransformAPI", () => {
6-
function transformsCorrectly(name: string, source: string, transformers: Transformer[]) {
6+
function transformsCorrectly(name: string, source: string, transformers: TransformerFactory<SourceFile>[]) {
77
it(name, () => {
88
Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${name}.js`, () => {
99
const transformed = transform(createSourceFile("source.ts", source, ScriptTarget.ES2015), transformers);

src/services/transform.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,16 @@
22
/// <reference path="transpile.ts"/>
33
namespace ts {
44
/**
5-
* Transform one or more source files using the supplied transformers.
6-
* @param source A `SourceFile` or an array of `SourceFiles`.
7-
* @param transformers An array of `Transformer` callbacks used to process the transformation.
5+
* Transform one or more nodes using the supplied transformers.
6+
* @param source A single `Node` or an array of `Node` objects.
7+
* @param transformers An array of `TransformerFactory` callbacks used to process the transformation.
88
* @param compilerOptions Optional compiler options.
99
*/
10-
export function transform(source: SourceFile | SourceFile[], transformers: Transformer[], compilerOptions?: CompilerOptions) {
10+
export function transform<T extends Node>(source: T | T[], transformers: TransformerFactory<T>[], compilerOptions?: CompilerOptions) {
1111
const diagnostics: Diagnostic[] = [];
1212
compilerOptions = fixupCompilerOptions(compilerOptions, diagnostics);
13-
const newLine = getNewLineCharacter(compilerOptions);
14-
const sourceFiles = isArray(source) ? source : [source];
15-
const fileMap = arrayToMap(sourceFiles, sourceFile => sourceFile.fileName);
16-
const emitHost: EmitHost = {
17-
getCompilerOptions: () => compilerOptions,
18-
getCanonicalFileName: fileName => fileName,
19-
getCommonSourceDirectory: () => "",
20-
getCurrentDirectory: () => "",
21-
getNewLine: () => newLine,
22-
getSourceFile: fileName => fileMap.get(fileName),
23-
getSourceFileByPath: fileName => fileMap.get(fileName),
24-
getSourceFiles: () => sourceFiles,
25-
isSourceFileFromExternalLibrary: () => false,
26-
isEmitBlocked: () => false,
27-
writeFile: () => Debug.fail("'writeFile()' is not supported during transformation.")
28-
};
29-
const result = transformFiles(/*resolver*/ undefined, emitHost, sourceFiles, transformers);
13+
const nodes = isArray(source) ? source : [source];
14+
const result = transformNodes(/*resolver*/ undefined, /*emitHost*/ undefined, compilerOptions, nodes, transformers, /*allowDtsFiles*/ true);
3015
result.diagnostics = concatenate(result.diagnostics, diagnostics);
3116
return result;
3217
}

0 commit comments

Comments
 (0)