<commit_before>// @flow

import { createRoutes, formatPattern } from "react-router"
import colors from "chalk"

import arrayUnique from "../../_utils/array-unique"

const defaultConsole = console

const flattenRoute = (route) => {
  const root = route.path ? route.path : ""
  let routesUrls = route.path ? [ route.path ] : []

  if (route.childRoutes) {
    route.childRoutes.forEach((route) => {
      routesUrls = [
        ...routesUrls,
        ...flattenRoute(route).map((r) => root + r),
      ]
    })
  }

  return routesUrls
}

// only simple types will be accepted
// array<string | number> | string | number
// other will be skipped
const paramsListFromCollection = (collection) => {
  const params = {}

  collection.forEach((item) => {
    if (!item.head) {
      return
    }

    Object.keys(item.head).forEach((key) => {
      // array<string | number>
      if (
        Array.isArray(item.head[key]) &&
        item.head[key].length &&
        (
          typeof item.head[key][0] === "string" ||
          typeof item.head[key][0] === "number"
        )
      ) {
        const k = key
          // categories => category
          .replace(/ies$/, "y")
          // tags => tag
          .replace(/s$/, "")

        if (!params[k]) {
          params[k] = []
        }
        params[k] = [
          ...params[k],
          ...item.head[key],
        ]
      }

      // string, number
      if (
        typeof item.head[key] === "string" ||
        typeof item.head[key] === "number"
      ) {
        if (!params[key]) {
          params[key] = []
        }
        params[key].push(item.head[key])
      }
    })
  })

  params.splat = collection.map((item) => item.__url)

  return params
}

