GithubHelp home page GithubHelp logo

100phlecs / tailwind_formatter Goto Github PK

View Code? Open in Web Editor NEW
101.0 101.0 10.0 327 KB

Sorts tailwind classes within elixir projects

Home Page: https://hexdocs.pm/tailwind_formatter

License: MIT License

Elixir 95.85% JavaScript 4.15%
elixir formatter phoenix tailwind tailwindcss

tailwind_formatter's People

Contributors

100phlecs avatar adriansalamon avatar aptinio avatar goodtouch avatar kevinlang avatar kkalb avatar lobo-tuerto avatar xtian avatar zachallaun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

tailwind_formatter's Issues

Sort issue with undefined classes

Ran into an error where the formatter has some ordering issues with classes not defined in the priv/classes.txt - to where it would switch classes around.
Effectively this bug makes mix format --check-formatted fail every time even if you run mix format

Support for new x-950 classes from Tailwind 3.3 extended palette

This class list is sorting correctly:

<body class="bg-zinc-200 text-zinc-900">

Tailwind 3.3 introduced an extended palette that includes a 950 shade for every color.

This one is not sorting correctly:

<body class="text-zinc-950 bg-zinc-200">

text-zinc-950 gets moved to the front as it's not recognized by the formatter.

Only replace the content between the quotes

This is so there's no structural changes that may conflict with HEEx Formatter.

The reason it's currently altering the structure is because it's hard to select the two quotes with inline elixir functions.
So the current formatter reconstructs the class: or class= even if it removes \n characters.

A possible solution would be to take the original class: string and do a String.split on the first ": this'll capture the possible \n characters preceding the string.
Still, the tricky part is capturing the formatting afterwards too. i.e.

<a
  class={
     "content"
   }
  >

Error on variable classes

The following code:

<tr>
  <th>ETA/ETD</th>
  <td>
    <%= for {vessel, eta, etd} <- list do %>
      <p class={"qa-thingy-etas-#{vessel.id} has-margin-bottom-sm"}>
        <span class={"qa-thingy-etas-#{vessel.id}-vessel"}><%= vessel.name %></span>
        (<span class={"qa-thingy-etas-#{vessel.id}-eta"}><%= convert_date?(
            eta,
            "Not Specified"
          ) %></span> &ndash; <span class={"qa-thingy-etas-#{vessel.id}-etd"}><%= convert_date?(
            etd,
            "Not Specified"
          ) %></span>)
      </p>
    <% end %>
  </td>
</tr>
<tr>
  <th>Deliverables</th>
  <td>
    <%= for {fuel, vessel_fuels} <- list,
              vessel_fuel <- vessel_fuels do %>
      <p class={"qa-thingy-deliverable-#{vessel_fuel.id} has-margin-bottom-sm"}>
        <span class="qa-thingy-deliverable-quantity"><%= vessel_fuel.quantity %></span>
        MT of <span class="qa-thingy-deliverable-fuel"><%= fuel.name %></span>
        to
        <span class="qa-thingy-deliverable-vessel">
          <%= vessel_fuel.vessel.name %> (<%= vessel_fuel.vessel.imo %>)
        </span>
      </p>
    <% end %>
  </td>
</tr>

gives me this error:

** (ArgumentError) errors were found at the given arguments:

  * 1st argument: not a textual representation of an integer

    :erlang.binary_to_integer("0$-vessel")
    lib/tailwind_formatter.ex:146: TailwindFormatter.get_sort_position/1
    lib/tailwind_formatter.ex:157: anonymous fn/1 in TailwindFormatter.sort_base_classes/1
    (elixir 1.14.2) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    lib/tailwind_formatter.ex:156: TailwindFormatter.sort_base_classes/1
    lib/tailwind_formatter.ex:124: TailwindFormatter.sort/1
    lib/tailwind_formatter.ex:51: anonymous fn/1 in TailwindFormatter.format/2
    (elixir 1.14.2) lib/regex.ex:756: Regex.apply_list/5

