Skip to content

Commit 70f077a

Browse files
committed
Adding changes to support react in .jsx files
1 parent 3904fbe commit 70f077a

File tree

15 files changed

+2495
-59
lines changed

15 files changed

+2495
-59
lines changed

package-lock.json

Lines changed: 388 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/code-analyzer-eslint-engine/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@salesforce/code-analyzer-eslint-engine",
33
"description": "Plugin package that adds 'eslint' as an engine into Salesforce Code Analyzer",
4-
"version": "0.38.0",
4+
"version": "0.39.0-SNAPSHOT",
55
"author": "The Salesforce Code Analyzer Team",
66
"license": "BSD-3-Clause",
77
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
@@ -13,6 +13,7 @@
1313
"main": "dist/index.js",
1414
"types": "dist/index.d.ts",
1515
"dependencies": {
16+
"@babel/preset-react": "^7.27.1",
1617
"@eslint/js": "^9.39.2",
1718
"@lwc/eslint-plugin-lwc": "^3.3.0",
1819
"@lwc/eslint-plugin-lwc-platform": "^6.3.0",
@@ -27,6 +28,7 @@
2728
"eslint": "^9.39.2",
2829
"eslint-plugin-import": "^2.32.0",
2930
"eslint-plugin-jest": "^29.5.0",
31+
"eslint-plugin-react": "^7.37.2",
3032
"globals": "^16.5.0",
3133
"semver": "^7.7.3",
3234
"typescript": "^5.9.3",

packages/code-analyzer-eslint-engine/src/base-config.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import eslintTs from "typescript-eslint";
44
import lwcEslintPluginLwcPlatform from "@lwc/eslint-plugin-lwc-platform";
55
import salesforceEslintConfigLwc from "@salesforce/eslint-config-lwc";
66
import sldsEslintPlugin from "@salesforce-ux/eslint-plugin-slds";
7+
import eslintPluginReact from "eslint-plugin-react";
78
import {ESLintEngineConfig} from "./config";
89
import 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

packages/code-analyzer-eslint-engine/src/config.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export type ESLintEngineConfig = {
3636
// Default: false
3737
disable_typescript_base_config: boolean
3838

39+
// If true then the base configuration that supplies the React/JSX rules will not be applied.
40+
// Default: true (React support is currently gated; will change to false when released)
41+
disable_react_base_config: boolean
42+
3943
// Extensions of the files in your workspace that will be used to discover rules.
4044
// To associate file extensions to the standard ESLint JavaScript rules, LWC rules, or custom JavaScript-based
4145
// rules, add them under the 'javascript' language. To associate file extensions to the standard TypeScript
@@ -66,9 +70,10 @@ export const DEFAULT_CONFIG: ESLintEngineConfig = {
6670
disable_lwc_base_config: false,
6771
disable_slds_base_config: false,
6872
disable_typescript_base_config: false,
73+
disable_react_base_config: true, // Gated for now - will change to false when released
6974
file_extensions: {
70-
javascript: ['.js', '.cjs', '.mjs'],
71-
typescript: ['.ts'],
75+
javascript: ['.js', '.cjs', '.mjs', '.jsx'],
76+
typescript: ['.ts'], // Note: .tsx support planned for next iteration
7277
html: ['.html', '.htm', '.cmp'],
7378
css: ['.css', '.scss'],
7479
other: []
@@ -114,6 +119,8 @@ export const ESLINT_ENGINE_CONFIG_DESCRIPTION: ConfigDescription = {
114119
valueType: "boolean",
115120
defaultValue: DEFAULT_CONFIG.disable_typescript_base_config
116121
},
122+
// Note: disable_react_base_config is gated and not user-configurable yet
123+
// TODO: Add to fieldDescriptions when React support is released
117124
file_extensions: {
118125
descriptionText: getMessage('ConfigFieldDescription_file_extensions'),
119126
valueType: "object",
@@ -136,6 +143,8 @@ export const LEGACY_ESLINT_IGNORE_FILE: string = '.eslintignore';
136143

137144

138145
export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtractor): ESLintEngineConfig {
146+
// Note: disable_react_base_config is intentionally excluded - React support is gated
147+
// TODO: Add 'disable_react_base_config' when React support is released
139148
configValueExtractor.validateContainsOnlySpecifiedKeys(['eslint_config_file', 'eslint_ignore_file',
140149
'auto_discover_eslint_config', 'disable_javascript_base_config', 'disable_lwc_base_config',
141150
'disable_slds_base_config', 'disable_typescript_base_config', 'file_extensions']);
@@ -150,6 +159,9 @@ export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtr
150159
disable_lwc_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_lwc_base_config'),
151160
disable_slds_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_slds_base_config'),
152161
disable_typescript_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_typescript_base_config'),
162+
// React support is gated - always force to true regardless of customer config
163+
// TODO: Change to eslintConfigValueExtractor.extractBooleanValue('disable_react_base_config') when released
164+
disable_react_base_config: true,
153165
file_extensions: eslintConfigValueExtractor.extractFileExtensionsValue(),
154166
};
155167
}

packages/code-analyzer-eslint-engine/src/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ const MESSAGE_CATALOG : { [key: string]: string } = {
4545
`The base configuration for TypeScript files adds the rules from the "plugin:@typescript-eslint:all" configuration to Code Analyzer.\n` +
4646
`See https://typescript-eslint.io/rules and https://eslint.org/docs/latest/rules for the lists of rules.`,
4747

48+
ConfigFieldDescription_disable_react_base_config:
49+
`Whether to turn off the default base configuration that supplies the React/JSX rules for .jsx and .tsx files\n` +
50+
`The base configuration for React adds the rules from the "eslint-plugin-react" configuration to Code Analyzer.\n` +
51+
`See https://www.npmjs.com/package/eslint-plugin-react for the list of rules.`,
52+
4853
ConfigFieldDescription_file_extensions:
4954
`Extensions of the files in your workspace that will be used to discover rules.\n` +
5055
`To associate file extensions to the standard ESLint JavaScript rules, LWC rules, or custom JavaScript-based\n` +

packages/code-analyzer-eslint-engine/src/missing-types-for-dependencies.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,24 @@ declare module '@salesforce-ux/eslint-plugin-slds' {
5151
};
5252
export = plugin;
5353
}
54+
55+
// This declaration adds in the missing types for "eslint-plugin-react"
56+
declare module 'eslint-plugin-react' {
57+
import type { ESLint, Linter } from 'eslint';
58+
import type { RuleDefinition } from "@eslint/core";
59+
60+
const plugin: ESLint.Plugin & {
61+
readonly rules: Record<string, RuleDefinition>;
62+
readonly configs: {
63+
readonly recommended: Linter.Config;
64+
readonly all: Linter.Config;
65+
readonly "jsx-runtime": Linter.Config;
66+
readonly flat: {
67+
readonly recommended: Linter.Config;
68+
readonly all: Linter.Config;
69+
readonly "jsx-runtime": Linter.Config;
70+
};
71+
};
72+
};
73+
export = plugin;
74+
}

0 commit comments

Comments
 (0)