csharpier icon indicating copy to clipboard operation
csharpier copied to clipboard

Formatting of object initializers depend on max line length

Open albert-tk25 opened this issue 11 months ago • 4 comments

Currently properties for object initializers are put on different lines of the expression length is greater than max line length and on same line otherwise, which looks strange.

Input:

var sort = new CustomSort() 
{ 
    Order = OrderBy.Ascending, 
    Field = CustomSortBy.Title 
};

var sort = new CustomSort() 
{ 
    Order = OrderBy.Ascending, 
    Field = CustomSortBy.Title,
    AnotherField =  CustomSortBy.Name
};

Output:

var sort = new CustomSort() { Order = OrderBy.Ascending, Field = CustomSortBy.Title };

var sort = new CustomSort() 
{ 
    Order = OrderBy.Ascending, 
    Field = CustomSortBy.Title,
    AnotherField =  CustomSortBy.Name
};

Expected behavior:

var sort = new CustomSort() 
{ 
    Order = OrderBy.Ascending, 
    Field = CustomSortBy.Title 
};

var sort = new CustomSort() 
{ 
    Order = OrderBy.Ascending, 
    Field = CustomSortBy.Title,
    AnotherField =  CustomSortBy.Name
};

albert-tk25 avatar Jan 14 '25 13:01 albert-tk25

The current rules are (I may be forgetting some extra edge cases that break)

  • If an initializer has three or more properties break
  • If the initializer is longer than the max width break

If all initializers break, then code like the following is going to take up a lot of vertical space

var point1 = new Point { X = 1, Y = 1 };
var point2 = new Point { X = 2, Y = 2 };
var point3 = new Point { X = 3, Y = 3 };

// vs

var point1 = new Point
{
    X = 1,
    Y = 1,
};
var point2 = new Point
{
    X = 2,
    Y = 2,
};
var point3 = new Point
{
    X = 3,
    Y = 3,
};

The current rules do break things like this, which is also not the greatest

var point1 = new Point { X = 1, Y = 1, Z = 1 };

// is output as

var point1 = new Point
{
    X = 1,
    Y = 1,
    Z = 1,
};

I was expermimenting with prettier, and it looks like they take the existence of a break after the brace into account, meaning

// input
var point1 = new Point { X = 1, Y = 1, Z = 1 };
var point2 = new Point { X = 2, Y = 2 };
var point3 = new Point {
X = 3, Y = 3, Z = 3 };
var point2 = new Point {
X = 2, Y = 2 };

// output is
var point1 = new Point { X = 1, Y = 1, Z = 1 };
var point2 = new Point { X = 2, Y = 2 };
var point3 = new Point
{
    X = 3,
    Y = 3,
    Z = 3,
};
var point2 = new Point
{
    X = 2,
    Y = 2,
};

I'm not opposed to it, but it does feel a bit wrong to be required to "give a hint" to csharpier about how you want something to be formatted. It would be useful for me to track down any discussions from when prettier implemented that logic.

The only other thing I can think of is trying to define what a "simple" initializer looks like vs a "complex" one. Where the 3 property Point in my example would be considered simple and only break if over the max width, and the 2 property initializer in your example is complex and will break even below the max width.

belav avatar Jan 17 '25 16:01 belav

It is a problem that's hard to solve apparently, see https://prettier.io/docs/en/rationale#multi-line-objects. I like their workaround for this problem, and I'd like to see it implemented in CSharpier.

LoremFooBar avatar Jan 20 '25 17:01 LoremFooBar

There doesn't seem to be a good solution to this. I would not be a fun of the Prettier approach.

For me a potential improvement would be to break also in case when the value assigned to one of the properties is not a literal (like in my case)

albert-tk25 avatar Feb 15 '25 17:02 albert-tk25