Skip to content

Commit 5956530

Browse files
authored
Autoupdate validation underline when template changed (#229)
1 parent 4ef67ef commit 5956530

19 files changed

+238
-31
lines changed

src/handlers/DocumentHandler.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { LspDocuments } from '../protocol/LspDocuments';
1010
import { ServerComponents } from '../server/ServerComponents';
1111
import { LintTrigger } from '../services/cfnLint/CfnLintService';
1212
import { ValidationTrigger } from '../services/guard/GuardService';
13+
import { publishValidationDiagnostics } from '../stacks/actions/StackActionOperations';
1314
import { LoggerFactory } from '../telemetry/LoggerFactory';
1415

1516
const log = LoggerFactory.getLogger('DocumentHandler');
@@ -123,6 +124,19 @@ export function didChangeHandler(
123124
}
124125
});
125126

127+
// Republish validation diagnostics if available
128+
const validationDetails = components.validationManager
129+
.getLastValidationByUri(documentUri)
130+
?.getValidationDetails();
131+
if (validationDetails) {
132+
void publishValidationDiagnostics(
133+
documentUri,
134+
validationDetails,
135+
components.syntaxTreeManager,
136+
components.diagnosticCoordinator,
137+
);
138+
}
139+
126140
components.documentManager.sendDocumentMetadata();
127141
};
128142
}

src/server/CfnInfraCore.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { DocumentMetadata } from '../document/DocumentProtocol';
88
import { LspComponents } from '../protocol/LspComponents';
99
import { DiagnosticCoordinator } from '../services/DiagnosticCoordinator';
1010
import { SettingsManager } from '../settings/SettingsManager';
11+
import { ValidationManager } from '../stacks/actions/ValidationManager';
1112
import { ClientMessage } from '../telemetry/ClientMessage';
1213
import { LoggerFactory } from '../telemetry/LoggerFactory';
1314
import { TelemetryService } from '../telemetry/TelemetryService';
@@ -31,6 +32,7 @@ export class CfnInfraCore implements Configurables, Closeable {
3132
readonly fileContextManager: FileContextManager;
3233

3334
readonly awsCredentials: AwsCredentials;
35+
readonly validationManager: ValidationManager;
3436
readonly diagnosticCoordinator: DiagnosticCoordinator;
3537
readonly cloudformationEndpoint?: string;
3638

@@ -62,9 +64,11 @@ export class CfnInfraCore implements Configurables, Closeable {
6264
initializeParams.initializationOptions?.aws?.encryption?.key,
6365
);
6466

67+
this.validationManager = overrides.validationManager ?? new ValidationManager();
68+
6569
this.diagnosticCoordinator =
6670
overrides.diagnosticCoordinator ??
67-
new DiagnosticCoordinator(lspComponents.diagnostics, this.syntaxTreeManager);
71+
new DiagnosticCoordinator(lspComponents.diagnostics, this.syntaxTreeManager, this.validationManager);
6872
}
6973

7074
configurables(): Configurable[] {

src/server/CfnLspProviders.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ export class CfnLspProviders implements Configurables, Closeable {
6565
this.validationWorkflowService =
6666
overrides.validationWorkflowService ??
6767
(localCfnClientExists()
68-
? ValidationWorkflowV2.create(core, external)
69-
: ValidationWorkflow.create(core, external));
68+
? ValidationWorkflowV2.create(core, external, core.validationManager)
69+
: ValidationWorkflow.create(core, external, core.validationManager));
7070
this.deploymentWorkflowService =
7171
overrides.deploymentWorkflowService ?? DeploymentWorkflow.create(core, external);
7272
this.changeSetDeletionWorkflowService =

src/services/DiagnosticCoordinator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SyntaxTreeManager } from '../context/syntaxtree/SyntaxTreeManager';
33
import { NodeType } from '../context/syntaxtree/utils/NodeType';
44
import { FieldNames } from '../context/syntaxtree/utils/TreeSitterTypes';
55
import { LspDiagnostics } from '../protocol/LspDiagnostics';
6+
import { ValidationManager } from '../stacks/actions/ValidationManager';
67
import { CFN_VALIDATION_SOURCE } from '../stacks/actions/ValidationWorkflow';
78
import { LoggerFactory } from '../telemetry/LoggerFactory';
89

