Accept static as a declaration modifier, not only a function modifier - #65
Merged
Conversation
component { static x = 1; } threw NullPointerException. STATIC was reachable
only as a functionModifier, so prediction committed to functionDeclaration,
found no FUNCTION token, and the visitor dereferenced ctx.FUNCTION() null.
It threw rather than reporting, so one such line took the whole file with it
-- through CFLint that is PARSE_ERROR and no lint results for the file at all.
localAssignmentExpression already carried FINAL for the same reason, which is
why final x = 1 worked and static x = 1 did not. STATIC now sits beside it.
The visitor is taught the shape rather than left to visitChildren:
CFVarDeclExpression gains staticDecl alongside finalDecl, and Decompile emits
static rather than falling through to var. Verified by reading the output, not
by a green parse -- component { static x = "v"; final y = "c"; } decompiles
with both markers intact.
Deliberately not fixed here, all pre-existing and confirmed against master
before this change so none of it is a regression:
var final x = 1; -> { var final; x = 1; }
var static x = 1; -> { var static; x = 1; }
static var x = 1; -> { static public var function x() ;; 1; }
FINAL and STATIC are both valid identifiers since #46, so "var final" parses
as VAR plus an identifier named final. That is a separate fault and the
fixture stays off it.
323 tests, ./gradlew build, differential harness unchanged at 1 with nothing
newly broken, CFLint's 675 against a clean build. The new fixture fails with
cfml.parsing/src/main stashed.
Closes #63
This was referenced Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #63.
component { static myStatic = "v"; }threw:
It threw rather than reporting an error, so one such line took the whole file with it. Through CFLint that surfaces as
PARSE_ERROR: Unable to parseand no lint results at all for the file.Cause
STATICwas reachable only as afunctionModifier.functionDeclarationstartsfunctionModifier* accessType? functionModifier* typeSpec? FUNCTION identifier, so prediction committed on seeingstatic, noFUNCTIONtoken arrived, and the visitor dereferencedctx.FUNCTION()unconditionally.localAssignmentExpressionalready carriedFINALfor exactly this reason — which is whyfinal x = 1worked andstatic x = 1did not, despite the two sitting side by side in bothcomponentModifierandfunctionModifier.STATICnow sits beside it.The visitor is taught the shape
Per
CLAUDE.md, a green parse proves nothing here — this is the rule wherevisitChildrensilently folds an unhandled shape into whatever is nearby. SoCFVarDeclExpressiongainsstaticDeclalongsidefinalDecl, andDecompileemitsstaticrather than falling through tovar.Checked by reading the output rather than trusting the parse:
Both markers survive the round trip.
Pre-existing faults left alone
Confirmed against
masterbefore this change, so none is a regression introduced here:FINALandSTATICare both valid identifiers since #46, sovar finalparses asVARplus an identifier namedfinaland splits into two statements.static varproduces outright garbage. Separate fault in the same rule; the fixture deliberately stays off these so it tests #63 and nothing else. Worth its own issue — say the word and I'll file it.Also unrelated and unchanged: any multi-element component decompiles with an extra
{ }around the body, includingcomponent { x = 1; y = 2; }with nostaticin sight.Verification
./gradlew buildcomponents/static_assignment_63.cfcfails withcfml.parsing/src/mainstashed — the expected tree does not matchNot covered by this PR
#64, the access modifier inside a
staticblock, is the other half of cfmleditor/CFLint#50 and is a separate fault instaticBlock. Coming next.Generated by Claude Code