Coder Social home page Coder Social logo

surface_formatter's People

Contributors

apmac-io avatar connorlay avatar davydog187 avatar girishramnani avatar harmon25 avatar herminiotorres avatar kianmeng avatar lnr0626 avatar malian avatar paulstatezny avatar rubysolo avatar tiagoefmoraes 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

Watchers

 avatar  avatar

surface_formatter's Issues

Support Surface v0.5

Hi @paulstatezny!

Since all syntax changes have been already implemented and the new converter is already available in https://github.com/surface-ui/surface/tree/surface-next, I think we can start working on other important projects that should be released alongside Surface to make sure everything goes as smooth as possible. My plan is to have v0.5 released by the end on the week unless something unexpected comes up.

Detailed information about the migration process can be found in the Migration Guide and the CHANGELOG has been updated with all changes.

Regarding the formatter, the most important changes to be aware of are probably:

  • Replace the sigil ~H with ~F to avoid conflict with HEEx
  • Replace interpolation delimiters {{ and }} with { and }
  • Support private comments using {!-- --}
  • Supporting some expressions at the attributes "area", namely { }, {... } and {= }
  • The new block expressions: {#if}..{#elseif}..{#else}..{/if}, {#for}..{#else}..{/for}, etc.

The last three changes above introduces new tokens or metadata at the tokenizer level so we'll have to handle them. For a quick reference, they are:

  • {:comment, comment, %{visibility: visibility}} - where visibility can be :private or :public, representing {!-- --} and <!-- --> respectively.
  • {:tagged_expr, marker, expr, meta} - where marker can be ... or =
  • {:block, name, expr, body, meta} - where name can be the name of the block (e.g. if, case) or sub-block (e.g. else, match)

The new NodeTranslator behaviour

We changed the design of the parser allowing external tools to use a different node translator that can generate a tree of nodes according to their needs. All you have to do is implementing the Surface.Compiler.NodeTranslator behaviour and define the nodes in the shape you want. Here's the implementation of the translator used by the default Surface parser: https://github.com/surface-ui/surface/blob/surface-next/lib/surface/compiler/parse_tree_translator.ex.

For the formatter, we can have a very similar one but much simpler as it requires less information and no validation.

Please let me know if you need any further clarification.

Cheers.

Code `:if={true}` is formatted into `:if`

Expected behavior

The formatter doesn't generate code that causes directives to be sent to the browser.

Actual behavior

<div :if={true} /> is formatted into <div :if>, which makes Surface send <div :if=""></div> to the browser instead of <div></div>.

Thanks @bretfunk for reporting.

Formatter indents lines further on every run

Input

Example .sface:

<div>
  {gettext("foo
    bar
    baz
  ")}
</div>

Expected

No change

Actual

On every run of mix format, the lines are indented further:

<div>
  {gettext("foo
                bar
                baz
              ")}
</div>

.formatter.exs

https://github.com/jshmrtn/hygeia/blob/b438cab763ce3f0eb6ff244c09c0e59e084dd7de/.formatter.exs#L1

[
  import_deps: [:phoenix, :phoenix_live_view, :surface, :ecto, :ecto_enum],
  inputs: [
    ".dialyzer_ignore.exs",
    ".formatter.exs",
    "*.{ex,exs}",
    "priv/*/seeds.exs",
    "priv/repo/seeds/*.exs",
    "{config,lib,test}/**/*.{ex,exs,sface}"
  ],
  subdirectories: ["priv/*/migrations"],
  plugins: [Surface.Formatter.Plugin]
]

Unnecessarily indents css classes on each run

Bug can be reproduced on this sample repo https://github.com/anildigital/surface_formatter_bug

$ mix surface.format

<TextInput
  class={
    "w-full h-12 max-w-full px-4 bg-xxx-100 hover:bg-xxx-120 text-base leading-normal
     text-color-yyy-100 box-border border border-solid border-kkk-100 rounded transition
     ease-in placeholder-hhh-100 placeholder-opacity-100 disabled:opacity-50
     disabled:cursor-not-allowed focus:border-mmmm-100 focus:outline-none
     no-scrollbar invalid:shadow-none invalid:border-ttt-100 #{@class}",

Then run again

$ mix surface.format

<TextInput
  class={
    "w-full h-12 max-w-full px-4 bg-xxx-100 hover:bg-xxx-120 text-base leading-normal
          text-color-yyy-100 box-border border border-solid border-kkk-100 rounded transition
          ease-in placeholder-hhh-100 placeholder-opacity-100 disabled:opacity-50
          disabled:cursor-not-allowed focus:border-mmmm-100 focus:outline-none
          no-scrollbar invalid:shadow-none invalid:border-ttt-100 #{@class}",

then run again

$ mix surface.format

<TextInput
  class={
    "w-full h-12 max-w-full px-4 bg-xxx-100 hover:bg-xxx-120 text-base leading-normal
                text-color-yyy-100 box-border border border-solid border-kkk-100 rounded transition
                ease-in placeholder-trunks-100 placeholder-opacity-100 disabled:opacity-50
                disabled:cursor-not-allowed focus:border-mmmm-100 focus:outline-none
                no-scrollbar invalid:shadow-none invalid:border-ttt-100 #{@class}",

formatter removes {/unless}

before

  @impl true
  def render(assigns) do
    ~F"""
    <div class="flex flex-col w-screen h-screen">
      <div class="flex-grow w-full bg-white overflow-auto">
        <div class="h-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div class="h-full py-6 flex flex-col justify-center items-center">
            <div class="relative py-6 sm:max-w-7xl sm:mx-auto">
              <div class="absolute inset-0 bg-gradient-to-r from-blue-700 to-blue-800 shadow-lg transform -skew-y-6 sm:skew-y-0 sm:rotate-2 sm:rounded-none" />
              <div class="relative bg-white border border-blue-800 shadow-lg rounded-none">
                {!--{#unless @step == 0}
                  <div class="absolute left-1 top-2 h-12 w-12">
                    <img src="/images/logo.svg" class="h-12 w-12">
                  </div>
                {/unless}--}
                {#case @step}
                  {#match 0}
                    {render_welcome()}
                  {#match 1}
                    <div class="max-w-none mx-auto">
                      <div class="bg-white overflow-hidden">
                        {render_card_header("Display & Reporting")}
                        <TimeAndDateSettings id="time-and-date-settings" {=@start_of_day} {=@timezone} />
                      </div>
                    </div>
                  {#match 2}
                    <div class="max-w-none mx-auto">
                      <div class="bg-white overflow-hidden">
                        {render_card_header(
                          "Default Alarm Columns",
                          "Choose the columns displayed on the alarm log and alarm search results screen."
                        )}
                        <div class="px-6 py-2">
                          <InitDefaultAlarmColumns id="default-alarm-columns" drop_zone={Settings.get(:default_alarm_columns)} />
                        </div>
                      </div>
                    </div>
                  {#match 3}
                    <div class="max-w-none mx-auto">
                      <div class="bg-white overflow-hidden">
                        {render_card_header(
                          "Alarm Categories",
                          "Label and order alarm categories.  Categories can be defined as a combination of single numbers, lists of numbers or ranges.  There can be no overlap between categories.  For example, '1', '100-199', '1, 2, 5' and '10, 100-199, 1001' are all valid. To avoid importing entire categories of alarms, uncheck LOG."
                        )}
                        <div class="px-6 py-2">
                          <AlarmCategoriesSettings id="alarm-categories-settings" show_sidebar={@live_action == :edit_alarm_category} />
                        </div>
                      </div>
                    </div>
                  {#match _}
                    {render_done()}
                {/case}
              </div>
            </div>
          </div>
        </div>
      </div>
      {#unless @step == 0}
        <div class="flex items-center justify-end p-4 w-full bg-gray-50">
          <Button click="change_step" values={step: @step - 1}>
            Back
          </Button>

          <Button color="green" click="change_step" values={step: @step + 1} left_margin>
            {#if @step < @steps}
              Next
            {#else}
              Done
            {/if}
          </Button>
        </div>
      {/unless}
    </div>
    """
  end

run mix surface.format and it removes the {/unless} from the second last line. The line stays there, but is empty :(

after:

  @impl true
  def render(assigns) do
    ~F"""
    <div class="flex flex-col w-screen h-screen">
      <div class="flex-grow w-full bg-white overflow-auto">
        <div class="h-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div class="h-full py-6 flex flex-col justify-center items-center">
            <div class="relative py-6 sm:max-w-7xl sm:mx-auto">
              <div class="absolute inset-0 bg-gradient-to-r from-blue-700 to-blue-800 shadow-lg transform -skew-y-6 sm:skew-y-0 sm:rotate-2 sm:rounded-none" />
              <div class="relative bg-white border border-blue-800 shadow-lg rounded-none">
                {!--{#unless @step == 0}
                  <div class="absolute left-1 top-2 h-12 w-12">
                    <img src="/images/logo.svg" class="h-12 w-12">
                  </div>
                {/unless}--}
                {#case @step}
                  {#match 0}
                    {render_welcome()}
                  {#match 1}
                    <div class="max-w-none mx-auto">
                      <div class="bg-white overflow-hidden">
                        {render_card_header("Display & Reporting")}
                        <TimeAndDateSettings id="time-and-date-settings" {=@start_of_day} {=@timezone} />
                      </div>
                    </div>
                  {#match 2}
                    <div class="max-w-none mx-auto">
                      <div class="bg-white overflow-hidden">
                        {render_card_header(
                          "Default Alarm Columns",
                          "Choose the columns displayed on the alarm log and alarm search results screen."
                        )}
                        <div class="px-6 py-2">
                          <InitDefaultAlarmColumns id="default-alarm-columns" drop_zone={Settings.get(:default_alarm_columns)} />
                        </div>
                      </div>
                    </div>
                  {#match 3}
                    <div class="max-w-none mx-auto">
                      <div class="bg-white overflow-hidden">
                        {render_card_header(
                          "Alarm Categories",
                          "Label and order alarm categories.  Categories can be defined as a combination of single numbers, lists of numbers or ranges.  There can be no overlap between categories.  For example, '1', '100-199', '1, 2, 5' and '10, 100-199, 1001' are all valid. To avoid importing entire categories of alarms, uncheck LOG."
                        )}
                        <div class="px-6 py-2">
                          <AlarmCategoriesSettings id="alarm-categories-settings" show_sidebar={@live_action == :edit_alarm_category} />
                        </div>
                      </div>
                    </div>
                  {#match _}
                    {render_done()}
                {/case}
              </div>
            </div>
          </div>
        </div>
      </div>
      {#unless @step == 0}
        <div class="flex items-center justify-end p-4 w-full bg-gray-50">
          <Button click="change_step" values={step: @step - 1}>
            Back
          </Button>

          <Button color="green" click="change_step" values={step: @step + 1} left_margin>
            {#if @step < @steps}
              Next
            {#else}
              Done
            {/if}
          </Button>
        </div>
      
    </div>
    """
  end

workaround: replace with {#if ...}

Is it possible to auto-format on save?

The extension works great.
I was just wondering if there's an option to automate format on save on VSCode (like the elixir format that runs with elixir-ls).
Would it be possible to have it as an option in the surface VSCode extension?

Formatter changes <template> tags

Running mix surface.format will change <template> to <#template>. For me, that change results in a compilation error because it's changing a template tag that I'm using for AlpineJS.

Formatter Plugin crashing with Elixir 1.13.0

Versions

  • Elixir: 1.13.0
  • Erlang: 24.1.6
  • surface: 0.6.1
  • surface_formatter: 0.7.1

Error

mix format failed for file: lib/hygeia_web/live/statistics_live/export.sface
** (FunctionClauseError) no function clause matching in Inspect.Algebra.format/2    
    
    The following arguments were given to Inspect.Algebra.format/2:
    
        # 1
        {:doc_group, {:doc_cons, {:doc_string, "gettext", 7}, {:doc_group, {:doc_cons, {:doc_cons, {:doc_cons, {:doc_nest, {:doc_cons, "(", {:doc_break, "", :strict}}, 2, :break}, {:doc_cons, {:doc_nest, {:doc_cons, {:doc_cons, "\"", {:doc_string, "From", 4}}, "\""}, 2, :break}, {:doc_break, "", :strict}}}, ")"}, :doc_nil}, :self}}, :self}
    
        # 2
        nil
    
    Attempted function clauses (showing 1 out of 1):
    
        def format(doc, width) when is_binary(doc) or doc === :doc_nil or doc === :doc_line or is_tuple(doc) and elem(doc, 0) === :doc_break or elem(doc, 0) === :doc_collapse or elem(doc, 0) === :doc_color or elem(doc, 0) === :doc_cons or elem(doc, 0) === :doc_fits or elem(doc, 0) === :doc_force or elem(doc, 0) === :doc_group or elem(doc, 0) === :doc_nest or elem(doc, 0) === :doc_string and width == :infinity or is_integer(width) and width >= 0
    
    (elixir 1.13.0) lib/inspect/algebra.ex:958: Inspect.Algebra.format/2
    lib/surface/formatter/phases/render.ex:38: Surface.Formatter.Phases.Render.render_node/2
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
    lib/surface/formatter/phases/render.ex:183: Surface.Formatter.Phases.Render.render_node/2
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
    lib/surface/formatter/phases/render.ex:183: Surface.Formatter.Phases.Render.render_node/2
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2

See in CI: https://github.com/jshmrtn/hygeia/runs/4412768200?check_suite_focus=true

Config

[
  import_deps: [:phoenix, :phoenix_live_view, :surface, :ecto, :ecto_enum],
  inputs: [
    ".dialyzer_ignore.exs",
    ".formatter.exs",
    "*.{ex,exs}",
    "priv/*/seeds.exs",
    "priv/repo/seeds/*.exs",
    "{config,lib,test}/**/*.{ex,exs,sface}"
  ],
  subdirectories: ["priv/*/migrations"],
  plugins: [Surface.Formatter.Plugin]
]

Bug: unless block closing tag being removed by formatter

Describe the bug

{/unless} is being removed by mix surface.format

How to reproduce it

  1. have a component render function with an {#unless}...{/unless} block in the ~F sigil.
  2. run mix surface.format
  3. check your component, the closing {/unless} is removed

The behavior you expected

I expect the formatter to not create compile errors.

Your Environment

Surface: v0.5.0
LiveView: v0.15.7
Elixir: v1.12.1

formatter flattens contents of <script> tags

it turned my <script> tag into this:

<script>
  document.addEventListener('alpine:init', () => {
  console.log('apline inited');
  Alpine.data('columnFilterOptions', () => ({
  all: true,
  open: false,
  options: [],
  initialSelection: [],
  selected: [],
  available: [],
  selectAll() {
  this.selected = this.options;
  },
  selectNone() {
  this.selected = [];
  },
  clear() {
  this.open = false;
  this.selected = this.initialSelection = this.options;
  },
  cancel() {
  this.open = false;
  this.selected = this.initialSelection;
  },
  apply() {
  this.open = false;
  this.initialSelection = this.selected;
  },
  init() {
  console.log('col filt init')
  this.$nextTick(() => {
  this.$watch('selected', (value) => {
  if (this.options.every((x) => { return this.selected.includes(x) })) {
  this.all = true;
  } else {
  this.all = false;
  }
  })
  })
  }
  }))
  })
</script>

Should it ignore contents of script tags or use a JS formatter?

Formatter converting valid `{=prop}` into `{=@prop}`

In the following code-snippet, the formatter converts {=class} into {=@class} even when {=class} was intended. This is the case for all props that are bound in the render scope.

defmodule Lotus.Span do
  use Surface.Component

  prop class, :css_class, default: []

  prop extra_class, :css_class, default: []

  slot default

  def render(assigns) do
    class = assigns.class ++ assigns.extra_class

    ~F"""
    <span {=class}>
      <#slot />
    </span>
    """
  end
end

moves next element onto end of previous line

Hi,

I thought I'd try with v0.6.0 but it is still moving stuff up onto the previous line. I feel like it worked when I first tried it but lately it is shifting comments and other tags up onto the end of the line above it. As you can see in the diff below, the commented out div, GotoTime and the start of the surface comment {!-- all disappeared at the end of the AlarmDivisionFilterDropdown. And for some reason it put the <button ... lines onto the one line.

image

I'm on Elixir 1.12.3, OTP 24, surface & formatter 0.6 and Windows 10, though I use LF line endings.

FunctionClauseError in Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1

The problem is new in 0.7.3, it worked in 0.7.2.

Update PR in our project: jshmrtn/hygeia#1036
Failed CI (detailed error): https://github.com/jshmrtn/hygeia/runs/4584434083?check_suite_focus=true
File with Problems: https://github.com/jshmrtn/hygeia/blob/master/lib/hygeia_web/live/organisation_live/merge.sface

Detailed Error:

mix format --check-formatted
mix format failed for file: lib/hygeia_web/live/organisation_live/merge.sface
** (FunctionClauseError) no function clause matching in Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1    
    
    The following arguments were given to Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1:
    
        # 1
        {:unless, [line: 1], [{:is_nil, [line: 1], [{:@, [line: 1], [{:delete, [line: 1], nil}]}]}, [do: {{:., [line: 1], [{:@, [line: 1], [{:delete, [line: 1], nil}]}, :uuid]}, [no_parens: true, line: 1], []}]]}
    
    Attempted function clauses (showing 2 out of 2):
    
        defp quoted_strings_with_newlines(+attribute_expression+) when -is_binary(attribute_expression)-
        defp quoted_strings_with_newlines(+nodes+) when -is_list(nodes)-
    
    lib/surface/formatter/phases/render.ex:377: Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1
Warning:     lib/surface/formatter/phases/render.ex:445: Surface.Formatter.Phases.Render.format_attribute_expression/2
    lib/surface/formatter/phases/render.ex:371: Surface.Formatter.Phases.Render.render_attribute/2
Warning:     (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
Warning:     lib/surface/formatter/phases/render.ex:203: Surface.Formatter.Phases.Render.render_opening_tag/3
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
Warning:     (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
Error: Process completed with exit code 1.

Weird formatting of <code> node inside <pre> node in Surface Catalogue

I formatted surface_catalogue using the main surface branch and found this change weird.

Before

                      <div class="code" style={"width: #{example.code_perc}%"}>
                        <pre class="language-surface">
                          <code class="content language-surface" phx-hook="Highlight" id={"example-code-#{index}-#{example.func}"}>
    {example.code}</code>
                        </pre>
                      </div>

After

                      <div class="code" style={"width: #{example.code_perc}%"}>
                        <pre class="language-surface">
                          <code
      class="content language-surface"
      phx-hook="Highlight"
      id={"example-code-#{index}-#{example.func}"}
    >
    {example.code}</code>
                        </pre>
                      </div>

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.