Skip to content

fix(LC0031): convert bare LockTable() to bare ReadIsolation in the code fix - #547

Merged
Arthurvdv merged 6 commits into
mainfrom
fix/lc0031-bare-locktable-codefix
Sep 13, 2026
Merged

Arthurvdv merged 6 commits into
mainfrom
fix/lc0031-bare-locktable-codefix

Conversation

@Arthurvdv

@Arthurvdv Arthurvdv commented Sep 13, 2026

Copy link
Copy Markdown
Member

Summary

Issue #530 reports LC0031 firing on bare LockTable() inside Base App table triggers (OnInsert, OnDelete) and notes that the code action "does not update anything here".

Verdict: not a false positive. The evidence:

  1. SDK: the compiler binds bare, Rec., this. and named-variable receivers to one TableClassTypeSymbol built-in with no self-receiver special case; LockTable carries no deprecation (unlike FindSet(ForUpdate, UpdateKey) in the same class).
  2. Microsoft Learn / BCQuality: LockTable is "a transaction-wide signal"; the endorsed use is a read directly preceding a dependent write. Neither mentions triggers as a special case.
  3. BC App corpus (W1, BC 23.5 to 28.4): trigger LockTable() calls went from 80 to 78 (two removals, zero additions). Business Foundation, E-Document Core, Subscription Billing and Excise Taxes contain no LockTable at all.
  4. Modern replacement: where Microsoft does need "make sure nobody took this number" in OnInsert, new code uses ReadIsolation on a second instance (Customer.Table.al, SustESGStandard.Table.al).

The real defect is the code fix returning the unchanged document on the bare form. The analyzer is unchanged.

What changed

  • Code fix (RecordInstanceIsolationLevel.cs): add the IdentifierNameSyntax path so bare LockTable() becomes bare ReadIsolation(IsolationLevel::UpdLock). The MemberAccessExpressionSyntax path (named variable, Rec., this., RecordRef) keeps its receiver; any other shape still returns the document untouched. The replacement copies the trivia of the original invocation, because a fresh SyntaxFactory identifier would drop the indentation and comments of a bare call (found in review, pinned by HasFix/BareSelfWithLeadingComment).
  • Fixtures: full receiver-form set (17 HasDiagnostic, 3 NoDiagnostic, 8 HasFix) pinning every receiver origin, RecordRef, arguments, and both ReadIsolation forms.
  • Rule doc: new .claude/rules/diagnostics/lc0031-record-instance-isolation-level.md with the trigger decision, the per-form verdicts, SDK facts and code fix decisions; the receiver-forms regression row now covers the code fix flavour of the gap.

Receiver-form verdicts

Form Analyzer CodeFix
Named variable ok, pinned ok, pinned
Rec. (table, tableextension, page, TableNo OnRun) ok, pinned ok, pinned (table); same path elsewhere
Bare self in table and tableextension ok, pinned fixed, pinned
Bare self on page / in TableNo OnRun ok, pinned ok, pinned (page)
this. (table, tableextension) ok, pinned (gated 14.0) ok, pinned (gated 14.0)
Namespaced fully qualified variable ok, pinned same path as named variable
RecordRef variable ok, pinned ok, pinned
LockTable(true) (arguments) ok, pinned arguments dropped (known, documented)

Test plan

  • HasFix/BareSelfInTrigger failed with an unchanged-document diff before the fix
  • All 28 LC0031 tests pass after the fix (17 HasDiagnostic, 3 NoDiagnostic, 8 HasFix); tableextension fixtures are gated on SDK 13.0 (AL0334 on older compilers), this fixtures on runtime 14.0
  • HasFix/BareSelfWithLeadingComment fails without WithTriviaFrom and passes with it
  • Full LinterCop test suite: 384 passed, 2 skipped (pre-existing namespace skips)
  • Three-TFM Release build (netstandard2.1;net8.0;net10.0): 0 warnings, 0 errors
  • dotnet format ALCops.sln --verify-no-changes clean
  • Validate-Rules.ps1: 54 rules files pass
  • /code-review high (Sonnet): trivia loss on the bare form fixed; doc verdict table corrected; the suggested TryGetMethodCall reuse was tried and reverted because the helper returns the base ExpressionSyntax, which the factory methods reject
  • CI green on all SDK legs (38 checks, Test results report green)

Closes #530
Related: #545

Docs companion PRs: ALCops/alcops.dev#185 (merged) and ALCops/alcops.dev#186 (tri-state version wording)

🤖 Generated with Claude Code

Arthurvdv and others added 6 commits September 13, 2026 11:03
Add the full receiver-form fixture set (named variable, Rec, bare,
this, tableextension, page, TableNo OnRun, namespaced, RecordRef,
LockTable with arguments) plus NoDiagnostic fixtures for the obsolete
gate and both ReadIsolation forms.

HasFix/BareSelfInTrigger and HasFix/BareSelfInTableExtension are
expected to fail at this commit: the code fix returns the unchanged
document when the invocation expression is not a MemberAccessExpression.

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

The code fix returned the unchanged document when the invocation
expression was not a MemberAccessExpressionSyntax, which is the case
for bare LockTable() inside tables and tableextensions. Add the
IdentifierNameSyntax path so bare LockTable() becomes bare
ReadIsolation(IsolationLevel::UpdLock).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A bare LockTable() on a page has an IdentifierNameSyntax expression like
the table form, so the fix must produce a bare ReadIsolation there too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add the rule doc: why self LockTable() in table triggers stays reported,
the per-form verdict table for analyzer and code fix, the SDK facts the
decision rests on, and the code fix decisions. Extend the receiver-forms
regression row with the code fix flavour of the bare-self gap.

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

A fresh SyntaxFactory identifier carries no trivia, so the bare form lost
the indentation and comments attached to the LockTable token; the
replacement now copies the trivia of the original invocation. The
receiver switch is typed on CodeExpressionSyntax so both forms share
one construction path.

Tableextension fixtures are gated on SDK 13.0: older compilers report
AL0334 for a tableextension whose target is declared in the same module.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Arthurvdv
Arthurvdv merged commit 24ec210 into main Sep 13, 2026
39 checks passed
@Arthurvdv
Arthurvdv deleted the fix/lc0031-bare-locktable-codefix branch September 13, 2026 10:56
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.

[Bug]: LC0031 - LockTable still used in table triggers

1 participant