diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts index 66eb53b6c..b7a1ffe3e 100644 --- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts @@ -4,7 +4,8 @@ import { mapRangeToOriginal, getLineAtPosition, getNodeIfIsInStartTag, - isInHTMLTagRange + isInHTMLTagRange, + getNodeIfIsInHTMLStartTag } from '../../../lib/documents'; import { filterAsync, isNotNullOrUndefined, pathToUrl, unique } from '../../../utils'; import { RenameProvider } from '../../interfaces'; @@ -503,12 +504,35 @@ export class RenameProviderImpl implements RenameProvider { const { parent } = snapshot; if (this.configManager.getConfig().svelte.useNewTransformation) { + let rangeStart = parent.offsetAt(location.range.start); let prefixText = location.prefixText?.trimRight(); + + // rename needs to be prefixed in case of a bind shortand on a HTML element + if (!prefixText) { + const original = parent.getText({ + start: Position.create( + location.range.start.line, + location.range.start.character - bind.length + ), + end: location.range.end + }); + if ( + original.startsWith(bind) && + getNodeIfIsInHTMLStartTag(parent.html, rangeStart) + ) { + return { + ...location, + prefixText: original.slice(bind.length) + '={', + suffixText: '}' + }; + } + } + if (!prefixText || prefixText.slice(-1) !== ':') { return location; } + // prefix is of the form `oldVarName: ` -> hints at a shorthand - let rangeStart = parent.offsetAt(location.range.start); // we need to make sure we only adjust shorthands on elements/components if ( !getNodeIfIsInStartTag(parent.html, rangeStart) || @@ -524,12 +548,14 @@ export class RenameProviderImpl implements RenameProvider { ) { return location; } + prefixText = prefixText.slice(0, -1) + '={'; location = { ...location, prefixText, suffixText: '}' }; + // rename range needs to be adjusted in case of an attribute shortand if (snapshot.getOriginalText().charAt(rangeStart - 1) === '{') { rangeStart--; @@ -539,6 +565,7 @@ export class RenameProviderImpl implements RenameProvider { end: parent.positionAt(rangeEnd) }; } + return location; } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts index 5754d01d5..f73a43990 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts @@ -117,7 +117,13 @@ export function convertHtmlxToJsx( handleComment(str, node); break; case 'Binding': - handleBinding(str, node as BaseDirective, parent, element); + handleBinding( + str, + node as BaseDirective, + parent, + element, + options.typingsNamespace === 'svelteHTML' + ); break; case 'Class': handleClassDirective(str, node as BaseDirective, element as Element); diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts index 50c2094d2..45bd5a7ce 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts @@ -4,16 +4,18 @@ import { BaseDirective, BaseNode } from '../../interfaces'; import { Element } from './Element'; import { InlineComponent } from './InlineComponent'; -const oneWayBindingAttributes: Map = new Map( - ['clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight'] - .map((e) => [e, 'HTMLDivElement'] as [string, string]) - .concat( - ['duration', 'buffered', 'seekable', 'seeking', 'played', 'ended'].map((e) => [ - e, - 'HTMLMediaElement' - ]) - ) -); +const oneWayBindingAttributes: Set = new Set([ + 'clientWidth', + 'clientHeight', + 'offsetWidth', + 'offsetHeight', + 'duration', + 'buffered', + 'seekable', + 'seeking', + 'played', + 'ended' +]); /** * List of all binding names that are transformed to sth like `binding = variable`. * This applies to readonly bindings and the this binding. @@ -34,7 +36,8 @@ export function handleBinding( str: MagicString, attr: BaseDirective, parent: BaseNode, - element: Element | InlineComponent + element: Element | InlineComponent, + preserveBind: boolean ): void { // bind group on input if (element instanceof Element && attr.name == 'group' && parent.name == 'input') { @@ -70,11 +73,25 @@ export function handleBinding( // other bindings which are transformed to normal attributes/props const isShorthand = attr.expression.start === attr.start + 'bind:'.length; - const name: TransformationArray = isShorthand - ? [[attr.expression.start, attr.expression.end]] - : [[attr.start + 'bind:'.length, str.original.lastIndexOf('=', attr.expression.start)]]; + const name: TransformationArray = + preserveBind && element instanceof Element + ? // HTML typings - preserve the bind: prefix + isShorthand + ? [`"${str.original.substring(attr.start, attr.end)}"`] + : [ + `"${str.original.substring( + attr.start, + str.original.lastIndexOf('=', attr.expression.start) + )}"` + ] + : // Other typings - remove the bind: prefix + isShorthand + ? [[attr.expression.start, attr.expression.end]] + : [[attr.start + 'bind:'.length, str.original.lastIndexOf('=', attr.expression.start)]]; const value: TransformationArray | undefined = isShorthand - ? undefined + ? preserveBind && element instanceof Element + ? [rangeWithTrailingPropertyAccess(str.original, attr.expression)] + : undefined : [rangeWithTrailingPropertyAccess(str.original, attr.expression)]; if (element instanceof Element) { element.addAttribute(name, value); diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js index 5b8661470..bfef43414 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js @@ -1,2 +1,2 @@ - { svelteHTML.createElement("input", { "type":`text`,value,});} - { svelteHTML.createElement("input", { "type":`checkbox`,checked,});} \ No newline at end of file + { svelteHTML.createElement("input", { "type":`text`,"bind:value":value,});} + { svelteHTML.createElement("input", { "type":`checkbox`,"bind:checked":checked,});} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js index 4cd1d112b..41ce94abb 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js @@ -1,3 +1,3 @@ - { svelteHTML.createElement("input", { "type":`text`,value:test,});} - { svelteHTML.createElement("input", { "type":`text`,value:test,});} - { svelteHTML.createElement("input", { "type":`text`,value:test,});} \ No newline at end of file + { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});} + { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});} + { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js index 2def2fb4c..62ca1fa88 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js @@ -4,4 +4,4 @@ { svelteHTML.createElement("img", { });__sveltets_2_ensureTransition(fade(svelteHTML.mapElementTag('img'),(params)));} { svelteHTML.createElement("img", { });classthing;} { svelteHTML.createElement("img", { });__sveltets_2_ensureAnimation(thing(svelteHTML.mapElementTag('img'),__sveltets_2_AnimationMove,(params)));} - { svelteHTML.createElement("img", { thing:binding,});} \ No newline at end of file + { svelteHTML.createElement("img", { "bind:thing":binding,});} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js index 95d35f951..cac5e098d 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js @@ -1,3 +1,3 @@ { svelteHTML.createElement("input", { });obj.;} - { svelteHTML.createElement("input", { value:obj.,});} + { svelteHTML.createElement("input", { "bind:value":obj.,});} { const $$_Input0C = __sveltets_2_ensureComponent(Input); new $$_Input0C({ target: __sveltets_2_any(), props: { value:obj.,}});} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts index 49a4e21c5..bdb4b83ca 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts +++ b/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts @@ -4,8 +4,8 @@ async () => { { const $$_div0 = svelteHTML.createElement("div", { });$compile_o { const $$_div0 = svelteHTML.createElement("div", { });$compile_options.foo= $$_div0.offsetHeight;} { const $$_div0 = svelteHTML.createElement("div", { });$compile_options = $$_div0;} { const $$_div0 = svelteHTML.createElement("div", { });$compile_options.foo = $$_div0;} - { svelteHTML.createElement("div", { noAssignment:$compile_options,});} - { svelteHTML.createElement("div", { noAssignment:$compile_options.foo,});}}; + { svelteHTML.createElement("div", { "bind:noAssignment":$compile_options,});} + { svelteHTML.createElement("div", { "bind:noAssignment":$compile_options.foo,});}}; return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {