Skip to content

feat(PC0029): trace global variables and cover all record receiver forms - #544

Merged
Arthurvdv merged 10 commits into
mainfrom
feat/pc0029-global-variable-tracing
Sep 13, 2026
Merged

Arthurvdv merged 10 commits into
mainfrom
feat/pc0029-global-variable-tracing

Conversation

@Arthurvdv

@Arthurvdv Arthurvdv commented Sep 12, 2026

Copy link
Copy Markdown
Member

Why

Issue #515 holds the eight deferred PC0029 ideas. Global variable tracing was approved; the remaining items are closed as won't-do (table below). While testing, three false negatives surfaced: a bare-self key-field write inside a table trigger, a namespaced codeunit with a fully qualified Record variable, and Rec."Primary Key" in the OnRun trigger of a codeunit with TableNo. Fixtures showed that the receiver forms were already handled and that the real gap was the implicit primary key: a table declared without a keys section has a synthesized primary key that ITableTypeSymbol.Keys never lists.

What

  • Global variable tracing: when CreateGuid() is assigned to an object-scope Guid variable, the analyzer walks the sibling method and trigger bodies of the same object (text-prefiltered on the variable name) to see whether the value reaches a key field.
  • Implicit primary key: key membership now reads ITableTypeSymbol.PrimaryKey before the declared Keys, so tables without a keys section are covered, including tables from referenced apps.
  • Parenthesized unwrap: the private one-level UnwrapConversion is replaced by Common's UnwrapConversions(), which also peels IParenthesizedExpression.
  • Version gate parity: NoDiagnostic and HasFix call RequireMinimumVersion("16.0") like HasDiagnostic; the AlreadySequentialGuid fixture is back.
  • Schema wording: UseSequentialGuidScope now says "field that is part of a table key" instead of "primary key fields".

Receiver-form coverage

26 new fixtures pin every context where Rec or self exists: bare self in table triggers, all self forms in a tableextension, page and pageextension Rec (explicit and bare), request page Rec, report and xmlport Rec via their request page, report dataitem (named and bare), TableNo codeunit OnRun (Rec, bare, Validate), a namespaced file with a fully qualified record type, tables without a keys section, and two NoDiagnostic guards (temporary page source table through Rec; a non-key field of a table without keys). Before the analyzer fix, exactly the five implicit-primary-key cases failed; everything else passed, so no change to GetReceiverTableType was needed.

Guide and follow-ups