@@ -21,6 +22,7 @@ export class DiagnosticCoordinator {
2122
constructor(
2223
private readonly lspDiagnostics: LspDiagnostics,
2324
private readonly syntaxTreeManager: SyntaxTreeManager,
25+
private readonly validationManager: ValidationManager,
2426
) {}
2527

2628
/**
@@ -98,6 +100,7 @@ export class DiagnosticCoordinator {
98100
const sourceDiagnostics = collection.get(CFN_VALIDATION_SOURCE);
99101
if (!sourceDiagnostics) return;
100102

103+
this.validationManager.getLastValidationByUri(uri)?.removeValidationDetailByDiagnosticId(diagnosticId);
101104
const filteredDiagnostics = sourceDiagnostics.filter((d) => d.data !== diagnosticId);
102105
collection.set(CFN_VALIDATION_SOURCE, filteredDiagnostics);
103106

src/stacks/actions/StackActionOperations.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,15 @@ export async function publishValidationDiagnostics(
387387
}
388388

389389
if (range) {
390+
const diagnosticId = uuidv4();
390391
diagnostics.push({
391392
severity: event.Severity === 'ERROR' ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
392393
range: range,
393394
message: event.Message,
394395
source: CFN_VALIDATION_SOURCE,
395-
data: uuidv4(),
396+
data: diagnosticId,
396397
});
398+
event.diagnosticId = diagnosticId;
397399
}
398400
}
399401

src/stacks/actions/StackActionRequestType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export type ValidationDetail = {
128128
Timestamp: DateTime;
129129
Severity: 'INFO' | 'ERROR';
130130
Message: string;
131+
diagnosticId?: string;
131132
};
132133

133134
export type DeploymentEvent = {

src/stacks/actions/Validation.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Parameter, Capability } from '@aws-sdk/client-cloudformation';
2-
import { StackActionPhase, StackChange } from './StackActionRequestType';
2+
import { StackActionPhase, StackChange, ValidationDetail } from './StackActionRequestType';
33

44
export class Validation {
55
private readonly uri: string;
@@ -11,6 +11,7 @@ export class Validation {
1111
private readonly s3Key?: string;
1212
private phase: StackActionPhase | undefined;
1313
private changes: StackChange[] | undefined;
14+
private validationDetails: ValidationDetail[] | undefined;
1415

1516
constructor(
1617
uri: string,
@@ -77,4 +78,18 @@ export class Validation {
7778
getS3Key(): string | undefined {
7879
return this.s3Key;
7980
}
81+
82+
getValidationDetails(): ValidationDetail[] | undefined {
83+
return this.validationDetails;
84+
}
85+
86+
setValidationDetails(validationDetails: ValidationDetail[]) {
87+
this.validationDetails = validationDetails;
88+
}
89+
90+
removeValidationDetailByDiagnosticId(diagnosticId: string): void {
91+
if (this.validationDetails) {
92+
this.validationDetails = this.validationDetails.filter((vd) => vd.diagnosticId !== diagnosticId);
93+
}
94+
}
8095
}

src/stacks/actions/ValidationManager.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@ import { Validation } from './Validation';
33

44
export class ValidationManager {
55
private readonly validations: Map<string, Validation> = new Map();
6+
private readonly uriToValidation: Map<string, Validation> = new Map();
67

78
add(validation: Validation): void {
89
this.validations.set(validation.getStackName(), validation);
10+
this.uriToValidation.set(validation.getUri(), validation);
911
}
1012

1113
get(stackName: string): Validation | undefined {
1214
return this.validations.get(stackName);
1315
}
1416

17+
getLastValidationByUri(uri: string): Validation | undefined {
18+
return this.uriToValidation.get(uri);
19+
}
20+
1521
setChanges(stackName: string, changes: StackChange[]): void {
1622
const validation = this.validations.get(stackName);
1723
if (validation) {
@@ -20,10 +26,15 @@ export class ValidationManager {
2026
}
2127

2228
remove(stackName: string): boolean {
29+
const validation = this.validations.get(stackName);
30+
if (validation) {
31+
this.uriToValidation.delete(validation.getUri());
32+
}
2333
return this.validations.delete(stackName);
2434
}
2535

2636
clear(): void {
2737
this.validations.clear();
38+
this.uriToValidation.clear();
2839
}
2940
}

src/stacks/actions/ValidationWorkflow.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,6 @@ export class ValidationWorkflow implements StackActionWorkflow<CreateValidationP
198198
}
199199

200200
protected async handleCleanup(params: CreateValidationParams, existingWorkflow: StackActionWorkflowState) {
201-
// Cleanup validation object to prevent memory leaks
202-
this.validationManager.remove(params.stackName);
203-
204201
if (!params.keepChangeSet) {
205202
try {
206203
if (await isStackInReview(params.stackName, this.cfnService)) {
@@ -214,13 +211,13 @@ export class ValidationWorkflow implements StackActionWorkflow<CreateValidationP
214211
}
215212
}
216213

217-
static create(core: CfnInfraCore, external: CfnExternal): ValidationWorkflow {
214+
static create(core: CfnInfraCore, external: CfnExternal, validationManager: ValidationManager): ValidationWorkflow {
218215
return new ValidationWorkflow(
219216
external.cfnService,
220217
core.documentManager,
221218
core.diagnosticCoordinator,
222219
core.syntaxTreeManager,
223-
new ValidationManager(),
220+
validationManager,
224221
external.s3Service,
225222
);
226223
}

src/stacks/actions/ValidationWorkflowV2.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ export class ValidationWorkflowV2 extends ValidationWorkflow {
5151
const result = await waitForChangeSetValidation(this.cfnServiceV2, changeSetName, stackName);
5252

5353
const validation = this.validationManager.get(stackName);
54-
if (validation) {
55-
validation.setPhase(result.phase);
56-
if (result.changes) {
57-
validation.setChanges(result.changes);
58-
}
54+
if (!validation) {
55+
throw new Error(`No validation found for stack: ${stackName}`);
56+
}
57+
58+
validation.setPhase(result.phase);
59+
if (result.changes) {
60+
validation.setChanges(result.changes);
5961
}
6062

6163
existingWorkflow = processWorkflowUpdates(this.workflows, existingWorkflow, {
@@ -82,6 +84,7 @@ export class ValidationWorkflowV2 extends ValidationWorkflow {
8284
validationDetails: validationDetails,
8385
});
8486

87+
validation.setValidationDetails(validationDetails);
8588
await publishValidationDiagnostics(
8689
uri,
8790
validationDetails,
@@ -107,13 +110,17 @@ export class ValidationWorkflowV2 extends ValidationWorkflow {
107110
}
108111
}
109112

110-
static override create(core: CfnInfraCore, external: CfnExternal): ValidationWorkflowV2 {
113+
static override create(
114+
core: CfnInfraCore,
115+
external: CfnExternal,
116+
validationManager: ValidationManager,
117+
): ValidationWorkflowV2 {
111118
return new ValidationWorkflowV2(
112119
new CfnServiceV2(external.awsClient),
113120
core.documentManager,
114121
core.diagnosticCoordinator,
115122
core.syntaxTreeManager,
116-
new ValidationManager(),
123+
validationManager,
117124
core.fileContextManager,
118125
external.featureFlags.getTargeted<string>('EnhancedDryRun'),
119126
core.awsCredentials,

0 commit comments

Comments
 (0)