From eeb9b6c9b1de4929eed290c18ed726f27703ab49 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Mon, 15 Jun 2026 16:03:28 +0530 Subject: [PATCH] fix: Add SSR processor configuration to prevent false positives on non-SSR LWC components (#2049) The SSR ESLint rules from @lwc/eslint-plugin-lwc were incorrectly firing on all LWC components instead of only SSR-enabled components. This was because Code Analyzer's base-config.ts uses the LWC recommended configuration which includes SSR rules, but did not configure the SSR processor. The SSR processor (@lwc/eslint-plugin-lwc/processors/ssr) is designed to check each component's -meta.xml file for lightning__ServerRenderable or lightning__ServerRenderableWithHydration capabilities and only create virtual .ssrjs files for SSR components. Changes: - Added processor: '@lwc/lwc/ssr' to createJavascriptPlusLwcConfigArray() in base-config.ts - Added test coverage for SSR processor configuration - Created test data with non-SSR LWC component to verify fix - Bumped version to 0.43.1-SNAPSHOT Fixes issue where SSR rules were incorrectly flagging non-SSR components that use browser globals or process.env.NODE_ENV. --- .../code-analyzer-eslint-engine/package.json | 2 +- .../src/base-config.ts | 6 ++ .../test/ssr-processor.test.ts | 88 +++++++++++++++++++ .../workspaceWithNonSSRLwc/helloWorld.js | 11 +++ .../helloWorld.js-meta.xml | 8 ++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 packages/code-analyzer-eslint-engine/test/ssr-processor.test.ts create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js create mode 100644 packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js-meta.xml diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index 0af286d5..647f73b0 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.43.0", + "version": "0.43.1-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..d4cf3ce5 100644 --- a/packages/code-analyzer-eslint-engine/src/base-config.ts +++ b/packages/code-analyzer-eslint-engine/src/base-config.ts @@ -140,6 +140,11 @@ export class BaseConfigFactory { } }; + // Add SSR processor to enable SSR-only linting for SSR-enabled components + // The processor checks -meta.xml files for SSR capabilities and only creates virtual .ssrjs files + // for components with lightning__ServerRenderable or lightning__ServerRenderableWithHydration + configs[0].processor = '@lwc/lwc/ssr'; + // File patterns for different config types const allJsExtensions = this.engineConfig.file_extensions.javascript; const lwcExtensions = allJsExtensions.filter(ext => ext !== '.jsx'); @@ -194,6 +199,7 @@ export class BaseConfigFactory { // we can work with the original index [4] instead of [3] to avoid confusion. configs.splice(1, 1); + // The SSR processor is already configured in configs[0] by createJavascriptPlusLwcConfigArray() return configs; } diff --git a/packages/code-analyzer-eslint-engine/test/ssr-processor.test.ts b/packages/code-analyzer-eslint-engine/test/ssr-processor.test.ts new file mode 100644 index 00000000..c09a11d3 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/ssr-processor.test.ts @@ -0,0 +1,88 @@ +import { + ConfigObject, + Engine, + EngineRunResults, + RunOptions, + Workspace +} from "@salesforce/code-analyzer-engine-api"; +import * as path from "node:path"; +import {ESLintEnginePlugin} from "../src"; +import {ESLintEngine} from "../src/engine"; +import {createRunOptions} from "./test-helpers"; + +jest.setTimeout(60_000); + +const testDataFolder: string = path.join(__dirname, 'test-data'); +const workspaceWithNonSSRLwc: string = path.join(testDataFolder, 'workspaceWithNonSSRLwc'); + +async function createEngineFromPlugin(configObject: ConfigObject): Promise { + const plugin: ESLintEnginePlugin = new ESLintEnginePlugin(); + const engine: ESLintEngine = await plugin.createEngine('eslint', configObject); + engine._runESLintWorkerTask._runInCurrentThreadInsteadofNewThread = true; + return engine; +} + +describe('SSR Processor Configuration', () => { + describe('Issue 2049: SSR rules should only apply to SSR-enabled components', () => { + it('should not report SSR rule violations on non-SSR LWC components', async () => { + // Setup: Default config with LWC enabled + const defaultConfig: ConfigObject = { + file_extensions: { + javascript: ['.js'], + typescript: ['.ts'], + html: ['.html'], + css: ['.css'], + other: [] + }, + config_root: __dirname + }; + + const engine: Engine = await createEngineFromPlugin(defaultConfig); + const workspace: Workspace = new Workspace('test', [workspaceWithNonSSRLwc], + [path.join(workspaceWithNonSSRLwc, 'helloWorld.js')]); + + // Act: Run the engine - the SSR rules are automatically enabled by LWC recommended config + const runOptions: RunOptions = createRunOptions(workspace); + // Run all rules to trigger SSR rules from the LWC recommended config + // Use empty array to run all configured rules + const results: EngineRunResults = await engine.runRules([], runOptions); + + // Assert: Non-SSR component should NOT have SSR rule violations + // The SSR processor should filter out SSR rules for non-SSR components + expect(results.violations).toBeDefined(); + const ssrViolations = results.violations.filter(v => + v.ruleName && v.ruleName.includes('ssr-') + ); + // Without the processor configured, this will fail because SSR rules will fire + // With the processor configured, SSR rules only fire on SSR-enabled components + expect(ssrViolations.length).toBe(0); + }); + + it('should parse files without errors when SSR processor is configured', async () => { + const defaultConfig: ConfigObject = { + file_extensions: { + javascript: ['.js'], + typescript: ['.ts'], + html: ['.html'], + css: ['.css'], + other: [] + }, + config_root: __dirname + }; + + const engine: Engine = await createEngineFromPlugin(defaultConfig); + const workspace: Workspace = new Workspace('test', [workspaceWithNonSSRLwc], + [path.join(workspaceWithNonSSRLwc, 'helloWorld.js')]); + + const runOptions: RunOptions = createRunOptions(workspace); + const results: EngineRunResults = await engine.runRules(['no-debugger'], runOptions); + + // Assert: Should not have parsing errors + expect(results.violations).toBeDefined(); + const parsingErrors = results.violations.filter(v => + v.message.includes('Parsing error') || v.message.includes('Unexpected character') + ); + expect(parsingErrors.length).toBe(0); + }); + }); +}); diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js new file mode 100644 index 00000000..0a5c2a93 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js @@ -0,0 +1,11 @@ +import { LightningElement } from 'lwc'; + +export default class HelloWorld extends LightningElement { + connectedCallback() { + // This code would violate SSR rules if SSR processor wasn't configured + console.log(window.location); // ssr-no-restricted-browser-globals + if (process.env.NODE_ENV === 'development') { // ssr-no-node-env + console.log('Development mode'); + } + } +} diff --git a/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js-meta.xml b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js-meta.xml new file mode 100644 index 00000000..2aabf244 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/test/test-data/workspaceWithNonSSRLwc/helloWorld.js-meta.xml @@ -0,0 +1,8 @@ + + + 62.0 + true + + lightning__AppPage + +