Repository: sveltejs/language-tools
Pull request #1024 (2021-05-24 10:43:06)

(fix) better transform of custom css properties
Transform instead of delete to ensure better DX (correct mappings) and to not break if the property value does contain for example a store which leads to a inner transform.
#1023
#974


Patch:
diff --git a/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts b/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts
index baf5b609..8b954ccb 100644
--- a/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts
+++ b/packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts
@@ -82,9 +82,10 @@ export function handleAttribute(
         }
     }
 
-    // Custom property -> remove completely due to JSX incompatibility
-    if (parent.type === 'InlineComponent' && attr.name.startsWith('--')) {
-        str.overwrite(attr.start, attr.end, '', { contentOnly: true });
+    // Custom CSS property
+    if (parent.type === 'InlineComponent' && attr.name.startsWith('--') && attr.value !== true) {
+        str.prependRight(attr.start, '{...__sveltets_cssProp({"');
+        buildTemplateString(attr, str, htmlx, '": `', '`})}');
         return;
     }
 
@@ -169,23 +170,30 @@ export function handleAttribute(
         return;
     }
 
-    // we have multiple attribute values, so we build a string out of them.
-    // technically the user can do something funky like attr="text "{value} or even attr=text{value}
-    // so instead of trying to maintain a nice sourcemap with prepends etc, we just overwrite the whole thing
+    // We have multiple attribute values, so we build a template string out of them.
+    buildTemplateString(attr, str, htmlx, '={`', '`}');
+}
 
