Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5a761bb
Add engine-agnostic data model for fixes and suggestions
nikhil-mittal-165 Mar 13, 2026
5c67ea4
Add ESLint engine support for fixes and suggestions
nikhil-mittal-165 Mar 13, 2026
2acef1c
Add engine-agnostic data model for fixes and suggestions
nikhil-mittal-165 Mar 13, 2026
6db670d
Merge dev into feature/fixes-suggestion-data-modelling
nikhil-mittal-165 Mar 13, 2026
8f208b4
validation logic
nikhil-mittal-165 Mar 13, 2026
8574785
add test case
nikhil-mittal-165 Mar 17, 2026
a8b63e2
Merge branch 'dev' into feature/fixes-suggestion-data-modelling
nikhil-mittal-165 Mar 17, 2026
4b4b262
add test cases
nikhil-mittal-165 Mar 17, 2026
060d0c7
Merge feature/fixes-suggestion-data-modelling into feature/eslint-fix…
nikhil-mittal-165 Mar 17, 2026
977c2a4
add test case
nikhil-mittal-165 Mar 17, 2026
6c0838f
fix test case
nikhil-mittal-165 Mar 17, 2026
d1552f4
multibyte test case
nikhil-mittal-165 Mar 17, 2026
56ada22
add correct version
nikhil-mittal-165 Mar 18, 2026
286adb5
Merge branch 'dev' into feature/fixes-suggestion-data-modelling
nikhil-mittal-165 Mar 18, 2026
737edfb
fix suppression test case
nikhil-mittal-165 Mar 18, 2026
36c43a9
Merge branch 'feature/fixes-suggestion-data-modelling' into feature/e…
nikhil-mittal-165 Mar 19, 2026
868629b
Merge dev into feature/eslint-fixes-suggestions
nikhil-mittal-165 Mar 20, 2026
633d015
review comment on file read
nikhil-mittal-165 Mar 24, 2026
0f0692d
resolve conflicts
nikhil-mittal-165 Mar 24, 2026
5cb7096
review comment fix
nikhil-mittal-165 Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ENGINE-TEMPLATE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"types": "dist/index.d.ts",
"dependencies": {
"@types/node": "^20.0.0",
"@salesforce/code-analyzer-engine-api": "0.36.0"
"@salesforce/code-analyzer-engine-api": "0.36.1-SNAPSHOT"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/code-analyzer-core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@salesforce/code-analyzer-core",
"description": "Core Package for the Salesforce Code Analyzer",
"version": "0.45.0",
"version": "0.45.1-SNAPSHOT",
"author": "The Salesforce Code Analyzer Team",
"license": "BSD-3-Clause",
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
Expand All @@ -16,7 +16,7 @@
},
"types": "dist/index.d.ts",
"dependencies": {
"@salesforce/code-analyzer-engine-api": "0.36.0",
"@salesforce/code-analyzer-engine-api": "0.36.1-SNAPSHOT",
"@types/node": "^20.0.0",
"csv-stringify": "^6.7.0",
"isbinaryfile": "^5.0.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/code-analyzer-engine-api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@salesforce/code-analyzer-engine-api",
"description": "Engine API Package for the Salesforce Code Analyzer",
"version": "0.36.0",
"version": "0.36.1-SNAPSHOT",
"author": "The Salesforce Code Analyzer Team",
"license": "BSD-3-Clause",
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
Expand Down
6 changes: 3 additions & 3 deletions packages/code-analyzer-eslint-engine/package.json
Original file line number Diff line number Diff line change
@@ -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.41.0",
"version": "0.42.0-SNAPSHOT",
"author": "The Salesforce Code Analyzer Team",
"license": "BSD-3-Clause",
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
Expand All @@ -18,8 +18,8 @@
"@lwc/eslint-plugin-lwc": "^3.4.0",
"@lwc/eslint-plugin-lwc-platform": "^6.3.0",
"@salesforce-ux/eslint-plugin-slds": "^1.2.1",
"@salesforce/code-analyzer-engine-api": "0.36.0",
"@salesforce/code-analyzer-eslint8-engine": "0.13.0",
"@salesforce/code-analyzer-engine-api": "0.36.1-SNAPSHOT",
"@salesforce/code-analyzer-eslint8-engine": "0.13.1-SNAPSHOT",
"@salesforce/eslint-config-lwc": "^4.1.2",
"@salesforce/eslint-plugin-lightning": "^2.0.0",
"@types/node": "^20.0.0",
Expand Down
110 changes: 100 additions & 10 deletions packages/code-analyzer-eslint-engine/src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import {
Engine,
EngineRunResults,
EventType,
Fix,
LogLevel,
RuleDescription,
RunOptions,
Suggestion,
SeverityLevel,
Violation,
Workspace,
} from '@salesforce/code-analyzer-engine-api'
import {ESLint, Linter} from "eslint";
import {ESLint, Linter, Rule} from "eslint";
import {RulesMeta} from "@eslint/core";
import {ESLintEngineConfig} from "./config";
import {UserConfigInfo, UserConfigState} from "./user-config-info";
Expand Down Expand Up @@ -136,26 +138,40 @@ export class ESLintEngine extends Engine {
rulesToRun: ruleNames,
engineConfig: this.engineConfig,
eslintContext: context,
progressRange: [30, 95] // 30% to 95%
progressRange: [30, 95], // 30% to 95%
includeFixes: runOptions.includeFixes,
includeSuggestions: runOptions.includeSuggestions
}
const lintResults: ESLint.LintResult[] = await this._runESLintWorkerTask.run(runTaskInput, runOptions.workingFolder);

const engineResults: EngineRunResults = {
violations: this.toViolations(lintResults, new Set(ruleNames))
violations: await this.toViolations(lintResults, new Set(ruleNames),
runOptions.includeFixes ?? false, runOptions.includeSuggestions ?? false)
};
this.emitRunRulesProgressEvent(100);
return engineResults;
}

