private _analyzeChildTree()

in apps/api-extractor/src/analyzer/AstSymbolTable.ts [343:475]


  private _analyzeChildTree(node: ts.Node, governingAstDeclaration: AstDeclaration): void {
    switch (node.kind) {
      case ts.SyntaxKind.JSDocComment: // Skip JSDoc comments - TS considers @param tags TypeReference nodes
        return;

      // Is this a reference to another AstSymbol?
      case ts.SyntaxKind.TypeReference: // general type references
      case ts.SyntaxKind.ExpressionWithTypeArguments: // special case for e.g. the "extends" keyword
      case ts.SyntaxKind.ComputedPropertyName: // used for EcmaScript "symbols", e.g. "[toPrimitive]".
      case ts.SyntaxKind.TypeQuery: // represents for "typeof X" as a type
        {
          // Sometimes the type reference will involve multiple identifiers, e.g. "a.b.C".
          // In this case, we only need to worry about importing the first identifier,
          // so do a depth-first search for it:
          const identifierNode: ts.Identifier | undefined = TypeScriptHelpers.findFirstChildNode(
            node,
            ts.SyntaxKind.Identifier
          );

          if (identifierNode) {
            let referencedAstEntity: AstEntity | undefined = this._entitiesByNode.get(identifierNode);
            if (!referencedAstEntity) {
              const symbol: ts.Symbol | undefined = this._typeChecker.getSymbolAtLocation(identifierNode);
              if (!symbol) {
                throw new Error('Symbol not found for identifier: ' + identifierNode.getText());
              }

              // Normally we expect getSymbolAtLocation() to take us to a declaration within the same source
              // file, or else to an explicit "import" statement within the same source file.  But in certain
              // situations (e.g. a global variable) the symbol will refer to a declaration in some other
              // source file.  We'll call that case a "displaced symbol".
              //
              // For more info, see this discussion:
              // https://github.com/microsoft/rushstack/issues/1765#issuecomment-595559849
              let displacedSymbol: boolean = true;
              for (const declaration of symbol.declarations || []) {
                if (declaration.getSourceFile() === identifierNode.getSourceFile()) {
                  displacedSymbol = false;
                  break;
                }
              }

              if (displacedSymbol) {
                if (this._globalVariableAnalyzer.hasGlobalName(identifierNode.text)) {
                  // If the displaced symbol is a global variable, then API Extractor simply ignores it.
                  // Ambient declarations typically describe the runtime environment (provided by an API consumer),
                  // so we don't bother analyzing them as an API contract.  (There are probably some packages
                  // that include interesting global variables in their API, but API Extractor doesn't support
                  // that yet; it would be a feature request.)

                  if (this._messageRouter.showDiagnostics) {
                    if (!this._alreadyWarnedGlobalNames.has(identifierNode.text)) {
                      this._alreadyWarnedGlobalNames.add(identifierNode.text);
                      this._messageRouter.logDiagnostic(
                        `Ignoring reference to global variable "${identifierNode.text}"` +
                          ` in ` +
                          SourceFileLocationFormatter.formatDeclaration(identifierNode)
                      );
                    }
                  }
                } else {
                  // If you encounter this, please report a bug with a repro.  We're interested to know
                  // how it can occur.
                  throw new InternalError(`Unable to follow symbol for "${identifierNode.text}"`);
                }
              } else {
                referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntity(
                  symbol,
                  governingAstDeclaration.astSymbol.isExternal
                );

                this._entitiesByNode.set(identifierNode, referencedAstEntity);
              }
            }

            if (referencedAstEntity) {
              governingAstDeclaration._notifyReferencedAstEntity(referencedAstEntity);
            }
          }
        }
        break;

      // Is this the identifier for the governingAstDeclaration?
      case ts.SyntaxKind.Identifier:
        {
          const identifierNode: ts.Identifier = node as ts.Identifier;
          if (!this._entitiesByNode.has(identifierNode)) {
            const symbol: ts.Symbol | undefined = this._typeChecker.getSymbolAtLocation(identifierNode);

            let referencedAstEntity: AstEntity | undefined = undefined;

            if (symbol === governingAstDeclaration.astSymbol.followedSymbol) {
              referencedAstEntity = this._fetchEntityForNode(identifierNode, governingAstDeclaration);
            }

            this._entitiesByNode.set(identifierNode, referencedAstEntity);
          }
        }
        break;

      case ts.SyntaxKind.ImportType:
        {
          const importTypeNode: ts.ImportTypeNode = node as ts.ImportTypeNode;
          let referencedAstEntity: AstEntity | undefined = this._entitiesByNode.get(importTypeNode);

          if (!this._entitiesByNode.has(importTypeNode)) {
            referencedAstEntity = this._fetchEntityForNode(importTypeNode, governingAstDeclaration);

            if (!referencedAstEntity) {
              // This should never happen
              throw new Error('Failed to fetch entity for import() type node: ' + importTypeNode.getText());
            }

            this._entitiesByNode.set(importTypeNode, referencedAstEntity);
          }

          if (referencedAstEntity) {
            governingAstDeclaration._notifyReferencedAstEntity(referencedAstEntity);
          }
        }
        break;
    }

    // Is this node declaring a new AstSymbol?
    const newGoverningAstDeclaration: AstDeclaration | undefined = this._fetchAstDeclaration(
      node,
      governingAstDeclaration.astSymbol.isExternal
    );

    for (const childNode of node.getChildren()) {
      this._analyzeChildTree(childNode, newGoverningAstDeclaration || governingAstDeclaration);
    }
  }