Skip to content

Commit 2a065bb

Browse files
authored
Merge pull request #112 from embroider-build/nvp/indentation-stripping
indentation stripping
2 parents c86349a + a087571 commit 2a065bb

File tree

3 files changed

+420
-1
lines changed

3 files changed

+420
-1
lines changed

src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,31 @@ testcase! {
324324
return template_UUID(``, { eval() { return eval(arguments[0]) } });
325325
}"#
326326
}
327+
328+
testcase! {
329+
extraneous_indentation_strip,
330+
r#"let x = <template>
331+
hello
332+
</template>"#,
333+
r#"import { template as template_UUID } from "@ember/template-compiler";
334+
let x = template_UUID(`hello`, { eval() { return eval(arguments[0])} });"#
335+
}
336+
337+
testcase! {
338+
multiline_extraneous_indentation_strip,
339+
r#"let x = <template>
340+
hello
341+
342+
extra line break
343+
<div>
344+
content
345+
</div>
346+
</template>"#,
347+
r#"import { template as template_UUID } from "@ember/template-compiler";
348+
let x = template_UUID(`hello
349+
350+
extra line break
351+
<div>
352+
content
353+
</div>`, { eval() { return eval(arguments[0])} });"#
354+
}

src/transform.rs

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ impl<'a> TransformVisitor<'a> {
5454
}
5555

