From 55f115a1b36f6cec7e53160294008648c2630330 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 13 Mar 2026 10:22:53 +0530 Subject: [PATCH 1/5] Add engine-agnostic data model for fixes and suggestions Define Fix and Suggestion types in engine-api, add core interfaces and implementations, update JSON/SARIF/XML output formats, and pass includeFixes/includeSuggestions flags through RunOptions to all engines. --- packages/ENGINE-TEMPLATE/package.json | 2 +- packages/code-analyzer-core/package.json | 2 +- .../code-analyzer-core/src/code-analyzer.ts | 10 ++- packages/code-analyzer-core/src/index.ts | 2 + .../results/json-run-results-format.ts | 47 +++++++++++- .../results/sarif-run-results-format.ts | 33 +++++++- .../results/xml-run-results-format.ts | 39 +++++++++- packages/code-analyzer-core/src/results.ts | 76 +++++++++++++++++++ .../code-analyzer-engine-api/package.json | 2 +- .../code-analyzer-engine-api/src/engines.ts | 12 +++ .../code-analyzer-engine-api/src/index.ts | 2 + .../code-analyzer-engine-api/src/results.ts | 28 +++++++ .../code-analyzer-eslint-engine/package.json | 4 +- .../code-analyzer-eslint8-engine/package.json | 4 +- .../code-analyzer-flow-engine/package.json | 4 +- .../code-analyzer-pmd-engine/package.json | 4 +- .../code-analyzer-regex-engine/package.json | 2 +- .../package.json | 4 +- .../code-analyzer-sfge-engine/package.json | 2 +- 19 files changed, 258 insertions(+), 21 deletions(-) diff --git a/packages/ENGINE-TEMPLATE/package.json b/packages/ENGINE-TEMPLATE/package.json index 32f51644..72a6d471 100644 --- a/packages/ENGINE-TEMPLATE/package.json +++ b/packages/ENGINE-TEMPLATE/package.json @@ -14,7 +14,7 @@ "types": "dist/index.d.ts", "dependencies": { "@types/node": "^20.0.0", - "@salesforce/code-analyzer-engine-api": "0.35.0" + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT" }, "devDependencies": { "@eslint/js": "^9.39.2", diff --git a/packages/code-analyzer-core/package.json b/packages/code-analyzer-core/package.json index b6705e8a..5887f6f0 100644 --- a/packages/code-analyzer-core/package.json +++ b/packages/code-analyzer-core/package.json @@ -16,7 +16,7 @@ }, "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "isbinaryfile": "^5.0.4", diff --git a/packages/code-analyzer-core/src/code-analyzer.ts b/packages/code-analyzer-core/src/code-analyzer.ts index 8a0a1e52..a5cfd001 100644 --- a/packages/code-analyzer-core/src/code-analyzer.ts +++ b/packages/code-analyzer-core/src/code-analyzer.ts @@ -87,6 +87,12 @@ export type SelectOptions = { export type RunOptions = { /** Object that specifies the user's workspace and which files should be targeted in the run analysis. */ workspace: Workspace + + /** When true, engines should include fix data on violations when available. */ + includeFixes?: boolean + + /** When true, engines should include suggestion data on violations when available. */ + includeSuggestions?: boolean } /** @@ -359,7 +365,9 @@ export class CodeAnalyzer { const engineRunOptions: engApi.RunOptions = { logFolder: this.config.getLogFolder(), workingFolder: workingFolder, - workspace: engApiWorkspace + workspace: engApiWorkspace, + includeFixes: runOptions.includeFixes, + includeSuggestions: runOptions.includeSuggestions }; const errorCallback: () => void = () => { // istanbul ignore else diff --git a/packages/code-analyzer-core/src/index.ts b/packages/code-analyzer-core/src/index.ts index d45dec3b..36c4543f 100644 --- a/packages/code-analyzer-core/src/index.ts +++ b/packages/code-analyzer-core/src/index.ts @@ -50,7 +50,9 @@ export { export type { CodeLocation, EngineRunResults, + Fix, RunResults, + Suggestion, Violation } from "./results" diff --git a/packages/code-analyzer-core/src/output-formats/results/json-run-results-format.ts b/packages/code-analyzer-core/src/output-formats/results/json-run-results-format.ts index 8e27ac5d..b56ceee0 100644 --- a/packages/code-analyzer-core/src/output-formats/results/json-run-results-format.ts +++ b/packages/code-analyzer-core/src/output-formats/results/json-run-results-format.ts @@ -1,4 +1,4 @@ -import {CodeLocation, RunResults, Violation} from "../../results"; +import {CodeLocation, Fix, RunResults, Suggestion, Violation} from "../../results"; import {RunResultsFormatter, CODE_ANALYZER_CORE_NAME} from "../../output-format"; import {Rule, SeverityLevel} from "../../rules"; @@ -68,6 +68,26 @@ export type JsonViolationOutput = { // An array of urls for resources associated with the violation resources: string[] + + // An array of fixes that can be applied to resolve the violation + fixes?: JsonFixOutput[] + + // An array of suggestions to help resolve the violation + suggestions?: JsonSuggestionOutput[] +} +export type JsonFixOutput = { + // The code location of the code to be replaced + location: JsonCodeLocationOutput + + // The replacement code to apply at the specified location + fixedCode: string +} +export type JsonSuggestionOutput = { + // The code location associated with the suggestion + location: JsonCodeLocationOutput + + // A message describing the suggested change + message: string } export type JsonCodeLocationOutput = { // The path, relative to runDir, of the file associated with the violation @@ -132,7 +152,7 @@ export function toJsonViolationOutputArray(violations: Violation[], runDir: stri function toJsonViolationOutput(violation: Violation, runDir: string, sanitizeFcn: (text: string) => string): JsonViolationOutput { const rule: Rule = violation.getRule(); - return { + const output: JsonViolationOutput = { rule: sanitizeFcn(rule.getName()), engine: sanitizeFcn(rule.getEngineName()), severity: rule.getSeverityLevel(), @@ -142,6 +162,29 @@ function toJsonViolationOutput(violation: Violation, runDir: string, sanitizeFcn message: sanitizeFcn(violation.getMessage()), resources: violation.getResourceUrls() }; + const fixes = violation.getFixes(); + if (fixes.length > 0) { + output.fixes = fixes.map(f => toJsonFixOutput(f, runDir)); + } + const suggestions = violation.getSuggestions(); + if (suggestions.length > 0) { + output.suggestions = suggestions.map(s => toJsonSuggestionOutput(s, runDir, sanitizeFcn)); + } + return output; +} + +function toJsonFixOutput(fix: Fix, runDir: string): JsonFixOutput { + return { + location: toJsonCodeLocationOutput(fix.getLocation(), runDir), + fixedCode: fix.getFixedCode() + }; +} + +function toJsonSuggestionOutput(suggestion: Suggestion, runDir: string, sanitizeFcn: (text: string) => string): JsonSuggestionOutput { + return { + location: toJsonCodeLocationOutput(suggestion.getLocation(), runDir), + message: sanitizeFcn(suggestion.getMessage()) + }; } function toJsonCodeLocationOutputArray(codeLocations: CodeLocation[], runDir: string): JsonCodeLocationOutput[] { diff --git a/packages/code-analyzer-core/src/output-formats/results/sarif-run-results-format.ts b/packages/code-analyzer-core/src/output-formats/results/sarif-run-results-format.ts index b2a3c974..82124019 100644 --- a/packages/code-analyzer-core/src/output-formats/results/sarif-run-results-format.ts +++ b/packages/code-analyzer-core/src/output-formats/results/sarif-run-results-format.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import {CodeLocation, EngineRunResults, RunResults, Violation} from "../../results"; +import {CodeLocation, EngineRunResults, Fix, RunResults, Violation} from "../../results"; import * as sarif from "sarif"; import {Rule, SeverityLevel} from "../../rules"; import {RunResultsFormatter} from "../../output-format"; @@ -56,7 +56,7 @@ function toSarifRun(engineRunResults: EngineRunResults, runDir: string): sarif.R function toSarifResult(violation: Violation, runDir: string, ruleIndex: number) : sarif.Result { const primaryCodeLocation = violation.getCodeLocations()[violation.getPrimaryLocationIndex()]; - return { + const result: sarif.Result = { ruleId: violation.getRule().getName(), ruleIndex: ruleIndex, level: toSarifNotificationLevel(violation.getRule().getSeverityLevel()), @@ -69,6 +69,35 @@ function toSarifResult(violation: Violation, runDir: string, ruleIndex: number) // And then we store the full locations array in the relatedLocations field if users want to see all of them relatedLocations: violation.getCodeLocations().map(codeLoc => toSarifLocation(codeLoc, runDir)) }; + const fixes = violation.getFixes(); + if (fixes.length > 0) { + result.fixes = fixes.map(fix => toSarifFix(fix, runDir)); + } + return result; +} + +function toSarifFix(fix: Fix, runDir: string): sarif.Fix { + const location = fix.getLocation(); + const file = location.getFile(); + return { + artifactChanges: [{ + artifactLocation: { + uri: file ? encodeURI(path.relative(runDir, file)) : undefined, + uriBaseId: file ? encodeURI(runDir) : undefined + }, + replacements: [{ + deletedRegion: { + startLine: location.getStartLine(), + startColumn: location.getStartColumn(), + endLine: location.getEndLine(), + endColumn: location.getEndColumn() + } as sarif.Region, + insertedContent: { + text: fix.getFixedCode() + } + }] + }] + }; } function toSarifLocation(codeLocation: CodeLocation, runDir: string): sarif.Location { diff --git a/packages/code-analyzer-core/src/output-formats/results/xml-run-results-format.ts b/packages/code-analyzer-core/src/output-formats/results/xml-run-results-format.ts index 82a46382..c321d8cc 100644 --- a/packages/code-analyzer-core/src/output-formats/results/xml-run-results-format.ts +++ b/packages/code-analyzer-core/src/output-formats/results/xml-run-results-format.ts @@ -1,7 +1,7 @@ import {RunResults} from "../../results"; import * as xmlbuilder from "xmlbuilder"; import {RunResultsFormatter, CODE_ANALYZER_CORE_NAME} from "../../output-format"; -import {JsonResultsOutput, toJsonResultsOutput} from "./json-run-results-format"; +import {JsonCodeLocationOutput, JsonResultsOutput, toJsonResultsOutput} from "./json-run-results-format"; /** * Formatter for Results XML Output Format @@ -69,8 +69,45 @@ export class XmlRunResultsFormatter implements RunResultsFormatter { for (const resource of violationOutput.resources) { resourcesNode.node('resource').text(resource); } + + if (violationOutput.fixes && violationOutput.fixes.length > 0) { + const fixesNode: xmlbuilder.XMLElement = violationNode.node('fixes'); + for (const fix of violationOutput.fixes) { + const fixNode: xmlbuilder.XMLElement = fixesNode.node('fix'); + addCodeLocationXmlNode(fixNode, 'location', fix.location); + fixNode.node('fixedCode').text(fix.fixedCode); + } + } + + if (violationOutput.suggestions && violationOutput.suggestions.length > 0) { + const suggestionsNode: xmlbuilder.XMLElement = violationNode.node('suggestions'); + for (const suggestion of violationOutput.suggestions) { + const suggestionNode: xmlbuilder.XMLElement = suggestionsNode.node('suggestion'); + addCodeLocationXmlNode(suggestionNode, 'location', suggestion.location); + suggestionNode.node('message').text(suggestion.message); + } + } } return violationsNode.end({ pretty: true, allowEmpty: true }); } } + +function addCodeLocationXmlNode(parentNode: xmlbuilder.XMLElement, nodeName: string, location: JsonCodeLocationOutput): void { + const locationNode: xmlbuilder.XMLElement = parentNode.node(nodeName); + if (location.file !== undefined) { + locationNode.node('file').text(location.file); + } + if (location.startLine !== undefined) { + locationNode.node('startLine').text(`${location.startLine}`); + } + if (location.startColumn !== undefined) { + locationNode.node('startColumn').text(`${location.startColumn}`); + } + if (location.endLine !== undefined) { + locationNode.node('endLine').text(`${location.endLine}`); + } + if (location.endColumn !== undefined) { + locationNode.node('endColumn').text(`${location.endColumn}`); + } +} diff --git a/packages/code-analyzer-core/src/results.ts b/packages/code-analyzer-core/src/results.ts index 882ed3d9..db8d1acb 100644 --- a/packages/code-analyzer-core/src/results.ts +++ b/packages/code-analyzer-core/src/results.ts @@ -51,6 +51,34 @@ export interface Violation { /** Returns an array of urls for resources associated with the violation */ getResourceUrls(): string[] + + /** Returns an array of {@link Fix} instances that can be applied to resolve the violation */ + getFixes(): Fix[] + + /** Returns an array of {@link Suggestion} instances with messages to help resolve the violation */ + getSuggestions(): Suggestion[] +} + +/** + * Describes a fix that can be applied to resolve a {@link Violation} + */ +export interface Fix { + /** Returns the {@link CodeLocation} of the code to be replaced */ + getLocation(): CodeLocation + + /** Returns the replacement code to apply at the specified location */ + getFixedCode(): string +} + +/** + * Describes a suggestion to help resolve a {@link Violation} + */ +export interface Suggestion { + /** Returns the {@link CodeLocation} of the code associated with the suggestion */ + getLocation(): CodeLocation + + /** Returns a message describing the suggested change */ + getMessage(): string } /** @@ -116,6 +144,38 @@ export interface RunResults { /******* IMPLEMENTATIONS: *************************************************************************/ +export class FixImpl implements Fix { + private readonly apiFix: engApi.Fix; + + constructor(apiFix: engApi.Fix) { + this.apiFix = apiFix; + } + + getLocation(): CodeLocation { + return new CodeLocationImpl(this.apiFix.location); + } + + getFixedCode(): string { + return this.apiFix.fixedCode; + } +} + +export class SuggestionImpl implements Suggestion { + private readonly apiSuggestion: engApi.Suggestion; + + constructor(apiSuggestion: engApi.Suggestion) { + this.apiSuggestion = apiSuggestion; + } + + getLocation(): CodeLocation { + return new CodeLocationImpl(this.apiSuggestion.location); + } + + getMessage(): string { + return this.apiSuggestion.message; + } +} + export class CodeLocationImpl implements CodeLocation { private readonly apiCodeLocation: engApi.CodeLocation; @@ -211,6 +271,14 @@ export class ViolationImpl implements Violation { return !this.apiViolation.resourceUrls ? urls : [...urls, ...this.apiViolation.resourceUrls.filter(url => !urls.includes(url))]; } + + getFixes(): Fix[] { + return (this.apiViolation.fixes ?? []).map(f => new FixImpl(f)); + } + + getSuggestions(): Suggestion[] { + return (this.apiViolation.suggestions ?? []).map(s => new SuggestionImpl(s)); + } } abstract class AbstractLocationlessViolation implements Violation { @@ -245,6 +313,14 @@ abstract class AbstractLocationlessViolation implements Violation { getResourceUrls(): string[] { return []; } + + getFixes(): Fix[] { + return []; + } + + getSuggestions(): Suggestion[] { + return []; + } } export class UninstantiableEngineErrorViolation extends AbstractLocationlessViolation { diff --git a/packages/code-analyzer-engine-api/package.json b/packages/code-analyzer-engine-api/package.json index aacf832a..39b0d38f 100644 --- a/packages/code-analyzer-engine-api/package.json +++ b/packages/code-analyzer-engine-api/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-engine-api", "description": "Engine API Package for the Salesforce Code Analyzer", - "version": "0.35.0", + "version": "0.36.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-engine-api/src/engines.ts b/packages/code-analyzer-engine-api/src/engines.ts index 7de4d38f..7a727aa6 100644 --- a/packages/code-analyzer-engine-api/src/engines.ts +++ b/packages/code-analyzer-engine-api/src/engines.ts @@ -60,6 +60,18 @@ export type RunOptions = { * the other workspace files (returned by the getWorkspaceFiles method) if needed to support the analysis. */ workspace: Workspace + + /** + * When true, engines should include {@link Fix} data on violations when available. + * Engines may skip expensive fix computation when this is false or undefined. + */ + includeFixes?: boolean + + /** + * When true, engines should include {@link Suggestion} data on violations when available. + * Engines may skip expensive suggestion computation when this is false or undefined. + */ + includeSuggestions?: boolean } /** diff --git a/packages/code-analyzer-engine-api/src/index.ts b/packages/code-analyzer-engine-api/src/index.ts index 7340aa20..276ddb99 100644 --- a/packages/code-analyzer-engine-api/src/index.ts +++ b/packages/code-analyzer-engine-api/src/index.ts @@ -60,6 +60,8 @@ export type { export type { CodeLocation, EngineRunResults, + Fix, + Suggestion, Violation } from "./results" diff --git a/packages/code-analyzer-engine-api/src/results.ts b/packages/code-analyzer-engine-api/src/results.ts index 1b473bfd..4307a9ab 100644 --- a/packages/code-analyzer-engine-api/src/results.ts +++ b/packages/code-analyzer-engine-api/src/results.ts @@ -39,6 +39,34 @@ export type Violation = { /** An array of urls for resources associated with the violation */ resourceUrls?: string[] + + /** An array of {@link Fix} instances that can be applied to resolve the violation */ + fixes?: Fix[] + + /** An array of {@link Suggestion} instances with messages to help resolve the violation */ + suggestions?: Suggestion[] +} + +/** + * Describes a fix that can be applied to resolve a {@link Violation} + */ +export type Fix = { + /** The {@link CodeLocation} of the code to be replaced */ + location: CodeLocation + + /** The replacement code to apply at the specified location */ + fixedCode: string +} + +/** + * Describes a suggestion to help resolve a {@link Violation} + */ +export type Suggestion = { + /** The {@link CodeLocation} of the code associated with the suggestion */ + location: CodeLocation + + /** A message describing the suggested change */ + message: string } /** diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index 18513ef2..ae2b6cde 100644 --- a/packages/code-analyzer-eslint-engine/package.json +++ b/packages/code-analyzer-eslint-engine/package.json @@ -18,8 +18,8 @@ "@lwc/eslint-plugin-lwc": "^3.3.0", "@lwc/eslint-plugin-lwc-platform": "^6.3.0", "@salesforce-ux/eslint-plugin-slds": "^1.1.0", - "@salesforce/code-analyzer-engine-api": "0.35.0", - "@salesforce/code-analyzer-eslint8-engine": "0.12.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", + "@salesforce/code-analyzer-eslint8-engine": "0.12.1-SNAPSHOT", "@salesforce/eslint-config-lwc": "^4.1.2", "@salesforce/eslint-plugin-lightning": "^2.0.0", "@types/node": "^20.0.0", diff --git a/packages/code-analyzer-eslint8-engine/package.json b/packages/code-analyzer-eslint8-engine/package.json index 03060e15..84edd929 100644 --- a/packages/code-analyzer-eslint8-engine/package.json +++ b/packages/code-analyzer-eslint8-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-eslint8-engine", "description": "Plugin package that adds 'eslint' (version 8) as an engine into Salesforce Code Analyzer", - "version": "0.12.0", + "version": "0.12.1-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -18,7 +18,7 @@ "@eslint/js": "8.57.1", "@lwc/eslint-plugin-lwc": "2.2.0", "@lwc/eslint-plugin-lwc-platform": "5.2.0", - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "3.7.2", "@salesforce/eslint-plugin-lightning": "1.0.1", "@types/node": "^20.0.0", diff --git a/packages/code-analyzer-flow-engine/package.json b/packages/code-analyzer-flow-engine/package.json index 00507267..c1270edc 100644 --- a/packages/code-analyzer-flow-engine/package.json +++ b/packages/code-analyzer-flow-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-flow-engine", "description": "Plugin package that adds 'Flow Scanner' as an engine into Salesforce Code Analyzer", - "version": "0.34.0", + "version": "0.34.1-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@types/node": "^20.0.0", "@types/semver": "^7.7.1", "semver": "^7.7.4" diff --git a/packages/code-analyzer-pmd-engine/package.json b/packages/code-analyzer-pmd-engine/package.json index 0f0ee51a..d0a07357 100644 --- a/packages/code-analyzer-pmd-engine/package.json +++ b/packages/code-analyzer-pmd-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-pmd-engine", "description": "Plugin package that adds 'pmd' and 'cpd' as engines into Salesforce Code Analyzer", - "version": "0.37.0", + "version": "0.37.1-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@types/node": "^20.0.0", "@types/semver": "^7.7.1", "semver": "^7.7.4" diff --git a/packages/code-analyzer-regex-engine/package.json b/packages/code-analyzer-regex-engine/package.json index 84ae6ddc..cd459fd3 100644 --- a/packages/code-analyzer-regex-engine/package.json +++ b/packages/code-analyzer-regex-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.0", "p-limit": "^3.1.0" diff --git a/packages/code-analyzer-retirejs-engine/package.json b/packages/code-analyzer-retirejs-engine/package.json index 16b7b3b1..d40be5e7 100644 --- a/packages/code-analyzer-retirejs-engine/package.json +++ b/packages/code-analyzer-retirejs-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-retirejs-engine", "description": "Plugin package that adds 'retire-js' as an engine into Salesforce Code Analyzer", - "version": "0.32.0", + "version": "0.32.1-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.0", "node-stream-zip": "^1.15.0", diff --git a/packages/code-analyzer-sfge-engine/package.json b/packages/code-analyzer-sfge-engine/package.json index f2f66fb9..1523a1e1 100644 --- a/packages/code-analyzer-sfge-engine/package.json +++ b/packages/code-analyzer-sfge-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.35.0", + "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.4" }, From 5fbbf3328a5ebd2e11ad99c6638a4293aa51fde9 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 13 Mar 2026 15:22:25 +0530 Subject: [PATCH 2/5] validation logic --- .../code-analyzer-core/src/code-analyzer.ts | 89 ++++++++++++------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/packages/code-analyzer-core/src/code-analyzer.ts b/packages/code-analyzer-core/src/code-analyzer.ts index a5cfd001..40f85ac3 100644 --- a/packages/code-analyzer-core/src/code-analyzer.ts +++ b/packages/code-analyzer-core/src/code-analyzer.ts @@ -814,6 +814,8 @@ function validateEngineRunResults(engineName: string, apiEngineRunResults: engAp validateViolationRuleName(violation, engineName, ruleSelection); validateViolationCodeLocations(violation, engineName); validateViolationPrimaryLocationIndex(violation, engineName); + validateFixCodeLocations(violation, engineName); + validateSuggestionCodeLocations(violation, engineName); } } @@ -837,47 +839,68 @@ function validateViolationCodeLocations(violation: engApi.Violation, engineName: throw new Error(getMessage('EngineReturnedViolationWithEmptyCodeLocationArray', engineName, violation.ruleName)); } for (const codeLocation of violation.codeLocations) { - const absFile: string = toAbsolutePath(codeLocation.file); - fs.existsSync(absFile) + validateCodeLocation(codeLocation, engineName, violation.ruleName); + } +} - if (!fs.existsSync(absFile)) { - throw new Error(getMessage('EngineReturnedViolationWithCodeLocationFileThatDoesNotExist', - engineName, violation.ruleName, absFile)); - } +function validateFixCodeLocations(violation: engApi.Violation, engineName: string): void { + if (!violation.fixes) { + return; + } + for (const fix of violation.fixes) { + validateCodeLocation(fix.location, engineName, violation.ruleName); + } +} - if (!fs.statSync(absFile).isFile()) { - throw new Error(getMessage('EngineReturnedViolationWithCodeLocationFileAsFolder', - engineName, violation.ruleName, absFile)); - } +function validateSuggestionCodeLocations(violation: engApi.Violation, engineName: string): void { + if (!violation.suggestions) { + return; + } + for (const suggestion of violation.suggestions) { + validateCodeLocation(suggestion.location, engineName, violation.ruleName); + } +} - if (!isValidLineOrColumn(codeLocation.startLine)) { - throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', - engineName, violation.ruleName, 'startLine', codeLocation.startLine)); - } +function validateCodeLocation(codeLocation: engApi.CodeLocation, engineName: string, ruleName: string): void { + const absFile: string = toAbsolutePath(codeLocation.file); - if (!isValidLineOrColumn(codeLocation.startColumn)) { + if (!fs.existsSync(absFile)) { + throw new Error(getMessage('EngineReturnedViolationWithCodeLocationFileThatDoesNotExist', + engineName, ruleName, absFile)); + } + + if (!fs.statSync(absFile).isFile()) { + throw new Error(getMessage('EngineReturnedViolationWithCodeLocationFileAsFolder', + engineName, ruleName, absFile)); + } + + if (!isValidLineOrColumn(codeLocation.startLine)) { + throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', + engineName, ruleName, 'startLine', codeLocation.startLine)); + } + + if (!isValidLineOrColumn(codeLocation.startColumn)) { + throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', + engineName, ruleName, 'startColumn', codeLocation.startColumn)); + } + + if (codeLocation.endLine !== undefined) { + if (!isValidLineOrColumn(codeLocation.endLine)) { throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', - engineName, violation.ruleName, 'startColumn', codeLocation.startColumn)); + engineName, ruleName, 'endLine', codeLocation.endLine)); + } else if (codeLocation.endLine < codeLocation.startLine) { + throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithEndLineBeforeStartLine', + engineName, ruleName, codeLocation.endLine, codeLocation.startLine)); } - if (codeLocation.endLine !== undefined) { - if (!isValidLineOrColumn(codeLocation.endLine)) { + // istanbul ignore else + if (codeLocation.endColumn !== undefined) { + if (!isValidLineOrColumn(codeLocation.endColumn)) { throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', - engineName, violation.ruleName, 'endLine', codeLocation.endLine)); - } else if (codeLocation.endLine < codeLocation.startLine) { - throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithEndLineBeforeStartLine', - engineName, violation.ruleName, codeLocation.endLine, codeLocation.startLine)); - } - - // istanbul ignore else - if (codeLocation.endColumn !== undefined) { - if (!isValidLineOrColumn(codeLocation.endColumn)) { - throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', - engineName, violation.ruleName, 'endColumn', codeLocation.endColumn)); - } else if (codeLocation.endLine == codeLocation.startLine && codeLocation.endColumn < codeLocation.startColumn) { - throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithEndColumnBeforeStartColumnOnSameLine', - engineName, violation.ruleName, codeLocation.endColumn, codeLocation.startColumn)); - } + engineName, ruleName, 'endColumn', codeLocation.endColumn)); + } else if (codeLocation.endLine == codeLocation.startLine && codeLocation.endColumn < codeLocation.startColumn) { + throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithEndColumnBeforeStartColumnOnSameLine', + engineName, ruleName, codeLocation.endColumn, codeLocation.startColumn)); } } } From 69e414dc721b2abdfbeb9ac2d62cfd87386723bf Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Tue, 17 Mar 2026 11:30:31 +0530 Subject: [PATCH 3/5] add test case --- .../test/code-analyzer.test.ts | 111 ++++++++++++++++ .../test/output-format.test.ts | 104 +++++++++++++++ packages/code-analyzer-core/test/stubs.ts | 122 ++++++++++++++++++ 3 files changed, 337 insertions(+) diff --git a/packages/code-analyzer-core/test/code-analyzer.test.ts b/packages/code-analyzer-core/test/code-analyzer.test.ts index a8f67488..4cd47494 100644 --- a/packages/code-analyzer-core/test/code-analyzer.test.ts +++ b/packages/code-analyzer-core/test/code-analyzer.test.ts @@ -686,6 +686,117 @@ describe("Tests for the run method of CodeAnalyzer", () => { expect(violations[0].getCodeLocations()[0].getEndColumn()).toBeUndefined(); }); + it("When an engine returns a fix with a code location file that does not exist, then an error is thrown", async () => { + const badViolation: engApi.Violation = stubs.getSampleViolationWithFixes(); + badViolation.fixes![0].location.file = 'test/doesNotExist'; + stubEngine1.resultsToReturn = { violations: [badViolation] }; + await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow( + getMessage('EngineReturnedViolationWithCodeLocationFileThatDoesNotExist', + 'stubEngine1', 'stub1RuleA', path.resolve('test', 'doesNotExist'))); + }); + + it("When an engine returns a fix with an invalid startLine, then an error is thrown", async () => { + const badViolation: engApi.Violation = stubs.getSampleViolationWithFixes(); + badViolation.fixes![0].location.startLine = -1; + stubEngine1.resultsToReturn = { violations: [badViolation] }; + await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow( + getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', + 'stubEngine1', 'stub1RuleA', 'startLine', -1)); + }); + + it("When an engine returns a fix with endLine before startLine, then an error is thrown", async () => { + const badViolation: engApi.Violation = stubs.getSampleViolationWithFixes(); + badViolation.fixes![0].location.startLine = 10; + badViolation.fixes![0].location.endLine = 5; + stubEngine1.resultsToReturn = { violations: [badViolation] }; + await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow( + getMessage('EngineReturnedViolationWithCodeLocationWithEndLineBeforeStartLine', + 'stubEngine1', 'stub1RuleA', 5, 10)); + }); + + it("When an engine returns a suggestion with a code location file that does not exist, then an error is thrown", async () => { + const badViolation: engApi.Violation = stubs.getSampleViolationWithSuggestions(); + badViolation.suggestions![0].location.file = 'test/doesNotExist'; + stubEngine1.resultsToReturn = { violations: [badViolation] }; + await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow( + getMessage('EngineReturnedViolationWithCodeLocationFileThatDoesNotExist', + 'stubEngine1', 'stub1RuleA', path.resolve('test', 'doesNotExist'))); + }); + + it("When an engine returns a suggestion with an invalid startColumn, then an error is thrown", async () => { + const badViolation: engApi.Violation = stubs.getSampleViolationWithSuggestions(); + badViolation.suggestions![0].location.startColumn = 0; + stubEngine1.resultsToReturn = { violations: [badViolation] }; + await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow( + getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', + 'stubEngine1', 'stub1RuleA', 'startColumn', 0)); + }); + + it("When an engine returns a violation with fixes, then getFixes returns the correct data", async () => { + stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationWithFixes()] }; + const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions); + const violations: Violation[] = overallResults.getViolations(); + expect(violations).toHaveLength(1); + + const fixes = violations[0].getFixes(); + expect(fixes).toHaveLength(1); + expect(fixes[0].getFixedCode()).toEqual('const correctedValue = true;'); + expect(fixes[0].getLocation().getFile()).toEqual(path.resolve('test/config.test.ts')); + expect(fixes[0].getLocation().getStartLine()).toEqual(3); + expect(fixes[0].getLocation().getStartColumn()).toEqual(6); + expect(fixes[0].getLocation().getEndLine()).toEqual(3); + expect(fixes[0].getLocation().getEndColumn()).toEqual(20); + }); + + it("When an engine returns a violation with suggestions, then getSuggestions returns the correct data", async () => { + stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationWithSuggestions()] }; + const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions); + const violations: Violation[] = overallResults.getViolations(); + expect(violations).toHaveLength(1); + + const suggestions = violations[0].getSuggestions(); + expect(suggestions).toHaveLength(2); + expect(suggestions[0].getMessage()).toEqual('Consider using a boolean literal instead'); + expect(suggestions[0].getLocation().getFile()).toEqual(path.resolve('test/config.test.ts')); + expect(suggestions[1].getMessage()).toEqual('Consider removing this unused variable'); + }); + + it("When an engine returns a violation with both fixes and suggestions, then both are accessible", async () => { + stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationWithFixesAndSuggestions()] }; + const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions); + const violations: Violation[] = overallResults.getViolations(); + expect(violations).toHaveLength(1); + expect(violations[0].getFixes()).toHaveLength(2); + expect(violations[0].getSuggestions()).toHaveLength(1); + }); + + it("When an engine returns a violation without fixes or suggestions, then getFixes and getSuggestions return empty arrays", async () => { + stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationForStub1RuleA()] }; + const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions); + const violations: Violation[] = overallResults.getViolations(); + expect(violations).toHaveLength(1); + expect(violations[0].getFixes()).toEqual([]); + expect(violations[0].getSuggestions()).toEqual([]); + }); + + it("When includeFixes and includeSuggestions are specified in RunOptions, they are forwarded to the engine", async () => { + stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationForStub1RuleA()] }; + await codeAnalyzer.run(selection, { + ...sampleRunOptions, + includeFixes: true, + includeSuggestions: true + }); + expect(stubEngine1.runRulesCallHistory[0].runOptions.includeFixes).toBe(true); + expect(stubEngine1.runRulesCallHistory[0].runOptions.includeSuggestions).toBe(true); + }); + + it("When includeFixes and includeSuggestions are not specified in RunOptions, they are undefined in the engine run options", async () => { + stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationForStub1RuleA()] }; + await codeAnalyzer.run(selection, sampleRunOptions); + expect(stubEngine1.runRulesCallHistory[0].runOptions.includeFixes).toBeUndefined(); + expect(stubEngine1.runRulesCallHistory[0].runOptions.includeSuggestions).toBeUndefined(); + }); + it("When an engine throws an exception when running, then a result is returned with a Critical violation of type UnexpectedError", async () => { codeAnalyzer = createCodeAnalyzer(); await codeAnalyzer.addEnginePlugin(new stubs.ThrowingEnginePlugin()); diff --git a/packages/code-analyzer-core/test/output-format.test.ts b/packages/code-analyzer-core/test/output-format.test.ts index f579ca95..029c23d2 100644 --- a/packages/code-analyzer-core/test/output-format.test.ts +++ b/packages/code-analyzer-core/test/output-format.test.ts @@ -174,6 +174,110 @@ describe("RunResultsFormatter Tests", () => { }); }); +describe("Output format tests for fixes and suggestions", () => { + let resultsWithFixes: RunResults; + + beforeAll(async () => { + const codeAnalyzer: CodeAnalyzer = new CodeAnalyzer(CodeAnalyzerConfig.withDefaults()); + codeAnalyzer._setClock(new FixedClock(new Date(2024, 6, 3, 9, 14, 34, 567))); + const stubPlugin: stubs.StubEnginePlugin = new stubs.StubEnginePlugin(); + await codeAnalyzer.addEnginePlugin(stubPlugin); + (stubPlugin.getCreatedEngine('stubEngine1') as stubs.StubEngine1).resultsToReturn = { + violations: [ + stubs.getSampleViolationWithFixes(), + stubs.getSampleViolationWithSuggestions(), + stubs.getSampleViolationWithFixesAndSuggestions() + ] + }; + const selection = await codeAnalyzer.selectRules(['all']); + resultsWithFixes = await codeAnalyzer.run(selection, {workspace: await codeAnalyzer.createWorkspace(['test'])}); + }); + + describe("JSON output format with fixes and suggestions", () => { + it("Violations with fixes include fixes array in JSON output", () => { + const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON)); + const violationsWithFixes = json.violations.filter((v: {fixes?: unknown[]}) => v.fixes && v.fixes.length > 0); + expect(violationsWithFixes.length).toBeGreaterThanOrEqual(1); + + const fix = violationsWithFixes[0].fixes[0]; + expect(fix).toHaveProperty('location'); + expect(fix).toHaveProperty('fixedCode'); + expect(fix.location).toHaveProperty('file'); + expect(fix.location).toHaveProperty('startLine'); + expect(fix.location).toHaveProperty('startColumn'); + }); + + it("Violations with suggestions include suggestions array in JSON output", () => { + const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON)); + const violationsWithSuggestions = json.violations.filter((v: {suggestions?: unknown[]}) => v.suggestions && v.suggestions.length > 0); + expect(violationsWithSuggestions.length).toBeGreaterThanOrEqual(1); + + const suggestion = violationsWithSuggestions[0].suggestions[0]; + expect(suggestion).toHaveProperty('location'); + expect(suggestion).toHaveProperty('message'); + }); + + it("Violations without fixes do not have a fixes key in JSON output", () => { + const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON)); + const violationsWithoutFixes = json.violations.filter((v: {fixes?: unknown[]}) => !v.fixes); + expect(violationsWithoutFixes.length).toBeGreaterThanOrEqual(1); + expect(violationsWithoutFixes[0]).not.toHaveProperty('fixes'); + }); + + it("Fix location file paths are relative to runDir in JSON output", () => { + const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON)); + const violationWithFix = json.violations.find((v: {fixes?: unknown[]}) => v.fixes && v.fixes.length > 0); + const fixFile = violationWithFix.fixes[0].location.file; + expect(fixFile).not.toContain(json.runDir); + expect(path.isAbsolute(fixFile)).toBe(false); + }); + }); + + describe("XML output format with fixes and suggestions", () => { + it("Violations with fixes include fix nodes in XML output", () => { + const xml = resultsWithFixes.toFormattedOutput(OutputFormat.XML); + expect(xml).toContain(''); + expect(xml).toContain(''); + expect(xml).toContain(''); + }); + + it("Violations with suggestions include suggestion nodes in XML output", () => { + const xml = resultsWithFixes.toFormattedOutput(OutputFormat.XML); + expect(xml).toContain(''); + expect(xml).toContain(''); + expect(xml).toContain(''); + }); + }); + + describe("SARIF output format with fixes", () => { + it("Violations with fixes include fix data in SARIF output", () => { + const sarif = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.SARIF)); + const allResults = sarif.runs.flatMap((run: {results: unknown[]}) => run.results); + const resultsWithFixData = allResults.filter((r: {fixes?: unknown[]}) => r.fixes && r.fixes.length > 0); + expect(resultsWithFixData.length).toBeGreaterThanOrEqual(1); + + const sarifFix = resultsWithFixData[0].fixes[0]; + expect(sarifFix).toHaveProperty('artifactChanges'); + expect(sarifFix.artifactChanges[0]).toHaveProperty('replacements'); + expect(sarifFix.artifactChanges[0].replacements[0]).toHaveProperty('deletedRegion'); + expect(sarifFix.artifactChanges[0].replacements[0]).toHaveProperty('insertedContent'); + }); + + it("Suggestions are not included in SARIF output", () => { + const sarifStr = resultsWithFixes.toFormattedOutput(OutputFormat.SARIF); + expect(sarifStr).not.toContain('"suggestions"'); + }); + }); + + describe("CSV output format with fixes and suggestions", () => { + it("Fixes and suggestions are not included in CSV output", () => { + const csv = resultsWithFixes.toFormattedOutput(OutputFormat.CSV); + expect(csv).not.toContain('fixedCode'); + expect(csv).not.toContain('const correctedValue'); + }); + }); +}); + describe("RuleSelectionFormatter Tests", () => { describe("Tests for the JSON output format", () => { diff --git a/packages/code-analyzer-core/test/stubs.ts b/packages/code-analyzer-core/test/stubs.ts index cf5d3cde..0dee059c 100644 --- a/packages/code-analyzer-core/test/stubs.ts +++ b/packages/code-analyzer-core/test/stubs.ts @@ -433,6 +433,128 @@ export function getSampleViolationForStub3RuleA(): engApi.Violation { } } +export function getSampleViolationWithFixes(): engApi.Violation { + return { + ruleName: 'stub1RuleA', + message: 'SomeViolationWithFixes', + codeLocations: [ + { + file: 'test/config.test.ts', + startLine: 3, + startColumn: 6, + endLine: 3, + endColumn: 20 + } + ], + primaryLocationIndex: 0, + fixes: [ + { + location: { + file: 'test/config.test.ts', + startLine: 3, + startColumn: 6, + endLine: 3, + endColumn: 20 + }, + fixedCode: 'const correctedValue = true;' + } + ] + }; +} + +export function getSampleViolationWithSuggestions(): engApi.Violation { + return { + ruleName: 'stub1RuleA', + message: 'SomeViolationWithSuggestions', + codeLocations: [ + { + file: 'test/config.test.ts', + startLine: 5, + startColumn: 1, + endLine: 5, + endColumn: 10 + } + ], + primaryLocationIndex: 0, + suggestions: [ + { + location: { + file: 'test/config.test.ts', + startLine: 5, + startColumn: 1, + endLine: 5, + endColumn: 10 + }, + message: 'Consider using a boolean literal instead' + }, + { + location: { + file: 'test/config.test.ts', + startLine: 5, + startColumn: 1, + endLine: 5, + endColumn: 10 + }, + message: 'Consider removing this unused variable' + } + ] + }; +} + +export function getSampleViolationWithFixesAndSuggestions(): engApi.Violation { + return { + ruleName: 'stub1RuleC', + message: 'SomeViolationWithBoth', + codeLocations: [ + { + file: 'test/code-analyzer.test.ts', + startLine: 21, + startColumn: 7, + endLine: 25, + endColumn: 4 + } + ], + primaryLocationIndex: 0, + fixes: [ + { + location: { + file: 'test/code-analyzer.test.ts', + startLine: 21, + startColumn: 7, + endLine: 21, + endColumn: 15 + }, + fixedCode: 'const x = 1;' + }, + { + location: { + file: 'test/code-analyzer.test.ts', + startLine: 23, + startColumn: 1, + endLine: 23, + endColumn: 10 + }, + fixedCode: 'let y = 2;' + } + ], + suggestions: [ + { + location: { + file: 'test/code-analyzer.test.ts', + startLine: 21, + startColumn: 7, + endLine: 25, + endColumn: 4 + }, + message: 'Refactor this block to use modern syntax' + } + ], + resourceUrls: [ + "https://example.com/aViolationSpecificUrl1", + ] + }; +} + /** * EmptyTagEnginePlugin - A plugin to help with testing rules with empty tags */ From f6aeb7ac04e11e010a7d0a3242c7bb6602a2ecb5 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Wed, 18 Mar 2026 13:57:42 +0530 Subject: [PATCH 4/5] add correct version --- packages/code-analyzer-eslint-engine/package.json | 2 +- packages/code-analyzer-eslint8-engine/package.json | 2 +- packages/code-analyzer-flow-engine/package.json | 2 +- packages/code-analyzer-pmd-engine/package.json | 2 +- packages/code-analyzer-retirejs-engine/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index ae2b6cde..972b3cdc 100644 --- a/packages/code-analyzer-eslint-engine/package.json +++ b/packages/code-analyzer-eslint-engine/package.json @@ -19,7 +19,7 @@ "@lwc/eslint-plugin-lwc-platform": "^6.3.0", "@salesforce-ux/eslint-plugin-slds": "^1.1.0", "@salesforce/code-analyzer-engine-api": "0.36.0-SNAPSHOT", - "@salesforce/code-analyzer-eslint8-engine": "0.12.1-SNAPSHOT", + "@salesforce/code-analyzer-eslint8-engine": "0.13.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "^4.1.2", "@salesforce/eslint-plugin-lightning": "^2.0.0", "@types/node": "^20.0.0", diff --git a/packages/code-analyzer-eslint8-engine/package.json b/packages/code-analyzer-eslint8-engine/package.json index 84edd929..bb92f3c5 100644 --- a/packages/code-analyzer-eslint8-engine/package.json +++ b/packages/code-analyzer-eslint8-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-eslint8-engine", "description": "Plugin package that adds 'eslint' (version 8) as an engine into Salesforce Code Analyzer", - "version": "0.12.1-SNAPSHOT", + "version": "0.13.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-flow-engine/package.json b/packages/code-analyzer-flow-engine/package.json index c1270edc..3c716974 100644 --- a/packages/code-analyzer-flow-engine/package.json +++ b/packages/code-analyzer-flow-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-flow-engine", "description": "Plugin package that adds 'Flow Scanner' as an engine into Salesforce Code Analyzer", - "version": "0.34.1-SNAPSHOT", + "version": "0.35.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-pmd-engine/package.json b/packages/code-analyzer-pmd-engine/package.json index d0a07357..0433a7df 100644 --- a/packages/code-analyzer-pmd-engine/package.json +++ b/packages/code-analyzer-pmd-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-pmd-engine", "description": "Plugin package that adds 'pmd' and 'cpd' as engines into Salesforce Code Analyzer", - "version": "0.37.1-SNAPSHOT", + "version": "0.38.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-retirejs-engine/package.json b/packages/code-analyzer-retirejs-engine/package.json index d40be5e7..b80624e9 100644 --- a/packages/code-analyzer-retirejs-engine/package.json +++ b/packages/code-analyzer-retirejs-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-retirejs-engine", "description": "Plugin package that adds 'retire-js' as an engine into Salesforce Code Analyzer", - "version": "0.32.1-SNAPSHOT", + "version": "0.33.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", From d2be5ab66d9a487a61092f49c3469522177a9a78 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Wed, 18 Mar 2026 15:04:06 +0530 Subject: [PATCH 5/5] fix suppression test case --- packages/code-analyzer-core/package.json | 2 +- .../test/suppressions-integration.test.ts | 4 +++- .../test/suppressions/suppression-processor.test.ts | 10 +++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/code-analyzer-core/package.json b/packages/code-analyzer-core/package.json index 5887f6f0..204dad5b 100644 --- a/packages/code-analyzer-core/package.json +++ b/packages/code-analyzer-core/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-core", "description": "Core Package for the Salesforce Code Analyzer", - "version": "0.44.0", + "version": "0.45.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-core/test/suppressions-integration.test.ts b/packages/code-analyzer-core/test/suppressions-integration.test.ts index af91003b..6cb5f6b5 100644 --- a/packages/code-analyzer-core/test/suppressions-integration.test.ts +++ b/packages/code-analyzer-core/test/suppressions-integration.test.ts @@ -5,7 +5,7 @@ import * as path from 'node:path'; import { processSuppressions } from '../src/suppressions/suppression-processor'; -import { Violation } from '../src/results'; +import { Violation, Fix, Suggestion } from '../src/results'; import { Rule } from '../src/rules'; import { SeverityLevel } from '@salesforce/code-analyzer-engine-api'; @@ -53,6 +53,8 @@ class MockViolation implements Violation { getCodeLocations(): MockCodeLocation[] { return [this.primaryLocation]; } getPrimaryLocationIndex(): number { return 0; } getResourceUrls(): string[] { return []; } + getFixes(): Fix[] { return []; } + getSuggestions(): Suggestion[] { return []; } } describe('Suppression Markers Integration Tests', () => { diff --git a/packages/code-analyzer-core/test/suppressions/suppression-processor.test.ts b/packages/code-analyzer-core/test/suppressions/suppression-processor.test.ts index 28b32024..3e8834a6 100644 --- a/packages/code-analyzer-core/test/suppressions/suppression-processor.test.ts +++ b/packages/code-analyzer-core/test/suppressions/suppression-processor.test.ts @@ -8,7 +8,7 @@ import { filterSuppressedViolations } from '../../src/suppressions/suppression-processor'; import { FileSuppressions, SuppressionRange } from '../../src/suppressions/suppression-types'; -import { Violation, CodeLocation } from '../../src/results'; +import { Violation, CodeLocation, Fix, Suggestion } from '../../src/results'; import { Rule, SeverityLevel } from '../../src/rules'; // Mock implementations for testing @@ -108,6 +108,14 @@ class MockViolation implements Violation { getResourceUrls(): string[] { return []; } + + getFixes(): Fix[] { + return []; + } + + getSuggestions(): Suggestion[] { + return []; + } } describe('isViolationSuppressed', () => {