Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ $results = New-Object System.Collections.ArrayList
$results
```

## COMMAND INVARIANTS

Structural rules for every function in `public/` and `private/functions/`. Rules 1 and 3 are enforced by the "command structure" Describe in `tests/dbatools.Tests.ps1`, which reports file, function and line; the rest is guidance a reviewer checks.

**Stop-Function flow control, in short:** without EnableException, `-Continue` executes `continue` and every other call sets the caller's interrupt flag and returns. With EnableException, `-SilentlyContinue` executes `continue` and every other call sets the flag and throws. The flag is set in the scope that called Stop-Function.

1. **(enforced) A Stop-Function that can continue needs a local target.** A call with `-Continue` or `-SilentlyContinue` must sit inside a loop or `switch` of the same function or scriptblock, and `-ContinueLabel` must name a label of one of them. Without a local target the `continue` leaves the command and silently skips an item of the caller's loop; with a label that matches nothing it ends the caller's whole script. A loop around a helper definition or a `ForEach-Object` callback is no target. Helpers that deliberately continue their caller's loop are listed as exceptions in the test, with the reason and the exact number of sites.
2. **Stop, then stop working.** When a Stop-Function call abandons the current operation, prevent further work against the invalid state explicitly, usually with `return` (or `continue` in a loop). Falling through is fine when it is the design and keeps the error and output contract: a failure row emitted after the warning, cleanup followed by flow control, a `$failed` flag that gates the rest.
3. **(enforced) A begin block that can stop guards process.** When `begin` can set the command's interrupt flag, the first statement of `process` is `if (Test-FunctionInterrupt) { return }`. A call counts when it runs in the command's own scope: directly in `begin`, or in a scriptblock that is dot-sourced or handed to `ForEach-Object`, `Where-Object`, `.ForEach()` or `.Where()`. `-SilentlyContinue` alone counts, because it sets the flag without EnableException. Calls in helper functions do not count - their flag lands in the helper.
4. **Guard the work in `end`, never the cleanup.** An `end` block that does the command's main work starts with the guard; one that disconnects or disposes runs unguarded so it also runs after a stop.
5. **Release the temporary connections you own, preserve the ones the caller owns or shares.** Cleanup must be reachable on error and early-exit paths, preferably in `finally` where the resource lifetime allows it. An unguarded `end` block alone is no cleanup guarantee.
6. **No `continue` semantics in handed-off scriptblocks.** A `-Continue` inside a scriptblock passed to `ForEach-Object` or `Invoke-Command` binds to whatever loop is running when it executes. Intentional dynamic binding needs a comment at the site, an entry in the rule 1 exceptions and a behavioral test; everything else gets restructured.

## COMMENT PRESERVATION REQUIREMENT

**ABSOLUTE MANDATE**: ALL COMMENTS MUST BE PRESERVED EXACTLY as they appear in the original code including:
Expand Down Expand Up @@ -279,6 +292,7 @@ The dbatools.library version used by CI and local development is pinned in **`.g
**dbatools Patterns:**
- [ ] SMO used first, T-SQL only when appropriate
- [ ] Pipeline output emitted immediately
- [ ] Command invariants kept (Stop-Function flow control, process guard, connection cleanup)
- [ ] No `-Detailed`/`-Simple` output mode switches
- [ ] Command names use singular nouns

Expand Down
12 changes: 12 additions & 0 deletions tests/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,18 @@ Two things keep that skip honest, and both are worth copying:
- **Assert the engine first.** The Azure tests begin with an `It` that fails unless `DatabaseEngineEdition` is `SqlDatabase`. Pointed at anything else the rest would pass while proving nothing, because every other engine simply takes the `USE`.
- **Assert the message, not just the failure.** Where the command has to refuse, the test also asserts that the error is *not* the raw "USE statement is not supported" - that is what fails if the code ever reaches `ChangeDatabase` again.

## STRUCTURAL CHECKS OF THE COMMANDS

The "command structure" Describe in `dbatools.Tests.ps1` enforces rules 1 and 3 of the command invariants in the repository `CLAUDE.md` (COMMAND INVARIANTS), which explains why they exist. It parses `public/` and `private/functions/` without SQL Server, so it runs with the Compliance tag:

```powershell
Invoke-Pester -Path .\tests\dbatools.Tests.ps1 -TagFilter Compliance
```

- The analyzer lives in `dbatools.CommandStructure.ps1`. A change to it needs a positive or negative fixture in the "command structure analyzer" Describe.
- A new rule 1 exception (a helper that deliberately continues its caller's loop) needs the reason in the exception list, a comment at the site and a behavioral test of the calling command. Prefer restructuring the helper.
- The structural checks do not replace behavioral coverage. A fix for a flow-control defect still gets a command-level regression test that fails on the old code, such as a caller loop that counts its iterations.

## TEST MANAGEMENT GUIDELINES

The dbatools test suite must remain manageable in size while ensuring adequate coverage for important functionality.
Expand Down
Loading
Loading