<commit_before>import * as fs from 'fs';
import {join} from 'path';
import * as ts from 'typescript';

import {TypeScriptFileRefactor} from './refactor';


function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
                                      symbolName: string,
                                      host: ts.CompilerHost,
                                      program: ts.Program): string | null {
  // Check this file.
  const hasSymbol = refactor.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
    .some((cd: ts.ClassDeclaration) => {
      return cd.name && cd.name.text == symbolName;
    });
  if (hasSymbol) {
    return refactor.fileName;
  }

  // We found the bootstrap variable, now we just need to get where it's imported.
  const exports = refactor.findAstNodes(null, ts.SyntaxKind.ExportDeclaration)
    .map(node => node as ts.ExportDeclaration);

  for (const decl of exports) {
    if (!decl.moduleSpecifier || decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) {
      continue;
    }

    const modulePath = (decl.moduleSpecifier as ts.StringLiteral).text;
    const resolvedModule = ts.resolveModuleName(
      modulePath, refactor.fileName, program.getCompilerOptions(), host);
    if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
      return null;
    }

    const module = resolvedModule.resolvedModule.resolvedFileName;
    if (!decl.exportClause) {
      const moduleRefactor = new TypeScriptFileRefactor(module, host, program);
      const maybeModule = _recursiveSymbolExportLookup(moduleRefactor, symbolName, host, program);
      if (maybeModule) {
        return maybeModule;
      }
      continue;
    }

    const binding = decl.exportClause as ts.NamedExports;
    for (const specifier of binding.elements) {
      if (specifier.name.text == symbolName) {
        // If it's a directory, load its index and recursively lookup.
        if (fs.statSync(module).isDirectory()) {
          const indexModule = join(module, 'index.ts');
          if (fs.existsSync(indexModule)) {
            const indexRefactor = new TypeScriptFileRefactor(indexModule, host, program);
            const maybeModule = _recursiveSymbolExportLookup(
              indexRefactor, symbolName, host, program);
            if (maybeModule) {
              return maybeModule;
            }
          }
        }

        // Create the source and verify that the symbol is at least a class.
        const source = new TypeScriptFileRefactor(module, host, program);
        const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
          .some((cd: ts.ClassDeclaration) => {
            return cd.name && cd.name.text == symbolName;
          });

        if (hasSymbol) {
          return module;
        }
      }
    }
  }

  return null;
}

function _symbolImportLookup(refactor: TypeScriptFileRefactor,
                             symbolName: string,
                             host: ts.CompilerHost,
                             program: ts.Program): string | null {
  // We found the bootstrap variable, now we just need to get where it's imported.
  const imports = refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration)
    .map(node => node as ts.ImportDeclaration);

  for (const decl of imports) {
    if (!decl.importClause || !decl.moduleSpecifier) {
      continue;
    }
    if (decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) {
      continue;
    }

    const resolvedModule = ts.resolveModuleName(
      (decl.moduleSpecifier as ts.StringLiteral).text,
      refactor.fileName, program.getCompilerOptions(), host);
    if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
      return null;
    }

    const module = resolvedModule.resolvedModule.resolvedFileName;
    if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamespaceImport) {
      const binding = decl.importClause.namedBindings as ts.NamespaceImport;
      if (binding.name.text == symbolName) {
        // This is a default export.
        return module;
      }
    } else if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamedImports) {
      const binding = decl.importClause.namedBindings as ts.NamedImports;
      for (const specifier of binding.elements) {
        if (specifier.name.text == symbolName) {
          // Create the source and recursively lookup the import.
          const source = new TypeScriptFileRefactor(module, host, program);
          const maybeModule = _recursiveSymbolExportLookup(source, symbolName, host, program);
          if (maybeModule) {
            return maybeModule;
          }
        }
      }
    }
  }
  return null;
}


