Skip to content

Commit a084f4b

Browse files
authored
NEW: @W-18395429@ Add SLDS Config Settings and Rules to ESLint (v9) (#328)
1 parent e68ed92 commit a084f4b

File tree

22 files changed

+357
-64
lines changed

22 files changed

+357
-64
lines changed

package-lock.json

Lines changed: 83 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 4 additions & 3 deletions
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.29.0",
4+
"version": "0.29.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",
@@ -16,10 +16,11 @@
1616
"@eslint/js": "^9.32.0",
1717
"@lwc/eslint-plugin-lwc": "^3.2.0",
1818
"@lwc/eslint-plugin-lwc-platform": "^6.1.0",
19-
"@salesforce/eslint-config-lwc": "^4.0.0",
20-
"@salesforce/eslint-plugin-lightning": "^2.0.0",
19+
"@salesforce-ux/eslint-plugin-slds": "^0.5.0",
2120
"@salesforce/code-analyzer-engine-api": "0.27.0",
2221
"@salesforce/code-analyzer-eslint8-engine": "0.5.0",
22+
"@salesforce/eslint-config-lwc": "^4.0.0",
23+
"@salesforce/eslint-plugin-lightning": "^2.0.0",
2324
"@types/node": "^20.0.0",
2425
"@typescript-eslint/eslint-plugin": "^8.39.0",
2526
"@typescript-eslint/parser": "^8.33.1",

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import eslintJs from "@eslint/js";
33
import eslintTs from "typescript-eslint";
44
import lwcEslintPluginLwcPlatform from "@lwc/eslint-plugin-lwc-platform";
55
import salesforceEslintConfigLwc from "@salesforce/eslint-config-lwc";
6+
import sldsEslintPlugin from "@salesforce-ux/eslint-plugin-slds";
67
import {ESLintEngineConfig} from "./config";
78
import globals from "globals";
89

@@ -44,6 +45,9 @@ export class BaseConfigFactory {
4445
} else if (this.useLwcBaseConfig()) {
4546
configArray.push(...this.createLwcConfigArray());
4647
}
48+
if (this.useSldsBaseConfig()) {
49+
configArray.push(...this.createSldsConfigArray());
50+
}
4751
if (this.useTsBaseConfig()) {
4852
configArray.push(...this.createTypescriptConfigArray());
4953
}
@@ -105,6 +109,13 @@ export class BaseConfigFactory {
105109
}];
106110
}
107111

