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

method_chaining_indentation rule doesn't apply correctly the first time when combined with no_space_around_double_colon #234

Closed
shengslogar opened this issue Nov 30, 2023 · 23 comments

Comments

@shengslogar
Copy link

shengslogar commented Nov 30, 2023

Pint Version

1.13.6

PHP Version

8.2.12

Description

Running into an interesting corner case where Pint incorrectly formats a file and subsequently passes future Pint --test checks in the same environment.

The two rules at play here, per Pint, are method_chaining_indentation and no_space_around_double_colon.

Without looking under the hood of Pint, seems like it might be caching results, as renaming the affected file and running Pint again corrects the formatting issue.

Steps To Reproduce

1. Create Test.php:

<?php

$formattedIncorrectly = Foo::bar
    ::baz()
    ->where('id', 1)
    ->firstOrFail();

$formattedCorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();

2. Run ./vendor/bin/pint:

  ................................................................................................................✓.........................................................
  .................

  ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel  
    FIXED   ................................................................................................................................. 187 files, 1 style issue fixed  
  ✓ app/Http/Controllers/TestFile.php                                                                              method_chaining_indentation, no_space_around_double_colon  

(Note the 186 other files are part of an existing project where this issue surfaced.)

3. Inspect the contents of Test.php:

Note the $formattedIncorrectly block has extra spacing behind ->where and ->firstOrFail.

<?php

$formattedIncorrectly = Foo::bar::baz()
        ->where('id', 1)
        ->firstOrFail();

$formattedCorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();

4. ./vendor/bin/pint --test -vvv doesn't pick up on any issues:


Box Requirements Checker
========================

> Using PHP 8.2.12
> PHP is using the following php.ini file:
  /opt/homebrew/etc/php/8.2/php.ini

> Checking Box requirements:
  ✔ The application requires a version matching "^8.1.0".
  ✔ The application requires the extension "zlib".
  ✔ The application requires the extension "json".
  ✔ The application requires the extension "mbstring".
  ✔ The application requires the extension "tokenizer".
  ✔ The application requires the extension "xml".
  
                                                                                                                                                                              
 [OK] Your system is ready to run the application.                                                                                                                            
                                                                                                                                                                              


  ..........................................................................................................................................................................
  .................

  ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel  
    PASS   ....................................................................................................................................................... 187 files  

5. Rename Test.php to AnotherTest.php
Issues are now detected by ./vendor/bin/pint --test -vvv, and running ./vendor/bin/pint formats the file as expected.

Box Requirements Checker
========================

> Using PHP 8.2.12
> PHP is using the following php.ini file:
  /opt/homebrew/etc/php/8.2/php.ini

> Checking Box requirements:
  ✔ The application requires a version matching "^8.1.0".
  ✔ The application requires the extension "zlib".
  ✔ The application requires the extension "json".
  ✔ The application requires the extension "mbstring".
  ✔ The application requires the extension "tokenizer".
  ✔ The application requires the extension "xml".
  
                                                                                                                                                                              
 [OK] Your system is ready to run the application.                                                                                                                            
                                                                                                                                                                              


  ...................................................................................................................⨯......................................................
  .................

  ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel  
    FAIL   ........................................................................................................................................ 187 files, 1 style issue  
  ⨯ app/Http/Controllers/AnotherTest.php                                                                                                         method_chaining_indentation  
  @@ -1,8 +1,8 @@
   <?php
   
   $formattedIncorrectly = Foo::bar::baz()
  -        ->where('id', 1)
  -        ->firstOrFail();
  +    ->where('id', 1)
  +    ->firstOrFail();
   
   $formattedCorrectly = Foo::bar::baz()
       ->where('id', 1)

The final corrected file looks like:

<?php

$formattedIncorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();

$formattedCorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();
Copy link

Thank you for reporting this issue!

As Laravel is an open source project, we rely on the community to help us diagnose and fix issues as it is not possible to research and fix every issue reported to us via GitHub.

If possible, please make a pull request fixing the issue you have described, along with corresponding tests. All pull requests are promptly reviewed by the Laravel team.

Thank you!

@mho22
Copy link

mho22 commented Dec 26, 2023

It seems like it is something linked with the laravel preset.

I tried the same test with this pint.json configuration :

 {
    "preset" : "psr12",
    "rules" : {
        "method_chaining_indentation" : true
    }
}

And the behavior is as expected.

<?php

$formattedIncorrectly = Foo::bar
    ::baz()
    ->where('id', 1)
    ->firstOrFail();

$formattedCorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();
vendor/bin/pint test.php --config pint.json
<?php

$formattedIncorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();

$formattedCorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();

@driesvints
Copy link
Member

Hi all, we'd appreciate a PR here. If nothing can be done then this is something that should go into the docs.

@mho22
Copy link

mho22 commented Jan 5, 2024

@driesvints I'll look into it.

@shengslogar
Copy link
Author

I'm afraid I don't know enough about the internals of Pint to be very helpful here. Apart from the LaravelPhpdocAlignmentFixer rule, is it primarily a thin wrapper around PHP CS Fixer or is it doing any further post-processing?

