Skip to content

feat: surface validation violations and CFN resources in LSP#1592

Open
megha-narayanan wants to merge 9 commits into
aws:feat/cdk-lspfrom
megha-narayanan:feat/explorer-assembly-reader
Open

feat: surface validation violations and CFN resources in LSP#1592
megha-narayanan wants to merge 9 commits into
aws:feat/cdk-lspfrom
megha-narayanan:feat/explorer-assembly-reader

Conversation

@megha-narayanan
Copy link
Copy Markdown

  • Adds an assembly reader that joins cdk.out/'s tree.json with each stack's manifest metadata into a ConstructNode tree carrying logicalId, CFN type, and source location.
  • LSP publishes CDK validation violations as Diagnostics anchored to the construct's TypeScript source line, with rule-level severity.
  • LSP serves CodeLens entries above each construct creation site summarising the CFN resources it produces.
  • Source resolution covers .ts and .js (with sibling .js.map); non-TS apps degrade gracefully (no crash, no source-linked features).

Fixes #

Checklist

  • This change contains a major version upgrade for a dependency and I confirm all breaking changes are addressed
    • Release notes for the new version:

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@github-actions github-actions Bot added the p2 label Jun 3, 2026
@aws-cdk-automation aws-cdk-automation requested a review from a team June 3, 2026 19:35
@megha-narayanan megha-narayanan changed the title feat(cdk-explorer): surface validation violations and CFN resources in LSP feat: surface validation violations and CFN resources in LSP Jun 3, 2026
@megha-narayanan megha-narayanan marked this pull request as ready for review June 3, 2026 19:58
Copy link
Copy Markdown
Contributor

@ShadowCat567 ShadowCat567 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial comments, will come back and do more of a review of the logic for the codelens + diagnostics.
In general for LSP features, it would be helpful to have some screenshots of what you are implementing looks like in the IDE

? buildTree(rawTree, stackIndex, onWarn)
: [];

let violations: PolicyValidationReportJson | undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check whether online validations appear in this report?
Since I think it might be possible that a customer runs cdk validate themselves and online validations appear in the report with offline validations.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I think (in the actual version of validate) they do. the schema doesn't distinguish between online and offline violations, so we can't really filter them out. My thought would be that this is a good thing though, right? If we have violations detected, we should surface them the same way. ofc, if tehres no source location thats a separate issue and they get filtered out anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure that online validations are not part of this report.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depends. if running cdk synth, online validation is not a part of the report. if running cdk validate --online, then it will be a part of the report.

the plugin name will be CloudFormation -- see https://github.com/aws/aws-cdk-cli/pull/1539/changes#diff-efa522e30d6c1c6a99aa8a5da647de2173348e9dd3c571bc2f37ec0916adbc33R758-R762 -- so we can filter those out if needed. or just run validate --offline

Comment thread packages/@aws-cdk/cdk-explorer/lib/core/assembly-reader.ts Outdated
Comment thread packages/@aws-cdk/cdk-explorer/lib/core/source-resolver.ts Outdated
Comment thread packages/@aws-cdk/cdk-explorer/lib/lsp/diagnostics.ts Outdated
Comment thread packages/@aws-cdk/cdk-explorer/test/core/assembly-reader.test.ts Outdated
…n LSP

Joins cdk.out/'s tree.json with manifest metadata into a ConstructNode
tree. LSP publishes validation violations as line-anchored Diagnostics
and CFN resources as CodeLens above each construct's creation site.
Resolves .ts and .js (via sibling .js.map); non-TS apps degrade
gracefully. Tests use programmatic fixture builders covering flat,
Stage-based, and NestedStack assemblies.
@megha-narayanan megha-narayanan force-pushed the feat/explorer-assembly-reader branch from 41b998a to 2f91341 Compare June 4, 2026 18:27
@megha-narayanan megha-narayanan changed the base branch from feat/cdk-lsp-explorer to feat/cdk-lsp June 4, 2026 18:27
- assembly-reader: convert RawTreeNode comment to JSDoc so it's clearly
  attached to the interface (not an orphan above it).
- diagnostics: trim the Number.MAX_VALUE end-of-line comment to one line;
  the LSP-spec details aren't necessary at this point.
- source-resolver: drop the test-only `_clearTraceMapCache` export. The
  source-map cache is now an explicit `SourceMapCache` parameter created
  per `readAssembly` call, with `createSourceMapCache()` as the factory.
  Tests construct a fresh cache in `beforeEach`. Production has one cache
  per assembly read, so repeated `.js.map` parses still amortise.