export function resolveEntryModuleFromMain(mainPath: string,
                                           host: ts.CompilerHost,
                                           program: ts.Program) {
  const source = new TypeScriptFileRefactor(mainPath, host, program);

  const bootstrap = source.findAstNodes(source.sourceFile, ts.SyntaxKind.CallExpression, false)
    .map(node => node as ts.CallExpression)
    .filter(call => {
      const access = call.expression as ts.PropertyAccessExpression;
      return access.kind == ts.SyntaxKind.PropertyAccessExpression
          && access.name.kind == ts.SyntaxKind.Identifier
          && (access.name.text == 'bootstrapModule'
              || access.name.text == 'bootstrapModuleFactory');
    })
    .map(node => node.arguments[0] as ts.Identifier)
    .filter(node => node.kind == ts.SyntaxKind.Identifier);

  if (bootstrap.length != 1) {
    throw new Error('Tried to find bootstrap code, but could not. Specify either '
      + 'statically analyzable bootstrap code or pass in an entryModule '
      + 'to the plugins options.');
  }
  const bootstrapSymbolName = bootstrap[0].text;
  const module = _symbolImportLookup(source, bootstrapSymbolName, host, program);
  if (module) {
    return `${module.replace(/\.ts$/, '')}#${bootstrapSymbolName}`;
  }
<commit_msg>fix(@ngtools/webpack): search recursively for entry module (#3708)<commit_after>import * as fs from 'fs';
import {join} from 'path';
import * as ts from 'typescript';

import {TypeScriptFileRefactor} from './refactor';


function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
                                      symbolName: string,
                                      host: ts.CompilerHost,
                                      program: ts.Program): string | null {
  // Check this file.
  const hasSymbol = refactor.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
    .some((cd: ts.ClassDeclaration) => {
      return cd.name && cd.name.text == symbolName;
    });
  if (hasSymbol) {
    return refactor.fileName;
  }

  // We found the bootstrap variable, now we just need to get where it's imported.
  const exports = refactor.findAstNodes(null, ts.SyntaxKind.ExportDeclaration)
    .map(node => node as ts.ExportDeclaration);

  for (const decl of exports) {
    if (!decl.moduleSpecifier || decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) {
      continue;
    }

    const modulePath = (decl.moduleSpecifier as ts.StringLiteral).text;
    const resolvedModule = ts.resolveModuleName(
      modulePath, refactor.fileName, program.getCompilerOptions(), host);
    if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
      return null;
    }

    const module = resolvedModule.resolvedModule.resolvedFileName;
    if (!decl.exportClause) {
      const moduleRefactor = new TypeScriptFileRefactor(module, host, program);
      const maybeModule = _recursiveSymbolExportLookup(moduleRefactor, symbolName, host, program);
      if (maybeModule) {
        return maybeModule;
      }
      continue;
    }

    const binding = decl.exportClause as ts.NamedExports;
    for (const specifier of binding.elements) {
      if (specifier.name.text == symbolName) {
        // If it's a directory, load its index and recursively lookup.
        if (fs.statSync(module).isDirectory()) {
          const indexModule = join(module, 'index.ts');
          if (fs.existsSync(indexModule)) {
            const indexRefactor = new TypeScriptFileRefactor(indexModule, host, program);
            const maybeModule = _recursiveSymbolExportLookup(
              indexRefactor, symbolName, host, program);
            if (maybeModule) {
              return maybeModule;
            }
          }
        }

        // Create the source and verify that the symbol is at least a class.
        const source = new TypeScriptFileRefactor(module, host, program);
        const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
          .some((cd: ts.ClassDeclaration) => {
            return cd.name && cd.name.text == symbolName;
          });

        if (hasSymbol) {
          return module;
        }
      }
    }
  }

  return null;
}

function _symbolImportLookup(refactor: TypeScriptFileRefactor,
                             symbolName: string,
                             host: ts.CompilerHost,
                             program: ts.Program): string | null {
  // We found the bootstrap variable, now we just need to get where it's imported.
  const imports = refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration)
    .map(node => node as ts.ImportDeclaration);

  for (const decl of imports) {
    if (!decl.importClause || !decl.moduleSpecifier) {
      continue;
    }
    if (decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) {
      continue;
    }

    const resolvedModule = ts.resolveModuleName(
      (decl.moduleSpecifier as ts.StringLiteral).text,
      refactor.fileName, program.getCompilerOptions(), host);
    if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
      return null;
    }

    const module = resolvedModule.resolvedModule.resolvedFileName;
    if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamespaceImport) {
      const binding = decl.importClause.namedBindings as ts.NamespaceImport;
      if (binding.name.text == symbolName) {
        // This is a default export.
        return module;
      }
    } else if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamedImports) {
      const binding = decl.importClause.namedBindings as ts.NamedImports;
      for (const specifier of binding.elements) {
        if (specifier.name.text == symbolName) {
          // Create the source and recursively lookup the import.
          const source = new TypeScriptFileRefactor(module, host, program);
          const maybeModule = _recursiveSymbolExportLookup(source, symbolName, host, program);
          if (maybeModule) {
            return maybeModule;
          }
        }
      }
    }
  }
  return null;
}


export function resolveEntryModuleFromMain(mainPath: string,
                                           host: ts.CompilerHost,
                                           program: ts.Program) {
  const source = new TypeScriptFileRefactor(mainPath, host, program);

  const bootstrap = source.findAstNodes(source.sourceFile, ts.SyntaxKind.CallExpression, true)
    .map(node => node as ts.CallExpression)
    .filter(call => {
      const access = call.expression as ts.PropertyAccessExpression;
      return access.kind == ts.SyntaxKind.PropertyAccessExpression
          && access.name.kind == ts.SyntaxKind.Identifier
          && (access.name.text == 'bootstrapModule'
              || access.name.text == 'bootstrapModuleFactory');
    })
    .map(node => node.arguments[0] as ts.Identifier)
    .filter(node => node.kind == ts.SyntaxKind.Identifier);

  if (bootstrap.length != 1) {
    throw new Error('Tried to find bootstrap code, but could not. Specify either '
      + 'statically analyzable bootstrap code or pass in an entryModule '
      + 'to the plugins options.');
  }
  const bootstrapSymbolName = bootstrap[0].text;
  const module = _symbolImportLookup(source, bootstrapSymbolName, host, program);
  if (module) {
    return `${module.replace(/\.ts$/, '')}#${bootstrapSymbolName}`;
  }

{'fraction_non_alphanumeric': 0.100279927548164, 'fraction_numerical': 0.0008233163181294253, 'mean_word_length': 2.5810731132075473, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6194470', 'n_tokens_mistral': 3252, 'n_tokens_neox': 3195, 'n_words': 828}