@@ -4,6 +4,7 @@ import eslintTs from "typescript-eslint";
44import lwcEslintPluginLwcPlatform from "@lwc/eslint-plugin-lwc-platform" ;
55import salesforceEslintConfigLwc from "@salesforce/eslint-config-lwc" ;
66import sldsEslintPlugin from "@salesforce-ux/eslint-plugin-slds" ;
7+ import eslintPluginReact from "eslint-plugin-react" ;
78import { ESLintEngineConfig } from "./config" ;
89import globals from "globals" ;
910
@@ -54,20 +55,39 @@ export class BaseConfigFactory {
5455 if ( this . useTsBaseConfig ( ) ) {
5556 configArray . push ( ...this . createTypescriptConfigArray ( ) ) ;
5657 }
58+ // Add React plugin config for JSX files
59+ if ( this . useReactBaseConfig ( ) ) {
60+ configArray . push ( ...this . createReactConfigArray ( ) ) ;
61+ }
5762 return configArray ;
5863 }
5964
6065 private createJavascriptPlusLwcConfigArray ( ) : Linter . Config [ ] {
6166 let configs : Linter . Config [ ] = validateAndGetRawLwcConfigArray ( ) ;
6267
68+ // Deep clone the languageOptions to avoid mutating the original shared config from the LWC package
69+ const originalParserOptions = configs [ 0 ] . languageOptions ! . parserOptions as Linter . ParserOptions ;
70+ const clonedBabelOptions = JSON . parse ( JSON . stringify ( originalParserOptions . babelOptions ) ) ;
71+ configs [ 0 ] . languageOptions = {
72+ ...configs [ 0 ] . languageOptions ,
73+ parserOptions : {
74+ ...originalParserOptions ,
75+ babelOptions : clonedBabelOptions
76+ }
77+ } ;
78+
6379 // TODO: Remove the For the following 2 updates when https://github.com/salesforce/eslint-config-lwc/issues/158 is fixed
6480 // 1) Turn off the babel parser's configFile option from the lwc base plugin
65- ( configs [ 0 ] . languageOptions ! . parserOptions as Linter . ParserOptions ) . babelOptions . configFile = false ;
81+ clonedBabelOptions . configFile = false ;
6682 // 2) For some reason babel doesn't like .cjs files unless we explicitly set this to undefined because I think
6783 // ESLint 9 is setting it to "commonjs" automatically when the field doesn't exist in the parserOptions (and for
6884 // babel "commonjs" isn't a valid option)
6985 ( configs [ 0 ] . languageOptions ! . parserOptions as Linter . ParserOptions ) . sourceType = undefined ;
7086
87+ // 3) Add @babel/preset-react to enable JSX parsing for React/JSX files
88+ const existingPresets = clonedBabelOptions . presets || [ ] ;
89+ clonedBabelOptions . presets = [ ...existingPresets , '@babel/preset-react' ] ;
90+
7191 // Swap out eslintJs.configs.recommended with eslintJs.configs.all
7292 configs [ 1 ] = eslintJs . configs . all ;
7393
@@ -115,7 +135,14 @@ export class BaseConfigFactory {
115135 private createJavascriptConfigArray ( ) : Linter . Config [ ] {
116136 return [ {
117137 ... eslintJs . configs . all ,
118- files : this . engineConfig . file_extensions . javascript . map ( ext => `**/*${ ext } ` )
138+ files : this . engineConfig . file_extensions . javascript . map ( ext => `**/*${ ext } ` ) ,
139+ languageOptions : {
140+ parserOptions : {
141+ ecmaFeatures : {
142+ jsx : true // Enable JSX parsing for React/JSX files
143+ }
144+ }
145+ }
119146 } ] ;
120147 }
121148
@@ -160,6 +187,40 @@ export class BaseConfigFactory {
160187 return configs ;
161188 }
162189
190+ /**
191+ * Creates React plugin config for JavaScript files.
192+ *
193+ * React rules are applied to all JS files (.js, .jsx, .cjs, .mjs) - if a file
194+ * doesn't contain React code, the rules simply won't report any violations.
195+ *
196+ * Note: TypeScript React support (.tsx) is planned for the next iteration.
197+ */
198+ private createReactConfigArray ( ) : Linter . Config [ ] {
199+ // Apply React rules to all JavaScript files
200+ const jsExtensions = this . engineConfig . file_extensions . javascript ;
201+
202+ if ( jsExtensions . length === 0 ) {
203+ return [ ] ;
204+ }
205+
206+ // Get all rules from eslint-plugin-react's flat config
207+ const reactAllConfig = eslintPluginReact . configs . flat . all ;
208+
209+ return [ {
210+ ...reactAllConfig ,
211+ files : jsExtensions . map ( ext => `**/*${ ext } ` ) ,
212+ settings : {
213+ ...reactAllConfig . settings ,
214+ react : {
215+ // React version - "detect" automatically picks the installed version, falls back to latest
216+ version : 'detect' ,
217+ // Pragma is the function JSX compiles to (e.g., <div> → React.createElement('div'))
218+ pragma : 'React'
219+ }
220+ }
221+ } ] ;
222+ }
223+
163224 private useJsBaseConfig ( ) : boolean {
164225 return ! this . engineConfig . disable_javascript_base_config && this . engineConfig . file_extensions . javascript . length > 0 ;
165226 }
@@ -179,6 +240,13 @@ export class BaseConfigFactory {
179240 private useTsBaseConfig ( ) : boolean {
180241 return ! this . engineConfig . disable_typescript_base_config && this . engineConfig . file_extensions . typescript . length > 0 ;
181242 }
243+
244+ private useReactBaseConfig ( ) : boolean {
245+ // React config is independently controlled by disable_react_base_config
246+ // React rules apply to all JS files - no harm if file has no React code
247+ return ! this . engineConfig . disable_react_base_config &&
248+ this . engineConfig . file_extensions . javascript . length > 0 ;
249+ }
182250}
183251
184252// In order to supply all the eslint rules (instead of just the recommended ones) to be selectable, and in order to
0 commit comments