One naive approach here might be to compare the rules of the laravel and psr12 presets and run the offending code through, using process of elimination. Needless to say, there are a TON of rules. Here's where I've gotten so far:

Rules laravel has that differ from psr12 (we can already eliminate method_chaining_indentation per @mho22's comment):

'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => ['default' => 'single_space'],
'blank_line_before_statement' => ['statements' => ['continue', 'return']],
'braces_position' => ['control_structures_opening_brace' => 'same_line', 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_functions_opening_brace' => 'same_line', 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end', 'allow_single_line_empty_anonymous_classes' => false, 'allow_single_line_anonymous_functions' => false],
'cast_spaces' => true,
'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none']],
'class_definition' => ['multi_line_extends_each_single_line' => true, 'single_item_single_line' => true, 'single_line' => true],
'clean_namespace' => true,
'concat_space' => ['spacing' => 'none'],
'constant_case' => ['case' => 'lower'],
'control_structure_continuation_position' => ['position' => 'same_line'],
'declare_parentheses' => true,
'fully_qualified_strict_types' => true,
'general_phpdoc_tag_rename' => true,
'heredoc_to_nowdoc' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'integer_literal_case' => true,
'lambda_not_used_import' => true,
'linebreak_after_opening_tag' => true,
'list_syntax' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'method_argument_space' => ['on_multiline' => 'ignore'],
'method_chaining_indentation' => true,
'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
'native_function_casing' => true,
'native_type_declaration_casing' => true,
'no_alias_functions' => true,
'no_alias_language_construct_call' => true,
'no_alternative_syntax' => true,
'no_binary_string' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => ['tokens' => ['extra', 'throw', 'use']],
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ['use' => 'echo'],
'no_multiline_whitespace_around_double_arrow' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_around_offset' => ['positions' => ['inside', 'outside']],
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true],
'no_trailing_comma_in_singleline' => true,
'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield']],
'no_unneeded_braces' => true,
'no_unreachable_default_argument_value' => true,
'no_unset_cast' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'normalize_index_brace' => true,
'not_operator_with_successor_space' => true,
'nullable_type_declaration' => true,
'nullable_type_declaration_for_default_null_value' => true,
'object_operator_without_whitespace' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']],
'ordered_interfaces' => true,
'ordered_traits' => true,
'phpdoc_indent' => true,
'phpdoc_inline_tag_normalizer' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => ['order' => ['param', 'return', 'throws']],
'phpdoc_scalar' => true,
'phpdoc_separation' => ['groups' => [['deprecated', 'link', 'see', 'since'], ['author', 'copyright', 'license'], ['category', 'package', 'subpackage'], ['property', 'property-read', 'property-write'], ['param', 'return']]],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => false,
'phpdoc_tag_type' => ['tags' => ['inheritdoc' => 'inline']],
'phpdoc_to_comment' => false,
'phpdoc_trim' => true,
'phpdoc_types' => true,
'phpdoc_var_without_name' => true,
'psr_autoloading' => false,
'return_type_declaration' => ['space_before' => 'none'],
'self_accessor' => false,
'self_static_accessor' => true,
'simplified_null_return' => false,
'single_class_element_per_statement' => ['elements' => ['const', 'property']],
'single_import_per_statement' => true,
'single_line_comment_style' => ['comment_types' => ['hash']],
'single_quote' => true,
'single_space_around_construct' => true,
'space_after_semicolon' => true,
'standardize_not_equals' => true,
'trailing_comma_in_multiline' => ['elements' => ['arrays']],
'trim_array_spaces' => true,
'type_declaration_spaces' => true,
'types_spaces' => true,
'unary_operator_spaces' => true,
'visibility_required' => ['elements' => ['method', 'property']],
'whitespace_after_comma_in_array' => true,
'yoda_style' => ['always_move_variable' => false, 'equal' => false, 'identical' => false, 'less_and_greater' => false],

// Laravel
'Laravel/laravel_phpdoc_alignment' => true,

psr12

// @PSR1
// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/src/RuleSet/Sets/PSR1Set.php
'encoding' => true,
'full_opening_tag' => true,
// END @PSR1

// @PSR2
// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/src/RuleSet/Sets/PSR2Set.php
'blank_line_after_namespace' => true,
// 'braces_position' => true,
// 'class_definition' => true,
'constant_case' => true,
'control_structure_braces' => true,
'control_structure_continuation_position' => true,
'elseif' => true,
'function_declaration' => true,
'indentation_type' => true,
'line_ending' => true,
'lowercase_keywords' => true,
'method_argument_space' => [
    'attribute_placement' => 'ignore',
    'on_multiline' => 'ensure_fully_multiline',
],
'no_break_comment' => true,
'no_closing_tag' => true,
'no_multiple_statements_per_line' => true,
'no_space_around_double_colon' => true,
'no_spaces_after_function_name' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'single_blank_line_at_eof' => true,
'single_class_element_per_statement' => [
    'elements' => [
        'property',
    ],
],
// 'single_import_per_statement' => true,
'single_line_after_imports' => true,
'spaces_inside_parentheses' => true,
'statement_indentation' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
// 'visibility_required' => ['elements' => ['method', 'property']],
// END @PSR2

