Skip to content
Merged
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 packages/code-analyzer-eslint-engine/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@salesforce/code-analyzer-eslint-engine",
"description": "Plugin package that adds 'eslint' as an engine into Salesforce Code Analyzer",
"version": "0.40.1-SNAPSHOT",
"version": "0.40.2-SNAPSHOT",
"author": "The Salesforce Code Analyzer Team",
"license": "BSD-3-Clause",
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
Expand Down
131 changes: 131 additions & 0 deletions packages/code-analyzer-eslint-engine/src/base-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@ import eslintPluginJsxA11y from "eslint-plugin-jsx-a11y";
import {ESLintEngineConfig} from "./config";
import globals from "globals";

/**
* BaseConfigFactory creates ESLint configurations based on user preferences and file types.
*
* Parser Selection Strategy:
* ==========================
*
* JavaScript Files (.js, .jsx, .mjs, .cjs):
* -----------------------------------------
* 1. Both JS + LWC enabled → Full LWC config with Babel parser and all JS/LWC rules
* 2. Only JS enabled → JavaScript config with smart parser selection:
* - .js files: Babel (supports decorators + JSX)
* - .jsx/.mjs/.cjs: Espree (better performance)
* 3. Only LWC enabled → LWC config with Babel parser (no base JS rules)
* 4. Both JS + LWC disabled → Minimal parser config only (NO rules applied):
* - .js files: Babel for decorator support
* - .jsx/.mjs/.cjs: Espree with JSX support
*
* TypeScript Files (.ts, .tsx, .mts, .cts):
* ------------------------------------------
* 1. TS enabled → Full TypeScript config with typescript-eslint parser,
* all TS rules, and projectService for type-aware linting
* 2. TS disabled → Minimal TypeScript parser config (NO rules applied):
* - Parser can handle TS syntax (decorators, types)
* - No projectService (allows files outside tsconfig.json)
* - Only non-type-aware rules can run
*
* React Files (JSX/TSX):
* ----------------------
* 1. React enabled → All React rules + react-hooks rules
* 2. React disabled → No React rules, but JSX parsing still works via
* JS/TS parser configs above
*
* SLDS (CSS/HTML):
* ----------------
* Separate concern - applies SLDS-specific linting to CSS/HTML files when enabled.
*
* Key Design Principle:
* ---------------------
* When users disable base configs (disable_javascript_base_config, disable_lwc_base_config,
* disable_typescript_base_config), they disable the BASE RULES but NOT parsing capability.
* We always configure parsers to ensure files can be analyzed, even with minimal rule sets.
*/
export class BaseConfigFactory {
private readonly engineConfig: ESLintEngineConfig;

Expand Down Expand Up @@ -47,6 +89,10 @@ export class BaseConfigFactory {
configArray.push(...this.createJavascriptConfigArray());
} else if (this.useLwcBaseConfig()) {
configArray.push(...this.createLwcConfigArray());
} else if (this.engineConfig.file_extensions.javascript.length > 0) {
// When both base configs are disabled, we still need to configure a parser for JavaScript files
// to avoid ESLint falling back to Espree which can't parse decorators
configArray.push(...this.createMinimalJavascriptParserConfig());
}
if (this.useSldsCSSBaseConfig()) {
configArray.push(...this.createSldsCSSConfigArray());
Expand All @@ -56,6 +102,10 @@ export class BaseConfigFactory {
}
if (this.useTsBaseConfig()) {
configArray.push(...this.createTypescriptConfigArray());
} else if (this.engineConfig.file_extensions.typescript.length > 0) {
// When TS base config is disabled, we still need to configure a parser for TypeScript files
// to avoid ESLint falling back to a parser that can't handle TypeScript syntax
configArray.push(...this.createMinimalTypescriptParserConfig());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to introduce new function here? Can we directly use useTsBaseConfig? Or maybe combine this.useTsBaseConfig() with or of this.engineConfig.file_extensions.typescript.length > 0.

}
// Add React plugin config for JSX files
if (this.useReactBaseConfig()) {
Expand Down Expand Up @@ -195,6 +245,87 @@ export class BaseConfigFactory {
}
}

private createMinimalJavascriptParserConfig(): Linter.Config[] {
// When both disable_javascript_base_config and disable_lwc_base_config are true,
// we still need to configure a parser for JavaScript files to avoid ESLint falling
// back to Espree which can't parse decorators. This method configures ONLY the parser,
// without applying any base JavaScript or LWC rules.
const hasJsExtension = this.engineConfig.file_extensions.javascript.includes('.js');

if (hasJsExtension) {
// .js files might have LWC decorators - use Babel parser with decorator support
const lwcConfig = validateAndGetRawLwcConfigArray()[0];
const babelParser = lwcConfig.languageOptions?.parser;
const originalParserOptions = lwcConfig.languageOptions?.parserOptions as Linter.ParserOptions;
const originalBabelOptions = originalParserOptions.babelOptions || {};

// Add @babel/preset-react to support JSX in React files alongside LWC files
const enhancedParserOptions = {
...originalParserOptions,
babelOptions: {
...originalBabelOptions,
configFile: false,
// Add React preset for JSX support (.jsx files and React in .js files)
presets: [...(originalBabelOptions.presets || []), require.resolve('@babel/preset-react')]
}
Comment on lines +256 to +270
Copy link
Contributor

@namrata111f namrata111f Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here can we use the createJavascriptPlusLwcConfigArray and useJsBaseConfig instead here.

};

return [{
files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`),
languageOptions: {
parser: babelParser,
parserOptions: enhancedParserOptions
}
}];
} else {
// Only .jsx, .mjs, .cjs (no .js) - use Espree for better performance
return [{
files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`),
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true // Enable JSX parsing for React/JSX files
}
}
}
}];
}
}

private createMinimalTypescriptParserConfig(): Linter.Config[] {
// When disable_typescript_base_config is true, we still need to configure a parser
// for TypeScript files to avoid ESLint falling back to Espree which can't parse
// TypeScript syntax. This method configures ONLY the parser, without applying base rules.
//
// IMPORTANT LIMITATION - Type-Aware Rules:
// =========================================
// We intentionally do NOT set projectService here. The projectService option
// is used for type-aware linting, but it requires files to be in a tsconfig.json project.
// Without projectService, the TypeScript parser can still parse TypeScript syntax
// (decorators, type annotations, etc.) but ONLY non-type-aware rules can run.
//
// Type-aware rules (e.g., @typescript-eslint/await-thenable) will NOT work with this
// minimal config. If users need type-aware rules, they should enable the TypeScript
// base config (disable_typescript_base_config: false).
//
// This trade-off allows users to:
// ✓ Parse TypeScript files without a tsconfig.json
// ✓ Run basic ESLint rules on TypeScript code
// ✗ Cannot use type-aware TypeScript rules

// Get the first TypeScript config which contains the parser setup
const tsConfig = (eslintTs.configs.all as Linter.Config[])[0];

return [{
files: this.engineConfig.file_extensions.typescript.map(ext => `**/*${ext}`),
languageOptions: {
...(tsConfig.languageOptions ?? {})
// Explicitly omit parserOptions.projectService to allow parsing files
// that aren't part of a TypeScript project
}
}];
}

private createSldsHTMLConfigArray(): Linter.Config[] {
return sldsEslintPlugin.configs['flat/recommended-html'].map((htmlConfig: Linter.Config) => {
return {
Expand Down
Loading