Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup Node.js and Yarn
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '24'

- uses: actions/cache@v4
id: cache
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ npm install --save-dev eslint @callstack/eslint-config

You can choose one of the following environments to work with by extending your ESLint config (`eslint.config.mjs` for flat config, or `.eslintrc` / `eslintConfig` field in `package.json` for the eslintrc config style) with `@callstack` config tailored to your project.

If you're using ESLint 10 or newer, use the flat config entrypoints. ESLint 10 no longer loads `eslintrc` files.

### React Native config

Usage:

#### eslintrc format (ESLint < v9)
#### eslintrc format (ESLint < v10)

```json
{
Expand Down Expand Up @@ -64,7 +66,7 @@ Additionally, it sets `"react-native/react-native"` environment and native platf

Usage:

#### eslintrc format (ESLint < v9)
#### eslintrc format (ESLint < v10)

```json
{
Expand Down Expand Up @@ -102,7 +104,7 @@ Plugins used:

Usage:

#### eslintrc format (ESLint < v9)
#### eslintrc format (ESLint < v10)

```json
{
Expand Down Expand Up @@ -145,7 +147,7 @@ Additionally, it sets `es6` and `node` environments.

### Example of extending the configuration

##### eslintrc format (ESLint < v9)
##### eslintrc format (ESLint < v10)

```json
{
Expand Down Expand Up @@ -187,7 +189,7 @@ yarn eslint --ext '.js,.ts' ./src

To do so, you'll need to override our setup for TS files in your ESLint config:

##### eslintrc format (ESLint < v9)
##### eslintrc format (ESLint < v10)

```json
{
Expand Down
80 changes: 80 additions & 0 deletions babel-eslint-parser.compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const babelEslintParser = require('@babel/eslint-parser');

function addGlobals(scopeManager, names) {
const globalScope = scopeManager.globalScope ?? scopeManager.scopes?.[0];

if (!globalScope || typeof globalScope.__defineGeneric !== 'function') {
return;
}

const nameSet = new Set(names);

for (const name of names) {
if (!globalScope.set.has(name)) {
globalScope.__defineGeneric(
name,
globalScope.set,
globalScope.variables,
null,
null
);
}
}

if (Array.isArray(globalScope.through)) {
globalScope.through = globalScope.through.filter((reference) => {
if (!nameSet.has(reference.identifier.name)) {
return true;
}

const variable = globalScope.set.get(reference.identifier.name);

if (!variable) {
return true;
}

reference.resolved = variable;
variable.references.push(reference);
return false;
});
}

if (globalScope.implicit) {
if (globalScope.implicit.set instanceof Map) {
for (const name of names) {
globalScope.implicit.set.delete(name);
}
}

if (Array.isArray(globalScope.implicit.variables)) {
globalScope.implicit.variables = globalScope.implicit.variables.filter(
(variable) => !nameSet.has(variable.name)
);
}

if (Array.isArray(globalScope.implicit.left)) {
globalScope.implicit.left = globalScope.implicit.left.filter(
(reference) => !nameSet.has(reference.identifier.name)
);
}
}
}

function parseForESLint(code, options) {
const result = babelEslintParser.parseForESLint(code, options);

if (
result?.scopeManager &&
typeof result.scopeManager.addGlobals !== 'function'
) {
result.scopeManager.addGlobals = (names) =>
addGlobals(result.scopeManager, names);
}

return result;
}

module.exports = {
...babelEslintParser,
parseForESLint,
};
21 changes: 10 additions & 11 deletions node.factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ function createFlatNodeConfig() {
const pluginFlowtype = require('eslint-plugin-flowtype');
const tsEslintPlugin = require('@typescript-eslint/eslint-plugin');
const pluginJest = require('eslint-plugin-jest');
const babelEslintParser = require('@babel/eslint-parser');
const babelEslintParser = require('./babel-eslint-parser.compat');
const tsEslintParser = require('@typescript-eslint/parser');
const { fixupPluginRules } = require('@eslint/compat');
const { fixupConfigRules, fixupPluginRules } = require('@eslint/compat');

// move the parserOptions & parser properties to the languageOptions object to support flat config
// TODO: strip the below as soon as eslint-plugin-flowtype supports eslint@9
Expand All @@ -110,11 +110,10 @@ function createFlatNodeConfig() {

pluginFlowtypeRecommendedConfig.languageOptions.parserOptions =
pluginFlowtypeRecommendedConfig.parserOptions;
pluginFlowtypeRecommendedConfig.languageOptions.parser =
pluginFlowtypeRecommendedConfig.parser;

delete pluginFlowtypeRecommendedConfig.parser;
// The parser is supplied by the JS override below; keeping Flowtype's legacy
// parser reference here breaks flat config on newer ESLint versions.
delete pluginFlowtypeRecommendedConfig.parserOptions;
delete pluginFlowtypeRecommendedConfig.parser;

// since eslint-plugin-flowtype does not support eslint@9 yet, rules do not
// have the meta.schema property set, which is now required; this results in
Expand All @@ -127,7 +126,7 @@ function createFlatNodeConfig() {

return [
js.configs.recommended,
pluginPromise.configs['flat/recommended'],
...fixupConfigRules(pluginPromise.configs['flat/recommended']),
configPrettier,
{
languageOptions: {
Expand All @@ -140,8 +139,8 @@ function createFlatNodeConfig() {
},
},
plugins: {
import: pluginImport,
prettier: pluginPrettier,
import: fixupPluginRules(pluginImport),
prettier: fixupPluginRules(pluginPrettier),
},
...baseConfigOptions,
},
Expand Down Expand Up @@ -179,7 +178,7 @@ function createFlatNodeConfig() {
},
{
// this is a port of 'extends' for the next object
...pluginJest.configs['flat/recommended'],
...fixupConfigRules(pluginJest.configs['flat/recommended'])[0],
files: TEST_PATTERNS,
},
{
Expand All @@ -189,7 +188,7 @@ function createFlatNodeConfig() {
globals: globals.jest,
},
plugins: {
jest: pluginJest,
jest: fixupPluginRules(pluginJest),
},
},
];
Expand Down
42 changes: 21 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "15.0.0",
"description": "ESLint preset extending recommended ESLint config, TypeScript, Prettier and Jest",
"engines": {
"node": ">=18.0.0"
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"main": "./react-native.js",
"homepage": "https://github.com/callstack/eslint-config-callstack#readme",
Expand All @@ -19,33 +19,33 @@
"callstack"
],
"dependencies": {
"@babel/core": "^7.22.20",
"@babel/eslint-parser": "^7.22.15",
"@babel/plugin-syntax-flow": "^7.22.5",
"@babel/plugin-transform-react-jsx": "^7.22.15",
"@eslint/compat": "^1.1.1",
"@react-native/eslint-plugin": "^0.74.0 || ^0.73.0 || ^0.72.0",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"eslint-config-prettier": "^9.0.0",
"@babel/core": "^7.29.0",
"@babel/eslint-parser": "^7.28.6",
"@babel/plugin-syntax-flow": "^7.28.6",
"@babel/plugin-transform-react-jsx": "^7.28.6",
"@eslint/compat": "^2.0.5",
"@react-native/eslint-plugin": "^0.85.1",
"@typescript-eslint/eslint-plugin": "^8.58.2",
"@typescript-eslint/parser": "^8.58.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^28.7.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-native": "^4.1.0",
"eslint-plugin-react-native-a11y": "^3.4.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.15.2",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-native": "^5.0.0",
"eslint-plugin-react-native-a11y": "^3.5.1",
"eslint-restricted-globals": "^0.2.0",
"globals": "^15.9.0",
"prettier": "^3.0.3"
"globals": "^17.5.0",
"prettier": "^3.8.2"
},
"peerDependencies": {
"eslint": ">=8.1.0"
},
"devDependencies": {
"eslint": "^8.49.0",
"eslint": "^8.57.0",
"typescript": "^5.2.2"
},
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions react-native.factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ function createFlatRNConfig() {
// eslint-plugin-react-native-a11y does not support eslint@9 yet and: specifies plugins in array form & parserOptions in root, which we patch this here
// TODO: strip the below as soon as eslint-plugin-react-native-a11y supports eslint@9
plugins: {
'react-native-a11y': pluginA11y,
'react-native-a11y': fixupPluginRules(pluginA11y),
},
languageOptions: {
parserOptions: pluginA11y.configs.all.parserOptions,
},
},
{
plugins: { '@react-native': rnPluginEslint },
plugins: { '@react-native': fixupPluginRules(rnPluginEslint) },
rules: commonAtReactNativePluginRules,
},
{
Expand Down
35 changes: 21 additions & 14 deletions react.factory.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const OFF = 0;
const WARNING = 1;
const ERROR = 2;

const commonParserOptions = {
ecmaFeatures: {
Expand All @@ -14,8 +13,6 @@ const commonParserOptions = {
'react/no-unused-prop-types': OFF,
'react/prop-types': OFF,
'react/require-default-props': OFF,
'react-hooks/rules-of-hooks': ERROR,
'react-hooks/exhaustive-deps': WARNING,
},
settings: {
react: {
Expand All @@ -29,38 +26,48 @@ function createFlatReactConfig() {
const reactPlugin = require('eslint-plugin-react');
const reactHooksPlugin = require('eslint-plugin-react-hooks');
const globals = require('globals');
const { fixupPluginRules } = require('@eslint/compat');
const { fixupConfigRules, fixupPluginRules } = require('@eslint/compat');
const reactHooksRules = reactHooksPlugin.configs['recommended-latest'].rules;

return [
...nodeConfig,
...fixupConfigRules(reactPlugin.configs.flat.recommended),
{
plugins: {
'react-hooks': fixupPluginRules(reactHooksPlugin),
},
},
reactPlugin.configs.flat.recommended,
{
...commonConfig,
languageOptions: {
globals: globals.browser,
parserOptions: commonParserOptions,
},
plugins: {
react: reactPlugin,
react: fixupPluginRules(reactPlugin),
'react-hooks': fixupPluginRules(reactHooksPlugin),
},
rules: {
...reactHooksRules,
...commonConfig.rules,
},
...commonConfig,
},
];
}

function createLegacyReactConfig() {
const reactHooksPlugin = require('eslint-plugin-react-hooks');

return {
extends: [require.resolve('./node.js'), 'plugin:react/recommended'],
...commonConfig,
extends: [
require.resolve('./node.js'),
'plugin:react/recommended',
],
env: {
browser: true,
},
plugins: ['react', 'react-hooks'],
parserOptions: commonParserOptions,
...commonConfig,
rules: {
...reactHooksPlugin.configs['recommended-latest'].rules,
...commonConfig.rules,
},
};
}

Expand Down
15 changes: 15 additions & 0 deletions test/eslint-v10/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import callstackConfig from '@callstack/eslint-config/react-native.flat.js';

export default [
{
ignores: ['**/eslint.config.mjs'],
},
{
settings: {
jest: {
version: 'latest',
},
},
},
...callstackConfig,
];
14 changes: 14 additions & 0 deletions test/eslint-v10/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "plugin-test-eslint-v10",
"version": "0.0.0",
"description": "test",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^10.0.0",
"eslint10": "npm:eslint@^10.0.0"
},
"scripts": {
"test": "cd .. && node ./node_modules/eslint10/bin/eslint.js --max-warnings=0 --report-unused-disable-directives --config=./eslint-v10/eslint.config.mjs . && node ./node_modules/eslint10/bin/eslint.js --config=./eslint-v10/eslint.config.mjs --print-config ./index.js"
}
}
Loading
Loading