From af9e95d370af2460bbd02a4ac2341ea629685a4f Mon Sep 17 00:00:00 2001 From: David Schach <636977+dschach@users.noreply.github.com> Date: Tue, 10 Nov 2026 20:36:03 +1100 Subject: [PATCH] Stop the LWC rules and the ES2015+ rules from reporting on Aura files The LWC base config and the base JavaScript config apply to every .js file, so the LWC, LWC platform, Lightning, import, and Jest rules, and the rules that ask for syntax that ECMAScript 5 does not have, report correct Aura code. A new config, appended last for the files in an aura folder, turns those rules off. It turns them off rather than ignoring the files, so that the plugins stay registered and a user config that names one of their rules still works. Fixes #519. Also reported as forcedotcom/code-analyzer#2112. --- .../code-analyzer-eslint-engine/package.json | 2 +- .../src/base-config.ts | 64 ++++++++++++++++++- .../test/engine.test.ts | 59 +++++++++++++++++ .../workspaceWithAuraFiles/eslint.config.js | 10 +++ .../default/aura/accountList/accountList.cmp | 5 ++ .../aura/accountList/accountListController.js | 16 +++++ .../main/default/lwc/lwcProbe/lwcProbe.js | 9 +++ 7 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/eslint.config.js create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountList.cmp create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountListController.js create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/lwc/lwcProbe/lwcProbe.js diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index da76978b..20d3abe9 100644 --- a/packages/code-analyzer-eslint-engine/package.json +++ b/packages/code-analyzer-eslint-engine/package.json @@ -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.47.0", + "version": "0.48.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", diff --git a/packages/code-analyzer-eslint-engine/src/base-config.ts b/packages/code-analyzer-eslint-engine/src/base-config.ts index de808652..ee20a3a7 100644 --- a/packages/code-analyzer-eslint-engine/src/base-config.ts +++ b/packages/code-analyzer-eslint-engine/src/base-config.ts @@ -178,6 +178,9 @@ export class BaseConfigFactory { })); lwcConfigs[1] = baseJsConfig; + // Appended last, so that the index-based edits in createLwcConfigArray are unchanged + lwcConfigs.push(createAuraConfig(lwcExtensions, lwcConfigs, true)); + return lwcConfigs; } @@ -194,6 +197,10 @@ export class BaseConfigFactory { // we can work with the original index [4] instead of [3] to avoid confusion. configs.splice(1, 1); + // Without the base JavaScript rules, the Aura config (the last element) turns off only the plugin rules + const lwcExtensions = this.engineConfig.file_extensions.javascript.filter(ext => ext !== '.jsx'); + configs[configs.length - 1] = createAuraConfig(lwcExtensions, configs, false); + return configs; } @@ -231,7 +238,7 @@ export class BaseConfigFactory { }; const base = parserOnly ? {} : { ...eslintJs.configs.all }; - return [{ + const jsConfigs: Linter.Config[] = [{ ...base, files: allJsFilePatterns, languageOptions: { @@ -239,6 +246,10 @@ export class BaseConfigFactory { parserOptions: enhancedParserOptions } }]; + if (!parserOnly) { + jsConfigs.push(createAuraConfig(['.js'], [], true)); + } + return jsConfigs; } else { // Only .jsx, .mjs, .cjs (no .js) - use Espree for better performance const base = parserOnly ? {} : { ...eslintJs.configs.all }; @@ -439,6 +450,57 @@ function validateAndGetRawLwcConfigArray(): Linter.Config[] { return rawLwcConfigs.map(config => { return {... config}}); } +// The base JavaScript rules (from eslintJs.configs.all) that ask for syntax that ECMAScript 5 does not have, such as +// let, const, arrow functions, template literals, and the newer regular expression flags. +const ES5_INCOMPATIBLE_RULES: string[] = [ + 'logical-assignment-operators', + 'no-var', + 'object-shorthand', + 'prefer-arrow-callback', + 'prefer-const', + 'prefer-destructuring', + 'prefer-exponentiation-operator', + 'prefer-named-capture-group', + 'prefer-numeric-literals', + 'prefer-object-has-own', + 'prefer-object-spread', + 'prefer-rest-params', + 'prefer-spread', + 'prefer-template', + 'require-unicode-regexp' +]; + +/** + * Creates the config for the JavaScript of Aura components: the controllers, helpers, and renderers in an "aura" + * folder. Aura components are not Lightning web components, and Salesforce checks their JavaScript with its own + * plugin, @salesforce/eslint-plugin-aura, which parses it as ECMAScript 5. So for these files only, this config turns + * off every rule of the plugins that the given configs register (the LWC, LWC platform, Lightning, import, and Jest + * plugins of the LWC base config), and, when includeBaseJsRules is true, the base JavaScript rules that ECMAScript 5 + * cannot satisfy. The rules are turned off rather than the files ignored, so that the plugins stay registered for + * Aura files and a user config that names one of their rules still works. A user config comes after the base config, + * so a user can still turn any of these rules back on for Aura files. + * See https://github.com/forcedotcom/code-analyzer-core/issues/519 + */ +function createAuraConfig(extensions: string[], pluginConfigs: Linter.Config[], includeBaseJsRules: boolean): Linter.Config { + const rules: Linter.RulesRecord = {}; + for (const config of pluginConfigs) { + for (const [pluginName, plugin] of Object.entries(config.plugins ?? {})) { + for (const ruleName of Object.keys(plugin.rules ?? /* istanbul ignore next */ {})) { + rules[`${pluginName}/${ruleName}`] = 'off'; + } + } + } + if (includeBaseJsRules) { + for (const ruleName of ES5_INCOMPATIBLE_RULES) { + rules[ruleName] = 'off'; + } + } + return { + files: extensions.map(ext => `**/aura/**/*${ext}`), + rules: rules + }; +} + function containsBabelOptions(config: Linter.Config): boolean { return (config.languageOptions?.parserOptions as Linter.ParserOptions)?.babelOptions !== undefined; } diff --git a/packages/code-analyzer-eslint-engine/test/engine.test.ts b/packages/code-analyzer-eslint-engine/test/engine.test.ts index a06ec50a..e5584c7d 100644 --- a/packages/code-analyzer-eslint-engine/test/engine.test.ts +++ b/packages/code-analyzer-eslint-engine/test/engine.test.ts @@ -958,6 +958,65 @@ describe('Typical tests for the runRules method of ESLintEngine', () => { }); }); +describe('Tests for the JavaScript of Aura components', () => { + // See https://github.com/forcedotcom/code-analyzer-core/issues/519 + // accountListController.js is a correct Aura controller: it uses $A, var, and document.querySelector, as Aura code + // may. lwcProbe.js is a Lightning web component with the same var and document.querySelector. + const workspaceWithAuraFiles: string = path.join(testDataFolder, 'workspaceWithAuraFiles'); + const auraRulesToRun: string[] = [ + '@lwc/lwc-platform/no-aura', + '@lwc/lwc/no-document-query', + 'no-var', + 'object-shorthand', + 'prefer-arrow-callback' + ]; + + function violationsIn(results: EngineRunResults, fileName: string): string[] { + return results.violations + .filter(v => path.basename(v.codeLocations[0].file) === fileName) + .map(v => v.ruleName) + .sort(); + } + + it('When running with defaults, then the LWC rules and the ES2015+ rules do not report on Aura files, and still report on LWC files', async () => { + const engine: Engine = await createEngineFromPlugin(DEFAULT_CONFIG_FOR_TESTING); + const results: EngineRunResults = await engine.runRules(auraRulesToRun, + createRunOptions(new Workspace('id', [workspaceWithAuraFiles]))); + + expect(violationsIn(results, 'accountListController.js')).toEqual([]); + expect(violationsIn(results, 'lwcProbe.js')).toEqual(['@lwc/lwc/no-document-query', 'no-var']); + }); + + it('When disable_lwc_base_config=true, then the ES2015+ rules do not report on Aura files, and still report on other files', async () => { + const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, disable_lwc_base_config: true}); + const results: EngineRunResults = await engine.runRules(['no-var', 'object-shorthand', 'prefer-arrow-callback'], + createRunOptions(new Workspace('id', [workspaceWithAuraFiles]))); + + expect(violationsIn(results, 'accountListController.js')).toEqual([]); + expect(violationsIn(results, 'lwcProbe.js')).toEqual(['no-var']); + }); + + it('When disable_javascript_base_config=true, then the LWC rules do not report on Aura files, and still report on LWC files', async () => { + const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, disable_javascript_base_config: true}); + const results: EngineRunResults = await engine.runRules(['@lwc/lwc-platform/no-aura', '@lwc/lwc/no-document-query'], + createRunOptions(new Workspace('id', [workspaceWithAuraFiles]))); + + expect(violationsIn(results, 'accountListController.js')).toEqual([]); + expect(violationsIn(results, 'lwcProbe.js')).toEqual(['@lwc/lwc/no-document-query']); + }); + + it('When a user config turns on an LWC rule for every JavaScript file, then it applies to Aura files too and nothing throws', async () => { + const engine: Engine = await createEngineFromPlugin({...DEFAULT_CONFIG_FOR_TESTING, + eslint_config_file: path.join(workspaceWithAuraFiles, 'eslint.config.js') + }); + const results: EngineRunResults = await engine.runRules(['@lwc/lwc/no-document-query'], + createRunOptions(new Workspace('id', [workspaceWithAuraFiles]))); + + expect(violationsIn(results, 'accountListController.js')).toEqual(['@lwc/lwc/no-document-query']); + expect(violationsIn(results, 'lwcProbe.js')).toEqual(['@lwc/lwc/no-document-query']); + }); +}); + describe('Tests for React Hooks rules', () => { it('React Hooks rules are included in describeRules output', async () => { const engine: Engine = await createEngineFromPlugin(DEFAULT_CONFIG_FOR_TESTING); diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/eslint.config.js b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/eslint.config.js new file mode 100644 index 00000000..f50daa40 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/eslint.config.js @@ -0,0 +1,10 @@ +// A user config that turns on an LWC rule for every JavaScript file, Aura files included. The LWC plugin stays +// registered for Aura files, so this config must not make ESLint throw on the Aura controller. +module.exports = [ + { + files: ['**/*.js'], + rules: { + '@lwc/lwc/no-document-query': 'error' + } + } +]; diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountList.cmp b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountList.cmp new file mode 100644 index 00000000..32ae671e --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountList.cmp @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountListController.js b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountListController.js new file mode 100644 index 00000000..3e8a310d --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/aura/accountList/accountListController.js @@ -0,0 +1,16 @@ +({ + doInit: function (component) { + var action = component.get('c.getAccounts'); + action.setCallback(this, function (response) { + if (response.getState() === 'SUCCESS') { + component.set('v.accounts', response.getReturnValue()); + } + }); + $A.enqueueAction(action); + }, + + handleSelect: function (component) { + var selected = document.querySelector('.slds-is-selected'); + component.set('v.selectedId', selected ? selected.dataset.id : null); + } +}); diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/lwc/lwcProbe/lwcProbe.js b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/lwc/lwcProbe/lwcProbe.js new file mode 100644 index 00000000..6aafeef4 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithAuraFiles/force-app/main/default/lwc/lwcProbe/lwcProbe.js @@ -0,0 +1,9 @@ +import { LightningElement } from 'lwc'; + +export default class LwcProbe extends LightningElement { + connectedCallback() { + var total = 0; + this.total = total; + this.node = document.querySelector('.slds-is-selected'); + } +}