Add support for Flat Config (eslint v9) (#54297)

Summary:
Implements: https://github.com/facebook/react-native/issues/42996

Add support for Flat config (eslint v9) to `eslint-config-react-native`. To achieve this, I created `packages/eslint-config-react-native/flat.js` to export flat config as well as maintain legacy config for backward compatibility. I recognize we have had [the PR for supporting Flat config](https://github.com/facebook/react-native/issues/42996) already, but it looks stale and doesn't seem likely to move forward. That's why I created this PR.

I also updated `eslint-plugin-react-native` and `eslint-plugin-specs`  so that they comply with the current eslint plugin format.

## Changelog:

[GENERAL] [ADDED] - Add support for Flat Config (eslint v9)

Pull Request resolved: https://github.com/facebook/react-native/pull/54297

Test Plan:
1. Pull this branch into your local machine
2. `cd packages/eslint-config-react-native`
3.  `yarn link`
4. Clone https://github.com/pipopotamasu/rn-eslint-config-test in your local machine
5. `cd path/to/rn-eslint-config-test`
6. `yarn install`
7. `yarn link "react-native/eslint-config"`
8. `yarn lint`
9. Confirm that eslint rules from `react-native/eslint-config` are applied

<img width="1470" height="628" alt="image" src="https://github.com/user-attachments/assets/955fd655-8a79-4dd4-9031-e505d218d63a" />

Reviewed By: vzaidman

Differential Revision: D85851077

Pulled By: huntie

fbshipit-source-id: 9c78ecc4f5c3b10175a89417628b599735f95215
This commit is contained in:
pipopotamasu
2025-11-13 10:47:06 -08:00
committed by meta-codesync[bot]
parent 8101b7f47c
commit 18592451de
11 changed files with 540 additions and 334 deletions
+1 -1
View File
@@ -81,7 +81,7 @@
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-native": "^4.0.0",
"eslint-plugin-react-native": "^5.0.0",
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
"fb-dotslash": "0.5.8",
@@ -12,6 +12,32 @@ yarn add --dev eslint prettier @react-native/eslint-config
## Usage
### For ESLint 9+ (Flat Config)
Add to your `eslint.config.js`:
```javascript
const reactNativeConfig = require('@react-native/eslint-config/flat');
module.exports = [
...reactNativeConfig,
// Your custom config here
];
```
Or with ES modules:
```javascript
import reactNativeConfig from '@react-native/eslint-config/flat';
export default [
...reactNativeConfig,
// Your custom config here
];
```
### For ESLint 8 (Legacy Config)
Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json`):
```json
+114
View File
@@ -0,0 +1,114 @@
/**
* 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
* @noflow
*/
const sharedConfig = require('./shared.js');
const babelParser = require('@babel/eslint-parser');
const reactNativePlugin = require('@react-native/eslint-plugin');
const typescriptPlugin = require('@typescript-eslint/eslint-plugin');
const typescriptParser = require('@typescript-eslint/parser');
const prettierConfig = require('eslint-config-prettier');
const eslintCommentsPlugin = require('eslint-plugin-eslint-comments');
const ftFlowPlugin = require('eslint-plugin-ft-flow');
const jestPlugin = require('eslint-plugin-jest');
const reactPlugin = require('eslint-plugin-react');
const reactHooksPlugin = require('eslint-plugin-react-hooks');
const reactNativeExternalPlugin = require('eslint-plugin-react-native');
// Convert globals from legacy format (true/false) to flat config format ('readonly'/'writable')
const convertGlobals = globals => {
const converted = {};
for (const [key, value] of Object.entries(globals)) {
converted[key] = value ? 'writable' : 'readonly';
}
return converted;
};
// Flat Config for ESLint 9+
module.exports = [
// Apply prettier config first (to disable conflicting rules)
prettierConfig,
// Base configuration for all files
{
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parserOptions: sharedConfig.parserOptions,
globals: convertGlobals(sharedConfig.globals),
},
plugins: {
'eslint-comments': eslintCommentsPlugin,
react: reactPlugin,
'react-hooks': reactHooksPlugin,
'react-native': reactNativeExternalPlugin,
'@react-native': reactNativePlugin,
jest: jestPlugin,
},
settings: sharedConfig.settings,
rules: sharedConfig.rules,
},
// JavaScript files with Babel parser and Flow
{
...sharedConfig.overrides.flow,
files: ['**/*.js'],
languageOptions: {
parser: babelParser,
},
plugins: {
'ft-flow': ftFlowPlugin,
},
},
// JSX files with Babel parser
{
files: ['**/*.jsx'],
languageOptions: {
parser: babelParser,
},
},
// All JS/TS files - React Native specific rules
{
...sharedConfig.overrides.reactNative,
files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
},
// TypeScript files
{
...sharedConfig.overrides.typescript,
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescriptPlugin,
},
},
// Test files
{
...sharedConfig.overrides.jest,
files: [
'**/*.{spec,test}.{js,ts,tsx}',
'**/__{mocks,tests}__/**/*.{js,ts,tsx}',
],
languageOptions: {
globals: {
...jestPlugin.environments.globals.globals,
},
},
},
];
+10 -255
View File
@@ -8,17 +8,14 @@
* @noflow
*/
const sharedConfig = require('./shared.js');
module.exports = {
env: {
es6: true,
},
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
parserOptions: sharedConfig.parserOptions,
extends: ['prettier'],
@@ -31,54 +28,31 @@ module.exports = {
'jest',
],
settings: {
react: {
version: 'detect',
},
},
settings: sharedConfig.settings,
overrides: [
{
...sharedConfig.overrides.flow,
files: ['*.js'],
parser: '@babel/eslint-parser',
plugins: ['ft-flow'],
rules: {
// Flow Plugin
// The following rules are made available via `eslint-plugin-ft-flow`
'ft-flow/define-flow-type': 1,
'ft-flow/use-flow-type': 1,
},
},
{
files: ['*.jsx'],
parser: '@babel/eslint-parser',
},
{
...sharedConfig.overrides.reactNative,
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
rules: {
'@react-native/no-deep-imports': 1,
},
},
{
...sharedConfig.overrides.typescript,
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint/eslint-plugin'],
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
'no-unused-vars': 'off',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 1,
'no-undef': 'off',
},
},
{
...sharedConfig.overrides.jest,
files: [
'*.{spec,test}.{js,ts,tsx}',
'**/__{mocks,tests}__/**/*.{js,ts,tsx}',
@@ -87,230 +61,11 @@ module.exports = {
jest: true,
'jest/globals': true,
},
rules: {
'react-native/no-inline-styles': 0,
},
},
],
// Map from global var to bool specifying if it can be redefined
globals: {
__DEV__: true,
__dirname: false,
__fbBatchedBridgeConfig: false,
AbortController: false,
Blob: true,
alert: false,
cancelAnimationFrame: false,
cancelIdleCallback: false,
clearImmediate: true,
clearInterval: false,
clearTimeout: false,
console: false,
document: false,
ErrorUtils: false,
escape: false,
Event: false,
EventTarget: false,
exports: false,
fetch: false,
File: true,
FileReader: false,
FormData: false,
global: false,
Headers: false,
Intl: false,
Map: true,
module: false,
navigator: false,
process: false,
Promise: true,
requestAnimationFrame: true,
requestIdleCallback: true,
require: false,
Set: true,
setImmediate: true,
setInterval: false,
setTimeout: false,
queueMicrotask: true,
URL: false,
URLSearchParams: false,
WebSocket: true,
window: false,
XMLHttpRequest: false,
},
globals: sharedConfig.globals,
rules: {
// General
'no-cond-assign': 1, // disallow assignment in conditional expressions
'no-console': 0, // disallow use of console (off by default in the node environment)
'no-const-assign': 2, // disallow assignment to const-declared variables
'no-constant-condition': 0, // disallow use of constant expressions in conditions
'no-control-regex': 1, // disallow control characters in regular expressions
'no-debugger': 1, // disallow use of debugger
'no-dupe-class-members': 2, // Disallow duplicate name in class members
'no-dupe-keys': 2, // disallow duplicate keys when creating object literals
'no-empty': 0, // disallow empty statements
'no-ex-assign': 1, // disallow assigning to the exception in a catch block
'no-extra-boolean-cast': 1, // disallow double-negation boolean casts in a boolean context
'no-func-assign': 1, // disallow overwriting functions written as function declarations
'no-inner-declarations': 0, // disallow function or variable declarations in nested blocks
'no-invalid-regexp': 1, // disallow invalid regular expression strings in the RegExp constructor
'no-negated-in-lhs': 1, // disallow negation of the left operand of an in expression
'no-obj-calls': 1, // disallow the use of object properties of the global object (Math and JSON) as functions
'no-regex-spaces': 1, // disallow multiple spaces in a regular expression literal
'no-sparse-arrays': 1, // disallow sparse arrays
'no-unreachable': 2, // disallow unreachable statements after a return, throw, continue, or break statement
'use-isnan': 1, // disallow comparisons with the value NaN
'valid-jsdoc': 0, // Ensure JSDoc comments are valid (off by default)
'valid-typeof': 1, // Ensure that the results of typeof are compared against a valid string
// Best Practices
// These are rules designed to prevent you from making mistakes. They either prescribe a better way of doing something or help you avoid footguns.
'block-scoped-var': 0, // treat var statements as if they were block scoped (off by default)
complexity: 0, // specify the maximum cyclomatic complexity allowed in a program (off by default)
'consistent-return': 0, // require return statements to either always or never specify values
'default-case': 0, // require default case in switch statements (off by default)
'dot-notation': 1, // encourages use of dot notation whenever possible
eqeqeq: [1, 'allow-null'], // require the use of === and !==
'guard-for-in': 0, // make sure for-in loops have an if statement (off by default)
'no-alert': 1, // disallow the use of alert, confirm, and prompt
'no-caller': 1, // disallow use of arguments.caller or arguments.callee
'no-div-regex': 1, // disallow division operators explicitly at beginning of regular expression (off by default)
'no-else-return': 0, // disallow else after a return in an if (off by default)
'no-eq-null': 0, // disallow comparisons to null without a type-checking operator (off by default)
'no-eval': 2, // disallow use of eval()
'no-extend-native': 1, // disallow adding to native types
'no-extra-bind': 1, // disallow unnecessary function binding
'no-fallthrough': 1, // disallow fallthrough of case statements
'no-implied-eval': 1, // disallow use of eval()-like methods
'no-labels': 1, // disallow use of labeled statements
'no-iterator': 1, // disallow usage of __iterator__ property
'no-lone-blocks': 1, // disallow unnecessary nested blocks
'no-loop-func': 0, // disallow creation of functions within loops
'no-multi-str': 0, // disallow use of multiline strings
'no-native-reassign': 0, // disallow reassignments of native objects
'no-new': 1, // disallow use of new operator when not part of the assignment or comparison
'no-new-func': 2, // disallow use of new operator for Function object
'no-new-wrappers': 1, // disallows creating new instances of String,Number, and Boolean
'no-octal': 1, // disallow use of octal literals
'no-octal-escape': 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251";
'no-proto': 1, // disallow usage of __proto__ property
'no-redeclare': 0, // disallow declaring the same variable more then once
'no-return-assign': 1, // disallow use of assignment in return statement
'no-script-url': 1, // disallow use of javascript: urls.
'no-self-compare': 1, // disallow comparisons where both sides are exactly the same (off by default)
'no-sequences': 1, // disallow use of comma operator
'no-unused-expressions': 0, // disallow usage of expressions in statement position
'no-useless-escape': 1, // disallow escapes that don't have any effect in literals
'no-void': 1, // disallow use of void operator (off by default)
'no-warning-comments': 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default)
'no-with': 1, // disallow use of the with statement
radix: 1, // require use of the second argument for parseInt() (off by default)
'vars-on-top': 0, // requires to declare all vars on top of their containing scope (off by default)
yoda: 1, // require or disallow Yoda conditions
// Variables
// These rules have to do with variable declarations.
'no-catch-shadow': 1, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment)
'no-delete-var': 1, // disallow deletion of variables
'no-global-assign': 2, // disallow assignments to native objects or read-only global variables
'no-label-var': 1, // disallow labels that share a name with a variable
'no-shadow': 1, // disallow declaration of variables already declared in the outer scope
'no-shadow-restricted-names': 1, // disallow shadowing of names such as arguments
'no-undef': 2, // disallow use of undeclared variables unless mentioned in a /*global */ block
'no-undefined': 0, // disallow use of undefined variable (off by default)
'no-undef-init': 1, // disallow use of undefined when initializing variables
'no-unused-vars': [
1,
{vars: 'all', args: 'none', ignoreRestSiblings: true},
], // disallow declaration of variables that are not used in the code
'no-use-before-define': 0, // disallow use of variables before they are defined
// Node.js
// These rules are specific to JavaScript running on Node.js.
'handle-callback-err': 1, // enforces error handling in callbacks (off by default) (on by default in the node environment)
'no-mixed-requires': 1, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment)
'no-new-require': 1, // disallow use of new operator with the require function (off by default) (on by default in the node environment)
'no-path-concat': 1, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment)
'no-process-exit': 0, // disallow process.exit() (on by default in the node environment)
'no-restricted-modules': 1, // restrict usage of specified node modules (off by default)
'no-sync': 0, // disallow use of synchronous methods (off by default)
// ESLint Comments Plugin
// The following rules are made available via `eslint-plugin-eslint-comments`
'eslint-comments/no-aggregating-enable': 1, // disallows eslint-enable comments for multiple eslint-disable comments
'eslint-comments/no-unlimited-disable': 1, // disallows eslint-disable comments without rule names
'eslint-comments/no-unused-disable': 1, // disallow disables that don't cover any errors
'eslint-comments/no-unused-enable': 1, // // disallow enables that don't enable anything or enable rules that weren't disabled
// Stylistic Issues
// These rules are purely matters of style and are quite subjective.
camelcase: 0, // require camel case names
'consistent-this': 1, // enforces consistent naming when capturing the current execution context (off by default)
'func-names': 0, // require function expressions to have a name (off by default)
'func-style': 0, // enforces use of function declarations or expressions (off by default)
'new-cap': 0, // require a capital letter for constructors
'no-nested-ternary': 0, // disallow nested ternary expressions (off by default)
'no-array-constructor': 1, // disallow use of the Array constructor
'no-empty-character-class': 1, // disallow the use of empty character classes in regular expressions
'no-lonely-if': 0, // disallow if as the only statement in an else block (off by default)
'no-new-object': 1, // disallow use of the Object constructor
'no-ternary': 0, // disallow the use of ternary operators (off by default)
'no-underscore-dangle': 0, // disallow dangling underscores in identifiers
'sort-vars': 0, // sort variables within the same declaration block (off by default)
'max-nested-callbacks': 0, // specify the maximum depth callbacks can be nested (off by default)
'one-var': 0, // allow just one var statement per function (off by default)
// Legacy
// The following rules are included for compatibility with JSHint and JSLint. While the names of the rules may not match up with the JSHint/JSLint counterpart, the functionality is the same.
'max-depth': 0, // specify the maximum depth that blocks can be nested (off by default)
'max-params': 0, // limits the number of parameters that can be used in the function declaration. (off by default)
'max-statements': 0, // specify the maximum number of statement allowed in a function (off by default)
'no-bitwise': 1, // disallow use of bitwise operators (off by default)
'no-plusplus': 0, // disallow use of unary operators, ++ and -- (off by default)
// React Plugin
// The following rules are made available via `eslint-plugin-react`.
'react/display-name': 0,
'react/jsx-boolean-value': 0,
'react/jsx-no-comment-textnodes': 2,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-undef': 2,
'react/jsx-sort-props': 0,
'react/jsx-uses-react': 1,
'react/jsx-uses-vars': 1,
'react/no-did-mount-set-state': 1,
'react/no-did-update-set-state': 1,
'react/no-multi-comp': 0,
'react/no-string-refs': 2,
'react/no-unknown-property': 0,
'react/no-unstable-nested-components': 1,
'react/react-in-jsx-scope': 0,
'react/self-closing-comp': 1,
'react/wrap-multilines': 0,
// React-Hooks Plugin
// The following rules are made available via `eslint-plugin-react-hooks`
'react-hooks/rules-of-hooks': 2,
'react-hooks/exhaustive-deps': 2,
// React-Native Plugin
// The following rules are made available via `eslint-plugin-react-native`
'react-native/no-inline-styles': 1,
// Jest Plugin
// The following rules are made available via `eslint-plugin-jest`.
'jest/no-disabled-tests': 1,
'jest/no-focused-tests': 1,
'jest/no-identical-title': 1,
'jest/valid-expect': 1,
},
rules: sharedConfig.rules,
};
@@ -19,6 +19,10 @@
"node": ">= 20.19.4"
},
"main": "index.js",
"exports": {
".": "./index.js",
"./flat": "./flat.js"
},
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.25.1",
@@ -31,10 +35,10 @@
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-native": "^4.0.0"
"eslint-plugin-react-native": "^5.0.0"
},
"peerDependencies": {
"eslint": ">=8",
"eslint": "^8.0.0 || ^9.0.0",
"prettier": ">=2"
},
"devDependencies": {
@@ -0,0 +1,281 @@
/**
* 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
* @noflow
*/
module.exports = {
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: 'detect',
},
},
overrides: {
flow: {
rules: {
// Flow Plugin
// The following rules are made available via `eslint-plugin-ft-flow`
'ft-flow/define-flow-type': 1,
'ft-flow/use-flow-type': 1,
},
},
reactNative: {
rules: {
'@react-native/no-deep-imports': 1,
},
},
typescript: {
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
'no-unused-vars': 'off',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 1,
'no-undef': 'off',
},
},
jest: {
rules: {
'react-native/no-inline-styles': 0,
},
},
},
// Map from global var to bool specifying if it can be redefined
globals: {
__DEV__: true,
__dirname: false,
__fbBatchedBridgeConfig: false,
AbortController: false,
Blob: true,
alert: false,
cancelAnimationFrame: false,
cancelIdleCallback: false,
clearImmediate: true,
clearInterval: false,
clearTimeout: false,
console: false,
document: false,
ErrorUtils: false,
escape: false,
Event: false,
EventTarget: false,
exports: false,
fetch: false,
File: true,
FileReader: false,
FormData: false,
global: false,
Headers: false,
Intl: false,
Map: true,
module: false,
navigator: false,
process: false,
Promise: true,
requestAnimationFrame: true,
requestIdleCallback: true,
require: false,
Set: true,
setImmediate: true,
setInterval: false,
setTimeout: false,
queueMicrotask: true,
URL: false,
URLSearchParams: false,
WebSocket: true,
window: false,
XMLHttpRequest: false,
},
rules: {
// General
'no-cond-assign': 1, // disallow assignment in conditional expressions
'no-console': 0, // disallow use of console (off by default in the node environment)
'no-const-assign': 2, // disallow assignment to const-declared variables
'no-constant-condition': 0, // disallow use of constant expressions in conditions
'no-control-regex': 1, // disallow control characters in regular expressions
'no-debugger': 1, // disallow use of debugger
'no-dupe-class-members': 2, // Disallow duplicate name in class members
'no-dupe-keys': 2, // disallow duplicate keys when creating object literals
'no-empty': 0, // disallow empty statements
'no-ex-assign': 1, // disallow assigning to the exception in a catch block
'no-extra-boolean-cast': 1, // disallow double-negation boolean casts in a boolean context
'no-func-assign': 1, // disallow overwriting functions written as function declarations
'no-inner-declarations': 0, // disallow function or variable declarations in nested blocks
'no-invalid-regexp': 1, // disallow invalid regular expression strings in the RegExp constructor
'no-negated-in-lhs': 1, // disallow negation of the left operand of an in expression
'no-obj-calls': 1, // disallow the use of object properties of the global object (Math and JSON) as functions
'no-regex-spaces': 1, // disallow multiple spaces in a regular expression literal
'no-sparse-arrays': 1, // disallow sparse arrays
'no-unreachable': 2, // disallow unreachable statements after a return, throw, continue, or break statement
'use-isnan': 1, // disallow comparisons with the value NaN
'valid-jsdoc': 0, // Ensure JSDoc comments are valid (off by default)
'valid-typeof': 1, // Ensure that the results of typeof are compared against a valid string
// Best Practices
// These are rules designed to prevent you from making mistakes. They either prescribe a better way of doing something or help you avoid footguns.
'block-scoped-var': 0, // treat var statements as if they were block scoped (off by default)
complexity: 0, // specify the maximum cyclomatic complexity allowed in a program (off by default)
'consistent-return': 0, // require return statements to either always or never specify values
'default-case': 0, // require default case in switch statements (off by default)
'dot-notation': 1, // encourages use of dot notation whenever possible
eqeqeq: [1, 'allow-null'], // require the use of === and !==
'guard-for-in': 0, // make sure for-in loops have an if statement (off by default)
'no-alert': 1, // disallow the use of alert, confirm, and prompt
'no-caller': 1, // disallow use of arguments.caller or arguments.callee
'no-div-regex': 1, // disallow division operators explicitly at beginning of regular expression (off by default)
'no-else-return': 0, // disallow else after a return in an if (off by default)
'no-eq-null': 0, // disallow comparisons to null without a type-checking operator (off by default)
'no-eval': 2, // disallow use of eval()
'no-extend-native': 1, // disallow adding to native types
'no-extra-bind': 1, // disallow unnecessary function binding
'no-fallthrough': 1, // disallow fallthrough of case statements
'no-implied-eval': 1, // disallow use of eval()-like methods
'no-labels': 1, // disallow use of labeled statements
'no-iterator': 1, // disallow usage of __iterator__ property
'no-lone-blocks': 1, // disallow unnecessary nested blocks
'no-loop-func': 0, // disallow creation of functions within loops
'no-multi-str': 0, // disallow use of multiline strings
'no-native-reassign': 0, // disallow reassignments of native objects
'no-new': 1, // disallow use of new operator when not part of the assignment or comparison
'no-new-func': 2, // disallow use of new operator for Function object
'no-new-wrappers': 1, // disallows creating new instances of String,Number, and Boolean
'no-octal': 1, // disallow use of octal literals
'no-octal-escape': 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251";
'no-proto': 1, // disallow usage of __proto__ property
'no-redeclare': 0, // disallow declaring the same variable more then once
'no-return-assign': 1, // disallow use of assignment in return statement
'no-script-url': 1, // disallow use of javascript: urls.
'no-self-compare': 1, // disallow comparisons where both sides are exactly the same (off by default)
'no-sequences': 1, // disallow use of comma operator
'no-unused-expressions': 0, // disallow usage of expressions in statement position
'no-useless-escape': 1, // disallow escapes that don't have any effect in literals
'no-void': 1, // disallow use of void operator (off by default)
'no-warning-comments': 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default)
'no-with': 1, // disallow use of the with statement
radix: 1, // require use of the second argument for parseInt() (off by default)
'vars-on-top': 0, // requires to declare all vars on top of their containing scope (off by default)
yoda: 1, // require or disallow Yoda conditions
// Variables
// These rules have to do with variable declarations.
'no-catch-shadow': 1, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment)
'no-delete-var': 1, // disallow deletion of variables
'no-global-assign': 2, // disallow assignments to native objects or read-only global variables
'no-label-var': 1, // disallow labels that share a name with a variable
'no-shadow': 1, // disallow declaration of variables already declared in the outer scope
'no-shadow-restricted-names': 1, // disallow shadowing of names such as arguments
'no-undef': 2, // disallow use of undeclared variables unless mentioned in a /*global */ block
'no-undefined': 0, // disallow use of undefined variable (off by default)
'no-undef-init': 1, // disallow use of undefined when initializing variables
'no-unused-vars': [
1,
{vars: 'all', args: 'none', ignoreRestSiblings: true},
], // disallow declaration of variables that are not used in the code
'no-use-before-define': 0, // disallow use of variables before they are defined
// Node.js
// These rules are specific to JavaScript running on Node.js.
'handle-callback-err': 1, // enforces error handling in callbacks (off by default) (on by default in the node environment)
'no-mixed-requires': 1, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment)
'no-new-require': 1, // disallow use of new operator with the require function (off by default) (on by default in the node environment)
'no-path-concat': 1, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment)
'no-process-exit': 0, // disallow process.exit() (on by default in the node environment)
'no-restricted-modules': 1, // restrict usage of specified node modules (off by default)
'no-sync': 0, // disallow use of synchronous methods (off by default)
// ESLint Comments Plugin
// The following rules are made available via `eslint-plugin-eslint-comments`
'eslint-comments/no-aggregating-enable': 1, // disallows eslint-enable comments for multiple eslint-disable comments
'eslint-comments/no-unlimited-disable': 1, // disallows eslint-disable comments without rule names
'eslint-comments/no-unused-disable': 1, // disallow disables that don't cover any errors
'eslint-comments/no-unused-enable': 1, // // disallow enables that don't enable anything or enable rules that weren't disabled
// Stylistic Issues
// These rules are purely matters of style and are quite subjective.
camelcase: 0, // require camel case names
'consistent-this': 1, // enforces consistent naming when capturing the current execution context (off by default)
'func-names': 0, // require function expressions to have a name (off by default)
'func-style': 0, // enforces use of function declarations or expressions (off by default)
'new-cap': 0, // require a capital letter for constructors
'no-nested-ternary': 0, // disallow nested ternary expressions (off by default)
'no-array-constructor': 1, // disallow use of the Array constructor
'no-empty-character-class': 1, // disallow the use of empty character classes in regular expressions
'no-lonely-if': 0, // disallow if as the only statement in an else block (off by default)
'no-new-object': 1, // disallow use of the Object constructor
'no-ternary': 0, // disallow the use of ternary operators (off by default)
'no-underscore-dangle': 0, // disallow dangling underscores in identifiers
'sort-vars': 0, // sort variables within the same declaration block (off by default)
'max-nested-callbacks': 0, // specify the maximum depth callbacks can be nested (off by default)
'one-var': 0, // allow just one var statement per function (off by default)
// Legacy
// The following rules are included for compatibility with JSHint and JSLint. While the names of the rules may not match up with the JSHint/JSLint counterpart, the functionality is the same.
'max-depth': 0, // specify the maximum depth that blocks can be nested (off by default)
'max-params': 0, // limits the number of parameters that can be used in the function declaration. (off by default)
'max-statements': 0, // specify the maximum number of statement allowed in a function (off by default)
'no-bitwise': 1, // disallow use of bitwise operators (off by default)
'no-plusplus': 0, // disallow use of unary operators, ++ and -- (off by default)
// React Plugin
// The following rules are made available via `eslint-plugin-react`.
'react/display-name': 0,
'react/jsx-boolean-value': 0,
'react/jsx-no-comment-textnodes': 2,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-undef': 2,
'react/jsx-sort-props': 0,
'react/jsx-uses-react': 1,
'react/jsx-uses-vars': 1,
'react/no-did-mount-set-state': 1,
'react/no-did-update-set-state': 1,
'react/no-multi-comp': 0,
'react/no-string-refs': 2,
'react/no-unknown-property': 0,
'react/no-unstable-nested-components': 1,
'react/react-in-jsx-scope': 0,
'react/self-closing-comp': 1,
'react/wrap-multilines': 0,
// React-Hooks Plugin
// The following rules are made available via `eslint-plugin-react-hooks`
'react-hooks/rules-of-hooks': 2,
'react-hooks/exhaustive-deps': 2,
// React-Native Plugin
// The following rules are made available via `eslint-plugin-react-native`
'react-native/no-inline-styles': 1,
// Jest Plugin
// The following rules are made available via `eslint-plugin-jest`.
'jest/no-disabled-tests': 1,
'jest/no-focused-tests': 1,
'jest/no-identical-title': 1,
'jest/valid-expect': 1,
},
};
+9 -3
View File
@@ -8,7 +8,13 @@
* @noflow
*/
exports.rules = {
'platform-colors': require('./platform-colors'),
'no-deep-imports': require('./no-deep-imports'),
module.exports = {
meta: {
name: '@react-native/eslint-plugin',
version: require('./package.json').version,
},
rules: {
'platform-colors': require('./platform-colors'),
'no-deep-imports': require('./no-deep-imports'),
},
};
@@ -45,7 +45,10 @@ export default TurboModuleRegistry.get<Spec>('XYZ');
filename: `${NATIVE_MODULES_DIR}/XYZ.js`,
errors: [
{
message: rule.errors.misnamedHasteModule('XYZ'),
message: rule.meta.messages.misnamedHasteModule.replace(
'{{hasteModuleName}}',
'XYZ',
),
},
],
},
+4
View File
@@ -13,6 +13,10 @@
const reactNativeModules = require('./react-native-modules');
module.exports = {
meta: {
name: '@react-native/eslint-plugin-specs',
version: require('./package.json').version,
},
rules: {
'react-native-modules': reactNativeModules,
},
+81 -68
View File
@@ -15,12 +15,6 @@ const path = require('path');
// We use the prepack hook before publishing package to set this value to true
const PACKAGE_USAGE = false;
const ERRORS = {
misnamedHasteModule(hasteModuleName) {
return `Module ${hasteModuleName}: All files using TurboModuleRegistry must start with Native.`;
},
};
let RNModuleParser;
let RNParserUtils;
let RNFlowParser;
@@ -120,78 +114,97 @@ function isGeneratedFile(context) {
/**
* A lint rule to guide best practices in writing type safe React NativeModules.
*/
function rule(context) {
const filename = context.getFilename();
const hasteModuleName = path.basename(filename).replace(/\.js$/, '');
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Enforce best practices for writing type-safe React Native TurboModules',
recommended: false,
},
messages: {
misnamedHasteModule:
'Module {{hasteModuleName}}: All files using TurboModuleRegistry must start with Native.',
parsingError: '{{message}}',
},
schema: [],
},
if (isGeneratedFile(context)) {
return {};
}
create(context) {
const filename = context.getFilename();
const hasteModuleName = path.basename(filename).replace(/\.js$/, '');
let isModule = false;
if (isGeneratedFile(context)) {
return {};
}
return {
'Program:exit': function (node) {
if (!isModule) {
return;
}
let isModule = false;
// Report invalid file names
if (!VALID_SPEC_NAMES.test(hasteModuleName)) {
context.report({
node,
message: ERRORS.misnamedHasteModule(hasteModuleName),
});
}
return {
'Program:exit': function (node) {
if (!isModule) {
return;
}
const {
buildModuleSchema,
createParserErrorCapturer,
parser,
translateTypeAnnotation,
} = requireModuleParser();
// Report invalid file names
if (!VALID_SPEC_NAMES.test(hasteModuleName)) {
context.report({
node,
messageId: 'misnamedHasteModule',
data: {
hasteModuleName,
},
});
}
const [parsingErrors, tryParse] = createParserErrorCapturer();
const sourceCode = context.getSourceCode().getText();
const ast = parser.getAst(sourceCode, filename);
tryParse(() => {
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
const {
buildModuleSchema,
createParserErrorCapturer,
parser,
translateTypeAnnotation,
);
});
} = requireModuleParser();
parsingErrors.forEach(error => {
error.nodes.forEach(flowNode => {
context.report({
loc: flowNode.loc,
message: error.message,
const [parsingErrors, tryParse] = createParserErrorCapturer();
const sourceCode = context.getSourceCode().getText();
const ast = parser.getAst(sourceCode, filename);
tryParse(() => {
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
parser,
translateTypeAnnotation,
);
});
parsingErrors.forEach(error => {
error.nodes.forEach(flowNode => {
context.report({
loc: flowNode.loc,
messageId: 'parsingError',
data: {
message: error.message,
},
});
});
});
});
},
CallExpression(node) {
if (!isModuleRequire(node)) {
return;
}
},
CallExpression(node) {
if (!isModuleRequire(node)) {
return;
}
isModule = true;
},
InterfaceExtends(node) {
if (node.id.name !== 'TurboModule') {
return;
}
isModule = true;
},
InterfaceExtends(node) {
if (node.id.name !== 'TurboModule') {
return;
}
isModule = true;
},
};
}
rule.errors = ERRORS;
module.exports = rule;
isModule = true;
},
};
},
};
+4 -4
View File
@@ -4341,10 +4341,10 @@ eslint-plugin-react-native-globals@^0.1.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2"
integrity sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==
eslint-plugin-react-native@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz#5343acd3b2246bc1b857ac38be708f070d18809f"
integrity sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q==
eslint-plugin-react-native@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-5.0.0.tgz#2ee990ba4967c557183b31121578547fb5c02d5d"
integrity sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q==
dependencies:
eslint-plugin-react-native-globals "^0.1.1"