Skip to content

fix(hover): bare-word lookup no longer matches unrelated CLASS/INTERF… - #391

Open
geircodes wants to merge 1 commit into
msarson:version-1.0.1from
geircodes:fix/bare-word-hover-class-member-guard
Open

fix(hover): bare-word lookup no longer matches unrelated CLASS/INTERF…#391
geircodes wants to merge 1 commit into
msarson:version-1.0.1from
geircodes:fix/bare-word-hover-class-member-guard

Conversation

@geircodes

Copy link
Copy Markdown

fix(hover): bare-word lookup no longer matches unrelated CLASS/INTERFACE members

What happened

Hovering a bare, undeclared word can resolve to an unrelated CLASS's or INTERFACE's method/property
elsewhere in the include chain — labeled correctly as "🔷 Class property of X", but shown as if it
were a legitimate match, when the correct behavior is to show nothing.

MyClass CLASS, TYPE
Test     PROCEDURE()
    END 

MyProc PROCEDURE() 
 CODE
   IF test = TRUE

test is not declared in the procedure, but still the hover found a match and told that test was a method in MyClass.

Repro (see the new test MemberLocatorService.BareWordClassMemberGuard.test.ts): a file includes
declarations.inc, which declares SomeClass CLASS,TYPE ... DoWork PROCEDURE(*?) ... END. Hovering
the bare word DoWork anywhere else in that file — where it is not declared and has no local scope
— resolves to SomeClass.DoWork instead of correctly finding nothing. Same shape for an INTERFACE
member.

Root cause

MemberLocatorService.isVariableLookupCandidate(t) — the gate for the bare-word MEMBER-parent and
INCLUDE-chain walk used by hover/F12/Ctrl+F12 — accepted any TokenType.Structure, any
procedure/function-shaped token, or any column-0 token, with no check for CLASS/INTERFACE
containment
. A class method prototype (DoWork PROCEDURE(...) inside CLASS...END) tokenizes
identically to a real top-level global procedure, so it passed the gate unfiltered.

Class members are never reachable as a bare unqualified word in Clarion — only via SELF.X /
instance.X — so this was always a wrong candidate; the check just never existed.
VariableHoverResolver.buildGlobalVariableHover already computes isInClassBlock for the exact
same token, but only to relabel the hover text ("🔷 Class property of X"), never to reject the
match.

This surfaces through either bare-word walk branch — the current file's own INCLUDE chain, or the
MEMBER-parent file's INCLUDE chain (via the one-hop INCLUDE('member.clw') shim convention handled
by resolveMemberHeaderToken(), a separate in-flight fix on fix/member-header-via-include) — both
share the same isVariableLookupCandidate gate, so this fix applies to both.

Fix

isVariableLookupCandidate now takes the token's owning TextDocument and rejects the match when
tokenCache.getStructure(doc).getStructureContextAt(t.line) reports inClass or inInterface. All
5 call sites in this base updated to pass the right doc (data.doc / parentData.doc / the ambient
document param, matched per call site).

Structure-opening lines themselves are NOT considered "inside" the structure per
DocumentStructure's existing containment semantics (matches isInClassBlock's existing behavior),
so a global CLASS/QUEUE/GROUP type name lookup is unaffected — only members nested between
CLASS...END / INTERFACE...END are excluded.

Testing

New test file MemberLocatorService.BareWordClassMemberGuard.test.ts: pins that a CLASS method name
and an INTERFACE method name both resolve to nothing via findVariableTokenInParentChain, and that a
genuine top-level global declared in the very same include chain still resolves (guards against the
fix overreaching).

npx tsc -b: clean. npm run test:server (isolated worktree off origin/master): 2126 passing,
3 pending (pre-existing), 0 failing.

Scope

One file changed + one new test: server/src/services/MemberLocatorService.ts,
server/src/test/MemberLocatorService.BareWordClassMemberGuard.test.ts (new). Plain scope-resolution
bug in the shared bare-word lookup, unrelated to any in-flight feature work, so it goes out as its
own PR.

…ACE members

isVariableLookupCandidate() (used by hover/F12/Ctrl+F12's bare-word
MEMBER-parent and INCLUDE-chain walk) accepted any column-0 or
procedure-shaped token with no check for CLASS/INTERFACE containment.
A class method/property prototype is tokenized identically to a real
top-level global, so an undeclared word sharing a name with some
unrelated class's method resolved to that method instead of correctly
finding nothing - since class members are only reachable via qualified
access (SELF.X / instance.X), never as a bare word.

Guard the lookup with the token's own structure context (inClass /
inInterface) so only genuine top-level globals satisfy the bare-word
search. Structure-opening lines themselves are unaffected, so global
class/queue/group type-name lookups keep working.
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