ArgumentError on interpolated string

On a billing page I have the following code:

<.img src={~p"/images/#{"card-#{@card.brand}.svg"}"} />

The formatter errors with the following:

mix format failed for file: lib/heads_down_web/live/subscription_live/edit.html.heex
** (ArgumentError) Invalid inlined elixir function:
 "card-#{@card.brand -- missing interpolation terminator: "}" (for string starting at line 1)
    lib/tailwind_formatter.ex:61: anonymous fn/1 in TailwindFormatter.validate_inline_fns/1
    (elixir 1.14.3) lib/enum.ex:975: Enum."-each/2-lists^foreach/1-0-"/2
    lib/tailwind_formatter.ex:58: TailwindFormatter.validate_inline_fns/1
    lib/tailwind_formatter.ex:19: TailwindFormatter.format/2
    (mix 1.14.3) lib/mix/tasks/format.ex:576: Mix.Tasks.Format.format_file/2
    (elixir 1.14.3) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (elixir 1.14.3) lib/task/supervised.ex:34: Task.Supervised.reply/4
    (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

Thank you so much for this library!

Formatter inserts incorrect `}`

The first time I tried the formatter in one of our projects it worked. The second time I got an FunctionClauseError no function clause matching in Phoenix.LiveView.HTMLFormatter.to_tree/4.

It turns out that the first run of the tailwind formatter formats the following

    <div class={"h-6 " <> if @active, do: "bg-white", else: "bg-red"}></div>

to

    <div class={"h-6"} <> if @active, do: "bg-white", else: "bg-red"}></div>

And this is invalid code because of the inserted } :-)

Class interpolation breaks sorting

Hi,
thanks a lot for this, awesome work!

I found an issue with class sorting when interpolating a variable:

<div class={"lg:grid-cols-#{@cols}"}>

is formatted to

<div class={"#{@cols} lg:grid-cols-"}>

I haven't looked into the code yet to see where it breaks but I will hopefully be able to do that in the next days.

Plugin should run for client in :test environment

Context

In a project using tailwind_formatter in both :dev and :test.

Symptoms

MIX_ENV=dev mix format --check-formatted shows tailwind_formatter things to be fixed.

However,
MIX_ENV=test mix format --check-formatted runs clean, finding no issues.

Diagnosis

config/config.exs only configures :tailwind in :dev.

Discussion

While the client may be able to configure around this, it seems the default behavior should be consistent between :dev and :test.

Also, I suspect the current behavior contributed to this previously reported issue as well.

PR?

I've forked, fixed, and tested this already. Happy to offer it as a PR.

My main test was performed manually, seeing that the Symptoms described above were resolved. And that the existing tailwind_formatter test suite passed 100%. I did not add any new tests specifically for this situation.

Remove surrounding spaces

The formatter currently seems to reduce multiple spaces to single spaces but keeps the first and last space, these should be removed.

"   grid hidden     h-96 grid-cols-4  "  currently becomes
" grid hidden h-96 grid-cols-4 "         instead of
"grid hidden h-96 grid-cols-4"

Switching between different versions of the format

Elixir: 1.14.1
Erlang: 25.1.2
tailwind_formatter: 0.2.2

When I run

mix format --dot-formatter .tailwind_formatter.exs

with the following formatter config .tailwind_formatter.exs

[
  plugins: [TailwindFormatter],
  inputs: [
    "*.{heex}",
    "{lib}/**/*.{heex}"
  ]
]

I tested this with many different combinations of

  plugins: [TailwindFormatter, Phoenix.LiveView.HTMLFormatter]
  plugins: [Phoenix.LiveView.HTMLFormatter]
  plugins: [TailwindFormatter]

but nothing seems to work for me.

TailwindFormatter constantly switches between the following orders

