WIP --annotateTransforms switch to add transformer diagnostics to Source Maps

This commit is contained in:
Ron Buckton 2022-10-25 17:18:22 -04:00
parent 2c12b14999
commit 0946a7f6ed
No known key found for this signature in database
GPG Key ID: 9ADA0DFD36502AB9
10 changed files with 592 additions and 55 deletions

View File

@ -318,6 +318,13 @@ namespace ts {
description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit,
defaultValueDescription: Diagnostics.Platform_specific
},
{
name: "annotateTransforms",
type: "boolean",
affectsEmit: true,
category: Diagnostics.Compiler_Diagnostics,
defaultValueDescription: false
}
];
/* @internal */

View File

@ -384,7 +384,7 @@ namespace ts {
return;
}
// Transform the source files
const transform = transformNodes(resolver, host, factory, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false);
const transform = transformNodes(resolver, host, /*factoryIn*/ undefined, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false);
const printerOptions: PrinterOptions = {
removeComments: compilerOptions.removeComments,
@ -397,7 +397,8 @@ namespace ts {
inlineSources: compilerOptions.inlineSources,
extendedDiagnostics: compilerOptions.extendedDiagnostics,
writeBundleFileInfo: !!bundleBuildInfo,
relativeToBuildInfo
relativeToBuildInfo,
annotateTransforms: compilerOptions.annotateTransforms
};
// Create a printer to print the nodes
@ -457,7 +458,8 @@ namespace ts {
onlyPrintJsDocStyle: true,
writeBundleFileInfo: !!bundleBuildInfo,
recordInternalSection: !!bundleBuildInfo,
relativeToBuildInfo
relativeToBuildInfo,
annotateTransforms: compilerOptions.annotateTransforms
};
const declarationPrinter = createPrinter(printerOptions, {
@ -877,6 +879,7 @@ namespace ts {
Substitution,
Comments,
SourceMaps,
TransformerAnnotations,
Emit,
}
@ -897,6 +900,7 @@ namespace ts {
const extendedDiagnostics = !!printerOptions.extendedDiagnostics;
const newLine = getNewLineCharacter(printerOptions);
const moduleKind = getEmitModuleKind(printerOptions);
const annotateTransforms = printerOptions.annotateTransforms;
const bundledHelpers = new Map<string, boolean>();
let currentSourceFile: SourceFile | undefined;
@ -1293,6 +1297,11 @@ namespace ts {
return pipelineEmitWithSourceMaps;
}
// falls through
case PipelinePhase.TransformerAnnotations:
if (annotateTransforms && sourceMapGenerator) {
return pipelineEmitWithTransformerAnnotations;
}
// falls through
case PipelinePhase.Emit:
return pipelineEmitWithHint;
default:
@ -5885,6 +5894,22 @@ namespace ts {
}
}
function pipelineEmitWithTransformerAnnotations(hint: EmitHint, node: Node) {
const pipelinePhase = getNextPipelinePhase(PipelinePhase.TransformerAnnotations, hint, node);
const transformerNames = annotateTransforms && sourceMapGenerator && getTransformerNames(node);
if (transformerNames) {
// Add leading and trailing annotations for the node. While this only emits transformer annotations
// currently, the format for annotations is intentionally generic to allow for other kinds of annotations
// in the future
sourceMapGenerator!.addAnnotation(writer.getLine(), writer.getColumn(), "typescript.transformers", { kind: "start", transformers: transformerNames });
pipelinePhase(hint, node);
sourceMapGenerator!.addAnnotation(writer.getLine(), writer.getColumn(), "typescript.transformers", { kind: "end" });
}
else {
pipelinePhase(hint, node);
}
}
/**
* Skips trivia such as comments and white-space that can be optionally overridden by the source-map source
*/
@ -6047,4 +6072,44 @@ namespace ts {
typeof parenthesizerRule === "object" ? emitListItemWithParenthesizerRuleSelector :
emitListItemWithParenthesizerRule;
}
function getTransformerNames(node: Node | undefined) {
let transformerNames: string[] | undefined;
let lastTransformerName: string | undefined;
while (node) {
const transformerName = getTransformerName(node.transformer);
if (transformerName && transformerName !== lastTransformerName) {
transformerNames = append(transformerNames, transformerName);
lastTransformerName = transformerName;
}
node = node.original;
}
return transformerNames;
}
function getTransformerName(transformer: TransformerFactory<Node> | undefined) {
switch (transformer) {
case transformTypeScript: return "ts";
case transformESNext: return "esnext";
case transformES2021: return "es2021";
case transformES2020: return "es2020";
case transformES2019: return "es2019";
case transformES2018: return "es2018";
case transformES2017: return "es2017";
case transformES2016: return "es2016";
case transformES2015: return "es2015";
case transformES5: return "es5";
case transformClassFields: return "classFields";
case transformLegacyDecorators: return "legacyDecorators";
case transformGenerators: return "generators";
case transformJsx: return "jsx";
case transformECMAScriptModule: return "esmodule";
case transformModule: return "module";
case transformSystemModule: return "system";
case transformNodeModule: return "node";
case transformDeclarations: return "declarations";
case undefined: return;
default: return transformer.name;
}
}
}

View File

@ -39,12 +39,27 @@ namespace ts {
let hasPendingSource = false;
let hasPendingName = false;
const singleAnnotationCharCodes: number[] = [];
let singleAnnotation = "";
const annotationsCharCodes: number[] = [];
let annotations = "";
let lastAnnotatedGeneratedLine = 0;
let lastAnnotatedGeneratedCharacter = 0;
let lastAnnotationsNameIndex = 0;
let lastAnnotations: SourceMapAnnotation[] | undefined;
let hasLastAnnotation = false;
let pendingAnnotatedGeneratedLine = 0;
let pendingAnnotatedGeneratedCharacter = 0;
let pendingAnnotations: SourceMapAnnotation[] | undefined;
let hasPendingAnnotation = false;
return {
getSources: () => rawSources,
addSource,
setSourceContent,
addName,
addMapping,
addAnnotation,
appendSourceMap,
toJSON,
toString: () => JSON.stringify(toJSON())
@ -142,6 +157,186 @@ namespace ts {
exit();
}
function canEncodeAnnotation(annotation: unknown) {
switch (typeof annotation) {
case "number": return isFinite(annotation);
case "string": return true;
case "boolean": return true;
case "object": return true;
default: return false;
}
}
function encodeSingleAnnotation(annotation: unknown) {
let lastNameIndex = 0;
return encodeSingleAnnotationWorker(annotation);
function encodeSingleAnnotationWorker(annotation: unknown) {
switch (typeof annotation) {
case "number":
appendSingleAnnotationCharCode(CharacterCodes.hash);
appendBase64VLQ(appendSingleAnnotationCharCode, annotation);
break;
case "string":
appendSingleAnnotationCharCode(CharacterCodes.at);
const nameIndex = addName(annotation);
appendBase64VLQ(appendSingleAnnotationCharCode, nameIndex - lastNameIndex);
lastNameIndex = nameIndex;
break;
case "boolean":
appendSingleAnnotationCharCode(annotation ? CharacterCodes.t : CharacterCodes.f);
break;
case "object":
if (!annotation) {
appendSingleAnnotationCharCode(CharacterCodes.exclamation);
break;
}
if (isArray(annotation)) {
appendSingleAnnotationCharCode(CharacterCodes.openBracket);
for (let i = 0; i < annotation.length; i++) {
if (i > 0) {
appendSingleAnnotationCharCode(CharacterCodes.comma);
}
if (canEncodeAnnotation(annotation[i])) {
encodeSingleAnnotationWorker(annotation[i]);
}
}
appendSingleAnnotationCharCode(CharacterCodes.closeBracket);
break;
}
appendSingleAnnotationCharCode(CharacterCodes.openBrace);
let hasWrittenProperty = false;
for (const key in annotation) {
const value = (annotation as any)[key];
if (canEncodeAnnotation(key) && canEncodeAnnotation(value)) {
if (hasWrittenProperty) {
appendSingleAnnotationCharCode(CharacterCodes.comma);
}
encodeSingleAnnotationWorker(key);
appendSingleAnnotationCharCode(CharacterCodes.colon);
encodeSingleAnnotationWorker(value);
hasWrittenProperty = true;
}
}
appendSingleAnnotationCharCode(CharacterCodes.closeBrace);
break;
}
}
}
function isNewAnnotatedGeneratedPosition(generatedLine: number, generatedCharacter: number) {
return !hasPendingAnnotation
|| pendingAnnotatedGeneratedLine !== generatedLine
|| pendingAnnotatedGeneratedCharacter !== generatedCharacter;
}
function shouldCommitPendingAnnotation() {
return !hasLastAnnotation
|| lastAnnotatedGeneratedLine !== pendingAnnotatedGeneratedLine
|| lastAnnotatedGeneratedCharacter !== pendingAnnotatedGeneratedCharacter
|| lastAnnotations !== pendingAnnotations;
}
function flushSingleAnnotationBuffer(): void {
if (singleAnnotationCharCodes.length > 0) {
singleAnnotation += String.fromCharCode.apply(undefined, singleAnnotationCharCodes);
singleAnnotationCharCodes.length = 0;
}
}
function appendSingleAnnotationCharCode(charCode: number) {
singleAnnotationCharCodes.push(charCode);
// String.fromCharCode accepts its arguments on the stack, so we have to chunk the input,
// otherwise we can get stack overflows for large source maps
if (singleAnnotationCharCodes.length >= 1024) {
flushSingleAnnotationBuffer();
}
}
function flushAnnotationsBuffer(): void {
if (annotationsCharCodes.length > 0) {
annotations += String.fromCharCode.apply(undefined, annotationsCharCodes);
annotationsCharCodes.length = 0;
}
}
function appendAnnotationsCharCode(charCode: number) {
annotationsCharCodes.push(charCode);
// String.fromCharCode accepts its arguments on the stack, so we have to chunk the input,
// otherwise we can get stack overflows for large source maps
if (annotationsCharCodes.length >= 1024) {
flushAnnotationsBuffer();
}
}
function commitPendingAnnotations() {
if (!hasPendingAnnotation || !shouldCommitPendingAnnotation()) {
return;
}
enter();
// Line/Comma delimiters
if (lastAnnotatedGeneratedLine < pendingAnnotatedGeneratedLine) {
// Emit line delimiters
do {
appendAnnotationsCharCode(CharacterCodes.semicolon);
lastAnnotatedGeneratedLine++;
}
while (lastAnnotatedGeneratedLine < pendingAnnotatedGeneratedLine);
// Only need to set this once
lastAnnotatedGeneratedCharacter = 0;
}
else {
Debug.assertEqual(lastAnnotatedGeneratedLine, pendingAnnotatedGeneratedLine, "generatedLine cannot backtrack");
// Emit comma to separate the entry
if (hasLast) {
appendAnnotationsCharCode(CharacterCodes.comma);
}
}
// 1. Relative generated character
appendBase64VLQ(appendAnnotationsCharCode, pendingAnnotatedGeneratedCharacter - lastAnnotatedGeneratedCharacter);
lastAnnotatedGeneratedCharacter = pendingAnnotatedGeneratedCharacter;
// 2. Annotations
if (canEncodeAnnotation(pendingAnnotations)) {
encodeSingleAnnotation(pendingAnnotations);
flushSingleAnnotationBuffer();
const annotationsNameIndex = addName(singleAnnotation);
singleAnnotation = "";
appendBase64VLQ(appendAnnotationsCharCode, annotationsNameIndex - lastAnnotationsNameIndex);
lastAnnotationsNameIndex = annotationsNameIndex;
}
lastAnnotations = pendingAnnotations;
pendingAnnotations = undefined;
hasLastAnnotation = false;
exit();
}
function addAnnotation(generatedLine: number, generatedCharacter: number, annotationName: string, annotationValue: unknown) {
Debug.assert(generatedLine >= pendingAnnotatedGeneratedLine, "generatedLine cannot backtrack");
Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative");
enter();
const annotation: SourceMapAnnotation = { name: annotationName, value: annotationValue };
if (isNewAnnotatedGeneratedPosition(generatedLine, generatedCharacter)) {
commitPendingAnnotations();
pendingAnnotatedGeneratedLine = generatedLine;
pendingAnnotatedGeneratedCharacter = generatedCharacter;
pendingAnnotations = [annotation];
hasPendingAnnotation = true;
}
else {
pendingAnnotations ??= [];
pendingAnnotations.push(annotation);
}
exit();
}
function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) {
Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack");
Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative");
@ -247,25 +442,25 @@ namespace ts {
}
// 1. Relative generated character
appendBase64VLQ(pendingGeneratedCharacter - lastGeneratedCharacter);
appendBase64VLQ(appendMappingCharCode, pendingGeneratedCharacter - lastGeneratedCharacter);
lastGeneratedCharacter = pendingGeneratedCharacter;
if (hasPendingSource) {
// 2. Relative sourceIndex
appendBase64VLQ(pendingSourceIndex - lastSourceIndex);
appendBase64VLQ(appendMappingCharCode, pendingSourceIndex - lastSourceIndex);
lastSourceIndex = pendingSourceIndex;
// 3. Relative source line
appendBase64VLQ(pendingSourceLine - lastSourceLine);
appendBase64VLQ(appendMappingCharCode, pendingSourceLine - lastSourceLine);
lastSourceLine = pendingSourceLine;
// 4. Relative source character
appendBase64VLQ(pendingSourceCharacter - lastSourceCharacter);
appendBase64VLQ(appendMappingCharCode, pendingSourceCharacter - lastSourceCharacter);
lastSourceCharacter = pendingSourceCharacter;
if (hasPendingName) {
// 5. Relative nameIndex
appendBase64VLQ(pendingNameIndex - lastNameIndex);
appendBase64VLQ(appendMappingCharCode, pendingNameIndex - lastNameIndex);
lastNameIndex = pendingNameIndex;
}
}
@ -284,7 +479,7 @@ namespace ts {
function toJSON(): RawSourceMap {
commitPendingMapping();
flushMappingBuffer();
return {
const sourceMap: RawSourceMap = {
version: 3,
file,
sourceRoot,
@ -293,9 +488,15 @@ namespace ts {
mappings,
sourcesContent,
};
if (hasPendingAnnotation || hasLastAnnotation) {
commitPendingAnnotations();
flushAnnotationsBuffer();
sourceMap.x_ms_ts_annotations = annotations;
}
return sourceMap;
}
function appendBase64VLQ(inValue: number): void {
function appendBase64VLQ(appendCharCode: (charCode: number) => void, inValue: number): void {
// Add a new least significant bit that has the sign of the value.
// if negative number the least significant bit that gets added to the number has value 1
// else least significant bit value that gets added is 0
@ -316,7 +517,7 @@ namespace ts {
// There are still more digits to decode, set the msb (6th bit)
currentDigit = currentDigit | 32;
}
appendMappingCharCode(base64FormatEncode(currentDigit));
appendCharCode(base64FormatEncode(currentDigit));
} while (inValue > 0);
}
}
@ -408,9 +609,37 @@ namespace ts {
sourceCharacter: number;
}
export interface CharCodeReader {
readonly length: number;
pos: number;
peek(): number;
read(): number;
}
export function createCharCodeReader(text: string): CharCodeReader {
const length = text.length;
return {
length,
pos: 0,
peek() {
const pos = this.pos;
return pos < length ? text.charCodeAt(pos) : -1;
},
read() {
const pos = this.pos;
if (pos < length) {
const ch = text.charCodeAt(pos);
this.pos++;
return ch;
}
return -1;
}
};
}
export function decodeMappings(mappings: string): MappingsDecoder {
const reader = createCharCodeReader(mappings);
let done = false;
let pos = 0;
let generatedLine = 0;
let generatedCharacter = 0;
let sourceIndex = 0;
@ -420,53 +649,53 @@ namespace ts {
let error: string | undefined;
return {
get pos() { return pos; },
get pos() { return reader.pos; },
get error() { return error; },
get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); },
next() {
while (!done && pos < mappings.length) {
const ch = mappings.charCodeAt(pos);
while (!done && reader.pos < reader.length) {
const ch = reader.peek();
if (ch === CharacterCodes.semicolon) {
// new line
generatedLine++;
generatedCharacter = 0;
pos++;
reader.read();
continue;
}
if (ch === CharacterCodes.comma) {
// Next entry is on same line - no action needed
pos++;
reader.read();
continue;
}
let hasSource = false;
let hasName = false;
generatedCharacter += base64VLQFormatDecode();
generatedCharacter += base64VLQFormatDecode(reader, setError);
if (hasReportedError()) return stopIterating();
if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found");
if (!isSourceMappingSegmentEnd()) {
hasSource = true;
sourceIndex += base64VLQFormatDecode();
sourceIndex += base64VLQFormatDecode(reader, setError);
if (hasReportedError()) return stopIterating();
if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found");
if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex");
sourceLine += base64VLQFormatDecode();
sourceLine += base64VLQFormatDecode(reader, setError);
if (hasReportedError()) return stopIterating();
if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found");
if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine");
sourceCharacter += base64VLQFormatDecode();
sourceCharacter += base64VLQFormatDecode(reader, setError);
if (hasReportedError()) return stopIterating();
if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found");
if (!isSourceMappingSegmentEnd()) {
hasName = true;
nameIndex += base64VLQFormatDecode();
nameIndex += base64VLQFormatDecode(reader, setError);
if (hasReportedError()) return stopIterating();
if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found");
@ -515,44 +744,45 @@ namespace ts {
}
function isSourceMappingSegmentEnd() {
return (pos === mappings.length ||
mappings.charCodeAt(pos) === CharacterCodes.comma ||
mappings.charCodeAt(pos) === CharacterCodes.semicolon);
return (reader.pos === reader.length ||
reader.peek() === CharacterCodes.comma ||
reader.peek() === CharacterCodes.semicolon);
}
function base64VLQFormatDecode(): number {
let moreDigits = true;
let shiftCount = 0;
let value = 0;
}
for (; moreDigits; pos++) {
if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1;
export function base64VLQFormatDecode(reader: CharCodeReader, reportError: (error: string) => void): number {
let moreDigits = true;
let shiftCount = 0;
let value = 0;
// 6 digit number
const currentByte = base64FormatDecode(mappings.charCodeAt(pos));
if (currentByte === -1) return setError("Invalid character in VLQ"), -1;
for (; moreDigits; reader.read()) {
if (reader.pos >= reader.length) return reportError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1;
// If msb is set, we still have more bits to continue
moreDigits = (currentByte & 32) !== 0;
// 6 digit number
const currentByte = base64FormatDecode(reader.peek());
if (currentByte === -1) return reportError("Invalid character in VLQ"), -1;
// least significant 5 bits are the next msbs in the final value.
value = value | ((currentByte & 31) << shiftCount);
shiftCount += 5;
}
// If msb is set, we still have more bits to continue
moreDigits = (currentByte & 32) !== 0;
// Least significant bit if 1 represents negative and rest of the msb is actual absolute value
if ((value & 1) === 0) {
// + number
value = value >> 1;
}
else {
// - number
value = value >> 1;
value = -value;
}
return value;
// least significant 5 bits are the next msbs in the final value.
value = value | ((currentByte & 31) << shiftCount);
shiftCount += 5;
}
// Least significant bit if 1 represents negative and rest of the msb is actual absolute value
if ((value & 1) === 0) {
// + number
value = value >> 1;
}
else {
// - number
value = value >> 1;
value = -value;
}
return value;
}
export function sameMapping<T extends Mapping>(left: T, right: T) {

View File

@ -0,0 +1,146 @@
namespace ts {
export interface DecodedSourceMapAnnotation {
generatedLine: number;
generatedCharacter: number;
annotations: SourceMapAnnotation[];
}
/**
* Decodes the custom `x_ms_ts_annotations` field of a SourceMap.
*/
export function decodeSourceMapAnnotations(annotations: string, names: readonly string[]): Iterator<DecodedSourceMapAnnotation> {
const reader = createCharCodeReader(annotations);
let done = false;
let generatedLine = 0;
let generatedCharacter = 0;
let nameIndex = 0;
return {
next() {
debugger;
while (!done && reader.pos < reader.length) {
const ch = reader.peek();
if (ch === CharacterCodes.semicolon) {
generatedLine++;
generatedCharacter = 0;
reader.read();
continue;
}
if (ch === CharacterCodes.comma) {
reader.read();
continue;
}
generatedCharacter += base64VLQFormatDecode(reader, noop);
if (generatedCharacter < 0) break;
if (isSegmentEnd()) continue;
nameIndex += base64VLQFormatDecode(reader, noop);
const name = elementAt(names, nameIndex);
if (name) {
const annotations = decodeAnnotations(createCharCodeReader(name));
if (annotations) {
return { value: { generatedLine, generatedCharacter, annotations }, done };
}
}
}
done = true;
return { value: undefined!, done: true } as { value: never, done: true };
}
};
function isSegmentEnd() {
return (reader.pos === reader.length ||
reader.peek() === CharacterCodes.comma ||
reader.peek() === CharacterCodes.semicolon);
}
function decodeAnnotations(reader: CharCodeReader): SourceMapAnnotation[] | undefined {
let nameIndex = 0;
const annotations = decodeAnnotationWorker();
if (Array.isArray(annotations) && annotations.every(isSourceMapAnnotation)) {
return annotations;
}
function decodeAnnotationWorker(): unknown {
if (reader.pos < reader.length) {
let ch = reader.peek();
if (ch === CharacterCodes.hash) {
reader.read();
return base64VLQFormatDecode(reader, noop);
}
else if (ch === CharacterCodes.at) {
reader.read();
nameIndex += base64VLQFormatDecode(reader, noop);
return elementAt(names, nameIndex);
}
else if (ch === CharacterCodes.t || ch === CharacterCodes.f) {
reader.read();
return ch === CharacterCodes.t;
}
else if (ch === CharacterCodes.exclamation) {
reader.read();
return null; // eslint-disable-line no-null/no-null
}
else if (ch === CharacterCodes.openBracket) {
const array: unknown[] = [];
reader.read();
while (reader.pos < reader.length) {
ch = reader.peek();
if (ch === CharacterCodes.closeBracket) {
reader.read();
return array;
}
if (array.length > 0) {
if (ch !== CharacterCodes.comma) {
return;
}
reader.read();
}
array.push(decodeAnnotationWorker());
}
}
else if (ch === CharacterCodes.openBrace) {
const obj: any = {};
let hasReadProperty = false;
reader.read();
while (reader.pos < reader.length) {
ch = reader.peek();
if (ch === CharacterCodes.closeBrace) {
reader.read();
return obj;
}
if (hasReadProperty) {
if (ch !== CharacterCodes.comma) {
return;
}
reader.read();
if (reader.pos >= reader.length) return;
}
const key = decodeAnnotationWorker();
if (typeof key !== "string") return;
if (reader.pos >= reader.length) return;
ch = reader.peek();
if (ch !== CharacterCodes.colon) return;
reader.read();
if (reader.pos >= reader.length) return;
const value = decodeAnnotationWorker();
obj[key] = value;
hasReadProperty = true;
}
}
}
}
}
}
function isSourceMapAnnotation(value: unknown): value is SourceMapAnnotation {
return typeof value === "object" && !!value && typeof (value as SourceMapAnnotation).name === "string";
}
}

View File

@ -147,12 +147,13 @@ namespace ts {
*
* @param resolver The emit resolver provided by the checker.
* @param host The emit host object used to interact with the file system.
* @param factoryIn The `NodeFactory` to use to create new nodes.
* @param options Compiler options to surface in the `TransformationContext`.
* @param nodes An array of nodes to transform.
* @param transforms An array of `TransformerFactory` callbacks.
* @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files.
*/
export function transformNodes<T extends Node>(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory<T>[], allowDtsFiles: boolean): TransformationResult<T> {
export function transformNodes<T extends Node>(resolver: EmitResolver | undefined, host: EmitHost | undefined, factoryIn: NodeFactory | undefined, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory<T>[], allowDtsFiles: boolean): TransformationResult<T> {
const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count);
let lexicalEnvironmentVariableDeclarations: VariableDeclaration[];
let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[];
@ -171,12 +172,13 @@ namespace ts {
let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution;
let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification;
let state = TransformationState.Uninitialized;
const shouldAttachTransformer = options.annotateTransforms || Debug.isDebugging;
const diagnostics: DiagnosticWithLocation[] = [];
// The transformation context is provided to each transformer as part of transformer
// initialization.
const context: TransformationContext = {
factory,
factory: factoryIn ?? factory,
getCompilerOptions: () => options,
getEmitResolver: () => resolver!, // TODO: GH#18217
getEmitHost: () => host!, // TODO: GH#18217
@ -224,7 +226,7 @@ namespace ts {
performance.mark("beforeTransform");
// Chain together and initialize each transformer.
const transformersWithContext = transformers.map(t => t(context));
const transformersWithContext = transformers.map(createTransformerWithContext);
const transformation = (node: T): T => {
for (const transform of transformersWithContext) {
node = transform(node);
@ -258,6 +260,41 @@ namespace ts {
diagnostics
};
function createTransformerWithContext(t: TransformerFactory<T>) {
if (factoryIn || !shouldAttachTransformer) {
return t(context);
}
const baseFactory = factory.baseFactory;
const perTransformerBaseFactory: BaseNodeFactory = {
createBaseSourceFileNode: kind => attachTransformer(baseFactory.createBaseSourceFileNode(kind), t),
createBaseIdentifierNode: kind => attachTransformer(baseFactory.createBaseIdentifierNode(kind), t),
createBasePrivateIdentifierNode: kind => attachTransformer(baseFactory.createBasePrivateIdentifierNode(kind), t),
createBaseTokenNode: kind => attachTransformer(baseFactory.createBaseTokenNode(kind), t),
createBaseNode: kind => attachTransformer(baseFactory.createBaseNode(kind), t),
};
const perTransformerFactory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, perTransformerBaseFactory);
const perTransformerContext: TransformationContext = Object.create(context, {
factory: {
configurable: true,
writable: true,
value: perTransformerFactory
},
getEmitHelperFactory: {
configurable: true,
writable: true,
value: memoize(() => createEmitHelperFactory(perTransformerContext))
}
});
return t(perTransformerContext);
}
function attachTransformer<TNode extends Node>(node: TNode, transformer: TransformerFactory<T>) {
node.transformer = transformer;
return node;
}
function transformRoot(node: T) {
return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node;
}

View File

@ -43,6 +43,7 @@
"checker.ts",
"visitorPublic.ts",
"sourcemap.ts",
"sourcemapPublic.ts",
"transformers/utilities.ts",
"transformers/destructuring.ts",
"transformers/taggedTemplate.ts",

View File

@ -885,6 +885,7 @@ namespace ts {
/* @internal */ emitNode?: EmitNode; // Associated EmitNode (initialized by transforms)
/* @internal */ contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution
/* @internal */ inferenceContext?: InferenceContext; // Inference context for contextual type
/* @internal */ transformer?: TransformerFactory<Node>; // Source transformer that created the node
}
export interface JSDocContainer {
@ -6669,6 +6670,7 @@ namespace ts {
esModuleInterop?: boolean;
/* @internal */ showConfig?: boolean;
useDefineForClassFields?: boolean;
annotateTransforms?: boolean;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
@ -8786,6 +8788,7 @@ namespace ts {
/*@internal*/ stripInternal?: boolean;
/*@internal*/ preserveSourceNewlines?: boolean;
/*@internal*/ terminateUnterminatedLiterals?: boolean;
/*@internal*/ annotateTransforms?: boolean;
/*@internal*/ relativeToBuildInfo?: (path: string) => string;
}
@ -8798,6 +8801,8 @@ namespace ts {
sourcesContent?: (string | null)[] | null;
mappings: string;
names?: string[] | null;
[key: `x_${string}`]: unknown;
x_ms_ts_annotations?: string;
}
/**
@ -8830,6 +8835,10 @@ namespace ts {
* Appends a source map.
*/
appendSourceMap(generatedLine: number, generatedCharacter: number, sourceMap: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter): void;
/**
* Adds a JSON annotation at the specified line and character.
*/
addAnnotation(generatedLine: number, generatedCharacter: number, annotationName: string, annotationValue: unknown): void;
/**
* Gets the source map as a `RawSourceMap` object.
*/
@ -8840,6 +8849,11 @@ namespace ts {
toString(): string;
}
export interface SourceMapAnnotation {
name: string;
value: unknown;
}
/* @internal */
export interface DocumentPositionMapperHost {
getSourceFileLike(fileName: string): SourceFileLike | undefined;

View File

@ -3088,6 +3088,7 @@ declare namespace ts {
typeRoots?: string[];
esModuleInterop?: boolean;
useDefineForClassFields?: boolean;
annotateTransforms?: boolean;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
export interface WatchOptions {
@ -4052,6 +4053,10 @@ declare namespace ts {
omitTrailingSemicolon?: boolean;
noEmitHelpers?: boolean;
}
export interface SourceMapAnnotation {
name: string;
value: unknown;
}
export interface GetEffectiveTypeRootsHost {
directoryExists?(directoryName: string): boolean;
getCurrentDirectory?(): string;
@ -5141,6 +5146,17 @@ declare namespace ts {
*/
function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined;
}
declare namespace ts {
interface DecodedSourceMapAnnotation {
generatedLine: number;
generatedCharacter: number;
annotations: SourceMapAnnotation[];
}
/**
* Decodes the custom `x_ms_ts_annotations` field of a SourceMap.
*/
function decodeSourceMapAnnotations(annotations: string, names: readonly string[]): Iterator<DecodedSourceMapAnnotation>;
}
declare namespace ts {
function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions): string | undefined;
function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[];

View File

@ -3088,6 +3088,7 @@ declare namespace ts {
typeRoots?: string[];
esModuleInterop?: boolean;
useDefineForClassFields?: boolean;
annotateTransforms?: boolean;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
export interface WatchOptions {
@ -4052,6 +4053,10 @@ declare namespace ts {
omitTrailingSemicolon?: boolean;
noEmitHelpers?: boolean;
}
export interface SourceMapAnnotation {
name: string;
value: unknown;
}
export interface GetEffectiveTypeRootsHost {
directoryExists?(directoryName: string): boolean;
getCurrentDirectory?(): string;
@ -5141,6 +5146,17 @@ declare namespace ts {
*/
function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined;
}
declare namespace ts {
interface DecodedSourceMapAnnotation {
generatedLine: number;
generatedCharacter: number;
annotations: SourceMapAnnotation[];
}
/**
* Decodes the custom `x_ms_ts_annotations` field of a SourceMap.
*/
function decodeSourceMapAnnotations(annotations: string, names: readonly string[]): Iterator<DecodedSourceMapAnnotation>;
}
declare namespace ts {
function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions): string | undefined;
function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[];

View File

@ -0,0 +1,5 @@
{
"compilerOptions": {
"annotateTransforms": true
}
}