diff --git a/.eslintrc.js b/.eslintrc.js index 3b5cbfce35c521..b0fe642f250416 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,10 +38,10 @@ module.exports = { { files: ['Libraries/**/*.js'], rules: { - '@react-native-community/no-haste-imports': 2, '@react-native-community/error-subclass-name': 2, '@react-native-community/platform-colors': 2, '@react-native/specs/react-native-modules': 2, + 'lint/no-haste-imports': 2, 'lint/no-react-native-imports': 2, }, }, diff --git a/packages/eslint-plugin-react-native-community/README.md b/packages/eslint-plugin-react-native-community/README.md index 0ae562692f5dd1..6b04171a6a6dff 100644 --- a/packages/eslint-plugin-react-native-community/README.md +++ b/packages/eslint-plugin-react-native-community/README.md @@ -28,10 +28,6 @@ Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json Enforces that error classes ( = classes with PascalCase names ending with `Error`) only extend other error classes, and that regular functions don't have names that could be mistaken for those of error classes. -### `no-haste-imports` - -Disallows Haste module names in `import` statements and `require()` calls. - ### `platform-colors` Enforces that calls to `PlatformColor` and `DynamicColorIOS` are statically analyzable to enable performance optimizations. diff --git a/packages/eslint-plugin-react-native-community/index.js b/packages/eslint-plugin-react-native-community/index.js index 3a1fa9896a49b4..b2649b98bbf729 100644 --- a/packages/eslint-plugin-react-native-community/index.js +++ b/packages/eslint-plugin-react-native-community/index.js @@ -9,6 +9,5 @@ exports.rules = { 'error-subclass-name': require('./error-subclass-name'), - 'no-haste-imports': require('./no-haste-imports'), 'platform-colors': require('./platform-colors'), }; diff --git a/tools/eslint/rules/__tests__/no-haste-imports-test.js b/tools/eslint/rules/__tests__/no-haste-imports-test.js new file mode 100644 index 00000000000000..ac14485c9051d1 --- /dev/null +++ b/tools/eslint/rules/__tests__/no-haste-imports-test.js @@ -0,0 +1,99 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +const rule = require('../no-haste-imports.js'); +const {RuleTester} = require('eslint'); + +const ruleTester = new RuleTester({ + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, +}); + +ruleTester.run('require(...)', rule, { + valid: [ + { + code: `const X = require('x');`, + }, + { + code: `const X = require('./X');`, + }, + { + code: `const Y = require('X/Y');`, + }, + ], + invalid: [ + { + code: `const X = require('X');`, + errors: [{messageId: 'hasteImport', data: {importPath: 'X'}}], + output: null, // Expect no autofix to be suggested. + }, + { + code: `const useX = require('useX');`, + errors: [{messageId: 'hasteImport', data: {importPath: 'useX'}}], + output: null, // Expect no autofix to be suggested. + }, + ], +}); + +ruleTester.run('import(...)', rule, { + valid: [ + { + code: `import('x');`, + }, + { + code: `import('./X');`, + }, + { + code: `import('X/Y');`, + }, + ], + invalid: [ + { + code: `import('X');`, + errors: [{messageId: 'hasteImport', data: {importPath: 'X'}}], + output: null, // Expect no autofix to be suggested. + }, + { + code: `import('useX');`, + errors: [{messageId: 'hasteImport', data: {importPath: 'useX'}}], + output: null, // Expect no autofix to be suggested. + }, + ], +}); + +ruleTester.run("import ... from '...'", rule, { + valid: [ + { + code: `import X from 'x';`, + }, + { + code: `import X from './X';`, + }, + { + code: `import Y from 'X/Y';`, + }, + ], + invalid: [ + { + code: `import X from 'X';`, + errors: [{messageId: 'hasteImport', data: {importPath: 'X'}}], + output: null, // Expect no autofix to be suggested. + }, + { + code: `import useX from 'useX';`, + errors: [{messageId: 'hasteImport', data: {importPath: 'useX'}}], + output: null, // Expect no autofix to be suggested. + }, + ], +}); diff --git a/packages/eslint-plugin-react-native-community/no-haste-imports.js b/tools/eslint/rules/no-haste-imports.js similarity index 56% rename from packages/eslint-plugin-react-native-community/no-haste-imports.js rename to tools/eslint/rules/no-haste-imports.js index 6c5c855a2ab4c6..834ab53e4a28a7 100644 --- a/packages/eslint-plugin-react-native-community/no-haste-imports.js +++ b/tools/eslint/rules/no-haste-imports.js @@ -7,40 +7,60 @@ * @format */ +'use strict'; + module.exports = { meta: { type: 'problem', docs: { - description: - 'disallow Haste module names in import statements and require calls', + description: 'Disallow importing Haste module names', + recommended: true, + }, + messages: { + hasteImport: + "'{{importPath}}' seems to be a Haste module name; use path-based imports intead", }, 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); + if ( + node.callee.type === 'Identifier' && + node.callee.name === 'require' && + node.arguments.length === 1 + ) { + processSource(node.arguments[0]); } }, + ImportExpression(node) { + processSource(node.source); + }, + ImportDeclaration(node) { + processSource(node.source); + }, }; + + function processSource(source) { + if (source.type !== 'Literal' || typeof source.value !== 'string') { + return; + } + const importPath = source.value; + if (!isLikelyHasteModuleName(importPath)) { + return; + } + context.report({ + node: source, + messageId: 'hasteImport', + data: { + importPath, + }, + }); + } }, }; -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 @@ -59,15 +79,3 @@ function isLikelyHasteModuleName(importPath) { /[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' - ); -}