Skip to content

Commit 3f00447

Browse files
committed
[lint] Add a lint rule to disallow Haste imports
This is an ESLint plugin that infers whether an import looks like a Haste module name. To keep the linter fast and simple, it does not look in the Haste map. Instead, it looks for uppercase characters in single-name import paths, since npm has disallowed uppercase letters in package names for a long time. There are some false negatives (e.g. "merge" is a Haste module and this linter rule would not pick it up) but those are about 1.1% of the module names in the RN repo, and unit tests and integration tests will fail anyway once Haste is turned off. Test Plan: Run `yarn lint` as a sanity check. Modify an import statement to use a Haste module name and verify the linter reports an error. Do the same with a require call.
1 parent bf43158 commit 3f00447

File tree

8 files changed

+108
-1
lines changed

8 files changed

+108
-1
lines changed

Libraries/react-native/react-native-implementation.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
'use strict';
1212

1313
const invariant = require('invariant');
14-
const warnOnce = require('warnOnce');
14+
const warnOnce = require('../Utilities/warnOnce');
1515

1616
// Export React, plus some native additions.
17+
/* eslint-disable @react-native-community/no-haste-imports */
1718
module.exports = {
1819
// Components
1920
get AccessibilityInfo() {

packages/eslint-config-react-native-community/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module.exports = {
2222
'react',
2323
'react-hooks',
2424
'react-native',
25+
'@react-native-community',
2526
'jest',
2627
],
2728

@@ -303,5 +304,7 @@ module.exports = {
303304
'jest/no-focused-tests': 1,
304305
'jest/no-identical-title': 1,
305306
'jest/valid-expect': 1,
307+
308+
'@react-native-community/no-haste-imports': 2,
306309
},
307310
};

packages/eslint-config-react-native-community/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"url": "git@github.com:facebook/react-native.git"
99
},
1010
"dependencies": {
11+
"@react-native-community/eslint-plugin": "file:../eslint-plugin-react-native-community",
1112
"@typescript-eslint/eslint-plugin": "^1.5.0",
1213
"@typescript-eslint/parser": "^1.5.0",
1314
"babel-eslint": "10.0.1",

packages/eslint-config-react-native-community/yarn.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@
9090
lodash "^4.17.11"
9191
to-fast-properties "^2.0.0"
9292

93+
"@react-native-community/eslint-plugin@file:../eslint-plugin-react-native-community":
94+
version "1.0.0"
95+
9396
"@typescript-eslint/eslint-plugin@^1.5.0":
9497
version "1.5.0"
9598
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.5.0.tgz#85c509bcfc2eb35f37958fa677379c80b7a8f66f"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# eslint-plugin-react-native-community
2+
3+
This plugin is intended to be used in `@react-native-community/eslint-plugin`. You probably want to install that package instead.
4+
5+
## Installation
6+
7+
```
8+
yarn add --dev eslint @react-native-community/eslint-plugin
9+
```
10+
11+
*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like*
12+
13+
## Usage
14+
15+
Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json`):
16+
17+
```json
18+
{
19+
"plugins": ["@react-native-community"]
20+
}
21+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports.rules = {
2+
'no-haste-imports': require('./no-haste-imports'),
3+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module.exports = {
2+
meta: {
3+
type: 'problem',
4+
docs: {
5+
description:
6+
'disallow Haste module names in import statements and require calls',
7+
},
8+
schema: [],
9+
},
10+
11+
create(context) {
12+
return {
13+
ImportDeclaration(node) {
14+
checkImportForHaste(context, node.source.value, node.source);
15+
},
16+
CallExpression(node) {
17+
if (isStaticRequireCall(node)) {
18+
const [firstArgument] = node.arguments;
19+
checkImportForHaste(context, firstArgument.value, firstArgument);
20+
}
21+
},
22+
};
23+
},
24+
};
25+
26+
function checkImportForHaste(context, importPath, node) {
27+
if (isLikelyHasteModuleName(importPath)) {
28+
context.report({
29+
node,
30+
message: `"${importPath}" appears to be a Haste module name. Use path-based imports instead.`,
31+
});
32+
}
33+
}
34+
35+
function isLikelyHasteModuleName(importPath) {
36+
// Our heuristic assumes an import path is a Haste module name if it is not a
37+
// path and doesn't appear to be an npm package. For several years, npm has
38+
// disallowed uppercase characters in package names.
39+
//
40+
// This heuristic has a ~1% false negative rate for the filenames in React
41+
// Native, which is acceptable since the linter will not complain wrongly and
42+
// the rate is so low. False negatives that slip through will be caught by
43+
// tests with Haste disabled.
44+
return (
45+
// Exclude relative paths
46+
!importPath.startsWith('.') &&
47+
// Exclude package-internal paths and scoped packages
48+
!importPath.includes('/') &&
49+
// Include camelCase and UpperCamelCase
50+
/[A-Z]/.test(importPath)
51+
);
52+
}
53+
54+
function isStaticRequireCall(node) {
55+
return (
56+
node &&
57+
node.callee &&
58+
node.callee.type === 'Identifier' &&
59+
node.callee.name === 'require' &&
60+
node.arguments.length === 1 &&
61+
node.arguments[0].type === 'Literal' &&
62+
typeof node.arguments[0].value === 'string'
63+
);
64+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@react-native-community/eslint-plugin",
3+
"version": "1.0.0",
4+
"description": "ESLint rules for @react-native-community/eslint-config",
5+
"main": "index.js",
6+
"repository": {
7+
"type": "git",
8+
"url": "git@github.com:facebook/react-native.git"
9+
},
10+
"license": "MIT"
11+
}

0 commit comments

Comments
 (0)