const createUrlsFromParamsReplacementInUrl = (url, params, console) => {
  // don't compute anything if url doesn't seems to have dynamic parameters
  // react-router url params are like ``:that`` (or splat *)
  if (url.indexOf(":") === -1 && url.indexOf("*") === -1) {
    return [ url ]
  }

  const urls = []

  const possibleErrorEnd = `parameter for path "${ url }"`

  let missingKeys = []
  let nonMissingKeys = []

  Object.keys(params).forEach((paramName) => {
    params[paramName].forEach((paramValue) => {
      const urlParams = { [paramName]: paramValue }
      try {
        const hydratedUrl = formatPattern(url, urlParams)
        urls.push(hydratedUrl)
        nonMissingKeys.push(paramName)
      }
      catch (e) {
        // console.log(paramName, e.message)

        const matches = e.message.match(/Missing \"(.*)\" parameter for path/)
        if (matches && matches[1]) {
          missingKeys.push(matches[1])
        }

        if (
          e.message.indexOf("Missing") === 0 &&
          e.message.lastIndexOf(possibleErrorEnd) ===
            (e.message.length - possibleErrorEnd.length - 1)
        ) {
          throw e
        }
      }
    })
  })

  nonMissingKeys = arrayUnique(nonMissingKeys)
  missingKeys = arrayUnique(missingKeys).filter(
    (key) => nonMissingKeys.indexOf(key) === -1
  )
  if (missingKeys.length) {
    console.log("⚠️ " + colors.red(
      "It looks like some parameters can't be mapped to create routes: ",
      missingKeys.map((key) => ":" + key).join(", "),
    ))
  }

  // @todo improve te algorithm to avoid duplicates,
  // we will probably get better perfs

  return arrayUnique(urls.sort())
}

const hydrateRoutesUrls = (routesUrls, collection, console) => {
  const paramsList = paramsListFromCollection(collection)

  return routesUrls.reduce((acc, url) => {
    return [
      ...acc,
      ...createUrlsFromParamsReplacementInUrl(url, paramsList, console),
    ]
  }, [])
}

export default (
  routes: React$Element,
  collection: PhenomicCollection,
  console: console = defaultConsole,
): Array<string>  => {

  const flattenedRoutes = createRoutes(routes)
    .reduce((acc, r) => [ ...acc, ...flattenRoute(r) ], [])

  if (flattenedRoutes.filter((url) => url.indexOf("*") > -1).length > 1) {
    throw new Error(
      "Phenomic can only handle one splat (*) in react-router routes. \n" +
      "You must use only one splat. If you have a specific need, do not "+
      "hesitate to open an issue at " +
      "https://github.com/MoOx/phenomic/issues/new"
    )
  }

  return hydrateRoutesUrls(flattenedRoutes, collection, console)
}
<commit_msg>Fix latest flow issue due to upgrade/merge<commit_after>// @flow

import { createRoutes, formatPattern } from "react-router"
import colors from "chalk"

import arrayUnique from "../../_utils/array-unique"

const defaultConsole = console

const flattenRoute = (route) => {
  const root = route.path ? route.path : ""
  let routesUrls = route.path ? [ route.path ] : []

  if (route.childRoutes) {
    route.childRoutes.forEach((route) => {
      routesUrls = [
        ...routesUrls,
        ...flattenRoute(route).map((r) => root + r),
      ]
    })
  }

  return routesUrls
}

// only simple types will be accepted
// array<string | number> | string | number
// other will be skipped
const paramsListFromCollection = (collection) => {
  const params = {}

  collection.forEach((item) => {
    if (!item.head) {
      return
    }

    Object.keys(item.head).forEach((key) => {
      // array<string | number>
      if (
        Array.isArray(item.head[key]) &&
        item.head[key].length &&
        (
          typeof item.head[key][0] === "string" ||
          typeof item.head[key][0] === "number"
        )
      ) {
        const k = key
          // categories => category
          .replace(/ies$/, "y")
          // tags => tag
          .replace(/s$/, "")

        if (!params[k]) {
          params[k] = []
        }
        params[k] = [
          ...params[k],
          ...item.head[key],
        ]
      }

      // string, number
      if (
        typeof item.head[key] === "string" ||
        typeof item.head[key] === "number"
      ) {
        if (!params[key]) {
          params[key] = []
        }
        params[key].push(item.head[key])
      }
    })
  })

  params.splat = collection.map((item) => item.__url)

  return params
}

const createUrlsFromParamsReplacementInUrl = (url, params, console) => {
  // don't compute anything if url doesn't seems to have dynamic parameters
  // react-router url params are like ``:that`` (or splat *)
  if (url.indexOf(":") === -1 && url.indexOf("*") === -1) {
    return [ url ]
  }

  const urls = []

  const possibleErrorEnd = `parameter for path "${ url }"`

  let missingKeys = []
  let nonMissingKeys = []

  Object.keys(params).forEach((paramName) => {
    params[paramName].forEach((paramValue) => {
      const urlParams = { [paramName]: paramValue }
      try {
        const hydratedUrl = formatPattern(url, urlParams)
        urls.push(hydratedUrl)
        nonMissingKeys.push(paramName)
      }
      catch (e) {
        // console.log(paramName, e.message)

        const matches = e.message.match(/Missing \"(.*)\" parameter for path/)
        if (matches && matches[1]) {
          missingKeys.push(matches[1])
        }

        if (
          e.message.indexOf("Missing") === 0 &&
          e.message.lastIndexOf(possibleErrorEnd) ===
            (e.message.length - possibleErrorEnd.length - 1)
        ) {
          throw e
        }
      }
    })
  })

  nonMissingKeys = arrayUnique(nonMissingKeys)
  missingKeys = arrayUnique(missingKeys).filter(
    (key) => nonMissingKeys.indexOf(key) === -1
  )
  if (missingKeys.length) {
    console.log("⚠️ " + colors.red(
      "It looks like some parameters can't be mapped to create routes: ",
      missingKeys.map((key) => ":" + key).join(", "),
    ))
  }

  // @todo improve te algorithm to avoid duplicates,
  // we will probably get better perfs

  return arrayUnique(urls.sort())
}

const hydrateRoutesUrls = (routesUrls, collection, console) => {
  const paramsList = paramsListFromCollection(collection)

  return routesUrls.reduce((acc, url) => {
    return [
      ...acc,
      ...createUrlsFromParamsReplacementInUrl(url, paramsList, console),
    ]
  }, [])
}

export default (
  routes: React$Element<any>,
  collection: PhenomicCollection,
  console: typeof console = defaultConsole,
): Array<string>  => {

  const flattenedRoutes = createRoutes(routes)
    .reduce((acc, r) => [ ...acc, ...flattenRoute(r) ], [])

  if (flattenedRoutes.filter((url) => url.indexOf("*") > -1).length > 1) {
    throw new Error(
      "Phenomic can only handle one splat (*) in react-router routes. \n" +
      "You must use only one splat. If you have a specific need, do not "+
      "hesitate to open an issue at " +
      "https://github.com/MoOx/phenomic/issues/new"
    )
  }

  return hydrateRoutesUrls(flattenedRoutes, collection, console)
}

{'fraction_non_alphanumeric': 0.1549650670026343, 'fraction_numerical': 0.0025197571870347038, 'mean_word_length': 2.3063233623627415, 'pattern_counts': {'":': 4, '<': 10, '<?xml version=': 0, '>': 52, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7130945', 'n_tokens_mistral': 2764, 'n_tokens_neox': 2711, 'n_words': 611}