Skip to content

Commit 3f63973

Browse files
committed
Dont include used attributes in suggestions
1 parent b7ca396 commit 3f63973

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

.prettierignore

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

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

+45-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function doComplete(
4242
const scanner = createScanner(text, node.start);
4343
let currentTag: string;
4444
let currentAttributeName = '';
45+
let currentTagStartOffset: number;
4546

4647
function getReplaceRange(replaceStart: number, replaceEnd: number = offset): Range {
4748
if (replaceStart > offset) {
@@ -147,7 +148,11 @@ export function doComplete(
147148
return result;
148149
}
149150

150-
function collectAttributeNameSuggestions(nameStart: number, nameEnd: number = offset): CompletionList {
151+
function collectAttributeNameSuggestions(
152+
usedAttributes: string[],
153+
nameStart: number,
154+
nameEnd: number = offset
155+
): CompletionList {
151156
const execArray = /^[:@]/.exec(scanner.getTokenText());
152157
const filterPrefix = execArray ? execArray[0] : '';
153158
const start = filterPrefix ? nameStart + 1 : nameStart;
@@ -158,6 +163,16 @@ export function doComplete(
158163
tagProviders.forEach(provider => {
159164
const priority = provider.priority;
160165
provider.collectAttributes(currentTag, (attribute, type, documentation) => {
166+
if (
167+
usedAttributes.includes(normalizeAttribute(attribute)) &&
168+
// can listen to same event by adding modifiers
169+
type !== 'event' &&
170+
// `class` and `:class`, `style` and `:style` can coexist
171+
attribute !== 'class' &&
172+
attribute !== 'style'
173+
) {
174+
return;
175+
}
161176
if ((type === 'event' && filterPrefix !== '@') || (type !== 'event' && filterPrefix === '@')) {
162177
return;
163178
}
@@ -271,6 +286,23 @@ export function doComplete(
271286
return offset;
272287
}
273288

289+
function collectUsedAttributes(): string[] {
290+
const attrScanner = createScanner(text, currentTagStartOffset);
291+
292+
let token = attrScanner.scan();
293+
let currentAttributeName!: string;
294+
const attrs = [];
295+
while (token !== TokenType.EOS && token !== TokenType.StartTagClose) {
296+
if (token === TokenType.AttributeName) {
297+
currentAttributeName = normalizeAttribute(attrScanner.getTokenText());
298+
} else if (token === TokenType.AttributeValue) {
299+
attrs.push(currentAttributeName);
300+
}
301+
token = attrScanner.scan();
302+
}
303+
return attrs;
304+
}
305+
274306
let token = scanner.scan();
275307

276308
while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) {
@@ -280,6 +312,7 @@ export function doComplete(
280312
const endPos = scanNextForEndPos(TokenType.StartTag);
281313
return collectTagSuggestions(offset, endPos);
282314
}
315+
currentTagStartOffset = scanner.getTokenOffset();
283316
break;
284317
case TokenType.StartTag:
285318
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
@@ -289,7 +322,8 @@ export function doComplete(
289322
break;
290323
case TokenType.AttributeName:
291324
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
292-
return collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
325+
const usedAttrs = collectUsedAttributes();
326+
return collectAttributeNameSuggestions(usedAttrs, scanner.getTokenOffset(), scanner.getTokenEnd());
293327
}
294328
currentAttributeName = scanner.getTokenText();
295329
break;
@@ -321,7 +355,8 @@ export function doComplete(
321355
return collectTagSuggestions(startPos, endTagPos);
322356
case ScannerState.WithinTag:
323357
case ScannerState.AfterAttributeName:
324-
return collectAttributeNameSuggestions(scanner.getTokenEnd());
358+
const usedAttrs = collectUsedAttributes();
359+
return collectAttributeNameSuggestions(usedAttrs, scanner.getTokenEnd());
325360
case ScannerState.BeforeAttributeValue:
326361
return collectAttributeValueSuggestions(currentAttributeName, scanner.getTokenEnd());
327362
case ScannerState.AfterOpeningEndTag:
@@ -392,3 +427,10 @@ function getWordEnd(s: string, offset: number, limit: number): number {
392427
}
393428
return offset;
394429
}
430+
431+
function normalizeAttribute(attr: string): string {
432+
// trim modifiers
433+
attr = attr.replace(/\..+$/, '');
434+
435+
return attr.replace(/^(?:v-bind:|:)/, '').replace(/^v-on:/, '@');
436+
}

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)