From 89915db62c8087ea4451c4007e2ad799aefce3b3 Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Mon, 25 May 2026 19:43:10 +0200 Subject: [PATCH] feat: support ESLint v10 --- index.js | 39 +++++++++++++++++---- lib/rules/no-single-element-style-arrays.js | 3 +- lib/rules/no-unused-styles.js | 2 +- lib/rules/sort-styles.js | 2 +- lib/rules/split-platform-components.js | 6 ++-- lib/util/Components.js | 2 +- lib/util/stylesheet.js | 3 +- package-lock.json | 2 +- package.json | 2 +- tests/index.js | 16 +++++++++ tests/lib/rules/sort-styles.js | 20 +---------- 11 files changed, 61 insertions(+), 36 deletions(-) diff --git a/index.js b/index.js index c293ffc..7dca3b1 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,9 @@ 'use strict'; +const { globals } = require('eslint-plugin-react-native-globals').environments.all; +const pkg = require('./package.json'); + const allRules = { 'no-unused-styles': require('./lib/rules/no-unused-styles'), 'no-inline-styles': require('./lib/rules/no-inline-styles'), @@ -14,20 +17,27 @@ const allRules = { function configureAsError(rules) { const result = {}; - for (const key in rules) { - if (!rules.hasOwnProperty(key)) { - continue; + Object.keys(rules).forEach((key) => { + if (!Object.prototype.hasOwnProperty.call(rules, key)) { + return; } result['react-native/' + key] = 2; - } + }); return result; } +const meta = { + name: pkg.name, + version: pkg.version, +}; + +const plugin = { meta, rules: allRules }; + const allRulesConfig = configureAsError(allRules); module.exports = { + ...plugin, deprecatedRules: {}, - rules: allRules, rulesConfig: { 'no-unused-styles': 0, 'no-inline-styles': 0, @@ -38,11 +48,13 @@ module.exports = { 'no-single-element-style-arrays': 0 }, environments: { + // Kept for ESLint 8/9 legacy (.eslintrc) users; ignored by ESLint v10 'react-native': { - globals: require('eslint-plugin-react-native-globals').environments.all.globals, + globals: globals, }, }, configs: { + // Legacy format (ESLint 8/9 with .eslintrc) all: { plugins: [ 'react-native', @@ -54,5 +66,20 @@ module.exports = { }, rules: allRulesConfig, }, + // Flat config format (ESLint 9+ / 10+ with eslint.config.js) + 'flat/all': { + plugins: { + 'react-native': plugin + }, + languageOptions: { + globals: globals, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + rules: allRulesConfig, + }, }, }; diff --git a/lib/rules/no-single-element-style-arrays.js b/lib/rules/no-single-element-style-arrays.js index ce43bc9..6541b12 100644 --- a/lib/rules/no-single-element-style-arrays.js +++ b/lib/rules/no-single-element-style-arrays.js @@ -25,7 +25,8 @@ module.exports = { 'Single element style arrays are not necessary and cause unnecessary re-renders', fix(fixer) { const realStyleNode = JSXExpressionNode.value.expression.elements[0]; - const styleSource = context.getSourceCode().getText(realStyleNode); + const sc = context.sourceCode || context.getSourceCode(); + const styleSource = sc.getText(realStyleNode); return fixer.replaceText(JSXExpressionNode.value.expression, styleSource); }, }); diff --git a/lib/rules/no-unused-styles.js b/lib/rules/no-unused-styles.js index 90f806c..13a22e7 100644 --- a/lib/rules/no-unused-styles.js +++ b/lib/rules/no-unused-styles.js @@ -27,7 +27,7 @@ const create = Components.detect((context, components) => { node.key.name, ].join(''); - context.report(node, message); + context.report({ node, message }); }); } }); diff --git a/lib/rules/sort-styles.js b/lib/rules/sort-styles.js index e2be029..d75d72f 100644 --- a/lib/rules/sort-styles.js +++ b/lib/rules/sort-styles.js @@ -30,7 +30,7 @@ function create(context) { const { ignoreStyleProperties } = options; const isValidOrder = order === 'asc' ? (a, b) => a <= b : (a, b) => a >= b; - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); function sort(array) { return [].concat(array).sort((a, b) => { diff --git a/lib/rules/split-platform-components.js b/lib/rules/split-platform-components.js index 31ce486..cd85e0b 100644 --- a/lib/rules/split-platform-components.js +++ b/lib/rules/split-platform-components.js @@ -44,11 +44,11 @@ function create(context) { const propName = getName(node); if (propName.includes('IOS') && !filename.match(iosPathRegex)) { - context.report(node, containsAndroidAndIOS ? conflictMessage : iosMessage); + context.report({ node, message: containsAndroidAndIOS ? conflictMessage : iosMessage }); } if (propName.includes('Android') && !filename.match(androidPathRegex)) { - context.report(node, containsAndroidAndIOS ? conflictMessage : androidMessage); + context.report({ node, message: containsAndroidAndIOS ? conflictMessage : androidMessage }); } }); } @@ -71,7 +71,7 @@ function create(context) { } }, 'Program:exit': function () { - const filename = context.getFilename(); + const filename = context.filename || context.getFilename(); reportErrors(reactComponents, filename); }, }; diff --git a/lib/util/Components.js b/lib/util/Components.js index f744d52..fa04ee6 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -100,7 +100,7 @@ Components.prototype.length = function () { }; function componentRule(rule, context, _node) { - const sourceCode = context.getSourceCode(); + const sourceCode = context.sourceCode || context.getSourceCode(); const components = new Components(); // Utilities for component detection diff --git a/lib/util/stylesheet.js b/lib/util/stylesheet.js index 09811e9..255e9de 100644 --- a/lib/util/stylesheet.js +++ b/lib/util/stylesheet.js @@ -86,8 +86,7 @@ StyleSheets.prototype.getObjectExpressions = function () { }; let currentContent; -const getSourceCode = (node) => currentContent - .getSourceCode(node) +const getSourceCode = (node) => (currentContent.sourceCode || currentContent.getSourceCode()) .getText(node); const getStyleSheetObjectNames = (settings) => settings['react-native/style-sheet-object-names'] || ['StyleSheet']; diff --git a/package-lock.json b/package-lock.json index 45f0487..cff36b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "typescript": "^5.2.2" }, "peerDependencies": { - "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 || ^10" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index a688279..6553016 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "typescript": "^5.2.2" }, "peerDependencies": { - "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 || ^10" }, "keywords": [ "eslint", diff --git a/tests/index.js b/tests/index.js index ae87130..b679796 100644 --- a/tests/index.js +++ b/tests/index.js @@ -56,4 +56,20 @@ describe('configurations', () => { assert(inDeprecatedRules ^ inAllConfig); // eslint-disable-line no-bitwise }); }); + + it('should export a \'flat/all\' configuration', () => { + const flatAll = plugin.configs['flat/all']; + assert(flatAll); + assert(flatAll.plugins && flatAll.plugins['react-native']); + assert(flatAll.languageOptions); + Object.keys(flatAll.rules).forEach((configName) => { + assert.equal(configName.indexOf('react-native/'), 0); + assert.equal(flatAll.rules[configName], 2); + }); + rules.forEach((ruleName) => { + const inDeprecatedRules = Boolean(plugin.deprecatedRules[ruleName]); + const inFlatAllConfig = Boolean(flatAll.rules['react-native/' + ruleName]); + assert(inDeprecatedRules ^ inFlatAllConfig); // eslint-disable-line no-bitwise + }); + }); }); diff --git a/tests/lib/rules/sort-styles.js b/tests/lib/rules/sort-styles.js index 9c1e3bd..4b4b04a 100644 --- a/tests/lib/rules/sort-styles.js +++ b/tests/lib/rules/sort-styles.js @@ -423,25 +423,7 @@ const tests = { // comments 3 }) `, - output: ` - const styles = StyleSheet.create({ - a: { - d: 4, - // comments 1 - c: 3, - a: 1, - b: 2, - }, - d: {}, - c: {}, - // comments 2 - b: { - a: 1, - b: 2, - }, - // comments 3 - }) - `, + output: null, errors: [ { message: