Skip to content

fix(graphical): dotted refs as constants + resolve globals/struct-members in LSP scope - #950

Merged
thiagoralves merged 2 commits into
developmentfrom
fix/graphical-dotted-refs-and-lsp-globals
Jul 22, 2026
Merged

fix(graphical): dotted refs as constants + resolve globals/struct-members in LSP scope#950
thiagoralves merged 2 commits into
developmentfrom
fix/graphical-dotted-refs-and-lsp-globals

Conversation

@thiagoralves

@thiagoralves thiagoralves commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Byte-identical companion PRs (shared frontend/ surface — verified compare-surfaces.py match: true, 0 diffs). Fixes graphical (LD/FBD) variable-box editing so complex/global references compile and resolve.

Fixes

  1. 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 to createVariable, whose isLegalIdentifier check 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); createVariable and isLegalIdentifier are untouched, so new variable names still reject those characters. Applied to all four box-commit sites (fbd/ladder × autocomplete/block).

  2. Global (VAR_EXTERNAL) variables resolve in the LSP scope query. serializePouScopeForQuery re-emits the POU's external variables as plain VAR in the throwaway query doc, so strucpp resolves the global (and its type's members) without a matching VAR_GLOBAL.

  3. Struct members autocomplete/validate. strucpp returns STRUCT members as CompletionItemKind.Field (5); the scope filter only accepted Variable (6), so struct fields were dropped for both completion and validation. Now Field is accepted alongside Variable.

Verification

  • tsc 0 errors, ESLint clean, Prettier clean.
  • Editor jest + web vitest suites for the touched pure logic pass (the 3 pre-existing boot.test.ts "Worker is not defined" failures on web are unrelated).
  • Manually verified live in the browser (openplc-web dev:local): dotted refs bind with no toast; test_global resolves; test_complex. lists all struct fields in an ANY box and narrows to structureBool in a BOOL box.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Graphical FBD and Ladder editors now correctly accept expressions such as structure members, array elements, typed literals, and reserved words without incorrectly creating variables.
    • Improved autocomplete and type resolution for structure fields and other value references.
    • External variables are now handled correctly when resolving types and members in scope queries.

thiagoralves and others added 2 commits July 21, 2026 21:08
…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>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This 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.

Changes

Graphical expression binding

Layer / File(s) Summary
Direct expression binding
src/frontend/components/_atoms/graphical-editor/fbd/..., src/frontend/components/_atoms/graphical-editor/ladder/...
FBD and Ladder autocomplete and block submission flows bind non-legal identifiers verbatim instead of creating local variables.
Variable and field completion resolution
src/frontend/services/graphical-scope.ts
Scope completion and expression type resolution accept both variable and field symbols, including compatible derived members.

Serialized external scope

Layer / File(s) Summary
Inline external variables in query scopes
src/frontend/utils/PLC/pou-signature-serializer.ts, src/frontend/utils/PLC/__tests__/pou-signature-serializer.test.ts
Temporary ST query scopes emit external interface variables as local VAR declarations, with tests covering the generated declaration and expression reference.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: joaogsp

Poem

I bound the fields the old flow missed,
And let typed paths remain as-is.
External vars now safely stay,
In local query scopes they play.
Hop, hop—clean expressions win! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fixes for dotted refs and LSP scope resolution.
Description check ✅ Passed The description covers the main changes and verification, though the issue/Jira reference and DOD checklist template sections are missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/graphical-dotted-refs-and-lsp-globals

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thiagoralves
thiagoralves merged commit e48914d into development Jul 22, 2026
3 of 4 checks passed
@thiagoralves
thiagoralves deleted the fix/graphical-dotted-refs-and-lsp-globals branch July 22, 2026 10:16

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/frontend/utils/PLC/__tests__/pou-signature-serializer.test.ts (1)

188-214: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the returned cursor position.

This test verifies the serialized text but ignores serializePouScopeForQuery’s position result. Add assertions for both position.line and position.character so 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

📥 Commits

Reviewing files that changed from the base of the PR and between 634ae95 and 169e7c8.

📒 Files selected for processing (7)
  • src/frontend/components/_atoms/graphical-editor/fbd/autocomplete/index.tsx
  • src/frontend/components/_atoms/graphical-editor/fbd/block.tsx
  • src/frontend/components/_atoms/graphical-editor/ladder/autocomplete/index.tsx
  • src/frontend/components/_atoms/graphical-editor/ladder/block.tsx
  • src/frontend/services/graphical-scope.ts
  • src/frontend/utils/PLC/__tests__/pou-signature-serializer.test.ts
  • src/frontend/utils/PLC/pou-signature-serializer.ts

Comment on lines +138 to +140
if (!isLegalIdentifier(variableName)[0]) {
submitVariableToBlock({ name: variableName } as PLCVariable)
return

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.

🗄️ 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 the as PLCVariable cast 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.

Comment on lines +508 to +514
// 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
}

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.

🎯 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 the createIfNotFound condition.
  • 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.

Comment on lines +190 to +202
// 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

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.

🗄️ 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.

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.

1 participant