mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 06:36:55 +01:00
18 more AL/BC patterns: data-modeling, testing, style, security, error-handling, ui, upgrade, web-services, appsource (#157)
* Add 18 more community AL/BC patterns across appsource, data-modeling, error-handling, security, style, testing, ui, upgrade, and web-services Second contribution from CURABIS ApS, generalized from patterns observed across real AppSource/PTE development. Cross-checked against the current microsoft/knowledge corpus before opening; several originally-drafted candidates were dropped as duplicates of existing files. * Address Jesper Schulz-Wedde's review on PR #157 - release-must-update-app-version.md: reframe around AppSource's actual strict full-version-ordering requirement; scope branching-policy claims as team convention, not platform rule. - pictures-must-use-media-not-blob.md: MediaSet is a collection of independent media objects, not automatic image variants/thumbnails. - log-writes-must-survive-rollback.{md,good.al}: StartSession's only data channel into the new session is its Record parameter to a TableNo-scoped codeunit; a setter called on a local instance before starting the session populates nothing in the new session. - exposed-objects-must-be-in-a-permission-set.md: correct the three exposure mechanisms (Web Services config, PageType/QueryType=API, ServiceEnabled as a method-only attribute). - pages-must-not-contain-business-logic.md: scope to persisted mutations and cross-entry-point rules; presentation-only calculations and table-owned invariants are not violations. - given-blocks-must-cover-full-precondition-chain.good.al: replace invented LibrarySales calls with the real API (CreateCustomer/CreateSalesOrderForCustomerNo/PostSalesDocument). - test-feature-scenario-tags.{md,good.al}: move [SCENARIO] inside the test procedure body to match the current BCApps corpus; keep [FEATURE] at codeunit level per Microsoft's own documented option. - ui-test-codeunit-naming.md: scope the _UT suffix and adjacent-ID pairing as an explicit team convention, not a BCApps-wide standard. - page-design-must-match-bc-page-type-conventions.md / table-design-must-match-bc-table-type-conventions.md: Card's single-key primary-key claim is a contextual heuristic, not a mandatory constraint (Ship-to Address, Customer/Vendor Bank Account are real composite-key Card pages); a Subsidiary table with its own identity commonly gets List+Card, not Worksheet/Tabular. - api-page-least-privilege-write-access.{md,good.al}: only page-placed fields are ever exposed; set InsertAllowed/DeleteAllowed=false in the good sample so a narrow field set can't still create/delete records. - source-organized-by-feature-not-object-type.md, test-one-when-per-test.md: scope as team/testing-design conventions, not Microsoft platform requirements. - upgrade-tag-logic-must-not-nest-deeply.md: add the Microsoft Learn citation that already backs the two-level nesting limit. - Wire the new articles into the testing/data-modeling/error-handling/ security/ui review skills' candidate-selection signals. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Address second round of Jesper Schulz-Wedde's review on PR #157 - log-writes-must-survive-rollback.good.al: fixed invalid trigger OnRun(var Rec: ...) declaration; Rec is implicit when TableNo is set. - exposed-objects-must-be-in-a-permission-set.md: distinguished the three exposure mechanisms (page/query web service or API, codeunit published as a web service, [ServiceEnabled] bound action on a page) and their actual permission targets (page/query "..." = X vs codeunit "..." = X). - code-must-not-change-workdate.md: scoped from an absolute "never" to "not as a side effect of unrelated logic" - verified real WorkDate(x) setter usage in BCApps demo-data generators and test codeunits. - bcpt-scenarios-must-be-app-specific.md: SingleInstance and StartScenario/EndScenario reframed as context-dependent patterns, not mandatory requirements - BCPT Create Customer uses neither. - test-feature-scenario-tags.good.al/.bad.al: replaced the invented LibrarySales.CreateCustomerWithPrice/"Item Price Mgt." calls with a real, verified price-list-line test using Library - Sales/Library - Inventory/ Library - Price Calculation. - page-design-must-match-bc-page-type-conventions.md: scoped the missing UsageCategory anti-pattern to pages intended as searchable entry points. - defensive-vs-offensive-code-must-match-blast-radius.md/.good.al/.bad.al: replaced the VAT registration number "low blast radius" example with a genuinely cosmetic field (customer home page URL). - source-organized-by-feature-not-object-type.md: anti-pattern reframed as inconsistency with a repo's own convention, not the object-type scheme itself. - pictures-must-use-media-not-blob.md: removed leftover "image variants" wording contradicting the already-corrected MediaSet description. Proactively fixed while sweeping all fixtures for invented APIs: - given-blocks-must-cover-full-precondition-chain.bad.al: PostSalesOrder called with wrong arity and referenced an undeclared variable. - ui-test-codeunit-naming.good.al/.bad.al: replaced the same fake "Item Price Mgt."/TestPage "Item Price" with real Library - Sales calls and the real Customer Card TestPage. Worklist completeness: added review-skill cues for the 12 of 18 new rules that had none (al-appsource-review.md, al-data-modeling-review.md, al-error-handling-review.md, al-security-review.md, al-style-review.md x3, al-testing-review.md x2, al-ui-review.md, al-upgrade-review.md, al-web-services-review.md), and fixed test-feature-scenario-tags' cue, which only matched the compliant (tagged) shape instead of the anti-pattern (untagged/generic-named test). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix ten focused correctness items plus sample links from Jesper's 2026-09-15 re-review Six carried-over threads: - api-page-least-privilege-write-access fixtures: added the mandatory EntityName/EntitySetName properties (AL0485). - pages-must-not-contain-business-logic fixtures: Sales Line has no "Total Amount" field; replaced with the real "Line Amount" (field 103). - test-feature-scenario-tags.good.al and test-one-when-per-test.good.al: CreatePriceHeader leaves a price list in Draft status, which price calculation ignores. Added Validate(Status, Active) + Modify before the sales line that depends on it. Verified Status field/enum against PriceListHeader.Table.al and PriceStatus.Enum.al in the BCApps clone. - exposed-objects-must-be-in-a-permission-set.md: a published codeunit is a SOAP endpoint (SOAP is deprecated), not OData - Page/Query are the OData object types. Corrected and pointed new integrations at API pages/queries instead. - al-error-handling-review.md: the log-writes-must-survive-rollback cue selected on Session.StartSession, which only appears in the compliant fix, never in the anti-pattern - the bad fixture could never be worklisted. Recued on the actual risk shape (log insert around a failed TryFunction/GetLastError* path, then raise/propagate), with StartSession as an explicit compliant discriminator instead. - page-design-must-match-bc-page-type-conventions.md: the enum value is NavigatePage, not Navigate; noted the type list is a selected subset, not an exhaustive PageType catalogue (PromptDialog, ConfigurationDialog, UserControlHost, XmlPort also exist, out of this article's scope). Four new correctness gaps: - release-must-update-app-version.md: "the version is the only identity" was backwards - id is the app's stable identity, version identifies a release/code-state of it. - defensive-vs-offensive-code-must-match-blast-radius.good.al: the "low blast radius" example had no else branch, so a failed Customer.Get() left the field at its prior/default value instead of the explicit chosen fallback the article claims to demonstrate. Added the else. - bcpt-scenarios-must-be-app-specific.good.al: InitTest and both measured StartScenario/EndScenario sections were empty/comment-only, so the "app-specific" fixture measured no actual work. Filled in a real, self-contained header+line creation path. - upgrade-tag-logic-must-not-nest-deeply.good.al: the flattened version dropped both safety conditions the bad fixture had (Discount % = 0, nonblank posting group), silently changing behavior instead of just removing nesting. Extracted the guarded update into a helper with both conditions preserved as early exits. Also converted this PR's remaining plain-backtick "See sample:" sample references (16 articles) to the READ-convention markdown-link form, matching the fix already made on #156/#158. Rebased onto upstream/main (conflicts in al-ui-review.md, al-style-review.md, al-upgrade-review.md against merged upstream PRs - all additive, both sides' worklist cues retained). * Fix four merge-critical issues from Jesper's 2026-09-22 review - pages-must-not-contain-business-logic.good.al/.bad.al: the "good" codeunit still directly assigned real Sales Line."Line Amount" and called Modify(), bypassing the field's normal Validate cascade (discount, VAT, related-amount maintenance) - persisting inconsistent document lines regardless of which object the code lived in. Replaced the real Sales Line example with a self-contained "Sample Order Line" table and switched the codeunit to Validate()/Modify(true), so the fixture demonstrates the page-vs-codeunit separation without teaching unsafe direct field writes to a real BC document table. - bcpt-scenarios-must-be-app-specific.good.al: Customer.FindFirst() assumed a pre-existing customer (fails against an empty environment), and a session-local NextNo counter for the header key collides across concurrent BCPT sessions and repeated runs. Creates its own customer when none exists, and generates keys from CreateGuid() instead of an in-memory counter. - upgrade-tag-logic-must-not-nest-deeply: the rule conflated two different things - nesting one tag's existence check inside another (the real anti-pattern Microsoft's guidance warns against) with having business-data safety conditions inside a single tagged migration's own loop body (which Microsoft's own worked example does, and its own design guidance explicitly requires: "Implement extra safety checks to avoid data corruption, even though you're using upgrade tags"). Rewrote the Description/Best Practice/Anti Pattern to scope the rule to actual tag nesting and migrations blended under one tag, and rewrote both fixtures: good.al now shows two safety conditions correctly nested inside one migration's own loop plus a second, genuinely separate migration as its own flat tagged procedure; bad.al now shows the real anti-pattern, one tag's check nested inside another's guarded body. - table-design-must-match-bc-table-type-conventions: the rule and its worklist cue fired on any new table with a keys block, forcing buffers, queues, logs, mapping tables, and staging tables into the nearest-looking one of nine business-record archetypes. Added an explicit scope note that these nine types aren't an exhaustive table catalogue, and narrowed the al-data-modeling-review.md cue to require positive evidence (a type-specific naming suffix, key shape, or usage) before worklisting, instead of a bare keys/primary-key declaration. * Narrow upgrade-tag nesting cue to match revised article Cue now flags only nested upgrade-tag checks or functionally unrelated migrations under one tag, and explicitly excludes record loops and business-data safety guards belonging to a single migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
4287233f80
commit
f63943dcfd
59 changed files with 1612 additions and 2 deletions
|
|
@ -52,6 +52,7 @@ The following targeted checks cover every current `appsource` article across the
|
|||
- A page or report that repository context identifies as a direct user entry point omits `UsageCategory` or sets it to `None` — `set-usagecategory-on-searchable-entry-points`. Do not select this article based only on object type; exclude supporting parts, dialogs, API pages, and objects intentionally reached through another page.
|
||||
- A `DateTime` assignment adds or subtracts a fixed duration to represent an assumed regional offset — `do-not-hard-code-time-zone-offsets`. Require contextual evidence such as an hour-sized constant, offset-oriented name, or time-zone comment; do not flag deadlines, schedules, or elapsed-time calculations.
|
||||
- For BC v27 or later, `app.json` adds or changes the `help` URL to a path deeper than two levels, or a changed Copilot/context-sensitive help arrangement would ground the app under an overly broad truncated parent — `keep-copilot-help-url-to-two-path-levels`.
|
||||
- A release/submission pipeline change (`AL-Go-Settings.json`, a publish/release workflow) or an `app.json` version bump is present without the new complete version being strictly greater than the previously submitted one, or the change asserts a hand-edited build/revision or every-merge-is-a-release policy as a universal AppSource rule rather than a project-specific workflow choice — `release-must-update-app-version`. Require repository/pipeline context to know the previously submitted version; a single `app.json` diff cannot prove ordering on its own.
|
||||
- Changed code declares or calls `File.Open`/`File.Create`/`File.Read`/`File.Write` in an app targeting Business Central Online — `file-datatype-saas`.
|
||||
|
||||
Once the candidate worklist is known, resolve layer-precedence conflicts per READ. Drop lower-precedence files whose normative guidance (`## Best Practice` or `## Anti Pattern`) directly contradicts a higher-precedence candidate, and record each dropped file in `suppressed` with `reason: "layer-precedence"`. Files that would have been candidates but are hidden because their layer is disabled in consumer configuration are recorded with `reason: "configuration"`. Files that never became candidates are NOT recorded in `suppressed`.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ A file enters the candidate worklist when its `keywords` intersect the extracted
|
|||
The following targeted checks cover every current `data-modeling` article. Treat each as a candidate-selection cue: when the signal appears in changed code, add the named article to the worklist and evaluate it in Action.
|
||||
|
||||
- A `* Setup` table or its page changes singleton structure, uses a nonblank or generated key, permits insert/delete, uses a List page, or does not ensure the blank-keyed row exists — `setup-table-is-a-singleton`.
|
||||
- A new field is typed `Media`, `MediaSet`, or `BLOB` and the field's caption/name suggests a picture or image — `pictures-must-use-media-not-blob`.
|
||||
- Code outside a test codeunit or a demo-data generator calls `WorkDate(NewDate)` (the assignment form, not a bare `WorkDate()` read) as part of logic whose purpose is unrelated to the work date itself — `code-must-not-change-workdate`. A test deliberately setting a date context, or a demo-data routine that saves, sets, and restores the work date to backdate the data it creates, is not this anti-pattern.
|
||||
- A new or extended table's name, fields, or usage positively establish it as one of Business Central's nine business-record types — a name ending `Ledger Entry`/`Register`/`Journal Line`/`Header`/`Line`/`Setup`, an auto-generated `Entry No.`/`No.` key posted from elsewhere, a `Template Name`+`Batch Name`+`Line No.` key, or a singleton `Primary Key` field — `table-design-must-match-bc-table-type-conventions`. Do not worklist it from a bare `keys` block or primary-key declaration alone: a temporary/buffer table, a work queue, a log, a cross-reference/mapping table, or a process-local staging table is not one of the nine types and is out of this rule's scope entirely, not an unresolved case.
|
||||
- Code reads `Item Ledger Entry."Document No."` (or `"Last Shipping No."`/`"Last Posting No."`) after a combined Ship+Invoice **sales** post — `item-ledger-entry-document-no-follows-last-shipping-no`. This is a sales-specific rule: purchase combined posting is Receive+Invoice and uses receiving fields such as `"Last Receiving No."`, not the shipment/document-number behavior this article describes. Do not worklist it from purchase posting code.
|
||||
- A custom master table changes its primary key, `No.`/`No. Series` fields, or `OnInsert` without assigning a blank `No.` from setup through a number series — `master-table-no-from-number-series-in-oninsert`.
|
||||
- BC v22 or later code introduces or retains `NoSeriesManagement`, `InitSeries`, `SelectSeries`, or `SetSeries`, or number assignment/manual-entry checks do not use codeunit `"No. Series"` methods such as `GetNextNo`, `IsManual`, or `TestManual` — `use-no-series-codeunit-not-noseriesmanagement`.
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ A file enters the candidate worklist when its `keywords` intersect the extracted
|
|||
The following targeted checks cover every current `error-handling` article:
|
||||
|
||||
- `[ErrorBehavior(ErrorBehavior::Collect)]`, `ErrorInfo.Collectible`, `HasCollectedErrors`, `GetCollectedErrors`, or `ClearCollectedErrors` is added or changed, especially when errors are collected without later surfacing/clearing them — `collect-validation-errors-with-errorbehavior`.
|
||||
- New or changed code inserts an error/duration log record around a failed `TryFunction`/`GetLastErrorText`/`GetLastErrorCode` path and then raises, propagates, or rethrows the error — `log-writes-must-survive-rollback`. Do not worklist it when the log insert already happens inside a `Session.StartSession`-targeted codeunit's `OnRun`; that is the compliant shape, not the signal to flag.
|
||||
- A guarded lookup (`if Record.Get(...) then ... else` or similar) sets a value used later, and the same guard shape (with the same blank/zero fallback style) is applied to a field that feeds a posted amount, a tax/VAT calculation, a quantity or price actually used in a transaction, or a legally/compliance-facing output — `defensive-vs-offensive-code-must-match-blast-radius`. The signal is a posting-critical or compliance-facing field guarded defensively with a silent fallback, not the mere presence of a guarded lookup.
|
||||
- Developer-only invariant text is raised with default client visibility, or a user-actionable validation is hidden as `ErrorType::Internal` — `errortype-internal-vs-client-for-diagnostics`.
|
||||
- `FieldError` receives a complete capitalized sentence, repeats the field caption/value, or ends the predicate with punctuation — `fielderror-default-message-logic`.
|
||||
- An unguarded `FieldError` is used as though it performed a comparison, or `TestField` is forced onto a complex rule needing a tailored predicate — `fielderror-vs-testfield`.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ Narrow the relevant files to the subset that applies to the changes under review
|
|||
|
||||
- The changed AL object names and types — especially permission sets, codeunits handling authentication or authorization, objects touching `Isolated Storage`, `OAuth2` flows, web service endpoints, API pages, event publishers, and RecordRef helpers.
|
||||
- The changed procedures and triggers, weighted toward those that call `HttpClient`, validate or compose URLs, write to telemetry, read or write secrets, unwrap SecretText, manipulate record-level security, expose var Boolean guard parameters, or bypass the permission model (for example, `RecordRef.Open`, `Record.WritePermission`, direct table access from a non-owning app).
|
||||
- Tokens extracted from the diff that relate to security concerns (`IsolatedStorage`, `SetEncrypted`, `OAuth2`, `SecretText`, `Unwrap`, `NonDebuggable`, `Password`, `Token`, `HttpClient`, `Uri`, `AreURIsHaveSameHost`, `IsValidURIPattern`, `RecordRef`, `RecordId`, `TransferFields`, `Codeunit.Run`, `Access = Internal`, `internalsVisibleTo`, `Open`, `IntegrationEvent`, `SkipValidation`, `HasAccess`, `Permission`, `UserSecurityId`, `Commit`, `SetFilter`).
|
||||
- Tokens extracted from the diff that relate to security concerns (`IsolatedStorage`, `SetEncrypted`, `OAuth2`, `SecretText`, `Unwrap`, `NonDebuggable`, `Password`, `Token`, `HttpClient`, `Uri`, `AreURIsHaveSameHost`, `IsValidURIPattern`, `RecordRef`, `RecordId`, `TransferFields`, `Codeunit.Run`, `Access = Internal`, `internalsVisibleTo`, `Open`, `IntegrationEvent`, `SkipValidation`, `HasAccess`, `Permission`, `UserSecurityId`, `Commit`, `SetFilter`, `ServiceEnabled`, `PageType = API`, `QueryType = API`, `permissionset`, `Web Services`).
|
||||
|
||||
A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone.
|
||||
|
||||
|
|
@ -49,6 +49,7 @@ For secret values, select the most specific sink owner:
|
|||
|
||||
- When a `Text`/`Code` credential is declared, passed, returned, or unwrapped without a visible HTTP URI/header/body sink, use `secrettext-for-credentials.md`.
|
||||
- When that value is interpolated into a URI, authorization header, or HTTP body and sent through `HttpClient`, use `secrettext-with-httpclient.md` as the primary finding. It supersedes the generic credential-type article at that location; keep the latter only as a supporting reference when useful.
|
||||
- When a page/query is registered in Web Services, declares `PageType = API`/`QueryType = API`, a codeunit is registered in Web Services, or `[ServiceEnabled]` is added to a page procedure, and no permission set in the app grants a matching `page "..." = X` / `query "..." = X` / `codeunit "..." = X` entry for that specific object — use `exposed-objects-must-be-in-a-permission-set.md`. The anti-pattern is the exposed object missing its own execute entry, even when the underlying table's `tabledata` permissions look complete; require repository-level permission-set context, since one file cannot prove an entry is absent elsewhere in the app.
|
||||
|
||||
Once the candidate worklist is known, resolve layer-precedence conflicts per READ. Drop lower-precedence files whose normative guidance (`## Best Practice` or `## Anti Pattern`) directly contradicts a higher-precedence candidate, and record each dropped file in `suppressed` with `reason: "layer-precedence"`. Files that would have been candidates but are hidden because their layer is disabled in consumer configuration are recorded with `reason: "configuration"`. Files that never became candidates are NOT recorded in `suppressed`.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ Apply these high-signal mappings before fuzzy topic ranking:
|
|||
- A `Label` or `TextConst` contains multiple or ambiguous placeholders but has no `Comment`, or its Comment does not explain every placeholder — `label-comment-explains-placeholders.md`. A single placeholder whose meaning is explicit in the text, such as `Customer %1`, is allowed without a Comment and must not be flagged.
|
||||
- A normal two-argument `Evaluate` has a resolved `DateFormula` destination and a hard-coded non-angle-bracket date-formula literal, directly or through a visible constant — `dateformula-evaluate-needs-language-independent-literals.md`. Do not use this cue for dynamic/localized external input, already invariant `<...>` input, or direct `CalcDate(Text, ...)` calls.
|
||||
- `function-call-parentheses-required.md` applies only to a zero-argument invocation written without `()`. Never worklist it from an invocation that already has parentheses or supplies arguments, including `Error(Label, Arg1, Arg2)`.
|
||||
- A new or changed comment restates what the adjacent code already makes obvious from its own names and structure (a comment that just repeats a variable/field/method name in prose) rather than explaining a non-obvious constraint, invariant, or workaround — `al-comments-must-not-restate-what-code-already-shows.md`. A comment absent entirely is not this anti-pattern; only a present-but-redundant comment is.
|
||||
- A `page`/`pageextension` adds or changes a procedure body that performs a calculation, validation, or record mutation belonging to a business operation reused across entry points, rather than presentation-specific state or a call into a codeunit — `pages-must-not-contain-business-logic.md`. A page calling a codeunit procedure, or a page's own presentation-only state and formatting, is not this anti-pattern; nor is a data invariant that belongs on the table itself.
|
||||
- Changed source files are added under an object-type folder (`Tables/`, `Pages/`, `Codeunits/`, etc.) in a repository whose existing structure is predominantly feature-based, or vice versa — `source-organized-by-feature-not-object-type.md`. The anti-pattern is inconsistency with the repository's own established convention, not the choice of either scheme; a repository consistently organized by object type throughout is not a violation. Require repository-level folder context; a single new file's path cannot prove the project's convention alone.
|
||||
- A new `.app` build artifact appears at the project root or another unversioned/arbitrary location, or is added to source control alongside the AL source that produced it — `al-build-output-must-not-pollute-project-root.md`. A deliberate `--outfolder`/`outputPath` destination added to `.gitignore` is the compliant shape, not the signal to flag.
|
||||
- A new or renamed AL identifier (variable, procedure, parameter, field, object, enum value, or label identifier) contains non-English words — `al-identifiers-english.md`. A caption, tooltip, or other user-facing text value in a non-English language is not this anti-pattern; only the identifier itself is in scope.
|
||||
- A field or variable is typed `Boolean` and its two possible values are genuinely named domain alternatives (a status pair like Inbound/Outbound, Debit/Credit, Buy/Sell) rather than a true/false predicate, or an `Option`/`Enum`/`Integer` models a domain concept that is intrinsically a yes/no flag — `binary-choice-must-be-boolean.md`. The signal is a semantic mismatch between the type and the domain concept, not the current number of states.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ A file enters the candidate worklist when its `keywords` intersect the extracted
|
|||
The following targeted checks cover every current `testing` article. Treat each as a candidate-selection cue: when the signal appears in changed code, add the named article to the worklist and evaluate it in Action.
|
||||
|
||||
- A method in a `Subtype = Test` codeunit adds or changes `[TransactionModel(...)]`, exercises code that calls `Commit` under `AutoRollback`, defaults broadly to `AutoCommit`, or chooses `None` for a writing test — `transactionmodel-attribute-governs-test-transactions`.
|
||||
- A new or changed `[Test]` procedure is added, whether or not it already carries `[FEATURE]`/`[SCENARIO]`/`[GIVEN]`/`[WHEN]`/`[THEN]` tags — `test-feature-scenario-tags`. A procedure with no tags at all, or a generic name like `Test1`, is the anti-pattern signal; presence of the tags is the compliant shape, not the thing to search for.
|
||||
- A test codeunit calls `TestPage` methods (`OpenNew`, `OpenView`, `OpenEdit`) alongside `[Test]` procedures in the same codeunit that call business-logic procedures directly with no `TestPage` involved — `ui-test-codeunit-naming`. The anti-pattern signal is both kinds of test mixed into one codeunit (or, on a project using the `_UT` convention, a UI-layer codeunit missing the suffix); a codeunit containing only `TestPage`-driven tests is not itself a violation.
|
||||
- A `[GIVEN]`-tagged setup precedes a posting call or report execution and does not visibly set up posting-group/VAT setup records, an explicit date, or (for a report test) both an included and an excluded record — `given-blocks-must-cover-full-precondition-chain`.
|
||||
- A test procedure contains more than one `[WHEN]` block, or more than one distinct action not labelled `[GIVEN]`, without the procedure name declaring a flow/defect-then-fix shape — `test-one-when-per-test`.
|
||||
- A `BCPT*` scenario codeunit is added and the PerformanceTest app's only other scenario codeunits are copies of Microsoft's shipped BCPT samples (`BCPT Create Customer`, `BCPT Create Item Journal`, `BCPT Post GL Entries`, etc.) with no scenario exercising the extension's own codeunits, FlowFields, or pages — `bcpt-scenarios-must-be-app-specific`.
|
||||
- An `AutoCommit` test runs under a `Subtype = TestRunner` codeunit that omits `TestIsolation` or sets it to `Disabled`, leaving committed data between tests — `testisolation-belongs-on-the-test-runner`. Require runner/repository context; a standalone test file cannot prove which runner executes it.
|
||||
- A permission-sensitive test uses `TestPermissions = Disabled`, claims to test a restricted user without `"Permissions Mock"`/`"Library - Lower Permissions"`, or declares `[TestPermissions(...)]` without applying that context — `permission-tests-must-lower-the-execution-context`.
|
||||
- Test fixture code manually calls `Init`/`Insert`, invents keys or prerequisite records, or bypasses available `LibrarySales`, `LibraryPurchase`, `LibraryERM`, `LibraryInventory`, `LibraryRandom`, or equivalent library codeunits — `use-library-codeunits-for-test-fixtures`.
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ Discard files that are not applicable. Retain conditionally applicable files onl
|
|||
Narrow the relevant files to the subset that applies to the changes under review.
|
||||
|
||||
- **UI-file filter.** UI review applies to files declaring `page`, `pageextension`, or `pagecustomization`, and to JavaScript/CSS/HTML that implements a control add-in's rendering or Business Central communication. When the diff contains no such files, return `outcome: "not-applicable"` without evaluating knowledge files.
|
||||
- A new or changed page's name/suffix, primary-key handling, `CardPageID`, `SubPageLink`, `AutoSplitKey`, or `UsageCategory` doesn't match the conventions of its own declared `PageType` — `page-design-must-match-bc-page-type-conventions.md`. A Card page over a composite-key table that supplements a master record, or a supporting/subpage/dialog page intended only to be reached through another workflow and correctly omitting `UsageCategory`, is not this anti-pattern on its own; check whether the page is actually mixing conventions or is meant as a searchable entry point before flagging.
|
||||
- For each relevant knowledge file, compute overlap against changed page declarations and control add-in files, weighted toward `Caption`, `ToolTip`, `AboutTitle`, `AboutText`, `OptionCaption`, `ShowCaption`, `InstructionalText`, `GridLayout`, `Style`, `StyleExpr`, promoted action definitions, field importance, page background tasks, DOM creation, ARIA attributes, keyboard/focus handlers, packaged-resource AJAX, and calls from JavaScript into AL.
|
||||
- Tokens extracted from the diff (`Caption`, `ToolTip`, `AboutTitle`, `AboutText`, `PageType`, `ShowCaption`, `InstructionalText`, `grid`, `fixed`, `GridLayout`, `Style`, `StyleExpr`, `Importance`, `Promoted`, `Additional`, `area(Promoted)`, `actionref`, `PromotedCategory`, `PromotedOnly`, `PromotedIsBig`, `ShowAs`, `SplitButton`, `fieldgroups`, `DropDown`, `UpdatePropagation`, `EnqueueBackgroundTask`, `OnAfterGetCurrRecord`, `OnAfterGetRecord`, `OnPageBackgroundTaskCompleted`, `OnPageBackgroundTaskError`, `RunPageBackgroundTask`, `Favorable`, `Unfavorable`, `Ambiguous`, `cuegroup`, `controladdin`, `control-add-in`, `usercontrol`, `aria-`, `tabindex`, `keydown`, `focus`, `innerHTML`, `createElement`, `packaged-resource`, `ajax`, `$.get`, `$.ajax`, `XMLHttpRequest`, `xhrFields`, `withCredentials`, `withcredentials`, `InvokeExtensibilityMethod`, `invokeextensibilitymethod`, `skipIfBusy`, `successCallback`, `success-callback`, `errorCallback`, `setInterval`, `JSON.stringify`, `payload`, `throttling`, `reduced-functionality`, `ClientServicesMaxUploadSize`, `&`, `Specifies`, `Message(`, `Confirm(`, `Error(` in a page context, `Disabled`, `Invalid`, `Whitelist`, `Blacklist`, trailing punctuation patterns on captions).
|
||||
- Tokens extracted from the diff (`Caption`, `ToolTip`, `AboutTitle`, `AboutText`, `PageType`, `ShowCaption`, `InstructionalText`, `grid`, `fixed`, `GridLayout`, `Style`, `StyleExpr`, `Importance`, `Promoted`, `Additional`, `area(Promoted)`, `actionref`, `PromotedCategory`, `PromotedOnly`, `PromotedIsBig`, `ShowAs`, `SplitButton`, `fieldgroups`, `DropDown`, `UpdatePropagation`, `EnqueueBackgroundTask`, `OnAfterGetCurrRecord`, `OnAfterGetRecord`, `OnPageBackgroundTaskCompleted`, `OnPageBackgroundTaskError`, `RunPageBackgroundTask`, `Favorable`, `Unfavorable`, `Ambiguous`, `cuegroup`, `controladdin`, `control-add-in`, `usercontrol`, `aria-`, `tabindex`, `keydown`, `focus`, `innerHTML`, `createElement`, `packaged-resource`, `ajax`, `$.get`, `$.ajax`, `XMLHttpRequest`, `xhrFields`, `withCredentials`, `withcredentials`, `InvokeExtensibilityMethod`, `invokeextensibilitymethod`, `skipIfBusy`, `successCallback`, `success-callback`, `errorCallback`, `setInterval`, `JSON.stringify`, `payload`, `throttling`, `reduced-functionality`, `ClientServicesMaxUploadSize`, `&`, `Specifies`, `Message(`, `Confirm(`, `Error(` in a page context, `Disabled`, `Invalid`, `Whitelist`, `Blacklist`, trailing punctuation patterns on captions, `CardPageID`, `AutoSplitKey`, `PageType = Card`, `PageType = List`, `PageType = Worksheet`, `PageType = Document`).
|
||||
|
||||
A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed page element. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ Narrow the relevant files to the subset that applies to the changes under review
|
|||
- Worklist the install-versus-upgrade rule when migration helpers are reachable only from an install codeunit.
|
||||
- Worklist `install-and-upgrade-codeunits-have-no-order.md` when a change adds multiple install or upgrade codeunits whose same-phase triggers share state or depend on one another.
|
||||
- Worklist `appversion-meaning-depends-on-execution-context.md` when install or upgrade code branches on `ModuleInfo.AppVersion()` or confuses it with `DataVersion()`.
|
||||
- An upgrade tag's existence check (`HasUpgradeTag`) is nested inside another tag's guarded body, or one tagged procedure performs two or more functionally unrelated migrations (different tables, fields, or concerns) under a single tag, or one procedure mixes the gated logic for more than one distinct upgrade tag — `upgrade-tag-logic-must-not-nest-deeply.md`. Do not flag record loops or business-data safety guards (corruption checks, redundant-write checks, or other conditions) that serve the single migration the tag represents, however many `if` levels they take — that is the compliant shape the article explicitly permits.
|
||||
|
||||
A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. When the diff contains no upgrade-related changes by any of the above signals, return `outcome: "not-applicable"` without evaluating files.
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ Narrow the relevant files to the subset that applies to the changes under review
|
|||
- Outbound HTTP integration code that constructs or sends requests, captures a client method's optional Boolean result, checks an `HttpResponseMessage`, or reads and parses response content.
|
||||
- Integration code that converts values to or from exchanged text: `Format` or `Evaluate` on a request URL, body, or file line, and `JsonToken`/`JsonValue` conversions of payload properties.
|
||||
- Webhook subscriber handlers and subscription lifecycle code, especially code that creates or renews subscriptions, handles `validationToken`, schedules from `expirationDateTime`, or targets resources whose eligibility is visible in the diff.
|
||||
- An API page (`PageType = API`) is added or changed and its `InsertAllowed`/`ModifyAllowed`/`DeleteAllowed` properties are left at their default `true` (or explicitly set `true`) for an operation the endpoint's stated purpose does not need — `api-page-least-privilege-write-access.md`. The signal is an operation left enabled beyond what the endpoint's own described purpose requires, not the mere presence of these properties; a page that genuinely needs full CRUD and grants it deliberately is not a violation.
|
||||
- An API page (`PageType = API`) with `InsertAllowed = true` lists a field in `ODataKeyFields` and that same field control sets `Editable = false` — `api-page-key-fields-must-be-editable-on-insert.md`. A system-generated key such as `SystemId` marked read-only is not this anti-pattern.
|
||||
- An API page exposes a field via `Rec` directly, and that field is a stored value computed from other fields inside an `OnValidate` trigger rather than a FlowField recalculated in `OnAfterGetRecord` — `stored-derived-fields-must-not-be-exposed-directly.md`. Exposing a genuine FlowField, or a value already recalculated in `OnAfterGetRecord`, is not this anti-pattern.
|
||||
- Tokens extracted from the diff that relate to API surface and behaviour (`PageType`, `QueryType`, `API`, `api-page`, `page-part`, `APIPublisher`, `APIGroup`, `APIVersion`, `EntityName`, `EntitySetName`, `ODataKeyFields`, `SystemId`, `SubPageLink`, `subpagelink`, `Multiplicity`, `multiplicity`, `Many`, `ZeroOrOne`, `SourceTableTemporary`, `Job Queue Entry`, `webhook`, `webhookSupportedResources`, `webhook-supported-resources`, `subscriptions`, `notificationUrl`, `validationToken`, `validationtoken`, `expirationDateTime`, `expirationdatetime`, `ServiceEnabled`, `WebServiceActionContext`, `SetActionResponse`, `ReadIsolation`, `IsolationLevel`, `ReadCommitted`, `InsertAllowed`, `ModifyAllowed`, `DeleteAllowed`, `Editable`, `SourceTable`, `HttpClient`, `HttpRequestMessage`, `HttpResponseMessage`, `Get`, `Post`, `Put`, `Delete`, `Send`, `IsSuccessStatusCode`, `HttpStatusCode`, `Content`, `ReadAs`, `JsonObject`, `JsonToken`, `JsonValue`, `AsValue`, `IsNull`, `SelectToken`, `Format`, `Evaluate`, `XmlDocument`).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue