/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'disallow Haste module names in import statements and require calls',
    },
    schema: [],
  },

  create(context) {
    return {
      ImportDeclaration(node) {
        checkImportForHaste(context, node.source.value, node.source);
      },
      CallExpression(node) {
        if (isStaticRequireCall(node)) {
          const [firstArgument] = node.arguments;
          checkImportForHaste(context, firstArgument.value, firstArgument);
        }
      },
    };
  },
};

function checkImportForHaste(context, importPath, node) {
  if (isLikelyHasteModuleName(importPath)) {
    context.report({
      node,
      message: `"${importPath}" appears to be a Haste module name. Use path-based imports instead.`,
    });
  }
}

function isLikelyHasteModuleName(importPath) {
  // Our heuristic assumes an import path is a Haste module name if it is not a
  // path and doesn't appear to be an npm package. For several years, npm has
  // disallowed uppercase characters in package names.
  //
  // This heuristic has a ~1% false negative rate for the filenames in React
  // Native, which is acceptable since the linter will not complain wrongly and
  // the rate is so low. False negatives that slip through will be caught by
  // tests with Haste disabled.
  return (
    // Exclude relative paths
    !importPath.startsWith('.') &&
    // Exclude package-internal paths and scoped packages
    !importPath.includes('/') &&
    // Include camelCase and UpperCamelCase
    /[A-Z]/.test(importPath)
  );
}

function isStaticRequireCall(node) {
  return (
    node &&
    node.callee &&
    node.callee.type === 'Identifier' &&
    node.callee.name === 'require' &&
    node.arguments.length === 1 &&
    node.arguments[0].type === 'Literal' &&
    typeof node.arguments[0].value === 'string'
  );
}
