Skip to content

Commit 690c988

Browse files
committed
Merge branch 'remove-used-attributes-from-suggestion' of https://github.com/sapphi-red/vetur into sapphi-red-remove-used-attributes-from-suggestion
2 parents 107ba04 + e8f9635 commit 690c988

File tree

7 files changed

+69
-35
lines changed

7 files changed

+69
-35
lines changed

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
server/src/modes/template/test/completion.test.ts

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
- Show deprecated hint in script block.
66
- Infer wrong vue version when no `dependencies` field in package.json. #2632
7-
- Fix building in directory that has space in the path when development. Thanks to contribution from [@jasonlyu123](https://github.com/jasonlyu123). #2641.
7+
- 🙌 Fix building in directory that has space in the path when development. Thanks to contribution from [@jasonlyu123](https://github.com/jasonlyu123). #2641.
8+
- 🙌 Remove used attributes from suggestions. Thanks to contribution from [@sapphi-red](https://github.com/sapphi-red).
89

910
### 0.31.3 | 2020-12-13 | [VSIX](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/octref/vsextensions/vetur/0.31.3/vspackage)
1011

server/src/modes/template/services/htmlCompletion.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NULL_COMPLETION } from '../../nullMode';
1616
import { getModifierProvider, Modifier } from '../modifierProvider';
1717
import { toMarkupContent } from '../../../utils/strings';
1818
import { Priority } from '../tagProviders/common';
19+
import { kebabCase } from 'lodash';
1920

2021
export function doComplete(
2122
document: TextDocument,
@@ -147,8 +148,15 @@ export function doComplete(
147148
return result;
148149
}
149150

151+
function getUsedAttributes(offset: number) {
152+
const node = htmlDocument.findNodeBefore(offset);
153+
return new Set(node.attributeNames.map(normalizeAttributeNameToKebabCase));
154+
}
155+
150156
function collectAttributeNameSuggestions(nameStart: number, nameEnd: number = offset): CompletionList {
151-
const execArray = /^[:@]/.exec(scanner.getTokenText());
157+
const usedAttributes = getUsedAttributes(nameStart);
158+
const currentAttribute = scanner.getTokenText();
159+
const execArray = /^[:@]/.exec(currentAttribute);
152160
const filterPrefix = execArray ? execArray[0] : '';
153161
const start = filterPrefix ? nameStart + 1 : nameStart;
154162
const range = getReplaceRange(start, nameEnd);
@@ -158,6 +166,18 @@ export function doComplete(
158166
tagProviders.forEach(provider => {
159167
const priority = provider.priority;
160168
provider.collectAttributes(currentTag, (attribute, type, documentation) => {
169+
if (
170+
// include current typing attribute for completing `="$1"`
171+
!(attribute === currentAttribute && text[nameEnd] !== '=') &&
172+
// can listen to same event by adding modifiers
173+
type !== 'event' &&
174+
// `class` and `:class`, `style` and `:style` can coexist
175+
attribute !== 'class' &&
176+
attribute !== 'style' &&
177+
usedAttributes.has(normalizeAttributeNameToKebabCase(attribute))
178+
) {
179+
return;
180+
}
161181
if ((type === 'event' && filterPrefix !== '@') || (type !== 'event' && filterPrefix === '@')) {
162182
return;
163183
}
@@ -392,3 +412,26 @@ function getWordEnd(s: string, offset: number, limit: number): number {
392412
}
393413
return offset;
394414
}
415+
416+
export function normalizeAttributeNameToKebabCase(attr: string): string {
417+
let result = attr;
418+
419+
if (result.startsWith('v-model:')) {
420+
result = attr.slice('v-model:'.length);
421+
}
422+
423+
if (result.startsWith('v-bind:')) {
424+
result = attr.slice('v-bind:'.length);
425+
} else if (result.startsWith(':')) {
426+
result = attr.slice(':'.length);
427+
}
428+
429+
// Remove modifiers
430+
if (result.includes('.')) {
431+
result = result.slice(0, result.indexOf('.'));
432+
}
433+
434+
result = kebabCase(result);
435+
436+
return result;
437+
}

server/src/modes/template/services/vuePropValidation.ts

+5-25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { TextDocument } from 'vscode-languageserver-textdocument';
44
import { HTMLDocument, Node } from '../parser/htmlParser';
55
import { kebabCase } from 'lodash';
66
import { getSameTagInSet } from '../tagProviders/common';
7+
import { normalizeAttributeNameToKebabCase } from './htmlCompletion';
78

89
export function doPropValidation(document: TextDocument, htmlDocument: HTMLDocument, info: VueFileInfo): Diagnostic[] {
910
const diagnostics: Diagnostic[] = [];
@@ -50,7 +51,7 @@ function generateDiagnostic(n: Node, definedProps: PropInfo[], document: TextDoc
5051
const seenProps = n.attributeNames.map(attr => {
5152
return {
5253
name: attr,
53-
normalized: normalizeHtmlAttributeNameToKebabCase(
54+
normalized: normalizeHtmlAttributeNameToKebabCaseAndReplaceVModel(
5455
attr,
5556
definedProps.find(prop => prop.isBoundToModel)?.name ?? 'value'
5657
)
@@ -86,31 +87,10 @@ function generateDiagnostic(n: Node, definedProps: PropInfo[], document: TextDoc
8687
};
8788
}
8889

89-
function normalizeHtmlAttributeNameToKebabCase(attr: string, modelProp: string) {
90-
let result = attr;
91-
90+
function normalizeHtmlAttributeNameToKebabCaseAndReplaceVModel(attr: string, modelProp: string) {
9291
// v-model.trim
93-
if (!result.startsWith('v-model:') && result.startsWith('v-model')) {
92+
if (!attr.startsWith('v-model:') && attr.startsWith('v-model')) {
9493
return kebabCase(modelProp);
9594
}
96-
97-
// Allow `v-model:prop` in vue 3
98-
if (result.startsWith('v-model:')) {
99-
result = attr.slice('v-model:'.length);
100-
}
101-
102-
if (result.startsWith('v-bind:')) {
103-
result = attr.slice('v-bind:'.length);
104-
} else if (result.startsWith(':')) {
105-
result = attr.slice(':'.length);
106-
}
107-
108-
// Remove modifiers
109-
if (result.includes('.')) {
110-
result = result.slice(0, result.indexOf('.'));
111-
}
112-
113-
result = kebabCase(result);
114-
115-
return result;
95+
return normalizeAttributeNameToKebabCase(attr);
11696
}

server/src/modes/template/test/completion.test.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,21 @@ suite('HTML Completion', () => {
7777
.become('<input tabindex="$1"');
7878

7979
html`<input t|ype="text"`
80-
.has('type')
81-
.become('<input type="text"')
80+
.hasNo('type')
8281
.has('tabindex')
8382
.become('<input tabindex="text"');
8483

8584
html`<input type="text" |`
8685
.has('style')
8786
.become('<input type="text" style="$1"')
88-
.has('type')
89-
.become('<input type="text" type="$1"')
87+
.hasNo('type')
9088
.has('size')
9189
.become('<input type="text" size="$1"');
9290

9391
html`<input type="text" s|`
9492
.has('style')
9593
.become('<input type="text" style="$1"')
96-
.has('type')
97-
.become('<input type="text" type="$1"')
94+
.hasNo('type')
9895
.has('size')
9996
.become('<input type="text" size="$1"');
10097

@@ -114,7 +111,19 @@ suite('HTML Completion', () => {
114111

115112
html`<input :di| type="text"`.has('dir').become('<input :dir="$1" type="text"');
116113

114+
html`<input :type="type" |`.hasNo('type');
115+
116+
html`<input :type.prop="type" |`.hasNo('type');
117+
118+
// `class` and `:class`, `style` and `:style` can coexist
119+
html`<input :class="$style.input" |`.has('class');
120+
html`<input style="style" |`.has('style');
121+
html`<input :cl|ass="$style.input"`.has('class').become('<input :class="$style.input"');
122+
117123
html`<input @|`.has('mousemove').become('<input @mousemove="$1"');
124+
125+
// can listen to same event by adding modifiers
126+
html`<input @mousemove="mousemove" @|`.has('mousemove').become('<input @mousemove="mousemove" @mousemove="$1"');
118127
});
119128

120129
test('Complete Value', () => {

test/interpolation/features/completion/property.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('Should autocomplete interpolation for <template> in property class com
9393
];
9494

9595
it(`completes child component's props`, async () => {
96-
await testCompletion(parentTemplateDocUri, position(2, 27), propsList);
96+
await testCompletion(parentTemplateDocUri, position(2, 26), propsList);
9797
});
9898

9999
it(`completes child component's props when camel case component name`, async () => {

test/interpolation/fixture/completion/propertyDecorator/Parent.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div>
3-
<basic-property-class :foo=""></basic-property-class>
3+
<basic-property-class ></basic-property-class>
44
<basic-property-class v-if="" @click="" :foo=""></basic-property-class>
55
<BasicPropertyClass />
66
<

0 commit comments

Comments
 (0)