@@ -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+
79138impl < ' 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