Skip to content

Commit 255ce57

Browse files
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.
1 parent 0173a24 commit 255ce57

5 files changed

Lines changed: 114 additions & 1 deletion

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.43.0",
4+
"version": "0.43.1-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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ export class BaseConfigFactory {
140140
}
141141
};
142142

143+
// Add SSR processor to enable SSR-only linting for SSR-enabled components
144+
// The processor checks -meta.xml files for SSR capabilities and only creates virtual .ssrjs files
145+
// for components with lightning__ServerRenderable or lightning__ServerRenderableWithHydration
146+
configs[0].processor = '@lwc/lwc/ssr';
147+
143148
// File patterns for different config types
144149
const allJsExtensions = this.engineConfig.file_extensions.javascript;
145150
const lwcExtensions = allJsExtensions.filter(ext => ext !== '.jsx');
@@ -194,6 +199,7 @@ export class BaseConfigFactory {
194199
// we can work with the original index [4] instead of [3] to avoid confusion.
195200
configs.splice(1, 1);
196201

202+
// The SSR processor is already configured in configs[0] by createJavascriptPlusLwcConfigArray()
197203
return configs;
198204
}
199205

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
ConfigObject,
3+
Engine,
4+
EngineRunResults,
5+
RunOptions,
6+
Workspace
7+
} from "@salesforce/code-analyzer-engine-api";
8+
import * as path from "node:path";
9+
import {ESLintEnginePlugin} from "../src";
10+
import {ESLintEngine} from "../src/engine";
11+
import {createRunOptions} from "./test-helpers";
12+
13+
jest.setTimeout(60_000);
14+
15+
const testDataFolder: string = path.join(__dirname, 'test-data');
16+
const workspaceWithNonSSRLwc: string = path.join(testDataFolder, 'workspaceWithNonSSRLwc');
17+
18+
async function createEngineFromPlugin(configObject: ConfigObject): Promise<Engine> {
19+
const plugin: ESLintEnginePlugin = new ESLintEnginePlugin();
20+
const engine: ESLintEngine = await plugin.createEngine('eslint', configObject);
21+
engine._runESLintWorkerTask._runInCurrentThreadInsteadofNewThread = true;
22+
return engine;
23+
}
24+
25+
describe('SSR Processor Configuration', () => {
26+
describe('Issue 2049: SSR rules should only apply to SSR-enabled components', () => {
27+
it('should not report SSR rule violations on non-SSR LWC components', async () => {
28+
// Setup: Default config with LWC enabled
29+
const defaultConfig: ConfigObject = {
30+
file_extensions: {
31+
javascript: ['.js'],
32+
typescript: ['.ts'],
33+
html: ['.html'],
34+
css: ['.css'],
35+
other: []
36+
},
37+
config_root: __dirname
38+
};
39+
40+
const engine: Engine = await createEngineFromPlugin(defaultConfig);
41+
const workspace: Workspace = new Workspace('test', [workspaceWithNonSSRLwc],
42+
[path.join(workspaceWithNonSSRLwc, 'helloWorld.js')]);
43+
44+
// Act: Run the engine - the SSR rules are automatically enabled by LWC recommended config
45+
const runOptions: RunOptions = createRunOptions(workspace);
46+
// Run all rules to trigger SSR rules from the LWC recommended config
47+
// Use empty array to run all configured rules
48+
const results: EngineRunResults = await engine.runRules([], runOptions);
49+
50+
// Assert: Non-SSR component should NOT have SSR rule violations
51+
// The SSR processor should filter out SSR rules for non-SSR components
52+
expect(results.violations).toBeDefined();
53+
const ssrViolations = results.violations.filter(v =>
54+
v.ruleName && v.ruleName.includes('ssr-')
55+
);
56+
// Without the processor configured, this will fail because SSR rules will fire
57+
// With the processor configured, SSR rules only fire on SSR-enabled components
58+
expect(ssrViolations.length).toBe(0);
59+
});
60+
61+
it('should parse files without errors when SSR processor is configured', async () => {
62+
const defaultConfig: ConfigObject = {
63+
file_extensions: {
64+
javascript: ['.js'],
65+
typescript: ['.ts'],
66+
html: ['.html'],
67+
css: ['.css'],
68+
other: []
69+
},
70+
config_root: __dirname
71+
};
72+
73+
const engine: Engine = await createEngineFromPlugin(defaultConfig);
74+
const workspace: Workspace = new Workspace('test', [workspaceWithNonSSRLwc],
75+
[path.join(workspaceWithNonSSRLwc, 'helloWorld.js')]);
76+
77+
const runOptions: RunOptions = createRunOptions(workspace);
78+
const results: EngineRunResults = await engine.runRules(['no-debugger'], runOptions);
79+
80+
// Assert: Should not have parsing errors
81+
expect(results.violations).toBeDefined();
82+
const parsingErrors = results.violations.filter(v =>
83+
v.message.includes('Parsing error') || v.message.includes('Unexpected character')
84+
);
85+
expect(parsingErrors.length).toBe(0);
86+
});
87+
});
88+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { LightningElement } from 'lwc';
2+
3+
export default class HelloWorld extends LightningElement {
4+
connectedCallback() {
5+
// This code would violate SSR rules if SSR processor wasn't configured
6+
console.log(window.location); // ssr-no-restricted-browser-globals
7+
if (process.env.NODE_ENV === 'development') { // ssr-no-node-env
8+
console.log('Development mode');
9+
}
10+
}
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>62.0</apiVersion>
4+
<isExposed>true</isExposed>
5+
<targets>
6+
<target>lightning__AppPage</target>
7+
</targets>
8+
</LightningComponentBundle>

0 commit comments

Comments
 (0)