private toViolations(eslintResults: ESLint.LintResult[], specifiedRules: Set<string>): Violation[] {
private async toViolations(eslintResults: ESLint.LintResult[], specifiedRules: Set<string>,
includeFixes: boolean, includeSuggestions: boolean): Promise<Violation[]> {
const violations: Violation[] = [];
for (const eslintResult of eslintResults) {
let lineStartOffsets: number[] | undefined;
for (const resultMsg of eslintResult.messages) {
if (!resultMsg.ruleId) { // If there is no ruleName, this is how ESLint indicates something else went wrong (like a parse error).
this.handleEslintErrorOrWarning(eslintResult.filePath, resultMsg);
continue;
}
const violation: Violation = toViolation(eslintResult.filePath, resultMsg);

const needsFileContent = (includeFixes && resultMsg.fix) ||
(includeSuggestions && resultMsg.suggestions?.length);
if (needsFileContent && !lineStartOffsets) {
const source = eslintResult.source ?? await fs.readFile(eslintResult.filePath, 'utf8');
lineStartOffsets = computeLineStartOffsets(source);
}

const violation: Violation = toViolation(eslintResult.filePath, resultMsg,
includeFixes, includeSuggestions, lineStartOffsets);

if (specifiedRules.has(violation.ruleName)) {
violations.push(violation);
Expand Down Expand Up @@ -219,6 +235,10 @@ function toRuleDescription(ruleName: string, metadata: RulesMeta, status: ESLint
if (ruleUrl && ruleUrl.includes("://git.soma")) {
ruleUrl = undefined;
}
if (metadata.fixable) {
tags = [...tags, 'Fixable'];
}

return {
name: ruleName,
severityLevel: severityLevel,
Expand Down Expand Up @@ -260,11 +280,10 @@ function toTagsForCustomRule(metadata: RulesMeta): string[] {
}


function toViolation(file: string, resultMsg: Linter.LintMessage): Violation {
// Note: If in the future we add in some sort of suggestion or fix field on Violation, then we might want to
// leverage the fix and/or suggestions field on the LintMessage object.
// See: https://eslint.org/docs/v8.x/integrate/nodejs-api#-lintmessage-type
return {
function toViolation(file: string, resultMsg: Linter.LintMessage,
includeFixes: boolean, includeSuggestions: boolean,
lineStartOffsets?: number[]): Violation {
const violation: Violation = {
ruleName: resultMsg.ruleId as string,
message: resultMsg.message,
codeLocations: [{
Expand All @@ -276,6 +295,77 @@ function toViolation(file: string, resultMsg: Linter.LintMessage): Violation {
}],
primaryLocationIndex: 0
};

if (!lineStartOffsets) {
return violation;
}
if (includeFixes && resultMsg.fix) {
violation.fixes = [convertEslintFix(file, resultMsg.fix, lineStartOffsets)];
}
if (includeSuggestions && resultMsg.suggestions?.length) {
violation.suggestions = resultMsg.suggestions.map(s =>
convertEslintSuggestion(file, s, lineStartOffsets));
}

return violation;
}

function computeLineStartOffsets(fileContent: string): number[] {
const offsets: number[] = [0];
for (let i = 0; i < fileContent.length; i++) {
if (fileContent[i] === '\n') {
offsets.push(i + 1);
}
}
return offsets;
}

function indexToLineColumn(index: number, lineStartOffsets: number[]): { line: number, column: number } {
// Binary search for the line containing this index
let low = 0;
let high = lineStartOffsets.length - 1;
while (low < high) {
const mid = Math.ceil((low + high + 1) / 2);
if (mid >= lineStartOffsets.length || lineStartOffsets[mid] > index) {
high = mid - 1;
} else {
low = mid;
}
}
return {
line: low + 1, // 1-based
column: index - lineStartOffsets[low] + 1 // 1-based
};
}

function convertEslintFix(file: string, eslintFix: Rule.Fix, lineStartOffsets: number[]): Fix {
const start = indexToLineColumn(eslintFix.range[0], lineStartOffsets);
const end = indexToLineColumn(eslintFix.range[1], lineStartOffsets);
return {
location: {
file: file,
startLine: start.line,
startColumn: start.column,
endLine: end.line,
endColumn: end.column
},
fixedCode: eslintFix.text
};
}

function convertEslintSuggestion(file: string, eslintSuggestion: Linter.LintSuggestion, lineStartOffsets: number[]): Suggestion {
const start = indexToLineColumn(eslintSuggestion.fix.range[0], lineStartOffsets);
const end = indexToLineColumn(eslintSuggestion.fix.range[1], lineStartOffsets);
return {
location: {
file: file,
startLine: start.line,
startColumn: start.column,
endLine: end.line,
endColumn: end.column
},
message: eslintSuggestion.desc
};
}

function normalizeStartValue(startValue: number): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export type RunESLintWorkerTaskInput = {
rulesToRun: string[]
engineConfig: ESLintEngineConfig,
eslintContext: ESLintContext,
progressRange: [number, number]
progressRange: [number, number],
includeFixes?: boolean,
includeSuggestions?: boolean
}

export class RunESLintWorkerTask extends WorkerTask<RunESLintWorkerTaskInput, ESLint.LintResult[]> {
Expand Down
Loading
Loading