package declarations import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" ) type SymbolTrackerImpl struct { resolver printer.EmitResolver state *SymbolTrackerSharedState host DeclarationEmitHost fallbackStack []*ast.Node // For detecting class expression self-references during member serialization. // When set, TrackSymbol will record usage without reporting accessibility errors. watchedClassSymbol *ast.Symbol classSymbolTracked bool getIsolatedDeclarationError func(node *ast.Node) *ast.Diagnostic } // PopErrorFallbackNode implements checker.SymbolTracker. func (s *SymbolTrackerImpl) PopErrorFallbackNode() { s.fallbackStack = s.fallbackStack[:len(s.fallbackStack)-1] } // PushErrorFallbackNode implements checker.SymbolTracker. func (s *SymbolTrackerImpl) PushErrorFallbackNode(node *ast.Node) { s.fallbackStack = append(s.fallbackStack, node) } // ReportCyclicStructureError implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportCyclicStructureError() { location := s.errorLocation() if location != nil { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_inferred_type_of_0_references_a_type_with_a_cyclic_structure_which_cannot_be_trivially_serialized_A_type_annotation_is_necessary, s.errorDeclarationNameWithFallback())) } } // ReportInaccessibleThisError implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportInaccessibleThisError() { location := s.errorLocation() if location != nil { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, s.errorDeclarationNameWithFallback(), "this")) } } // ReportInaccessibleUniqueSymbolError implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportInaccessibleUniqueSymbolError() { location := s.errorLocation() if location != nil { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, s.errorDeclarationNameWithFallback(), "unique symbol")) } } func (s *SymbolTrackerImpl) isBoundExpando(node *ast.Node) bool { if !(ast.IsExpandoPropertyDeclaration(node) && ast.IsPropertyAccessExpression(node.AsBinaryExpression().Left)) { return false } ref := s.resolver.GetReferencedValueDeclarationUnsafe(ast.GetLeftmostExpression(node.AsBinaryExpression().Left, true)) if ref == nil { return false } return s.resolver.IsExpandoFunctionDeclarationUnsafe(ref) } func (s *SymbolTrackerImpl) isChildOfBoundExpando(node *ast.Node) bool { return ast.FindAncestorOrQuit(node, func(n *ast.Node) ast.FindAncestorResult { if ast.IsSourceFile(n) || ast.IsBlock(n) { return ast.FindAncestorQuit } return ast.ToFindAncestorResult(s.isBoundExpando(n)) }) != nil } // ReportInferenceFallback implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportInferenceFallback(node *ast.Node) { if !s.state.isolatedDeclarations { return } if ast.GetSourceFileOfNode(node) != s.state.currentSourceFile { return // Nested error on a declaration in another file - ignore, will be reemitted if file is in the output file set } if s.state.resolver.IsExpandoFunctionDeclarationUnsafe(node) { // within a node builder call that should already lock the checker, use the unsafe call s.state.reportExpandoFunctionErrors(node) } if !s.isChildOfBoundExpando(node) { // expando props get an error when their host is visited by the above, this prevents a follow-on error on a non-inferrable expression s.state.addDiagnostic(s.getIsolatedDeclarationError(node)) } } // ReportLikelyUnsafeImportRequiredError implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportLikelyUnsafeImportRequiredError(specifier string, symbolName string) { location := s.errorLocation() if location != nil { if symbolName != "" { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_2_from_1_This_is_likely_not_portable_A_type_annotation_is_necessary, s.errorDeclarationNameWithFallback(), specifier, symbolName)) } else { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, s.errorDeclarationNameWithFallback(), specifier)) } } } // ReportNonSerializableProperty implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportNonSerializableProperty(propertyName string) { location := s.errorLocation() if location != nil { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_type_of_this_node_cannot_be_serialized_because_its_property_0_cannot_be_serialized, propertyName)) } } // ReportNonlocalAugmentation implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportNonlocalAugmentation(containingFile *ast.SourceFile, parentSymbol *ast.Symbol, augmentingSymbol *ast.Symbol) { primaryDeclaration := core.Find(parentSymbol.Declarations, func(d *ast.Node) bool { return ast.GetSourceFileOfNode(d) == containingFile }) augmentingDeclarations := core.Filter(augmentingSymbol.Declarations, func(d *ast.Node) bool { return ast.GetSourceFileOfNode(d) != containingFile }) if primaryDeclaration != nil && len(augmentingDeclarations) > 0 { for _, augmentations := range augmentingDeclarations { diag := createDiagnosticForNode(augmentations, diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized) related := createDiagnosticForNode(primaryDeclaration, diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) diag.AddRelatedInfo(related) s.state.addDiagnostic(diag) } } } // ReportPrivateInBaseOfClassExpression implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportPrivateInBaseOfClassExpression(propertyName string) { location := s.errorLocation() if location != nil { diag := createDiagnosticForNode(location, diagnostics.Property_0_of_exported_anonymous_class_type_may_not_be_private_or_protected, propertyName) if ast.IsVariableDeclaration(location.Parent) { related := createDiagnosticForNode(location, diagnostics.Add_a_type_annotation_to_the_variable_0, s.errorDeclarationNameWithFallback()) diag.AddRelatedInfo(related) } s.state.addDiagnostic(diag) } } // ReportTruncationError implements checker.SymbolTracker. func (s *SymbolTrackerImpl) ReportTruncationError() { location := s.errorLocation() if location != nil { s.state.addDiagnostic(createDiagnosticForNode(location, diagnostics.The_inferred_type_of_this_node_exceeds_the_maximum_length_the_compiler_will_serialize_An_explicit_type_annotation_is_needed)) } } func (s *SymbolTrackerImpl) errorFallbackNode() *ast.Node { if len(s.fallbackStack) >= 1 { return s.fallbackStack[len(s.fallbackStack)-1] } return nil } func (s *SymbolTrackerImpl) errorLocation() *ast.Node { location := s.state.errorNameNode if location == nil { location = s.errorFallbackNode() } return location } func (s *SymbolTrackerImpl) errorDeclarationNameWithFallback() string { if s.state.errorNameNode != nil { return scanner.DeclarationNameToString(s.state.errorNameNode) } if s.errorFallbackNode() != nil && ast.GetNameOfDeclaration(s.errorFallbackNode()) != nil { return scanner.DeclarationNameToString(ast.GetNameOfDeclaration(s.errorFallbackNode())) } if s.errorFallbackNode() != nil && ast.IsExportAssignment(s.errorFallbackNode()) { if s.errorFallbackNode().AsExportAssignment().IsExportEquals { return "export=" } return "default" } return "(Missing)" // same fallback declarationNameToString uses when node is zero-width (ie, nameless) } // TrackSymbol implements checker.SymbolTracker. func (s *SymbolTrackerImpl) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool { if symbol.Flags&ast.SymbolFlagsTypeParameter != 0 { return false } // When watching for a class expression symbol, record its usage without // reporting accessibility errors — the caller will handle visibility by // wrapping the class in a namespace. if s.watchedClassSymbol != nil && symbol == s.watchedClassSymbol { s.classSymbolTracked = true return false } issuedDiagnostic := s.handleSymbolAccessibilityError(s.resolver.IsSymbolAccessible(symbol, enclosingDeclaration, meaning /*shouldComputeAliasToMarkVisible*/, true)) return issuedDiagnostic } func (s *SymbolTrackerImpl) handleSymbolAccessibilityError(symbolAccessibilityResult printer.SymbolAccessibilityResult) bool { if symbolAccessibilityResult.Accessibility == printer.SymbolAccessibilityAccessible { // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info if len(symbolAccessibilityResult.AliasesToMakeVisible) > 0 { for _, ref := range symbolAccessibilityResult.AliasesToMakeVisible { s.state.lateMarkedStatements = core.AppendIfUnique(s.state.lateMarkedStatements, ref) } } // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible // The checker should issue errors on unresolvable names, skip the declaration emit error for using a private/unreachable name for those } else if symbolAccessibilityResult.Accessibility != printer.SymbolAccessibilityNotResolved { // Report error errorInfo := s.state.getSymbolAccessibilityDiagnostic(symbolAccessibilityResult) if errorInfo != nil { info := *errorInfo diagNode := symbolAccessibilityResult.ErrorNode if diagNode == nil { diagNode = errorInfo.errorNode } if info.typeName != nil { s.state.addDiagnostic(createDiagnosticForNode(diagNode, info.diagnosticMessage, scanner.GetTextOfNode(info.typeName), symbolAccessibilityResult.ErrorSymbolName, symbolAccessibilityResult.ErrorModuleName)) } else { s.state.addDiagnostic(createDiagnosticForNode(diagNode, info.diagnosticMessage, symbolAccessibilityResult.ErrorSymbolName, symbolAccessibilityResult.ErrorModuleName)) } return true } } return false } func createDiagnosticForNode(node *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic { return checker.NewDiagnosticForNode(node, message, args...) } type SymbolTrackerSharedState struct { lateMarkedStatements []*ast.Node diagnostics []*ast.Diagnostic getSymbolAccessibilityDiagnostic GetSymbolAccessibilityDiagnostic errorNameNode *ast.Node isolatedDeclarations bool stripInternal bool currentSourceFile *ast.SourceFile resolver printer.EmitResolver reportExpandoFunctionErrors func(node *ast.Node) } func (s *SymbolTrackerSharedState) addDiagnostic(diag *ast.Diagnostic) { s.diagnostics = append(s.diagnostics, diag) } func NewSymbolTracker(host DeclarationEmitHost, resolver printer.EmitResolver, state *SymbolTrackerSharedState) *SymbolTrackerImpl { tracker := &SymbolTrackerImpl{host: host, resolver: resolver, state: state, getIsolatedDeclarationError: createGetIsolatedDeclarationErrors(resolver)} return tracker }