diff --git a/packages/eslint-config-react-native/README.md b/packages/eslint-config-react-native/README.md index e8045d262cfb57..f06151c0d00486 100644 --- a/packages/eslint-config-react-native/README.md +++ b/packages/eslint-config-react-native/README.md @@ -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 diff --git a/packages/eslint-config-react-native/flat.js b/packages/eslint-config-react-native/flat.js new file mode 100644 index 00000000000000..2b427cde0956a1 --- /dev/null +++ b/packages/eslint-config-react-native/flat.js @@ -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, + }, + }, + }, +]; diff --git a/packages/eslint-config-react-native/index.js b/packages/eslint-config-react-native/index.js index cb19c8ba0d0e15..7be4f9d939c1ff 100644 --- a/packages/eslint-config-react-native/index.js +++ b/packages/eslint-config-react-native/index.js @@ -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, - }, - - 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. + globals: sharedConfig.globals, - '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, }; diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json index 129341ce920e30..1ac7d178a3287f 100644 --- a/packages/eslint-config-react-native/package.json +++ b/packages/eslint-config-react-native/package.json @@ -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": { diff --git a/packages/eslint-config-react-native/shared.js b/packages/eslint-config-react-native/shared.js new file mode 100644 index 00000000000000..0d190f3cba861f --- /dev/null +++ b/packages/eslint-config-react-native/shared.js @@ -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, + }, +}; diff --git a/packages/eslint-plugin-react-native/index.js b/packages/eslint-plugin-react-native/index.js index c0e23cd9df817e..7a839fa195d679 100644 --- a/packages/eslint-plugin-react-native/index.js +++ b/packages/eslint-plugin-react-native/index.js @@ -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'), + }, }; diff --git a/packages/eslint-plugin-specs/__tests__/react-native-modules-test.js b/packages/eslint-plugin-specs/__tests__/react-native-modules-test.js index 2ef1dbbf9c2f30..7785db83139f31 100644 --- a/packages/eslint-plugin-specs/__tests__/react-native-modules-test.js +++ b/packages/eslint-plugin-specs/__tests__/react-native-modules-test.js @@ -45,7 +45,10 @@ export default TurboModuleRegistry.get('XYZ'); filename: `${NATIVE_MODULES_DIR}/XYZ.js`, errors: [ { - message: rule.errors.misnamedHasteModule('XYZ'), + message: rule.meta.messages.misnamedHasteModule.replace( + '{{hasteModuleName}}', + 'XYZ', + ), }, ], }, diff --git a/packages/eslint-plugin-specs/index.js b/packages/eslint-plugin-specs/index.js index 79e20afa3b48c2..9c5128df2ebb22 100644 --- a/packages/eslint-plugin-specs/index.js +++ b/packages/eslint-plugin-specs/index.js @@ -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, }, diff --git a/packages/eslint-plugin-specs/react-native-modules.js b/packages/eslint-plugin-specs/react-native-modules.js index 77ab33406007d2..0737110bffe402 100644 --- a/packages/eslint-plugin-specs/react-native-modules.js +++ b/packages/eslint-plugin-specs/react-native-modules.js @@ -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$/, ''); - - if (isGeneratedFile(context)) { - return {}; - } +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: [], + }, - let isModule = false; + create(context) { + const filename = context.getFilename(); + const hasteModuleName = path.basename(filename).replace(/\.js$/, ''); - return { - 'Program:exit': function (node) { - if (!isModule) { - return; - } - - // Report invalid file names - if (!VALID_SPEC_NAMES.test(hasteModuleName)) { - context.report({ - node, - message: ERRORS.misnamedHasteModule(hasteModuleName), - }); - } + if (isGeneratedFile(context)) { + return {}; + } - const { - buildModuleSchema, - createParserErrorCapturer, - parser, - translateTypeAnnotation, - } = requireModuleParser(); + let isModule = false; - const [parsingErrors, tryParse] = createParserErrorCapturer(); + return { + 'Program:exit': function (node) { + if (!isModule) { + return; + } - const sourceCode = context.getSourceCode().getText(); - const ast = parser.getAst(sourceCode, filename); + // Report invalid file names + if (!VALID_SPEC_NAMES.test(hasteModuleName)) { + context.report({ + node, + messageId: 'misnamedHasteModule', + data: { + hasteModuleName, + }, + }); + } - 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, - }); - }); - }); - }, - CallExpression(node) { - if (!isModuleRequire(node)) { - return; - } + const [parsingErrors, tryParse] = createParserErrorCapturer(); - isModule = true; - }, - InterfaceExtends(node) { - if (node.id.name !== 'TurboModule') { - return; - } - - isModule = true; - }, - }; -} + const sourceCode = context.getSourceCode().getText(); + const ast = parser.getAst(sourceCode, filename); -rule.errors = ERRORS; + tryParse(() => { + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + parser, + translateTypeAnnotation, + ); + }); -module.exports = rule; + parsingErrors.forEach(error => { + error.nodes.forEach(flowNode => { + context.report({ + loc: flowNode.loc, + messageId: 'parsingError', + data: { + message: error.message, + }, + }); + }); + }); + }, + CallExpression(node) { + if (!isModuleRequire(node)) { + return; + } + + isModule = true; + }, + InterfaceExtends(node) { + if (node.id.name !== 'TurboModule') { + return; + } + + isModule = true; + }, + }; + }, +}; diff --git a/yarn.lock b/yarn.lock index 74755fcc6ea9f0..c2efa9f41589ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4348,6 +4348,13 @@ eslint-plugin-react-native@^4.0.0: dependencies: eslint-plugin-react-native-globals "^0.1.1" +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" + eslint-plugin-react@^7.30.1: version "7.35.1" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.1.tgz#afc80387031aa99dd6e0a14437c77d02e5700b47"