// @PSR12
// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/src/RuleSet/Sets/PSR12Set.php
'binary_operator_spaces' => [
    'default' => 'at_least_single_space',
],
'blank_line_after_opening_tag' => true,
'blank_line_between_import_groups' => true,
'blank_lines_before_namespace' => true,
'braces_position' => [
    'allow_single_line_empty_anonymous_classes' => true,
],
'class_definition' => [
    'inline_constructor_arguments' => false, // handled by method_argument_space fixer
    'space_before_parenthesis' => true, // defined in PSR12 ¶8. Anonymous Classes
],
'compact_nullable_type_declaration' => true,
'declare_equal_normalize' => true,
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'new_with_parentheses' => true,
'no_blank_lines_after_class_opening' => true,
'no_leading_import_slash' => true,
'no_whitespace_in_blank_line' => true,
'ordered_class_elements' => [
    'order' => [
        'use_trait',
    ],
],
'ordered_imports' => [
    'imports_order' => [
        'class',
        'function',
        'const',
    ],
    'sort_algorithm' => 'none',
],
'return_type_declaration' => true,
'short_scalar_cast' => true,
'single_import_per_statement' => ['group_to_single_imports' => false],
'single_trait_insert_per_statement' => true,
'ternary_operator_spaces' => true,
'unary_operator_spaces' => [
    'only_dec_inc' => true,
],
'visibility_required' => true,
// END @PSR12

// Laravel @PSR12
// https://github.com/laravel/pint/blob/main/resources/presets/psr12.php
'no_unused_imports' => true,
// END Laravel @PSR12

psr12 Flattened

'binary_operator_spaces' => [
    'default' => 'at_least_single_space',
],
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => true,
'blank_line_between_import_groups' => true,
'blank_lines_before_namespace' => true,
'braces_position' => [
    'allow_single_line_empty_anonymous_classes' => true,
],
'class_definition' => [
    'inline_constructor_arguments' => false,
    'space_before_parenthesis' => true,
],
'compact_nullable_type_declaration' => true,
'constant_case' => true,
'control_structure_braces' => true,
'control_structure_continuation_position' => true,
'declare_equal_normalize' => true,
'elseif' => true,
'encoding' => true,
'full_opening_tag' => true,
'function_declaration' => true,
'indentation_type' => true,
'line_ending' => true,
'lowercase_cast' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
'method_argument_space' => [
    'attribute_placement' => 'ignore',
    'on_multiline' => 'ensure_fully_multiline',
],
'new_with_parentheses' => true,
'no_blank_lines_after_class_opening' => true,
'no_break_comment' => true,
'no_closing_tag' => true,
'no_leading_import_slash' => true,
'no_multiple_statements_per_line' => true,
'no_space_around_double_colon' => true,
'no_spaces_after_function_name' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_unused_imports' => true,
'no_whitespace_in_blank_line' => true,
'ordered_class_elements' => [
    'order' => [
        'use_trait',
    ],
],
'ordered_imports' => [
    'imports_order' => [
        'class',
        'function',
        'const',
    ],
    'sort_algorithm' => 'none',
],
'return_type_declaration' => true,
'short_scalar_cast' => true,
'single_blank_line_at_eof' => true,
'single_class_element_per_statement' => [
    'elements' => [
        'property',
    ],
],
'single_import_per_statement' => ['group_to_single_imports' => false],
'single_line_after_imports' => true,
'single_trait_insert_per_statement' => true,
'spaces_inside_parentheses' => true,
'statement_indentation' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'ternary_operator_spaces' => true,
'unary_operator_spaces' => [
    'only_dec_inc' => true,
],
'visibility_required' => true,

@mho22
Copy link

mho22 commented Jan 5, 2024

I copy pasted all the rules from laravel preset and added them in my pint.json configuration with psr-12 preset. And I don't have errors.

pint.json


{
    "preset" : "psr12",
    "rules" : {
        "array_indentation" : true,
        "array_syntax" : { "syntax" : "short" },
        "binary_operator_spaces" : { "default" : "single_space" },
        "blank_line_after_namespace" : true,
        "blank_line_after_opening_tag" : true,
        "blank_line_before_statement" : { "statements" : [ "continue", "return" ] },
        "blank_line_between_import_groups" : true,
        "blank_lines_before_namespace" : true,
        "braces_position" : { "control_structures_opening_brace" : "same_line", "functions_opening_brace" : "next_line_unless_newline_at_signature_end", "anonymous_functions_opening_brace" : "same_line", "classes_opening_brace" : "next_line_unless_newline_at_signature_end", "anonymous_classes_opening_brace" : "next_line_unless_newline_at_signature_end", "allow_single_line_empty_anonymous_classes" : false, "allow_single_line_anonymous_functions" : false },
        "cast_spaces" : true,
        "class_attributes_separation" : { "elements" : { "const" : "one", "method" : "one", "property" : "one", "trait_import" : "none" } },
        "class_definition" : { "multi_line_extends_each_single_line" : true, "single_item_single_line" : true, "single_line" : true },
        "clean_namespace" : true,
        "compact_nullable_type_declaration" : true,
        "concat_space" : { "spacing" : "none" },
        "constant_case" : { "case" : "lower" },
        "control_structure_braces" : true,
        "control_structure_continuation_position" : { "position" : "same_line" },
        "declare_equal_normalize" : true,
        "declare_parentheses" : true,
        "elseif" : true,
        "encoding" : true,
        "full_opening_tag" : true,
        "fully_qualified_strict_types" : true,
        "function_declaration" : true,
        "general_phpdoc_tag_rename" : true,
        "heredoc_to_nowdoc" : true,
        "include" : true,
        "increment_style" : {"style" : "post"},
        "indentation_type" : true,
        "integer_literal_case" : true,
        "lambda_not_used_import" : true,
        "line_ending" : true,
        "linebreak_after_opening_tag" : true,
        "list_syntax" : true,
        "lowercase_cast" : true,
        "lowercase_keywords" : true,
        "lowercase_static_reference" : true,
        "magic_constant_casing" : true,
        "magic_method_casing" : true,
        "method_argument_space" : { "on_multiline" : "ignore" },
        "method_chaining_indentation" : true,
        "multiline_whitespace_before_semicolons" : { "strategy" : "no_multi_line" },
        "native_function_casing" : true,
        "native_type_declaration_casing" : true,
        "no_alias_functions" : true,
        "no_alias_language_construct_call" : true,
        "no_alternative_syntax" : true,
        "no_binary_string" : true,
        "no_blank_lines_after_class_opening" : true,
        "no_blank_lines_after_phpdoc" : true,
        "no_closing_tag" : true,
        "no_empty_phpdoc" : true,
        "no_empty_statement" : true,
        "no_extra_blank_lines" : { "tokens" : [ "extra", "throw", "use" ] },
        "no_leading_import_slash" : true,
        "no_leading_namespace_whitespace" : true,
        "no_mixed_echo_print" : { "use" : "echo" },
        "no_multiline_whitespace_around_double_arrow" : true,
        "no_multiple_statements_per_line" : true,
        "no_short_bool_cast" : true,
        "no_singleline_whitespace_before_semicolons" : true,
        "no_space_around_double_colon" : true,
        "no_spaces_after_function_name" : true,
        "no_spaces_around_offset" : { "positions" : [ "inside", "outside" ] },
        "no_superfluous_phpdoc_tags" : { "allow_mixed" : true, "allow_unused_params" : true },
        "no_trailing_comma_in_singleline" : true,
        "no_trailing_whitespace" : true,
        "no_trailing_whitespace_in_comment" : true,
        "no_unneeded_control_parentheses" : { "statements" : [ "break", "clone", "continue", "echo_print", "return", "switch_case", "yield" ] },
        "no_unneeded_braces" : true,
        "no_unreachable_default_argument_value" : true,
        "no_unset_cast" : true,
        "no_unused_imports" : true,
        "no_useless_return" : true,
        "no_whitespace_before_comma_in_array" : true,
        "no_whitespace_in_blank_line" : true,
        "normalize_index_brace" : true,
        "not_operator_with_successor_space" : true,
        "nullable_type_declaration" : true,
        "nullable_type_declaration_for_default_null_value" : true,
        "object_operator_without_whitespace" : true,
        "ordered_imports" : {"sort_algorithm" : "alpha", "imports_order" : [ "const", "class", "function" ] },
        "ordered_interfaces" : true,
        "ordered_traits" : true,
        "phpdoc_indent" : true,
        "phpdoc_inline_tag_normalizer" : true,
        "phpdoc_no_access" : true,
        "phpdoc_no_package" : true,
        "phpdoc_no_useless_inheritdoc" : true,
        "phpdoc_order" : { "order" : [ "param", "return", "throws" ] },
        "phpdoc_scalar" : true,
        "phpdoc_separation" : { "groups" : [ [ "deprecated", "link", "see", "since"], [ "author", "copyright", "license"], [ "category", "package", "subpackage"], [ "property", "property-read", "property-write"], [ "param", "return" ] ] },
        "phpdoc_single_line_var_spacing" : true,
        "phpdoc_summary" : false,
        "phpdoc_tag_type" : { "tags" : { "inheritdoc" : "inline" } },
        "phpdoc_to_comment" : false,
        "phpdoc_trim" : true,
        "phpdoc_types" : true,
        "phpdoc_var_without_name" : true,
        "psr_autoloading" : false,
        "return_type_declaration" : {"space_before" : "none"},
        "self_accessor" : false,
        "self_static_accessor" : true,
        "short_scalar_cast" : true,
        "simplified_null_return" : false,
        "single_blank_line_at_eof" : true,
        "single_class_element_per_statement" : { "elements" : [ "const", "property" ] },
        "single_import_per_statement" : true,
        "single_line_after_imports" : true,
        "single_line_comment_style" : { "comment_types" : [ "hash" ] },
        "single_quote" : true,
        "single_space_around_construct" : true,
        "space_after_semicolon" : true,
        "spaces_inside_parentheses" : true,
        "standardize_not_equals" : true,
        "statement_indentation" : true,
        "switch_case_semicolon_to_colon" : true,
        "switch_case_space" : true,
        "ternary_operator_spaces" : true,
        "trailing_comma_in_multiline" : { "elements" : [ "arrays" ] },
        "trim_array_spaces" : true,
        "type_declaration_spaces" : true,
        "types_spaces" : true,
        "unary_operator_spaces" : true,
        "visibility_required" : { "elements" : [ "method", "property" ] },
        "whitespace_after_comma_in_array" : true,
        "yoda_style" : { "always_move_variable" : false,"equal" : false, "identical" : false, "less_and_greater" : false },
        "Laravel/laravel_phpdoc_alignment" : true
    }
}

This makes the problem even more unsettling.

@shengslogar
Copy link
Author

Can repro what you pasted successfully.

I tried comparing psr12 to laravel again, this time looking to see what additional rules psr12 has that laravel doesn't, and ended up with the following:

{
  "preset": "laravel",
  "rules": {
    "new_with_parentheses": true,
    "no_break_comment": true,
    "ordered_class_elements": {
      "order": ["use_trait"]
    },
    "single_trait_insert_per_statement": true
  }
}

This should be functionally the same as the pint.json in your comment, but it still causes the issue as originally stated. Need more coffee... ☕

@shengslogar
Copy link
Author

Triple-checked my last comment.

Here's my work:

$psr12Keys = [
    "binary_operator_spaces",
    "blank_line_after_namespace",
    "blank_line_after_opening_tag",
    "blank_line_between_import_groups",
    "blank_lines_before_namespace",
    "braces_position",
    "class_definition",
    "compact_nullable_type_declaration",
    "constant_case",
    "control_structure_braces",
    "control_structure_continuation_position",
    "declare_equal_normalize",
    "elseif",
    "encoding",
    "full_opening_tag",
    "function_declaration",
    "indentation_type",
    "line_ending",
    "lowercase_cast",
    "lowercase_keywords",
    "lowercase_static_reference",
    "method_argument_space",
    "new_with_parentheses",
    "no_blank_lines_after_class_opening",
    "no_break_comment",
    "no_closing_tag",
    "no_leading_import_slash",
    "no_multiple_statements_per_line",
    "no_space_around_double_colon",
    "no_spaces_after_function_name",
    "no_trailing_whitespace",
    "no_trailing_whitespace_in_comment",
    "no_unused_imports",
    "no_whitespace_in_blank_line",
    "ordered_class_elements",
    "ordered_imports",
    "return_type_declaration",
    "short_scalar_cast",
    "single_blank_line_at_eof",
    "single_class_element_per_statement",
    "single_import_per_statement",
    "single_line_after_imports",
    "single_trait_insert_per_statement",
    "spaces_inside_parentheses",
    "statement_indentation",
    "switch_case_semicolon_to_colon",
    "switch_case_space",
    "ternary_operator_spaces",
    "unary_operator_spaces",
    "visibility_required",
];

$laravelKeys = [
    "array_indentation",
    "array_syntax",
    "binary_operator_spaces",
    "blank_line_after_namespace",
    "blank_line_after_opening_tag",
    "blank_line_before_statement",
    "blank_line_between_import_groups",
    "blank_lines_before_namespace",
    "braces_position",
    "cast_spaces",
    "class_attributes_separation",
    "class_definition",
    "clean_namespace",
    "compact_nullable_type_declaration",
    "concat_space",
    "constant_case",
    "control_structure_braces",
    "control_structure_continuation_position",
    "declare_equal_normalize",
    "declare_parentheses",
    "elseif",
    "encoding",
    "full_opening_tag",
    "fully_qualified_strict_types",
    "function_declaration",
    "general_phpdoc_tag_rename",
    "heredoc_to_nowdoc",
    "include",
    "increment_style",
    "indentation_type",
    "integer_literal_case",
    "lambda_not_used_import",
    "line_ending",
    "linebreak_after_opening_tag",
    "list_syntax",
    "lowercase_cast",
    "lowercase_keywords",
    "lowercase_static_reference",
    "magic_constant_casing",
    "magic_method_casing",
    "method_argument_space",
    "method_chaining_indentation",
    "multiline_whitespace_before_semicolons",
    "native_function_casing",
    "native_type_declaration_casing",
    "no_alias_functions",
    "no_alias_language_construct_call",
    "no_alternative_syntax",
    "no_binary_string",
    "no_blank_lines_after_class_opening",
    "no_blank_lines_after_phpdoc",
    "no_closing_tag",
    "no_empty_phpdoc",
    "no_empty_statement",
    "no_extra_blank_lines",
    "no_leading_import_slash",
    "no_leading_namespace_whitespace",
    "no_mixed_echo_print",
    "no_multiline_whitespace_around_double_arrow",
    "no_multiple_statements_per_line",
    "no_short_bool_cast",
    "no_singleline_whitespace_before_semicolons",
    "no_space_around_double_colon",
    "no_spaces_after_function_name",
    "no_spaces_around_offset",
    "no_superfluous_phpdoc_tags",
    "no_trailing_comma_in_singleline",
    "no_trailing_whitespace",
    "no_trailing_whitespace_in_comment",
    "no_unneeded_control_parentheses",
    "no_unneeded_braces",
    "no_unreachable_default_argument_value",
    "no_unset_cast",
    "no_unused_imports",
    "no_useless_return",
    "no_whitespace_before_comma_in_array",
    "no_whitespace_in_blank_line",
    "normalize_index_brace",
    "not_operator_with_successor_space",
    "nullable_type_declaration",
    "nullable_type_declaration_for_default_null_value",
    "object_operator_without_whitespace",
    "ordered_imports",
    "ordered_interfaces",
    "ordered_traits",
    "phpdoc_indent",
    "phpdoc_inline_tag_normalizer",
    "phpdoc_no_access",
    "phpdoc_no_package",
    "phpdoc_no_useless_inheritdoc",
    "phpdoc_order",
    "phpdoc_scalar",
    "phpdoc_separation",
    "phpdoc_single_line_var_spacing",
    "phpdoc_summary",
    "phpdoc_tag_type",
    "phpdoc_to_comment",
    "phpdoc_trim",
    "phpdoc_types",
    "phpdoc_var_without_name",
    "psr_autoloading",
    "return_type_declaration",
    "self_accessor",
    "self_static_accessor",
    "short_scalar_cast",
    "simplified_null_return",
    "single_blank_line_at_eof",
    "single_class_element_per_statement",
    "single_import_per_statement",
    "single_line_after_imports",
    "single_line_comment_style",
    "single_quote",
    "single_space_around_construct",
    "space_after_semicolon",
    "spaces_inside_parentheses",
    "standardize_not_equals",
    "statement_indentation",
    "switch_case_semicolon_to_colon",
    "switch_case_space",
    "ternary_operator_spaces",
    "trailing_comma_in_multiline",
    "trim_array_spaces",
    "type_declaration_spaces",
    "types_spaces",
    "unary_operator_spaces",
    "visibility_required",
    "whitespace_after_comma_in_array",
    "yoda_style",
    "Laravel/laravel_phpdoc_alignment",
];

array_diff($psr12Keys, $laravelKeys) = [
    22 => "new_with_parentheses",
    24 => "no_break_comment",
    34 => "ordered_class_elements",
    42 => "single_trait_insert_per_statement",
]

Two things to potentially try next:

  1. Run pint.json with all merged rules but with no base preset
  2. Based on results of (1) look at repo to see if there are any special biases around the named laravel preset

@shengslogar
Copy link
Author

Manually building a combined file that should be equivalent to your previous comment that formatted everything correctly still yields the original problematic results. I'm missing something...

manually-merged-psr12-and-laravel-pint.json

{
  "rules": {

    // Rules exclusive to psr12 preset

    "new_with_parentheses": true,
    "no_break_comment": true,
    "ordered_class_elements": {
      "order": ["use_trait"]
    },
    "single_trait_insert_per_statement": true,

    // laravel preset rules

    "array_indentation": true,
    "array_syntax": {
      "syntax": "short"
    },
    "binary_operator_spaces": {
      "default": "single_space"
    },
    "blank_line_after_namespace": true,
    "blank_line_after_opening_tag": true,
    "blank_line_before_statement": {
      "statements": ["continue", "return"]
    },
    "blank_line_between_import_groups": true,
    "blank_lines_before_namespace": true,
    "braces_position": {
      "control_structures_opening_brace": "same_line",
      "functions_opening_brace": "next_line_unless_newline_at_signature_end",
      "anonymous_functions_opening_brace": "same_line",
      "classes_opening_brace": "next_line_unless_newline_at_signature_end",
      "anonymous_classes_opening_brace": "next_line_unless_newline_at_signature_end",
      "allow_single_line_empty_anonymous_classes": false,
      "allow_single_line_anonymous_functions": false
    },
    "cast_spaces": true,
    "class_attributes_separation": {
      "elements": {
        "const": "one",
        "method": "one",
        "property": "one",
        "trait_import": "none"
      }
    },
    "class_definition": {
      "multi_line_extends_each_single_line": true,
      "single_item_single_line": true,
      "single_line": true
    },
    "clean_namespace": true,
    "compact_nullable_type_declaration": true,
    "concat_space": {
      "spacing": "none"
    },
    "constant_case": {
      "case": "lower"
    },
    "control_structure_braces": true,
    "control_structure_continuation_position": {
      "position": "same_line"
    },
    "declare_equal_normalize": true,
    "declare_parentheses": true,
    "elseif": true,
    "encoding": true,
    "full_opening_tag": true,
    "fully_qualified_strict_types": true,
    "function_declaration": true,
    "general_phpdoc_tag_rename": true,
    "heredoc_to_nowdoc": true,
    "include": true,
    "increment_style": {
      "style": "post"
    },
    "indentation_type": true,
    "integer_literal_case": true,
    "lambda_not_used_import": true,
    "line_ending": true,
    "linebreak_after_opening_tag": true,
    "list_syntax": true,
    "lowercase_cast": true,
    "lowercase_keywords": true,
    "lowercase_static_reference": true,
    "magic_constant_casing": true,
    "magic_method_casing": true,
    "method_argument_space": {
      "on_multiline": "ignore"
    },
    "method_chaining_indentation": true,
    "multiline_whitespace_before_semicolons": {
      "strategy": "no_multi_line"
    },
    "native_function_casing": true,
    "native_type_declaration_casing": true,
    "no_alias_functions": true,
    "no_alias_language_construct_call": true,
    "no_alternative_syntax": true,
    "no_binary_string": true,
    "no_blank_lines_after_class_opening": true,
    "no_blank_lines_after_phpdoc": true,
    "no_closing_tag": true,
    "no_empty_phpdoc": true,
    "no_empty_statement": true,
    "no_extra_blank_lines": {
      "tokens": ["extra", "throw", "use"]
    },
    "no_leading_import_slash": true,
    "no_leading_namespace_whitespace": true,
    "no_mixed_echo_print": {
      "use": "echo"
    },
    "no_multiline_whitespace_around_double_arrow": true,
    "no_multiple_statements_per_line": true,
    "no_short_bool_cast": true,
    "no_singleline_whitespace_before_semicolons": true,
    "no_space_around_double_colon": true,
    "no_spaces_after_function_name": true,
    "no_spaces_around_offset": {
      "positions": ["inside", "outside"]
    },
    "no_superfluous_phpdoc_tags": {
      "allow_mixed": true,
      "allow_unused_params": true
    },
    "no_trailing_comma_in_singleline": true,
    "no_trailing_whitespace": true,
    "no_trailing_whitespace_in_comment": true,
    "no_unneeded_control_parentheses": {
      "statements": ["break", "clone", "continue", "echo_print", "return", "switch_case", "yield"]
    },
    "no_unneeded_braces": true,
    "no_unreachable_default_argument_value": true,
    "no_unset_cast": true,
    "no_unused_imports": true,
    "no_useless_return": true,
    "no_whitespace_before_comma_in_array": true,
    "no_whitespace_in_blank_line": true,
    "normalize_index_brace": true,
    "not_operator_with_successor_space": true,
    "nullable_type_declaration": true,
    "nullable_type_declaration_for_default_null_value": true,
    "object_operator_without_whitespace": true,
    "ordered_imports": {
      "sort_algorithm": "alpha",
      "imports_order": ["const", "class", "function"]
    },
    "ordered_interfaces": true,
    "ordered_traits": true,
    "phpdoc_indent": true,
    "phpdoc_inline_tag_normalizer": true,
    "phpdoc_no_access": true,
    "phpdoc_no_package": true,
    "phpdoc_no_useless_inheritdoc": true,
    "phpdoc_order": {
      "order": ["param", "return", "throws"]
    },
    "phpdoc_scalar": true,
    "phpdoc_separation": {
      "groups": [
        ["deprecated", "link", "see", "since"],
        ["author", "copyright", "license"],
        ["category", "package", "subpackage"],
        ["property", "property-read", "property-write"],
        ["param", "return"]
      ]
    },
    "phpdoc_single_line_var_spacing": true,
    "phpdoc_summary": false,
    "phpdoc_tag_type": {
      "tags": {
        "inheritdoc": "inline"
      }
    },
    "phpdoc_to_comment": false,
    "phpdoc_trim": true,
    "phpdoc_types": true,
    "phpdoc_var_without_name": true,
    "psr_autoloading": false,
    "return_type_declaration": {
      "space_before": "none"
    },
    "self_accessor": false,
    "self_static_accessor": true,
    "short_scalar_cast": true,
    "simplified_null_return": false,
    "single_blank_line_at_eof": true,
    "single_class_element_per_statement": {
      "elements": ["const", "property"]
    },
    "single_import_per_statement": true,
    "single_line_after_imports": true,
    "single_line_comment_style": {
      "comment_types": ["hash"]
    },
    "single_quote": true,
    "single_space_around_construct": true,
    "space_after_semicolon": true,
    "spaces_inside_parentheses": true,
    "standardize_not_equals": true,
    "statement_indentation": true,
    "switch_case_semicolon_to_colon": true,
    "switch_case_space": true,
    "ternary_operator_spaces": true,
    "trailing_comma_in_multiline": {
      "elements": ["arrays"]
    },
    "trim_array_spaces": true,
    "type_declaration_spaces": true,
    "types_spaces": true,
    "unary_operator_spaces": true,
    "visibility_required": {
      "elements": ["method", "property"]
    },
    "whitespace_after_comma_in_array": true,
    "yoda_style": {
      "always_move_variable": false,
      "equal": false,
      "identical": false,
      "less_and_greater": false
    },
    "Laravel/laravel_phpdoc_alignment": true
  }
}

