Skip to content

Commit

Permalink
Merge pull request #31301 from microsoft/fixCustomTransformers
Browse files Browse the repository at this point in the history
Add opt-in behavior for custom transforms to support bundles
  • Loading branch information
rbuckton committed May 8, 2019
2 parents b40b542 + 0c1a283 commit 15e9c4c
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 38 deletions.
8 changes: 4 additions & 4 deletions src/compiler/emitter.ts
Expand Up @@ -222,7 +222,7 @@ namespace ts {

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

const printerOptions: PrinterOptions = {
removeComments: compilerOptions.removeComments,
Expand Down Expand Up @@ -349,7 +349,7 @@ namespace ts {
// Do that here when emitting only dts files
nonJsFiles.forEach(collectLinkedAliases);
}
const declarationTransform = transformNodes(resolver, host, compilerOptions, inputListOrBundle, concatenate([transformDeclarations], declarationTransformers), /*allowDtsFiles*/ false);
const declarationTransform = transformNodes(resolver, host, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false);
if (length(declarationTransform.diagnostics)) {
for (const diagnostic of declarationTransform.diagnostics!) {
emitterDiagnostics.add(diagnostic);
Expand Down Expand Up @@ -723,7 +723,7 @@ namespace ts {
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
getProgramBuildInfo: returnUndefined
};
emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, /*emitOnlyDtsFiles*/ false, getTransformers(config.options));
emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, getTransformers(config.options), /*emitOnlyDtsFiles*/ false);
return outputFiles;
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory.ts
Expand Up @@ -2949,7 +2949,7 @@ namespace ts {
return node;
}

export function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends: ReadonlyArray<UnparsedSource> = emptyArray) {
export function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends: ReadonlyArray<UnparsedSource | InputFiles> = emptyArray) {
if (node.sourceFiles !== sourceFiles || node.prepends !== prepends) {
return createBundle(sourceFiles, prepends);
}
Expand Down
7 changes: 2 additions & 5 deletions src/compiler/program.ts
Expand Up @@ -1453,9 +1453,8 @@ namespace ts {
notImplementedResolver,
getEmitHost(writeFileCallback),
/*targetSourceFile*/ undefined,
/*transformers*/ noTransformers,
/*emitOnlyDtsFiles*/ false,
/*transformers*/ undefined,
/*declaraitonTransformers*/ undefined,
/*onlyBuildInfo*/ true
);

Expand Down Expand Up @@ -1574,14 +1573,12 @@ namespace ts {

performance.mark("beforeEmit");

const transformers = emitOnlyDtsFiles ? [] : getTransformers(options, customTransformers);
const emitResult = emitFiles(
emitResolver,
getEmitHost(writeFileCallback),
sourceFile,
getTransformers(options, customTransformers, emitOnlyDtsFiles),
emitOnlyDtsFiles,
transformers,
customTransformers && customTransformers.afterDeclarations
);

performance.mark("afterEmit");
Expand Down
50 changes: 47 additions & 3 deletions src/compiler/transformer.ts
Expand Up @@ -24,13 +24,24 @@ namespace ts {
EmitNotifications = 1 << 1,
}

export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers) {
export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray };

export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers {
return {
scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles),
declarationTransformers: getDeclarationTransformers(customTransformers),
};
}

function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) {
if (emitOnlyDtsFiles) return emptyArray;

const jsx = compilerOptions.jsx;
const languageVersion = getEmitScriptTarget(compilerOptions);
const moduleKind = getEmitModuleKind(compilerOptions);
const transformers: TransformerFactory<SourceFile | Bundle>[] = [];

addRange(transformers, customTransformers && customTransformers.before);
addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory));

transformers.push(transformTypeScript);

Expand Down Expand Up @@ -71,11 +82,44 @@ namespace ts {
transformers.push(transformES5);
}

addRange(transformers, customTransformers && customTransformers.after);
addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory));
return transformers;
}

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

/**
* Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles.
*/
function wrapCustomTransformer(transformer: CustomTransformer): Transformer<Bundle | SourceFile> {
return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node);
}

/**
* Wrap a transformer factory that may return a custom script or declaration transformer object.
*/
function wrapCustomTransformerFactory<T extends SourceFile | Bundle>(transformer: TransformerFactory<T> | CustomTransformerFactory, handleDefault: (node: Transformer<T>) => Transformer<Bundle | SourceFile>): TransformerFactory<Bundle | SourceFile> {
return context => {
const customTransformer = transformer(context);
return typeof customTransformer === "function"
? handleDefault(customTransformer)
: wrapCustomTransformer(customTransformer);
};
}

function wrapScriptTransformerFactory(transformer: TransformerFactory<SourceFile> | CustomTransformerFactory): TransformerFactory<Bundle | SourceFile> {
return wrapCustomTransformerFactory(transformer, chainBundle);
}

function wrapDeclarationTransformerFactory(transformer: TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory): TransformerFactory<Bundle | SourceFile> {
return wrapCustomTransformerFactory(transformer, identity);
}

export function noEmitSubstitution(_hint: EmitHint, node: Node) {
return node;
}
Expand Down
19 changes: 16 additions & 3 deletions src/compiler/types.ts
Expand Up @@ -3011,13 +3011,26 @@ namespace ts {
Completely = 1 << 1,
}

export type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;

export interface CustomTransformer {
transformSourceFile(node: SourceFile): SourceFile;
transformBundle(node: Bundle): Bundle;
}

