text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.getComponentPropsInterface = exports.getComponentName = exports.updateReduxStore = exports.addReduxStoreToMain = exports.addBrowserRouter = exports.addRoute = exports.addInitialRoutes = exports.isTag = exports.findClosestOpening = exports.findElements = exports.findComponentImportPath = exports.findDefaultClassOrFunction = exports.findDefaultExportIdentifier = exports.findDefaultExportDeclaration = exports.findDefaultExport = exports.findMainRenderStatement = exports.addImport = void 0;
  const ts = require("typescript");
  const find_nodes_1 = require("@nrwl/workspace/src/utilities/typescript/find-nodes");
  const devkit_1 = require("@nrwl/devkit");
  function addImport(source, statement) {
      const allImports = find_nodes_1.findNodes(source, ts.SyntaxKind.ImportDeclaration);
      if (allImports.length > 0) {
          const lastImport = allImports[allImports.length - 1];
          return [
              {
                  type: devkit_1.ChangeType.Insert,
                  index: lastImport.end + 1,
                  text: `\n${statement}\n`,
              },
          ];
      }
      else {
          return [
              {
                  type: devkit_1.ChangeType.Insert,
                  index: 0,
                  text: `\n${statement}\n`,
              },
          ];
      }
  }
  exports.addImport = addImport;
  function findMainRenderStatement(source) {
      // 1. Try to find ReactDOM.render.
      const calls = find_nodes_1.findNodes(source, ts.SyntaxKind.CallExpression);
      for (const expr of calls) {
          const inner = expr.expression;
          if (ts.isPropertyAccessExpression(inner) &&
              /ReactDOM/i.test(inner.expression.getText()) &&
              inner.name.getText() === 'render') {
              return expr;
          }
      }
      // 2. Try to find render from 'react-dom'.
      const imports = find_nodes_1.findNodes(source, ts.SyntaxKind.ImportDeclaration);
      const hasRenderImport = imports.some((i) => i.moduleSpecifier.getText().includes('react-dom') &&
          /\brender\b/.test(i.importClause.namedBindings.getText()));
      if (hasRenderImport) {
          const calls = find_nodes_1.findNodes(source, ts.SyntaxKind.CallExpression);
          for (const expr of calls) {
              if (expr.expression.getText() === 'render') {
                  return expr;
              }
          }
      }
      return null;
  }
  exports.findMainRenderStatement = findMainRenderStatement;
  function findDefaultExport(source) {
      return (findDefaultExportDeclaration(source) || findDefaultClassOrFunction(source));
  }
  exports.findDefaultExport = findDefaultExport;
  function findDefaultExportDeclaration(source) {
      const identifier = findDefaultExportIdentifier(source);
      if (identifier) {
          const variables = find_nodes_1.findNodes(source, ts.SyntaxKind.VariableDeclaration);
          const fns = find_nodes_1.findNodes(source, ts.SyntaxKind.FunctionDeclaration);
          const cls = find_nodes_1.findNodes(source, ts.SyntaxKind.ClassDeclaration);
          const all = [...variables, ...fns, ...cls];
          const exported = all
              .filter((x) => x.name.kind === ts.SyntaxKind.Identifier)
              .find((x) => x.name.text === identifier.text);
          return exported || null;
      }
      else {
          return null;
      }
  }
  exports.findDefaultExportDeclaration = findDefaultExportDeclaration;
  function findDefaultExportIdentifier(source) {
      const exports = find_nodes_1.findNodes(source, ts.SyntaxKind.ExportAssignment);
      const identifier = exports
          .map((x) => x.expression)
          .find((x) => x.kind === ts.SyntaxKind.Identifier);
      return identifier || null;
  }
  exports.findDefaultExportIdentifier = findDefaultExportIdentifier;
  function findDefaultClassOrFunction(source) {
      const fns = find_nodes_1.findNodes(source, ts.SyntaxKind.FunctionDeclaration);
      const cls = find_nodes_1.findNodes(source, ts.SyntaxKind.ClassDeclaration);
      return (fns.find(hasDefaultExportModifier) ||
          cls.find(hasDefaultExportModifier) ||
          null);
  }
  exports.findDefaultClassOrFunction = findDefaultClassOrFunction;
  function hasDefaultExportModifier(x) {
      return (x.modifiers &&
          x.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
          x.modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword));
  }
  function findComponentImportPath(componentName, source) {
      const allImports = find_nodes_1.findNodes(source, ts.SyntaxKind.ImportDeclaration);
      const matching = allImports.filter((i) => {
          return (i.importClause &&
              i.importClause.name &&
              i.importClause.name.getText() === componentName);
      });
      if (matching.length === 0) {
          return null;
      }
      const appImport = matching[0];
      return appImport.moduleSpecifier.getText().replace(/['"]/g, '');
  }
  exports.findComponentImportPath = findComponentImportPath;
  function findElements(source, tagName) {
      const nodes = find_nodes_1.findNodes(source, [
          ts.SyntaxKind.JsxSelfClosingElement,
          ts.SyntaxKind.JsxOpeningElement,
      ]);
      return nodes.filter((node) => isTag(tagName, node));
  }
  exports.findElements = findElements;
  function findClosestOpening(tagName, node) {
      if (!node) {
          return null;
      }
      if (isTag(tagName, node)) {
          return node;
      }
      else {
          return findClosestOpening(tagName, node.parent);
      }
  }
  exports.findClosestOpening = findClosestOpening;
  function isTag(tagName, node) {
      if (ts.isJsxOpeningLikeElement(node)) {
          return (node.tagName.kind === ts.SyntaxKind.Identifier &&
              node.tagName.text === tagName);
      }
      if (ts.isJsxElement(node) && node.openingElement) {
          return (node.openingElement.tagName.kind === ts.SyntaxKind.Identifier &&
              node.openingElement.tagName.getText() === tagName);
      }
      return false;
  }
  exports.isTag = isTag;
  function addInitialRoutes(sourcePath, source) {
      const jsxClosingElements = find_nodes_1.findNodes(source, [
          ts.SyntaxKind.JsxClosingElement,
          ts.SyntaxKind.JsxClosingFragment,
      ]);
      const outerMostJsxClosing = jsxClosingElements[jsxClosingElements.length - 1];
      if (!outerMostJsxClosing) {
          devkit_1.logger.warn(`Could not find JSX elements in ${sourcePath}; Skipping insert routes`);
          return [];
      }
      const insertRoutes = {
          type: devkit_1.ChangeType.Insert,
          index: outerMostJsxClosing.getStart(),
          text: `
      {/* START: routes */}
      {/* These routes and navigation have been generated for you */}
      {/* Feel free to move and update them to fit your needs */}
      <br/>
      <hr/>
      <br/>
      <div role="navigation">
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/page-2">Page 2</Link></li>
        </ul>
      </div>
      <Route
        path="/"
        exact
        render={() => (
          <div>This is the generated root route. <Link to="/page-2">Click here for page 2.</Link></div>
        )}
      />
      <Route
        path="/page-2"
        exact
        render={() => (
          <div><Link to="/">Click here to go back to root page.</Link></div>
        )}
      />
      {/* END: routes */}
      `,
      };
      return [
          ...addImport(source, `import { Route, Link } from 'react-router-dom';`),
          insertRoutes,
      ];
  }
  exports.addInitialRoutes = addInitialRoutes;
  function addRoute(sourcePath, source, options) {
      const routes = findElements(source, 'Route');
      const links = findElements(source, 'Link');
      if (routes.length === 0) {
          devkit_1.logger.warn(`Could not find <Route/> components in ${sourcePath}; Skipping add route`);
          return [];
      }
      else {
          const changes = [];
          const firstRoute = routes[0];
          const firstLink = links[0];
          changes.push(...addImport(source, `import { ${options.componentName} } from '${options.moduleName}';`));
          changes.push({
              type: devkit_1.ChangeType.Insert,
              index: firstRoute.getEnd(),
              text: `<Route path="${options.routePath}" component={${options.componentName}} />`,
          });
          if (firstLink) {
              const parentLi = findClosestOpening('li', firstLink);
              if (parentLi) {
                  changes.push({
                      type: devkit_1.ChangeType.Insert,
                      index: parentLi.getEnd(),
                      text: `<li><Link to="${options.routePath}">${options.componentName}</Link></li>`,
                  });
              }
              else {
                  changes.push({
                      type: devkit_1.ChangeType.Insert,
                      index: firstLink.parent.getEnd(),
                      text: `<Link to="${options.routePath}">${options.componentName}</Link>`,
                  });
              }
          }
          return changes;
      }
  }
  exports.addRoute = addRoute;
  function addBrowserRouter(sourcePath, source) {
      const app = findElements(source, 'App')[0];
      if (app) {
          return [
              ...addImport(source, `import { BrowserRouter } from 'react-router-dom';`),
              {
                  type: devkit_1.ChangeType.Insert,
                  index: app.getStart(),
                  text: `<BrowserRouter>`,
              },
              {
                  type: devkit_1.ChangeType.Insert,
                  index: app.getEnd(),
                  text: `</BrowserRouter>`,
              },
          ];
      }
      else {
          devkit_1.logger.warn(`Could not find App component in ${sourcePath}; Skipping add <BrowserRouter>`);
          return [];
      }
  }
  exports.addBrowserRouter = addBrowserRouter;
  function addReduxStoreToMain(sourcePath, source) {
      const renderStmt = findMainRenderStatement(source);
      if (!renderStmt) {
          devkit_1.logger.warn(`Could not find ReactDOM.render in ${sourcePath}`);
          return [];
      }
      const jsx = renderStmt.arguments[0];
      return [
          ...addImport(source, `import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
  import { Provider } from 'react-redux';`),
          {
              type: devkit_1.ChangeType.Insert,
              index: renderStmt.getStart(),
              text: `
  const store = configureStore({
    reducer: {},
    // Additional middleware can be passed to this array
    middleware: [...getDefaultMiddleware()],
    devTools: process.env.NODE_ENV !== 'production',
    // Optional Redux store enhancers
    enhancers: [],
  });
  
  `,
          },
          {
              type: devkit_1.ChangeType.Insert,
              index: jsx.getStart(),
              text: `<Provider store={store}>`,
          },
          {
              type: devkit_1.ChangeType.Insert,
              index: jsx.getEnd(),
              text: `</Provider>`,
          },
      ];
  }
  exports.addReduxStoreToMain = addReduxStoreToMain;
  function updateReduxStore(sourcePath, source, feature) {
      const calls = find_nodes_1.findNodes(source, ts.SyntaxKind.CallExpression);
      let reducerDescriptor;
      // Look for configureStore call
      for (const expr of calls) {
          if (!expr.expression.getText().includes('configureStore')) {
              continue;
          }
          const arg = expr.arguments[0];
          if (ts.isObjectLiteralExpression(arg)) {
              let found;
              for (const prop of arg.properties) {
                  if (ts.isPropertyAssignment(prop) &&
                      prop.name.getText() === 'reducer' &&
                      ts.isObjectLiteralExpression(prop.initializer)) {
                      found = prop.initializer;
                      break;
                  }
              }
              if (found) {
                  reducerDescriptor = found;
                  break;
              }
          }
      }
      // Look for combineReducer call
      if (!reducerDescriptor) {
          for (const expr of calls) {
              if (!expr.expression.getText().includes('combineReducer')) {
                  continue;
              }
              const arg = expr.arguments[0];
              if (ts.isObjectLiteralExpression(arg)) {
                  reducerDescriptor = arg;
                  break;
              }
          }
      }
      if (!reducerDescriptor) {
          devkit_1.logger.warn(`Could not find configureStore/combineReducer call in ${sourcePath}`);
          return [];
      }
      return [
          ...addImport(source, `import { ${feature.keyName}, ${feature.reducerName} } from '${feature.modulePath}';`),
          {
              type: devkit_1.ChangeType.Insert,
              index: reducerDescriptor.getStart() + 1,
              text: `[${feature.keyName}]: ${feature.reducerName}${reducerDescriptor.properties.length > 0 ? ',' : ''}`,
          },
      ];
  }
  exports.updateReduxStore = updateReduxStore;
  function getComponentName(sourceFile) {
      const defaultExport = findDefaultExport(sourceFile);
      if (!(defaultExport &&
          find_nodes_1.findNodes(defaultExport, ts.SyntaxKind.JsxElement).length > 0)) {
          return null;
      }
      return defaultExport;
  }
  exports.getComponentName = getComponentName;
  function getComponentPropsInterface(sourceFile) {
      const cmpDeclaration = getComponentName(sourceFile);
      let propsTypeName = null;
      if (ts.isFunctionDeclaration(cmpDeclaration)) {
          const propsParam = cmpDeclaration.parameters.find((x) => ts.isParameter(x) && x.name.text === 'props');
          if (propsParam && propsParam.type) {
              propsTypeName = propsParam.type
                  .typeName.text;
          }
      }
      else if (cmpDeclaration.initializer &&
          ts.isArrowFunction(cmpDeclaration.initializer)) {
          const arrowFn = cmpDeclaration
              .initializer;
          const propsParam = arrowFn.parameters.find((x) => ts.isParameter(x) && x.name.text === 'props');
          if (propsParam && propsParam.type) {
              propsTypeName = propsParam.type
                  .typeName.text;
          }
      }
      else if (
      // do we have a class component extending from React.Component
      ts.isClassDeclaration(cmpDeclaration) &&
          cmpDeclaration.heritageClauses &&
          cmpDeclaration.heritageClauses.length > 0) {
          const heritageClause = cmpDeclaration.heritageClauses[0];
          if (heritageClause) {
              const propsTypeExpression = heritageClause.types.find((x) => {
                  const name = x.expression.escapedText ||
                      x.expression.name.text;
                  return name === 'Component' || name === 'PureComponent';
              });
              if (propsTypeExpression && propsTypeExpression.typeArguments) {
                  propsTypeName = propsTypeExpression
                      .typeArguments[0].typeName.getText();
              }
          }
      }
      else {
          return null;
      }
      if (propsTypeName) {
          return find_nodes_1.findNodes(sourceFile, ts.SyntaxKind.InterfaceDeclaration).find((x) => {
              return x.name.getText() === propsTypeName;
          });
      }
      else {
          return null;
      }
  }
  exports.getComponentPropsInterface = getComponentPropsInterface;
  //# sourceMappingURL=ast-utils.js.map