Skip to content

Commit

Permalink
[lint] Add a lint rule to disallow Haste imports
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ide committed May 27, 2019
1 parent bf43158 commit 3f00447
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 1 deletion.
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
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"
}

0 comments on commit 3f00447

Please sign in to comment.