Skip to content

Commit

Permalink
Handle same component name logic. Fix vuejs#2049
Browse files Browse the repository at this point in the history
  • Loading branch information
yoyo930021 committed Jul 24, 2020
1 parent 5a5c043 commit d01706e
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 52 deletions.
32 changes: 15 additions & 17 deletions server/src/modes/template/services/htmlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Range,
TextEdit,
InsertTextFormat,
CompletionItem
CompletionItem,
} from 'vscode-languageserver-types';
import { HTMLDocument } from '../parser/htmlParser';
import { TokenType, createScanner, ScannerState } from '../parser/htmlScanner';
Expand All @@ -26,7 +26,7 @@ export function doComplete(

const result: CompletionList = {
isIncomplete: false,
items: []
items: [],
};

const offset = document.offsetAt(position);
Expand All @@ -49,7 +49,7 @@ export function doComplete(

function collectOpenTagSuggestions(afterOpenBracket: number, tagNameEnd?: number): CompletionList {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
tagProviders.forEach(provider => {
tagProviders.forEach((provider) => {
const priority = provider.priority;
provider.collectTags((tag, label) => {
result.items.push({
Expand All @@ -58,7 +58,7 @@ export function doComplete(
documentation: label,
textEdit: TextEdit.replace(range, tag),
sortText: priority + tag,
insertTextFormat: InsertTextFormat.PlainText
insertTextFormat: InsertTextFormat.PlainText,
});
});
});
Expand Down Expand Up @@ -96,7 +96,7 @@ export function doComplete(
kind: CompletionItemKind.Property,
filterText: '/' + tag + closeTag,
textEdit: TextEdit.replace(range, '/' + tag + closeTag),
insertTextFormat: InsertTextFormat.PlainText
insertTextFormat: InsertTextFormat.PlainText,
};
const startIndent = getLineIndent(curr.start);
const endIndent = getLineIndent(afterOpenBracket - 1);
Expand All @@ -114,15 +114,15 @@ export function doComplete(
return result;
}

tagProviders.forEach(provider => {
tagProviders.forEach((provider) => {
provider.collectTags((tag, label) => {
result.items.push({
label: '/' + tag,
kind: CompletionItemKind.Property,
documentation: label,
filterText: '/' + tag + closeTag,
textEdit: TextEdit.replace(range, '/' + tag + closeTag),
insertTextFormat: InsertTextFormat.PlainText
insertTextFormat: InsertTextFormat.PlainText,
});
});
});
Expand All @@ -143,10 +143,9 @@ export function doComplete(
const value = isFollowedBy(text, nameEnd, ScannerState.AfterAttributeName, TokenType.DelimiterAssign)
? ''
: '="$1"';
const tag = currentTag.toLowerCase();
tagProviders.forEach(provider => {
tagProviders.forEach((provider) => {
const priority = provider.priority;
provider.collectAttributes(tag, (attribute, type, documentation) => {
provider.collectAttributes(currentTag, (attribute, type, documentation) => {
if ((type === 'event' && filterPrefix !== '@') || (type !== 'event' && filterPrefix === '@')) {
return;
}
Expand All @@ -160,21 +159,21 @@ export function doComplete(
textEdit: TextEdit.replace(range, codeSnippet),
insertTextFormat: InsertTextFormat.Snippet,
sortText: priority + attribute,
documentation
documentation,
});
});
});
const attributeName = scanner.getTokenText();
if (/\.$/.test(attributeName)) {
function addModifier(modifiers: { items: Modifier[]; priority: number }) {
modifiers.items.forEach(modifier => {
modifiers.items.forEach((modifier) => {
result.items.push({
label: modifier.label,
kind: CompletionItemKind.Method,
textEdit: TextEdit.insert(document.positionAt(nameEnd), modifier.label),
insertTextFormat: InsertTextFormat.Snippet,
sortText: modifiers.priority + modifier.label,
documentation: modifier.documentation
documentation: modifier.documentation,
});
});
}
Expand Down Expand Up @@ -229,17 +228,16 @@ export function doComplete(
range = getReplaceRange(valueStart, valueEnd);
addQuotes = true;
}
const tag = currentTag.toLowerCase();
const attribute = currentAttributeName.toLowerCase();
tagProviders.forEach(provider => {
provider.collectValues(tag, attribute, value => {
tagProviders.forEach((provider) => {
provider.collectValues(currentTag, attribute, (value) => {
const insertText = addQuotes ? '"' + value + '"' : value;
result.items.push({
label: value,
filterText: insertText,
kind: CompletionItemKind.Unit,
textEdit: TextEdit.replace(range, insertText),
insertTextFormat: InsertTextFormat.PlainText
insertTextFormat: InsertTextFormat.PlainText,
});
});
});
Expand Down
3 changes: 1 addition & 2 deletions server/src/modes/template/services/htmlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export function doHover(
}

function getAttributeHover(tag: string, attribute: string, range: Range): Hover {
tag = tag.toLowerCase();
let hover: Hover = NULL_HOVER;
for (const provider of tagProviders) {
provider.collectAttributes(tag, (attr, type, documentation) => {
Expand Down Expand Up @@ -89,7 +88,7 @@ export function doHover(
}
const tagRange = {
start: document.positionAt(scanner.getTokenOffset()),
end: document.positionAt(scanner.getTokenEnd())
end: document.positionAt(scanner.getTokenEnd()),
};
switch (token) {
case TokenType.StartTag:
Expand Down
16 changes: 7 additions & 9 deletions server/src/modes/template/tagProviders/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MarkupContent } from 'vscode-languageserver-types';
import { kebabCase } from 'lodash';

interface TagCollector {
(tag: string, documentation: string | MarkupContent): void;
Expand Down Expand Up @@ -50,6 +51,10 @@ export interface IValueSets {
[tag: string]: string[];
}

export function getSameTagInSet<T>(tagSet: Record<string, T>, tag: string): T | undefined {
return tagSet[tag] ?? tagSet[tag.toLowerCase()] ?? tagSet[kebabCase(tag)];
}

export function collectTagsDefault(collector: TagCollector, tagSet: ITagSet): void {
for (const tag in tagSet) {
collector(tag, tagSet[tag].label);
Expand All @@ -63,14 +68,7 @@ export function collectAttributesDefault(
globalAttributes: StandaloneAttribute[]
): void {
if (tag) {
let tags = tagSet[tag];
if (!tags) {
for (const t in tagSet) {
if (t.toLowerCase() === tag) {
tags = tagSet[t];
}
}
}
const tags = getSameTagInSet(tagSet, tag);

if (tags) {
const attributes = tags.attributes;
Expand Down Expand Up @@ -110,7 +108,7 @@ export function collectValuesDefault(
}
}
if (tag) {
const tags = tagSet[tag];
const tags = getSameTagInSet(tagSet, tag);
if (tags) {
const attributes = tags.attributes;
if (attributes) {
Expand Down
22 changes: 11 additions & 11 deletions server/src/modes/template/tagProviders/externalTagProviders.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';
import { kebabCase } from 'lodash';

import { IHTMLTagProvider, Priority } from './common';
import { IHTMLTagProvider, Priority, getSameTagInSet } from './common';

import * as elementTags from 'element-helper-json/element-tags.json';
import * as elementAttributes from 'element-helper-json/element-attributes.json';
Expand Down Expand Up @@ -77,7 +78,12 @@ export function getDependencyTagProvider(workspacePath: string, depPkgJson: any)

export function getExternalTagProvider(id: string, tags: any, attributes: any): IHTMLTagProvider {
function findAttributeDetail(tag: string, attr: string) {
return attributes[attr] || attributes[tag + '/' + attr];
return (
attributes[attr] ||
attributes[`${tag}/${attr}`] ||
attributes[`${tag.toLowerCase}/${attr}`] ||
attributes[`${kebabCase(tag)}/${attr}`]
);
}

return {
Expand All @@ -89,10 +95,7 @@ export function getExternalTagProvider(id: string, tags: any, attributes: any):
}
},
collectAttributes(tag, collector) {
if (!tags[tag]) {
return;
}
const attrs = tags[tag].attributes;
const attrs = getSameTagInSet<any>(tags, tag)?.attributes;
if (!attrs) {
return;
}
Expand All @@ -102,10 +105,7 @@ export function getExternalTagProvider(id: string, tags: any, attributes: any):
}
},
collectValues(tag, attr, collector) {
if (!tags[tag]) {
return;
}
const attrs = tags[tag].attributes;
const attrs = getSameTagInSet<any>(tags, tag)?.attributes;
if (!attrs || attrs.indexOf(attr) < 0) {
return;
}
Expand All @@ -116,6 +116,6 @@ export function getExternalTagProvider(id: string, tags: any, attributes: any):
for (const option of detail.options) {
collector(option);
}
}
},
};
}
44 changes: 32 additions & 12 deletions test/interpolation/completion/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ describe('Should autocomplete interpolation for <template>', () => {
}`,
'js'
),
kind: CompletionItemKind.Field
kind: CompletionItemKind.Field,
},
{
label: 'msg',
documentation: new MarkdownString('My msg').appendCodeblock(`msg: 'Vetur means "Winter" in icelandic.'`, 'js'),
kind: CompletionItemKind.Field
kind: CompletionItemKind.Field,
},
{
label: 'count',
Expand All @@ -38,7 +38,7 @@ describe('Should autocomplete interpolation for <template>', () => {
}`,
'js'
),
kind: CompletionItemKind.Field
kind: CompletionItemKind.Field,
},
{
label: 'hello',
Expand All @@ -49,8 +49,8 @@ describe('Should autocomplete interpolation for <template>', () => {
'js'
),

kind: CompletionItemKind.Function
}
kind: CompletionItemKind.Function,
},
];

describe('Should complete props, data, computed and methods', () => {
Expand All @@ -62,18 +62,18 @@ describe('Should autocomplete interpolation for <template>', () => {
await testCompletion(templateDocUri, position(3, 11), [
{
label: 'msg',
kind: CompletionItemKind.Field
}
kind: CompletionItemKind.Field,
},
]);
});

it(`completes child component tag`, async () => {
await testCompletion(parentTemplateDocUri, position(4, 5), [
await testCompletion(parentTemplateDocUri, position(6, 5), [
{
label: 'basic',
kind: CompletionItemKind.Property,
documentationStart: 'My basic tag\n```js\nexport default {'
}
documentationStart: 'My basic tag\n```js\nexport default {',
},
]);
});

Expand All @@ -88,8 +88,28 @@ describe('Should autocomplete interpolation for <template>', () => {
default: false
}`,
'js'
)
}
),
},
]);
});

it(`completes child component's props when kebab case component name`, async () => {
await testCompletion(parentTemplateDocUri, position(4, 15), [
{
label: 'bar',
kind: CompletionItemKind.Value,
documentation: new MarkdownString('My bar').appendCodeblock(`bar: String`, 'js'),
},
]);
});

it(`completes child component's props when camel case component name`, async () => {
await testCompletion(parentTemplateDocUri, position(5, 14), [
{
label: 'bar',
kind: CompletionItemKind.Value,
documentation: new MarkdownString('My bar').appendCodeblock(`bar: String`, 'js'),
},
]);
});

Expand Down
6 changes: 5 additions & 1 deletion test/interpolation/fixture/completion/Parent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
<div>
<basic :></basic>
<basic v-if="" @click="" :foo=""></basic>
<test-comp />
<TestComp />
<
</div>
</template>

<script>
import Basic from './Basic.vue'
import TestComp from './TestComp.vue'
export default {
components: {
Basic
Basic,
TestComp
},
props: {
/**
Expand Down
20 changes: 20 additions & 0 deletions test/interpolation/fixture/completion/TestComp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<div>{{ bar }}</div>
</template>

<script>
export default {
name: 'TestComp',
props: {
/**
* My bar
*/
bar: String
},
data () {
return {
count: 0
}
}
}
</script>

0 comments on commit d01706e

Please sign in to comment.