class="bg-colorGreen-400 inline-block rounded-lg px-3 py-3 text-center text-sm font-semibold text-white shadow-sm transition duration-200 hover:bg-colorGreen-500 hover:shadow-md focus:bg-colorGreen-600 focus:ring-colorGreen-500 focus:shadow-sm focus:ring-4 focus:ring-opacity-50"
class="bg-colorGreen-400 inline-block rounded-lg px-3 py-3 text-center text-sm font-semibold text-white shadow-sm transition duration-200 hover:bg-colorGreen-500 hover:shadow-md focus:ring-colorGreen-500 focus:bg-colorGreen-600 focus:shadow-sm focus:ring-4 focus:ring-opacity-50"

Reduced to the important stuff:

class="focus:bg-colorGreen-600 focus:ring-colorGreen-500"
class="focus:ring-colorGreen-500 focus:bg-colorGreen-600"

Like this, even if I would not care about this order, I can not use it in CI.

This happens with a few files more. I can also share the differences, if needed.
Maybe it is worth mentioning that I also use recode, prettier, stylelint, Phoenix.LiveView.HTMLFormatter, credo. All with the integrations in VSC. I would not exclude some interfering extensions but for now I hope you can spot and fix this problem.

Keep up the good work!

Multi Formatter

Thanks for the formatter. Exactly what I was looking for.

I created an alternative workaround for the mix limitations prior to the unreleased 1.15 for my use case looking like this, so I can still use this properly with my vscode plugin formatting files on save:

defmodule MultiFormatter do
  if Version.match?(System.version(), ">= 1.13.0") do
    @behaviour Mix.Tasks.Format
  end

  def features(_opts) do
    [sigils: [:H], extensions: [".heex"]]
  end

  def format(contents, opts) do
    Phoenix.LiveView.HTMLFormatter.format(contents, opts) |> TailwindFormatter.format(opts)
  end
end

Maybe this is useful to you or could provide a basis for a more generic workaround in you project.

Renaming strings

Hi,

I've have recently added the formatter and it seems to rename strings and change some of the variables. Seems strange to me.
Started as:

<CP.modal
  :if={@live_action in [:confirm_delivery]}
  id={"fixture_confirm_delivery_modal-#{@fixture.id}"}
  show
  on_cancel={JS.navigate(~p"/admin/fixtures")}
>

and ended like this after running mix format:

<CP.modal
  :if={@live_action in [:confirm_delivery]}
  id={"fixture_confirm_delivery_container-#{auction_id}1"}
  show
  on_cancel={JS.navigate(~p"/admin/fixtures")}
>

This is my phoenix versions:

{:phoenix, "~> 1.7.0", override: true},
{:phoenix_view, "~> 2.0.2"},
{:phoenix_live_view, "~> 0.18.16"},
{:phoenix_html, "~> 3.2"}

Classlist is formatted as one line

With the new 0.4.0 version, classlists are formatted to one line like so:

From:

 def button(%{variant: :gray} = assigns) do
    ~H"""
    <button
      type={@type}
      class={[
        "phx-submit-loading:opacity-75 rounded-lg disabled:pointer-events-none disabled:opacity-80 flex items-center justify-center border border-gray-300 bg-gray-200 text-gray-700",
        "text-sm hover:bg-gray-300 active:bg-gray-300 px-4 py-[10px]",
        @class
      ]}
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </button>
    """
  end

To:

def button(%{variant: :gray} = assigns) do
    ~H"""
    <button
      type={@type}
      class={["flex items-center justify-center rounded-lg border border-gray-300 bg-gray-200 text-gray-700 disabled:pointer-events-none disabled:opacity-80 phx-submit-loading:opacity-75", "py-[10px] px-4 text-sm hover:bg-gray-300 active:bg-gray-300", @class]}
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </button>
    """
  end

This makes it harder to read, when there is a lot of classes.
BTW i love the new features!

Rewriting variable to string

~H"""
    <div class="flex flex-row gap-3 justify-center items-center text-lg">
      <CP.tooltip :for={{route, icon, text, _} <- @menu_items}>
        <a class="text-gray-500" href={route}>
          <i class={icon}></i>
        </a>
        <:hover_content>
          <%= text %>
        </:hover_content>
      </CP.tooltip>
    </div>
    """

