Giter VIP home page Giter VIP logo

panoply / esthetic Goto Github PK

View Code? Open in Web Editor NEW
35.0 8.0 2.0 11.81 MB

Æ ~ New generation beautification support for multiple languages (pre-release)

Home Page: https://aesthetic.js.org

License: MIT License

JavaScript 26.50% TypeScript 63.61% Liquid 9.90%
formatter liquid liquify prettydiff sparser typescript beautification formatting shopify beautify format prettier aesthetic beautifier esthetic

esthetic's Introduction



WIP ~ WORK IN PROGRESS

The new generation code beautification tool for formatting HTML, Liquid, CSS/SCSS, JavaScript, TypeScript and more! Æsthetic leverages the Sparser lexing algorithm and its parse approach has been repurposed from the distributed source of the late and powerful PrettyDiff.

Documentation

Documentation lives on æsthetic.dev

Features

  • Fast, performant and lightweight (50kb gzip).
  • Format, parse and language detection capabilities.
  • Provides a granular set of beautification rules.
  • Supports 10+ different front~end facing languages.
  • Uniformed data structures with incremental traversal.
  • Simple and painless integration within existing projects.

Installation

Æsthetic is supports both CJS/ESM environments and also provides basic CLI support.

PNPM
pnpm add esthetic
YARN
yarn add esthetic
NPM
npm install esthetic --save
CDN
https://unpkg.com/esthetic

Usage

Consult the documentation for a better understanding.

CLI
$ esthetic <file> --flag
ESM
import esthetic from 'esthetic';

esthetic.format('...', { /* rules */ })
CJS
const esthetic = require('esthetic');

esthetic.format('...', { /* rules */ })

Contributing

Looking to contribute? Æsthetic leverages pnpm so ensure you're using it as your package manager. Development is intended to be conducted within the vscode text editor. Fork or clone the project and install dependencies.

Pre-requisites

Testing / Development

Æsthetic uses the powerful AVA test runner together with a small helper utility that helps alleviate some of the complexities involved with testing tools of its criteria. It's recommended that you develop in a two pane terminal. The dev.test.mjs and dev.txt files are core to testing and working on the module, they will be called when running pnpm play

Commands

The following commands are available as executable scripts.

pnpm dev         Bundles module with ESBuild (via tsup) in watch mode
pnpm play        Starts up AVA in development mode and runs the dev.txt
pnpm build       Generates the distribution bundles
pnpm pack        Packages the module up for distribution on NPM registry
pnpm test        Runs all the tests
pnpm tests       Cherry pick test cases to run

Consult the tests readme for more information on test prefixed commands

Acknowledgements

Æsthetic owes its existence to Sparser and PrettyDiff. This project has been adapted from these 2 brilliant tools and while largely refactored + overhauled the original parse architecture remains intact.

Æsthetic is made possible because of the Austin Cheney who is the original author of Sparser and PrettyDiff. Austin is one of the great minds in JavaScript and I want to thank him for open sourcing these tools.

Both PrettyDiff and Sparser were retired in 2019 after a nearly a decade of production. Austin has since created Shared File Systems which is a privacy first point-to-point communication tool. Please check it out and also have a read of wisdom which personally helped me become a better developer.

Follow me on Twitter or shoot me an Email.

esthetic's People

Contributors

panoply 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

esthetic's Issues

CSS/JS formatting, brackets unexpected behavior

  • CSS formatting in {% style %} renders as such with debatable position of brackets (same behavior for JS brackets opening on a separate line):

