fix(graphical): dotted refs as constants + resolve globals/struct-members in LSP scope - #950
Conversation
…n LSP scope query Byte-identical companion to the openplc-editor PR (shared frontend surface). 1. Dotted / non-identifier box entries (member access like some_global_complex.structureVar, typed literals like T#500ms, reserved words) are bound to the block verbatim as a constant/reference instead of being routed to createVariable, whose isLegalIdentifier check rejects them with an Illegal Variable Name toast and discards the entry. New variable NAMES still reject those characters. Applied to all four box-commit sites (fbd/ladder autocomplete + block). 2. serializePouScopeForQuery re-emits the POU's VAR_EXTERNAL variables as plain VAR in the throwaway scope-query doc, so strucpp resolves globals and their struct/array members without a matching VAR_GLOBAL — restoring LSP autocomplete + validation for global variables in LD and FBD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…validate Struct-typed variables (local or via a global VAR_EXTERNAL) never offered their members in the LD/FBD variable boxes, e.g. `test_complex.structureBool` for a BOOL box. Root cause: strucpp returns STRUCT members with CompletionItemKind.Field (5), while graphical-scope only accepted Variable (6) — the kind FUNCTION_BLOCK instance members come back as. So struct fields were dropped by the kind filter before the type filter ran, in both the completion and validation paths. Accept Field (5) alongside Variable (6) via isValueCompletionKind at all three call sites (completion, member-drill, type resolution). Verified live in the browser: `<struct>.` now lists all fields in an ANY box and narrows to the type-compatible field (structureBool) in a BOOL box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WalkthroughThis change updates FBD and Ladder variable submission to bind non-identifier expressions directly, expands graphical scope resolution to include field symbols, and converts external variables to local declarations when serializing temporary ST query scopes. ChangesGraphical expression binding
Serialized external scope
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/frontend/utils/PLC/__tests__/pou-signature-serializer.test.ts (1)
188-214: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the returned cursor position.
This test verifies the serialized text but ignores
serializePouScopeForQuery’spositionresult. Add assertions for bothposition.lineandposition.characterso cursor placement regressions cannot pass unnoticed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/utils/PLC/__tests__/pou-signature-serializer.test.ts` around lines 188 - 214, Update the test around serializePouScopeForQuery to assert the returned position as well as text: destructure position from the result and add expectations for both position.line and position.character using the cursor location produced by the serialized query. Keep the existing serialization assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx`:
- Around line 138-140: Replace the partial PLCVariable cast in the FBD
autocomplete path at
src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx:138-140
with the supported unresolved-expression binding representation. Apply the same
representation in the ladder autocomplete path at
src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx:195-202,
and update connected-variable consumers to handle that representation
consistently.
In `@src/frontend/components/_atoms/graphical-editor/fbd/block.tsx`:
- Around line 508-514: The non-identifier branch is currently unreachable on
ordinary textarea commits because it is gated by createIfNotFound. In
src/frontend/components/_atoms/graphical-editor/fbd/block.tsx#L508-L514 and
src/frontend/components/_atoms/graphical-editor/ladder/block.tsx#L606-L612,
restructure each block-commit handler so isLegalIdentifier(variableNameToSubmit)
is checked before the createIfNotFound condition, updating the node variable and
returning for non-identifiers while preserving existing creation behavior for
legal identifiers.
In
`@src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx`:
- Around line 190-202: Update the invalid-identifier branch to use
submitVariableToBlock instead of updating only the node, so connected blocks
refresh their connectedVariables. Add or reuse a supported unresolved-expression
binding type for values such as member and array references, then pass that
binding through submitVariableToBlock while preserving the existing verbatim
expression name.
---
Nitpick comments:
In `@src/frontend/utils/PLC/__tests__/pou-signature-serializer.test.ts`:
- Around line 188-214: Update the test around serializePouScopeForQuery to
assert the returned position as well as text: destructure position from the
result and add expectations for both position.line and position.character using
the cursor location produced by the serialized query. Keep the existing
serialization assertions unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 7feb575c-3391-4e7b-bd94-dd4f0109e7a2
📒 Files selected for processing (7)
src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsxsrc/frontend/components/_atoms/graphical-editor/fbd/block.tsxsrc/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsxsrc/frontend/components/_atoms/graphical-editor/ladder/block.tsxsrc/frontend/services/graphical-scope.tssrc/frontend/utils/PLC/__tests__/pou-signature-serializer.test.tssrc/frontend/utils/PLC/pou-signature-serializer.ts
| if (!isLegalIdentifier(variableName)[0]) { | ||
| submitVariableToBlock({ name: variableName } as PLCVariable) | ||
| return |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use an explicit unresolved-expression binding type in both autocomplete paths.
Both paths persist only a name where the shared PLCVariable contract requires additional fields.
src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx#L138-L140: replace theas PLCVariablecast with a supported expression-binding representation.src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx#L195-L202: use the same representation and ensure connected-variable consumers handle it.
📍 Affects 2 files
src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx#L138-L140(this comment)src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx#L195-L202
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx`
around lines 138 - 140, Replace the partial PLCVariable cast in the FBD
autocomplete path at
src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx:138-140
with the supported unresolved-expression binding representation. Apply the same
representation in the ladder autocomplete path at
src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx:195-202,
and update connected-variable consumers to handle that representation
consistently.
| // An entry that can't be a new variable NAME — a member/array reference, | ||
| // a typed literal (`T#500ms`), a reserved word — is bound to the block | ||
| // verbatim as a constant/reference instead of erroring. | ||
| if (!isLegalIdentifier(variableNameToSubmit)[0]) { | ||
| updateNodeVariable({ name: variableNameToSubmit }) | ||
| return | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Apply non-identifier handling to every block-commit path.
The new branch is unreachable for the ordinary textarea commit because both handlers pass createIfNotFound = false.
src/frontend/components/_atoms/graphical-editor/fbd/block.tsx#L508-L514: handle non-identifiers before thecreateIfNotFoundcondition.src/frontend/components/_atoms/graphical-editor/ladder/block.tsx#L606-L612: apply the same restructuring.
📍 Affects 2 files
src/frontend/components/_atoms/graphical-editor/fbd/block.tsx#L508-L514(this comment)src/frontend/components/_atoms/graphical-editor/ladder/block.tsx#L606-L612
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/components/_atoms/graphical-editor/fbd/block.tsx` around lines
508 - 514, The non-identifier branch is currently unreachable on ordinary
textarea commits because it is gated by createIfNotFound. In
src/frontend/components/_atoms/graphical-editor/fbd/block.tsx#L508-L514 and
src/frontend/components/_atoms/graphical-editor/ladder/block.tsx#L606-L612,
restructure each block-commit handler so isLegalIdentifier(variableNameToSubmit)
is checked before the createIfNotFound condition, updating the node variable and
returning for non-identifiers while preserving existing creation behavior for
legal identifiers.
| // If the entry can't be a new variable NAME — a member/array reference | ||
| // (`some_struct.field`, `arr[3]`), a typed literal (`T#500ms`), a reserved | ||
| // word, etc. — don't try to create a variable. Bind it to the node | ||
| // verbatim as a constant/reference; strucpp validates the expression. New | ||
| // local-variable creation is only for plain, legal identifiers. | ||
| if (!isLegalIdentifier(variableName)[0]) { | ||
| updateNode({ | ||
| editorName: pouName, | ||
| rungId: rung.id, | ||
| nodeId: node.id, | ||
| node: { ...node, data: { ...node.data, variable: { name: variableName } } }, | ||
| }) | ||
| return |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use submitVariableToBlock for expression bindings.
This branch updates only the variable node, while submitVariableToBlock also refreshes the connected block’s connectedVariables. If the node is connected, entering my_struct.field leaves the block holding the previous binding. Route this path through the shared helper after introducing a supported unresolved-expression binding type.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx`
around lines 190 - 202, Update the invalid-identifier branch to use
submitVariableToBlock instead of updating only the node, so connected blocks
refresh their connectedVariables. Add or reuse a supported unresolved-expression
binding type for values such as member and array references, then pass that
binding through submitVariableToBlock while preserving the existing verbatim
expression name.
Byte-identical companion PRs (shared
frontend/surface — verifiedcompare-surfaces.pymatch: true, 0 diffs). Fixes graphical (LD/FBD) variable-box editing so complex/global references compile and resolve.Fixes
Dotted box entries bind as constants (not rejected). A member/array reference (
some_global_complex.structureVar), typed literal (T#500ms), or reserved word entered in a variable box was routed tocreateVariable, whoseisLegalIdentifiercheck rejected the dot with an "Illegal Variable Name" toast and discarded the entry (while the box still displayed it → the compiler saw the old value). Now, when the entry can't be a legal new variable name, it's bound to the block verbatim (constant/reference);createVariableandisLegalIdentifierare untouched, so new variable names still reject those characters. Applied to all four box-commit sites (fbd/ladder × autocomplete/block).Global (
VAR_EXTERNAL) variables resolve in the LSP scope query.serializePouScopeForQueryre-emits the POU's external variables as plainVARin the throwaway query doc, so strucpp resolves the global (and its type's members) without a matchingVAR_GLOBAL.Struct members autocomplete/validate. strucpp returns STRUCT members as
CompletionItemKind.Field(5); the scope filter only acceptedVariable(6), so struct fields were dropped for both completion and validation. NowFieldis accepted alongsideVariable.Verification
tsc0 errors, ESLint clean, Prettier clean.boot.test.ts"Worker is not defined" failures on web are unrelated).dev:local): dotted refs bind with no toast;test_globalresolves;test_complex.lists all struct fields in an ANY box and narrows tostructureBoolin a BOOL box.🤖 Generated with Claude Code
Summary by CodeRabbit