Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve NormalizeWhitespace for object initializers #65249

Merged
merged 21 commits into from
Nov 18, 2022

Conversation

DoctorKrolic
Copy link
Contributor

Fixes: #61204

Normilize whitespace for object/collection initializers by putting every new statement in a separate line:

new SomeClass
{
    A = 1,
    B = 2,
    C = new OtherClass()
    {
        D = "hello there"
    }
}

new int[]
{
    1,
    2,
    3,
}

new Dictionary<int, int>
{
    [0] = 1,
    [1] = 2,
    [2] = 3
}

When initializer is inside string interpolation (doesn't matter verbatim or not), the behaviour changes to nicely format initializer in a single line, e.g.:

$"{new SomeClass { A = 1, B = 2, C = new OtherClass() { D = "hello there" } }}"
$"{new int[] { 1, 2, 3 }}"
$"{new Dictionary<int, int> { [0] = 1, [1] = 2, [2] = 3 }}"

@DoctorKrolic DoctorKrolic requested a review from a team as a code owner November 6, 2022 18:24
@ghost ghost added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Nov 6, 2022
@CyrusNajmabadi
Copy link
Member

When initializer is inside string interpolation (doesn't matter verbatim or not), the behaviour changes to nicely format initializer in a single line, e.g.:

note: i added support for multiline expressions in string interpolations to the language.

@DoctorKrolic
Copy link
Contributor Author

note: i added support for multiline expressions in string interpolations to the language.

Yes, but of 2 available variants I think the single-line looks better. Let me show:

How it was before this PR:

$"{new SomeClass
{A = 1, B = 2}}"

How it could be if I didn't explicitly chenged it:

$"{new SomeClass
{
    A = 1,
    B = 2
}}"

How it is currently done:

$"{new SomeClass { A = 1, B = 2 }}"

Considering that it is very unlikely that an interpolated string would contain large object initializer, I think, the single-line approach looks better. Even if it can be not very obvious from the case I've shown above, just add some content and it will become a mess:

$"This is the first part of an interpolation string {new SomeClass
{
    A = 1,
    B = 2
}}. This is the second part if an interpolation string. And as this constant part grows the start of object declaration is more and more separated from its initializer: {new SomeOtherClass
{
    C = "whos this initializer is?)"
    D = 3.14m
}}"

The NormalizeWhitespace isn't a formatting engine after all, it just provides minimal functionality for code to be readable

DoctorKrolic and others added 2 commits November 6, 2022 21:48
Co-authored-by: CyrusNajmabadi <cyrus.najmabadi@gmail.com>
Co-authored-by: CyrusNajmabadi <cyrus.najmabadi@gmail.com>
@CyrusNajmabadi
Copy link
Member

Considering that it is very unlikely that an interpolated string would contain large object initializer, I think, the single-line approach looks better.

Please doc the code to include this explanation. Thanks!

@CyrusNajmabadi
Copy link
Member

Looked through, but i didn't see a test with nested initializers. e.g. new X { A = { ... } }. Did i miss it?

@DoctorKrolic
Copy link
Contributor Author

In the latest commit there is a test with many nested initializers. Isn't it what you want to see?

@CyrusNajmabadi
Copy link
Member

could you point me to it?

@DoctorKrolic
Copy link
Contributor Author

@CyrusNajmabadi
Copy link
Member

There are a bunch of nested different initializers and anonymous object declarations

I don't see the code construct I was calling out in my request though. I don't see simple nested initializers.

@DoctorKrolic
Copy link
Contributor Author

Can I have a review here?

@jcouv jcouv self-assigned this Nov 11, 2022
Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 15)

@jcouv
Copy link
Member

jcouv commented Nov 14, 2022

@dotnet/roslyn-compiler for second review. Thanks

@DoctorKrolic
Copy link
Contributor Author

Still want a second review) And someone also restart failed CI pls

@RikkiGibson RikkiGibson self-assigned this Nov 16, 2022
@RikkiGibson
Copy link
Contributor

@dotnet/roslyn-ide for sign-off on workspaces tests change

Copy link
Contributor

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM just had some minor comments to address first.

=> node is AccessorListSyntax accessorList &&
node.Parent is PropertyDeclarationSyntax property &&
property.Initializer != null;
=> node is AccessorListSyntax { Parent: PropertyDeclarationSyntax { Initializer: not null } };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, please try to keep style-only changes like these to a minimum in the compiler layer. No need to undo any such changes already in the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I only removed accessorList as it was never used, but Cyrus then suggested to use pattern matching and I accepted it. Ok, will try to avoid such changes later.

Also I have an idea to convert all multiline strings is SyntaxNormalizerTests to raw form in order to make them actually readable in a separate PR after this one is merged. Would such change be considered as style-only and rejected?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I have an idea to convert all multiline strings is SyntaxNormalizerTests to raw form in order to make them actually readable in a separate PR after this one is merged

I personally would be fine with this. @jcouv as long as you're also fine with this, I'd say @DoctorKrolic can go ahead and open a subsequent PR to convert the @"" strings in that file to """ """.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would be fine with this. they're tests, so there's no risk. and test-strings were a primary use case for raw-strings :)

src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs Outdated Show resolved Hide resolved
src/Compilers/CSharp/Portable/Syntax/SyntaxNormalizer.cs Outdated Show resolved Hide resolved
public void TestNormalizeObjectInitializer()
{
TestNormalizeExpression(
"new{}", """
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, where did all these baselines come from? Were they taken from somewhere or was there some method used which lead to producing all of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I produced them manually myself. There is some logic behind almost every one. Here the pattern is the following:

  1. Empty {}
  2. Add simple properties
  3. Add , after the last simple property (I actually caught several bugs when decided to add these tests)
  4. Repeat this patter for new{...} -> new SomeClass{...} -> new SomeClass(){...}
  5. Add nested complex property with its own initializer/anonymous object declaration
  6. Repeat the whole pattern 1-4 for this nested property
  7. Add another double-nested property and repeat 1-4 for it

Similar with with initializers tests

Collection/array initializers are not that much structured. But there are some interesting cases there as well, e.g. new[] { 2 + 2 * 2 }. Here the first parent of first 2is not an initializer, but a binary expression, while in a simpler case like new [] { 2 } the parent is an initializer, so I had to adopt code for that.

Probably the only test that is not actually structured is the last one in TestNormalizeNestedInitializers, where I just produced some weird pretty unrealistic scenario with a bunch on different nested initializers/anonymous object creations and I managed to find some bugs using it as well)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM Thanks (iteration 20)

@DoctorKrolic
Copy link
Contributor Author

@RikkiGibson I resolved merge conflicts, just need an approval from you

@jcouv jcouv merged commit 1eaab62 into dotnet:main Nov 18, 2022
@ghost ghost added this to the Next milestone Nov 18, 2022
@RikkiGibson
Copy link
Contributor

Thanks @DoctorKrolic for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve NormalizeWhitespace for object initializers
5 participants