Skip to content

Commit be73e33

Browse files
Merge pull request #124 from bloomberg/isolated-declarations-safety-improvements
Improve declaration emit type safety.
2 parents f49ebd9 + 202d522 commit be73e33

File tree

12 files changed

+185
-261
lines changed

12 files changed

+185
-261
lines changed

src/compiler/_namespaces/ts.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ export * from "../transformers/declarations/emitBinder";
6161
export * from "../transformers/declarations/emitResolver";
6262
export * from "../transformers/declarations/transpileDeclaration";
6363
export * from "../transformers/declarations/localInferenceResolver";
64-
export * from "../transformers/declarations/types";
6564
export * from "../transformers/declarations";
6665
export * from "../transformer";
6766
export * from "../emitter";

src/compiler/transformer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
NodeFlags,
4141
noop,
4242
notImplemented,
43+
NullTransformationContext,
4344
returnUndefined,
4445
ScriptTarget,
4546
setEmitFlags,
@@ -49,6 +50,7 @@ import {
4950
SyntaxKind,
5051
tracing,
5152
TransformationContext,
53+
TransformationContextKind,
5254
TransformationResult,
5355
transformClassFields,
5456
transformDeclarations,
@@ -267,6 +269,7 @@ export function transformNodes<T extends Node>(resolver: EmitResolver | undefine
267269
// The transformation context is provided to each transformer as part of transformer
268270
// initialization.
269271
const context: TransformationContext = {
272+
kind: TransformationContextKind.FullContext,
270273
factory,
271274
getCompilerOptions: () => options,
272275
getEmitResolver: () => resolver!, // TODO: GH#18217
@@ -664,7 +667,8 @@ export function transformNodes<T extends Node>(resolver: EmitResolver | undefine
664667
}
665668

666669
/** @internal */
667-
export const nullTransformationContext: TransformationContext = {
670+
export const nullTransformationContext: NullTransformationContext = {
671+
kind: TransformationContextKind.NullContext,
668672
factory: factory, // eslint-disable-line object-shorthand
669673
getCompilerOptions: () => ({}),
670674
getEmitResolver: notImplemented,

src/compiler/transformers/declarations.ts

Lines changed: 46 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ConstructorTypeNode,
2424
ConstructSignatureDeclaration,
2525
contains,
26+
CoreEmitResolver,
2627
createDiagnosticForNode,
2728
createDiagnosticForRange,
2829
createEmptyExports,
@@ -46,7 +47,6 @@ import {
4647
EnumDeclaration,
4748
ExportAssignment,
4849
ExportDeclaration,
49-
Expression,
5050
ExpressionWithTypeArguments,
5151
factory,
5252
FileReference,
@@ -139,7 +139,7 @@ import {
139139
isMethodSignature,
140140
isModifier,
141141
isModuleDeclaration,
142-
IsolatedEmitResolver,
142+
IsolatedTransformationContext,
143143
isOmittedExpression,
144144
isPrivateIdentifier,
145145
isPropertySignature,
@@ -167,7 +167,6 @@ import {
167167
LateBoundDeclaration,
168168
LateVisibilityPaintedStatement,
169169
length,
170-
LocalInferenceResolver,
171170
map,
172171
mapDefined,
173172
MethodDeclaration,
@@ -221,6 +220,7 @@ import {
221220
SyntaxKind,
222221
toFileNameLowerCase,
223222
TransformationContext,
223+
TransformationContextKind,
224224
transformNodes,
225225
tryCast,
226226
tryGetModuleSpecifierFromDeclaration,
@@ -293,7 +293,7 @@ const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals
293293
*
294294
* @internal
295295
*/
296-
export function transformDeclarations(context: TransformationContext, _useTscEmit = true) {
296+
export function transformDeclarations(context: TransformationContext | IsolatedTransformationContext) {
297297
const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context");
298298
let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic;
299299
let needsDeclare = true;
@@ -318,108 +318,80 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
318318
let refs: Map<NodeId, SourceFile>;
319319
let libs: Map<string, boolean>;
320320
let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass
321-
const { localInferenceResolver, isolatedDeclarations, host, resolver, symbolTracker, ensureNoInitializer, useTscEmit } = createTransformerServices();
321+
const { localInferenceResolver, isolatedDeclarations, host, resolver, symbolTracker, ensureNoInitializer, useLocalInferenceTypePrint } = createTransformerServices();
322322
const options = context.getCompilerOptions();
323323
const { noResolve, stripInternal } = options;
324324
return transformRoot;
325325

326-
function createTransformerServices(): {
327-
isolatedDeclarations: true;
328-
useTscEmit: false;
329-
resolver: IsolatedEmitResolver;
330-
localInferenceResolver: LocalInferenceResolver;
331-
host: undefined;
332-
symbolTracker: undefined;
333-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
334-
} | {
335-
isolatedDeclarations: true;
336-
useTscEmit: true;
337-
resolver: EmitResolver;
338-
localInferenceResolver: LocalInferenceResolver;
339-
host: EmitHost;
340-
symbolTracker: SymbolTracker;
341-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
342-
} | {
343-
isolatedDeclarations: false;
344-
useTscEmit: false;
345-
resolver: EmitResolver;
346-
localInferenceResolver: undefined;
347-
host: EmitHost;
348-
symbolTracker: SymbolTracker;
349-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
350-
} {
351-
const { isolatedDeclarations, resolver: localInferenceResolver } = createLocalInferenceResolver({
352-
ensureParameter,
353-
context,
354-
visitDeclarationSubtree,
355-
setEnclosingDeclarations(node) {
356-
const oldNode = enclosingDeclaration;
357-
enclosingDeclaration = node;
358-
return oldNode;
359-
},
360-
checkEntityNameVisibility(name, container) {
361-
return checkEntityNameVisibility(name, container ?? enclosingDeclaration);
362-
},
363-
});
326+
function createTransformerServices() {
327+
const isolatedDeclarations = context.getCompilerOptions().isolatedDeclarations;
364328

365329
if (isolatedDeclarations) {
366-
if (!_useTscEmit) {
367-
const resolver: IsolatedEmitResolver = context.getEmitResolver();
330+
const localInferenceResolver = createLocalInferenceResolver({
331+
ensureParameter,
332+
context,
333+
visitDeclarationSubtree,
334+
setEnclosingDeclarations(node) {
335+
const oldNode = enclosingDeclaration;
336+
enclosingDeclaration = node;
337+
return oldNode;
338+
},
339+
checkEntityNameVisibility(name, container) {
340+
return checkEntityNameVisibility(name, container ?? enclosingDeclaration);
341+
},
342+
});
343+
if (context.kind === TransformationContextKind.IsolatedContext) {
344+
const resolver: CoreEmitResolver = context.getEmitResolver();
368345
// Ideally nothing should require the symbol tracker in isolated declarations mode.
369346
// createLiteralConstValue is the one exception
370347
const emptySymbolTracker = {};
371348
return {
372349
isolatedDeclarations,
373-
useTscEmit: false,
350+
useLocalInferenceTypePrint: true,
374351
resolver,
375352
localInferenceResolver,
376353
symbolTracker: undefined,
377354
host: undefined,
378-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
379-
if (shouldPrintWithInitializer(node)) {
380-
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, emptySymbolTracker); // TODO: Make safe
381-
}
382-
return undefined;
383-
},
384-
};
355+
ensureNoInitializer: createEnsureNoInitializer(emptySymbolTracker),
356+
} as const;
385357
}
386358
else {
387359
const host = context.getEmitHost();
388360
const resolver: EmitResolver = context.getEmitResolver();
389361
const symbolTracker = createSymbolTracker(resolver, host);
390362
return {
391363
isolatedDeclarations,
392-
useTscEmit: true,
364+
useLocalInferenceTypePrint: false,
393365
resolver,
394366
localInferenceResolver,
395367
symbolTracker,
396368
host,
397-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
398-
if (shouldPrintWithInitializer(node)) {
399-
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
400-
}
401-
return undefined;
402-
},
403-
};
369+
ensureNoInitializer: createEnsureNoInitializer(symbolTracker),
370+
} as const;
404371
}
405372
}
406373
else {
374+
Debug.assert(context.kind === TransformationContextKind.FullContext);
407375
const host = context.getEmitHost();
408376
const resolver = context.getEmitResolver();
409377
const symbolTracker: SymbolTracker = createSymbolTracker(resolver, host);
410378
return {
411-
isolatedDeclarations,
412-
useTscEmit: false,
413-
localInferenceResolver,
379+
isolatedDeclarations: false,
380+
useLocalInferenceTypePrint: false,
381+
localInferenceResolver: undefined,
414382
resolver,
415383
symbolTracker,
416384
host,
417-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
418-
if (shouldPrintWithInitializer(node)) {
419-
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
420-
}
421-
return undefined;
422-
},
385+
ensureNoInitializer: createEnsureNoInitializer(symbolTracker),
386+
} as const;
387+
}
388+
389+
function createEnsureNoInitializer(symbolTracker: SymbolTracker) {
390+
return function ensureNoInitializer(node: CanHaveLiteralInitializer) {
391+
if (shouldPrintWithInitializer(node)) {
392+
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
393+
}
394+
return undefined;
423395
};
424396
}
425397
}
@@ -659,6 +631,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
659631
libs = new Map();
660632
existingTypeReferencesSources = node.sourceFiles;
661633
let hasNoDefaultLib = false;
634+
Debug.assert(!isolatedDeclarations, "Bundles are not supported in isolated declarations");
662635
const bundle = factory.createBundle(
663636
map(node.sourceFiles, sourceFile => {
664637
if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217
@@ -681,7 +654,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
681654
sourceFile,
682655
[factory.createModuleDeclaration(
683656
[factory.createModifier(SyntaxKind.DeclareKeyword)],
684-
factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)),
657+
factory.createStringLiteral(getResolvedExternalModuleName(host, sourceFile)),
685658
factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)),
686659
)],
687660
/*isDeclarationFile*/ true,
@@ -952,7 +925,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
952925
}
953926
if (isolatedDeclarations) {
954927
const { typeNode, isInvalid } = localInferenceResolver.fromInitializer(node, type, currentSourceFile);
955-
if (!useTscEmit || isInvalid) {
928+
if (useLocalInferenceTypePrint || isInvalid) {
956929
return typeNode;
957930
}
958931
}
@@ -1121,7 +1094,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
11211094
if (isBundledEmit) {
11221095
// Bundle emit not supported for isolatedDeclarations
11231096
if (!isolatedDeclarations) {
1124-
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
1097+
const newName = getExternalModuleNameFromDeclaration(host, resolver, parent);
11251098
if (newName) {
11261099
return factory.createStringLiteral(newName);
11271100
}

src/compiler/transformers/declarations/emitBinder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import {
6262
isVarConst,
6363
isVariableDeclaration,
6464
MappedTypeNode,
65-
MemberKey,
6665
MethodDeclaration,
6766
MethodSignature,
6867
ModifierFlags,
@@ -816,6 +815,11 @@ export function bindSourceFileForDeclarationEmit(file: SourceFile) {
816815
}
817816
}
818817

818+
/** @internal */
819+
export type MemberKey = string & {
820+
__memberKey: void;
821+
};
822+
819823
/**
820824
* Gets the symbolic name for a member from its type.
821825
* @internal

src/compiler/transformers/declarations/emitResolver.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
bindSourceFileForDeclarationEmit,
33
ComputedPropertyName,
4+
CoreEmitResolver,
45
createEntityVisibilityChecker,
56
createEvaluator,
67
Debug,
@@ -39,7 +40,6 @@ import {
3940
isIdentifier,
4041
isInfinityOrNaNString,
4142
isNumericLiteral,
42-
IsolatedEmitResolver,
4343
isPrefixUnaryExpression,
4444
isPrimitiveLiteralValue,
4545
isPropertyAccessExpression,
@@ -70,7 +70,7 @@ import {
7070
} from "../../_namespaces/ts";
7171

7272
/** @internal */
73-
export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitResolver {
73+
export function createEmitDeclarationResolver(file: SourceFile): CoreEmitResolver {
7474
const { getNodeLinks, resolveMemberKey, resolveName, resolveEntityName } = bindSourceFileForDeclarationEmit(file);
7575

7676
const { isEntityNameVisible } = createEntityVisibilityChecker({
@@ -394,4 +394,4 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
394394

395395
return false;
396396
}
397-
}
397+
}

src/compiler/transformers/declarations/localInferenceResolver.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
isMethodOrAccessor,
3232
isNoSubstitutionTemplateLiteral,
3333
isNumericLiteral,
34+
IsolatedTransformationContext,
3435
isOmittedExpression,
3536
isOptionalDeclaration,
3637
isParameter,
@@ -120,44 +121,39 @@ export function createLocalInferenceResolver({
120121
visitDeclarationSubtree(input: Node): VisitResult<Node | undefined>;
121122
checkEntityNameVisibility(name: EntityNameOrEntityNameExpression, container?: Node): void;
122123
ensureParameter(p: ParameterDeclaration): ParameterDeclaration;
123-
context: TransformationContext;
124-
}): { resolver: LocalInferenceResolver; isolatedDeclarations: true; } | { resolver: undefined; isolatedDeclarations: false; } {
124+
context: IsolatedTransformationContext | TransformationContext;
125+
}): LocalInferenceResolver {
125126
let currentSourceFile: SourceFile;
126127
const options = context.getCompilerOptions();
127128
const resolver = context.getEmitResolver();
128-
if (!options.isolatedDeclarations) {
129-
return { resolver: undefined, isolatedDeclarations: false };
130-
}
129+
Debug.assert(options.isolatedDeclarations, "createLocalInferenceResolver can only be called when isolatedDeclarations is true");
131130
const { factory } = context;
132131
let inferenceContext: { isInvalid: boolean; disableErrors: boolean; } = undefined!;
133132
const strictNullChecks = !!options.strict || !!options.strictNullChecks;
134133

135134
return {
136-
resolver: {
137-
fromInitializer(node: HasInferredType | ExportAssignment, type: TypeNode | undefined, sourceFile: SourceFile) {
138-
const oldSourceFile = currentSourceFile;
139-
const hasExistingContext = inferenceContext !== undefined;
140-
if (!hasExistingContext) {
141-
inferenceContext = { isInvalid: false, disableErrors: false };
142-
}
143-
currentSourceFile = sourceFile;
144-
try {
145-
const typeNode = localInferenceFromInitializer(node, type);
146-
if (typeNode !== undefined) {
147-
return { isInvalid: inferenceContext.isInvalid, typeNode };
148-
}
149-
return { isInvalid: true, typeNode: invalid(node) };
135+
fromInitializer(node: HasInferredType | ExportAssignment, type: TypeNode | undefined, sourceFile: SourceFile) {
136+
const oldSourceFile = currentSourceFile;
137+
const hasExistingContext = inferenceContext !== undefined;
138+
if (!hasExistingContext) {
139+
inferenceContext = { isInvalid: false, disableErrors: false };
140+
}
141+
currentSourceFile = sourceFile;
142+
try {
143+
const typeNode = localInferenceFromInitializer(node, type);
144+
if (typeNode !== undefined) {
145+
return { isInvalid: inferenceContext.isInvalid, typeNode };
150146
}
151-
finally {
152-
currentSourceFile = oldSourceFile;
153-
if (!hasExistingContext) {
154-
inferenceContext = undefined!;
155-
}
147+
return { isInvalid: true, typeNode: invalid(node) };
148+
}
149+
finally {
150+
currentSourceFile = oldSourceFile;
151+
if (!hasExistingContext) {
152+
inferenceContext = undefined!;
156153
}
157-
},
158-
makeInvalidType,
154+
}
159155
},
160-
isolatedDeclarations: options.isolatedDeclarations,
156+
makeInvalidType,
161157
};
162158
function hasParseError(node: Node) {
163159
return !!(node.flags & NodeFlags.ThisNodeHasError);

0 commit comments

Comments
 (0)