+function buildTemplateString(
+    attr: Attribute,
+    str: MagicString,
+    htmlx: string,
+    leadingOverride: string,
+    trailingOverride: string
+) {
     const equals = htmlx.lastIndexOf('=', attr.value[0].start);
-    str.overwrite(equals, attr.value[0].start, '={`');
+    str.overwrite(equals, attr.value[0].start, leadingOverride);
 
-    for (const n of attr.value) {
+    for (const n of attr.value as BaseNode[]) {
         if (n.type == 'MustacheTag') {
             str.appendRight(n.start, '$');
         }
     }
 
     if (isQuote(htmlx[attr.end - 1])) {
-        str.overwrite(attr.end - 1, attr.end, '`}');
+        str.overwrite(attr.end - 1, attr.end, trailingOverride);
     } else {
-        str.appendLeft(attr.end, '`}');
+        str.appendLeft(attr.end, trailingOverride);
     }
 }
 
diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts
index 379aec9e..182a1b4e 100644
--- a/packages/svelte2tsx/svelte-shims.d.ts
+++ b/packages/svelte2tsx/svelte-shims.d.ts
@@ -105,6 +105,7 @@ declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {}
 declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {};
 declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
 declare function __sveltets_ensureType<T>(type: AConstructorTypeOf<T>, el: T): {};
+declare function __sveltets_cssProp(prop: Record<string, any>): {};
 declare function __sveltets_ctorOf<T>(type: T): AConstructorTypeOf<T>;
 declare function __sveltets_instanceOf<T = any>(type: AConstructorTypeOf<T>): T;
 declare function __sveltets_allPropsType(): SvelteAllProps


Test patch:
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx
index be7cc3a5..7259b44d 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/component-slot-infer-props/expected.jsx
@@ -1,4 +1,4 @@
-<><Parent bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}}  >{() => {/*Ωignore_startΩ*/const Ψcomplex={a},Ψa_dashed_complex={a};/*Ωignore_endΩ*/() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
+<><Parent bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}} {...__sveltets_cssProp({"--custom-cssprop": `foo`})} >{() => {/*Ωignore_startΩ*/const Ψcomplex={a},Ψa_dashed_complex={a};/*Ωignore_endΩ*/() => { let {foo} = /*Ωignore_startΩ*/new Parent({target: __sveltets_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['default'];<>
     {() => { let {bar} = /*Ωignore_startΩ*/new Parent({target: __sveltets_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':Ψcomplex, 'a-dashed-complex':Ψa_dashed_complex}})/*Ωignore_endΩ*/.$$slot_def['named'];<><Component  >
         {foo} {bar}
     </Component></>}}
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/expected.jsx
index 04ffa146..64bc8614 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/expected.jsx
@@ -1,1 +1,7 @@
-<><Component   betweenprop   /></>
\ No newline at end of file
+<><Component 
+  {...__sveltets_cssProp({"--custom-css-property1": `${'hi'}`})}
+  {...__sveltets_cssProp({"--custom-css-property2": `hi`})}
+  betweenprop
+  {...__sveltets_cssProp({"--custom-css-property3": `hi${jo}hi`})}
+  {...__sveltets_cssProp({"--custom-css-property4": `hi${jo}hi`})}
+  {...__sveltets_cssProp({"--custom-css-property5": `hi${jo}hi`})} /></>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/input.svelte
index 9c5957b2..22f7f271 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/input.svelte
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/custom-css-properties/input.svelte
@@ -1,1 +1,7 @@
-<Component --custom-css-property1={'hi'} --custom-css-property2="hi" betweenprop  --custom-css-property3="hi{jo}hi" />
+<Component 
+  --custom-css-property1={'hi'}
+  --custom-css-property2="hi"
+  betweenprop
+  --custom-css-property3="hi{jo}hi"
+  --custom-css-property4='hi{jo}hi'
+  --custom-css-property5=hi{jo}hi />
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx
index c5fa4e29..a76072aa 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/event-handler-component-infer-props/expected.jsx
@@ -1,1 +1,1 @@
-<><Component bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}}   />{/*Ωignore_startΩ*/new Component({target: __sveltets_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':{a}, 'a-dashed-complex':{a}}})/*Ωignore_endΩ*/.$on('click', e => e)}</>
\ No newline at end of file
+<><Component bare shorthand={shorthand} text1="val1" text2="val2" text3={`a${a}b${b}`} textEmpty="" literal={true} strLiteral={'foo'} complex={{a}} a-dashed-complex={{a}} {...__sveltets_cssProp({"--custom-cssprop": `foo`})}  />{/*Ωignore_startΩ*/new Component({target: __sveltets_any(''), props: {'bare':true, 'shorthand':shorthand, 'text1':"", 'text2':"", 'text3':"", 'textEmpty':"", 'literal':true, 'strLiteral':"", 'complex':{a}, 'a-dashed-complex':{a}}})/*Ωignore_endΩ*/.$on('click', e => e)}</>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/custom-css-properties-with-$store/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/custom-css-properties-with-$store/expected.tsx
new file mode 100644
index 00000000..c6d3e11f
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/custom-css-properties-with-$store/expected.tsx
@@ -0,0 +1,11 @@
+///<reference types="svelte" />
+<></>;function render() {
+<><Component 
+  {...__sveltets_cssProp({"--custom-css-property1": `${(__sveltets_store_get(jo), $jo)}`})}
+  {...__sveltets_cssProp({"--custom-css-property2": `hi${(__sveltets_store_get(jo), $jo)}hi`})}
+  {...__sveltets_cssProp({"--custom-css-property3": `hi${(__sveltets_store_get(jo), $jo)}hi`})}
+  {...__sveltets_cssProp({"--custom-css-property4": `hi${(__sveltets_store_get(jo), $jo)}hi`})} /></>
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/custom-css-properties-with-$store/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/custom-css-properties-with-$store/input.svelte
new file mode 100644
index 00000000..c1395f87
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/custom-css-properties-with-$store/input.svelte
@@ -0,0 +1,5 @@
+<Component 
+  --custom-css-property1={$jo}
+  --custom-css-property2="hi{$jo}hi"
+  --custom-css-property3='hi{$jo}hi'
+  --custom-css-property4=hi{$jo}hi />