Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/language-core/lib/codegen/template/elementProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,10 @@ export function* generatePropExp(
exp.loc.source,
'template',
exp.loc.start.offset,
codeFeatures.withoutHighlightAndCompletion,
{
...codeFeatures.withoutHighlightAndCompletion,
__shorthandExpression: 'html',
},
);

if (ctx.scopes.some(scope => scope.has(propVariableName))) {
Expand Down
6 changes: 4 additions & 2 deletions packages/language-core/lib/codegen/template/interpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export function* generateInterpolation(
start + offset,
type === 'errorMappingOnly'
? codeFeatures.verification
: type === 'shorthand'
? { ...data, __shorthandExpression: 'js' }
: data,
];
}
Expand All @@ -81,7 +83,7 @@ function* forEachInterpolationSegment(
[
code: string,
offset: number,
type?: 'errorMappingOnly' | 'startEnd',
type?: 'errorMappingOnly' | 'shorthand' | 'startEnd',
] | string
> {
const code = prefix + originalCode + suffix;
Expand Down Expand Up @@ -120,7 +122,7 @@ function* forEachInterpolationSegment(
yield names.ctx;
}
yield `.`;
yield [name, offset];
yield [name, offset, isShorthand ? 'shorthand' : undefined];
}

prevEnd = offset + name.length;
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' |

export interface VueCodeInformation extends CodeInformation {
__importCompletion?: boolean;
__shorthandExpression?: 'html' | 'js';
__combineToken?: symbol;
__linkedToken?: symbol;
}
Expand Down
173 changes: 173 additions & 0 deletions packages/language-server/tests/renaming.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,7 @@ test('Scoped Classes', async () => {
"line": 5,
"offset": 20,
},
"suffixText": ": foo",
},
{
"end": {
Expand Down Expand Up @@ -1239,6 +1240,178 @@ test('Template Ref', async () => {
`);
});

test('Same Name Shorthand', async () => {
expect(
await requestRenameToTsServer(
'tsconfigProject/fixture.vue',
'vue',
`
<template>
<Comp :foo-bar| />
{{ new Comp({ fooBar }) }}
</template>

<script lang="ts" setup>
const fooBar = 1;
</script>
`,
),
).toMatchInlineSnapshot(`
{
"info": {
"canRename": true,
"displayName": "fooBar",
"fullDisplayName": "__object.fooBar",
"kind": "property",
"kindModifiers": "",
"triggerSpan": {
"end": {
"line": 3,
"offset": 19,
},
"start": {
"line": 3,
"offset": 12,
},
},
},
"locs": [
{
"file": "\${testWorkspacePath}/tsconfigProject/fixture.vue",
"locs": [
{
"contextEnd": {
"line": 8,
"offset": 21,
},
"contextStart": {
"line": 8,
"offset": 4,
},
"end": {
"line": 8,
"offset": 16,
},
"start": {
"line": 8,
"offset": 10,
},
},
{
"end": {
"line": 4,
"offset": 25,
},
"prefixText": "fooBar: ",
"start": {
"line": 4,
"offset": 19,
},
},
{
"end": {
"line": 3,
"offset": 19,
},
"prefixText": "foo-bar="",
"start": {
"line": 3,
"offset": 12,
},
"suffixText": """,
},
],
},
],
}
`);
expect(
await requestRenameToTsServer(
'tsconfigProject/fixture.vue',
'vue',
`
<template>
<Comp :foo-bar />
{{ new Comp({ fooBar }) }}
</template>

<script lang="ts" setup>
declare function Comp(props: {
fooBar|: number;
}): void;
</script>
`,
),
).toMatchInlineSnapshot(`
{
"info": {
"canRename": true,
"displayName": "fooBar",
"fullDisplayName": "__type.fooBar",
"kind": "property",
"kindModifiers": "declare",
"triggerSpan": {
"end": {
"line": 9,
"offset": 11,
},
"start": {
"line": 9,
"offset": 5,
},
},
},
"locs": [
{
"file": "\${testWorkspacePath}/tsconfigProject/fixture.vue",
"locs": [
{
"end": {
"line": 4,
"offset": 25,
},
"start": {
"line": 4,
"offset": 19,
},
"suffixText": ": fooBar",
},
{
"end": {
"line": 3,
"offset": 19,
},
"start": {
"line": 3,
"offset": 12,
},
"suffixText": "="fooBar"",
},
{
"contextEnd": {
"line": 9,
"offset": 20,
},
"contextStart": {
"line": 9,
"offset": 5,
},
"end": {
"line": 9,
"offset": 11,
},
"start": {
"line": 9,
"offset": 5,
},
},
],
},
],
}
`);
});

const openedDocuments: TextDocument[] = [];

afterEach(async () => {
Expand Down
114 changes: 113 additions & 1 deletion packages/typescript-plugin/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@volar/typescript/lib/node/transform';
import { getServiceScript } from '@volar/typescript/lib/node/utils';
import { type Language, type VueCodeInformation, type VueCompilerOptions, VueVirtualCode } from '@vue/language-core';
import { capitalize, isGloballyAllowed } from '@vue/shared';
import { camelize, capitalize, isGloballyAllowed } from '@vue/shared';
import type * as ts from 'typescript';

const windowsPathReg = /\\/g;
Expand All @@ -20,6 +20,7 @@ export function preprocessLanguageService(
getSuggestionDiagnostics,
getCompletionsAtPosition,
getCodeFixesAtPosition,
findRenameLocations,
} = languageService;

languageService.getQuickInfoAtPosition = (fileName, position, ...rests) => {
Expand Down Expand Up @@ -200,6 +201,117 @@ export function preprocessLanguageService(
}
return result;
};

languageService.findRenameLocations = (fileName, position, ...rests) => {
// @ts-expect-error
const result = findRenameLocations(fileName, position, ...rests);
if (!result?.length) {
return result;
}

const language = getLanguage();
if (!language) {
return result;
}

const [serviceScript, _targetScript, sourceScript] = getServiceScript(language, fileName);
if (!serviceScript || !(sourceScript?.generated?.root instanceof VueVirtualCode)) {
return result;
}

const map = language.maps.get(serviceScript.code, sourceScript);
const leadingOffset = sourceScript.snapshot.getLength();
const isShorthand = (data: VueCodeInformation) => !!data.__shorthandExpression;

// { foo: __VLS_ctx.foo }
// ^^^ ^^^
// if the rename is triggered directly on the shorthand,
// skip the entire request on the generated property name
if ([...map.toSourceLocation(position - leadingOffset, isShorthand)].length === 0) {
for (const [offset] of map.toSourceLocation(position - leadingOffset, () => true)) {
for (const _ of map.toGeneratedLocation(offset, isShorthand)) {
return;
}
}
}

const preferAlias = typeof rests[2] === 'boolean'
? rests[2]
: rests[2]?.providePrefixAndSuffixTextForRename ?? true;
if (!preferAlias) {
return result;
}

const locations = [...result];
outer: for (let i = 0; i < locations.length; i++) {
const { textSpan } = locations[i]!;
const generatedLeft = textSpan.start - leadingOffset;
const generatedRight = textSpan.start + textSpan.length - leadingOffset;

// { foo: __VLS_ctx.foo }
// ^^^
for (const [start, end, { data }] of map.toSourceRange(generatedLeft, generatedRight, true, isShorthand)) {
locations.splice(i, 1, {
...locations[i]!,
...getPrefixAndSuffixForShorthandRename(
(data as VueCodeInformation).__shorthandExpression!,
'right',
sourceScript.snapshot.getText(start, end),
),
});
continue outer;
}

// { foo: __VLS_ctx.foo }
// ^^^
for (const [start, end] of map.toSourceRange(generatedLeft, generatedRight, true, () => true)) {
for (const [, , { data }] of map.toGeneratedRange(start, end, true, isShorthand)) {
locations.splice(i, 1, {
...locations[i]!,
...getPrefixAndSuffixForShorthandRename(
(data as VueCodeInformation).__shorthandExpression!,
'left',
sourceScript.snapshot.getText(start, end),
),
});
continue outer;
}
}
}
return locations;
};
}

function getPrefixAndSuffixForShorthandRename(
type: 'html' | 'js',
target: 'left' | 'right',
originalText: string,
): Pick<ts.RenameLocation, 'prefixText' | 'suffixText'> {
if (type === 'html') {
if (target === 'left') {
return {
suffixText: `="${camelize(originalText)}"`,
};
}
else {
return {
prefixText: `${originalText}="`,
suffixText: `"`,
};
}
}
else {
if (target === 'left') {
return {
suffixText: `: ${originalText}`,
};
}
else {
return {
prefixText: `${originalText}: `,
};
}
}
}

export function postprocessLanguageService<T>(
Expand Down