Decision table (from the #515 triage)

Item Verdict Reason
RecordRef SetTable() tracing Won't do Far too complex for an Info-severity hint
Return value tracing (exit(CreateGuid()), caller assigns to key) Won't do Needs a compilation-wide caller scan that re-runs on every editor pass
Global variable tracking Do Cheap: the existing symbol tracer already works on any symbol; only the sibling bodies of the same object must be bound
Parameter (var) tracking back to callers Won't do Same caller-scan cost as return values
Cross-module opt-in mode Won't do Flagging "we cannot see where it goes" is noise
AlreadySequentialGuid NoDiagnostic fixture Do Was removed only because the test SDK lacked CreateSequentialGuid(); every PC0029 fixture now runs on runtime 16.0
Suppression attribute/comment Won't do #pragma warning disable PC0029 already covers it (documented on alcops.dev)
Event parameter opt-in mode Won't do Same category as cross-module

Test notes

  • 53 PC0029 test cases pass (42 HasDiagnostic, 9 NoDiagnostic, 2 HasFix); Common tests pass.
  • Regression-first: every new HasDiagnostic fixture was committed before the analyzer change it targets and confirmed failing with "no diagnostic at marker".
  • All three TFMs compile (netstandard2.1, net8.0, net10.0); dotnet format clean; Validate-Rules.ps1 passes.

Docs companion: ALCops/alcops.dev#184

Closes #515

🤖 Generated with Claude Code

Arthurvdv and others added 3 commits September 12, 2026 15:14
… AlreadySequentialGuid

NoDiagnostic and HasFix test methods now call RequireMinimumVersion("16.0")
matching HasDiagnostic, so all three skip consistently on SDKs below the
CreateSequentialGuid() threshold.

AlreadySequentialGuid.al verifies that Guid.CreateSequentialGuid() assigned
to a key field is not flagged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When CreateGuid() is assigned to a codeunit or table global variable,
the analyzer now walks all sibling method and trigger bodies of the
containing object to determine whether the value flows to a key field.

SymbolFlowTracer accepts an optional containingSymbol so that bare-self
field access inside a table resolves correctly: a global variable's own
ContainingSymbol is the object (whose ContainingType is null), while the
method symbol provides the expected receiver context.

DescendantNodes().OfType<MethodOrTriggerDeclarationSyntax>() covers
page/report control triggers that are not object members. Including the
current body handles same-procedure global assignments without a special
case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ever mention it

Replace the private one-level UnwrapConversion (only IConversionExpression)
with Common's UnwrapConversions() which also peels IParenthesizedExpression,
so (CreateGuid()) assigned to a key field is now detected.

Add a syntax pre-filter: body.ToString().IndexOf("CreateGuid") skips bodies
that never mention the method, avoiding an unnecessary bind.

Fix the UseSequentialGuidScope schema description to say "field that is part
of a table key" instead of "primary key fields" (all declared keys count).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Arthurvdv and others added 2 commits September 12, 2026 15:27
… items

The rule doc now states that object-scope variables are traced through
every method and trigger body of the containing object, why the tracer
receives the sibling method as containing symbol, and why the analyzer
pre-filters bodies on the CreateGuid text and uses the shared conversion
unwrap. Caller-direction tracing, RecordRef.SetTable and a dedicated
suppression mechanism are recorded as deliberate non-reports.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… name

TraceGlobalVariable bound every method and trigger body of the containing
object before running the tracer. A case-insensitive text check on the
variable name now precedes the bind, mirroring the CreateGuid pre-filter
of the main callback: bodies that never reference the global cost a string
scan instead of a full bind, and the tracer still decides by symbol.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Arthurvdv
Arthurvdv marked this pull request as ready for review September 12, 2026 22:48
Arthurvdv and others added 2 commits September 13, 2026 08:33
…d TableNo OnRun

HasDiagnostic fixtures for every receiver form the plan identified:
- Bare self and Validate in table triggers (OnInsert)
- Implicit primary key (table without keys section): named variable, Rec self, Validate
- Namespace-qualified record variable (with and without explicit keys)
- TableNo codeunit OnRun: Rec self (implicit + explicit keys), bare self, Validate
- Table extension: bare, Rec, this, Validate
- Page Rec and bare self, page extension Rec
- Request page Rec, report Rec via request page, xmlport Rec via request page
- Report dataitem: named variable and bare self

NoDiagnostic fixtures:
- Page with SourceTableTemporary = true (temporary suppression)
- Implicit primary key: assignment to non-key field (field 2)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…section

IsFieldInAnyKey iterated only table.Keys (declared keys). A table
without a keys { } section has no declared keys; its synthesized
primary key (lowest-Id Guid field) is exposed solely through
table.PrimaryKey (TableTypeSymbol.cs:109-125, SynthesizedKeySymbol).

Check PrimaryKey.Fields first, then the declared Keys loop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t primary key and namespaces

The record-receiver-forms guide becomes receiver-forms.md and now states
where each object kind gets its Rec (a synthesized global in tables and
the page family, a synthesized local inside a TableNo codeunit's OnRun,
none in dataitems), that bare self binds with a null instance only inside
tables and tableextensions, that the four forms apply to fields, record
methods and user procedures alike, that ITableTypeSymbol.Keys omits the
synthesized primary key of a table without a keys section, and that
namespaced record types bind to the same symbol. testing.md, REVIEW.md,
the new-analyzer, fix-false-positive and new-codefix skills and the
regression catalog point at the guide and require the matching fixtures.
The PC0029 rule doc records the PrimaryKey check and the fixture coverage.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Arthurvdv Arthurvdv changed the title feat(PC0029): trace CreateGuid() through object-scope Guid variables feat(PC0029): trace global variables and cover all record receiver forms Sep 13, 2026
Arthurvdv and others added 2 commits September 13, 2026 09:24
Five report sites built the same "value flows to key field" sentence
inline; a KeyFieldReason helper on the result struct keeps the wording
in one place so a change cannot leave the sites inconsistent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
In a code-block action the body is already bound whenever any analyzer
in the run registers operation actions, and the member semantic model
caches bound nodes, so scanning body.ToString() could never skip the
bind. It only replaced the walker traversal with a body-sized string
allocation on every callback. The walker now runs unconditionally; the
rule doc records why no pre-filter exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Arthurvdv
Arthurvdv merged commit 4a40959 into main Sep 13, 2026
38 checks passed
@Arthurvdv
Arthurvdv deleted the feat/pc0029-global-variable-tracing branch September 13, 2026 08:04
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.

PC0029: roadmap - RecordRef, return-value and variable tracing, suppression, event parameters

1 participant