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
47 changes: 47 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/code-analyzer-eslint-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.5.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^7.0.1",
"globals": "^16.5.0",
"semver": "^7.7.3",
"typescript": "^5.9.3",
Expand Down
94 changes: 63 additions & 31 deletions packages/code-analyzer-eslint-engine/src/base-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import lwcEslintPluginLwcPlatform from "@lwc/eslint-plugin-lwc-platform";
import salesforceEslintConfigLwc from "@salesforce/eslint-config-lwc";
import sldsEslintPlugin from "@salesforce-ux/eslint-plugin-slds";
import eslintPluginReact from "eslint-plugin-react";
import eslintPluginReactHooks from "eslint-plugin-react-hooks";
import {ESLintEngineConfig} from "./config";
import globals from "globals";

Expand Down Expand Up @@ -63,7 +64,7 @@ export class BaseConfigFactory {
}

private createJavascriptPlusLwcConfigArray(): Linter.Config[] {
let configs: Linter.Config[] = validateAndGetRawLwcConfigArray();
const configs: Linter.Config[] = validateAndGetRawLwcConfigArray();

// Reconstruct languageOptions to avoid mutating the original shared config from the LWC package
// TODO: Remove configFile and sourceType overrides when https://github.com/salesforce/eslint-config-lwc/issues/158 is fixed
Expand All @@ -88,8 +89,17 @@ export class BaseConfigFactory {
}
};

// Swap out eslintJs.configs.recommended with eslintJs.configs.all
configs[1] = eslintJs.configs.all;
// File patterns for different config types
const allJsExtensions = this.engineConfig.file_extensions.javascript;
const lwcExtensions = allJsExtensions.filter(ext => ext !== '.jsx');
const allJsFilePatterns = allJsExtensions.map(ext => `**/*${ext}`);
const lwcFilePatterns = lwcExtensions.map(ext => `**/*${ext}`);

// Base JS rules (eslintJs.configs.all) - applies to ALL JS files including .jsx
const baseJsConfig: Linter.Config = {
...eslintJs.configs.all,
files: allJsFilePatterns
};

// This one rule makes eslint throw an exception if the user doesn't have jest installed (which should be
// optional), so we turn it off for now. See https://github.com/salesforce/eslint-config-lwc/issues/161
Expand All @@ -105,18 +115,15 @@ export class BaseConfigFactory {
'@lwc/lwc-platform/valid-offline-wire': 'off'
}

// Restrict these configs to just javascript files, excluding .jsx
// since those are React files, not LWC components
const lwcExtensions = this.engineConfig.file_extensions.javascript
.filter(ext => ext !== '.jsx');
configs = configs.map(config => {
return {
...config,
files: lwcExtensions.map(ext => `**/*${ext}`)
}
});
// Apply LWC file patterns to LWC-specific configs (excludes .jsx - React files aren't LWC)
// Then insert the base JS config at position 1
const lwcConfigs: Linter.Config[] = configs.map(config => ({
...config,
files: lwcFilePatterns
}));
lwcConfigs[1] = baseJsConfig;

return configs;
return lwcConfigs;
}