5656
fn content_literal(&self, contents: &Box<ContentTagContent>) -> ExprOrSpread {
57+
let stripped_content = strip_indent(&contents.value);
5758
Box::new(Expr::Tpl(Tpl {
5859
span: contents.span,
5960
exprs: vec![],
6061
quasis: vec![TplElement {
6162
span: contents.span,
6263
cooked: None,
63-
raw: escape_template_literal(&contents.value),
64+
raw: escape_template_literal(&stripped_content.into()),
6465
tail: false,
6566
}],
6667
}))
@@ -76,6 +77,64 @@ fn escape_template_literal(input: &Atom) -> Atom {
7677
.into()
7778
}
7879

80+
fn strip_indent(input: &str) -> String {
81+
let lines: Vec<&str> = input.lines().collect();
82+
83+
if lines.len() <= 1 {
84+
return input.to_string();
85+
}
86+
87+
let start = lines.iter().position(|l| !l.trim().is_empty()).unwrap_or(lines.len());
88+
let end = lines.iter().rposition(|l| !l.trim().is_empty()).map(|i| i + 1).unwrap_or(0);
89+
90+
if start >= end {
91+
return String::new();
92+
}
93+
94+
let lines = &lines[start..end];
95+
96+
let mut min_indent: Option<usize> = None;
97+
let mut has_spaces = false;
98+
let mut has_tabs = false;
99+
100+
for line in lines {
101+
let content = line.trim_start();
102+
if content.is_empty() {
103+
continue;
104+
}
105+
106+
let indent_size = line.len() - content.len();
107+
let indent_chars = &line[..indent_size];
108+
109+
has_spaces |= indent_chars.contains(' ');
110+
has_tabs |= indent_chars.contains('\t');
111+
112+
if has_spaces && has_tabs {
113+
return lines.join("\n");
114+
}
115+
116+
min_indent = Some(min_indent.map_or(indent_size, |current| current.min(indent_size)));
117+
}
118+
119+
let min_indent = min_indent.unwrap_or(0);
120+
121+
if min_indent == 0 {
122+
return lines.join("\n");
123+
}
124+
125+
lines
126+
.iter()
127+
.map(|line| {
128+
if line.len() >= min_indent {
129+
&line[min_indent..]
130+
} else {
131+
line
132+
}
133+
})
134+
.collect::<Vec<_>>()
135+
.join("\n")
136+
}
137+
79138
impl<'a> VisitMut for TransformVisitor<'a> {
80139
fn visit_mut_expr(&mut self, n: &mut Expr) {
81140
n.visit_mut_children_with(self);
@@ -301,3 +360,140 @@ test!(
301360
r#"let x = <template>Hello\nWorld\u1234</template>"#,
302361
r#"let x = template(`Hello\\nWorld\\u1234`, { eval() { return eval(arguments[0]) }})"#
303362
);
363+
364+
test!(
365+
Default::default(),
366+
|_| as_folder(TransformVisitor::new(
367+
&Ident::new("template".into(), Default::default()),
368+
None,
369+
)),
370+
strips_leading_trailing_whitespace,
371+
r#"let x = <template>
372+
<span>Hello</span>
373+
</template>"#,
374+
r#"let x = template(`<span>Hello</span>`, { eval() { return eval(arguments[0]) }})"#
375+
);
376+
377+
test!(
378+
Default::default(),
379+
|_| as_folder(TransformVisitor::new(
380+
&Ident::new("template".into(), Default::default()),
381+
None,
382+
)),
383+
strips_common_indentation,
384+
r#"let x = <template>
385+
<div>
386+
<span>Hello</span>
387+
</div>
388+
</template>"#,
389+
r#"let x = template(`<div>
390+
<span>Hello</span>
391+
</div>`, { eval() { return eval(arguments[0]) }})"#
392+
);
393+
394+
test!(
395+
Default::default(),
396+
|_| as_folder(TransformVisitor::new(
397+
&Ident::new("template".into(), Default::default()),
398+
None,
399+
)),
400+
strips_indentation_multiline,
401+
r#"<template>
402+
Hello
403+
<span>there</span>.
404+
<p>
405+
<span>how are you</span>
406+
</p>
407+
</template>"#,
408+
r#"export default template(`Hello
409+
<span>there</span>.
410+
<p>
411+
<span>how are you</span>
412+
</p>`, { eval() { return eval(arguments[0]) }},);"#
413+
);
414+
415+
test!(
416+
Default::default(),
417+
|_| as_folder(TransformVisitor::new(
418+
&Ident::new("template".into(), Default::default()),
419+
None,
420+
)),
421+
class_member_strips_indentation,
422+
r#"class X {
423+
<template>
424+
<span>Hello</span>
425+
</template>
426+
}"#,
427+
r#"class X {
428+
static {
429+
template(`<span>Hello</span>`, { component: this, eval() { return eval(arguments[0]) }},);
430+
}
431+
}"#
432+
);
433+
434+
test!(
435+
Default::default(),
436+
|_| as_folder(TransformVisitor::new(
437+
&Ident::new("template".into(), Default::default()),
438+
None,
439+
)),
440+
deeply_nested_indentation,
441+
r#"class Component {
442+
method() {
443+
return <template>
444+
<div>
445+
<span>Nested</span>
446+
</div>
447+
</template>;
448+
}
449+
}"#,
450+
r#"class Component {
451+
method() {
452+
return template(`<div>
453+
<span>Nested</span>
454+
</div>`, { eval() { return eval(arguments[0]) }});
455+
}
456+
}"#
457+
);
458+
459+
test!(
460+
Default::default(),
461+
|_| as_folder(TransformVisitor::new(
462+
&Ident::new("template".into(), Default::default()),
463+
None,
464+
)),
465+
preserves_internal_indentation,
466+
r#"let x = <template>
467+
<div>
468+
<pre>
469+
some code
470+
with indentation
471+
</pre>
472+
</div>
473+
</template>"#,
474+
r#"let x = template(`<div>
475+
<pre>
476+
some code
477+
with indentation
478+
</pre>
479+
</div>`, { eval() { return eval(arguments[0]) }})"#
480+
);
481+
482+
test!(
483+
Default::default(),
484+
|_| as_folder(TransformVisitor::new(
485+
&Ident::new("template".into(), Default::default()),
486+
None,
487+
)),
488+
opt_out_with_comment,
489+
r#"let x = <template>
490+
{{!-- prevent automatic de-indent --}}
491+
<pre>
492+
content here
493+
</pre>
494+
</template>"#,
495+
r#"let x = template(`{{!-- prevent automatic de-indent --}}
496+
<pre>
497+
content here
498+
</pre>`, { eval() { return eval(arguments[0]) }})"#
499+
);

0 commit comments

Comments
 (0)