112+
private createSldsConfigArray(): Linter.Config[] {
113+
return sldsEslintPlugin.configs['flat/recommended'].map(conf => ({
114+
...conf,
115+
files: this.engineConfig.file_extensions.html.map(ext => `**/*${ext}`)
116+
}));
117+
}
118+
108119
private createTypescriptConfigArray(): Linter.Config[] {
109120
const configs: Linter.Config[] = [];
110121
for (const conf of ([eslintJs.configs.all, ...eslintTs.configs.all] as Linter.Config[])) {
@@ -136,6 +147,10 @@ export class BaseConfigFactory {
136147
return !this.engineConfig.disable_lwc_base_config && this.engineConfig.file_extensions.javascript.length > 0;
137148
}
138149

150+
private useSldsBaseConfig(): boolean {
151+
return !this.engineConfig.disable_slds_base_config && this.engineConfig.file_extensions.html.length > 0;
152+
}
153+
139154
private useTsBaseConfig(): boolean {
140155
return !this.engineConfig.disable_typescript_base_config && this.engineConfig.file_extensions.typescript.length > 0;
141156
}

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,21 @@ export type ESLintEngineConfig = {
2828
// Default: false
2929
disable_lwc_base_config: boolean
3030

31+
// If true then the base configuration that supplies the slds rules for html and cmp files will not be applied.
32+
// Default: false
33+
disable_slds_base_config: boolean
34+
3135
// If true then the base configuration that supplies the standard rules for typescript files will not be applied.
3236
// Default: false
3337
disable_typescript_base_config: boolean
3438

3539
// Extensions of the files in your workspace that will be used to discover rules.
3640
// To associate file extensions to the standard ESLint JavaScript rules, LWC rules, or custom JavaScript-based
3741
// rules, add them under the 'javascript' language. To associate file extensions to the standard TypeScript
38-
// rules or custom TypeScript-based rules, add them under the 'typescript' language. To allow for the
39-
// discovery of custom rules that are associated with any other language, then add the associated file
40-
// extensions under the 'other' language.
42+
// rules or custom TypeScript-based rules, add them under the 'typescript' language. To associate file extensions
43+
// to standard LWC HTML rules, Component (CMP) rules, or custom HTML rules, add them under the 'html' language.
44+
// To allow for the discovery of custom rules that are associated with any other language, then add the associated
45+
// file extensions under the 'other' language.
4146
file_extensions: FileExtensionsObject
4247

4348
// (INTERNAL USE ONLY) Copy of the code analyzer config root.
@@ -47,6 +52,7 @@ export type ESLintEngineConfig = {
4752
export type FileExtensionsObject = {
4853
javascript: string[],
4954
typescript: string[],
55+
html: string[],
5056
other: string[]
5157
};
5258

@@ -56,10 +62,12 @@ export const DEFAULT_CONFIG: ESLintEngineConfig = {
5662
auto_discover_eslint_config: false,
5763
disable_javascript_base_config: false,
5864
disable_lwc_base_config: false,
65+
disable_slds_base_config: false,
5966
disable_typescript_base_config: false,
6067
file_extensions: {
6168
javascript: ['.js', '.cjs', '.mjs'],
6269
typescript: ['.ts'],
70+
html: ['.html', '.htm', '.cmp'],
6371
other: []
6472
},
6573
config_root: process.cwd() // INTERNAL USE ONLY
@@ -93,6 +101,11 @@ export const ESLINT_ENGINE_CONFIG_DESCRIPTION: ConfigDescription = {
93101
valueType: "boolean",
94102
defaultValue: DEFAULT_CONFIG.disable_lwc_base_config
95103
},
104+
disable_slds_base_config: {
105+
descriptionText: getMessage('ConfigFieldDescription_disable_slds_base_config'),
106+
valueType: "boolean",
107+
defaultValue: DEFAULT_CONFIG.disable_slds_base_config
108+
},
96109
disable_typescript_base_config: {
97110
descriptionText: getMessage('ConfigFieldDescription_disable_typescript_base_config'),
98111
valueType: "boolean",
@@ -122,7 +135,7 @@ export const LEGACY_ESLINT_IGNORE_FILE: string = '.eslintignore';
122135
export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtractor): ESLintEngineConfig {
123136
configValueExtractor.validateContainsOnlySpecifiedKeys(['eslint_config_file', 'eslint_ignore_file',
124137
'auto_discover_eslint_config', 'disable_javascript_base_config', 'disable_lwc_base_config',
125-
'disable_typescript_base_config', 'file_extensions']);
138+
'disable_slds_base_config', 'disable_typescript_base_config', 'file_extensions']);
126139

127140
const eslintConfigValueExtractor: ESLintEngineConfigValueExtractor = new ESLintEngineConfigValueExtractor(configValueExtractor);
128141
return {
@@ -132,6 +145,7 @@ export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtr
132145
auto_discover_eslint_config: eslintConfigValueExtractor.extractBooleanValue('auto_discover_eslint_config'),
133146
disable_javascript_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_javascript_base_config'),
134147
disable_lwc_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_lwc_base_config'),
148+
disable_slds_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_slds_base_config'),
135149
disable_typescript_base_config: eslintConfigValueExtractor.extractBooleanValue('disable_typescript_base_config'),
136150
file_extensions: eslintConfigValueExtractor.extractFileExtensionsValue(),
137151
};

packages/code-analyzer-eslint-engine/src/messages.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ const MESSAGE_CATALOG : { [key: string]: string } = {
3535
`and "plugin:@lwc/lwc-platform/recommended" configurations to Code Analyzer.\n` +
3636
`See https://github.com/salesforce/eslint-config-lwc and https://www.npmjs.com/package/@lwc/eslint-plugin-lwc-platform.`,
3737

38+
ConfigFieldDescription_disable_slds_base_config:
39+
`Whether to turn off the default base configuration that supplies the SLDS rules for Lightning Web Components and Aura Components.\n` +
40+
`The base configuration for SLDS adds the rules from the "plugin:@salesforce-ux/eslint-plugin-slds/recommended" configuration to Code Analyzer.\n` +
41+
`See https://www.npmjs.com/package/@salesforce-ux/eslint-plugin-slds`,
42+
3843
ConfigFieldDescription_disable_typescript_base_config:
3944
`Whether to turn off the default base configuration that supplies the standard rules for TypeScript files\n` +
4045
`The base configuration for TypeScript files adds the rules from the "plugin:@typescript-eslint:all" configuration to Code Analyzer.\n` +
@@ -44,9 +49,10 @@ const MESSAGE_CATALOG : { [key: string]: string } = {
4449
`Extensions of the files in your workspace that will be used to discover rules.\n` +
4550
`To associate file extensions to the standard ESLint JavaScript rules, LWC rules, or custom JavaScript-based\n` +
4651
`rules, add them under the 'javascript' language. To associate file extensions to the standard TypeScript\n` +
47-
`rules or custom TypeScript-based rules, add them under the 'typescript' language. To allow for the\n` +
48-
`discovery of custom rules that are associated with any other language, then add the associated file\n` +
49-
`extensions under the 'other' language.`,
52+
`rules or custom TypeScript-based rules, add them under the 'typescript' language.\n` +
53+
`To associate file extensions to standard LWC HTML rules, Component (CMP) rules, or custom HTML rules, add them\n` +
54+
`under the 'html' language. To allow for the discovery of custom rules that are associated with any other language,\n` +
55+
`then add the associated file extensions under the 'other' language.`,
5056

5157
UnsupportedEngineName:
5258
`The ESLintEnginePlugin doesn't support an engine with name '%s'.`,

packages/code-analyzer-eslint-engine/src/missing-types-for-dependencies.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,18 @@ declare module '@salesforce/eslint-config-lwc' {
3535
};
3636
export = moduleObject;
3737
}
38+
39+
// This declaration adds in the missing types for "@salesforce-ux/eslint-plugin-slds" whose package.json file's main field points to:
40+
// node_modules/@salesforce-ux/eslint-plugin-slds/build/index.js
41+
declare module '@salesforce-ux/eslint-plugin-slds' {
42+
import type { ESLint, Linter } from 'eslint';
43+
import type { RuleDefinition } from "@eslint/core";
44+
45+
const plugin: ESLint.ObjectMetaProperties & {
46+
readonly rules: Record<string, RuleDefinition>;
47+
readonly configs: {
48+
readonly "flat/recommended": Linter.Config[];
49+
};
50+
};
51+
export = plugin;
52+
}

packages/code-analyzer-eslint-engine/src/rule-mappings.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,5 +1595,21 @@ export const RULE_MAPPINGS: Record<string, {severity: SeverityLevel, tags: strin
15951595
"@typescript-eslint/use-unknown-in-catch-callback-variable": {
15961596
severity: SeverityLevel.Moderate,
15971597
tags: [/* NOT RECOMMENDED */ COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.TYPESCRIPT]
1598+
},
1599+
1600+
// =================================================================================================================
1601+
// SLDS/HTML BASE RULES - @salesforce-ux/eslint-plugin-slds
1602+
// =================================================================================================================
1603+
"@salesforce-ux/slds/enforce-bem-usage": {
1604+
severity: SeverityLevel.Low,
1605+
tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.HTML]
1606+
},
1607+
"@salesforce-ux/slds/modal-close-button-issue": {
1608+
severity: SeverityLevel.Moderate,
1609+
tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.BEST_PRACTICES, COMMON_TAGS.LANGUAGES.HTML]
1610+
},
1611+
"@salesforce-ux/slds/no-deprecated-classes-slds2": {
1612+
severity: SeverityLevel.High,
1613+
tags: [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CATEGORIES.ERROR_PRONE, COMMON_TAGS.LANGUAGES.HTML]
15981614
}
15991615
}

packages/code-analyzer-eslint-engine/src/workspace.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class SpecifiedESLintWorkspace extends ESLintWorkspace {
9797
const relevantFileExtensions: string[] = [
9898
...this.fileExts.javascript,
9999
...this.fileExts.typescript,
100+
...this.fileExts.html,
100101
...this.fileExts.other];
101102
this.cachedPreIgnoredFilesToScan = (await this.workspace.getTargetedFiles())
102103
.filter(file => relevantFileExtensions.includes(path.extname(file).toLowerCase()));
@@ -126,6 +127,7 @@ class UnspecifiedESLintWorkspace extends ESLintWorkspace {
126127
const relevantFileExtensions: string[] = [
127128
...this.fileExts.javascript,
128129
...this.fileExts.typescript,
130+
...this.fileExts.html,
129131
...this.fileExts.other];
130132
return relevantFileExtensions.map(ext => `${baseDir}${path.sep}placeholderCandidateFile${ext}`);
131133
}

packages/code-analyzer-eslint-engine/test/end-to-end.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ describe('End to end test', () => {
6969
'@typescript-eslint/no-unused-vars', // there are 4 of these
7070
'no-invalid-regexp'
7171
]));
72+
const violationsFromHTMLFile: Violation[] = engineRunResults.violations.filter(v => path.extname(v.codeLocations[0].file) === '.html');
73+
expect(violationsFromHTMLFile).toHaveLength(1);
74+
expect(violationsFromHTMLFile[0].ruleName).toEqual('@salesforce-ux/slds/enforce-bem-usage');
7275

7376
const warnLogs: LogEvent[] = logEvents.filter(e => e.logLevel == LogLevel.Warn);
7477
expect(warnLogs).toHaveLength(0);
@@ -109,6 +112,10 @@ describe('End to end test', () => {
109112
'no-invalid-regexp'
110113
]));
111114

115+
// SLDS violations are only relevant for v9+
116+
const violationsFromHTMLFile: Violation[] = engineRunResults.violations.filter(v => path.extname(v.codeLocations[0].file) === '.html');
117+
expect(violationsFromHTMLFile).toHaveLength(0);
118+
112119
const warnLogs: LogEvent[] = logEvents.filter(e => e.logLevel == LogLevel.Warn);
113120
expect(warnLogs).toHaveLength(1);
114121
expect(warnLogs[0].message).toContain('Using ESLint v8 instead of ESLint v9');

0 commit comments

Comments
 (0)