private _fetchAstSymbol()

in apps/api-extractor/src/analyzer/AstSymbolTable.ts [538:694]


  private _fetchAstSymbol(options: IFetchAstSymbolOptions): AstSymbol | undefined {
    const followedSymbol: ts.Symbol = options.followedSymbol;

    // Filter out symbols representing constructs that we don't care about
    const arbitraryDeclaration: ts.Declaration | undefined =
      TypeScriptHelpers.tryGetADeclaration(followedSymbol);
    if (!arbitraryDeclaration) {
      return undefined;
    }

    if (
      followedSymbol.flags &
      (ts.SymbolFlags.TypeParameter | ts.SymbolFlags.TypeLiteral | ts.SymbolFlags.Transient)
    ) {
      if (!TypeScriptInternals.isLateBoundSymbol(followedSymbol)) {
        return undefined;
      }
    }

    // API Extractor doesn't analyze ambient declarations at all
    if (TypeScriptHelpers.isAmbient(followedSymbol, this._typeChecker)) {
      // We make a special exemption for ambient declarations that appear in a source file containing
      // an "export=" declaration that allows them to be imported as non-ambient.
      if (!this._exportAnalyzer.isImportableAmbientSourceFile(arbitraryDeclaration.getSourceFile())) {
        return undefined;
      }
    }

    // Make sure followedSymbol isn't an alias for something else
    if (TypeScriptHelpers.isFollowableAlias(followedSymbol, this._typeChecker)) {
      // We expect the caller to have already followed any aliases
      throw new InternalError('AstSymbolTable._fetchAstSymbol() cannot be called with a symbol alias');
    }

    let astSymbol: AstSymbol | undefined = this._astSymbolsBySymbol.get(followedSymbol);

    if (!astSymbol) {
      // None of the above lookups worked, so create a new entry...
      let nominalAnalysis: boolean = false;

      if (options.isExternal) {
        // If the file is from an external package that does not support AEDoc, normally we ignore it completely.
        // But in some cases (e.g. checking star exports of an external package) we need an AstSymbol to
        // represent it, but we don't need to analyze its sibling/children.
        const followedSymbolSourceFileName: string = arbitraryDeclaration.getSourceFile().fileName;

        if (!this._packageMetadataManager.isAedocSupportedFor(followedSymbolSourceFileName)) {
          nominalAnalysis = true;

          if (!options.includeNominalAnalysis) {
            return undefined;
          }
        }
      }

      let parentAstSymbol: AstSymbol | undefined = undefined;

      if (!nominalAnalysis) {
        for (const declaration of followedSymbol.declarations || []) {
          if (!AstDeclaration.isSupportedSyntaxKind(declaration.kind)) {
            throw new InternalError(
              `The "${followedSymbol.name}" symbol has a` +
                ` ts.SyntaxKind.${ts.SyntaxKind[declaration.kind]} declaration which is not (yet?)` +
                ` supported by API Extractor`
            );
          }
        }

        // We always fetch the entire chain of parents for each declaration.
        // (Children/siblings are only analyzed on demand.)

        // Key assumptions behind this squirrely logic:
        //
        // IF a given symbol has two declarations D1 and D2; AND
        // If D1 has a parent P1, then
        // - D2 will also have a parent P2; AND
        // - P1 and P2's symbol will be the same
        // - but P1 and P2 may be different (e.g. merged namespaces containing merged interfaces)

        // Is there a parent AstSymbol?  First we check to see if there is a parent declaration:
        const arbitraryDeclaration: ts.Node | undefined =
          TypeScriptHelpers.tryGetADeclaration(followedSymbol);

        if (arbitraryDeclaration) {
          const arbitraryParentDeclaration: ts.Node | undefined =
            this._tryFindFirstAstDeclarationParent(arbitraryDeclaration);

          if (arbitraryParentDeclaration) {
            const parentSymbol: ts.Symbol = TypeScriptHelpers.getSymbolForDeclaration(
              arbitraryParentDeclaration as ts.Declaration,
              this._typeChecker
            );

            parentAstSymbol = this._fetchAstSymbol({
              followedSymbol: parentSymbol,
              isExternal: options.isExternal,
              includeNominalAnalysis: false,
              addIfMissing: true
            });
            if (!parentAstSymbol) {
              throw new InternalError('Unable to construct a parent AstSymbol for ' + followedSymbol.name);
            }
          }
        }
      }

      const localName: string | undefined =
        options.localName || AstSymbolTable.getLocalNameForSymbol(followedSymbol);

      astSymbol = new AstSymbol({
        followedSymbol: followedSymbol,
        localName: localName,
        isExternal: options.isExternal,
        nominalAnalysis: nominalAnalysis,
        parentAstSymbol: parentAstSymbol,
        rootAstSymbol: parentAstSymbol ? parentAstSymbol.rootAstSymbol : undefined
      });

      this._astSymbolsBySymbol.set(followedSymbol, astSymbol);

      // Okay, now while creating the declarations we will wire them up to the
      // their corresponding parent declarations
      for (const declaration of followedSymbol.declarations || []) {
        let parentAstDeclaration: AstDeclaration | undefined = undefined;
        if (parentAstSymbol) {
          const parentDeclaration: ts.Node | undefined = this._tryFindFirstAstDeclarationParent(declaration);

          if (!parentDeclaration) {
            throw new InternalError('Missing parent declaration');
          }

          parentAstDeclaration = this._astDeclarationsByDeclaration.get(parentDeclaration);
          if (!parentAstDeclaration) {
            throw new InternalError('Missing parent AstDeclaration');
          }
        }

        const astDeclaration: AstDeclaration = new AstDeclaration({
          declaration,
          astSymbol,
          parent: parentAstDeclaration
        });

        this._astDeclarationsByDeclaration.set(declaration, astDeclaration);
      }
    }

    if (options.isExternal !== astSymbol.isExternal) {
      throw new InternalError(
        `Cannot assign isExternal=${options.isExternal} for` +
          ` the symbol ${astSymbol.localName} because it was previously registered` +
          ` with isExternal=${astSymbol.isExternal}`
      );
    }

    return astSymbol;
  }