Skip to content

Conversation

@satyakigh
Copy link
Collaborator

@satyakigh satyakigh commented Dec 15, 2025

Description

This PR improves how the CloudFormation Language Server parses and handles intrinsic functions (like !If, !Sub, !Ref, Fn::GetAtt, etc.) in both YAML and JSON
templates. It also adds a JSON fallback parser for malformed/incomplete documents.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Key Changes

1. SyntaxTree.ts - Core parsing improvements

Intrinsic function path handling:

  • Refactored YAML tag handling (!If, !Sub, !Ref, etc.) to run independently rather than in an else-if chain
  • This ensures both the intrinsic function name AND the parent property are captured in the path
  • Added support for flow_node in addition to block_node for YAML intrinsics
  • Added check to skip when parent is ERROR node (lets fallback handle malformed trees)

New JSON fallback parser:

  • Added pathAndEntityJsonFallback() method (~100 lines) for malformed JSON documents
  • Uses text-based stack parsing to infer context when tree-sitter produces ERROR nodes
  • Tracks JSON structure ({, }, [, ], :, ,) to build path context
  • Enables completions in incomplete JSON templates (e.g., typing a property name)

Minor fix:

  • Added handling for cursor after YAML TAG nodes (e.g., !Sub or incomplete !E)

2. LogicalIdReferenceFinder.ts - Simplified reference extraction

  • Simplified early-exit checks using text.includes() instead of indexOf()
  • Removed separate JsonSub/YamlSub regex patterns - now uses unified SubVariables pattern
  • Added !If and Fn::If: pattern matching for YAML
  • Unified ${...} variable extraction for both JSON and YAML into single SubVariables regex
  • Improved SubVariables regex to capture only the resource name from ${Resource.Attribute} syntax
  • Added 'AWS' to CommonProperties exclusion set (filters out AWS::Region, etc.)

@satyakigh satyakigh marked this pull request as ready for review December 15, 2025 17:39
@satyakigh satyakigh requested a review from a team as a code owner December 15, 2025 17:39
kddejong
kddejong previously approved these changes Dec 15, 2025
@satyakigh satyakigh force-pushed the tree-improvements branch 3 times, most recently from 0432828 to 3785bce Compare December 15, 2025 21:12
@satyakigh satyakigh changed the title Improve intrinsic function parsing Improve intrinsic functions and add json fallback parsing Dec 15, 2025
extractMatches(text, JsonRef, logicalIds);
}
if (getAttIndex !== -1) {
if (text.includes('"Fn::GetAtt"')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would using IntrinsicFunction enum make these more maintainable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A lof of the intrinsic functions are being used here, it was easier to read and follow without using the enums here

current.children?.length > 0 &&
current.children.some((child) => NodeType.isNodeType(child, YamlNodeTypes.TAG))
) {
const tagNode = current.children.find((child) => NodeType.isNodeType(child, YamlNodeTypes.TAG));
Copy link
Contributor

Choose a reason for hiding this comment

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

We are iterating and checking node type twice, once in the condition to enter this scope (line 510) and again to assign tagNode.
Can we search once and continue if not found?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Both of these exit early since it just looks for the first child and if we compute this before the if, then the search always runs even if the other conditions might be false. This is only looking for first element so it should be fast enough

Copy link
Contributor

Choose a reason for hiding this comment

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

Was not suggesting to compute before if but rather remove line 510 from if condition, and exit if no tagNode is found after line 512.

const parent = current.parent;
if (!parent) break;

// Handle YAML tags like !If, !Sub, !Ref, etc.
Copy link
Contributor

Choose a reason for hiding this comment

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

This 100 line method is getting longer. It would be good to refactor the new Tag change into a method. We can take care of the rest later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is just moving the existing logic from below to be up top, only the comments are extra

@satyakigh satyakigh merged commit 0982708 into main Dec 16, 2025
16 checks passed
@satyakigh satyakigh deleted the tree-improvements branch December 16, 2025 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants