Skip to content

Commit f867f41

Browse files
Merge branch 'dev' into feature/pmd-ast-dump
2 parents 73b8cf3 + 1bc8840 commit f867f41

3 files changed

Lines changed: 197 additions & 123 deletions

File tree

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

Lines changed: 1 addition & 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.40.2",
4+
"version": "0.41.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",

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

Lines changed: 52 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ export class BaseConfigFactory {
8989
configArray.push(...this.createJavascriptConfigArray());
9090
} else if (this.useLwcBaseConfig()) {
9191
configArray.push(...this.createLwcConfigArray());
92-
} else if (this.engineConfig.file_extensions.javascript.length > 0) {
92+
} else {
9393
// When both base configs are disabled, we still need to configure a parser for JavaScript files
94-
// to avoid ESLint falling back to Espree which can't parse decorators
95-
configArray.push(...this.createMinimalJavascriptParserConfig());
94+
// to avoid ESLint falling back to Espree which can't parse decorators (returns [] if no JS extensions)
95+
configArray.push(...this.createJavascriptConfigArray(true));
9696
}
9797
if (this.useSldsCSSBaseConfig()) {
9898
configArray.push(...this.createSldsCSSConfigArray());
@@ -105,7 +105,7 @@ export class BaseConfigFactory {
105105
} else if (this.engineConfig.file_extensions.typescript.length > 0) {
106106
// When TS base config is disabled, we still need to configure a parser for TypeScript files
107107
// to avoid ESLint falling back to a parser that can't handle TypeScript syntax
108-
configArray.push(...this.createMinimalTypescriptParserConfig());
108+
configArray.push(...this.createTypescriptConfigArray(true));
109109
}
110110
// Add React plugin config for JSX files
111111
if (this.useReactBaseConfig()) {
@@ -197,11 +197,20 @@ export class BaseConfigFactory {
197197
return configs;
198198
}
199199

200-
private createJavascriptConfigArray(): Linter.Config[] {
200+
/**
201+
* Builds JavaScript ESLint config. When parserOnly is true (both JS and LWC base configs disabled), returns
202+
* only parser config with no rules so ESLint doesn't fall back to Espree which can't parse decorators.
203+
*/
204+
private createJavascriptConfigArray(parserOnly?: boolean): Linter.Config[] {
205+
const jsExtensions = this.engineConfig.file_extensions.javascript;
206+
if (parserOnly && jsExtensions.length === 0) {
207+
return [];
208+
}
201209
// Smart parser selection based on file extensions:
202210
// - .js files may contain LWC decorators (@api, @track, @wire) → need Babel
203211
// - .jsx, .mjs, .cjs are typically React or modules without decorators → can use Espree (faster)
204-
const hasJsExtension = this.engineConfig.file_extensions.javascript.includes('.js');
212+
const allJsFilePatterns = jsExtensions.map(ext => `**/*${ext}`);
213+
const hasJsExtension = jsExtensions.includes('.js');
205214

206215
if (hasJsExtension) {
207216
// .js files might have LWC decorators - use Babel parser with decorator support
@@ -221,19 +230,21 @@ export class BaseConfigFactory {
221230
}
222231
};
223232

233+
const base = parserOnly ? {} : { ...eslintJs.configs.all };
224234
return [{
225-
... eslintJs.configs.all,
226-
files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`),
235+
...base,
236+
files: allJsFilePatterns,
227237
languageOptions: {
228238
parser: babelParser,
229239
parserOptions: enhancedParserOptions
230240
}
231241
}];
232242
} else {
233243
// Only .jsx, .mjs, .cjs (no .js) - use Espree for better performance
244+
const base = parserOnly ? {} : { ...eslintJs.configs.all };
234245
return [{
235-
... eslintJs.configs.all,
236-
files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`),
246+
...base,
247+
files: allJsFilePatterns,
237248
languageOptions: {
238249
parserOptions: {
239250
ecmaFeatures: {
@@ -245,85 +256,41 @@ export class BaseConfigFactory {
245256
}
246257
}
247258

248-
private createMinimalJavascriptParserConfig(): Linter.Config[] {
249-
// When both disable_javascript_base_config and disable_lwc_base_config are true,
250-
// we still need to configure a parser for JavaScript files to avoid ESLint falling
251-
// back to Espree which can't parse decorators. This method configures ONLY the parser,
252-
// without applying any base JavaScript or LWC rules.
253-
const hasJsExtension = this.engineConfig.file_extensions.javascript.includes('.js');
254-
255-
if (hasJsExtension) {
256-
// .js files might have LWC decorators - use Babel parser with decorator support
257-
const lwcConfig = validateAndGetRawLwcConfigArray()[0];
258-
const babelParser = lwcConfig.languageOptions?.parser;
259-
const originalParserOptions = lwcConfig.languageOptions?.parserOptions as Linter.ParserOptions;
260-
const originalBabelOptions = originalParserOptions.babelOptions || {};
261-
262-
// Add @babel/preset-react to support JSX in React files alongside LWC files
263-
const enhancedParserOptions = {
264-
...originalParserOptions,
265-
babelOptions: {
266-
...originalBabelOptions,
267-
configFile: false,
268-
// Add React preset for JSX support (.jsx files and React in .js files)
269-
presets: [...(originalBabelOptions.presets || []), require.resolve('@babel/preset-react')]
270-
}
271-
};
259+
/**
260+
* Builds TypeScript ESLint config. When parserOnly is true (TS base config disabled), returns only parser config
261+
* with no rules and no projectService so files outside tsconfig.json can be parsed; type-aware rules won't run.
262+
* Both paths use the same logic to build languageOptions/parserOptions; only rules and projectService differ.
263+
*/
264+
private createTypescriptConfigArray(parserOnly?: boolean): Linter.Config[] {
265+
const filePatterns = this.engineConfig.file_extensions.typescript.map(ext => `**/*${ext}`);
266+
const configsToApply = (parserOnly
267+
? (eslintTs.configs.all as Linter.Config[])
268+
: ([eslintJs.configs.all, ...eslintTs.configs.all] as Linter.Config[]));
269+
const configs: Linter.Config[] = [];
272270

273-
return [{
274-
files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`),
275-
languageOptions: {
276-
parser: babelParser,
277-
parserOptions: enhancedParserOptions
278-
}
279-
}];
280-
} else {
281-
// Only .jsx, .mjs, .cjs (no .js) - use Espree for better performance
282-
return [{
283-
files: this.engineConfig.file_extensions.javascript.map(ext => `**/*${ext}`),
271+
for (const conf of configsToApply) {
272+
const baseLanguageOptions = conf.languageOptions ?? {};
273+
const baseParserOptions = (baseLanguageOptions.parserOptions as Linter.ParserOptions) ?? {};
274+
const parserOptions: Linter.ParserOptions = parserOnly
275+
? (() => { const { projectService: _omit, ...rest } = baseParserOptions; return rest; })()
276+
: {
277+
...baseParserOptions,
278+
// Finds the tsconfig.json file nearest to each source file. This should work for most users.
279+
// If not, then we may consider letting user specify this via config or alternatively users can
280+
// just set disable_typescript_base_config=true and configure typescript in their own eslint
281+
// config file. See https://typescript-eslint.io/packages/parser/#projectservice
282+
projectService: true
283+
};
284+
configs.push({
285+
...(parserOnly ? {} : conf),
286+
files: filePatterns,
284287
languageOptions: {
285-
parserOptions: {
286-
ecmaFeatures: {
287-
jsx: true // Enable JSX parsing for React/JSX files
288-
}
289-
}
288+
...baseLanguageOptions,
289+
parserOptions
290290
}
291-
}];
291+
});
292292
}
293-
}
294-
295-
private createMinimalTypescriptParserConfig(): Linter.Config[] {
296-
// When disable_typescript_base_config is true, we still need to configure a parser
297-
// for TypeScript files to avoid ESLint falling back to Espree which can't parse
298-
// TypeScript syntax. This method configures ONLY the parser, without applying base rules.
299-
//
300-
// IMPORTANT LIMITATION - Type-Aware Rules:
301-
// =========================================
302-
// We intentionally do NOT set projectService here. The projectService option
303-
// is used for type-aware linting, but it requires files to be in a tsconfig.json project.
304-
// Without projectService, the TypeScript parser can still parse TypeScript syntax
305-
// (decorators, type annotations, etc.) but ONLY non-type-aware rules can run.
306-
//
307-
// Type-aware rules (e.g., @typescript-eslint/await-thenable) will NOT work with this
308-
// minimal config. If users need type-aware rules, they should enable the TypeScript
309-
// base config (disable_typescript_base_config: false).
310-
//
311-
// This trade-off allows users to:
312-
// ✓ Parse TypeScript files without a tsconfig.json
313-
// ✓ Run basic ESLint rules on TypeScript code
314-
// ✗ Cannot use type-aware TypeScript rules
315-
316-
// Get the first TypeScript config which contains the parser setup
317-
const tsConfig = (eslintTs.configs.all as Linter.Config[])[0];
318-
319-
return [{
320-
files: this.engineConfig.file_extensions.typescript.map(ext => `**/*${ext}`),
321-
languageOptions: {
322-
...(tsConfig.languageOptions ?? {})
323-
// Explicitly omit parserOptions.projectService to allow parsing files
324-
// that aren't part of a TypeScript project
325-
}
326-
}];
293+
return configs;
327294
}
328295

329296
private createSldsHTMLConfigArray(): Linter.Config[] {
@@ -344,29 +311,6 @@ export class BaseConfigFactory {
344311
});
345312
}
346313

347-
private createTypescriptConfigArray(): Linter.Config[] {
348-
const configs: Linter.Config[] = [];
349-
for (const conf of ([eslintJs.configs.all, ...eslintTs.configs.all] as Linter.Config[])) {
350-
configs.push({
351-
...conf,
352-
files: this.engineConfig.file_extensions.typescript.map(ext => `**/*${ext}`),
353-
languageOptions: {
354-
... (conf.languageOptions ?? {}),
355-
parserOptions: {
356-
... (conf.languageOptions?.parserOptions ?? {}),
357-
358-
// Finds the tsconfig.json file nearest to each source file. This should work for most users.
359-
// If not, then we may consider letting user specify this via config or alternatively users can
360-
// just set disable_typescript_base_config=true and configure typescript in their own eslint
361-
// config file. See https://typescript-eslint.io/packages/parser/#projectservice
362-
projectService: true
363-
}
364-
}
365-
});
366-
}
367-
return configs;
368-
}
369-
370314
/**
371315
* Creates React plugin config for JavaScript and TypeScript files.
372316
*

0 commit comments

Comments
 (0)