Enhanced blocks logic#491
Conversation
|
You have |
Hi @bobcozzi,
Let me know if I missed anything. |
|
Thank you! I see that now. Are you supporting fixed-format as well? I am asking because this line: contains fixed-format opcode. Thus replicating the Also, have you considered seeing if you can support RPG IV Conditional Compiler Directive? |
Hi @bobcozzi I didn't unserstand what you mean with "Thus replicating the For /IF and so on, I see that highlighting and foldinf is already supported: |
|
Just poking around, so I may be way off base. But don't you need to consider |
I suppose if Developers still have that old System/38 RPG III "END" opcode, then yes, it should potentially be there. If they're still using it. I see no reason to put any extra effort into supporting it, however. |
I'd agree it's not worth a lot of extra effort. I was pointing it out incase it was simply a matter of having I still run into it once and a while. Some may have originated in RPG III, but just as likely to have come from a (lazy?) RPG IV developer. It is valid fixed format, may consider how not supporting it would break the new functionality. |
Help me understand this—I’ll admit I’m by no means an expert on fixed-length RPGs… |
Yes, https://www.ibm.com/docs/en/i/7.6.0?topic=codes-endyy-end-structured-group
|
Okay, I was immediately proven wrong... Adding “END” isn't as straightforward as I'd hoped; it's causing quite a few issues when trying to identify the block that the generic keyword belongs to. In fact, there could be several possibilities, especially when blocks are nested... I quickly tried changing the data structure for storing the block structure, but even in this case there are errors... which I hadn't noticed before, since it was unique I'll try to look into it again in the next few days, but I can't guarantee that it will be feasible in the short term... @bobcozzi @charles-slc I'd like to know if this issue with the generic END is a recurring problem (in which case it would be worth the effort to completely overhaul the logic) or if it's a fairly isolated case |
Not sure I understand the question. Do you mean:
For 1, I can't think of anything similar. For 2, as Bob pointed out it's a relic from RPG III. But it is still valid RPG IV. A quick search of our codebase turned up about 3% of our programs using it at least once. (high score was 193 occurrences) |
|
The "END" opcode in fixed format: "END" was deprecated about 10 years prior to RPG IV being released. It was included in RPG IV to enable simpler CVTRPGSRC conversion; so that conversion tool could simply convert (i.e., reformat) RPGIII into RPG IV without maintaining context during conversion. It is not now, nor has it ever been advocated as a thing--kind of like using an Input field "status" indicator where if blank then set on indicator 71. Sure it is supported in the compiler, but should it be used? So in summary, "END" is used in place of any of the following: So when "END" is detected, it matches the most recently detected conditional statement above it. |
|
ok, I think I’ve managed it—at least it worked in all the test cases I generated. |
|
I'll give it a shot this week. I don't know if it helps or hurts, but note that Think of it as a stack, pushing the condition op-codes (and line nbr), popping when you hit |
There was a problem hiding this comment.
PR #491 Review: Enhanced blocks logic
PR: #491
Author: @buzzia2001
Reviewer: @bobcozzi
Date: June 3, 2026
Overview
I downloaded and ran a few test instances and reviewed the code--making notes along the way. Then I asked claude to summarize my notes into this .md file for me (hence the lengthy review).
This PR adds block highlighting, code folding, and breadcrumb navigation support for RPGLE block structures. The implementation includes support for:
- IF/ENDIF blocks (and all variants: IFEQ, IFNE, etc.)
- Loop blocks (DO, DOW, DOU, FOR, FOR-EACH)
- SELECT/WHEN/OTHER/ENDSL blocks
- MONITOR/ON-ERROR blocks
- Declaration blocks (DCL-PROC, DCL-DS, DCL-PI, etc.)
- Subroutine blocks (BEGSR/ENDSR)
- Legacy fixed-format support including generic
ENDopcode
Total Changes: 4,976 additions / 2,828 deletions across 19 files
Features Tested
Working Features
| Feature | Status | Notes |
|---|---|---|
| Block highlighting | Pass | Highlights matching open/close keywords with middle keywords |
| Code folding | Pass | Folding arrows appear for all supported block types |
| Breadcrumb navigation | Pass | Shows current block context in navigation bar |
Generic END support |
Pass | Properly closes most recent compatible block |
| SQL block exclusion | Pass | FOR/SELECT inside SQL blocks correctly ignored |
| Comment/string exclusion | Pass | Keywords in comments/strings ignored |
| Multiple IF variants | Pass | IFEQ, IFNE, IFGT, etc. all supported |
| Fixed-format support | Pass | Works with both free-format and fixed-format code |
Critical Issue: Mismatched Block Terminators
The algorithm matches POSSIBLE closing keywords, not CORRECT closing keywords.
The Problem
When the cursor is placed on a block opening keyword (e.g., FOR), the matcher searches forward for any closing keyword that CAN close that block type, not the CORRECT closing keyword based on proper nesting rules.
Example Code with Issue
for x = 1 to 10; // Opens FOR block
x = 1 + 2;
if (x > 4); // Opens IF block (nested)
C = X;
endfor; // WRONG! Should be ENDIF
endif; // WRONG! Should be ENDFOR
What happens:
- When cursor is on
FOR, the highlighter matches it with the firstENDFORit finds - The algorithm doesn't validate that the
IFblock should be closed before theFORblock - Result: The matching appears correct visually, but the code has a syntax error
Visual Evidence
The screenshot shows the for loop being matched with endfor even though there's an improperly nested if block in between. The highlighting makes it appear correct when it's actually a syntax error.

