PowerShell / PowerShell/PSScriptAnalyzer

PSUseConsistentIndentation double-indents attribute bodies that open a scriptblock (`[Attr({ … })]`)

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

Nobody has claimed this yet.

Dominant language
C#
Stars
2.2k
Forks
414
Avg merge
13h 1m
Merged PRs (30d)
2

Description

Prerequisites
  • I have read the documentation and my issue is not covered there.
  • I have searched the existing issues and my issue is not already reported.
Summary

PSUseConsistentIndentation (and therefore Invoke-Formatter) double-indents the body of an attribute that opens a scriptblock, e.g. [ArgumentCompleter({...})]. The body gets IndentationSize * 2 and the closing })] gets IndentationSize * 1.

This looks like the same root cause as #2159 (hashtable inside a method call: the LParen and the AtCurly each add an indentation level). Here the two adjacent openers are ( and { from a type/attribute literal instead of @{, so [AttributeName({ hits it too. #2159 was fixed on main by #2173 ("only the last unclosed opener on a line affects indentation"), but neither that fix nor #2159 covers the attribute form, and it is still broken in the latest release 1.25.0.

Steps to reproduce
$code = @'
[ArgumentCompleter({
	Param($commandName, $parameterName)
	$validKeys = @('a', 'b')
	$validKeys | Where-Object { $_ } | ForEach-Object { "$_=" }
})]
'@

$settings = @{
	IncludeRules = @('PSUseConsistentIndentation')
	Rules = @{
		PSUseConsistentIndentation = @{
			Enable        = $true
			IndentationSize = 4
			Kind          = 'tab'
		}
	}
}

Invoke-Formatter -ScriptDefinition $code -Settings $settings
Expected behavior

One level of indentation for the body, matching the opener line, and the closing })] at the opener's level:

[ArgumentCompleter({
	Param($commandName, $parameterName)
	$validKeys = @('a', 'b')
	$validKeys | Where-Object { $_ } | ForEach-Object { "$_=" }
})]
Actual behavior

Two levels for the body and one level for the closing })]:

[ArgumentCompleter({
		Param($commandName, $parameterName)
		$validKeys = @('a', 'b')
		$validKeys | Where-Object { $_ } | ForEach-Object { "$_=" }
	})]

The same happens with spaces (Kind = 'space', IndentationSize = 4) and with NewLineAfterOpenBrace/PSPlaceOpenBrace not involved at all — PSUseConsistentIndentation alone is enough.

It also affects other attribute forms that open a scriptblock, e.g.

[ValidateScript({
	$_ -gt 0
})]

and the buggy output is not idempotent in a harmless way: because the opener line itself is not re-indented, re-running the formatter keeps the body at the wrong level, and any tool that re-applies formatting sees a permanent diff.

Environment
PSVersion: 7.6.6
PSEdition: Core
OS: Windows 11 (26100)
PSScriptAnalyzer: 1.25.0

Also reproduced with PSScriptAnalyzer 1.24.0 (bundled with ms-vscode.powershell 2025.4.0) and with Windows PowerShell 5.1 / 26100 (Desktop edition), so it is not host- or edition-specific.

Related
  • #2159 — PSUseConsistentIndentation: Hashtable inside method call gets double-indented (same LParen+XCurly double count; fixed by #2173)
  • #2173 — the fix that pops the level when an opener is not the last unclosed opener on the line; it does not catch the attribute opening ({
  • #1168 — "Formatting .where and .foreach methods is incorrect" (adjacent-opener family)

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

Start by running the supplied Invoke-Formatter reproduction with PSUseConsistentIndentation enabled, then trace the indentation handling for the PSUseConsistentIndentation rule when an attribute or type literal opens a scriptblock. Add a regression test covering [ArgumentCompleter({ ... })] and [ValidateScript({ ... })], verifying one body indentation level and the closing })] at the opener's level.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, powershell
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.