<commit_before>const allowedImportPath = ['cerebral/proxy']
const allowedImportVariables = [
  'props',
  'state',
  'signals',
  'sequences',
  'computed',
  'moduleState',
  'moduleComputed',
  'moduleSignals',
]

function isDirectImport(location) {
  return allowedImportPath.indexOf(location.toLowerCase()) >= 0
}

function isIndirectImport(location) {
  return location.includes('cerebral.proxy')
}

function isAllowedImport(importName) {
  return allowedImportVariables.indexOf(importName) >= 0
}

function isPlainPropertyAccess(t, property, computed) {
  return (
    (t.isIdentifier(property) && computed === false) ||
    (t.isLiteral(property) && property.value)
  )
}

export default function({ types: t }) {
  // Tests if the `name` is a valid import variable
  // or if it's a binding to a valid one.
  // Will return the name of the used variabel or null
  // if nothing was found
  function getUsedVariable(scope, name) {
    const binding = scope.getBinding(name)
    if (!binding) return {}
    if (binding.path.getData('cerebral_proxy_tagged')) {
      return {
        tagged: true,
        name,
      }
    } else if (
      t.isVariableDeclarator(binding.path.node) &&
      binding.path.node.init
    ) {
      return getUsedVariable(binding.path.scope, binding.path.node.init.name)
    }

    return {
      tagged: false,
      name: binding.kind === 'module' ? name : null,
    }
  }

  return {
    pre() {
      // Used to track renaming imports in local file
      // eg. import { state as s } from 'cerebral/proxies';
      this.importedVariable = new Set()
      this.namespace = null
    },
    visitor: {
      ImportDeclaration(path) {
        const {
          node: {
            source: { value: importLocation },
          },
        } = path

        // The plan:
        //
        // 1) When import of cerebral/proxy found (directImport)
        //    a. Trigger conversion to tags
        //    b. Rewrite import to cerebral/tags

        // 2) When import containing cerebral.proxy in the name (indirectImport)
        //    a. Trigger conversion to tags

        const directImport = isDirectImport(importLocation) // 1)
        const indirectImport = isIndirectImport(importLocation) // 2)
        const { specifiers } = path.node

        if (directImport || indirectImport) {
          // 1,2) a. Trigger conversion to tags

          // Complain about default import
          if (specifiers.some((item) => t.isImportDefaultSpecifier(item))) {
            throw path.buildCodeFrameError(`Default import is not allowed`)
          }

          if (
            specifiers.length === 1 &&
            t.isImportNamespaceSpecifier(specifiers[0])
          ) {
            this.importedVariable = new Set(allowedImportVariables)
            this.namespace = specifiers[0].local.name
          } else {
            // Verify that all imports are allowed and track the localName
            for (const {
              imported: { name: importName },
              local: { name: localName },
            } of path.node.specifiers) {
              if (isAllowedImport(importName)) {
                this.importedVariable.add(localName)
              } else if (directImport) {
                // Only complain about unkown imports
                // if it is a directImport
                throw path.buildCodeFrameError(
                  `"${importName}" is not a valid import`
                )
              }
            }
          }

          // 1) b. Rewrite import to cerebral/tags
          if (directImport) {
            // Change import to the 'cerebral/tags'
            path.node.source.value = 'cerebral/tags'

            // Remove "path" from imports
            path.node.specifiers = specifiers.filter(
              (item) =>
                t.isImportNamespaceSpecifier(item) ||
                (item.imported.name !== 'statePath' &&
                  item.imported.name !== 'computedPath')
            )

            // remove if there aren't any specifiers left
            // This could happen if only `path` was used.
            if (path.node.specifiers.length === 0) {
              path.remove()
            }
          }
        }
      },
      MemberExpression(path) {
        // Always use the innermost MemberExpression
        // and don't process taggedTemplateExpressions
        if (
          !t.isIdentifier(path.node.object) ||
          t.isTaggedTemplateExpression(path.parent)
        ) {
          return
        }

        const objectName = path.node.object.name
        const { tagged, name: tagRoot } = getUsedVariable(
          path.scope,
          objectName
        )

        // Contains the target of the taggedTemplate
        // either e.g. `state` or proxies.state
        let targetExpression
        let tagName = tagRoot

        // Is tagRoot a reference to the namespace e.g. proxies.state
        if (this.namespace && tagRoot === this.namespace) {
          tagName = path.node.property.name

          // Show compile time warning for
          // unkown properties
          if (!this.importedVariable.has(tagName)) {
            throw path
              .get('property')
              .buildCodeFrameError(`"${tagName}" is not a valid tag`)
          }

          targetExpression = t.memberExpression(
            t.identifier(objectName),
            t.identifier(tagName)
          )
          path = path.parentPath
        } else {
          // if this variable was tagged before
          // we don't want to bail here
          if (!tagged && !this.importedVariable.has(tagRoot)) {
            return
          }
          targetExpression = t.identifier(tagRoot)
        }

        let quasi = []
        let quasis = [quasi]
        let expressions = []
        let prevWasExpression = false

        let rootMemberExpression
        let currentMember = path

        // Iterate trough all parents
        while (t.isMemberExpression(currentMember)) {
          const {
            node: { property, computed },
          } = currentMember

          // Plain id like state.a[1].b['test']
          if (isPlainPropertyAccess(t, property, computed)) {
            const value = t.isLiteral(property) ? property.value : property.name
            quasi.push(
              (quasi.length !== 0 || prevWasExpression ? '.' : '') + value
            )
            prevWasExpression = false
            // Nested expressions like state.a[state.b]
          } else {
            quasi.push('.')
            expressions.push(property)
            quasi = []<commit_msg>refactor(proxy): fix handling of correct tags in babel plugin<commit_after>const allowedImportPath = ['cerebral/proxy']
const allowedImportVariables = [
  'path',
  'props',
  'state',
  'sequences',
  'computed',
  'moduleState',
  'moduleComputed',
  'moduleSequences',
]

function isDirectImport(location) {
  return allowedImportPath.indexOf(location.toLowerCase()) >= 0
}

function isIndirectImport(location) {
  return location.includes('cerebral.proxy')
}

function isAllowedImport(importName) {
  return allowedImportVariables.indexOf(importName) >= 0
}

function isPlainPropertyAccess(t, property, computed) {
  return (
    (t.isIdentifier(property) && computed === false) ||
    (t.isLiteral(property) && property.value)
  )
}

export default function({ types: t }) {
  // Tests if the `name` is a valid import variable
  // or if it's a binding to a valid one.
  // Will return the name of the used variabel or null
  // if nothing was found
  function getUsedVariable(scope, name) {
    const binding = scope.getBinding(name)
    if (!binding) return {}
    if (binding.path.getData('cerebral_proxy_tagged')) {
      return {
        tagged: true,
        name,
      }
    } else if (
      t.isVariableDeclarator(binding.path.node) &&
      binding.path.node.init
    ) {
      return getUsedVariable(binding.path.scope, binding.path.node.init.name)
    }

    return {
      tagged: false,
      name: binding.kind === 'module' ? name : null,
    }
  }

  return {
    pre() {
      // Used to track renaming imports in local file
      // eg. import { state as s } from 'cerebral/proxies';
      this.importedVariable = new Set()
      this.namespace = null
    },
    visitor: {
      ImportDeclaration(path) {
        const { node: { source: { value: importLocation } } } = path

        // The plan:
        //
        // 1) When import of cerebral/proxy found (directImport)
        //    a. Trigger conversion to tags
        //    b. Rewrite import to cerebral/tags

        // 2) When import containing cerebral.proxy in the name (indirectImport)
        //    a. Trigger conversion to tags

        const directImport = isDirectImport(importLocation) // 1)
        const indirectImport = isIndirectImport(importLocation) // 2)
        const { specifiers } = path.node

        if (directImport || indirectImport) {
          // 1,2) a. Trigger conversion to tags

          // Complain about default import
          if (specifiers.some((item) => t.isImportDefaultSpecifier(item))) {
            throw path.buildCodeFrameError(`Default import is not allowed`)
          }

          if (
            specifiers.length === 1 &&
            t.isImportNamespaceSpecifier(specifiers[0])
          ) {
            this.importedVariable = new Set(allowedImportVariables)
            this.namespace = specifiers[0].local.name
          } else {
            // Verify that all imports are allowed and track the localName
            for (const {
              imported: { name: importName },
              local: { name: localName },
            } of path.node.specifiers) {
              if (isAllowedImport(importName)) {
                this.importedVariable.add(localName)
              } else if (directImport) {
                // Only complain about unkown imports
                // if it is a directImport
                throw path.buildCodeFrameError(
                  `"${importName}" is not a valid import`
                )
              }
            }
          }

          // 1) b. Rewrite import to cerebral/tags
          if (directImport) {
            // Change import to the 'cerebral/tags'
            path.node.source.value = 'cerebral/tags'

            // Remove "path" from imports
            path.node.specifiers = specifiers.filter(
              (item) =>
                t.isImportNamespaceSpecifier(item) ||
                (item.imported.name !== 'statePath' &&
                  item.imported.name !== 'computedPath')
            )

            // remove if there aren't any specifiers left
            // This could happen if only `path` was used.
            if (path.node.specifiers.length === 0) {
              path.remove()
            }
          }
        }
      },
      MemberExpression(path) {
        // Always use the innermost MemberExpression
        // and don't process taggedTemplateExpressions
        if (
          !t.isIdentifier(path.node.object) ||
          t.isTaggedTemplateExpression(path.parent)
        ) {
          return
        }

        const objectName = path.node.object.name
        const { tagged, name: tagRoot } = getUsedVariable(
          path.scope,
          objectName
        )

        // Contains the target of the taggedTemplate
        // either e.g. `state` or proxies.state
        let targetExpression
        let tagName = tagRoot

        // Is tagRoot a reference to the namespace e.g. proxies.state
        if (this.namespace && tagRoot === this.namespace) {
          tagName = path.node.property.name

          // Show compile time warning for
          // unkown properties
          if (!this.importedVariable.has(tagName)) {
            throw path
              .get('property')
              .buildCodeFrameError(`"${tagName}" is not a valid tag`)
          }

          targetExpression = t.memberExpression(
            t.identifier(objectName),
            t.identifier(tagName)
          )
          path = path.parentPath
        } else {
          // if this variable was tagged before
          // we don't want to bail here
          if (!tagged && !this.importedVariable.has(tagRoot)) {
            return
          }
          targetExpression = t.identifier(tagRoot)
        }

        let quasi = []
        let quasis = [quasi]
        let expressions = []
        let prevWasExpression = false

        let rootMemberExpression
        let currentMember = path

        // Iterate trough all parents
        while (t.isMemberExpression(currentMember)) {
          const { node: { property, computed } } = currentMember

          // Plain id like state.a[1].b['test']
          if (isPlainPropertyAccess(t, property, computed)) {
            const value = t.isLiteral(property) ? property.value : property.name
            quasi.push(
              (quasi.length !== 0 || prevWasExpression ? '.' : '') + value
            )
            prevWasExpression = false
            // Nested expressions like state.a[state.b]
          } else {
            quasi.push('.')
            expressions.push(property)
            quasi = []
{'fraction_non_alphanumeric': 0.10243531202435312, 'fraction_numerical': 0.00228310502283105, 'mean_word_length': 1.7647801388596676, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, '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': '2008491', 'n_tokens_mistral': 3569, 'n_tokens_neox': 3458, 'n_words': 912}