Skip to content

Commit e9662b9

Browse files
committed
fix and refactor
1 parent 552187a commit e9662b9

File tree

3 files changed

+43
-61
lines changed

3 files changed

+43
-61
lines changed

server/src/modes/template/htmlMode.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export class HTMLMode implements LanguageMode {
9595
this.vueDocuments.refreshAndGet(embedded),
9696
tagProviders,
9797
this.env.getConfig().emmet,
98-
this.autoImportSfcPlugin.doComplete(document)
98+
this.autoImportSfcPlugin.doComplete(document),
99+
info
99100
);
100101
}
101102
doHover(document: TextDocument, position: Position) {

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

+36-35
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,
@@ -42,7 +43,6 @@ export function doComplete(
4243
const scanner = createScanner(text, node.start);
4344
let currentTag: string;
4445
let currentAttributeName = '';
45-
let currentTagStartOffset: number;
4646

4747
function getReplaceRange(replaceStart: number, replaceEnd: number = offset): Range {
4848
if (replaceStart > offset) {
@@ -148,12 +148,15 @@ export function doComplete(
148148
return result;
149149
}
150150

151-
function collectAttributeNameSuggestions(
152-
usedAttributes: Set<string>,
153-
nameStart: number,
154-
nameEnd: number = offset
155-
): CompletionList {
156-
const execArray = /^[:@]/.exec(scanner.getTokenText());
151+
function getUsedAttributes(offset: number) {
152+
const node = htmlDocument.findNodeBefore(offset);
153+
return node.attributeNames.map(normalizeAttributeNameToKebabCase);
154+
}
155+
156+
function collectAttributeNameSuggestions(nameStart: number, nameEnd: number = offset): CompletionList {
157+
const usedAttributes = getUsedAttributes(nameStart);
158+
const currentAttribute = scanner.getTokenText();
159+
const execArray = /^[:@]/.exec(currentAttribute);
157160
const filterPrefix = execArray ? execArray[0] : '';
158161
const start = filterPrefix ? nameStart + 1 : nameStart;
159162
const range = getReplaceRange(start, nameEnd);
@@ -164,12 +167,14 @@ export function doComplete(
164167
const priority = provider.priority;
165168
provider.collectAttributes(currentTag, (attribute, type, documentation) => {
166169
if (
167-
usedAttributes.has(normalizeAttribute(attribute)) &&
170+
// include current typing attribute for completing `="$1"`
171+
!(attribute === currentAttribute && text[nameEnd] !== '=') &&
168172
// can listen to same event by adding modifiers
169173
type !== 'event' &&
170174
// `class` and `:class`, `style` and `:style` can coexist
171175
attribute !== 'class' &&
172-
attribute !== 'style'
176+
attribute !== 'style' &&
177+
usedAttributes.includes(normalizeAttributeNameToKebabCase(attribute))
173178
) {
174179
return;
175180
}
@@ -286,23 +291,6 @@ export function doComplete(
286291
return offset;
287292
}
288293

289-
function collectUsedAttributes(): Set<string> {
290-
const attrScanner = createScanner(text, currentTagStartOffset);
291-
292-
let token = attrScanner.scan();
293-
let currentAttributeName!: string;
294-
const attrs = new Set<string>();
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.add(currentAttributeName);
300-
}
301-
token = attrScanner.scan();
302-
}
303-
return attrs;
304-
}
305-
306294
let token = scanner.scan();
307295

308296
while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) {
@@ -312,7 +300,6 @@ export function doComplete(
312300
const endPos = scanNextForEndPos(TokenType.StartTag);
313301
return collectTagSuggestions(offset, endPos);
314302
}
315-
currentTagStartOffset = scanner.getTokenOffset();
316303
break;
317304
case TokenType.StartTag:
318305
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
@@ -322,8 +309,7 @@ export function doComplete(
322309
break;
323310
case TokenType.AttributeName:
324311
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
325-
const usedAttrs = collectUsedAttributes();
326-
return collectAttributeNameSuggestions(usedAttrs, scanner.getTokenOffset(), scanner.getTokenEnd());
312+
return collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
327313
}
328314
currentAttributeName = scanner.getTokenText();
329315
break;
@@ -355,8 +341,7 @@ export function doComplete(
355341
return collectTagSuggestions(startPos, endTagPos);
356342
case ScannerState.WithinTag:
357343
case ScannerState.AfterAttributeName:
358-
const usedAttrs = collectUsedAttributes();
359-
return collectAttributeNameSuggestions(usedAttrs, scanner.getTokenEnd());
344+
return collectAttributeNameSuggestions(scanner.getTokenEnd());
360345
case ScannerState.BeforeAttributeValue:
361346
return collectAttributeValueSuggestions(currentAttributeName, scanner.getTokenEnd());
362347
case ScannerState.AfterOpeningEndTag:
@@ -428,9 +413,25 @@ function getWordEnd(s: string, offset: number, limit: number): number {
428413
return offset;
429414
}
430415

431-
function normalizeAttribute(attr: string): string {
432-
// trim modifiers
433-
attr = attr.replace(/\..+$/, '');
416+
export function normalizeAttributeNameToKebabCase(attr: string): string {
417+
let result = attr;
434418

435-
return attr.replace(/^(?:v-bind:|:)/, '').replace(/^v-on:/, '@');
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;
436437
}

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
}

0 commit comments

Comments
 (0)