export interface CustomTransformers {
/** Custom transformers to evaluate before built-in .js transformations. */
before?: TransformerFactory<SourceFile>[];
before?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
/** Custom transformers to evaluate after built-in .js transformations. */
after?: TransformerFactory<SourceFile>[];
after?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
/** Custom transformers to evaluate after built-in .d.ts transformations. */
afterDeclarations?: TransformerFactory<Bundle | SourceFile>[];
afterDeclarations?: (TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory)[];
}

/*@internal*/
export interface EmitTransformers {
scriptTransformers: readonly TransformerFactory<SourceFile | Bundle>[];
declarationTransformers: readonly TransformerFactory<SourceFile | Bundle>[];
}

export interface SourceMapSpan {
Expand Down
34 changes: 20 additions & 14 deletions src/testRunner/unittests/customTransforms.ts
Expand Up @@ -140,22 +140,28 @@ namespace ts {
],
{
before: [
context => node => visitNode(node, function visitor(node: Node): Node {
if (isIdentifier(node) && node.text === "original") {
const newNode = createIdentifier("changed");
setSourceMapRange(newNode, {
pos: 0,
end: 7,
// Do not provide a custom skipTrivia function for `source`.
source: createSourceMapSource("another.html", "changed;")
});
return newNode;
}
return visitEachChild(node, visitor, context);
})
context => {
const transformSourceFile: Transformer<SourceFile> = node => visitNode(node, function visitor(node: Node): Node {
if (isIdentifier(node) && node.text === "original") {
const newNode = createIdentifier("changed");
setSourceMapRange(newNode, {
pos: 0,
end: 7,
// Do not provide a custom skipTrivia function for `source`.
source: createSourceMapSource("another.html", "changed;")
});
return newNode;
}
return visitEachChild(node, visitor, context);
});
return {
transformSourceFile,
transformBundle: node => createBundle(map(node.sourceFiles, transformSourceFile), node.prepends),
};
}
]
},
{ sourceMap: true }
{ sourceMap: true, outFile: "source.js" }
);

});
Expand Down
13 changes: 9 additions & 4 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Expand Up @@ -1879,13 +1879,18 @@ declare namespace ts {
sourceFile: SourceFile;
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
}
type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;
interface CustomTransformer {
transformSourceFile(node: SourceFile): SourceFile;
transformBundle(node: Bundle): Bundle;
}
interface CustomTransformers {
/** Custom transformers to evaluate before built-in .js transformations. */
before?: TransformerFactory<SourceFile>[];
before?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
/** Custom transformers to evaluate after built-in .js transformations. */
after?: TransformerFactory<SourceFile>[];
after?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
/** Custom transformers to evaluate after built-in .d.ts transformations. */
afterDeclarations?: TransformerFactory<Bundle | SourceFile>[];
afterDeclarations?: (TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory)[];
}
interface SourceMapSpan {
/** Line number in the .js file. */
Expand Down Expand Up @@ -4071,7 +4076,7 @@ declare namespace ts {
function createInputFiles(javascriptText: string, declarationText: string): InputFiles;
function createInputFiles(readFileText: (path: string) => string | undefined, javascriptPath: string, javascriptMapPath: string | undefined, declarationPath: string, declarationMapPath: string | undefined, buildInfoPath: string | undefined): InputFiles;
function createInputFiles(javascriptText: string, declarationText: string, javascriptMapPath: string | undefined, javascriptMapText: string | undefined, declarationMapPath: string | undefined, declarationMapText: string | undefined): InputFiles;
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource>): Bundle;
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource | InputFiles>): Bundle;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>): CallExpression;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>, param: ParameterDeclaration, paramValue: Expression): CallExpression;
function createImmediatelyInvokedArrowFunction(statements: ReadonlyArray<Statement>): CallExpression;
Expand Down
13 changes: 9 additions & 4 deletions tests/baselines/reference/api/typescript.d.ts
Expand Up @@ -1879,13 +1879,18 @@ declare namespace ts {
sourceFile: SourceFile;
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
}
type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;
interface CustomTransformer {
transformSourceFile(node: SourceFile): SourceFile;
transformBundle(node: Bundle): Bundle;
}
interface CustomTransformers {
/** Custom transformers to evaluate before built-in .js transformations. */
before?: TransformerFactory<SourceFile>[];
before?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
/** Custom transformers to evaluate after built-in .js transformations. */
after?: TransformerFactory<SourceFile>[];
after?: (TransformerFactory<SourceFile> | CustomTransformerFactory)[];
/** Custom transformers to evaluate after built-in .d.ts transformations. */
afterDeclarations?: TransformerFactory<Bundle | SourceFile>[];
afterDeclarations?: (TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory)[];
}
interface SourceMapSpan {
/** Line number in the .js file. */
Expand Down Expand Up @@ -4071,7 +4076,7 @@ declare namespace ts {
function createInputFiles(javascriptText: string, declarationText: string): InputFiles;
function createInputFiles(readFileText: (path: string) => string | undefined, javascriptPath: string, javascriptMapPath: string | undefined, declarationPath: string, declarationMapPath: string | undefined, buildInfoPath: string | undefined): InputFiles;
function createInputFiles(javascriptText: string, declarationText: string, javascriptMapPath: string | undefined, javascriptMapText: string | undefined, declarationMapPath: string | undefined, declarationMapText: string | undefined): InputFiles;
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource>): Bundle;
function updateBundle(node: Bundle, sourceFiles: ReadonlyArray<SourceFile>, prepends?: ReadonlyArray<UnparsedSource | InputFiles>): Bundle;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>): CallExpression;
function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray<Statement>, param: ParameterDeclaration, paramValue: Expression): CallExpression;
function createImmediatelyInvokedArrowFunction(statements: ReadonlyArray<Statement>): CallExpression;
Expand Down

0 comments on commit 15e9c4c

Please sign in to comment.