diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5857bd1ea94e5..cf28044e99aca 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -784,6 +784,7 @@ import { ModuleInstanceState, ModuleKind, ModuleResolutionKind, + ModuleSpecifierResolutionHost, NamedDeclaration, NamedExports, NamedImportsOrExports, @@ -5993,7 +5994,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; - const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); + const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0)); if (typeNode === undefined) return Debug.fail("should always get typenode"); // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. // Otherwise, we always strip comments out. @@ -6077,23 +6078,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const moduleResolverHost = + tracker?.trackSymbol ? tracker.moduleResolverHost : + flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? createBasicNodeBuilderModuleSpecifierResolutionHost(host) : + undefined; const context: NodeBuilderContext = { enclosingDeclaration, flags: flags || NodeBuilderFlags.None, - // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost - tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { - getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", - getCurrentDirectory: () => host.getCurrentDirectory(), - getSymlinkCache: maybeBind(host, host.getSymlinkCache), - getPackageJsonInfoCache: () => host.getPackageJsonInfoCache?.(), - useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), - redirectTargetsMap: host.redirectTargetsMap, - getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), - isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), - fileExists: fileName => host.fileExists(fileName), - getFileIncludeReasons: () => host.getFileIncludeReasons(), - readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, - } : undefined }, + tracker: undefined!, encounteredError: false, reportedDiagnostic: false, visitedTypes: undefined, @@ -6101,45 +6093,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { inferTypeParameters: undefined, approximateLength: 0 }; - context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); + context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost); const resultingNode = cb(context); if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { - context.tracker?.reportTruncationError?.(); + context.tracker.reportTruncationError(); } return context.encounteredError ? undefined : resultingNode; } - function wrapSymbolTrackerToReportForContext(context: NodeBuilderContext, tracker: SymbolTracker): SymbolTracker { - const oldTrackSymbol = tracker.trackSymbol; - return { - ...tracker, - reportCyclicStructureError: wrapReportedDiagnostic(tracker.reportCyclicStructureError), - reportInaccessibleThisError: wrapReportedDiagnostic(tracker.reportInaccessibleThisError), - reportInaccessibleUniqueSymbolError: wrapReportedDiagnostic(tracker.reportInaccessibleUniqueSymbolError), - reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError), - reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation), - reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression), - reportNonSerializableProperty: wrapReportedDiagnostic(tracker.reportNonSerializableProperty), - trackSymbol: oldTrackSymbol && ((...args) => { - const result = oldTrackSymbol(...args); - if (result) { - context.reportedDiagnostic = true; - } - return result; - }), - }; - - function wrapReportedDiagnostic any>(method: T | undefined): T | undefined { - if (!method) { - return method; - } - return (((...args) => { - context.reportedDiagnostic = true; - return method(...args); - }) as T); - } - } - function checkTruncationLength(context: NodeBuilderContext): boolean { if (context.truncating) return context.truncating; return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); @@ -6287,9 +6248,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { context.encounteredError = true; } - if (context.tracker.reportInaccessibleThisError) { - context.tracker.reportInaccessibleThisError(); - } + context.tracker.reportInaccessibleThisError?.(); } context.approximateLength += 4; return factory.createThisTypeNode(); @@ -6926,7 +6885,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { anyType : getNonMissingTypeOfSymbol(propertySymbol); const saveEnclosingDeclaration = context.enclosingDeclaration; context.enclosingDeclaration = undefined; - if (context.tracker.trackSymbol && isLateBoundName(propertySymbol.escapedName)) { + if (context.tracker.canTrackSymbol && isLateBoundName(propertySymbol.escapedName)) { if (propertySymbol.declarations) { const decl = first(propertySymbol.declarations); if (hasLateBindableName(decl)) { @@ -6941,7 +6900,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - else if (context.tracker?.reportNonSerializableProperty) { + else { context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); } } @@ -7245,7 +7204,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function cloneBindingName(node: BindingName): BindingName { return elideInitializerAndSetEmitFlags(node) as BindingName; function elideInitializerAndSetEmitFlags(node: Node): Node { - if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + if (context.tracker.canTrackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { trackComputedName(node.expression, context.enclosingDeclaration, context); } let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; @@ -7266,7 +7225,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { - if (!context.tracker.trackSymbol) return; + if (!context.tracker.canTrackSymbol) return; // get symbol of the first identifier of the entityName const firstIdentifier = getFirstIdentifier(accessExpression); const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); @@ -7276,7 +7235,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 + context.tracker.trackSymbol(symbol, context.enclosingDeclaration, meaning); return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); } @@ -7816,7 +7775,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (initial.typeParameterSymbolList) { initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); } - initial.tracker = wrapSymbolTrackerToReportForContext(initial, initial.tracker); + initial.tracker = new SymbolTrackerImpl(initial, initial.tracker.inner, initial.tracker.moduleResolverHost); return initial; } @@ -7898,7 +7857,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { introducesError = true; } else { - context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); + context.tracker.trackSymbol(sym, context.enclosingDeclaration, SymbolFlags.All); includePrivateSymbol?.(sym); } if (isIdentifier(node)) { @@ -8105,25 +8064,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ...oldcontext, usedSymbolNames: new Set(oldcontext.usedSymbolNames), remappedSymbolNames: new Map(), - tracker: { - ...oldcontext.tracker, - trackSymbol: (sym, decl, meaning) => { - const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeAliases*/ false); - if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { - // Lookup the root symbol of the chain of refs we'll use to access it and serialize it - const chain = lookupSymbolChainWorker(sym, context, meaning); - if (!(sym.flags & SymbolFlags.Property)) { - includePrivateSymbol(chain[0]); - } - } - else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { - return oldcontext.tracker.trackSymbol(sym, decl, meaning); + tracker: undefined!, + }; + const tracker: SymbolTracker = { + ...oldcontext.tracker.inner, + trackSymbol: (sym, decl, meaning) => { + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeAliases*/ false); + if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { + // Lookup the root symbol of the chain of refs we'll use to access it and serialize it + const chain = lookupSymbolChainWorker(sym, context, meaning); + if (!(sym.flags & SymbolFlags.Property)) { + includePrivateSymbol(chain[0]); } - return false; - }, + } + else if (oldcontext.tracker.inner?.trackSymbol) { + return oldcontext.tracker.inner.trackSymbol(sym, decl, meaning); + } + return false; }, }; - context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); + context.tracker = new SymbolTrackerImpl(context, tracker, oldcontext.tracker.moduleResolverHost); forEachEntry(symbolTable, (symbol, name) => { const baseName = unescapeLeadingUnderscores(name); void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` @@ -8424,7 +8384,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ), ModifierFlags.None ); - context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); + context.tracker.trackSymbol(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); } else { const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ @@ -9118,8 +9078,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue // a visibility error here (as they're not visible within any scope), but we want to hoist them // into the containing scope anyway, so we want to skip the visibility checks. - const oldTrack = context.tracker.trackSymbol; - context.tracker.trackSymbol = () => false; + const prevDisableTrackSymbol = context.tracker.disableTrackSymbol; + context.tracker.disableTrackSymbol = true; if (isExportAssignmentCompatibleSymbolName) { results.push(factory.createExportAssignment( /*modifiers*/ undefined, @@ -9147,7 +9107,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { serializeExportSpecifier(name, varName); } } - context.tracker.trackSymbol = oldTrack; + context.tracker.disableTrackSymbol = prevDisableTrackSymbol; return true; } else { @@ -9552,28 +9512,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isExternalModuleAugmentation(node.parent.parent); } - interface NodeBuilderContext { - enclosingDeclaration: Node | undefined; - flags: NodeBuilderFlags; - tracker: SymbolTracker; - - // State - encounteredError: boolean; - reportedDiagnostic: boolean; - visitedTypes: Set | undefined; - symbolDepth: Map | undefined; - inferTypeParameters: TypeParameter[] | undefined; - approximateLength: number; - truncating?: boolean; - typeParameterSymbolList?: Set; - typeParameterNames?: Map; - typeParameterNamesByText?: Set; - typeParameterNamesByTextNextNameCount?: Map; - usedSymbolNames?: Set; - remappedSymbolNames?: Map; - reverseMappedStack?: ReverseMappedSymbol[]; - } - function isDefaultBindingContext(location: Node) { return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); } @@ -47239,3 +47177,152 @@ export function signatureHasRestParameter(s: Signature) { export function signatureHasLiteralTypes(s: Signature) { return !!(s.flags & SignatureFlags.HasLiteralTypes); } + +function createBasicNodeBuilderModuleSpecifierResolutionHost(host: TypeCheckerHost): ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string } { + return { + getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", + getCurrentDirectory: () => host.getCurrentDirectory(), + getSymlinkCache: maybeBind(host, host.getSymlinkCache), + getPackageJsonInfoCache: () => host.getPackageJsonInfoCache?.(), + useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), + redirectTargetsMap: host.redirectTargetsMap, + getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), + isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), + fileExists: fileName => host.fileExists(fileName), + getFileIncludeReasons: () => host.getFileIncludeReasons(), + readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, + }; +} + +interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + flags: NodeBuilderFlags; + tracker: SymbolTrackerImpl; + + // State + encounteredError: boolean; + reportedDiagnostic: boolean; + visitedTypes: Set | undefined; + symbolDepth: Map | undefined; + inferTypeParameters: TypeParameter[] | undefined; + approximateLength: number; + truncating?: boolean; + typeParameterSymbolList?: Set; + typeParameterNames?: Map; + typeParameterNamesByText?: Set; + typeParameterNamesByTextNextNameCount?: Map; + usedSymbolNames?: Set; + remappedSymbolNames?: Map; + reverseMappedStack?: ReverseMappedSymbol[]; +} + +class SymbolTrackerImpl implements SymbolTracker { + moduleResolverHost: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string } | undefined = undefined; + context: NodeBuilderContext; + + readonly inner: SymbolTracker | undefined = undefined; + readonly canTrackSymbol: boolean; + disableTrackSymbol = false; + + constructor(context: NodeBuilderContext, tracker: SymbolTracker | undefined, moduleResolverHost: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string } | undefined) { + while (tracker instanceof SymbolTrackerImpl) { + tracker = tracker.inner; + } + + this.inner = tracker; + this.moduleResolverHost = moduleResolverHost; + this.context = context; + this.canTrackSymbol = !!this.inner?.trackSymbol; + } + + trackSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): boolean { + if (this.inner?.trackSymbol && !this.disableTrackSymbol) { + if (this.inner.trackSymbol(symbol, enclosingDeclaration, meaning)) { + this.onDiagnosticReported(); + return true; + } + } + return false; + } + + reportInaccessibleThisError(): void { + if (this.inner?.reportInaccessibleThisError) { + this.onDiagnosticReported(); + this.inner.reportInaccessibleThisError(); + } + } + + reportPrivateInBaseOfClassExpression(propertyName: string): void { + if (this.inner?.reportPrivateInBaseOfClassExpression) { + this.onDiagnosticReported(); + this.inner.reportPrivateInBaseOfClassExpression(propertyName); + } + } + + reportInaccessibleUniqueSymbolError(): void { + if (this.inner?.reportInaccessibleUniqueSymbolError) { + this.onDiagnosticReported(); + this.inner.reportInaccessibleUniqueSymbolError(); + } + } + + reportCyclicStructureError(): void { + if (this.inner?.reportCyclicStructureError) { + this.onDiagnosticReported(); + this.inner.reportCyclicStructureError(); + } + } + + reportLikelyUnsafeImportRequiredError(specifier: string): void { + if (this.inner?.reportLikelyUnsafeImportRequiredError) { + this.onDiagnosticReported(); + this.inner.reportLikelyUnsafeImportRequiredError(specifier); + } + } + + reportTruncationError(): void { + if (this.inner?.reportTruncationError) { + this.onDiagnosticReported(); + this.inner.reportTruncationError(); + } + } + + trackReferencedAmbientModule(decl: ModuleDeclaration, symbol: Symbol): void { + if (this.inner?.trackReferencedAmbientModule) { + this.onDiagnosticReported(); + this.inner.trackReferencedAmbientModule(decl, symbol); + } + } + + trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol): void { + if (this.inner?.trackExternalModuleSymbolOfImportTypeNode) { + this.onDiagnosticReported(); + this.inner.trackExternalModuleSymbolOfImportTypeNode(symbol); + } + } + + reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, augmentingSymbol: Symbol): void { + if (this.inner?.reportNonlocalAugmentation) { + this.onDiagnosticReported(); + this.inner.reportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol); + } + } + + reportNonSerializableProperty(propertyName: string): void { + if (this.inner?.reportNonSerializableProperty) { + this.onDiagnosticReported(); + this.inner.reportNonSerializableProperty(propertyName); + } + } + + reportImportTypeNodeResolutionModeOverride(): void { + if (this.inner?.reportImportTypeNodeResolutionModeOverride) { + this.onDiagnosticReported(); + this.inner.reportImportTypeNodeResolutionModeOverride(); + } + } + + private onDiagnosticReported() { + this.context.reportedDiagnostic = true; + } +} \ No newline at end of file diff --git a/src/compiler/types.ts b/src/compiler/types.ts index adb89da3d7d55..47f39f799a29e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5372,7 +5372,7 @@ export interface SymbolWalker { // This was previously deprecated in our public API, but is still used internally /** @internal */ -export interface SymbolWriter extends SymbolTracker { +export interface SymbolWriter { writeKeyword(text: string): void; writeOperator(text: string): void; writePunctuation(text: string): void; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9384e6f246528..96df69dee8b9c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -606,10 +606,6 @@ function createSingleLineStringWriter(): EmitTextWriter { increaseIndent: noop, decreaseIndent: noop, clear: () => str = "", - trackSymbol: () => false, - reportInaccessibleThisError: noop, - reportInaccessibleUniqueSymbolError: noop, - reportPrivateInBaseOfClassExpression: noop, }; } @@ -5324,10 +5320,6 @@ export function createTextWriter(newLine: string): EmitTextWriter { hasTrailingComment: () => hasTrailingComment, hasTrailingWhitespace: () => !!output.length && isWhiteSpaceLike(output.charCodeAt(output.length - 1)), clear: reset, - reportInaccessibleThisError: noop, - reportPrivateInBaseOfClassExpression: noop, - reportInaccessibleUniqueSymbolError: noop, - trackSymbol: () => false, writeKeyword: write, writeOperator: write, writeParameter: write, diff --git a/src/services/inlayHints.ts b/src/services/inlayHints.ts index 45bb04e6e1329..5544aaa920c02 100644 --- a/src/services/inlayHints.ts +++ b/src/services/inlayHints.ts @@ -387,7 +387,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] { const printer = createPrinter(options); return usingSingleLineStringWriter(writer => { - const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); + const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags); Debug.assertIsDefined(typeNode, "should always get typenode"); printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); }); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a5e151ac7ab1a..c0901ab286147 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -271,7 +271,6 @@ import { nodeIsMissing, nodeIsPresent, nodeIsSynthesized, - noop, normalizePath, NoSubstitutionTemplateLiteral, notImplemented, @@ -2721,10 +2720,6 @@ function getDisplayPartWriter(): DisplayPartsSymbolWriter { increaseIndent: () => { indent++; }, decreaseIndent: () => { indent--; }, clear: resetWriter, - trackSymbol: () => false, - reportInaccessibleThisError: noop, - reportInaccessibleUniqueSymbolError: noop, - reportPrivateInBaseOfClassExpression: noop, }; function writeIndent() {