diff --git a/SHA256.md b/SHA256.md index 96a36593..d058dbc8 100644 --- a/SHA256.md +++ b/SHA256.md @@ -15,7 +15,7 @@ make sure that their SHA values match the values in the list below. shasum -a 256 3. Confirm that the SHA in your output matches the value in this list of SHAs. - 86a417c38f40fec2f21bcafb12f418e0b982b2bf037181f7596797d6e5ae0bf7 ./extensions/sfdx-code-analyzer-vscode-1.12.0.vsix + e30c80e4f11990634c3a8dfe670063c8fc87dabced547375adf49dec8d01e88c ./extensions/sfdx-code-analyzer-vscode-1.13.0.vsix 4. Change the filename extension for the file that you downloaded from .zip to .vsix. diff --git a/package-lock.json b/package-lock.json index 02762ab3..cf14891a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sfdx-code-analyzer-vscode", - "version": "1.13.0", + "version": "1.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sfdx-code-analyzer-vscode", - "version": "1.13.0", + "version": "1.14.0", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/package.json b/package.json index a4bc1300..33d75077 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "color": "#ECECEC", "theme": "light" }, - "version": "1.13.0", + "version": "1.14.0", "publisher": "salesforce", "license": "BSD-3-Clause", "engines": { @@ -154,6 +154,61 @@ "markdownDescription": "Selection of rules used to scan your code with Code Analyzer.\n\nSelect rules using their name, engine name, severity level, tag, or a combination. Use commas for unions (such as \"Security,Performance\") and colons for intersections (such as \"pmd:Security\" or \"eslint:3\").\n\nThis setting is equivalent to the `--rule-selector` flag of the CLI commands. See [examples](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_code-analyzer_commands_unified.htm#cli_reference_code-analyzer_rules_unified)." } } + }, + { + "title": "Diagnostic Levels", + "properties": { + "codeAnalyzer.severity 1": { + "type": "string", + "enum": [ + "Error", + "Warning", + "Info" + ], + "default": "Warning", + "markdownDescription": "Diagnostic level for severity 1 violations (highest severity)." + }, + "codeAnalyzer.severity 2": { + "type": "string", + "enum": [ + "Error", + "Warning", + "Info" + ], + "default": "Warning", + "markdownDescription": "Diagnostic level for severity 2 violations." + }, + "codeAnalyzer.severity 3": { + "type": "string", + "enum": [ + "Error", + "Warning", + "Info" + ], + "default": "Warning", + "markdownDescription": "Diagnostic level for severity 3 violations." + }, + "codeAnalyzer.severity 4": { + "type": "string", + "enum": [ + "Error", + "Warning", + "Info" + ], + "default": "Warning", + "markdownDescription": "Diagnostic level for severity 4 violations." + }, + "codeAnalyzer.severity 5": { + "type": "string", + "enum": [ + "Error", + "Warning", + "Info" + ], + "default": "Warning", + "markdownDescription": "Diagnostic level for severity 5 violations (lowest severity)." + } + } } ], "menus": { diff --git a/src/extension.ts b/src/extension.ts index f6d55621..6df23f5e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -84,8 +84,20 @@ export async function activate(context: vscode.ExtensionContext): Promise diagnosticManager.handleTextDocumentChangeEvent(e)); + + // Listen for severity setting changes and refresh diagnostics + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('codeAnalyzer.severity 1') || + e.affectsConfiguration('codeAnalyzer.severity 2') || + e.affectsConfiguration('codeAnalyzer.severity 3') || + e.affectsConfiguration('codeAnalyzer.severity 4') || + e.affectsConfiguration('codeAnalyzer.severity 5')) { + diagnosticManager.refreshDiagnostics(); + } + }); + context.subscriptions.push(diagnosticManager); const scanManager: ScanManager = new ScanManager(); // TODO: We will be moving more of scanning stuff into the scan manager soon context.subscriptions.push(scanManager); @@ -100,7 +112,8 @@ export async function activate(context: vscode.ExtensionContext): Promise { logger.debug(`Access to ApexGuru has been set '${access}'.`); void vscode.commands.executeCommand('setContext', Constants.CONTEXT_VAR_SHOULD_SHOW_APEX_GURU_BUTTONS, diff --git a/src/lib/apexguru/apex-guru-run-action.ts b/src/lib/apexguru/apex-guru-run-action.ts index 7ead56f5..e8a76f70 100644 --- a/src/lib/apexguru/apex-guru-run-action.ts +++ b/src/lib/apexguru/apex-guru-run-action.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode"; import * as Constants from "../constants" import { ProgressReporter, TaskWithProgressRunner } from "../progress"; -import { CodeAnalyzerDiagnostic, DiagnosticManager, Violation } from "../diagnostics"; +import { CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, Violation } from "../diagnostics"; import { TelemetryService } from "../external-services/telemetry-service"; import { Display } from "../display"; import { messages } from "../messages"; @@ -12,17 +12,19 @@ export class ApexGuruRunAction { private readonly taskWithProgressRunner: TaskWithProgressRunner; private readonly apexGuruService: ApexGuruService; private readonly diagnosticManager: DiagnosticManager; + private readonly diagnosticFactory: DiagnosticFactory; private readonly telemetryService: TelemetryService; private readonly display: Display; - constructor(taskWithProgressRunner: TaskWithProgressRunner, apexGuruService: ApexGuruService, diagnosticManager: DiagnosticManager, telemetryService: TelemetryService, display: Display) { + constructor(taskWithProgressRunner: TaskWithProgressRunner, apexGuruService: ApexGuruService, diagnosticManager: DiagnosticManager, diagnosticFactory: DiagnosticFactory, telemetryService: TelemetryService, display: Display) { this.taskWithProgressRunner = taskWithProgressRunner; this.apexGuruService = apexGuruService; this.diagnosticManager = diagnosticManager; + this.diagnosticFactory = diagnosticFactory; this.telemetryService = telemetryService; this.display = display; } - + /** * Runs apex guru analysis against the specified file and displays the results. * @param commandName The command being run @@ -54,7 +56,7 @@ export class ApexGuruRunAction { increment: 90 }); - const diagnostics: CodeAnalyzerDiagnostic[] = violations.map(v => CodeAnalyzerDiagnostic.fromViolation(v)); + const diagnostics: CodeAnalyzerDiagnostic[] = violations.map(v => this.diagnosticFactory.fromViolation(v)); const oldApexGuruDiagnostics: CodeAnalyzerDiagnostic[] = this.diagnosticManager.getDiagnosticsForFile(fileUri) .filter(d => d.violation.engine === APEX_GURU_ENGINE_NAME); diff --git a/src/lib/code-analyzer-run-action.ts b/src/lib/code-analyzer-run-action.ts index e699c08a..8f3ebcb7 100644 --- a/src/lib/code-analyzer-run-action.ts +++ b/src/lib/code-analyzer-run-action.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import {Logger} from "./logger"; -import {CodeAnalyzerDiagnostic, DiagnosticManager, normalizeViolation, Violation} from "./diagnostics"; +import {CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, normalizeViolation, Violation} from "./diagnostics"; import {messages} from "./messages"; import {TelemetryService} from "./external-services/telemetry-service"; import * as Constants from './constants'; @@ -18,16 +18,18 @@ export class CodeAnalyzerRunAction { private readonly taskWithProgressRunner: TaskWithProgressRunner; private readonly codeAnalyzer: CodeAnalyzer; private readonly diagnosticManager: DiagnosticManager; + private readonly diagnosticFactory: DiagnosticFactory; private readonly telemetryService: TelemetryService; private readonly logger: Logger; private readonly display: Display; private readonly windowManager: WindowManager; private suppressedErrors: Set = new Set(); - constructor(taskWithProgressRunner: TaskWithProgressRunner, codeAnalyzer: CodeAnalyzer, diagnosticManager: DiagnosticManager, telemetryService: TelemetryService, logger: Logger, display: Display, windowManager: WindowManager) { + constructor(taskWithProgressRunner: TaskWithProgressRunner, codeAnalyzer: CodeAnalyzer, diagnosticManager: DiagnosticManager, diagnosticFactory: DiagnosticFactory, telemetryService: TelemetryService, logger: Logger, display: Display, windowManager: WindowManager) { this.taskWithProgressRunner = taskWithProgressRunner; this.codeAnalyzer = codeAnalyzer; this.diagnosticManager = diagnosticManager; + this.diagnosticFactory = diagnosticFactory; this.telemetryService = telemetryService; this.logger = logger; this.display = display; @@ -90,18 +92,18 @@ export class CodeAnalyzerRunAction { // past the length of the line in the editor window). const diagnostics: CodeAnalyzerDiagnostic[] = violationsWithFileLocation .map(v => normalizeViolation(v)) // <-- Maybe in the future we'll pass in the lineLengths for the primary file - .map(v => CodeAnalyzerDiagnostic.fromViolation(v)); + .map(v => this.diagnosticFactory.fromViolation(v)); const targetedFiles: string[] = await workspace.getTargetedFiles(); - - // Before adding in the new code analyzer diagnostics, we clear all the old code analyzer diagnostics + + // Before adding in the new code analyzer diagnostics, we clear all the old code analyzer diagnostics // except for ApexGuru based diagnostics which are handled separately. for (const file of targetedFiles) { - const diagsToClear: CodeAnalyzerDiagnostic[] = + const diagsToClear: CodeAnalyzerDiagnostic[] = this.diagnosticManager.getDiagnosticsForFile(vscode.Uri.file(file)) .filter(d => d.violation.engine !== APEX_GURU_ENGINE_NAME); this.diagnosticManager.clearDiagnostics(diagsToClear); } - + this.diagnosticManager.addDiagnostics(diagnostics); void this.displayResults(targetedFiles.length, violationsWithFileLocation); diff --git a/src/lib/diagnostics.ts b/src/lib/diagnostics.ts index a3e55ab6..b99e1053 100644 --- a/src/lib/diagnostics.ts +++ b/src/lib/diagnostics.ts @@ -5,6 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import {messages} from './messages'; +import {SettingsManager} from "./settings"; import * as vscode from 'vscode'; // For now we attempt to match the JsonViolationOutput schema as much as possible so that we don't need to transform @@ -65,11 +66,11 @@ export class CodeAnalyzerDiagnostic extends vscode.Diagnostic { readonly uri: vscode.Uri; // Private - see the fromViolation method below to see assumptions made on this constructor - private constructor(violation: Violation) { + private constructor(violation: Violation, severity: vscode.DiagnosticSeverity) { const primaryLocation: CodeLocation = violation.locations[violation.primaryLocationIndex]; super(toRange(primaryLocation), messages.diagnostics.messageGenerator(violation.severity, violation.message.trim()), - vscode.DiagnosticSeverity.Warning); // TODO: We should consider using 'Error' for sev 1 instead of always just using 'Warning'. Note that we reserve 'Information' for stale diagnostics. + severity); this.violation = violation; this.uri = vscode.Uri.file(primaryLocation.file); } @@ -84,18 +85,67 @@ export class CodeAnalyzerDiagnostic extends vscode.Diagnostic { this.severity = vscode.DiagnosticSeverity.Information; } } +5 + /** + * @internal + * This method is for internal use by DiagnosticFactory only. + * Use DiagnosticFactory.fromViolation() instead. + */ + static create(violation: Violation, severity: vscode.DiagnosticSeverity): CodeAnalyzerDiagnostic { + return new CodeAnalyzerDiagnostic(violation, severity); + } +} + + +export type ClearDiagnosticsOptions = { + range?: vscode.Range; + engineName?: string; // e.g., 'pmd', 'eslint-lwc' + ruleName?: string; // e.g., 'ApexDoc', 'no-unused-vars' +} + +export interface DiagnosticManager extends vscode.Disposable { + addDiagnostics(diags: CodeAnalyzerDiagnostic[]): void + clearAllDiagnostics(): void + clearDiagnostic(diag: CodeAnalyzerDiagnostic): void + clearDiagnostics(diags: CodeAnalyzerDiagnostic[]): void + clearDiagnosticsFromFile(uri: vscode.Uri, clearOptions?: ClearDiagnosticsOptions): void + clearDiagnosticsForFiles(uris: vscode.Uri[]): void + getDiagnosticsForFile(uri: vscode.Uri): readonly CodeAnalyzerDiagnostic[] + handleTextDocumentChangeEvent(event: vscode.TextDocumentChangeEvent): void + refreshDiagnostics(): void +} + +/** + * Factory class for creating CodeAnalyzerDiagnostic instances. + * Uses dependency injection for SettingsManager to enable testability. + */ +export class DiagnosticFactory { + constructor(private readonly settingsManager: SettingsManager) {} + + /** + * Determines the diagnostic severity based on the violation severity and user-configured mappings. + * Defaults to Warning if not configured. + * @param violationSeverity The severity number from the violation (1=highest, 5=lowest) + * @returns The appropriate VSCode DiagnosticSeverity + */ + private getDiagnosticSeverity(violationSeverity: number): vscode.DiagnosticSeverity { + return this.settingsManager.getSeverityLevel(violationSeverity); + } /** * IMPORTANT: This method assumes that the violation at this point has a primary code location with a file. * Do not call this method on a violation that does not satisfy this assumption. * @param violation + * @returns CodeAnalyzerDiagnostic */ - static fromViolation(violation: Violation): CodeAnalyzerDiagnostic { + fromViolation(violation: Violation): CodeAnalyzerDiagnostic { if (violation.locations.length == 0 || !violation.locations[violation.primaryLocationIndex].file) { // We should never reach this line of code. It is just here to prevent us from making programming mistakes. throw new Error('An attempt to process a violation without a valid file based code location occurred. This should not happen.'); } - const diagnostic: CodeAnalyzerDiagnostic = new CodeAnalyzerDiagnostic(violation); + + const severity = this.getDiagnosticSeverity(violation.severity); + const diagnostic: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.create(violation, severity); // Some violations have ranges that are too noisy, so for now we manually fix them here while we wait on PMD to fix them: const rulesToReduceViolationsToSingleLine: string[] = [ @@ -126,7 +176,7 @@ export class CodeAnalyzerDiagnostic extends vscode.Diagnostic { if (i !== violation.primaryLocationIndex && relatedLocation.file) { const relatedRange = toRange(relatedLocation); const vscodeLocation: vscode.Location = new vscode.Location(vscode.Uri.file(relatedLocation.file), relatedRange); - relatedLocations.push(new vscode.DiagnosticRelatedInformation(vscodeLocation, relatedLocation.comment ?? + relatedLocations.push(new vscode.DiagnosticRelatedInformation(vscodeLocation, relatedLocation.comment ?? messages.diagnostics.defaultAlternativeLocationMessage )); } @@ -138,29 +188,13 @@ export class CodeAnalyzerDiagnostic extends vscode.Diagnostic { } } - -export type ClearDiagnosticsOptions = { - range?: vscode.Range; - engineName?: string; // e.g., 'pmd', 'eslint-lwc' - ruleName?: string; // e.g., 'ApexDoc', 'no-unused-vars' -} - -export interface DiagnosticManager extends vscode.Disposable { - addDiagnostics(diags: CodeAnalyzerDiagnostic[]): void - clearAllDiagnostics(): void - clearDiagnostic(diag: CodeAnalyzerDiagnostic): void - clearDiagnostics(diags: CodeAnalyzerDiagnostic[]): void - clearDiagnosticsFromFile(uri: vscode.Uri, clearOptions?: ClearDiagnosticsOptions): void - clearDiagnosticsForFiles(uris: vscode.Uri[]): void - getDiagnosticsForFile(uri: vscode.Uri): readonly CodeAnalyzerDiagnostic[] - handleTextDocumentChangeEvent(event: vscode.TextDocumentChangeEvent): void -} - export class DiagnosticManagerImpl implements DiagnosticManager { private readonly diagnosticCollection: vscode.DiagnosticCollection; + public readonly diagnosticFactory: DiagnosticFactory; - public constructor(diagnosticCollection: vscode.DiagnosticCollection) { + public constructor(diagnosticCollection: vscode.DiagnosticCollection, settingsManager: SettingsManager) { this.diagnosticCollection = diagnosticCollection; + this.diagnosticFactory = new DiagnosticFactory(settingsManager); } public addDiagnostics(diags: CodeAnalyzerDiagnostic[]) { @@ -243,12 +277,38 @@ export class DiagnosticManagerImpl implements DiagnosticManager { const replacementLines: string[] = change.text.split('\n'); const updatedDiagnostics: CodeAnalyzerDiagnostic[] = diags - .map(diag => adjustDiagnosticToChange(diag, change, replacementLines)) - .filter(d => d !== null); // Removes the diagnostics that were marked for removal via null + .map(diag => adjustDiagnosticToChange(diag, change, replacementLines, this.diagnosticFactory)) + .filter((d): d is CodeAnalyzerDiagnostic => d !== null); // Removes the diagnostics that were marked for removal via null this.setDiagnosticsForFile(event.document.uri, updatedDiagnostics); } } + /** + * Refreshes all existing diagnostics by re-evaluating their severity based on current settings. + * This is called when severity settings change to update all displayed diagnostics. + */ + public refreshDiagnostics(): void { + this.diagnosticCollection.forEach((uri, diagnostics) => { + if (diagnostics.length === 0) { + return; + } + + // Cast to CodeAnalyzerDiagnostic and recreate with updated severity + const currentDiagnostics = diagnostics as CodeAnalyzerDiagnostic[]; + const refreshedDiagnostics: CodeAnalyzerDiagnostic[] = currentDiagnostics.map(diag => { + // Recreate the diagnostic with the current severity setting + const refreshedDiag = this.diagnosticFactory.fromViolation(diag.violation); + // Preserve stale state if the original diagnostic was stale + if (diag.isStale()) { + refreshedDiag.markStale(); + } + return refreshedDiag; + }); + + this.setDiagnosticsForFile(uri, refreshedDiagnostics); + }); + } + private addDiagnosticsForFile(uri: vscode.Uri, newDiags: CodeAnalyzerDiagnostic[]): void { const currentDiags: readonly CodeAnalyzerDiagnostic[] = this.getDiagnosticsForFile(uri); this.setDiagnosticsForFile(uri, [...currentDiags, ...newDiags]); @@ -323,13 +383,13 @@ function adjustToZeroBased(value: number): number { function adjustDiagnosticToChange(diag: CodeAnalyzerDiagnostic, change: vscode.TextDocumentContentChangeEvent, - replacementLines: string[]): CodeAnalyzerDiagnostic | null { + replacementLines: string[], diagnosticFactory: DiagnosticFactory): CodeAnalyzerDiagnostic | null { const violationAdjustment: Adjustment = adjustViolationToChange(diag.violation, change, replacementLines); if (violationAdjustment.newValue === null) { return null; // Do not add back a diagnostic if its violation has been marked for removal } - const newDiag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(diag.violation); + const newDiag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(diag.violation); if (violationAdjustment.overlapsWithChange || diag.isStale()) { diag.markStale(); // Not really needed, but added for safety just in case somehow the old diagnostic doesn't properly get thrown away. @@ -388,7 +448,7 @@ function adjustViolationToChange(oldViolation: Violation, change: vscode.TextDoc return suggestion; } const locationAdjustment: Adjustment = adjustLocationToChange(suggestion.location, change, replacementLines); - return locationAdjustment.newValue === null || locationAdjustment.overlapsWithChange ? + return locationAdjustment.newValue === null || locationAdjustment.overlapsWithChange ? null : {...suggestion, location: locationAdjustment.newValue}; }).filter(suggestion => suggestion !== null); @@ -409,7 +469,7 @@ function adjustLocationToChange(origLocation: CodeLocation, change: vscode.TextD startLine: rangeAdjustment.newValue.start.line + 1, startColumn: rangeAdjustment.newValue.start.character + 1, endLine: rangeAdjustment.newValue.end.line + 1, - endColumn: rangeAdjustment.newValue.end.character >= Number.MAX_SAFE_INTEGER ? + endColumn: rangeAdjustment.newValue.end.character >= Number.MAX_SAFE_INTEGER ? undefined : rangeAdjustment.newValue.end.character + 1 }, overlapsWithChange: rangeAdjustment.overlapsWithChange diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 586b9183..fc6b6410 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -15,6 +15,7 @@ export interface SettingsManager { // Configuration Settings getCodeAnalyzerConfigFile(): string; getCodeAnalyzerRuleSelectors(): string; + getSeverityLevel(severity: number): vscode.DiagnosticSeverity; // Other Settings that we may depend on getEditorCodeLensEnabled(): boolean; @@ -55,6 +56,31 @@ export class SettingsManagerImpl implements SettingsManager { return vscode.workspace.getConfiguration('codeAnalyzer').get('ruleSelectors'); } + // ================================================================================================================= + // ==== Diagnostic Levels Settings + // ================================================================================================================= + /** + * Maps configuration string values to VSCode diagnostic severity + * @returns VSCode diagnostic severity (Error, Warning, or Information) + */ + private mapToDiagnosticSeverity(configValue: string): vscode.DiagnosticSeverity { + switch (configValue) { + case 'Error': + return vscode.DiagnosticSeverity.Error; + case 'Warning': + return vscode.DiagnosticSeverity.Warning; + case 'Info': + return vscode.DiagnosticSeverity.Information; + default: + return vscode.DiagnosticSeverity.Warning; + } + } + + public getSeverityLevel(severity: number): vscode.DiagnosticSeverity { + const configValue = vscode.workspace.getConfiguration('codeAnalyzer').get(`severity ${severity}`) || 'Warning'; + return this.mapToDiagnosticSeverity(configValue); + } + // ================================================================================================================= // ==== Other Settings that we may depend on // ================================================================================================================= diff --git a/test/lib/agentforce/a4d-fix-action.test.ts b/test/lib/agentforce/a4d-fix-action.test.ts index 5f80a19e..4367ce62 100644 --- a/test/lib/agentforce/a4d-fix-action.test.ts +++ b/test/lib/agentforce/a4d-fix-action.test.ts @@ -40,7 +40,7 @@ describe('Tests for A4DFixAction', () => { unifiedDiffService = new stubs.SpyUnifiedDiffService(); diagnosticCollection = new FakeDiagnosticCollection(); diagnosticCollection.set(sampleUri, [sampleDiagForSingleLine, sampleDiagThatSpansTwoLines]); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, new stubs.StubSettingsManager()); telemetryService = new stubs.SpyTelemetryService(); logger = new stubs.SpyLogger(); display = new stubs.SpyDisplay(); diff --git a/test/lib/apexguru/apex-guru-run-action.test.ts b/test/lib/apexguru/apex-guru-run-action.test.ts index 563dd2e4..6f45f627 100644 --- a/test/lib/apexguru/apex-guru-run-action.test.ts +++ b/test/lib/apexguru/apex-guru-run-action.test.ts @@ -1,7 +1,7 @@ import * as vscode from "vscode";// The vscode module is mocked out. See: scripts/setup.jest.ts import * as stubs from "../../stubs"; -import { CodeAnalyzerDiagnostic, DiagnosticManager, DiagnosticManagerImpl, Violation } from "../../../src/lib/diagnostics"; +import { CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl, Violation } from "../../../src/lib/diagnostics"; import { FakeDiagnosticCollection } from "../../vscode-stubs"; import { ApexGuruRunAction } from "../../../src/lib/apexguru/apex-guru-run-action"; import { createSampleCodeAnalyzerDiagnostic } from "../../test-utils"; @@ -18,6 +18,7 @@ describe("Tests for ApexGuruRunAction", () => { let apexGuruService: stubs.StubApexGuruService; let diagnosticCollection: vscode.DiagnosticCollection; let diagnosticManager: DiagnosticManager; + let diagnosticFactory: DiagnosticFactory; let telemetryService: stubs.SpyTelemetryService; let display: stubs.SpyDisplay; let apexGuruRunAction: ApexGuruRunAction; @@ -26,17 +27,22 @@ describe("Tests for ApexGuruRunAction", () => { taskWithProgressRunner = new stubs.FakeTaskWithProgressRunner(); apexGuruService = new stubs.StubApexGuruService(); diagnosticCollection = new FakeDiagnosticCollection(); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + const settingsManager = new stubs.StubSettingsManager(); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, settingsManager); + diagnosticFactory = (diagnosticManager as DiagnosticManagerImpl).diagnosticFactory; diagnosticManager.addDiagnostics([samplePmdDiag, sampleApexGuruDiag]); // Start with some sample diagnostics telemetryService = new stubs.SpyTelemetryService(); display = new stubs.SpyDisplay(); apexGuruRunAction = new ApexGuruRunAction( - taskWithProgressRunner, apexGuruService, diagnosticManager, telemetryService, display); + taskWithProgressRunner, apexGuruService, diagnosticManager, diagnosticFactory, telemetryService, display); }); it("When ApexGuru scan throws error, then display error in error window and send exception telemetry event", async () => { + const throwingService = new stubs.ThrowingScanApexGuruService(); + const throwingDiagnosticManager = new DiagnosticManagerImpl(new FakeDiagnosticCollection(), new stubs.StubSettingsManager()); + const throwingDiagnosticFactory = throwingDiagnosticManager.diagnosticFactory; apexGuruRunAction = new ApexGuruRunAction( - taskWithProgressRunner, new stubs.ThrowingScanApexGuruService(), diagnosticManager, telemetryService, display); + taskWithProgressRunner, throwingService, throwingDiagnosticManager, throwingDiagnosticFactory, telemetryService, display); await apexGuruRunAction.run('SomeCommandName', sampleUri); @@ -226,8 +232,12 @@ describe("Tests for ApexGuruRunAction", () => { // Also validate that we removed the old apexguru diagnostic(s) but kept the other(s) const actDiags: readonly CodeAnalyzerDiagnostic[] = diagnosticManager.getDiagnosticsForFile(sampleUri); - const expDiags: CodeAnalyzerDiagnostic[] = [samplePmdDiag, CodeAnalyzerDiagnostic.fromViolation(violation1), - CodeAnalyzerDiagnostic.fromViolation(violation2), CodeAnalyzerDiagnostic.fromViolation(violation3)]; + const expDiags: CodeAnalyzerDiagnostic[] = [ + samplePmdDiag, + diagnosticFactory.fromViolation(violation1), + diagnosticFactory.fromViolation(violation2), + diagnosticFactory.fromViolation(violation3) + ].filter((d): d is CodeAnalyzerDiagnostic => d !== null); expectEquivalentDiagnostics(actDiags, expDiags); }); }); diff --git a/test/lib/apply-violation-fixes-action-provider.test.ts b/test/lib/apply-violation-fixes-action-provider.test.ts index 7c13e247..683c5759 100644 --- a/test/lib/apply-violation-fixes-action-provider.test.ts +++ b/test/lib/apply-violation-fixes-action-provider.test.ts @@ -4,10 +4,12 @@ import { createTextDocument } from "jest-mock-vscode"; import { StubCodeActionContext } from "../vscode-stubs"; import { ApplyViolationFixesAction } from "../../src/lib/apply-violation-fixes-action"; import { ApplyViolationFixesActionProvider } from "../../src/lib/apply-violation-fixes-action-provider"; -import { CodeAnalyzerDiagnostic } from "../../src/lib/diagnostics"; +import { CodeAnalyzerDiagnostic, DiagnosticFactory } from "../../src/lib/diagnostics"; +import * as stubs from "../stubs"; describe('ApplyViolationFixesActionProvider Tests', () => { let actionProvider: ApplyViolationFixesActionProvider; + const testDiagnosticFactory = new DiagnosticFactory(new stubs.StubSettingsManager()); beforeEach(() => { actionProvider = new ApplyViolationFixesActionProvider(); @@ -51,26 +53,31 @@ describe('ApplyViolationFixesActionProvider Tests', () => { `}`; const sampleApexDocument: vscode.TextDocument = createTextDocument(sampleApexUri, sampleApexContent, 'apex'); - const sampleDiag1: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag1: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 4 }, 'AvoidUsingSchemaGetGlobalDescribe', 'apexguru', // Note that these rule names are made up right now [{ location: { file: sampleApexUri.fsPath, startLine: 4, startColumn: 9 }, fixedCode: 'Schema.DescribeSObjectResult opportunityDescribe = Opportunity.sObjectType.getDescribe()' }] - )) - const sampleDiag2: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + )); + if (!sampleDiag1) throw new Error('Failed to create sampleDiag1'); + const sampleDiag2: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 9 }, 'AvoidSOQLInLoop', 'apexguru' - )) - const sampleDiag3: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + )); + if (!sampleDiag2) throw new Error('Failed to create sampleDiag2'); + const sampleDiag3: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 13 }, 'AvoidDMLInLoop', 'apexguru' - )) - const sampleDiag4: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + )); + if (!sampleDiag3) throw new Error('Failed to create sampleDiag3'); + const sampleDiag4: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 18 }, 'AvoidSOQLWithNegativeExpression', 'apexguru' - )) - const sampleDiag5: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + )); + if (!sampleDiag4) throw new Error('Failed to create sampleDiag4'); + const sampleDiag5: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 22 }, 'AvoidSOQLWithoutWhereClauseOrLimit', 'apexguru' - )) - const sampleDiag6: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + )); + if (!sampleDiag5) throw new Error('Failed to create sampleDiag5'); + const sampleDiag6: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 26 }, 'AvoidUsingSObjectsToInBind', 'apexguru', [{ location: { file: sampleApexUri.fsPath, startLine: 26 }, @@ -78,10 +85,12 @@ describe('ApplyViolationFixesActionProvider Tests', () => { `//Inside the SOQL: convert "accounts" into "accountsMap.keySet()"` }] - )) - const sampleDiag7: vscode.Diagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + )); + if (!sampleDiag6) throw new Error('Failed to create sampleDiag6'); + const sampleDiag7: vscode.Diagnostic | null = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 30 }, 'AvoidSOQLWithWildcardFilters', 'apexguru' - )) + )); + if (!sampleDiag7) throw new Error('Failed to create sampleDiag7'); // TODO: This test is temporary (as it is tied to the apex guru pilot code) and will be generalized soon. @@ -109,7 +118,7 @@ describe('ApplyViolationFixesActionProvider Tests', () => { }); it('stale diagnostics are filtered out', () => { - const staleDiag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const staleDiag: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 4 }, 'AvoidUsingSchemaGetGlobalDescribe', 'apexguru', [{ location: { file: sampleApexUri.fsPath, startLine: 4, startColumn: 9 }, diff --git a/test/lib/apply-violation-fixes-action.test.ts b/test/lib/apply-violation-fixes-action.test.ts index 76bb249c..784254ba 100644 --- a/test/lib/apply-violation-fixes-action.test.ts +++ b/test/lib/apply-violation-fixes-action.test.ts @@ -3,7 +3,7 @@ import * as vscode from "vscode"; // The vscode module is mocked out. See: scrip import * as stubs from "../stubs"; import { createTextDocument } from "jest-mock-vscode"; import { createSampleViolation } from "../test-utils"; -import { CodeAnalyzerDiagnostic, DiagnosticManager, DiagnosticManagerImpl } from "../../src/lib/diagnostics"; +import { CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl } from "../../src/lib/diagnostics"; import { FakeDiagnosticCollection } from "../vscode-stubs"; import { ApplyViolationFixesAction } from "../../src/lib/apply-violation-fixes-action"; import { messages } from "../../src/lib/messages"; @@ -26,14 +26,16 @@ describe('Tests for ApplyViolationFixesAction', () => { `}`; const sampleDocument: vscode.TextDocument = createTextDocument(sampleUri, sampleContent, 'apex'); - const sampleDiag1: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const testDiagnosticFactory = new DiagnosticFactory(new stubs.StubSettingsManager()); + const sampleDiag1: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleUri.fsPath, startLine: 4 }, 'AvoidUsingSchemaGetGlobalDescribe', 'apexguru', // Note that these rule names are made up right now [{ location: { file: sampleUri.fsPath, startLine: 4, startColumn: 9 }, fixedCode: 'Schema.DescribeSObjectResult opportunityDescribe = Opportunity.sObjectType.getDescribe();' }] )); - const sampleDiag2: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( // not relevant because it has no fixes + if (!sampleDiag1) throw new Error('Failed to create sampleDiag1'); + const sampleDiag2: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( // not relevant because it has no fixes { file: sampleUri.fsPath, startLine: 9 }, 'AvoidSOQLInLoop', 'apexguru' )); @@ -48,8 +50,9 @@ describe('Tests for ApplyViolationFixesAction', () => { beforeEach(() => { unifiedDiffService = new stubs.SpyUnifiedDiffService(); diagnosticCollection = new FakeDiagnosticCollection(); + if (!sampleDiag1 || !sampleDiag2) throw new Error('Failed to create sample diagnostics'); diagnosticCollection.set(sampleUri, [sampleDiag1, sampleDiag2]); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, new stubs.StubSettingsManager()); telemetryService = new stubs.SpyTelemetryService(); logger = new stubs.SpyLogger(); display = new stubs.SpyDisplay(); @@ -187,7 +190,8 @@ describe('Tests for ApplyViolationFixesAction', () => { it('When fix suggested is exactly the same as the original code, then show info message saying that no fix was suggested', async () => { // this fixed code is exactly the same as lines 2 and 3 - which sampleDiagThatSpansTwoLines's range gets extended to - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + if (!sampleDiag2) throw new Error('Failed to create sampleDiag2'); + const diag: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleUri.fsPath, startLine: 1, endLine: 5 }, 'SomeRuleName', 'SomeEngine', [{ location: { file: sampleUri.fsPath, startLine: 1, startColumn: 8, endColumn: 13 }, diff --git a/test/lib/code-analyzer-run-action.test.ts b/test/lib/code-analyzer-run-action.test.ts index 244a76b8..36f65cf6 100644 --- a/test/lib/code-analyzer-run-action.test.ts +++ b/test/lib/code-analyzer-run-action.test.ts @@ -6,9 +6,9 @@ import { SpyLogger, SpyTelemetryService, SpyWindowManager, - StubCodeAnalyzer, StubFileHandler, StubVscodeWorkspace + StubCodeAnalyzer, StubFileHandler, StubSettingsManager, StubVscodeWorkspace } from "../stubs"; -import {CodeAnalyzerDiagnostic, CodeLocation, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; +import {CodeAnalyzerDiagnostic, CodeLocation, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; import {FakeDiagnosticCollection} from "../vscode-stubs"; import {CodeAnalyzerRunAction, UNINSTANTIABLE_ENGINE_RULE} from "../../src/lib/code-analyzer-run-action"; import {messages} from "../../src/lib/messages"; @@ -34,13 +34,15 @@ describe('Tests for CodeAnalyzerRunAction', () => { taskWithProgressRunner = new FakeTaskWithProgressRunner(); codeAnalyzer = new StubCodeAnalyzer(); diagnosticCollection = new FakeDiagnosticCollection(); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + const settingsManager = new StubSettingsManager(); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, settingsManager); + const diagnosticFactory = (diagnosticManager as DiagnosticManagerImpl).diagnosticFactory; telemetryService = new SpyTelemetryService(); logger = new SpyLogger(); display = new SpyDisplay(); windowManager = new SpyWindowManager(); codeAnalyzerRunAction = new CodeAnalyzerRunAction(taskWithProgressRunner, codeAnalyzer, diagnosticManager, - telemetryService, logger, display, windowManager); + diagnosticFactory, telemetryService, logger, display, windowManager); }); it('When scan results in violations that are not associated with a file location, then show violation as display messages', async () => { @@ -90,8 +92,9 @@ describe('Tests for CodeAnalyzerRunAction', () => { }); it('When scan happens, it should clear old Code Analyzer diagnostics before setting new ones but keep existing ApexGuru diagnostics', async () => { - diagnosticManager.addDiagnostics([ - CodeAnalyzerDiagnostic.fromViolation({ + const testDiagnosticFactory = new DiagnosticFactory(new StubSettingsManager()); + const diags = [ + testDiagnosticFactory.fromViolation({ rule: 'dummyRule1', engine: APEX_GURU_ENGINE_NAME, message: 'messageFromApexGuru', @@ -101,7 +104,7 @@ describe('Tests for CodeAnalyzerRunAction', () => { tags: [], resources: [] }), - CodeAnalyzerDiagnostic.fromViolation({ + testDiagnosticFactory.fromViolation({ rule: 'dummyRule1', engine: 'pmd', message: 'messageFromPmd', @@ -111,7 +114,8 @@ describe('Tests for CodeAnalyzerRunAction', () => { tags: [], resources: [] }) - ]); + ].filter((d): d is CodeAnalyzerDiagnostic => d !== null); + diagnosticManager.addDiagnostics(diags); codeAnalyzer.scanReturnValue = [ createSampleViolation('New', 2, [{file: 'someFile.cls'}]), // Is sufficient to make this into a diagnostic ]; diff --git a/test/lib/diagnostics-clearDiagnostics.test.ts b/test/lib/diagnostics-clearDiagnostics.test.ts index 57e65be4..050b69d4 100644 --- a/test/lib/diagnostics-clearDiagnostics.test.ts +++ b/test/lib/diagnostics-clearDiagnostics.test.ts @@ -6,8 +6,9 @@ */ import * as vscode from "vscode"; -import {CodeAnalyzerDiagnostic, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; +import {CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; import {FakeDiagnosticCollection} from "../vscode-stubs"; +import * as stubs from "../stubs"; describe('DiagnosticManager.clearDiagnosticsFromFile', () => { let diagnosticManager: DiagnosticManager; @@ -16,7 +17,7 @@ describe('DiagnosticManager.clearDiagnosticsFromFile', () => { beforeEach(() => { diagnosticCollection = new FakeDiagnosticCollection(); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, new stubs.StubSettingsManager()); }); describe('When no options provided', () => { @@ -232,6 +233,7 @@ function createDiagnostic(uri: vscode.Uri, range: vscode.Range, engine: string, tags: [], resources: [] }; - return CodeAnalyzerDiagnostic.fromViolation(violation); + const diagnosticFactory = new DiagnosticFactory(new stubs.StubSettingsManager()); + return diagnosticFactory.fromViolation(violation); } diff --git a/test/lib/diagnostics-handleTextDocumentChangeEvent.test.ts b/test/lib/diagnostics-handleTextDocumentChangeEvent.test.ts index 6ce4cc89..72a38cfe 100644 --- a/test/lib/diagnostics-handleTextDocumentChangeEvent.test.ts +++ b/test/lib/diagnostics-handleTextDocumentChangeEvent.test.ts @@ -1,8 +1,9 @@ import * as vscode from "vscode"; // The vscode module is mocked out. See: scripts/setup.jest.ts import {createTextDocument} from "jest-mock-vscode"; import {FakeDiagnosticCollection} from "../vscode-stubs"; -import {CodeAnalyzerDiagnostic, CodeLocation, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; +import {CodeAnalyzerDiagnostic, CodeLocation, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; import {createSampleCodeAnalyzerDiagnostic} from "../test-utils"; +import * as stubs from "../stubs"; /* NOTE: Putting the tests for handleTextDocumentChangeEvent in its own file because it is a tricky algorithm and so @@ -23,10 +24,13 @@ describe(`Tests for the the DiagnosticManager class's handleTextDocumentChangeEv let diagnosticCollection: FakeDiagnosticCollection; let diagnosticManager: DiagnosticManager; + let diagnosticFactory: DiagnosticFactory; beforeEach(() => { diagnosticCollection = new FakeDiagnosticCollection(); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + const settingsManager = new stubs.StubSettingsManager(); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, settingsManager); + diagnosticFactory = (diagnosticManager as DiagnosticManagerImpl).diagnosticFactory; }); // Helper function to easily make an event for testing with a changeRange and replacementText @@ -872,7 +876,8 @@ describe(`Tests for the the DiagnosticManager class's handleTextDocumentChangeEv fixes: [], suggestions: [] } - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + if (!diag) throw new Error('Failed to create diagnostic'); diagnosticManager.addDiagnostics([diag]); const docChangeEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEventWith( @@ -915,7 +920,8 @@ describe(`Tests for the the DiagnosticManager class's handleTextDocumentChangeEv fixes: [], suggestions: [] } - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + if (!diag) throw new Error('Failed to create diagnostic'); diagnosticManager.addDiagnostics([diag]); const docChangeEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEventWith( @@ -956,7 +962,8 @@ describe(`Tests for the the DiagnosticManager class's handleTextDocumentChangeEv fixes: [], suggestions: [] } - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + if (!diag) throw new Error('Failed to create diagnostic'); diagnosticManager.addDiagnostics([diag]); const docChangeEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEventWith( @@ -1006,7 +1013,8 @@ describe(`Tests for the the DiagnosticManager class's handleTextDocumentChangeEv fixes: [], suggestions: [] } - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + if (!diag) throw new Error('Failed to create diagnostic'); diagnosticManager.addDiagnostics([diag]); const docChangeEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEventWith( @@ -1104,8 +1112,9 @@ describe(`Tests for the the DiagnosticManager class's handleTextDocumentChangeEv }, ] }; - const diag1: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation1); - const diag2: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation2); + const diag1: CodeAnalyzerDiagnostic | null = diagnosticFactory.fromViolation(violation1); + const diag2: CodeAnalyzerDiagnostic | null = diagnosticFactory.fromViolation(violation2); + if (!diag1 || !diag2) throw new Error('Failed to create diagnostics'); diagnosticManager.addDiagnostics([diag1, diag2]); const docChangeEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEventWith( diff --git a/test/lib/diagnostics.test.ts b/test/lib/diagnostics.test.ts index 4020dd8d..566e2a88 100644 --- a/test/lib/diagnostics.test.ts +++ b/test/lib/diagnostics.test.ts @@ -1,16 +1,23 @@ import * as vscode from "vscode"; // The vscode module is mocked out. See: scripts/setup.jest.ts -import {CodeAnalyzerDiagnostic, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; +import {CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl, Violation} from "../../src/lib/diagnostics"; import {FakeDiagnosticCollection} from "../vscode-stubs"; import {createSampleCodeAnalyzerDiagnostic} from "../test-utils"; import {messages} from "../../src/lib/messages"; +import * as stubs from "../stubs"; const sampleUri1: vscode.Uri = vscode.Uri.file('/path/to/file1'); const sampleUri2: vscode.Uri = vscode.Uri.file('/path/to/file2'); const sampleUri3: vscode.Uri = vscode.Uri.file('/path/to/file3'); describe('Tests for the CodeAnalyzerDiagnostic class', () => { - describe('Tests for the fromViolation static constructor method', () => { + describe('Tests for DiagnosticFactory.fromViolation method', () => { + let diagnosticFactory: DiagnosticFactory; + + beforeEach(() => { + diagnosticFactory = new DiagnosticFactory(new stubs.StubSettingsManager()); + }); + it('When a violation does not have a valid primary code location, then error', () => { const violation: Violation = { rule: 'dummyRule', @@ -22,9 +29,9 @@ describe('Tests for the CodeAnalyzerDiagnostic class', () => { tags: [], resources: [] } - expect(() => CodeAnalyzerDiagnostic.fromViolation(violation)).toThrow(); + expect(() => diagnosticFactory.fromViolation(violation)).toThrow(); violation.locations = [{}] // Case 2 - everything is undefined - expect(() => CodeAnalyzerDiagnostic.fromViolation(violation)).toThrow(); + expect(() => diagnosticFactory.fromViolation(violation)).toThrow(); }); it('When a violation with a single code location is given, then all the fields should be filled out correctly', () => { @@ -45,7 +52,8 @@ describe('Tests for the CodeAnalyzerDiagnostic class', () => { resources: ['https://hello.com', 'https://world.com'] }; - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + expect(diag).not.toBeNull(); expect(diag.violation).toEqual(violation); expect(diag.uri).toEqual(vscode.Uri.file('/path/to/some/someFile.cls')); expect(diag.code).toEqual({ @@ -91,7 +99,8 @@ describe('Tests for the CodeAnalyzerDiagnostic class', () => { resources: [] // Also test when there are no resources }; - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + expect(diag).not.toBeNull(); expect(diag.violation).toEqual(violation); expect(diag.uri).toEqual(vscode.Uri.file('/path/to/some/someFileWithSomeLineInfo.cls')); expect(diag.code).toEqual('dummyRule'); @@ -136,7 +145,8 @@ describe('Tests for the CodeAnalyzerDiagnostic class', () => { resources: [] }; - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + expect(diag).not.toBeNull(); expect(diag.range).toEqual(new vscode.Range(2, 4, 2, Number.MAX_SAFE_INTEGER)); }); @@ -155,7 +165,8 @@ describe('Tests for the CodeAnalyzerDiagnostic class', () => { resources: [] }; - const diag: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(violation); + const diag: CodeAnalyzerDiagnostic = diagnosticFactory.fromViolation(violation); + expect(diag).not.toBeNull(); expect(diag.range.start.line).toEqual(0); }); }); @@ -203,7 +214,7 @@ describe('Tests for the DiagnosticManager class', () => { beforeEach(() => { diagnosticCollection = new FakeDiagnosticCollection(); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, new stubs.StubSettingsManager()); }); describe('Tests for addDiagnostics', () => { @@ -306,4 +317,310 @@ describe('Tests for the DiagnosticManager class', () => { expect(diagnostics).toHaveLength(2); // Both diagnostics should remain }); }); + + describe('Tests for refreshDiagnostics', () => { + it('should do nothing when there are no diagnostics', () => { + diagnosticManager.refreshDiagnostics(); + expect(diagnosticCollection.get(sampleUri1)).toEqual(undefined); + }); + + it('should update severity when severity setting changes from Warning to Error', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation: Violation = { + rule: 'testRule', + engine: 'pmd', + message: 'test message', + severity: 1, + locations: [{ + file: sampleUri1.fsPath, + startLine: 1 + }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const initialDiag = diagnosticFactory.fromViolation(violation); + managerWithStub.addDiagnostics([initialDiag]); + + expect(initialDiag.severity).toBe(vscode.DiagnosticSeverity.Warning); + + // Change severity setting to Error + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + expect(refreshedDiags).toHaveLength(1); + expect(refreshedDiags[0].severity).toBe(vscode.DiagnosticSeverity.Error); + expect(refreshedDiags[0].violation).toEqual(violation); // Violation data should be preserved + expect(refreshedDiags[0].message).toBe(initialDiag.message); // Message should be preserved + }); + + it('should update severity when severity setting changes from Error to Warning', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation: Violation = { + rule: 'testRule', + engine: 'pmd', + message: 'test message', + severity: 2, + locations: [{ + file: sampleUri1.fsPath, + startLine: 1 + }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const initialDiag = diagnosticFactory.fromViolation(violation); + managerWithStub.addDiagnostics([initialDiag]); + + expect(initialDiag.severity).toBe(vscode.DiagnosticSeverity.Error); + + // Change severity setting to Warning + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + expect(refreshedDiags).toHaveLength(1); + expect(refreshedDiags[0].severity).toBe(vscode.DiagnosticSeverity.Warning); + }); + + it('should refresh diagnostics across multiple files', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation1: Violation = { + rule: 'rule1', + engine: 'pmd', + message: 'message1', + severity: 1, + locations: [{ file: sampleUri1.fsPath, startLine: 1 }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const violation2: Violation = { + rule: 'rule2', + engine: 'eslint', + message: 'message2', + severity: 2, + locations: [{ file: sampleUri2.fsPath, startLine: 2 }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const diag1 = diagnosticFactory.fromViolation(violation1); + const diag2 = diagnosticFactory.fromViolation(violation2); + managerWithStub.addDiagnostics([diag1, diag2]); + + expect(diag1.severity).toBe(vscode.DiagnosticSeverity.Warning); + expect(diag2.severity).toBe(vscode.DiagnosticSeverity.Warning); + + // Change severity setting to Error + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags1 = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + const refreshedDiags2 = diagnosticCollection.get(sampleUri2) as CodeAnalyzerDiagnostic[]; + + expect(refreshedDiags1).toHaveLength(1); + expect(refreshedDiags1[0].severity).toBe(vscode.DiagnosticSeverity.Error); + expect(refreshedDiags1[0].violation).toEqual(violation1); + + expect(refreshedDiags2).toHaveLength(1); + expect(refreshedDiags2[0].severity).toBe(vscode.DiagnosticSeverity.Error); + expect(refreshedDiags2[0].violation).toEqual(violation2); + }); + + it('should refresh multiple diagnostics in the same file', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation1: Violation = { + rule: 'rule1', + engine: 'pmd', + message: 'message1', + severity: 1, + locations: [{ file: sampleUri1.fsPath, startLine: 1 }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const violation2: Violation = { + rule: 'rule2', + engine: 'pmd', + message: 'message2', + severity: 3, + locations: [{ file: sampleUri1.fsPath, startLine: 5 }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const diag1 = diagnosticFactory.fromViolation(violation1); + const diag2 = diagnosticFactory.fromViolation(violation2); + managerWithStub.addDiagnostics([diag1, diag2]); + + expect(diag1.severity).toBe(vscode.DiagnosticSeverity.Warning); + expect(diag2.severity).toBe(vscode.DiagnosticSeverity.Warning); + + // Change severity setting to Error + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + expect(refreshedDiags).toHaveLength(2); + expect(refreshedDiags[0].severity).toBe(vscode.DiagnosticSeverity.Error); + expect(refreshedDiags[1].severity).toBe(vscode.DiagnosticSeverity.Error); + expect(refreshedDiags[0].violation).toEqual(violation1); + expect(refreshedDiags[1].violation).toEqual(violation2); + }); + + it('should preserve all diagnostic properties except severity', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation: Violation = { + rule: 'testRule', + engine: 'pmd', + message: 'test message', + severity: 1, + locations: [{ + file: sampleUri1.fsPath, + startLine: 3, + startColumn: 5, + endLine: 3, + endColumn: 10 + }], + primaryLocationIndex: 0, + tags: [], + resources: ['https://example.com'] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const initialDiag = diagnosticFactory.fromViolation(violation); + managerWithStub.addDiagnostics([initialDiag]); + + const initialRange = initialDiag.range; + const initialCode = initialDiag.code; + const initialSource = initialDiag.source; + const initialUri = initialDiag.uri; + + // Change severity setting to Error + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + expect(refreshedDiags).toHaveLength(1); + const refreshedDiag = refreshedDiags[0]; + + // Severity should change + expect(refreshedDiag.severity).toBe(vscode.DiagnosticSeverity.Error); + expect(refreshedDiag.severity).not.toBe(initialDiag.severity); + + // All other properties should be preserved + expect(refreshedDiag.range).toEqual(initialRange); + expect(refreshedDiag.code).toEqual(initialCode); + expect(refreshedDiag.source).toBe(initialSource); + expect(refreshedDiag.uri.fsPath).toBe(initialUri.fsPath); + expect(refreshedDiag.violation).toEqual(violation); + }); + + it('should preserve stale state when refreshing diagnostics', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation: Violation = { + rule: 'testRule', + engine: 'pmd', + message: 'test message', + severity: 1, + locations: [{ + file: sampleUri1.fsPath, + startLine: 1 + }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const initialDiag = diagnosticFactory.fromViolation(violation); + initialDiag.markStale(); // Mark as stale + managerWithStub.addDiagnostics([initialDiag]); + + expect(initialDiag.isStale()).toBe(true); + expect(initialDiag.severity).toBe(vscode.DiagnosticSeverity.Information); + + // Change severity setting to Error + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + expect(refreshedDiags).toHaveLength(1); + const refreshedDiag = refreshedDiags[0]; + + // Stale state should be preserved + expect(refreshedDiag.isStale()).toBe(true); + expect(refreshedDiag.severity).toBe(vscode.DiagnosticSeverity.Information); + expect(refreshedDiag.message).toContain(messages.staleDiagnosticPrefix); + }); + + it('should not mark non-stale diagnostics as stale when refreshing', () => { + const stubSettingsManager = new stubs.StubSettingsManager(); + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Warning; + const managerWithStub = new DiagnosticManagerImpl(diagnosticCollection, stubSettingsManager); + + const violation: Violation = { + rule: 'testRule', + engine: 'pmd', + message: 'test message', + severity: 1, + locations: [{ + file: sampleUri1.fsPath, + startLine: 1 + }], + primaryLocationIndex: 0, + tags: [], + resources: [] + }; + + const diagnosticFactory = managerWithStub.diagnosticFactory; + const initialDiag = diagnosticFactory.fromViolation(violation); + // Do NOT mark as stale + managerWithStub.addDiagnostics([initialDiag]); + + expect(initialDiag.isStale()).toBe(false); + + // Change severity setting to Error + stubSettingsManager.getSeverityLevelReturnValue = vscode.DiagnosticSeverity.Error; + managerWithStub.refreshDiagnostics(); + + const refreshedDiags = diagnosticCollection.get(sampleUri1) as CodeAnalyzerDiagnostic[]; + expect(refreshedDiags).toHaveLength(1); + const refreshedDiag = refreshedDiags[0]; + + // Should not be stale + expect(refreshedDiag.isStale()).toBe(false); + expect(refreshedDiag.severity).toBe(vscode.DiagnosticSeverity.Error); + }); + }); }); diff --git a/test/lib/settings.test.ts b/test/lib/settings.test.ts index 05112c4a..2074c4ce 100644 --- a/test/lib/settings.test.ts +++ b/test/lib/settings.test.ts @@ -85,6 +85,60 @@ describe('Tests for the SettingsManagerImpl class ', () => { }); }); + describe('Diagnostic Levels Settings', () => { + it('should map "Error" to DiagnosticSeverity.Error', () => { + getMock.mockReturnValue('Error'); + const result = settingsManager.getSeverityLevel(1); + expect(result).toBe(vscode.DiagnosticSeverity.Error); + expect(getMock).toHaveBeenCalledWith('severity 1'); + }); + + it('should map "Warning" to DiagnosticSeverity.Warning', () => { + getMock.mockReturnValue('Warning'); + const result = settingsManager.getSeverityLevel(2); + expect(result).toBe(vscode.DiagnosticSeverity.Warning); + expect(getMock).toHaveBeenCalledWith('severity 2'); + }); + + it('should map "Info" to DiagnosticSeverity.Information', () => { + getMock.mockReturnValue('Info'); + const result = settingsManager.getSeverityLevel(3); + expect(result).toBe(vscode.DiagnosticSeverity.Information); + expect(getMock).toHaveBeenCalledWith('severity 3'); + }); + + it('should default to Warning for unknown values', () => { + getMock.mockReturnValue('UnknownValue'); + const result = settingsManager.getSeverityLevel(5); + expect(result).toBe(vscode.DiagnosticSeverity.Warning); + expect(getMock).toHaveBeenCalledWith('severity 5'); + }); + + it('should default to Warning when config value is null', () => { + getMock.mockReturnValue(null); + const result = settingsManager.getSeverityLevel(1); + expect(result).toBe(vscode.DiagnosticSeverity.Warning); + expect(getMock).toHaveBeenCalledWith('severity 1'); + }); + + it('should default to Warning when config value is undefined', () => { + getMock.mockReturnValue(undefined); + const result = settingsManager.getSeverityLevel(2); + expect(result).toBe(vscode.DiagnosticSeverity.Warning); + expect(getMock).toHaveBeenCalledWith('severity 2'); + }); + + it('should handle different severity numbers', () => { + getMock.mockReturnValue('Error'); + expect(settingsManager.getSeverityLevel(1)).toBe(vscode.DiagnosticSeverity.Error); + expect(settingsManager.getSeverityLevel(2)).toBe(vscode.DiagnosticSeverity.Error); + expect(settingsManager.getSeverityLevel(5)).toBe(vscode.DiagnosticSeverity.Error); + expect(getMock).toHaveBeenCalledWith('severity 1'); + expect(getMock).toHaveBeenCalledWith('severity 2'); + expect(getMock).toHaveBeenCalledWith('severity 5'); + }); + }); + describe('Editor Settings', () => { it('should get codeLens setting', () => { getMock.mockReturnValue(true); diff --git a/test/lib/violation-suggestions-hover-provider.test.ts b/test/lib/violation-suggestions-hover-provider.test.ts index 711f3bf4..3455ee08 100644 --- a/test/lib/violation-suggestions-hover-provider.test.ts +++ b/test/lib/violation-suggestions-hover-provider.test.ts @@ -1,9 +1,13 @@ import * as vscode from "vscode"; // The vscode module is mocked out. See: scripts/setup.jest.ts -import { CodeAnalyzerDiagnostic, DiagnosticManager, DiagnosticManagerImpl, toRange } from "../../src/lib/diagnostics"; +import { CodeAnalyzerDiagnostic, DiagnosticFactory, DiagnosticManager, DiagnosticManagerImpl, toRange } from "../../src/lib/diagnostics"; import { FakeDiagnosticCollection } from "../vscode-stubs"; import { ViolationSuggestionsHoverProvider } from "../../src/lib/violation-suggestions-hover-provider"; import { createTextDocument } from "jest-mock-vscode"; import { createSampleViolation } from "../test-utils"; +import * as stubs from "../stubs"; + +// Create a shared DiagnosticFactory for test constants +const testDiagnosticFactory = new DiagnosticFactory(new stubs.StubSettingsManager()); describe('ViolationSuggestionsHoverProvider Tests', () => { const sampleApexUri: vscode.Uri = vscode.Uri.file('/someFile.cls'); @@ -42,41 +46,41 @@ describe('ViolationSuggestionsHoverProvider Tests', () => { ` }\n` + `}`; const sampleApexDocument: vscode.TextDocument = createTextDocument(sampleApexUri, sampleApexContent, 'apex'); - const sampleDiag1: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag1: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 4 }, 'AvoidUsingSchemaGetGlobalDescribe', 'apexguru', [], [{ location: { file: sampleApexUri.fsPath, startLine: 4 }, message: 'This is a single line suggestion that uses the same location as the violation' }] )); - const sampleDiag2: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag2: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 9 }, 'AvoidSOQLInLoop', 'apexguru', [], [] // no suggestions )); - const sampleDiag3: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag3: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 13 }, 'AvoidDMLInLoop', 'apexguru', [], [] // no suggestions )); - const sampleDiag4: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag4: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 18 }, 'AvoidSOQLWithNegativeExpression', 'apexguru', [], [{ location: { file: '/someOtherFile.cls', startLine: 4 }, message: 'This is suggestion that is associated with a different file' }] )); - const sampleDiag5: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag5: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 22 }, 'AvoidSOQLWithoutWhereClauseOrLimit', 'apexguru', [], [{ location: { file: sampleApexUri.fsPath, startLine: 22, startColumn: 22, endLine: 22, endColumn: 33 }, message: 'This is a multi\nline suggestion\nthat is only part of line 22' }] )); - const sampleDiag6: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag6: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 26 }, 'AvoidUsingSObjectsToInBind', 'apexguru', [], [{ location: { file: sampleApexUri.fsPath, startLine: 22, startColumn: 26, endLine: 23, endColumn: 6 }, message: 'This is a suggestion associated with a violation on line 26 but it shows up between line 22 and 23' }] )); - const sampleDiag7: CodeAnalyzerDiagnostic = CodeAnalyzerDiagnostic.fromViolation(createSampleViolation( + const sampleDiag7: CodeAnalyzerDiagnostic = testDiagnosticFactory.fromViolation(createSampleViolation( { file: sampleApexUri.fsPath, startLine: 30 }, 'AvoidSOQLWithWildcardFilters', 'apexguru', [], [{ location: { file: sampleApexUri.fsPath, startLine: 30, endLine: 32 }, @@ -94,7 +98,7 @@ describe('ViolationSuggestionsHoverProvider Tests', () => { beforeEach(() => { diagnosticCollection = new FakeDiagnosticCollection(); - diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection); + diagnosticManager = new DiagnosticManagerImpl(diagnosticCollection, new stubs.StubSettingsManager()); hoverProvider = new ViolationSuggestionsHoverProvider(diagnosticManager); diagnosticManager.addDiagnostics([sampleDiag1, sampleDiag2, sampleDiag3, sampleDiag4, sampleDiag5, sampleDiag6, sampleDiag7]); }); diff --git a/test/stubs.ts b/test/stubs.ts index b0fdf054..2d928019 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -270,6 +270,12 @@ export class StubSettingsManager implements SettingsManager { return this.getCodeAnalyzerRuleSelectorsReturnValue; } + getSeverityLevelReturnValue: vscode.DiagnosticSeverity = vscode.DiagnosticSeverity.Warning; + + getSeverityLevel(_severity: number): vscode.DiagnosticSeverity { + return this.getSeverityLevelReturnValue; + } + // ================================================================================================================= // ==== Other Settings that we may depend on // ================================================================================================================= diff --git a/test/test-utils.ts b/test/test-utils.ts index cff326ae..3b3d327b 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -1,9 +1,13 @@ import * as vscode from "vscode"; -import {CodeAnalyzerDiagnostic, CodeLocation, Fix, Suggestion} from "../src/lib/diagnostics"; +import {CodeAnalyzerDiagnostic, DiagnosticFactory, CodeLocation, Fix, Suggestion} from "../src/lib/diagnostics"; import { getErrorMessageWithStack } from "../src/lib/utils"; +import * as stubs from "./stubs"; + +// Create a shared DiagnosticFactory with StubSettingsManager for test utilities +const testDiagnosticFactory = new DiagnosticFactory(new stubs.StubSettingsManager()); export function createSampleCodeAnalyzerDiagnostic(uri: vscode.Uri, range: vscode.Range, ruleName: string = 'someRule', engineName: string = 'pmd'): CodeAnalyzerDiagnostic { - return CodeAnalyzerDiagnostic.fromViolation(createSampleViolation({ + return testDiagnosticFactory.fromViolation(createSampleViolation({ file: uri.fsPath, startLine: range.start.line + 1, // Violations are 1 based while ranges are 0 based, so adjusting for this startColumn: range.start.character + 1, diff --git a/test/vscode-stubs.ts b/test/vscode-stubs.ts index fed5587f..8d7c5a60 100644 --- a/test/vscode-stubs.ts +++ b/test/vscode-stubs.ts @@ -42,8 +42,11 @@ export class FakeDiagnosticCollection implements vscode.DiagnosticCollection { return this.diagMap.has(uri.fsPath); } - forEach(_callback: (uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[], collection: vscode.DiagnosticCollection) => unknown, _thisArg?: unknown): void { - throw new Error("Method not implemented."); + forEach(callback: (uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[], collection: vscode.DiagnosticCollection) => unknown, _thisArg?: unknown): void { + for (const [fsPath, diagnostics] of this.diagMap.entries()) { + const uri = vscode.Uri.file(fsPath); + callback(uri, diagnostics, this); + } } [Symbol.iterator](): Iterator<[uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]], unknown, unknown> {