private createLwcConfigArray(): Linter.Config[] {
Expand Down Expand Up @@ -191,16 +198,17 @@ export class BaseConfigFactory {
}

/**
* Creates React plugin config for JavaScript files.
* Creates React plugin config for JavaScript and TypeScript files.
*
* React rules are applied to all JS files (.js, .jsx, .cjs, .mjs) - if a file
* doesn't contain React code, the rules simply won't report any violations.
* Includes both eslint-plugin-react and eslint-plugin-react-hooks:
* - react/*: All React rules for JSX/TSX and component patterns
* - react-hooks/rules-of-hooks: Enforces the Rules of Hooks
* - react-hooks/exhaustive-deps: Verifies the list of dependencies for Hooks
*
* Note: TypeScript React support (.tsx) is planned for the next iteration.
* React rules are applied to all JS/TS files - if a file doesn't contain React code,
* the rules simply won't report any violations.
*/
private createReactConfigArray(): Linter.Config[] {
// Apply React rules to all JavaScript and TypeScript files

const jsExtensions = this.engineConfig.file_extensions.javascript;
const tsExtensions = this.engineConfig.file_extensions.typescript;
const reactExtensions = [...new Set([...jsExtensions, ...tsExtensions])];
Expand All @@ -209,22 +217,46 @@ export class BaseConfigFactory {
return [];
}

const filePatterns = reactExtensions.map(ext => `**/*${ext}`);

// Get all rules from eslint-plugin-react's flat config
const reactAllConfig = eslintPluginReact.configs.flat.all;

return [{
...reactAllConfig,
files: reactExtensions.map(ext => `**/*${ext}`),
settings: {
...reactAllConfig.settings,
react: {
// React version - "detect" automatically picks the installed version, falls back to latest
version: 'detect',
// Pragma is the function JSX compiles to (e.g., <div> → React.createElement('div'))
pragma: 'React'
// Get jsx-runtime config to disable outdated rules (react-in-jsx-scope, jsx-uses-react)
// These rules are not needed for React 17+ which is now the standard (released Oct 2020)
const jsxRuntimeConfig = eslintPluginReact.configs.flat['jsx-runtime'];

return [
// React all rules config
{
...reactAllConfig,
files: filePatterns,
settings: {
...reactAllConfig.settings,
react: {
// React version - "detect" automatically picks the installed version, falls back to latest
version: 'detect',
// Pragma is the function JSX compiles to (e.g., <div> → React.createElement('div'))
pragma: 'React'
}
}
},
// jsx-runtime config disables outdated rules for React 17+
{
...jsxRuntimeConfig,
files: filePatterns
},
// React Hooks plugin config - use flat.recommended but only enable the 2 classic rules
// (v7.x includes many React Compiler rules that we filter out)
{
...eslintPluginReactHooks.configs.flat.recommended,
files: filePatterns,
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
}
}
}];
];
}

private useJsBaseConfig(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,28 @@ declare module 'eslint-plugin-react' {
const plugin: ESLint.Plugin & {
readonly rules: Record<string, RuleDefinition>;
readonly configs: {
readonly recommended: Linter.Config;
readonly all: Linter.Config;
readonly "jsx-runtime": Linter.Config;
readonly flat: {
readonly recommended: Linter.Config;
readonly all: Linter.Config;
readonly "jsx-runtime": Linter.Config;
};
};
};
export = plugin;
}

// This declaration adds in the missing types for "eslint-plugin-react-hooks"
// We use configs.flat.recommended but override rules to only enable rules-of-hooks and exhaustive-deps
declare module 'eslint-plugin-react-hooks' {
import type { ESLint, Linter } from 'eslint';
import type { RuleDefinition } from "@eslint/core";

const plugin: ESLint.Plugin & {
readonly rules: Record<string, RuleDefinition>;
readonly configs: {
readonly flat: {
readonly recommended: Linter.Config;
};
};
};
export = plugin;
}
27 changes: 17 additions & 10 deletions packages/code-analyzer-eslint-engine/src/rule-mappings/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ export const RULE_MAPPINGS_REACT_RECOMMENDED: Record<string, {severity: Severity
severity: SeverityLevel.Moderate,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.ERROR_PRONE, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
},
"react/jsx-uses-react": {
// Marks the React import as "used" when JSX is present - needed for classic JSX transform (pre-React 17)
severity: SeverityLevel.Moderate,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.DESIGN, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
},
// Note: react/jsx-uses-react is disabled by jsx-runtime config (not needed for React 17+)
"react/jsx-uses-vars": {
// Marks variables used in JSX as "used" - prevents false positives from no-unused-vars
severity: SeverityLevel.Low,
Expand Down Expand Up @@ -80,10 +76,7 @@ export const RULE_MAPPINGS_REACT_RECOMMENDED: Record<string, {severity: Severity
severity: SeverityLevel.Moderate,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
},
"react/react-in-jsx-scope": {
severity: SeverityLevel.Moderate,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.DESIGN, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
},
// Note: react/react-in-jsx-scope is disabled by jsx-runtime config (not needed for React 17+)
"react/require-render-return": {
severity: SeverityLevel.High,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.ERROR_PRONE, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
Expand Down Expand Up @@ -414,7 +407,21 @@ export const RULE_MAPPINGS_REACT_NOT_RECOMMENDED: Record<string, {severity: Seve

};

// REACT HOOKS PLUGIN RULES (eslint-plugin-react-hooks)
// See https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks
export const RULE_MAPPINGS_REACT_HOOKS: Record<string, {severity: SeverityLevel, tags: string[]}> = {
"react-hooks/rules-of-hooks": {
severity: SeverityLevel.High,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.DESIGN, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
},
"react-hooks/exhaustive-deps": {
severity: SeverityLevel.Moderate,
tags: [COMMON_TAGS.RECOMMENDED, REACT, COMMON_TAGS.CATEGORIES.DESIGN, COMMON_TAGS.LANGUAGES.JAVASCRIPT, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
},
};

export const RULE_MAPPINGS_REACT: Record<string, {severity: SeverityLevel, tags: string[]}> = {
...RULE_MAPPINGS_REACT_RECOMMENDED,
...RULE_MAPPINGS_REACT_NOT_RECOMMENDED
...RULE_MAPPINGS_REACT_NOT_RECOMMENDED,
...RULE_MAPPINGS_REACT_HOOKS
};
Loading