How the Algorithm Works
The implementation uses a stack-based nesting algorithm:
- Builds a stack of all open blocks as it scans forward
- When finding a closing keyword, searches the stack from most-recent to oldest
- Matches with the first block that CAN be closed by that keyword
File: extension/client/src/language/bracketMatcher.ts
// Simplified logic from findMatchingClose()
for (let i = openIndex + 1; i < matches.length; i++) {
const word = matches[i].word;
// Check if this word closes a block
for (let j = stack.length - 1; j >= 0; j--) {
if (stack[j].close.includes(word)) { // NOTE: Checks CAN close, not SHOULD close
if (j === 0) {
return i; // Found our match
}
stack.splice(j, 1);
break;
}
}
}Missing Validation
The algorithm doesn't verify that the closing keyword type matches the opening keyword type. It only checks if the closing keyword is in the list of possible closers.
For example:
FORcan be closed by['endfor', 'end']IFcan be closed by['endif', 'end']
When it finds endfor, it sees "this CAN close a FOR block" and matches it, even if there's an unclosed IF block in between that should use endif first.
Test Coverage Analysis
The PR includes good test coverage in tests/suite/blockParser.test.ts and tests/test_end_folding.rpgle:
Tests included:
- Proper nesting with correct pairs
- Generic
ENDopcode closing most recent compatible block - SQL block exclusions (FOR/SELECT in SQL)
- Comments and strings exclusions
- All opcode variants (IFEQ, DOWNE, etc.)
- Triple-level nesting with proper closure
Missing test cases:
- Mismatched block terminators (the critical issue)
- Syntax error detection (using wrong closing keyword)
- Validation of proper nesting order
Test Results Summary
| Category | Result | Details |
|---|---|---|
| Functionality | Partial Pass | All features work but with design flaw |
| Nested blocks (correct) | Pass | Properly matches correctly nested blocks |
| Nested blocks (incorrect) | Fail | Accepts and highlights mismatched pairs |
| Generic END | Pass | Correctly closes most recent compatible block |
| Edge cases | Pass | SQL/comment/string exclusions work |
| Test coverage | Adequate | Good coverage but missing syntax validation tests |
Recommendations
1. Add Syntax Validation (High Priority)
The matcher should validate that closing keywords match the block type:
Option A: Don't highlight mismatched pairs
- If
endforis found but top of stack is anIFblock, don't match
Option B: Highlight with error decoration
- Show mismatched pairs in red/error color to alert the programmer
- Add diagnostic message explaining the mismatch
2. Add Test Cases for Invalid Syntax
Add tests to blockParser.test.ts:
it('should NOT match mismatched block terminators', () => {
const code = `for x = 1 to 10;
if (x > 4);
y = x;
endfor; // Wrong! Should be endif
endif; // Wrong! Should be endfor`;
// Should either not match, or flag as error
});3. Consider Compiler Validation
The ideal solution would validate that:
- Each closing keyword matches its opening keyword type
- The nesting order is correct (inner blocks close before outer blocks)
- Proper error messages guide the programmer to fix the issue
Code Quality Notes
Positive aspects:
- Clean, well-structured code
- Good separation of concerns (blockParser, sqlDetection, bracketMatcher)
- Comprehensive support for RPGLE opcodes
- Good hover info showing block start/end lines
Areas for improvement:
- Need validation layer to ensure syntactic correctness
- Consider adding LSP diagnostics for mismatched blocks
- Could benefit from more inline documentation
Decision
Status: REQUEST CHANGES
While this PR adds valuable functionality and works well for correctly-written code, the lack of validation for mismatched block terminators is a critical issue. The highlighter could give programmers false confidence that their code is correct when it actually has syntax errors.
Suggested approach:
- Merge this PR if the goal is just visual highlighting without validation
- OR add validation logic to detect and warn about mismatched pairs before merging
- File a follow-up issue to add comprehensive syntax validation if merged as-is
Files Changed
New files:
extension/client/src/language/bracketMatcher.ts(531 lines) - Block matching logicextension/server/src/providers/foldingRange.ts(131 lines) - Code folding providerlanguage/utils/blockParser.ts(60 lines) - Block parsing utilitieslanguage/utils/sqlDetection.ts(91 lines) - SQL block detectiontests/suite/blockParser.test.ts(151 lines) - Unit teststests/suite/sqlDetection.test.ts(187 lines) - SQL detection teststests/test_end_folding.rpgle(78 lines) - Integration test file
Modified files:
extension/server/src/providers/documentSymbols.ts- Breadcrumb supportextension/client/src/extension.ts- Register bracket matcherextension/server/src/server.ts- Register folding provider- Various package.json and package-lock.json updates
Additional Testing Performed
Created test file test_mismatched_blocks.rpgle to verify behavior with incorrect nesting - confirmed the issue exists as described above.
Built and tested the extension in VS Code Extension Development Host - all features work as designed but the validation gap exists.
There was a problem hiding this comment.
Hi @bobcozzi ,
When I first designed the change, I hadn't planned on including a validity check, but it actually makes sense to add one... I've uploaded a new version, and I also asked my friend Bob (the fake one) to create a test case for me using some incorrect code




Changes
This release improves block logic. Specifically, support has been added for highlighting different functional blocks, including the start and end of each block. Support has also been added for folding blocks and for displaying information within the navigation bar.
The support works for:
Checklist
console.logs I addedcloses #486
closes #521