@@ -4337,6 +4337,8 @@ namespace ts {
4337
4337
if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) {
4338
4338
return undefined;
4339
4339
}
4340
+ // TODO(jakebailey): do something different with caching here rather than clearing node.id?
4341
+ // Maybe, include in the key the length of the original chain?
4340
4342
const links = getSymbolLinks(symbol);
4341
4343
const cache = (links.accessibleChainCache ||= new Map());
4342
4344
// Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more
@@ -5090,7 +5092,7 @@ namespace ts {
5090
5092
}
5091
5093
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
5092
5094
type.flags & TypeFlags.TypeParameter &&
5093
- !isTypeSymbolAccessible (type.symbol , context.enclosingDeclaration )) {
5095
+ typeParameterIsCachedOrNotAccessible (type, context)) {
5094
5096
const name = typeParameterToName(type, context);
5095
5097
context.approximateLength += idText(name).length;
5096
5098
return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined);
@@ -5880,6 +5882,31 @@ namespace ts {
5880
5882
typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context));
5881
5883
}
5882
5884
5885
+ // For regular function/method declarations, the enclosing declaration will already be signature.declaration,
5886
+ // so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be
5887
+ // the declaration that the arrow function / function expression is assigned to.
5888
+ //
5889
+ // If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead
5890
+ // us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter,
5891
+ // not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function.
5892
+ //
5893
+ // We can't use the declaration directly; it may be in another file and so we may lose access to symbols
5894
+ // accessible to the current enclosing declaration, or gain access to symbols not accessible to the current
5895
+ // enclosing declaration. To keep this chain accurate, clone the function as a non-synthesized node (so that
5896
+ // it can be used as an enclosing declaration) and make its parent the current enclosing declaration.
5897
+ //
5898
+ // If the declaration is in a JS file, then we don't need to do this at all, as there are no annotations besides
5899
+ // JSDoc, which are always outside the function declaration, so are not in the parameter scope.
5900
+ const saveEnclosingDeclaration = context.enclosingDeclaration;
5901
+ const originalDeclaration = signature.declaration;
5902
+ if (saveEnclosingDeclaration && originalDeclaration && originalDeclaration !== saveEnclosingDeclaration && !isInJSFile(originalDeclaration)) {
5903
+ // Debug.assert(!isJSDocSignature(originalDeclaration));
5904
+ const clone = parseNodeFactory.cloneNode(originalDeclaration);
5905
+ // setTextRange(clone, originalDeclaration); // TODO(jakebailey): Is this needed?
5906
+ setParent(clone, saveEnclosingDeclaration); // TODO(jakebailey): we can chain these if we want.
5907
+ context.enclosingDeclaration = clone;
5908
+ }
5909
+
5883
5910
const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0];
5884
5911
// If the expanded parameter list had a variadic in a non-trailing position, don't expand it
5885
5912
const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports));
@@ -5936,6 +5963,7 @@ namespace ts {
5936
5963
node.typeArguments = factory.createNodeArray(typeArguments);
5937
5964
}
5938
5965
5966
+ context.enclosingDeclaration = saveEnclosingDeclaration;
5939
5967
return node;
5940
5968
}
5941
5969
@@ -6420,7 +6448,29 @@ namespace ts {
6420
6448
return false;
6421
6449
}
6422
6450
6451
+ // TODO(jakebailey): Is this change still needed?
6452
+ // Returns true if this type parameter should be converted to a name via typeParameterToName,
6453
+ // either because it is not accessible, or because it's a use of a type variable that has
6454
+ // already been rewritten by typeParameterToName and so needs that rewritten name.
6455
+ function typeParameterIsCachedOrNotAccessible(type: TypeParameter, context: NodeBuilderContext) {
6456
+ return !!getCachedTypeParameterName(type, context) || !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration);
6457
+ }
6458
+
6459
+ function getCachedTypeParameterName(type: TypeParameter, context: NodeBuilderContext) {
6460
+ if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) {
6461
+ const cached = context.typeParameterNames.get(getTypeId(type));
6462
+ if (cached) {
6463
+ return cached;
6464
+ }
6465
+ }
6466
+ return undefined;
6467
+ }
6468
+
6423
6469
function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) {
6470
+ const cached = getCachedTypeParameterName(type, context);
6471
+ if (cached) {
6472
+ return cached;
6473
+ }
6424
6474
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) {
6425
6475
const cached = context.typeParameterNames.get(getTypeId(type));
6426
6476
if (cached) {
@@ -6601,9 +6651,22 @@ namespace ts {
6601
6651
return initial;
6602
6652
}
6603
6653
6654
+ function isChildOfEnclosingDeclaration(enclosingDeclaration: Node | undefined, node: Node): boolean {
6655
+ return !!findAncestor(node, n => isEnclosingDeclarationOrOriginal(n));
6656
+ // If our enclosing declaration was created via cloning, then the enclosing declaration won't be
6657
+ // an ancestor of say, a parameter declaration, but an original node may be.
6658
+ function isEnclosingDeclarationOrOriginal(n: Node): boolean {
6659
+ for (let e = enclosingDeclaration; e; e = e.original) {
6660
+ if (e === n) {
6661
+ return true;
6662
+ }
6663
+ }
6664
+ return false;
6665
+ }
6666
+ }
6604
6667
6605
6668
function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) {
6606
- return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration )));
6669
+ return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || isChildOfEnclosingDeclaration(enclosingDeclaration, s )));
6607
6670
}
6608
6671
6609
6672
function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) {
@@ -6652,7 +6715,7 @@ namespace ts {
6652
6715
function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) {
6653
6716
if (!isErrorType(type) && context.enclosingDeclaration) {
6654
6717
const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration);
6655
- if (!!findAncestor( annotation, n => n === context.enclosingDeclaration) && annotation) {
6718
+ if (annotation && isChildOfEnclosingDeclaration( context.enclosingDeclaration, annotation)) { // TODO(jakebailey): this also needs to check original; move to helper
6656
6719
const annotated = getTypeFromTypeNode(annotation);
6657
6720
const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated;
6658
6721
if (thisInstantiated === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) {
@@ -6684,7 +6747,7 @@ namespace ts {
6684
6747
}
6685
6748
if (isIdentifier(node)) {
6686
6749
const type = getDeclaredTypeOfSymbol(sym);
6687
- const name = sym.flags & SymbolFlags.TypeParameter && !isTypeSymbolAccessible (type.symbol , context.enclosingDeclaration ) ? typeParameterToName(type, context) : factory.cloneNode(node);
6750
+ const name = sym.flags & SymbolFlags.TypeParameter && typeParameterIsCachedOrNotAccessible (type, context) ? typeParameterToName(type, context) : factory.cloneNode(node);
6688
6751
name.symbol = sym; // for quickinfo, which uses identifier symbol information
6689
6752
return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) };
6690
6753
}
@@ -7116,7 +7179,7 @@ namespace ts {
7116
7179
visitedSymbols.add(getSymbolId(visitedSym));
7117
7180
// Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol
7118
7181
const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope
7119
- if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration )))) {
7182
+ if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => isChildOfEnclosingDeclaration(enclosingDeclaration, d )))) {
7120
7183
const oldContext = context;
7121
7184
context = cloneNodeBuilderContext(context);
7122
7185
serializeSymbolWorker(symbol, isPrivate, propertyAsAlias);
0 commit comments