Skip to content

Formatting breaks pattern needed for null-conditonal operators by adding spaces #2066

Description

@ThaDaVos

Prerequisites

  • I have written a descriptive issue title.
  • I have searched all open and closed issues to ensure it has not already been reported.
  • I have read the troubleshooting guide.
  • I am sure this issue is with the extension itself and does not reproduce in a standalone PowerShell instance.
  • I have verified that I am using the latest version of Visual Studio Code and the PowerShell extension.
  • If this is a security issue, I have read the security issue reporting guidance.

Summary

I've been debugging my script for hours, and finally figured out that the format I did last time, broke my null-conditional operators as it adds spaces between the braces, for example:

$c = $a.{b}?.Trim();

Turns into

$c = $a.{ b }?.Trim();

Causing $c to be empty/null

I fixed this manually in my script but noticed that formatting changes it back - I think this is a wrong format for this specific use-case as it breaks these operators.

Information about the operator: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.4#null-conditional-operators--and-

PowerShell Version

PS> $PSVersionTable; $Host

Name                           Value
----                           -----
PSVersion                      7.4.5
PSEdition                      Core
GitCommitId                    7.4.5
OS                             Microsoft Windows 10.0.27695
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Name             : Visual Studio Code Host
Version          : 2024.2.2
InstanceId       : 7973e8e2-cbb7-4e99-98e3-9169ba600aa3
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : nl-NL
CurrentUICulture : nl-NL
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled  : True
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

Visual Studio Code Version

PS> code --version
1.93.1
38c31bc77e0dd6ae88a4e9cc93428cc27a56ba40
x64

Extension Version

PS> code --list-extensions --show-versions | Select-String powershell

ms-vscode.powershell@2024.2.2

Steps to Reproduce

  1. Write code as follows:
$c = $a.{b}?.Trim();
  1. Use document format in vscode

Visuals

No response

Logs

No response

Activity

  1. andyleejordan commented on Feb 25, 2025

    @andyleejordan
    Member

    Hi there, this looks to me like a potential bug in PSScriptAnalyzer, specifically the part of the UseConsistentWhitespace rule that looks for spaces within braces: https://github.com/PowerShell/PSScriptAnalyzer/blob/main/docs/Rules/UseConsistentWhitespace.md#checkinnerbrace-bool-default-value-is-true

    I'm going to transfer to that repo, it would be helpful if you can confirm by enabling/disabling that rule.

  2. liamjpeters commented on Nov 3, 2025

    @liamjpeters
    Contributor

    It's definitely the CheckInnerBrace part of UseConsistentWhitespace. Turning it on and off for a simple case, $a.{Prop}, confirms it.

    The curly-braced member access (not sure on any official naming) isn't parsed into any special AST node structure, and the token stream is just basically VARIABLE, DOT, LCURLY, IDENTIFIER, RCURLY.

    Accounting for this would be a case of looking for the braces that are used in this way and ignoring them when it comes time to formatting.

    It's made complex by the below being valid:

    $a = [PSCustomObject]@{
        'Prop' = 5
        '{Prop}' = 'Value'
        '$a.{Prop}' = 'Value2-electric-boogaloo'
    }
    $a.{{Prop}}     # prints 'Value'
    $a.{$a.{Prop}}  # prints 'Value2-electric-boogaloo'

    So nothing contained within the curly-braced member access braces should be formatted at all as it breaks it.

    One strategy could be:

    • Keep a list of excluded extents/ranges.
    • Iterate over tokens, for each dot token:
      • Check the dots extent isn't in an excluded extent/range
      • Check the dot has a preceding significant token (ignoring comments and skipping on line continuations and new lines) with no whitespace or new-line gaps. Variables, identifiers, literals, braces - all appear to be acceptable.
      • Check the dot's following significant token (ignoring comments, new lines, line continuations) is a LCURLY.
      • Find the matching RCURLY for this LCURLY.
      • Add an extent/range to the ignored list that goes from the LCURLY to the RCURLY.
    • When considering a brace for a whitespace addition, check first that it's not within an ignored extent/range.

    Andy Jordan (@andyleejordan) - Does that sound like a reasonable approach or am I over-complicating it and missing something simpler?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions