laravel / pint

Laravel Pint is an opinionated PHP code style fixer for minimalists.
https://laravel.com/docs/pint
MIT License
2.75k stars 138 forks source link

[1.x] Adds `blade` support #256

Open nunomaduro opened 5 months ago

nunomaduro commented 5 months ago

Note: This pull request is a work in progress, and you can help by trying it in your local project:

composer require laravel/pint:dev-feat/blade
./vendor/bin/pint --blade

Check the differences in your Blade templates and report any issues!


This pull request is a proof of concept that adds Laravel Blade support to Pint:

pint-banner-1

Here is the basic format applied:

<!-- Before -->
@foreach ($users as $user)
<p      class="p-4" >This is user {{ $user->id }}</p>
@endforeach

<!-- after: -->
@foreach ($users as $user)
    <p class="p-4">This is user {{ $user->id }}</p>
@endforeach

Tailwind classes are "sorted" using Tailwind's recommended class order:

<!-- Before -->
<button class="text-white px-4 sm:px-8 py-2 sm:py-3 bg-sky-700 hover:bg-sky-800">...</button>

<!-- After -->
<button class="bg-sky-700 px-4 py-2 text-white hover:bg-sky-800 sm:px-8 sm:py-3">...</button>

Technically, the way it works is simple - for the user, it is a completely fast experience, while behind the scenes, we are using Prettier and Prettier Plugin Blade to actually style those Blade templates. The dependencies are installed in Laravel Pint's own vendor directory on the first run, so there are no potential conflicts with existing user dependencies.

Of course, it requires Node to be installed on the user's machine.

Jubeki commented 5 months ago

I really love the idea and wanted to test it under a fresh repository.

Setup

New Laravel Project with Breeze (Blade+Alpine+Darkmode)

The commit with the changes: https://github.com/Jubeki/laravel-pint-blade-test/commit/504200c4abe0476480346fb828ae18022dcea756

Feedback

Overall a very good result, only with some little things bugging me.

It is a little slow on the first run. But the second run (probably due to cache) is much faster.

Somethings I noticed:

nunomaduro commented 5 months ago

@Jubeki We've just renamed the fixer name. Regarding your remarks, can you try to see (or even PR) your suggestions?

0528Makoto commented 5 months ago

I currently use this Blade Prettier Plugin package for prettier with blade, would it conflict? Since I have jobs that are executed every time I commit, the pint job and the prettier job, would the parse and format for the blade files be executed twice?

pd. This package npm also parses and formats alpine

nunomaduro commented 5 months ago

@DevMakoto, in that case, you would either disable blade formatting in Pint or disable blade formatting in Prettier.

0528Makoto commented 5 months ago

@DevMakoto, in that case, you would either disable blade formatting in Pint or disable blade formatting in Prettier.

@nunomaduro, this package of shufo doesn't parse or format Alpine.js for me. I would like to only use Pint if it has parsing and formatting for Blade.

Jubeki commented 5 months ago

I think the new prettier plugins works much better. ❤️

Same Setup as before, with the following result: https://github.com/Jubeki/laravel-pint-blade-test/commit/b756cd0749757436f6dc84684f510bb99e539bc1

The only remaining thing bothering me, is that the class attribute is sometimes moved to a new line, if it is too long (at least, I think that is the reason). (See https://github.com/Jubeki/laravel-pint-blade-test/commit/b756cd0749757436f6dc84684f510bb99e539bc1#diff-e720f286fb3c46871afbdac94bfe0293e346e36a23d16d636485431ddefe8324R3-R5 or the image below). I also found no configuration option to change it, like if a single attribute, keep it on same line.

I like that if there are multiple attributes, they are always moved to a new line each. (though it looked weird on the first glance, if they are all very short)

Single Attribute moved to new line (should not happen in my opinion):

Screenshot 2024-03-16 at 16 04 02

Multiple attributes moved to new lines (good in my opinion)

Screenshot 2024-03-16 at 16 08 19
Jubeki commented 5 months ago

I created a PR in the skeleton, so that you can see what would happen with the formatting on the skeleton: https://github.com/laravel/laravel/pull/6380

Probably best to disable Blade Formatting Testing on the skeleton repository. (Simply disable Laravel/blade in the root pint.json and that should fix the tests)

PR to fix issues with skeleton and static analysis: https://github.com/laravel/pint/pull/260

alissn commented 5 months ago

Hi,

In our project, after running pint, the namespace of Constants is deleted and not imported.

Here are the details:

➜  avo-core git:(master) ✗ php artisan about

  Environment ....................................................................
  Application Name ......................................................... cloud
  Laravel Version ........................................................ 10.48.3
  PHP Version ............................................................. 8.2.16
  Composer Version ............................................................. -
  Environment .............................................................. local
  Debug Mode ............................................................. ENABLED
  URL ............................................................. localhost:8000
  Maintenance Mode ........................................................... OFF

The pint.json contains this configuration:

  ...
"fully_qualified_strict_types": {
    "import_symbols": true,
    "leading_backslash_in_global_namespace": false
},
  ...

ScreenShot

image image
shufo commented 5 months ago

Hi. It's great to see blade support poc in this PR.

I tried formatting on local environment contains 167 blade files and it takes about 4~5 minutes to finish and 25 files detected error while almost of these are valid syntax.

51373edf3367:/app# find resources/ -name *.blade.php | wc -l
167

51373edf3367:/app# time ./vendor/bin/pint

  ✓✓✓!✓!✓!!!✓✓✓✓✓✓✓!!✓!✓✓✓!!✓✓✓✓!✓✓!✓✓✓✓✓✓✓✓✓✓✓✓.✓✓.✓✓✓✓.✓✓✓✓✓✓.✓✓!.✓✓.✓✓.!!✓.!✓✓✓!✓!✓✓✓✓✓✓!✓✓✓✓!✓✓✓✓✓✓!✓!✓✓!✓.✓.✓✓.✓✓✓!✓✓.✓✓.✓✓✓.✓✓.✓...✓✓✓.✓.!✓.✓✓.✓✓.✓.✓✓..✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓.✓✓✓✓✓✓✓✓✓✓✓✓✓✓.✓✓✓✓✓✓✓✓✓✓.✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓.✓✓✓✓✓✓✓✓✓✓✓✓✓

  ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel
    FAIL   ............................................................................................................................................................................................................................................................................................................................................................................... 251 files, 25 errors, 196 style issues fixed
  ✓ resources/lang/cs/articles.php                                                                                                                                                                                                                                                                                                                                                              ! resources/views/sketchbooks/index.blade.php                                                  
~long output~                                                                                                                                                                                                                                                                                            Unexpected end of input while parsing echo
  ✓ resources/views/sketchbooks/show.blade.php                                                                                                                                                                                                                                                                                                                                                                            Laravel/blade
  ✓ resources/views/skicar.blade.php                                                                                                                                                                                                                                                                                                                                                                                      Laravel/blade
  ✓ resources/views/skicare.blade.php                                                                                                                                                                                                                                                                                                                                                                                     Laravel/blade
  ✓ resources/views/users/form.blade.php                                                                                                                                                                                                                                                                                                                                                                                  Laravel/blade
  ! resources/views/users/index.blade.php                                                                                                                                                                                                                                                                                                                                                    Unexpected end of input while parsing echo
  ✓ resources/views/users/show.blade.php                                                                                                                                                                                                                                                                                                                                                                                  Laravel/blade
  ✓ resources/views/zoom.blade.php                                                                                                                                                                                                                                                                                                                                                                                        Laravel/blade

real    4m18.233s
user    4m48.056s
sys     0m59.064s

error example of false positive

[error] resources/views/admin/login.blade.php: SyntaxError: Unexpected end of input while parsing echo
[error]
[error]   13|                     <h3 class="panel-title">Prosím prihláste sa</h3>
[error]   14|                 </div>
[error]   15|                 <div class="panel-body">
[error]  >16|                     {!! Form::open(['action' => 'AuthController@postLogin', 'method' => 'post', 'id' => 'loginForm']) !!}

Prettier takes a long time to initialize, but once it is in memory it formats very quickly. So to improve this, I think running prettier command once and parsing output to see if it has errors is one of applicable approach.

As an aside, I now develop and use vscode-blade-formatter in VSCode (Marketplace), and it has same logic as @shufo/prettier-plugin-blade, so it would be great to be able to specify arbitrary plugin for formatting blade in pint to keep consistency between editor's save on format and format from program like pint. This is because the above false positives do not occur in @shufo/prettier-plugin-blade, or vice versa, errors may occur in @shufo/prettier-plugin-blade that do not occur in prettier-plugin-blade.

nunomaduro commented 5 months ago

@shufo Test again with the latest version. Should be away faster now.

amdad121 commented 5 months ago

It is a long-awaited feature of this.

It's working fine but it takes a little bit of time.

shufo commented 5 months ago

@nunomaduro

Good to hear. The first format time has been optimized down from 4 minutes to 1m20s in same condition.

5c9bac976ab9:/app# time ./vendor/bin/pint

  ✓✓✓!✓✓✓!✓!✓✓✓✓✓✓✓✓✓✓!✓✓✓!✓✓✓✓✓✓✓✓✓✓✓!!✓✓.✓✓✓✓✓.✓✓.✓✓✓✓.✓✓✓✓✓✓.✓✓!.✓✓.✓✓.!!✓.✓✓✓✓✓.✓.✓✓✓✓✓!.✓✓✓!

~long output~  ........................................................................................................................................................................... 502 files, 28 errors, 187 style issues fixed

real    1m20.239s
user    2m10.922s

As far as I can see, the first run is still six times behind the performance of native prettier. Tried both @shufo/prettier-plugin-blade, prettier-plugin-blade.

5c9bac976ab9:/app# cat .prettierrc
{
  "plugins": ["@shufo/prettier-plugin-blade"],
  "overrides": [
    {
      "files": ["*.blade.php"],
      "options": {
        "parser": "blade",
        "wrapAttributes": "force-expand-multiline",
        "tabWidth": 4
      }
    }
  ]
}

5c9bac976ab9:/app# time ./node_modules/.bin/prettier "resources/**/*.blade.php" -w
resources/views/admin/index.blade.php 480ms
resources/views/admin/login.blade.php 139ms

~long output~

real    0m12.146s
user    0m19.590s
sys     0m2.414s
adevade commented 4 months ago

Very exited for this feature! 🚀

I tried running on my Windows 11 machine and I'm getting strange behaviour, using Git Bash. Same results using PowerShell.

It takes the contents of one file and moves to another. For example it copies and replaces the content from admin/categories/edit.blade.php to admin/documents/edit.blade.php. But it also does the same from admin/documents/create.blade.php to admin/trashed-documents/index.blade.php, so it's not based on the filename.

Also getting different results and errors on each subsequent run.

Please let me know if I can provide more helpful info in some way!


Windows 11 Node 20.12.1 npm 10.5.2

Laravel Version ...... 10.48.8
PHP Version .......... 8.2.8
Composer Version ..... 2.7.2
Environment .......... local
Debug Mode ........... ENABLED
First run ```bash ❯ pint --blade ...............................................................!!!✓!!✓!✓✓!!!!!!!!!!!!✓✓!✓✓!✓.................. ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel FAIL ............................................................................................................................................................. 110 files, 20 errors, 9 style issues fixed ! resources\views\admin\categories\create.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\admin\categories\edit.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\admin\categories\index.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\admin\documents\create.blade.php Laravel/blade ! resources\views\admin\documents\edit.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\admin\documents\index.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\admin\trashed-documents\index.blade.php Laravel/blade ! resources\views\auth\login.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\components\button.blade.php Laravel/blade ✓ resources\views\components\card.blade.php Laravel/blade ! resources\views\components\container.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\empty-state.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\form.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\halle-logo.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\header-meta-item.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\input-group.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\input.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\list-container.blade.php (node:21508) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive… ! resources\views\components\list-item.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\select.blade.php Unexpected non-whitespace character after JSON at position 321 ! resources\views\errors\404.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\errors\503.blade.php Unexpected non-whitespace character after JSON at position 1402 ✓ resources\views\layouts\alert.blade.php Laravel/blade ✓ resources\views\layouts\app.blade.php Laravel/blade ! resources\views\layouts\footer.blade.php Unexpected non-whitespace character after JSON at position 1224 ✓ resources\views\layouts\head.blade.php Laravel/blade ✓ resources\views\layouts\header.blade.php Laravel/blade ! resources\views\layouts\nav.blade.php Unexpected non-whitespace character after JSON at position 473 ✓ resources\views\layouts\split.blade.php Laravel/blade ```
Second run ```bash ❯ pint --blade ...............................................................!!!.!✓.✓..!✓!!!!✓✓!!!✓..!..✓................... ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel FAIL ............................................................................................................................................................. 110 files, 13 errors, 7 style issues fixed ! resources\views\admin\categories\create.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\admin\categories\edit.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\admin\categories\index.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\admin\documents\edit.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\admin\documents\index.blade.php Laravel/blade ✓ resources\views\auth\login.blade.php Laravel/blade ! resources\views\components\container.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\components\empty-state.blade.php Laravel/blade ! resources\views\components\form.blade.php (node:23912) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true })… ! resources\views\components\halle-logo.blade.php Unexpected non-whitespace character after JSON at position 1435 ! resources\views\components\header-meta-item.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\input-group.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\components\input.blade.php Laravel/blade ✓ resources\views\components\list-container.blade.php Laravel/blade ! resources\views\components\list-item.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ! resources\views\components\select.blade.php Unexpected closing tag "b_kOgI1AW3xQB". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.ht
… ! resources\views\errors\404.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\errors\503.blade.php Laravel/blade ! resources\views\layouts\footer.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ✓ resources\views\layouts\nav.blade.php Laravel/blade ```
Simple Blade Component with error ```bash ❯ pint --blade resources/views/components/form.blade.php ! ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Laravel FAIL ........................................................................................................................................................................................ 1 file, 1 error ! resources\views\components\form.blade.php '..' is not recognized as an internal or external command, operable program or batch file. ``` ```blade @props([ 'method' => 'POST', 'action', 'noCsrf' => false, ])
@if (!in_array($method, ['GET', 'POST'])) @method($method) @endif @if (!$noCsrf) @csrf @endif {{ $slot }}
```
adevade commented 4 months ago

I got it working on my machine by tweaking the pintCommand in .blade.format.json! After adding php to the beginning of the command everything works as expected, and the files are parsed.

- "pintCommand": "../builds/pint {file}",
+ "pintCommand": "php ../builds/pint {file}",
adevade commented 3 months ago

I like that if there are multiple attributes, they are always moved to a new line each. (though it looked weird on the first glance, if they are all very short) -- @Jubeki

I actually think that 2 attributes on the same line is fine most of the time, since they're often short. Maybe this setting could be configurable?

In your screenshots there are some tags that are not moved to separate lines, despite having 2 attributes. <x-input-label for="..." :value="..." /> and <x-input-error :messages="..." class="..." />. Is this because of the Blade syntax colon? Or did you change the formatting by hand for the screenshots?


Some examples that would look better on the same line in my opinion:

image

image

image

wescopeland commented 3 months ago

Part of the joy of using Prettier is not having to worry about even the possibility of debates regarding the config. Even the few settings it supports, printWidth in particular, have been the source of debates on my teams.

If possible, please consider not adding additional config.

Additional reading: Prettier Option Philosophy

mansoorkhan96 commented 3 months ago

Thank you for working on this. I have tried it in a project.

Here is an issue:

It does not format anchor tags properly

<!-- Before -->
<li class="mb-3.5"><a class="text-white hover:text-gray-200 font-medium leading-relaxed" href="{{ route('team') }}">Team</a></li>

<!-- After -->
<li class="mb-3.5">
    <a
        class="font-medium leading-relaxed text-white hover:text-gray-200"
        href="{{ route('team') }}"
        >Team</a
    >
</li>

<!-- Expected -->
<li class="mb-3.5">
    <a
        class="font-medium leading-relaxed text-white hover:text-gray-200"
        href="{{ route('team') }}"
    >
        Team
    </a>
</li>
adevade commented 3 months ago

Part of the joy of using Prettier is not having to worry about even the possibility of debates regarding the config. Even the few settings it supports, printWidth in particular, have been the source of debates on my teams. -- @wescopeland

Yeah I guess you're right. It's just sooo close to how I actually want to format it 😅 I'll just have to get used to it 👍

chrillep commented 3 days ago

love the idea! In the meantime we use

with code-guide setting

.prettierrc.json

{
  "plugins": [
    "prettier-plugin-tailwindcss",
    "@shufo/prettier-plugin-blade"
  ],
  "overrides": [
    {
      "files": [
        "*.blade.php"
      ],
      "options": {
        "parser": "blade",
        "wrapAttributes": "force-expand-multiline",
        "sortTailwindcssClasses": true,
        "tailwindcssConfigPath": "tailwind.config.cjs",
        "sortHtmlAttributes": "code-guide"
      }
    }
  ]
}
--wrap-attributes The way to wrap attributes. [auto|force|force-aligned|force-expand-multiline|aligned-multiple|preserve|preserve-aligned]. default: auto
--wrap-attributes-min-attrs Minimum number of html tag attributes for force wrap attribute options. Wrap the first attribute only if 'force-expand-multiline' is specified in wrap attributes. default: 2.
--sort-tailwindcss-classes Sort Tailwind CSS classes. It will automatically look for and respect tailwind.config.js if it exists. default: false
--sort-html-attributes Sort HTML Attributes in the specified order. [none | alphabetical | code-guide | idiomatic | vuejs] default: none