image

  • The "compressCSS" rule also behaves unexpectedly, which is probably the same issue. (Feedback note: I appreciate that the "compressCSS" rule keeps every different css rule on a separate line, it's much more readable than a single one liner) :

image

ForceLeadAttribute not applied to <a> tokens

Description

When using forceLeadAttribute with wrap based attribute forcing, the lead attributes contained on <a> nodes are not being forced. This seems to only affect <a> token types.

Rules

Take the following beautification options:

{
  wrap: 80,
  markup: {
    forceLeadAttribute: true
  }
}

Current Result

The following code will not be forced even though word wrap limit is exceeded, where id="{{ image.id }}" is output inline and instead should be forced.

 <a id="{{ image.id }}"
    href="{{ image.src | img_url: 'master' | format: 'pjpg' }}"
    title="{{ image.alt | default: product.title }}"
    {%- unless forloop.first -%}class="d-none"{%- endunless -%}
    data-index-=" {{- forloop.index }}"
    data-height="2048"
    data-width="1365"
    data-spx-disable="true"> Some Link </a>

Expected Result

 <a 
    id="{{ image.id }}"
    href="{{ image.src | img_url: 'master' | format: 'pjpg' }}"
    title="{{ image.alt | default: product.title }}"
    {%- unless forloop.first -%}class="d-none"{%- endunless -%}
    data-index-=" {{- forloop.index }}"
    data-height="2048"
    data-width="1365"
    data-spx-disable="true"> Some Link </a>

Embedded expression indentation and wrap defect in JSX

Description

JSX and TSX languages have a number of defects occurring in the beautification process. This issue outlines the most pressing issues and the actions + changes which have taken place in the migration into Prettify.

Goals

Though JSX and TSX and not really the main focus in Prettify, given the logic already existed in Sparser and PrettyDiff support for these languages will be provided. I want to keep things persistent and somewhat aligned with Prettier logics here as developers already are accustomed to the Prettier specific beautification style and as such imposing changes that would otherwise drastically augment their existing code perfecence is something that should be avoided.

Context

Both the Sparser lexing algorithm and PrettyDiff beautification parse will undergo changes, specifically with how the level indentation store is generating output. Changes applied should fix these 2 issues but it goes without saying JSX and TSX are likely to still require some work for fluidity.

Wrap Defect

PrettyDiff is augmenting the data-structure when wrap was defined and language is set to jsx. The defect occurs when in quote conversion and wreaks havoc due to mismatched splice. I don't see why this is needed and any additional control it may be providing can be applied elsewhere or will be addressed in a separate patch.

Example

When wrap limit is defined and language is jsx the defect occurs.

function example() {

  // Issue occurs here due to splice augmentation being applied to external references
  return {" "}
  <div className='HelloWorld'
    title={
      `You are visitor number ${num}`
    }
    onMouseOver={onMouseOver}<strong> {
    greeting.slice(0, 1).toUpperCase() + greeting.slice(1).toLowerCase()
  } </strong>


}

Embedded Expression

Embedded expressions in JSX and TSX languages are indented onto new lines which is not ideal nor necessary and this default behaviour should be normalized. This is inherited from PrettyDiff and seems to be the default when handling JSX/TSX. This is not an issue per-say but more so a matter of preference and assumed standard for developers choosing JSX.

Current

function example() {

  return <div 
    className='HelloWorld' 
    title={
      `You are visitor number ${num}`
     } 
     onMouseOver={onMouseOver}>

    <strong>{
    greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase()
    }</strong>

  </div>;

}

Intended

function example() {

  return <div
    className='HelloWorld'
    title={`You are visitor number ${num}`}
    onMouseOver={onMouseOver}>

     <strong>
        {greeting.slice(0, 1).toUpperCase() + greeting.slice(1).toLowerCase()}
     </strong>
  
  </div>;
}

New Rule: attributeValues

Description

In some situations controlling how markup attribute values are formatted can be beneficial, especially when working with Liquid. Though I personally consider attribute values to expressed in a strict manner, this option is likely helpful in a lot of situations. The attributeValues formatting option introduces support for exactly this. This ruleset allows users to take reign and control of HTML attributes values and in addition will gracefully handle contained Liquid tokens infused within.

Goals

This option needs to facilitate multiple beautification options which directly pertain to how attribute values should be formatted in Markup languages. The inherited behaviour of PrettyDiff and Sparser here is a little problematic and changes applied for this enhancement require various refactors. The main goal here is provide stable enough processing and output for typical coding styles whereas the edge cases and chaotic projects, like that seen in the utter debauchery that is Shopify Dawn, will likely need some patches in order to handle otherwise despicable code structures.

Context

Introducing this option requires intercepting the lexing records constructed by Sparser. Refactoring the attribute lexer and tokenization approach of the markup lexer is essential. The attribute values also need to be walked and temporary reference generated before logic is passed to the various handlers in PrettyDiff.

Ruleset

The option will provide multiple beautification style choices. The initial rollout will include the following:

  • preserve
  • strip
  • collapse
  • wrap

There is consideration and room for advancement here, so introducing template specific beautification choices is likely to incur in the pre-release version or very soon after but for now will not be supported.

  • wrap-liquid
  • collapse-inline-preserve

Definition

The option will be available to markup and can be defined as followed:

prettify.options({
  markup: {
    attributeValues: 'preserve' | 'strip' | 'collapse' | 'wrap'
  }
})

Examples

The below examples showcase how the different formatting styles will output attribute values.

Preserve (default)

The preserve option will leave HTML attribute values intact, preserving the input provided.

Before Formatting

<div class="one 
  two         three {% if x %} four {% else %} five {% endif %}      {{ some.object }} 
  six         seven"></div>

After Formatting

<div class="one 
  two         three {% if x %} four {% else %} five {% endif %}      {{ some.object }} 
  six         seven"></div>

Strip

The strip option will strip newlines and equally distribute spacing by removing extraneous whitespace between entries from values and replacing them with a single space character.

Before Formatting

<div class="one    two   three 
       {% if x %} four {% else %} five {% end if %} {{ some.object }}             six seven"></div>

After Formatting

<div class="one two three {% if x %}four{% else %}five{% end if %} {{ some.object }} six seven"></div>

Wrap

The wrap option will apply indentation to attribute values if wrap limit has been exceeded. This option respects the inner contents of Liquid tokens and will not augment such expressions if detected. Indentation is applied for each whitespace separated character in the value sequence.

Below we assume wrap has been exceeded.

Before Formatting

<div class="one two three {% if x %} four {% else %} five {% end if %} {{ some.object }} six seven"></div>

After Formatting

<div class="one 
           two 
           three 
           {% if x %} four {% else %} five {% endif %} 
           {{ some.object }} 
           six 
           seven"></div>

Collapse

The collapse option will apply indentation to all attribute values. This option does not respect the inner contents of Liquid tokens and will apply forced indentation for each whitespace separated character in the value structure and any Liquid token encountered, for example:

Before Formatting

<div class="one two three {% if x %} four {% else %} five {% end if %} {{ some.object }} six seven"></div>

After Formatting

<div class="one 
          two 
          three 
          {% if x %} 
          four 
          {% else %} 
          five 
          {% endif %} 
          {{ some.object }} 
          six 
          seven"></div>

Liquid Conditional Open/Close Markup Structures

Description

This issue is utterly despicable and the fact that folks do this type of shit is bewildering and mildly infuriating. What is even worse is that Shopify advocates for this type of fucking debauchery, smh. While I personally would prefer the defect to persist in order to have developers rethink how they are writing code, I know such will come back to haunt me. Anyway, I digress.

The issue or defect should not really categorized as such but for the sake brevity I will label it accordingly. Liquid condition enclosed markup structures are open and close HTML markup tags encapsulated by a condition. This is best explained in an example.

{% if x %}
<div>
{% endif %}

<ul>
  <li>{{ something }}</li>
</ul>

{% if x %}
</div>
{% endif %}

Above is a prime example of a conditional open/close markup structure. Notice how the opening <div> and closing </div> tags and wrapped in an {% if %} control statement. As of right now, Prettify cannot handle such structures and will throw a parse error.

Context

In order to bring support for this in Prettify, I will need to keep a persisted store of markup tokens or alternatively augment the stack references in order to process the logic. Some additional thought here is required and this will likely been a post pre-release implementation. The Liquify parser does not yet support this and for the most part, I may decide to simply boycott implementation because advocating and doing this type of thing is fucking cretinous.

New Rule: attributeChain

Description

This is a Liquid specific beautification option and will controls how Liquid tags contained within HTML attributed should be formatted. The rule requires some considerable thought and does not want do too much handling.

Goals

Attribute chaining needs to respect the defined wrap limit but override forceAttributeon inner contents when using inline. In cases where wrap is defined and the option is using inline with the correct rule enabled (ie: true), then Prettify should apply whitespace dashes to surrounding tag delimiters of the inner content it newlined in the chain that exceeded the wrap length. Chaining should also respect preservation and input defined structures.

Context

This option requires refactors and multiple augmentation and patches be applied in the Sparer lexing algorithm. New token types will be introduced here in order to know what type of token we are dealing with. The tokenization of attribute values will also undergo overhauls to ensure we are capturing connected Liquid and HTML type expression.

It's hard to predict the structure a user will employ. Because Liquid is novice appealing due to its simplicity, developers can be utterly fucking insufferable when infusing template logics into HTML attributes and as such this beautification rule will likely need to undergo edge-case testing in order to provide the best possible outcomes when reasoning with shitty code.

Sparser Types

This option will introduce new token type reference into Sparser. The new types will behave nearly identically to the standard lexing of Template tokens with differencing being the actual tokens pushed into the data structures. These new types will allow for tokens like data-{{ some.object }} to passed.

template_attribute_start

A template tag within a HTML attribute that is not a singleton. When the attributeChain option is enabled and Prettify executes the beautification process this type value is used as a stack reference point to produce a chained result.

Example

The {% if x %} token in the below example would be marked as a template_attribute_start because it can be identified as a start type token given an ender exists.

<div
 id="foo"
 {% if x %}data-x{% else %}data-y{% endif %}></div>

template_attribute_else

A template tag within a HTML attribute that can be identified as an else type conditional.

Example

The {% elsif %} and {% else %} tokens in the below example would be marked as a template_attribute_else because they can be identified as a else type tokens given they are contained within conditional types if and unless tags. This type value is important for beautification handling as depending on defined options like attributeChain, forceAttribute, wrap and/or correct then a token type value of template_attribute_else may be used to separate contents onto newlines.

<div
 {% unless x %}class="foo"{% else %}class="bar"{% endunless %}
 id="some-id"
 {% if x %}data-x{% elsif y %}data-y{% endif %}></div>

template_attribute_end

A template tag within a HTML attribute that can be identified as an ender type.

Example

The {% endif %} token in the below example would be marked as a template_attribute_end because it can be identified as a ender type token given that a start type exists.

<div
 id="some-id"
 {% if x %}data-x{% endif %}></div>

Ruleset

The option will provide multiple beautification style choices. The initial rollout will include the following:

  • preserve
  • collapse
  • inline

There is consideration and room for advancement here, so the potential to extends these choices is likely.

Definition

The option will be available to markup and can be defined as followed:

prettify.options({
  markup: {
    attributeChain: 'preserve' | 'collapse' | 'inline'
  }
})

Examples

Preserve (default)

Preserve strips extraneous whitespace and newlines if they exceed the defined preserveLine limit but it will leave the overall structure intact. Notice how in the below example, the only difference before and after formatting is the extraneous whitespace.

<!-- Before formatting -->
<div
  class="x"
  {% if x %} id="{{ foo }}"
  {% else %}  data-x="xx"    {% endif %}>
</div>

<!-- After formatting -->
<div
  class="x"
  {% if x %}id="{{ foo }}"
  {% else %}data-x="xx"{% endif %}>
</div>

Inline

Notice how before formatting the contents of the tag block are separated onto new lines whereas after formatting the contents are chained together.

<!-- Before formatting -->
<div
  class="x"
  {% if x %}
  id="{{ foo }}"
  {% else %}
  data-x="xx"{% endif %}>
</div>

<!-- After formatting -->
<div
  class="x"
  {% if x %}id="{{ foo }}"{% else %}data-x="xx"{% endif %}>
</div>

Collapse

Collapse will newline the contents of the tag block. Notice how before formatting contents are expressed on a single line, whereas after formatting content is forced onto new lines.

<!-- Before formatting -->
<div
  class="x"
  {% if x %}id="{{ foo }}"{% else %}data-x="xx"{% endif %}>
</div>

<!-- After formatting -->
<div
  class="x"
  {% if x %}
  id="{{ foo }}"
  {% else %}
  data-x="xx"{% endif %}>
</div>

Support Liquid inline comments

Description

Little late to the party on this one, Liquid inline comment tags are now apart of the specification. Support for handling of these tags is required. This will be implemented as part of the next iterations applied to comment lexing.

Examples

Shopify's documentation informs about this: https://shopify.dev/api/liquid/tags#inline_comment

{% # content %}

{% # this is a comment %}

{%
  ###############################
  # This is a comment
  # across multiple lines
  ###############################
%}

Related

Empty Conditionals

Description

This is a defect occurring in Liquid conditional tags when the inner contents is empty. I've likely introduced this defect during Æsthetic overhaul. The issue occurs when the following structures is encountered.

BEFORE

This is also the intended output

<div>
  <section id="xxx" class="foo bar baz">
    <!-- comment -->
    {% if xx %}
      <h1>Hello World</h1>
    {% elsif xxxx %}

    {% elsif foo %}

    {% endif %}
  </section>
</div>
<div>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
</div>

AFTER

This is the current output when such structures are encountered

<div>
  <section id="xxx" class="foo bar baz">
    <!-- comment -->
    {% if xx %}
      <h1>Hello World</h1>
    {% elsif xxxx %}

{% elsif foo %}

   {% endif %}
  </section>
</div><div>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
</div>

This can be problematic when formatting and executing onSave in text editors or better yet when you simply have empty conditionals.

Liquid in JSON string values

Description

Liquid contained in JSON strings and likely JavaScript/TypeScript strings is being indented. Such structures should be preserved. Take the following example:

Current Result

The following is a current output result when beautification is being applied to an embedded region:

<script type="application/ld+json">
  {
      "availability": "http://schema.org/ {%- if product.available %}InStock
      {%- else -%}
        OutOfStock{%- endif -%}",
  }
</script>

Expected Result

The {% if %} and {% else %} conditional should be preserved.

<script type="application/ld+json">
  {
      "availability": "http://schema.org/ {%- if product.available %}InStock{%- else -%}OutOfStock{%- endif -%}",
  }
</script>

CSS Variables and Style Lexer Defects

Description

The style lexer and parser have a couple of defects and bugs, most of which are Liquid infusion based, meaning they only occur when Liquid is used within CSS code. The issues occur in both the Sparser and PrettyDiff adaptations.

Context

The root cause of these defects are at the data structure construction level of Sparser in the style lexer and these issues per-say will trickle their way into the PrettyDiff parse and beautification process. The main and most pressing defect is Sparsers inability to form consensus around CSS Variables and has trouble with the :root {} native selector type.

Root selector

Sparser is unable to form a clear enough type reference when the following select is encountered. This is likely due to the fact that CSS variables are fairly new and support was not considered at the time Austin wrote the lexer. The following code will fail and wreak havoc, stripping code and generally just causing fuckery.

:root {
 --main-bg-color: pink;
}

It's probably a good to idea to also introduce a specific reference type for the :root selector.

Isolated Liquid

This issue is cumbersome and easy to amend, however some additional thought should be given to the way Liquid is lexed. The current approach is relying on grammars references, whereas I'd prefer to only reach for them at as last resort in order to extend support automatically to custom Liquid tags and only use the standard references on expected tags like if, else etc. The current and most pressing defect arises when Liquid output type tokens are encountered, for example:

{{  some.output | filter }}
{{  another | filter }}

.class {}

The above example cannot be handled correctly. Newlines are not respected here and the type reference will be selector which causes issues in beautification process. The above example will output this:

{{some.output | filter}}{{another | filter}}
.class {}

Whereas the intended result should be the initial expression with newlines respected and only extraneous newlines should be stripped when preserveLine limit is exceeded.

CSS Variables

This is not really a defect and some respect for Austin (original author) who elegantly considered properties in an intelligent manner. Some patch work will be applied here however in order to have context in the data structure of CSS variables. I will introduce several new types here and likely a separate issue will address this.

The following new types will be implemented

property_variable

This type will describe the following structure:

.selector {
  font-size: var(--some-name);
}

value_variable

This type will describe the following structure:

.selector {
  --some-name:  10px;
}

Improve support for nested HTML tags inside a Liquid block

Description

Though this already supported in Prettify, it is important that the Prettify project supports all failings of the Liquid Prettier Plugin variation and does things better, faster and more refined.

Current nested markup (HTML) tags are handled as follows:

{% if x %}
  <div>
  {% endif %}
  <span>Hello World</span>
  {% if x %}
  </div>
{% endif %}

See the Playground

Improvements

While the current beautification approach is elegant enough, It might be nice to apply equal indentations, eg:

{% if x %}
  <div>
{% endif %}
  <span>Hello World</span>
{% if x %}
  </div>
{% endif %}

I will open this up for dialogue in the Discord to see what other developers feel is more appropriate.

Reference

Shopify/prettier-plugin-liquid#51

Support Liquid Line Comment Ignores

Description

It seems that this is not currently supported in local versions. The current supported inline comment lists are as follows:

{% # esthetic-ignore %}

{% comment %} esthetic-ignore {% endcomment %}
{% comment %} esthetic-ignore-start {% endcomment %}
{% comment %} esthetic-ignore-end {% endcomment %}
{% comment %} esthetic-ignore-next {% endcomment %}

<!-- esthetic-ignore -->
<!-- esthetic-ignore-start -->
<!-- esthetic-ignore-end -->
<!-- esthetic-ignore-next -->

/* esthetic-ignore */
/* esthetic-ignore-start  */
/* esthetic-ignore-end  */
/* esthetic-ignore-next  */

// esthetic-ignore 
// esthetic-ignore-start 
// esthetic-ignore-end 
// esthetic-ignore-next 

Required

Additional test cases need to be generated to ensure all comment ignores are behaving correctly, but most pressing here given use within vscode-liquid is liquid line comments. As per the above list, Liquid inline comments need support for:

{% # esthetic-ignore-start  %}    
{% # esthetic-ignore-end  %}
{% # esthetic-ignore-next  %}

HTML Attribute values converting to lowercase

Description

This issue is something that I believe to had introduced in the lexer during refactors. Could be something I considered a good idea during lack of sleep. Whatever the case it is problematic. Take the following:

<div id="Foo" data-attr-something="SoMeThInG"></div>

The above code will be converted to:

<div id="foo" data-attr-something="something"></div>

While this is fine for a lot of situation it is not nice for situations where one is using a framework like Stimulus, Alpine or a use case which requires case preservation.

Ruleset

I believe this calls for a new ruleset to be introduced. In some situation folks might want to enforce lowercase while others might want to preserve the case structures, it will vary. By default, casing should be preserved as it was provided and users should optionally enable enforced lowercase. I will create a separate issue going into more detail regarding this.

JSON Rules not being respected

Description

This issue pertains to JSON beautification rules which are currently not being respected or partially being respected. The script lexer is what handles JSON formatting and it was introduced in the rewrite from Sparser so as to isolate and maintain a separation of logics.

Context

This issue is requires some further investigation but for the most part should be an easy enough fix to apply.

Current Workaround

JSON beautification can be controlled using script lexing rules.

Liquid output token as HTML tag name

Description

This issue or defect should not really be categorized as such but for the sake brevity I will label it accordingly. Liquid conditional tags or output token use as HTML (markup) tag names will fail and throw a parse error. Developers should avoid doing this in any sense because it's a horrible practice and despite Shopify engineers doing this in Dawn, you shouldn't blindly follow their lead.

Take the following

<{{ some.tag }}>
  Hello World
</{{ some.tag }}>

<!-- OR -->
<{% if x %}{{ some.tag }}{% else %}div{% endif %}>
 Hello World
</{% if x %}{{ some.tag }}{% else %}div{% endif %}>

Notice in the above code sample how HTML tag names are being defined using Liquid tokens. This is going to cause Prettify to fail, so don't do this for now and if you are leveraging Prettify on Dawn, simply remove the tags that apply such logic.

Context

In order to bring support for this in Prettify, I will need to augment the lexing algorithm. Some thought will be required here as developers may structure this expression in a chaotic manner. It's important that JSX logic is not affected by this logic as embedded type expressions can sometimes use such a structure.

Indent Level not aligning correctly

Description

This is a defect I have introduced that partially is related to attributeChain (see #10) and occurs when a wrap based indentation is being used and the token is wrapped in a Liquid expression. It is a very minor issue.

Playground Reference

Issue can be visualized in the playground on line 10.

Context

This defect is occuring due to malformed level reference. Likely incurred during attribute parsing. This is a simple fix.

Liquid comments containing Liquid replicating

Description

This is a critical defect and needs to be addressed ASAP. Liquid comments containing Liquid code are not being ignored and instead replicated causes serious issues.

Example

Take the following example which is applying omitting code within Liquid comments.

Current
<!-- @prettify-ignore-start -->
{% comment %}

  {%- render 'something' -%} {%- render 'something' -%}  {% endcomment %}

{% endcomment %}
<!-- @prettify-ignore-end -->
Expected
{% comment %}

{%- render 'something' -%} 

{% endcomment %}

Current Workaround

The current approach here is to wrap inline comment ignores:

<!-- @prettify-ignore-start -->
{% comment %}

  {%- render 'something' -%}

{% endcomment %}
<!-- @prettify-ignore-end -->

JSON formatting issue

After hitting save, I'm seeing this:
{ "type": "image_picker", "id": "image-preview", "label": "Image preview" }, { "type": "text", "id": "block-title", "label": "Title" }, {
instead of this:
{ "type": "image_picker", "id": "image-preview", "label": "Image preview" }, { "type": "text", "id": "block-title", "label": "Title" },

Ignoring Regions with {% # @prettify-ignore-start %} not working

In my testings {% # @prettify-ignore-start %} / {% # @prettify-ignore-end %} did not work, whereas <!-- @prettify-ignore-start --> / <!-- @prettify-ignore-end --> and other available methods seem to work properly.

That being said, I'm also not able to comment anything else on separate lines ; because snippets of code to exclude usually have somethings to be said about, or some kind of explanation about why it's excluded:

{% comment %} 
   @prettify-ignore-start
   Bla bla bla...
{% endcomment %}

Tree shaking

Hey, the library looks sick! Could you tell me if are you gonna make it tree-shakable?
I now need basically only a formatter for HTML in the browser, but as far as I understand, all the functionality is loaded now, because it's in one compiled js file.

Support Bad Liquid

Description

While utterly insufferable - support for these type of structures will be made available. Made popular by the devs working on Shopify Dawn, these structures are both unsafe and highlighly novice.

Example

Take the following:

<{% if condition %}main{% else %}div{% endif %} class="xxx">

</{% if condition %}main{% else %}div{% endif %}>

Currently, these structures completely breaks the lexing process, however a new type will be introduced to gracefully handle these hedonistic expressions. Introducing:

  • liquid_start_bad
  • liquid_end_bad

These types will be inferred and applied to above structures during the parse and traversal operation. Proceeding attribute annotation will be handled in accordance like any other HTML start tag type.

Better handling for Liquid Captures

Description

The Liquid {% capture %} tags are sometimes problematic. The current in-place logic for handling of captures is as follows:

  1. Captures can be skipped by add them to ignoreTagList
  2. If captures are not added to ignoreTagList then Æsthetic will attempt to reason with them.

The defect will occur when the inner contents of captures get creates. Based on code samples provided by @MaxDesignFR some time ago now, it seems that Æsthetic still has trouble in format execution. This is critical and needs to be patch before 4.0 of vscode-liquid.

Required Patches

Æsthetic should only handle \n newline structures contained with captures, unless capture exists within ignoreTagList. Preservation behaviour is priority here, for example:

Before Formatting

We are assuming capture does not exists within ignoreTagList.

<div>{% capture foo %}
                                  hello {{ object.prop }} 
            {% if xxx %} 
                  {{ something }} {% endif %}
  {% endcapture %}</div>

After Formatting

Æsthetic should not touch or apply indentation and other rulesets within captures, only newlines. Based on the above sample, output should apply alignment only, for example:

<div>
  {% capture foo %}
    hello {{ object.prop }} 
    {% if xxx %} 
    {{ something }} {% endif %}
  {% endcapture %}
</div>

A couple of takeaways here, firstly the <div>{% capture foo %} and {% endcapture %}</div> is forced. Secondly, indentation is applied respectively and all newline occurrences are indented but with whitespace is stripped. The dedentTagList[] Liquid formatting rule should be respected here in cases where a user wants alignment.

Capture is within DedentTagList

If the capture tag name happens to exists within dendentTagList then the default indentation should not be applied, this would result in the following:

<div>
  {% capture foo %}
  hello {{ object.prop }} 
  {% if xxx %} 
  {{ something }} {% endif %}
  {% endcapture %}
</div>

Considerations

The above default handling might not be ideal in cases where the user wants to preserve whitespace, but this is the most logical approach in order to cater to the majority. More advanced code samples like that of MaxDesign need to respected. In situations of that nature, preservation will need to be defined within ignoreTagList[]. Adding capture to ignoreTagList or alternatively annotate with esthetic-ignore-next, for example (using the before formatting sample):

Using Capture in ignoreTagList

<div>
  {% capture foo %}
                                  hello {{ object.prop }} 
            {% if xxx %} 
                  {{ something }} {% endif %}
  {% endcapture %}
</div>

Notice here, we apply forcing to <div>{% capture foo %} and {% endcapture %}</div> but inner contents is preserved. This behaviour meets expectation but ensures structural intent.

https://æsthetic.dev/ longer working

The README, and possibly other documents, contain links to https://æsthetic.dev/ which no longer resolves in DNS, with or without the www. prefix.

This is not a browser-specific issue as I've checked both Firefox and Chrome.

I've also checked its decoded version, xn--sthetic-lxa.dev, and experienced the same issue.

Improve Text (content) handling

Description

Text content handling can be improved. The main 2 rules for controlling text is the preserveText and forceIndent markup rules. Both behave as intended but some re-thinking and consideration needs to be had in this area, especially with preserveText

My intention here is to normalize text nodes when preserveText is disabled (ie: false) and produce output that is identical to that of which is the default behaviour of rendering engines (like the browser). Changes will include:

  • Stripping newlines
  • Stripping extraneous whitespaces

Example

Take the following code sample:

<p>

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Sapien eget mi proin sed libero enim. 

Turpis egestas sed tempus urna et pharetra pharetra massa. 
</p>

The new logic to be introduced will (by default) result in the following:

<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sapien eget mi proin sed libero enim. Turpis egestas sed tempus urna et pharetra pharetra massa. 
</p>

Essentially, all newlines are removed the same way the browser would render such code.

Logic to consider

This new behaviour needs consideration when it comes to newline preservation on the global preserveLine rule. The data structures generated in the lexing process do a good enough job with identifying content types so there might be room or some thought should be had about potentially introducing a new beautification rule within markup that controls whether newline preservation is be respected within text content, it could be something like preserveTextLines.

The forceIndent rule despite being related to content formats can carry on behaving as it should.

Improve parse error information

Description

This is really important enhancement. The sparser algorithm did not account for this in an elegant way, so when encountering parse issues in code, specifically markup languages, the error reports informs upon start or end types not aligning, but give no context of line numbers and code samples.

Goals

The goal here are to improve and track node syntactic structures, this will help diagnose issues occuring in the beautification process. This enhancement is likely going to involve some serious adjustments in the traversal processes. Though I don't want to augment and adjust to sparse algorithm, it seems somewhat inevitable to make this available, well at least when dealing with markup.

Improve handling of SVG nodes

Description

SVG elements like use and currently setup to be interpreted as voids which makes Prettify complain there is missing start/end items. This rule can actually be adjusted via the grammar option but I have not yet documented this.

Input

The following structure is not accepted and Prettify will complain, this is because the <use> tag is configured as a void:

<svg class="icon">
  <use xlink:href="#svg-icon"></use>
</svg>

Current Workaround

While the above structure will fail, passing a void will ensure everything works:

<svg class="icon">
  <use xlink:href="#svg-icon" /> <!-- make the tag void -->
</svg>

Inline ignore regions defect

Description

When inline comment ignores (regions) are applied at the top of documents, ie: wrapping the first element node then beautification is ignored on the entire file, instead of the just the region (as intended).

Context

This is likely caused to a parse processing issue. Currently, I am only seeing this effect HTML (markup) documents. I have only tested using HTML inline comment ignores, so upon patching this it is important that all inline ignored comment regions are checked.

Example

Below is an example wherein because we have passed a region ignore as the the first expression in a document file then the entire file will be ignored, essentially it is behaving exactly like @prettify-ignore whereas it should only ignore the defined region.

<!-- @prettify-ignore-start -->
{%- capture class -%}

           IGNORE ME

{%- endcapture -%}
<!-- @prettify-ignore-end -->

{%- capture params -%} {{- collection.filters | map: 'param_name' | json | escape -}}
                       {%- endcapture -%}

Current Workaround

The current workaround is to ensure a token of some sort is prepended above, for example by asserting <!-- x--> beautification will work as intended.

<!-- x -->
<!-- @prettify-ignore-start -->
{%- capture class -%}

           IGNORE ME

{%- endcapture -%}
<!-- @prettify-ignore-end -->

{%- capture params -%} 
  {{- collection.filters | map: 'param_name' | json | escape -}}
{%- endcapture -%}

New Rule: attributeCasing

Description

This directly pertains to #24 and covers the introduction of a new markup beautification rule (option). I will not bring support for uppercase structures, that is a code smell and frowned upon. Only preservation and/or conversion for lowercase will be provided.

Goals

The goal with this rule is allow users to choose how they would like Prettify to handle their input when it comes to attribute casing. In some projects, folks might like a strict lowercase enforcement be applied whereas other project might require the complete opposite, ie: case preservation. This new rule will not effect Liquid attributes used in markup tokens, it only relates to HTML attributes.

Ruleset

This option will be a multiselect type which defaults to preserved:

Options Description
preserve All tag attribute keys/values are preserved (default)
lowercase All tag attribute keys/values are converted to lowercase
lowercase-keys Only attribute keys are converted to lowercase
lowercase-values Only attribute values are converted to lowercase

Definition

The option will be available to markup and can be defined as followed:

prettify.options({
  markup: {
    attributeCasing: 'preserve' | 'lowercase' | 'lowercase-keys' | 'lowercase-values'
  }
})

Examples

The below examples showcase how the different formatting styles will output attribute casing

Preserve (default)

The preserve option will leave HTML attribute casing intact.

Before Formatting

<div class="HelloWorld" data-VaLuE="eXampLE"></div>

After Formatting

<div class="HelloWorld" data-VaLuE="eXampLE"></div>

Lowercase

The lowercase option will convert all attribute keys and values to lowercase

Before Formatting

<div class="HelloWorld" data-VaLuE="eXampLE"></div>

After Formatting

<div class="helloworld" data-value="example"></div>

Lowercase Keys

The lowercase-keys option will convert all attribute keys to lowercase

Before Formatting

<div class="HelloWorld" data-VaLuE="eXampLE"></div>

After Formatting

<div class="HelloWorld" data-value="eXampLE"></div>

Lowercase Values

The lowercase-keys option will convert all attribute keys to lowercase

Before Formatting

<div class="HelloWorld" data-VaLuE="eXampLE"></div>

After Formatting

<div class="helloworld" data-VaLuE="example"></div>

Support SVG Voids

Description

This one is actually supported in the first minor release of markdown and liquid as thus this issue is merely here for prosperity reasons. Containing SVG tags accept both void and block type token expressions. This means that the following are valid:

<svg>
  <path d="xxxx"></path>
</svg>

<!-- OR -->

<svg>
  <path d="xxxx" />
</svg>

<!-- OR -->

<svg>
  <path d="xxxx">
</svg>

Handling of these structures would fail in the beta versions but support is now available and also an accompanying rule. The new rule and handling will gracefully work these structures and give you the option to convert or omit enders.

New Rule selfClosingSVG

The new markup will determine the existing structures and can remove enders, for example when the rule is enabled which it is by default this:

<svg>
  <path d="xxxx"></path>
</svg>

Will be converted to:

<svg>
  <path d="xxxx" />
</svg>

HTML5 Shiv Comments + External Embeds

Description

The markup lexer has trouble processing HTML5 Shiv comments (ie: Conditional Comments) when an external embedded language region is preceded. This is a defect that was partially inherited from PrettyDiff and partly introduced in the comment beautification overhaul for Prettify.

Context

The defect affects HTML and Liquid (markup languages) and it caused from the data structure tokens and multiline indentation logic employed to handle HTML comment formatting. The issue will only occur when an embedded language follows the shiv. The internal indentLevel needs re-thinking.

Re-creation

As aforementioned, the defect only occurs when an embedded (external) language region precedes it. AFAIK this below structure is the only way to recreate the defect. In the below code example, the conditional comment:

<!--[if lt IE 9]>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
<![endif]-->

Is immediately followed by a <style> embedded language code block. This is where the defect wreaks it havoc. The resulting beautification result will output misaligned indentation levels for all code following the shiv conditional comment, for example:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My page title</title>
    <link rel="stylesheet" href="style.css">

    <!-- the below three lines are a fix to get HTML5 semantic elements working in old versions of Internet Explorer-->
    <!--[if lt IE 9]>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
    <![endif]-->

    <style>
html {
  font-family: sans-serif;
}

form {
  background: #eee;
  border-radius: 20px;
  box-shadow: 1px 1px 1px black;
  padding: 20px;
  width: 330px;
}

    </style>
    </head>

  <body>
  </body>
</html>

Workaround

This is post pre-release fix. A temporary workaround is to either not use shiv conditional comments or do not place an external code region directly after.

Isolated Liquid attributes applying quote conversion

Description

The is a critical error and will be addressed in next patch. The defect occurs when an isolated Liquid attribute is contained within Markup HTML attributes is formatting with ruleset quoteConvert set to double or single. The beautifier will attempt to wrap the Liquid token resulting in invalid code structures.

Rules

{
  markup: {
    quoteConvert: 'double' // or 'single'
  }
}

Input

<div  {{ object.prop.prop[string][10] | filter: false }}></div>

Current Result

<div  "{ object.prop.prop[string][10] | filter: false }"></div>

Expected Result

The parser should exclude template types when applying quote conversion. The intended result should be leave the Liquid attribute alone.

<div  {{ object.prop.prop[string][10] | filter: false }}></div>

Current Workaround

This defect only occurs when rulesets assert quote conversion. Setting quoteConvert to none will prevent this defect from occurring in markup languages.

New Style Rule: forceValue

Description

This new rule helps tame CSS code and allows for a persistent style across your project and be very helpful in cases where you are infusing Liquid into style language and the rule has been introduced for such situations. While I personally consider this a lazy, elementary and novice tactic, It's not uncommon for Liquid to be infused with CSS and Shopify actually advocates such a practice. It is apparent that folks regularly employ the approach in their projects, especially when CSS values are pushing wrap limits. If you are not infusing Liquid into your styles, then this option while perfectly fine to use, it's generally frowned upon, however if your code style tastes prefer this, nothing would stop you from leveraging it.

This is a style exposed option and provides 3 style choices. The option will indent CSS selector property values onto newlines in CSS, SCSS or LESS languages. The optional is particularly helpful when Liquid tags are used as property values as in most cases CSS property value lengths will rarely exceed wraps.

Goals

The goals of this beautification option is to elegantly apply a consistent code style in CSS, SCSS or LESS languages. The rule places property values onto new lines and while not a common practice, it can be exceptionally helpful for cases where Liquid tokens use long naming conventions or you are infusing Liquid conditions, both situations are common in Shopify themes. As aforementioned, this option is not specific to Liquid infused styles, it can be used in styles that do not contain any liquid too.

Context

This option requires refactors only minor augmentation to be applied in the style beautification process. When a user defined wrap context is imperative and the logic for this is handled in lexical scopes. If the user has defined wrap but has not provided a word wrap limit then the rule will fallback to preserve. Both the preserve and collapse styles can also be supported with not too much complexity and heavy lifting.

Ruleset

The option will provide multiple beautification style choices. The initial rollout will include the following:

  • preserve
  • collapse
  • wrap

Definition

The option is set to preserve by default and made available to style rules. It can be defined as followed:

prettify.options({
  wrap: 80, // Define a wrap limit if using wrap as forceValue beautification style
  style: {
    forceValue: 'preserve' | 'collapse' | 'wrap'
  }
})

Preserve (default)

The below examples showcases how the default preserve style will behave. Notice how there is no difference between before and after formatting. The structure is left intact.

Before

:root {
  --media-padding: {{- settings.media_padding }}px
  --font-body-family:  {{- settings.type_body_font.family }},  {{- settings.type_body_font.fallback_families }};
  --font-body-weight-bold:  {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
}

.selector {
  color: rgb(211, 211, 211);
  font-size:  {{- settings.type_body_font.size  | plus: 5 | at_most: 30 }};
  font-weight:  {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
  font-family:   {{-  settings.prop  | default: settings.type_body_font.family  }};
}

After

:root {
  --media-padding: {{- settings.media_padding }}px
  --font-body-family:  {{- settings.type_body_font.family }},  {{- settings.type_body_font.fallback_families }};
  --font-body-weight-bold:  {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
}

.selector {
  color: rgb(211, 211, 211);
  font-size:  {{- settings.type_body_font.size  | plus: 5 | at_most: 30 }};
  font-weight:  {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
  font-family:   {{-  settings.prop  | default: settings.type_body_font.family  }};
}

Collapse

The collapse option is ideal and recommended when you are infusing Liquid into styles. This is exceptionally helpful if you are a heathen that inlines styles within a <style> embedded tag and sprinkles Liquid code within.

Notice how before formatting all selector property values are expressed inline, but after formatting they will output onto new lines. Another important takeaway here is the white space dash (trim) delimiters applied to Liquid tokens. When using this style choice with correct enabled, Prettify will reason with the input and apply the space trims where necessary, cool heh?

Before

:root {
  --media-padding: {{ settings.media_padding }}px
  --font-body-family: {{ settings.type_body_font.family }}, {{ settings.type_body_font.fallback_families }};
  --font-body-weight-bold: {{- settings.type_body_font.weight | plus: 300 | at_most: 1000 }};
}

.selector {
  color: rgb(211, 211, 211);
  font-size: {% if some_condition %} {{ settings.font_small  }}px{% else %} {{ settings.font_large  }}px{% endif %};
  font-family: {{ something.prop | filter: 'foo' | default: settings.type_body_font.family }};
  background: #ffffff;
}

After

:root {
  --media-padding: 
     {{- settings.media_padding }}px
  --font-body-family: 
     {{- settings.type_body_font.family }}, 
     {{- settings.type_body_font.fallback_families }};
  --font-body-weight-bold:  
     {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
}

.selector {
  color: 
    rgb(211, 211, 211);
  font-size: 
    {{- settings.type_body_font.size  | plus: 5 | at_most: 30 }};
  font-weight: 
    {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
  font-family:  
    {{-  settings.prop  | default: settings.type_body_font.family  }};
   background: 
     #ffffff;
}

Wrap

The wrap style choice requires you to define a word wrap limit in the global options. This style choice will only apply new line indentation on values which exceed the wrap limit. Notice how only a couple of values in the before and after examples are output to new lines. This option will rarely newline pure style selector property values given the tiny length of the values but helpful when you need to new line large values, typical of Liquid tags.

Before

:root {
  --media-padding: {{ settings.media_padding }}px
  --font-body-family: {{ settings.type_body_font.family }}, {{ settings.type_body_font.fallback_families }};
  --font-body-weight-bold: {{- settings.type_body_font.weight | plus: 300 | at_most: 1000 }};
}

.selector {
  color: rgb(211, 211, 211);
  font-size: {% if some_condition %} {{ settings.font_small  }}px{% else %} {{ settings.font_large  }}px{% endif %};
  font-family: {{ something.prop | filter: 'foo' | default: settings.type_body_font.family }};
}

After

:root {
  --media-padding:  {{- settings.media_padding }}px
  --font-body-family: 
     {{- settings.type_body_font.family }}, 
     {{- settings.type_body_font.fallback_families }};
  --font-body-weight-bold:  
     {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
}

.selector {
  color:  rgb(211, 211, 211);
  font-size:  {{- settings.type_body_font.size  | plus: 5 | at_most: 30 }};
  font-weight: 
    {{- settings.type_body_font.weight  | plus: 300 | at_most: 1000 }};
  font-family:  
    {{-  settings.prop  | default: settings.type_body_font.family  }};
}

Conditionals

In situations where you conditionally output selector property values, the control structures will behave the same way as output structures. Indentations are applied in the expressions. Below is an example of beautified conditional values.

.selector {
  color: rgb(211, 211, 211);
  font-size: 
    {%- if some_condition -%} 
      {{- settings.font_small  }}px
    {%- else -%}
      {{- settings.font_large  }}px
   {%- endif %};
}

Liquid + html comment formatting not indenting as expected

This use case:

{%- comment -%}
   Liquid comments
   blabla
{%- endcomment -%}
<!--
  HTML comment
   bla bla
-->


{%- comment -%}
  Liquid comment
{%- endcomment -%}

Indents like this:

{%- comment -%}
  Liquid comments
  blabla{%- endcomment -%}
<!--
HTML comment
bla bla
-->{%- comment -%}
  Liquid comment
{%- endcomment -%}

This seem to be an issue when multiple comments are succeeding, otherwise indenting works as expected.

Comment defects and Improvement

Description

Comments are wreaking mild havoc depending upon placement within markup structures. Take the following code sample:

BEFORE

<div id="xx" class="100" data-foo-bar=xxx">
  <!-- comment -->
</div>

AFTER

<div 
  id="xx" class="100" data-foo-bar=xxx">
<!-- comment -->
</div>

Improvements

The intended output needs to behave in accordance with structure intention. The approach to employ here will fix the current defects (as per above sample) but will also introduce some custom behaviour and {% else %} or {% elsif %} tags, wherein comment annotation will align themselves to the starting point of the {% else %}, for example:

CURRENT BEHAVIOUR

Take the following example, with rules commentNewline for both Liquid and Markup enabled.

{% if xx %}
  <ul>

      {% # comment %}
      <li id="foo" class="bar">

      <!-- comment --> 
      
      {% # comment %}
      {% render 'xx'%}
    </li>
</ul>

  {% comment %}
    Notice the indentation applied here above the else tag
  {% endcomment %}
{% else %}
 ...
{% endif %}

NEW BEHAVIOUR

The new behaviour will align the comment annotation to the else tag dedent.

{% if xx %}
  <ul>

      {% # comment %}
      <li id="foo" class="bar">

      <!-- comment --> 
      
      {% # comment %}
      {% render 'xx'%}
    </li>
</ul>

{% comment %}
    The comment will be aligned when an else tag follows
{% endcomment %}
{% else %}
 ...
{% endif %}

Changing module name to Æsthetic

Description

As this projects moves to a more stable release some thought has been had regarding the package name being used. Using the name "Prettify" is logical because it is deeply tied to and recognized with code beautification/formatting but it has some downsides, the main factor being that it is far too close and connected to "Prettier" making it less assertive overall.

Moving forward, the module and the project will be changed from Prettify to Æsthetic. The NPM registry name will ship under esthetic and it will be isolated away from the @liquify NPM organisation.

Planned Change

The change will actually happen in the next release.

New Markup Rule: valueForce

Description

This is a new rule to be introduced which directly pertains to Markup and specifically Liquid expressions. It is loosely based on #9 but takes a different approach, one which is partly inspired by the Liquid Prettier Plugin logic. The valueForce beautification rule is a multi-select rule that will force indent all attribute values to new lines in accordance with the attribute value expression provided.

Options

The rule accepts 5 options. It will default to intent when undefined.

  • wrap
  • newline
  • intent
  • always
  • never

intent

Setting this rule to intent will force attributes when an attribute value is determined to contain a newline character or if the defined wrap limit has exceeded. The intent value combined both wrap and newline together, forcing when either condition is met. We also have set a the forceAttribute to break when there are 3 or more attributes.

In this example, we have assumed formatting options as:

import prettify from '@liquify/prettify';

prettify.options({
  language: 'liquid',
   wrap: 80,
  markup: {
    forceAttribute: 3,
    valueForce: 'intent'
  }
})

Before Formatting

{% # all attributes will be forced  due to newline at some-class%}
<div id="foo" data-x="bar" class="{% if condition %}
some-class{% endif %}">

{{ object.prop }}

 {% # no forcing will be applied as value did not exceed wrap and no newlines exist within %}
<div id="xxx" class="{% if foo %}bar-class{% else %}baz-class{% endif %}">

{% # no forcing will be applied as attribute value did not exceed wrap limit and no newlines contained within %}
{{ object.prop }}

{% # the attribute value exceeds wrap so all attributes and the value are forced %}
<div  id="hello" data-x="world" class="{% if condition_wraps %} wrap-exceeds {% else %} indeed-it-does {% endif %}">

{{ object.prop }}      

 {% # lastly, all attributes here are forced due to forceAttribute have a limit of 3  %}
<div id="xxx" data-one="1" data-two="2" data-3="3">
I am forced!
</div>

</div>
</div>
</div>

After Formatting

  {% # all attributes will be forced %}
<div 
  id="foo" 
  data-x="bar" 
  class="
  {% if condition %}
    some-class
  {% endif %}">

  {{ object.prop }}

  {% # no forcing will be applied as value did not exceed wrap and no newlines exist within %}
  <div id="xxx" class="{% if foo %}bar-class{% else %}baz-class{% endif %}">

    {{ object.prop }}
    
    {% # the attribute value exceeds wrap so all attributes and the value are forced %}
    <div 
      id="hello" 
      data-x="world" 
      class="
      {% if condition %}
        wrap-exceeds
      {% else %}
        indeed-it-does
      {% endif %}">

      {{ object.prop }}

      {% # lastly, all attributes here are forced due to forceAttribute have a limit of 3  %}
      <div
         id="xxx" 
         data-one="1" 
         data-two="2" 
         data-3="3">
        I am forced!
      </div>      

    </div>
  </div>
</div>

wrap

Setting this rule to wrap will force attributes when an attribute value containing Liquid expressions exceeds word wrap limit. This option expects a wrap limit to be defined. Take the below code sample, assuming a wrap limit has been defined, the result would look as followed:

In this example, we have assumed formatting options as:

import prettify from '@liquify/prettify';

prettify.options({
  language: 'liquid',
   wrap: 80,
  markup: {
    valueForce: 'always'
  }
})

Before Formatting

{% # all attributes will be forced %}
<div id="foo" data-x="bar" class="{% if condition %}some-class{% else %}another-class{% endif %}">

{{ object.prop }}

</div>

After Formatting

{% # all attributes will be forced %}
<div 
  id="foo" 
  data-x="bar" 
  class="
  {% if condition %}
    some-class
  {% else %}
    another-class
  {% endif %}">

  {{ object.prop }}

</div>

newline

Setting this rule to newline will force attributes when an attribute value with a Liquid expressions contains a newline character. The newline character is a signal to Prettify that the all attributes and the value should be forced. The rule will only ever be invoked when a newline is provided, it does not matter where in the value string.

In this example, we have assumed formatting options as:

import prettify from '@liquify/prettify';

prettify.options({
  language: 'liquid',
  markup: {
    forceAttribute: false,
    valueForce: 'newline'
  }
})

Before Formatting

{% # all attributes will be forced %}
<div id="foo" data-x="bar" class="{% if condition %}
some-class{% else %}another-class{% endif %}">

{{ object.prop }}

</div>

After Formatting

{% # all attributes will be forced %}
<div 
  id="foo" 
  data-x="bar" 
  class="
  {% if condition %}
    some-class
  {% else %}
    another-class
  {% endif %}">

  {{ object.prop }}

</div>

always

Setting this rule to always will force attributes whenever a value contains a Liquid tag block. Like output tag tokens will no apply forced values. If you have set forceAttribute to be applied based on number of attributes, then forcing will be applied as usual but values that do not explicitly meet the conditions will behave as normal.

In this example, we have assumed formatting options as:

import prettify from '@liquify/prettify';

prettify.options({
  language: 'liquid',
  markup: {
    forceAttribute: 3,
    valueForce: 'always'
  }
})

Before Formatting

{% # all attributes will be forced %}
<div id="foo" data-x="bar" class="{% if condition %}some-class{% else %}another-class{% endif %}">

{{ object.prop }}

{% # Value will not be forced as no tag block is contained but attributes will due forceAttribute rule%}
<div id="foo" data-x="bar" class="class-name some-other class {{ some.thing | filter: 'foo' | append: 'abcdefg' }}">

{{ object.prop }}

</div>
</div>

After Formatting

{% # all attributes will be forced %}
<div 
  id="foo" 
  data-x="bar" 
  class="
  {% if condition %}
    some-class
  {% else %}
    another-class
  {% endif %}">

  {{ object.prop }}

  {% # Value will not be forced as no tag block is contained but attributes will due forceAttribute rule%}
  <div 
    id="foo" 
    data-x="bar" 
    class="class-name some-other class {{ some.thing | filter: 'foo' | append: 'abcdefg' }}">

   {{ object.prop }}

  </div>
</div>

never

Setting the rule to never will disable force value behaviour.

import prettify from '@liquify/prettify';

prettify.options({
  language: 'liquid',
  markup: {
    forceAttribute: 3,
    valueForce: 'never'
  }
})

Before Formatting

{% # all attributes will be forced %}
<div id="foo" data-x="bar" class="{% if condition %}some-class{% else %}another-class{% endif %}">

{{ object.prop }}

{% # Value will not be forced as no tag block is contained but attributes will due forceAttribute rule%}
<div id="foo" data-x="bar" class="class-name some-other class {{ some.thing | filter: 'foo' | append: 'abcdefg' }}">

{{ object.prop }}

</div>
</div>

After Formatting

{% # all attributes will be forced %}
<div 
  id="foo" 
  data-x="bar" 
  class="{% if condition %} some-class {% else %} another-class{% endif %}">

  {{ object.prop }}

  {% # Value will not be forced as no tag block is contained but attributes will due forceAttribute rule%}
  <div 
    id="foo" 
    data-x="bar" 
    class="class-name some-other class {{ some.thing | filter: 'foo' | append: 'abcdefg' }}">

   {{ object.prop }}

  </div>
</div>

Delimiter defect when singular Liquid output token

Description

Minor defect which occurs when a singular Liquid output token is attempted to be formatted. This only occurs when the provided input string contains a single Liquid output tag.

Input

Take the following input.

{{ object.prop }}

Current Result

The last delimiter brace will be indented onto a new line.

{{ object.prop }
}

Expected Result

The parser should not touch or augment the structure beyond the applied rules.

{{ object.prop }}

Custom Embedded Regions

Description

Custom embedded regions allow users to use different lexers on inner contents of tags. This caters for more advanced use cases and can be somewhat expensive so consideration is expected. The option is exposed in the global rulesets as a grammar definition and can be used to control the content of tokens. This extensibility is mostly geared toward markup languages, specifically Liquid.

Reasoning

In template languages like Liquid, you can extend its functionality. If you are working on an 11ty or Jekyll project then you can quite implement custom tags and in some cases the contents of these custom tags infer an embedded language. In the Shopify Liquid variation, the {% schema %} tags contains JSON and the {% style %} tag contains CSS and as such it's expected that beautification is handled accordingly, ie: their contents should be beautified as per inference.

Allowing users to set custom embedded regions means that they are not limited to the default support employed and can more freely write code. For example, let's say we are working on a 11ty project and have created a custom plugin that processes the content of tags named {% json_ld %} {% endjson_ld %} as JSON. If you are using Shopify's Prettier Plugin there is no possible way you can have the contents of those custom tags be beautified according to JSON but in Prettify with custom embedded regions this otherwise impossible task can easily be handled by defining the tag name json_ld as an embedded region.

This ruleset goes beyond just custom tags and can also be leverage on {% capture %} tokens, wherein the contents of a capture code block names {% capture style_code %} {% endcapture %} can be formatted according to style beautification options.

Goals

The goals for embedded regions is to allow users to maintain consistency in their code and is mostly geared toward Liquid projects, but it does also support HTML custom tags. The ruleset should be easy enough for novice users but also for users with more experience. Attributes need be considered and in order to facilitate the most extensible solution here, users will be able to pass in regular expressions.

Ruleset

In order to leverage and define custom embedded regions, one needs to extend the embedded grammar references. Grammar references can be used to help Prettify reason with input. This option is available in globals and can only be passed to HTML and Liquid grammars, each language identifier reference will be provided to users in typing completions.

esthetic.grammar({
   html: {
    embedded: {
      json: [],
      css: [],
      scss: [],
      javascript: [],
      // etc etc
    },
   liquid: {
    embedded: {
      json: [],
      css: [],
      scss: [],
      javascript: [],
      // etc etc
    }
  }
})

Example

In the below example we are extending the grammars to support multiple custom embedded regions in Liquid and single custom HTML tag. Notice how we leveraged a regular expression in ['stylesheet', /\s+['"]scss['"]/] in order to capture the attribute of the Liquid tag.
.

esthetic.grammar({
   html: {
    embedded: {
      json: ['json-tag'],
    },
    liquid: {
      embedded: {
        json: [
          ['schema'],
          ['json_ld'],
          ['capture', 'some_json'],
        ],
        css: [
          ['style'],
          ['stylesheet'],
        ],
        scss: [
          ['stylesheet', /\s+['"]scss['"]/],
        ],
        javascript: [
          ['javascript'],
          ['capture', 'some_js']
        ],
      }
    }
})

The above grammar definition will result in the following:

<!-- All content contained  in these tags will be beautified as CSS -->
{% style %}
  .foo { font-size: 10px; }
{% endstyle%}

<!-- All content contained  in these tags will be beautified as CSS -->
{% stylesheet %}
  .foo { font-size: 10px; }
{% endstylesheet %}

<!-- All content contained  in these tags will be beautified as SCSS -->
{% stylesheet 'scss' %}
  .foo { .bar { font-size: 10px; } }
{% endstylesheet %}

<!-- All content contained  in these tags will be beautified as JavaScript -->
{% javascript %}
  const foo = 'bar';
{% endjavascript %}

<!-- All content contained  in these tags will be beautified as JavaScript -->
{% capture some_js %}
  const foo = 'bar';
{% endcapture %}

<!-- All content contained  in these tags will be beautified as JSON -->
{% schema %}
{ "foo": "bar" }
{% endschema %}

<!-- All content contained  in these tags will be beautified as JSON -->
{% json_ld %}
{ "foo": "bar" }
{% endjson_ld %}

<!-- All content contained  in these tags will be beautified as JSON -->
{% capture some_json %}
{ "foo": "bar" }
{% endcapture %}

<!-- All content contained  in these tags will be beautified as JSON -->
<json-tag>
{ "foo": "bar" }
</json-tag>

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.