Twig icon indicating copy to clipboard operation
Twig copied to clipboard

Whitespace control - Trim left spaces

Open brain-diminished opened this issue 3 years ago • 6 comments

If an expression {{ }} or statement {% %} is directly followed by a linebreak, which is pretty often, it seems to be automatically removed. For instance:

<tag>
{% set nothing = 42 %}
</tag>

would output:

<tag>
</tag>

and not:

<tag>

</tag>

If we extend this reasoning, wouldn't it make sense to also trim spaces at the left of the expression/statement, in the case there are only spaces at the start of the line? To illustrate, let's reuse previous example with indentation:

    <tag>
    {% set nothing = 42 %}
    </tag>

It now outputs:

    <tag>
        </tag>

Currently, to maintain a good indentation, I always use a left space-only trim ~:

    <tag>
    {%~ set nothing = 42 %}
    </tag>

The use of ~ being pretty much systematic, I think it would be fully legitimate to infer it when the left part of the current line contains only spaces. I would agree that deciding to remove left spaces would mean that we would force the removal of "information" that, in some case that I cannot think of at the moment, some might actually want to preserve. Then, once again, Twig already does something similar by implicitly removing some linebreaks.

Therefore, I think a more natural way to deduce removable content would be to arbitrate on the whole line: if only composed of spaces apart from the statement, the whole line would be trimmed, including the ending linebreak, otherwise none of it. Moreover, this rule makes sense on a statement or comment, but not so much on an expression, which is expected to output something.

Two last exemples:

  <tag>{% for n in 0..3 %}
    {{ n }}
  {% endfor %}</tag>

outputs:

  <tag>    0
      1
      2
      3
  </tag>

whilst I believe one could legitimately expect to obtain instead:

  <tag>
    0
    1
    2
    3
  </tag>

And:

<tag>{# Some comment #}
</tag>

outputs:

<tag></tag>

whilst we could expect instead:

<tag>
</tag>

To sum up, the idea is quite simple: if a whole line (including the finishing CR/LF) is only composed of spaces, comments and statements, it shouldn't be rendered. Otherwise, everything should be preserved (including the finishing CR/LF).

What do you think?

brain-diminished avatar Apr 14 '22 01:04 brain-diminished

See https://twig.symfony.com/doc/3.x/templates.html#whitespace-control

stof avatar Apr 14 '22 08:04 stof

See https://twig.symfony.com/doc/3.x/templates.html#whitespace-control

I have question about whitespace control related to this. My test scenario:

--TEST--
Test line ending
--TEMPLATE--
{# without modifiers #}
first line {% if 1 > 0 %}1{% endif %}
and second line on new line

first line {% if 1 > 0 %}2{% endif %}{# with comment at end line #}
and second line on new line

{# with triminig whitespace except newline #}
first line {%~ if 1 > 0 %}3{% endif ~%}
and second line on new line

{# with whitespace at end of line #}
first line {% if 1 > 0 %}4{% endif %} 
and second line on new line

line before for
{% for i in range(1, 9) %}
	{{- i -}}
{% endfor %}
new line after endfor
--DATA--
return []
--EXPECT--
first line 1
and second line on new line

first line 2
and second line on new line

first line 3
and second line on new line

first line 4 
and second line on new line

line before for
123456789
new line after endfor

And output is

Test line ending (in whitespace/trim_line_ending.test)
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'first line 1\n
-and second line on new line\n
+'first line 1and second line on new line\n
 \n
-first line 2\n
-and second line on new line\n
+first line 2and second line on new line\n
 \n
-first line 3\n
+first line3\n
 and second line on new line\n
 \n
 first line 4 \n
@@ @@
 and second line on new line\n
 \n
 line before for\n
-123456789\n
-new line after endfor'
+123456789new line after endfor'

It not documented and lead to some kinds of errors. For example it can be dangerous in js:

var isFoo = {% if o.isFoo %}true{% else %}false{% endif %}
var isBar = true

and instead

var isFoo = true
var isBar = true

we got a wrong js-code

var isFoo = truevar isBar = true

pudovmaxim avatar Apr 13 '23 13:04 pudovmaxim

I make demo on raw php and twig https://onlinephp.io/c/d6084 https://twigfiddle.com/9h6dsl

And twig already uses different approaches for removing line endings after output and execution tags. In php, removing line endings is useful for a few reasons. This was discussed on the mailing list in 1998 (25 years ago!) https://marc.info/?t=90279165800002&r=1&w=2

But why does this rule about deleting a newline after closing a tag exist in Twig?

pudovmaxim avatar Apr 14 '23 07:04 pudovmaxim

It not documented and lead to some kinds of errors.

This is actually documented. It is the first sentence in the documentation section that I linked to you:

The first newline after a template tag is removed automatically (like in PHP).

stof avatar Apr 14 '23 07:04 stof

This is actually documented. It is the first sentence in the documentation section that I linked to you:

Yes, sorry about that. Misunderstood the meaning and thought about the line ending at end of template.

But the question is still open:

Why is this principle from php is used in twig? And how is it possible to disable it globally?

pudovmaxim avatar Apr 14 '23 08:04 pudovmaxim

you cannot disable it globally.

stof avatar Apr 14 '23 08:04 stof