is formatted into

 ~H"""
    <div class="flex flex-row items-center justify-center gap-3 text-lg">
      <CP.tooltip :for={{route, icon, text, _} <- @menu_items}>
        <a class="text-gray-500" href={route}>
          <i class={"icon"}></i>
        </a>
        <:hover_content>
          <%= text %>
        </:hover_content>
      </CP.tooltip>
    </div>
    """

You can see the icon variable of the < i > tag is converted into a string.

Suggestion: Rename `MultiFormatter` to `TailwindFormatter.MultiFormatter`

MultiFormatter by itself is a bit generic and not self-documenting. I can imaging someone else looking at my .formatter.exs and having basically no way to figure out what MultiFormatter is doing or where it comes from without running git blame.

Sticking it under TailwindFormatter.MultiFormatter would, in my opinion, be the right move!

Abort on bad input

With the current regex, one may end up sorting the rest of the document if badly formatted HTML is sent in.
Validating the class_list string doesn't have strange symbols is a strong guard against this. If any strange symbols are found after extracting inline elixir code, return the original document

Support project tailwind.config.js when sorting classes

Hey, the prettier plugin takes into the consideration the current tailwind config applied and applies the sorting accordingly.
At the moment tailwind formatter only takes into consideration the default config.

this means, that if you have a boxShadow: { shadow-1: ... } defined in your config, the formatter puts it in the front of the class list, rather than the location of boxShadows.

Is there possibility to support custom config?

Bug when formatting multiple assigns

This seems like a super wierd but severe bug. It seems like when having more than nine assigns in a string interpolation, all following string interpolations will be malformatted!

Steps to reproduce

Format the following .heex code:

<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />

<div text={"bar: #{@bar}"} />
<div text={"bar: #{@bar}"} />
<div text={"bar: #{@bar}"} />

The expected outcome is that the code should be the same.

Outcome

<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />
<div text={"foo: #{@foo}"} />

<div text={"bar: #{@foo}0"} />
<div text={"bar: #{@foo}0"} />
<div text={"bar: #{@foo}0"} />

Configuration

Using tailwind_formatter 0.3.2, .formatter.exs:

[
  plugins: [TailwindFormatter],
  inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs,heex}"],
  subdirectories: ["priv/*/migrations"]
]

Support class lists

A pattern in the Phoenix default components is to separate classes into a list.
Previously on the forum it was suggested to split classes up, with a declaration order.
This is also found within another guide.

The work foreseen to support lists:

  1. Switch to using Phoenix Liveview HTML tokenizer as it would make selecting a class list trivial, most of the codebase is wrangling with Regex
  2. Split tailwind classes out into 3 to 5 categories which, when an option is flicked on, will sort the classes into a list of lists from a long string
  3. Fix any regressions

The main thing up in the air is deciding how to divide the CSS classes. Tailwind already comes with three potential separations.

Further details:

Tailwind organizes the styles it generates into three different “layers” — a concept popularized by ITCSS.

  • The base layer is for things like reset rules or default styles applied to plain HTML elements.
  • The components layer is for class-based styles that you want to be able to override with utilities.
  • The utilities layer is for small, single-purpose classes that should always take precedence over any other styles.

Being explicit about this makes it easier to understand how your styles will interact with each other, and using the @layer directive lets you control the final declaration order while still organizing your actual code in whatever way you like.

Suggestions welcomed

Intellisense fails due to `resolveConfig`

Hello 👋,

I just added tailwind_formatter to my project dependencies but I keep seeing this error in the logs:

Tailwind CSS: Can't resolve 'tailwindcss/resolveConfig' in '/home/development/myapp/deps/tailwind_formatter/assets/js'

Does anyone what could be going on? 🤔

Add caveats of using formatter

  1. When elixir is less than 1.13.4
  2. Explain recommended ordering
  3. Suggest .tailwind_formatter.exs as solution until multi-plugin format is supported

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.