@shengslogar
Copy link
Author

Adding { "preset": "psr12" } to the top of the last combined file I just pasted formats everything correctly.

Unless I messed up manually merging the psr12 preset, all signs point to Pint treating laravel differently than other (or no) presets.

@mho22
Copy link

mho22 commented Jan 5, 2024

@shengslogar

I had a doubt regarding the sequence of the two specific rules, the order in which no_space_around_double_colon and method_chaining_indentation should be executed. Initially, it seemed logical that no_space_around_double_colon should be applied before method_chaining_indentation, but the observed behavior was the opposite.

The issue should come from the rule order in the Laravel preset, where they are arranged alphabetically. However, when I tried with a modified resources/presets/laravel.php file in a local forked repository, I achieved the desired outcome, and the rules were executed in the correct order.

@driesvints What's next ? A Pull Request with one rule not in the correct alphabetical order ? Or a Pull Request to add priority in the rules in PHP_CS_FIXER ?

@shengslogar
Copy link
Author

Interesting. So you're saying setting a base preset of psr12 worked because it just happened to promote the no_spaces_around_double_colon rule to the top of the list of rules, running no_spaces_around_double_colon before method_chaining_indentation.

That said — all of PHP CS Fixer's rules are listed alphabetically, suggesting that they intended them to all work together and might instead be their issue?

@mho22
Copy link

mho22 commented Jan 5, 2024

@shengslogar I believe that if PHP-CS-FIXER included a preset with both rules, they might face a similar issue to what we've encountered. However, it appears that method_chaining_indentation is exclusive to the PhpCsFixer set, while no_spaces_around_double_colon is in the PSR2 set. It might be worthwhile to propose to PHP-CS-FIXER to incorporate priority settings for these rules as well.

@shengslogar
Copy link
Author

@mho22 Agreed. Will wait to hear Dries' opinion on whether he'd like this resolved here or there. Thanks for spearheading a resolution! 🙏

@nunomaduro
Copy link
Member

@shengslogar The issue you described appears to be related to PHPCSFixer. To confirm, try reproducing the issue using PHPCSFixer alone. If you succeed in reproducing the issue, please report it to the PHPCSFixer repository; if not, feel free to re-open this issue.

@mho22
Copy link

mho22 commented Jan 8, 2024

@shengslogar I tried to reproduce the issue using PHPCSFixer alone and it succeeded reproducing it. The files here to reproduce it quickly :

composer.json

{
    "require": {
        "php": "^8.2"
    },
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^3.46"
    }
}

.php-cs-fixer.php

<?php

use PhpCsFixer\Finder;
use PhpCsFixer\Config;


$finder = ( new Finder() )->in( __DIR__ );


return ( new Config() )
    ->setUsingCache( false )
    ->setRules( [
        'method_chaining_indentation' => true,
        'no_space_around_double_colon' => true
    ] )
    ->setFinder( $finder );

test.php

<?php

$formattedIncorrectly = Foo::bar
    ::baz()
                    ->where('id', 1)
    ->firstOrFail();

$formattedCorrectly = Foo::bar::baz()
    ->where('id', 1)
    ->firstOrFail();

run vendor/bin/php-cs-fixer fix

@shengslogar
Copy link
Author

@mho22 Appreciate you taking the time to make a repro! You seem to have a much better handle on this than I do. Would you mind opening a ticket in the PHP CS Fixer repo or would you rather I do? This is the only issue I spotted that looks similar, but was closed due to being reported for an older version. Thanks for all your help on this!

@mho22
Copy link

mho22 commented Jan 8, 2024

@shengslogar Ok, I will open a ticket and tag you on it when I have time later today or tomorrow.

@shengslogar
Copy link
Author

@mho22 🙏

@mho22
Copy link

mho22 commented Jan 14, 2024

@shengslogar @nunomaduro The pull request #7723 addressing this issue has been merged into the PHP CS Fixer master branch. Now awaiting the next release. 👍

@mho22
Copy link

mho22 commented Jan 15, 2024

@shengslogar It is now released ! [v3.47.0](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/tag/v3.47.0)

@shengslogar
Copy link
Author

@mho22 THANK YOU for all your hard work on this! 07494e2 bumped composer.json to 3.47.0, so I assume the next tagged Pint release (not sure if the pint binary embeds this package?) will contain this fix. 🙌

@Jubeki
Copy link
Contributor

Jubeki commented Jan 16, 2024

Yes the next release will have the new versions defined in composer.lock. (Should probably be released sometime today)

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

No branches or pull requests

5 participants