Skip to content

Commit 6c59ee4

Browse files
committed
Add assertions to verify user-exposed behavior.
1 parent 2f624f5 commit 6c59ee4

File tree

2 files changed

+89
-40
lines changed

2 files changed

+89
-40
lines changed

src/compiler/transformer.ts

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ namespace ts {
2424
}
2525
}
2626

27+
const enum TransformationState {
28+
Uninitialized,
29+
Initialized,
30+
Completed,
31+
Disposed
32+
}
33+
2734
const enum SyntaxKindFeatureFlags {
2835
Substitution = 1 << 0,
2936
EmitNotifications = 1 << 1,
@@ -83,14 +90,16 @@ namespace ts {
8390
*/
8491
export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult {
8592
const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count);
86-
let lexicalEnvironmentDisabled = false;
8793
let lexicalEnvironmentVariableDeclarations: VariableDeclaration[];
8894
let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[];
8995
let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = [];
9096
let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = [];
9197
let lexicalEnvironmentStackOffset = 0;
9298
let lexicalEnvironmentSuspended = false;
9399
let emitHelpers: EmitHelper[];
100+
let onSubstituteNode: TransformationContext["onSubstituteNode"] = (_, node) => node;
101+
let onEmitNode: TransformationContext["onEmitNode"] = (hint, node, callback) => callback(hint, node);
102+
let state = TransformationState.Uninitialized;
94103

95104
// The transformation context is provided to each transformer as part of transformer
96105
// initialization.
@@ -106,27 +115,40 @@ namespace ts {
106115
hoistFunctionDeclaration,
107116
requestEmitHelper,
108117
readEmitHelpers,
109-
onSubstituteNode: (_, node) => node,
110118
enableSubstitution,
111-
isSubstitutionEnabled,
112-
onEmitNode: (hint, node, callback) => callback(hint, node),
113119
enableEmitNotification,
114-
isEmitNotificationEnabled
120+
isSubstitutionEnabled,
121+
isEmitNotificationEnabled,
122+
get onSubstituteNode() { return onSubstituteNode },
123+
set onSubstituteNode(value) {
124+
Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed.");
125+
Debug.assert(value !== undefined, "Value must not be 'undefined'");
126+
onSubstituteNode = value;
127+
},
128+
get onEmitNode() { return onEmitNode },
129+
set onEmitNode(value) {
130+
Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed.");
131+
Debug.assert(value !== undefined, "Value must not be 'undefined'");
132+
onEmitNode = value;
133+
}
115134
};
116135

117136
// Ensure the parse tree is clean before applying transformations
118-
dispose();
137+
forEach(sourceFiles, disposeEmitNodes);
119138

120139
performance.mark("beforeTransform");
121140

122141
// Chain together and initialize each transformer.
123142
const transformation = chain(...transformers)(context);
124143

144+
// prevent modification of transformation hooks.
145+
state = TransformationState.Initialized;
146+
125147
// Transform each source file.
126148
const transformed = map(sourceFiles, transformSourceFile);
127149

128-
// Disable modification of the lexical environment.
129-
lexicalEnvironmentDisabled = true;
150+
// prevent modification of the lexical environment.
151+
state = TransformationState.Completed;
130152

131153
performance.mark("afterTransform");
132154
performance.measure("transformTime", "beforeTransform", "afterTransform");
@@ -155,6 +177,7 @@ namespace ts {
155177
* Enables expression substitutions in the pretty printer for the provided SyntaxKind.
156178
*/
157179
function enableSubstitution(kind: SyntaxKind) {
180+
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
158181
enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution;
159182
}
160183

@@ -174,9 +197,10 @@ namespace ts {
174197
* @param emitCallback The callback used to emit the node or its substitute.
175198
*/
176199
function emitNodeWithSubstitution(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
200+
Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed.");
177201
if (node) {
178202
if (isSubstitutionEnabled(node)) {
179-
node = context.onSubstituteNode(hint, node) || node;
203+
node = onSubstituteNode(hint, node) || node;
180204
}
181205
emitCallback(hint, node);
182206
}
@@ -186,6 +210,7 @@ namespace ts {
186210
* Enables before/after emit notifications in the pretty printer for the provided SyntaxKind.
187211
*/
188212
function enableEmitNotification(kind: SyntaxKind) {
213+
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
189214
enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications;
190215
}
191216

@@ -206,9 +231,10 @@ namespace ts {
206231
* @param emitCallback The callback used to emit the node.
207232
*/
208233
function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
234+
Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed.");
209235
if (node) {
210236
if (isEmitNotificationEnabled(node)) {
211-
context.onEmitNode(hint, node, emitCallback);
237+
onEmitNode(hint, node, emitCallback);
212238
}
213239
else {
214240
emitCallback(hint, node);
@@ -220,7 +246,8 @@ namespace ts {
220246
* Records a hoisted variable declaration for the provided name within a lexical environment.
221247
*/
222248
function hoistVariableDeclaration(name: Identifier): void {
223-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase.");
249+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
250+
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
224251
const decl = createVariableDeclaration(name);
225252
if (!lexicalEnvironmentVariableDeclarations) {
226253
lexicalEnvironmentVariableDeclarations = [decl];
@@ -234,7 +261,8 @@ namespace ts {
234261
* Records a hoisted function declaration within a lexical environment.
235262
*/
236263
function hoistFunctionDeclaration(func: FunctionDeclaration): void {
237-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase.");
264+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
265+
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
238266
if (!lexicalEnvironmentFunctionDeclarations) {
239267
lexicalEnvironmentFunctionDeclarations = [func];
240268
}
@@ -248,7 +276,8 @@ namespace ts {
248276
* are pushed onto a stack, and the related storage variables are reset.
249277
*/
250278
function startLexicalEnvironment(): void {
251-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot start a lexical environment during the print phase.");
279+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
280+
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
252281
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
253282

254283
// Save the current lexical environment. Rather than resizing the array we adjust the
@@ -264,14 +293,16 @@ namespace ts {
264293

265294
/** Suspends the current lexical environment, usually after visiting a parameter list. */
266295
function suspendLexicalEnvironment(): void {
267-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot suspend a lexical environment during the print phase.");
296+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
297+
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
268298
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended.");
269299
lexicalEnvironmentSuspended = true;
270300
}
271301

272302
/** Resumes a suspended lexical environment, usually before visiting a function body. */
273303
function resumeLexicalEnvironment(): void {
274-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot resume a lexical environment during the print phase.");
304+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
305+
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
275306
Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended.");
276307
lexicalEnvironmentSuspended = false;
277308
}
@@ -281,7 +312,8 @@ namespace ts {
281312
* any hoisted declarations added in this environment are returned.
282313
*/
283314
function endLexicalEnvironment(): Statement[] {
284-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot end a lexical environment during the print phase.");
315+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
316+
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
285317
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
286318

287319
let statements: Statement[];
@@ -317,22 +349,36 @@ namespace ts {
317349
}
318350

319351
function requestEmitHelper(helper: EmitHelper): void {
320-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase.");
352+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
353+
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
321354
Debug.assert(!helper.scoped, "Cannot request a scoped emit helper.");
322355
emitHelpers = append(emitHelpers, helper);
323356
}
324357

325358
function readEmitHelpers(): EmitHelper[] | undefined {
326-
Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase.");
359+
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
360+
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
327361
const helpers = emitHelpers;
328362
emitHelpers = undefined;
329363
return helpers;
330364
}
331365

332366
function dispose() {
333-
// Clean up emit nodes on parse tree
334-
for (const sourceFile of sourceFiles) {
335-
disposeEmitNodes(sourceFile);
367+
if (state < TransformationState.Disposed) {
368+
// Clean up emit nodes on parse tree
369+
forEach(sourceFiles, disposeEmitNodes);
370+
371+
// Release references to external entries for GC purposes.
372+
lexicalEnvironmentVariableDeclarations = undefined;
373+
lexicalEnvironmentVariableDeclarationsStack = undefined;
374+
lexicalEnvironmentFunctionDeclarations = undefined;
375+
lexicalEnvironmentFunctionDeclarationsStack = undefined;
376+
onSubstituteNode = undefined;
377+
onEmitNode = undefined;
378+
emitHelpers = undefined;
379+
380+
// Prevent further use of the transformation result.
381+
state = TransformationState.Disposed;
336382
}
337383
}
338384
}

src/compiler/types.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,39 +3842,30 @@
38423842
/** Ends a lexical environment, returning any declarations. */
38433843
endLexicalEnvironment(): Statement[];
38443844

3845-
/**
3846-
* Hoists a function declaration to the containing scope.
3847-
*/
3845+
/** Hoists a function declaration to the containing scope. */
38483846
hoistFunctionDeclaration(node: FunctionDeclaration): void;
38493847

3850-
/**
3851-
* Hoists a variable declaration to the containing scope.
3852-
*/
3848+
/** Hoists a variable declaration to the containing scope. */
38533849
hoistVariableDeclaration(node: Identifier): void;
38543850

3855-
/**
3856-
* Records a request for a non-scoped emit helper in the current context.
3857-
*/
3851+
/** Records a request for a non-scoped emit helper in the current context. */
38583852
requestEmitHelper(helper: EmitHelper): void;
38593853

3860-
/**
3861-
* Gets and resets the requested non-scoped emit helpers.
3862-
*/
3854+
/** Gets and resets the requested non-scoped emit helpers. */
38633855
readEmitHelpers(): EmitHelper[] | undefined;
38643856

3865-
/**
3866-
* Enables expression substitutions in the pretty printer for the provided SyntaxKind.
3867-
*/
3857+
/** Enables expression substitutions in the pretty printer for the provided SyntaxKind. */
38683858
enableSubstitution(kind: SyntaxKind): void;
38693859

3870-
/**
3871-
* Determines whether expression substitutions are enabled for the provided node.
3872-
*/
3860+
/** Determines whether expression substitutions are enabled for the provided node. */
38733861
isSubstitutionEnabled(node: Node): boolean;
38743862

38753863
/**
38763864
* Hook used by transformers to substitute expressions just before they
38773865
* are emitted by the pretty printer.
3866+
*
3867+
* NOTE: Transformation hooks should only be modified during `Transformer` initialization,
3868+
* before returning the `FileTransformer` callback.
38783869
*/
38793870
onSubstituteNode?: (hint: EmitHint, node: Node) => Node;
38803871

@@ -3893,6 +3884,9 @@
38933884
/**
38943885
* Hook used to allow transformers to capture state before or after
38953886
* the printer emits a node.
3887+
*
3888+
* NOTE: Transformation hooks should only be modified during `Transformer` initialization,
3889+
* before returning the `FileTransformer` callback.
38963890
*/
38973891
onEmitNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void;
38983892
}
@@ -3927,7 +3921,16 @@
39273921
dispose(): void;
39283922
}
39293923

3930-
export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile;
3924+
/**
3925+
* A function that is used to initialize and return a `FileTransformer` callback, which in turn
3926+
* will be used to transform one or more `SourceFile` objects.
3927+
*/
3928+
export type Transformer = (context: TransformationContext) => FileTransformer;
3929+
3930+
/**
3931+
* A function that transforms a `SourceFile` object.
3932+
*/
3933+
export type FileTransformer = (node: SourceFile) => SourceFile;
39313934

39323935
export interface Printer {
39333936
/**

0 commit comments

Comments
 (0)