Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lint] Add a lint rule to disallow Haste imports #25058

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Libraries/Animated/src/NativeAnimatedModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Libraries/BatchedBridge/__tests__/MessageQueue-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion Libraries/BatchedBridge/__tests__/NativeModules-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ActivityIndicatorViewViewConfig = {
uiViewClassName: 'RCTActivityIndicatorView',
Expand All @@ -30,7 +30,7 @@ const ActivityIndicatorViewViewConfig = {
...ReactNativeViewViewConfig.validAttributes,
hidesWhenStopped: true,
animating: true,
color: { process: require('processColor') },
color: { process: require('../../StyleSheet/processColor') },
size: true,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Components/Slider/SliderNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ type Options = {

type SliderType = CodegenNativeComponent<'Slider', NativeProps, Options>;

module.exports = ((require('SliderNativeViewConfig'): any): SliderType);
module.exports = ((require('./SliderNativeViewConfig'): any): SliderType);
20 changes: 10 additions & 10 deletions Libraries/Components/Slider/SliderNativeViewConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 SliderViewConfig = {
uiViewClassName: 'RCTSlider',
Expand Down Expand Up @@ -48,17 +48,17 @@ const SliderViewConfig = {
...ReactNativeViewViewConfig.validAttributes,
disabled: true,
enabled: true,
maximumTrackImage: { process: require('resolveAssetSource') },
maximumTrackTintColor: { process: require('processColor') },
maximumTrackImage: { process: require('../../Image/resolveAssetSource') },
maximumTrackTintColor: { process: require('../../StyleSheet/processColor') },
maximumValue: true,
minimumTrackImage: { process: require('resolveAssetSource') },
minimumTrackTintColor: { process: require('processColor') },
minimumTrackImage: { process: require('../../Image/resolveAssetSource') },
minimumTrackTintColor: { process: require('../../StyleSheet/processColor') },
minimumValue: true,
step: true,
testID: true,
thumbImage: { process: require('resolveAssetSource') },
thumbTintColor: { process: require('processColor') },
trackImage: { process: require('resolveAssetSource') },
thumbImage: { process: require('../../Image/resolveAssetSource') },
thumbTintColor: { process: require('../../StyleSheet/processColor') },
trackImage: { process: require('../../Image/resolveAssetSource') },
value: true,
onChange: true,
onValueChange: true,
Expand Down
4 changes: 2 additions & 2 deletions Libraries/NativeModules/specs/NativeDialogManagerAndroid.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion Libraries/react-native/react-native-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
'use strict';

const invariant = require('invariant');
const warnOnce = require('warnOnce');
const warnOnce = require('../Utilities/warnOnce');

// Export React, plus some native additions.
/* eslint-disable @react-native-community/no-haste-imports */
module.exports = {
// Components
get AccessibilityInfo() {
Expand Down
2 changes: 1 addition & 1 deletion ReactAndroid/src/androidTest/js/ScrollViewTestModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"@react-native-community/cli": "2.0.0-alpha.20",
"@react-native-community/cli-platform-android": "2.0.0-alpha.20",
"@react-native-community/cli-platform-ios": "2.0.0-alpha.20",
"@react-native-community/eslint-plugin": "file:./packages/eslint-plugin-react-native-community",
"abort-controller": "^3.0.0",
"art": "^0.10.0",
"base64-js": "^1.1.2",
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-config-react-native-community/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
'react',
'react-hooks',
'react-native',
'@react-native-community',
'jest',
],

Expand Down Expand Up @@ -303,5 +304,7 @@ module.exports = {
'jest/no-focused-tests': 1,
'jest/no-identical-title': 1,
'jest/valid-expect': 1,

'@react-native-community/no-haste-imports': 2,
},
};
1 change: 1 addition & 0 deletions packages/eslint-config-react-native-community/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"url": "git@github.com:facebook/react-native.git"
},
"dependencies": {
"@react-native-community/eslint-plugin": "file:../eslint-plugin-react-native-community",
"@typescript-eslint/eslint-plugin": "^1.5.0",
"@typescript-eslint/parser": "^1.5.0",
"babel-eslint": "10.0.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-config-react-native-community/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
lodash "^4.17.11"
to-fast-properties "^2.0.0"

"@react-native-community/eslint-plugin@file:../eslint-plugin-react-native-community":
version "1.0.0"

"@typescript-eslint/eslint-plugin@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.5.0.tgz#85c509bcfc2eb35f37958fa677379c80b7a8f66f"
Expand Down
21 changes: 21 additions & 0 deletions packages/eslint-plugin-react-native-community/README.md
Original file line number Diff line number Diff line change
@@ -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"]
}
```
3 changes: 3 additions & 0 deletions packages/eslint-plugin-react-native-community/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
exports.rules = {
'no-haste-imports': require('./no-haste-imports'),
};
64 changes: 64 additions & 0 deletions packages/eslint-plugin-react-native-community/no-haste-imports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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'
);
}
11 changes: 11 additions & 0 deletions packages/eslint-plugin-react-native-community/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 3 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,9 @@
shell-quote "1.6.1"
ws "^1.1.0"

"@react-native-community/eslint-plugin@file:./packages/eslint-plugin-react-native-community":
version "1.0.0"

"@reactions/component@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@reactions/component/-/component-2.0.2.tgz#40f8c1c2c37baabe57a0c944edb9310dc1ec6642"
Expand Down