diff --git a/.eslintrc b/.eslintrc index d18f7eb828..9c36dc1804 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,6 +6,14 @@ ], "overrides": [ + { + "files": [ + "Libraries/**/*.js", + ], + rules: { + '@react-native-community/no-haste-imports': 2 + } + }, { "files": [ "**/__fixtures__/**/*.js", diff --git a/Libraries/Alert/NativeAlertManager.js b/Libraries/Alert/NativeAlertManager.js index 0a540cac72..16191e7e47 100644 --- a/Libraries/Alert/NativeAlertManager.js +++ b/Libraries/Alert/NativeAlertManager.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export type Buttons = Array<{ text?: string, diff --git a/Libraries/Animated/src/NativeAnimatedModule.js b/Libraries/Animated/src/NativeAnimatedModule.js index b13d6f3319..80ece9d69d 100644 --- a/Libraries/Animated/src/NativeAnimatedModule.js +++ b/Libraries/Animated/src/NativeAnimatedModule.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; type EndResult = {finished: boolean}; type EndCallback = (result: EndResult) => void; diff --git a/Libraries/BatchedBridge/__tests__/MessageQueue-test.js b/Libraries/BatchedBridge/__tests__/MessageQueue-test.js index f8568cae72..0c2db74b52 100644 --- a/Libraries/BatchedBridge/__tests__/MessageQueue-test.js +++ b/Libraries/BatchedBridge/__tests__/MessageQueue-test.js @@ -33,7 +33,7 @@ describe('MessageQueue', function() { beforeEach(function() { jest.resetModules(); MessageQueue = require('../MessageQueue'); - MessageQueueTestModule = require('MessageQueueTestModule'); + MessageQueueTestModule = require('../__mocks__/MessageQueueTestModule'); queue = new MessageQueue(); queue.registerCallableModule( 'MessageQueueTestModule', diff --git a/Libraries/BatchedBridge/__tests__/NativeModules-test.js b/Libraries/BatchedBridge/__tests__/NativeModules-test.js index 6457385f23..3bc364694e 100644 --- a/Libraries/BatchedBridge/__tests__/NativeModules-test.js +++ b/Libraries/BatchedBridge/__tests__/NativeModules-test.js @@ -40,7 +40,7 @@ describe('MessageQueue', function() { beforeEach(function() { jest.resetModules(); - global.__fbBatchedBridgeConfig = require('MessageQueueTestConfig'); + global.__fbBatchedBridgeConfig = require('../__mocks__/MessageQueueTestConfig'); BatchedBridge = require('../BatchedBridge'); NativeModules = require('../NativeModules'); }); diff --git a/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js b/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js index 2e90cdf4f8..b0fe8dd1d7 100644 --- a/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js +++ b/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { +getCurrentBoldTextState: ( diff --git a/Libraries/Components/RefreshControl/PullToRefreshViewNativeViewConfig.js b/Libraries/Components/RefreshControl/PullToRefreshViewNativeViewConfig.js index 27a1071076..f76c8e02dd 100644 --- a/Libraries/Components/RefreshControl/PullToRefreshViewNativeViewConfig.js +++ b/Libraries/Components/RefreshControl/PullToRefreshViewNativeViewConfig.js @@ -10,9 +10,9 @@ 'use strict'; -const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); -const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig'); -const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence'); +const ReactNativeViewConfigRegistry = require('../../Renderer/shims/ReactNativeViewConfigRegistry'); +const ReactNativeViewViewConfig = require('../View/ReactNativeViewViewConfig'); +const verifyComponentAttributeEquivalence = require('../../Utilities/verifyComponentAttributeEquivalence'); const PullToRefreshViewViewConfig = { uiViewClassName: 'PullToRefreshView', @@ -35,8 +35,8 @@ const PullToRefreshViewViewConfig = { validAttributes: { ...ReactNativeViewViewConfig.validAttributes, - tintColor: { process: require('processColor') }, - titleColor: { process: require('processColor') }, + tintColor: { process: require('../../StyleSheet/processColor') }, + titleColor: { process: require('../../StyleSheet/processColor') }, title: true, refreshing: true, onRefresh: true, diff --git a/Libraries/Components/ToastAndroid/NativeToastAndroid.js b/Libraries/Components/ToastAndroid/NativeToastAndroid.js index 89e7443874..079a7d0112 100644 --- a/Libraries/Components/ToastAndroid/NativeToastAndroid.js +++ b/Libraries/Components/ToastAndroid/NativeToastAndroid.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { +getConstants: () => {| diff --git a/Libraries/Image/NativeImageEditor.js b/Libraries/Image/NativeImageEditor.js index 4f4da0b538..e208cb47b6 100644 --- a/Libraries/Image/NativeImageEditor.js +++ b/Libraries/Image/NativeImageEditor.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { +cropImage: ( diff --git a/Libraries/Modal/NativeModalManager.js b/Libraries/Modal/NativeModalManager.js index 28bf3d9a54..d293caf68f 100644 --- a/Libraries/Modal/NativeModalManager.js +++ b/Libraries/Modal/NativeModalManager.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { // RCTEventEmitter diff --git a/Libraries/NativeModules/specs/NativeDialogManagerAndroid.js b/Libraries/NativeModules/specs/NativeDialogManagerAndroid.js index 1ffb71c8ce..d1e1f9a7c8 100644 --- a/Libraries/NativeModules/specs/NativeDialogManagerAndroid.js +++ b/Libraries/NativeModules/specs/NativeDialogManagerAndroid.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry'; /* 'buttonClicked' | 'dismissed' */ type DialogAction = string; diff --git a/Libraries/Network/NativeNetworkingAndroid.js b/Libraries/Network/NativeNetworkingAndroid.js index 6db46cef70..06ccdf29fa 100644 --- a/Libraries/Network/NativeNetworkingAndroid.js +++ b/Libraries/Network/NativeNetworkingAndroid.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; type Header = [string, string]; diff --git a/Libraries/Network/NativeNetworkingIOS.js b/Libraries/Network/NativeNetworkingIOS.js index f61b3520a0..5c685de784 100644 --- a/Libraries/Network/NativeNetworkingIOS.js +++ b/Libraries/Network/NativeNetworkingIOS.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { +sendRequest: ( diff --git a/Libraries/PermissionsAndroid/NativePermissionsAndroid.js b/Libraries/PermissionsAndroid/NativePermissionsAndroid.js index fbac01fc8d..025f9af184 100644 --- a/Libraries/PermissionsAndroid/NativePermissionsAndroid.js +++ b/Libraries/PermissionsAndroid/NativePermissionsAndroid.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; // TODO: Use proper enum types. export type PermissionStatus = string; diff --git a/Libraries/Settings/NativeSettingsManager.js b/Libraries/Settings/NativeSettingsManager.js index fd050c996b..19de825248 100644 --- a/Libraries/Settings/NativeSettingsManager.js +++ b/Libraries/Settings/NativeSettingsManager.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { +getConstants: () => {| diff --git a/Libraries/Utilities/NativeDeviceInfo.js b/Libraries/Utilities/NativeDeviceInfo.js index dd90580654..8e0b785a6f 100644 --- a/Libraries/Utilities/NativeDeviceInfo.js +++ b/Libraries/Utilities/NativeDeviceInfo.js @@ -10,8 +10,8 @@ 'use strict'; -import type {TurboModule} from 'RCTExport'; -import * as TurboModuleRegistry from 'TurboModuleRegistry'; +import type {TurboModule} from '../TurboModule/RCTExport'; +import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; type DisplayMetricsAndroid = {| width: number, diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index 94aeecb539..8e0a72c5ce 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -10,8 +10,10 @@ 'use strict'; +/* eslint-disable @react-native-community/no-haste-imports */ + const invariant = require('invariant'); -const warnOnce = require('warnOnce'); +const warnOnce = require('../Utilities/warnOnce'); // Export React, plus some native additions. module.exports = { diff --git a/ReactAndroid/src/androidTest/js/ScrollViewTestModule.js b/ReactAndroid/src/androidTest/js/ScrollViewTestModule.js index 5da2b7686c..3e09724580 100644 --- a/ReactAndroid/src/androidTest/js/ScrollViewTestModule.js +++ b/ReactAndroid/src/androidTest/js/ScrollViewTestModule.js @@ -25,7 +25,7 @@ const {ScrollListener} = NativeModules; const NUM_ITEMS = 100; -import type {PressEvent} from 'CoreEventTypes'; +import type {PressEvent} from 'react-native/Libraries/Types/CoreEventTypes'; // Shared by integration tests for ScrollView and HorizontalScrollView diff --git a/package.json b/package.json index ec40027e2d..d9bbd3257e 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "devDependencies": { "@babel/core": "^7.0.0", "@babel/generator": "^7.0.0", + "@react-native-community/eslint-plugin": "1.0.0", "@reactions/component": "^2.0.2", "async": "^2.4.0", "babel-eslint": "10.0.1", diff --git a/packages/eslint-config-react-native-community/index.js b/packages/eslint-config-react-native-community/index.js index d1cfc69487..8691452b8f 100644 --- a/packages/eslint-config-react-native-community/index.js +++ b/packages/eslint-config-react-native-community/index.js @@ -22,6 +22,7 @@ module.exports = { 'react', 'react-hooks', 'react-native', + '@react-native-community', 'jest', ], diff --git a/packages/eslint-plugin-react-native-community/README.md b/packages/eslint-plugin-react-native-community/README.md new file mode 100644 index 0000000000..262c7e9217 --- /dev/null +++ b/packages/eslint-plugin-react-native-community/README.md @@ -0,0 +1,21 @@ +# eslint-plugin-react-native-community + +This plugin is intended to be used in `@react-native-community/eslint-plugin`. You probably want to install that package instead. + +## Installation + +``` +yarn add --dev eslint @react-native-community/eslint-plugin +``` + +*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* + +## Usage + +Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json`): + +```json +{ + "plugins": ["@react-native-community"] +} +``` diff --git a/packages/eslint-plugin-react-native-community/index.js b/packages/eslint-plugin-react-native-community/index.js new file mode 100644 index 0000000000..f016505750 --- /dev/null +++ b/packages/eslint-plugin-react-native-community/index.js @@ -0,0 +1,12 @@ +/** + * 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 + */ + +exports.rules = { + 'no-haste-imports': require('./no-haste-imports'), +}; diff --git a/packages/eslint-plugin-react-native-community/no-haste-imports.js b/packages/eslint-plugin-react-native-community/no-haste-imports.js new file mode 100644 index 0000000000..5282c6950e --- /dev/null +++ b/packages/eslint-plugin-react-native-community/no-haste-imports.js @@ -0,0 +1,73 @@ +/** + * 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' + ); +} diff --git a/packages/eslint-plugin-react-native-community/package.json b/packages/eslint-plugin-react-native-community/package.json new file mode 100644 index 0000000000..5a6c28f26d --- /dev/null +++ b/packages/eslint-plugin-react-native-community/package.json @@ -0,0 +1,11 @@ +{ + "name": "@react-native-community/eslint-plugin", + "version": "1.0.0", + "description": "ESLint rules for @react-native-community/eslint-config", + "main": "index.js", + "repository": { + "type": "git", + "url": "git@github.com:facebook/react-native.git" + }, + "license": "MIT" +} diff --git a/yarn.lock b/yarn.lock index 1db6f52c69..37f37f1218 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1160,6 +1160,11 @@ shell-quote "1.6.1" ws "^1.1.0" +"@react-native-community/eslint-plugin@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@react-native-community/eslint-plugin/-/eslint-plugin-1.0.0.tgz#ae9a430f2c5795debca491f15a989fce86ea75a0" + integrity sha512-GLhSN8dRt4lpixPQh+8prSCy6PYk/MT/mvji/ojAd5yshowDo6HFsimCSTD/uWAdjpUq91XK9tVdTNWfGRlKQA== + "@reactions/component@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@reactions/component/-/component-2.0.2.tgz#40f8c1c2c37baabe57a0c944edb9310dc1ec6642"