Skip to content

Commit

Permalink
fix and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Dec 14, 2020
1 parent 552187a commit e9662b9
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 61 deletions.
3 changes: 2 additions & 1 deletion server/src/modes/template/htmlMode.ts
Expand Up @@ -95,7 +95,8 @@ export class HTMLMode implements LanguageMode {
this.vueDocuments.refreshAndGet(embedded),
tagProviders,
this.env.getConfig().emmet,
this.autoImportSfcPlugin.doComplete(document)
this.autoImportSfcPlugin.doComplete(document),
info
);
}
doHover(document: TextDocument, position: Position) {
Expand Down
71 changes: 36 additions & 35 deletions server/src/modes/template/services/htmlCompletion.ts
Expand Up @@ -16,6 +16,7 @@ import { NULL_COMPLETION } from '../../nullMode';
import { getModifierProvider, Modifier } from '../modifierProvider';
import { toMarkupContent } from '../../../utils/strings';
import { Priority } from '../tagProviders/common';
import { kebabCase } from 'lodash';

export function doComplete(
document: TextDocument,
Expand All @@ -42,7 +43,6 @@ export function doComplete(
const scanner = createScanner(text, node.start);
let currentTag: string;
let currentAttributeName = '';
let currentTagStartOffset: number;

function getReplaceRange(replaceStart: number, replaceEnd: number = offset): Range {
if (replaceStart > offset) {
Expand Down Expand Up @@ -148,12 +148,15 @@ export function doComplete(
return result;
}

function collectAttributeNameSuggestions(
usedAttributes: Set<string>,
nameStart: number,
nameEnd: number = offset
): CompletionList {
const execArray = /^[:@]/.exec(scanner.getTokenText());
function getUsedAttributes(offset: number) {
const node = htmlDocument.findNodeBefore(offset);
return node.attributeNames.map(normalizeAttributeNameToKebabCase);
}

function collectAttributeNameSuggestions(nameStart: number, nameEnd: number = offset): CompletionList {
const usedAttributes = getUsedAttributes(nameStart);
const currentAttribute = scanner.getTokenText();
const execArray = /^[:@]/.exec(currentAttribute);
const filterPrefix = execArray ? execArray[0] : '';
const start = filterPrefix ? nameStart + 1 : nameStart;
const range = getReplaceRange(start, nameEnd);
Expand All @@ -164,12 +167,14 @@ export function doComplete(
const priority = provider.priority;
provider.collectAttributes(currentTag, (attribute, type, documentation) => {
if (
usedAttributes.has(normalizeAttribute(attribute)) &&
// include current typing attribute for completing `="$1"`
!(attribute === currentAttribute && text[nameEnd] !== '=') &&
// can listen to same event by adding modifiers
type !== 'event' &&
// `class` and `:class`, `style` and `:style` can coexist
attribute !== 'class' &&
attribute !== 'style'
attribute !== 'style' &&
usedAttributes.includes(normalizeAttributeNameToKebabCase(attribute))
) {
return;
}
Expand Down Expand Up @@ -286,23 +291,6 @@ export function doComplete(
return offset;
}

function collectUsedAttributes(): Set<string> {
const attrScanner = createScanner(text, currentTagStartOffset);

let token = attrScanner.scan();
let currentAttributeName!: string;
const attrs = new Set<string>();
while (token !== TokenType.EOS && token !== TokenType.StartTagClose) {
if (token === TokenType.AttributeName) {
currentAttributeName = normalizeAttribute(attrScanner.getTokenText());
} else if (token === TokenType.AttributeValue) {
attrs.add(currentAttributeName);
}
token = attrScanner.scan();
}
return attrs;
}

let token = scanner.scan();

while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) {
Expand All @@ -312,7 +300,6 @@ export function doComplete(
const endPos = scanNextForEndPos(TokenType.StartTag);
return collectTagSuggestions(offset, endPos);
}
currentTagStartOffset = scanner.getTokenOffset();
break;
case TokenType.StartTag:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
Expand All @@ -322,8 +309,7 @@ export function doComplete(
break;
case TokenType.AttributeName:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
const usedAttrs = collectUsedAttributes();
return collectAttributeNameSuggestions(usedAttrs, scanner.getTokenOffset(), scanner.getTokenEnd());
return collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
currentAttributeName = scanner.getTokenText();
break;
Expand Down Expand Up @@ -355,8 +341,7 @@ export function doComplete(
return collectTagSuggestions(startPos, endTagPos);
case ScannerState.WithinTag:
case ScannerState.AfterAttributeName:
const usedAttrs = collectUsedAttributes();
return collectAttributeNameSuggestions(usedAttrs, scanner.getTokenEnd());
return collectAttributeNameSuggestions(scanner.getTokenEnd());
case ScannerState.BeforeAttributeValue:
return collectAttributeValueSuggestions(currentAttributeName, scanner.getTokenEnd());
case ScannerState.AfterOpeningEndTag:
Expand Down Expand Up @@ -428,9 +413,25 @@ function getWordEnd(s: string, offset: number, limit: number): number {
return offset;
}

function normalizeAttribute(attr: string): string {
// trim modifiers
attr = attr.replace(/\..+$/, '');
export function normalizeAttributeNameToKebabCase(attr: string): string {
let result = attr;

return attr.replace(/^(?:v-bind:|:)/, '').replace(/^v-on:/, '@');
if (result.startsWith('v-model:')) {
result = attr.slice('v-model:'.length);
}

if (result.startsWith('v-bind:')) {
result = attr.slice('v-bind:'.length);
} else if (result.startsWith(':')) {
result = attr.slice(':'.length);
}

// Remove modifiers
if (result.includes('.')) {
result = result.slice(0, result.indexOf('.'));
}

result = kebabCase(result);

return result;
}
30 changes: 5 additions & 25 deletions server/src/modes/template/services/vuePropValidation.ts
Expand Up @@ -4,6 +4,7 @@ import type { TextDocument } from 'vscode-languageserver-textdocument';
import { HTMLDocument, Node } from '../parser/htmlParser';
import { kebabCase } from 'lodash';
import { getSameTagInSet } from '../tagProviders/common';
import { normalizeAttributeNameToKebabCase } from './htmlCompletion';

export function doPropValidation(document: TextDocument, htmlDocument: HTMLDocument, info: VueFileInfo): Diagnostic[] {
const diagnostics: Diagnostic[] = [];
Expand Down Expand Up @@ -50,7 +51,7 @@ function generateDiagnostic(n: Node, definedProps: PropInfo[], document: TextDoc
const seenProps = n.attributeNames.map(attr => {
return {
name: attr,
normalized: normalizeHtmlAttributeNameToKebabCase(
normalized: normalizeHtmlAttributeNameToKebabCaseAndReplaceVModel(
attr,
definedProps.find(prop => prop.isBoundToModel)?.name ?? 'value'
)
Expand Down Expand Up @@ -86,31 +87,10 @@ function generateDiagnostic(n: Node, definedProps: PropInfo[], document: TextDoc
};
}

function normalizeHtmlAttributeNameToKebabCase(attr: string, modelProp: string) {
let result = attr;

function normalizeHtmlAttributeNameToKebabCaseAndReplaceVModel(attr: string, modelProp: string) {
// v-model.trim
if (!result.startsWith('v-model:') && result.startsWith('v-model')) {
if (!attr.startsWith('v-model:') && attr.startsWith('v-model')) {
return kebabCase(modelProp);
}

// Allow `v-model:prop` in vue 3
if (result.startsWith('v-model:')) {
result = attr.slice('v-model:'.length);
}

if (result.startsWith('v-bind:')) {
result = attr.slice('v-bind:'.length);
} else if (result.startsWith(':')) {
result = attr.slice(':'.length);
}

// Remove modifiers
if (result.includes('.')) {
result = result.slice(0, result.indexOf('.'));
}

result = kebabCase(result);

return result;
return normalizeAttributeNameToKebabCase(attr);
}

0 comments on commit e9662b9

Please sign in to comment.