- assembly-reader.test: replace the `if (result.status !== 'success')
  throw new Error(...)` narrowing trick (11 sites) with an
  `expectSuccess(result)` helper that asserts via `expect(...).toBe('success')`
  and returns the typed `data`. No more thrown errors in test bodies.
@megha-narayanan
Copy link
Copy Markdown
Author

Ugly but functional surfacing of resources with source resolution
Screenshot 2026-06-04 at 4 17 04 PM

@megha-narayanan
Copy link
Copy Markdown
Author

And violations: Screenshot 2026-06-04 at 4 24 48 PM

Copy link
Copy Markdown
Contributor

@ShadowCat567 ShadowCat567 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The biggest question I have is related to the fixtures, I don't think it's a pattern that is used elsewhere in this repo and I would like a bit more explanation in the readme about what their deal is

* metadata to produce a ConstructNode tree where every CFN resource carries
* its logicalId, CFN type, and source location.
*
* Supports:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there anything you know of that we don't support with this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I know of, mostly added this comment for clarity of what I had tested and designed for.

try {
violations = loadViolations(assemblyDir);
} catch (err) {
violationsError = err instanceof Error ? err.message : String(err);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors are usually Objects, JSON.stringify() would probably give you nicer looking output than String() - similar comment with the other places you are using String() instead of JSON.stringify()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just assume that err is always an Error, and reject any code that throws anything else during code review.

Comment thread packages/@aws-cdk/cdk-explorer/lib/core/assembly-reader.ts Outdated
cache: SourceMapCache,
onWarn?: WarnFn,
): SourceLocation | undefined {
const frames = pickCreationFrames(metadataEntries);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are frames in this context?

Copy link
Copy Markdown
Author

@megha-narayanan megha-narayanan Jun 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lines of the creation stack trace. made this better documented

range,
severity: severityFor(violation.severity),
code: violation.ruleName,
source: 'CDK Synth',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do all of our violations have a source of CDK synth could this instead be used to surface which plugin it came from?


Most tests build their `cdk.out/` programmatically via `builders.ts` —
keeps test intent in TypeScript and avoids drift when aws-cdk-lib or the
cloud-assembly schema upgrades. `builders.test.ts` sanity-checks each
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we stay on a certain version of aws-cdk-lib/cloud-assembly schema or does this update with them?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I need more context about what these fixtures are and why we need them before I can properly review them

@ShadowCat567
Copy link
Copy Markdown
Contributor

And violations: Screenshot 2026-06-04 at 4 24 48 PM

:O Do the squiggles change color based on severity?

import { createSourceMapCache, resolveSourceLocation, type SourceLocation, type SourceMapCache, type WarnFn } from './source-resolver';

/** A construct from tree.json plus the CFN metadata the LSP surfaces. */
export interface ConstructNode {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually we'll also need to store information about properties and their assignment locations. So, just think about how you're going to model it then, and if the current model is extensible for that. For example, you could add a new attribute to this interface called something like properties. Or you could decide to have a tree in which the nodes may be constructs or properties (and properties are children of constructs).

Copy link
Copy Markdown
Author

@megha-narayanan megha-narayanan Jun 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, i think the model is extensible for this. I'd add an optional properties?: PropertyNode[] to the node (PropertyNode { name; sourceLocation }) rather than making properties tree children, so the construct index/iteration and CodeLens stay construct-only. The data's already available PROPERTY_ASSIGNMENT metadata ({ propertyName, stackTrace }) flows through the same buildConstructTree decorate callback that resolves creation-site locations, so resolving an assignment location reuses the exact source-map machinery. Planning to land the field + populate it as a follow-up to keep this PR scoped.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this code live in @aws-cdk/cloud-assembly-api ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Moved the tree builder into @aws-cdk/cloud-assembly-api as buildConstructTree(assembly, decorate) it owns the tree.json parse + stack-metadata join and produces a generic ConstructTreeNode. The explorer passes a decorate callback that adds sourceLocation; that stays here since it needs trace-mapping (dependency). I also moved ConstructIndex there so it's reusable, which is why it's generic over the node type (ConstructIndex); the explorer uses ConstructIndex to keep its sourceLocation. If you'd rather the index stay specific to teh explorer, easy to change. lmk if this looks good, it adds a small public API surface.

? buildTree(rawTree, stackIndex, onWarn)
: [];

let violations: PolicyValidationReportJson | undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure that online validations are not part of this report.

try {
violations = loadViolations(assemblyDir);
} catch (err) {
violationsError = err instanceof Error ? err.message : String(err);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just assume that err is always an Error, and reject any code that throws anything else during code review.

* nested-stack constructs into the PARENT stack's artifact metadata,
* keyed by full construct path.
*/
export function readAssembly(assemblyDir: string, onWarn?: WarnFn): AssemblyReadResult {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return warnings on the successful result?

Comment on lines +60 to +63
/**
* Mirrors toolkit-lib's findCreationStackTrace preference: prefer the
* aws:cdk:logicalId.trace, fall back to aws:cdk:creationStack.data.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't just mirror, it should literally be the same code path.

Copy link
Copy Markdown
Author

@megha-narayanan megha-narayanan Jun 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're totally right. I was trying to evoid messing with APIs, but I need to do it at some point. I exported findCreationStackTrace from toolkit-lib's source-tracing so this can be the literal same code path; it takes (stack, constructPath). While there I also exported findMutationStackTraces for the property-assignment locations otavio raised. Source-map decode stays in the explorer. ok to add these two to toolkit-lib's public API?

Comment on lines +26 to +29
* Convert a validation report into LSP diagnostics keyed by file URI.
* Violations whose construct path is unknown, has no source location, or
* points outside TypeScript are dropped (with a reason) rather than thrown.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we were going to represent warnings without a source location in some default location. Is this an initial iteration, or is this the final plan?

const loc = node.sourceLocation;
if (!loc) return { error: 'node has no sourceLocation (non-TS or framework-only trace)' };

if (!loc.file.endsWith('.ts') && !loc.file.endsWith('.tsx')) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have "limiting to TypeScript for now" be a type of processing we do consciously do at the start, rather than something in the middle of different kinds of error recovery code.

if (!constructPath) return { error: 'empty constructPath' };

const node = nodesByPath.get(constructPath);
if (!node) return { error: 'no tree node for constructPath' };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these errors for?

If we show them to users, I would like them to look like messages that are appropriate to show to a user (could not find this node in tree.json).

If we're not going to show this to a user, then they could be error codes or we don't have this field at all.

return { error: `non-TypeScript source: ${loc.file}` };
}
if (loc.line < 1 || loc.column < 1) {
return { error: `invalid line/column: ${loc.line}:${loc.column}` };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we could still relate it to a file? Can't we stick it at line 1 of this file then?

readAssembly: options.readAssembly,
logger: connection.console,
onPublishDiagnostics: (uri, diagnostics) => {
void connection.sendDiagnostics({ uri, diagnostics });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the void here, and why is that okay?

Is that a silenced "you shall await this" linter error?

Addresses PR aws#1592 review feedback:

- Move the tree.json + stack-metadata join (buildConstructTree) and ConstructIndex into @aws-cdk/cloud-assembly-api as generic, reusable cloud-assembly tooling. cdk-explorer passes a decorate callback that adds sourceLocation (stays here; needs trace-mapping).

- Introduce ConstructIndex (a keyed Map over the tree), removing the duplicated walk() in tree-utils and codelens.

- codeLensesForFile now takes a ConstructIndex and uses filter/map + a groupBy combinator.
…SOURCE_TYPE_ATTRIBUTE

Make two assembly-format constants canonical in cloud-assembly-schema instead of hardcoded in consumers:

- VALIDATION_REPORT_FILE ('validation-report.json') in manifest.ts, next to loadValidationReport.

- CFN_RESOURCE_TYPE_ATTRIBUTE ('aws:cdk:cloudformation:type') tree-node attribute in metadata-schema.ts.

cloud-assembly-api (construct-tree) and cdk-explorer (assembly-reader, test fixture) now consume them from schema.
…nsts)

- source-resolver: rename ambiguous 'frames' to 'creationStackFrames' (ShadowCat567).

- construct-tree: move CDK_INTERNAL_IDS to the top beneath imports (ShadowCat567).
The builder moved here from cdk-explorer but only ConstructIndex was tested, dropping function coverage below threshold. Add buildConstructTree tests (tree+metadata join, internal-node filtering, decorate callback, empty/no-tree case).
… frame selection

Export findCreationStackTrace + findMutationStackTraces from toolkit-lib's source-tracing, and have cdk-explorer call findCreationStackTrace instead of its duplicate pickCreationFrames (now deleted).

buildConstructTree's decorate callback now provides (fields, stack, constructPath) so the explorer can call the toolkit-lib tracer; source-resolver keeps only the frame->location source-map decode (resolveFramesToLocation).
…tree artifact

buildConstructTree now resolves the tree file via assembly.tree().file instead of assuming 'tree.json', so it honors whatever filename the manifest declares (and returns [] when there is no tree artifact).
The functions threshold was temporarily lowered to 50 in the first (skeleton) PR. The package has grown and now sits at 81% function coverage, so restore it to 80 to match statements/branches/lines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants