export function analyzeImportDeclaration()

in src/typescript/imports.ts [69:222]


export function analyzeImportDeclaration(
  node: ts.ImportDeclaration | ts.JSDocImportTag,
  context: AstRenderer<any>,
  submoduleReferences: SubmoduleReferenceMap,
): ImportStatement[];
export function analyzeImportDeclaration(
  node: ts.ImportDeclaration | ts.JSDocImportTag,
  context: AstRenderer<any>,
  submoduleReferences?: SubmoduleReferenceMap,
): ImportStatement | ImportStatement[] {
  const packageName = stringFromLiteral(node.moduleSpecifier);

  const starBindings = matchAst(
    node,
    nodeOfType(
      ts.SyntaxKind.ImportDeclaration,
      nodeOfType(ts.SyntaxKind.ImportClause, nodeOfType('namespace', ts.SyntaxKind.NamespaceImport)),
    ),
  );

  if (starBindings) {
    const sourceName = context.textOf(starBindings.namespace.name);
    const bareImport: ImportStatement = {
      node,
      packageName,
      moduleSymbol: lookupJsiiSymbolFromNode(context.typeChecker, starBindings.namespace.name),
      imports: {
        import: 'full',
        alias: sourceName,
        sourceName,
      },
    };
    if (submoduleReferences == null) {
      return bareImport;
    }

    const rootSymbol = context.typeChecker.getSymbolAtLocation(starBindings.namespace.name);
    const refs = rootSymbol && Array.from(submoduleReferences.values()).filter((ref) => ref.root === rootSymbol);
    // No submodule reference, or only 1 where the path is empty (this is used to signal the use of the bare import so it's not erased)
    if (refs == null || refs.length === 0 || (refs.length === 1 && refs[0].path.length === 0)) {
      return [bareImport];
    }

    return refs.flatMap(({ lastNode, path, root, submoduleChain }, idx, array): ImportStatement[] => {
      if (
        array
          .slice(0, idx)
          .some(
            (other) => other.root === root && context.textOf(other.submoduleChain) === context.textOf(submoduleChain),
          )
      ) {
        // This would be a duplicate, so we're skipping it
        return [];
      }

      const moduleSymbol = lookupJsiiSymbolFromNode(context.typeChecker, lastNode);
      return [
        {
          node,
          packageName: [packageName, ...path.map((n) => context.textOf(n))].join('/'),
          moduleSymbol,
          imports: {
            import: 'full',
            alias: undefined, // No alias exists in the source text for this...
            sourceName: context.textOf(submoduleChain),
          },
        },
      ];
    });
  }

  const namedBindings = matchAst(
    node,
    nodeOfType(
      ts.SyntaxKind.ImportDeclaration,
      nodeOfType(
        ts.SyntaxKind.ImportClause,
        nodeOfType(ts.SyntaxKind.NamedImports, allOfType(ts.SyntaxKind.ImportSpecifier, 'specifiers')),
      ),
    ),
  );

  const extraImports = new Array<ImportStatement>();
  const elements: ImportBinding[] = (namedBindings?.specifiers ?? []).flatMap(
    ({ name, propertyName }): ImportBinding[] => {
      // regular import { name }
      // renamed import { propertyName as name }
      const directBinding = {
        sourceName: context.textOf(propertyName ?? name),
        alias: propertyName && context.textOf(name),
        importedSymbol: lookupJsiiSymbolFromNode(context.typeChecker, propertyName ?? name),
      } as const;

      if (submoduleReferences != null) {
        const symbol = context.typeChecker.getSymbolAtLocation(name);
        let omitDirectBinding = false;
        for (const match of Array.from(submoduleReferences.values()).filter((ref) => ref.root === symbol)) {
          if (match.path.length === 0) {
            // This is a namespace binding that is used as-is (not via a transitive path). It needs to be preserved.
            omitDirectBinding = false;
            continue;
          }
          const subPackageName = [packageName, ...match.path.map((n) => n.getText(n.getSourceFile()))].join('/');
          const importedSymbol = lookupJsiiSymbolFromNode(context.typeChecker, match.lastNode);
          const moduleSymbol = fmap(importedSymbol, parentSymbol);
          const importStatement =
            extraImports.find((stmt) => {
              if (moduleSymbol != null) {
                return stmt.moduleSymbol === moduleSymbol;
              }
              return stmt.packageName === subPackageName;
            }) ??
            extraImports[
              extraImports.push({
                moduleSymbol,
                node: match.lastNode,
                packageName: subPackageName,
                imports: { import: 'selective', elements: [] },
              }) - 1
            ];

          (importStatement.imports as SelectiveImport).elements.push({
            sourceName: context.textOf(match.submoduleChain),
            importedSymbol,
          });
        }
        if (omitDirectBinding) {
          return [];
        }
      }

      return [directBinding];
    },
  );

  if (submoduleReferences == null) {
    return {
      node,
      packageName,
      imports: { import: 'selective', elements },
      moduleSymbol: fmap(elements?.[0]?.importedSymbol, parentSymbol),
    };
  }

  return [
    {
      node,
      packageName,
      imports: { import: 'selective', elements },
      moduleSymbol: fmap(elements?.[0]?.importedSymbol, parentSymbol),
    },
    ...extraImports,
  ];
}