microsoft / microsoft/CSS-Exchange

Add a formatter check to disallow backtick line continuation

Open
#2,573 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Build Process Enhancement
Dominant language
PowerShell
Stars
1.3k
Forks
395
Avg merge
14h 7m
Merged PRs (30d)
5

Description

Summary

Our code formatter (.build/Invoke-CodeFormatterOnFiles.ps1) has no check that flags backtick (`) line continuation in .ps1 / .psm1 files. Backtick continuation is a well-known PowerShell anti-pattern — trailing whitespace after the backtick silently breaks the continuation, and the character is easy to miss in review. Splatting, breaking after a pipe (|), or breaking inside an open paren / brace is preferred in every case.

Neither PSSA (as configured in PSScriptAnalyzerSettings.psd1) nor any of the existing CodeFormatterChecks/ files (CheckContainsCurlyQuotes, CheckFileHasNewlineAtEndOfFile, CheckMarkdownFileHasNoBOM, CheckMultipleEmptyLines, CheckScriptFileHasBOM, CheckScriptFileHasComplianceHeader, CheckScriptFormat, CheckTokenTypeCasing) target backtick continuation. PSAvoidUsingBackticks is not a built-in PSSA rule (it lives in the community CommunityAnalyzerRules module) and is not referenced anywhere in this repo.

Evidence

Discovered while reviewing PR #2571.

  • Transport/Get-TerrlExternalRecipientEstimate.ps19 backtick continuations (lines 316–319, 830–833, 879).
  • Transport/Tests/Get-TerrlExternalRecipientEstimate.Tests.ps139 backtick continuations.

Every occurrence in the PR is either a cmdlet call that would be more readable as a splat, or an expression that could break naturally after | / (. Example:

# Before (line 316)
} elseif (Test-TerrlExchangeOnlineJournalReport `
        -SenderAddress $senderAddress `
        -Recipient $recipient `
        -JournalSet $journalSet `
        -JournalSenderSet $journalSenderSet) {

# After (splat)
$journalArgs = @{
    SenderAddress    = $senderAddress
    Recipient        = $recipient
    JournalSet       = $journalSet
    JournalSenderSet = $journalSenderSet
}
} elseif (Test-TerrlExchangeOnlineJournalReport @journalArgs) {

Proposed change

Add a new CheckBacktickLineContinuation.ps1 under .build/CodeFormatterChecks/ and wire it into .build/Invoke-CodeFormatterOnFiles.ps1:

$errorCount += (CheckBacktickLineContinuation $fileInfo $Save) ? 1 : 0

Detection strategy: tokenize with [System.Management.Automation.PSParser]::Tokenize (already used by CheckTokenTypeCasing) and flag any LineContinuation token, OR match the regex `\s*$ on non-blank source lines that are outside here-strings, comments, and string literals (token stream is safer than the regex — regex would produce false positives inside single-line here-strings or ` characters inside strings).

Reporting: each occurrence should be reported with file + 1-based line number so contributors can locate them quickly.

-Save behavior: none. Auto-fixing backticks to splats is not safe (requires choosing splat variable names, deciding on placement), so this check should be report-only and let the author refactor by hand.

Acceptance criteria

  • CheckBacktickLineContinuation.ps1 exists and is dot-sourced by Invoke-CodeFormatterOnFiles.ps1.
  • Invoke-CodeFormatterOnFiles calls it for each file and increments $errorCount on hits.
  • Running against a repro script containing a backtick continuation returns a non-zero error count and prints file + line number.
  • Backticks inside strings, here-strings (@" ... "@, @' ... '@), and comments do not trigger the check.
  • Escaped characters that legitimately use backticks in strings ("`t", "`n", "`$var") are not flagged.
  • -Save is a no-op for this check (auto-rewrite is not attempted).
  • A follow-up sweep PR (or PRs, if scope requires) removes existing offenders across Admin/, Diagnostics/, Shared/, Transport/, Setup/, and the Tests/ folders.

Non-goals

  • Auto-fix. Refactoring backtick continuations into splats or natural pipe/paren breaks is an authoring decision, not a formatter transformation.
  • Blocking backticks in interactive PowerShell examples inside docs/ markdown files.
  • Adding the CommunityAnalyzerRules module as a formatter dependency.

Repro

@'
Get-ChildItem -Path C:\Temp `
    -Recurse `
    -Filter *.log
'@ | Set-Content -Path .\repro.ps1 -Encoding utf8BOM

. .build\Invoke-CodeFormatterOnFiles.ps1
Invoke-CodeFormatterOnFiles -FilePaths .\repro.ps1  # currently returns 0 errors

Related

  • #2572 — Type-token casing check (drafted alongside this one from PR #2571 review).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read .build/Invoke-CodeFormatterOnFiles.ps1 and the existing .build/CodeFormatterChecks/ files, especially CheckTokenTypeCasing, to follow the formatter check pattern. Run the provided repro script through Invoke-CodeFormatterOnFiles and verify the new check reports each real continuation with its file and line number, ignores strings, here-strings, and comments, and leaves files unchanged with -Save.

Written by the indexing model from the issue text.

Assessment

Tech stack
powershell
Domain
tooling
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.