Skip to content

Commit

Permalink
Fix #2133
Browse files Browse the repository at this point in the history
  • Loading branch information
octref committed Aug 7, 2020
1 parent 0d99197 commit 449e7c6
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 56 deletions.
4 changes: 2 additions & 2 deletions server/src/modes/template/tagProviders/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface ITagSet {
}

export class HTMLTagSpecification {
constructor(public label: string | MarkupContent, public attributes: Attribute[] = []) {}
constructor(public documentation: string | MarkupContent, public attributes: Attribute[] = []) {}
}

export interface IValueSets {
Expand All @@ -57,7 +57,7 @@ export function getSameTagInSet<T>(tagSet: Record<string, T>, tag: string): T |

export function collectTagsDefault(collector: TagCollector, tagSet: ITagSet): void {
for (const tag in tagSet) {
collector(tag, tagSet[tag].label);
collector(tag, tagSet[tag].documentation);
}
}

Expand Down
43 changes: 32 additions & 11 deletions server/src/modes/template/tagProviders/routerTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,52 @@ import {

const routerTags = {
'router-link': new HTMLTagSpecification(
'Link to navigate user. The target location is specified with the to prop.\n\n',
'Link to navigate user. The target location is specified with the to prop.\n\n[API Reference](https://router.vuejs.org/api/#router-link)',
[
genAttribute(
'to',
undefined,
'The target route of the link. It can be either a string or a location descriptor object.'
'The target route of the link. It can be either a string or a location descriptor object.\n\n[API Reference](https://router.vuejs.org/api/#to)'
),
genAttribute(
'replace',
undefined,
'Setting replace prop will call router.replace() instead of router.push() when clicked, so the navigation will not leave a history record.'
'Setting replace prop will call `router.replace()` instead of `router.push()` when clicked, so the navigation will not leave a history record.\n\n[API Reference](https://router.vuejs.org/api/#replace)'
),
genAttribute(
'append',
'v',
'Setting append prop always appends the relative path to the current path. For example, assuming we are navigating from /a to a relative link b, without append we will end up at /b, but with append we will end up at /a/b.'
'Setting append prop always appends the relative path to the current path. For example, assuming we are navigating from /a to a relative link b, without append we will end up at /b, but with append we will end up at /a/b.\n\n[API Reference](https://router.vuejs.org/api/#append)'
),
genAttribute(
'tag',
undefined,
'Specify which tag to render to, and it will still listen to click events for navigation.'
'Specify which tag to render to, and it will still listen to click events for navigation.\n\n[API Reference](https://router.vuejs.org/api/#tag)'
),
genAttribute(
'active-class',
undefined,
'Configure the active CSS class applied when the link is active.\n\n[API Reference](https://router.vuejs.org/api/#active-class)'
),
genAttribute(
'exact',
'v',
'Force the link into "exact match mode".\n\n[API Reference](https://router.vuejs.org/api/#exact)'
),
genAttribute(
'event',
undefined,
'Specify the event(s) that can trigger the link navigation.\n\n[API Reference](https://router.vuejs.org/api/#event)'
),
genAttribute('active-class', undefined, 'Configure the active CSS class applied when the link is active.'),
genAttribute('exact', 'v', 'Force the link into "exact match mode".'),
genAttribute('event', undefined, 'Specify the event(s) that can trigger the link navigation.'),
genAttribute(
'exact-active-class',
undefined,
'Configure the active CSS class applied when the link is active with exact match.'
'Configure the active CSS class applied when the link is active with exact match.\n\n[API Reference](https://router.vuejs.org/api/#exact-active-class)'
),
genAttribute(
'aria-current-value',
'ariaCurrentType',
'Configure the value of `aria-current` when the link is active with exact match. It must be one of the [allowed values for `aria-current`](https://www.w3.org/TR/wai-aria-1.2/#aria-current) in the ARIA spec. In most cases, the default of `page` should be the best fit.\n\n[API Reference](https://router.vuejs.org/api/#aria-current-value)'
)
]
),
Expand All @@ -50,12 +67,16 @@ const routerTags = {
genAttribute(
'name',
undefined,
"When a <router-view> has a name, it will render the component with the corresponding name in the matched route record's components option"
"When a `<router-view>` has a name, it will render the component with the corresponding name in the matched route record's components option.\n\n[API Reference](https://router.vuejs.org/api/#to)"
)
]
)
};

const valueSets = {
ariaCurrentType: ['page', 'step', 'location', 'date', 'time']
};

export function getRouterTagProvider(): IHTMLTagProvider {
return {
getId: () => 'vue-router',
Expand All @@ -65,7 +86,7 @@ export function getRouterTagProvider(): IHTMLTagProvider {
collectAttributesDefault(tag, collector, routerTags, []);
},
collectValues: (tag: string, attribute: string, collector: (value: string) => void) => {
collectValuesDefault(tag, attribute, collector, routerTags, [], {});
collectValuesDefault(tag, attribute, collector, routerTags, [], valueSets);
}
};
}
95 changes: 60 additions & 35 deletions server/src/modes/template/tagProviders/vueTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,65 @@ import {
collectValuesDefault,
genAttribute,
AttributeCollector,
Priority
Priority,
Attribute
} from './common';

const u = undefined;
function getAttribute(label: string, type: string | undefined, documentation: string) {
const linkedDocumentation = documentation + '\n\n' + `[API Reference](https://vuejs.org/v2/api/#${label})`;
return genAttribute(label, type, linkedDocumentation);
}

const vueDirectives = [
genAttribute('v-text', u, 'Updates the element’s `textContent`.'),
genAttribute('v-html', u, 'Updates the element’s `innerHTML`. XSS prone.'),
genAttribute(
getAttribute('v-text', undefined, 'Updates the element’s `textContent`.'),
getAttribute('v-html', undefined, 'Updates the element’s `innerHTML`. XSS prone.'),
getAttribute(
'v-show',
u,
undefined,
'Toggle’s the element’s `display` CSS property based on the truthy-ness of the expression value.'
),
genAttribute('v-if', u, 'Conditionally renders the element based on the truthy-ness of the expression value.'),
genAttribute('v-else', 'v', 'Denotes the “else block” for `v-if` or a `v-if`/`v-else-if` chain.'),
genAttribute('v-else-if', u, 'Denotes the “else if block” for `v-if`. Can be chained.'),
genAttribute('v-for', u, 'Renders the element or template block multiple times based on the source data.'),
genAttribute('v-on', u, 'Attaches an event listener to the element.'),
genAttribute('v-bind', u, 'Dynamically binds one or more attributes, or a component prop to an expression.'),
genAttribute('v-model', u, 'Creates a two-way binding on a form input element or a component.'),
genAttribute('v-pre', 'v', 'Skips compilation for this element and all its children.'),
genAttribute('v-cloak', 'v', 'Indicates Vue instance for this element has NOT finished compilation.'),
genAttribute('v-once', 'v', 'Render the element and component once only.'),
genAttribute('key', u, 'Hint at VNodes identity for VDom diffing, e.g. list rendering'),
genAttribute('ref', u, 'Register a reference to an element or a child component.'),
genAttribute(
getAttribute(
'v-if',
undefined,
'Conditionally renders the element based on the truthy-ness of the expression value.'
),
getAttribute('v-else', 'v', 'Denotes the “else block” for `v-if` or a `v-if`/`v-else-if` chain.'),
getAttribute('v-else-if', undefined, 'Denotes the “else if block” for `v-if`. Can be chained.'),
getAttribute('v-for', undefined, 'Renders the element or template block multiple times based on the source data.'),
getAttribute('v-on', undefined, 'Attaches an event listener to the element.'),
getAttribute('v-bind', undefined, 'Dynamically binds one or more attributes, or a component prop to an expression.'),
getAttribute('v-model', undefined, 'Creates a two-way binding on a form input element or a component.'),
getAttribute('v-pre', 'v', 'Skips compilation for this element and all its children.'),
getAttribute('v-cloak', 'v', 'Indicates Vue instance for this element has NOT finished compilation.'),
getAttribute('v-once', 'v', 'Render the element and component once only.'),
getAttribute('key', undefined, 'Hint at VNodes identity for VDom diffing, e.g. list rendering'),
getAttribute('ref', undefined, 'Register a reference to an element or a child component.'),
getAttribute(
'slot',
u,
undefined,
'Used on content inserted into child components to indicate which named slot the content belongs to.'
),
genAttribute('slot-scope', u, 'the name of a temporary variable that holds the props object passed from the child')
getAttribute(
'slot-scope',
undefined,
'the name of a temporary variable that holds the props object passed from the child'
)
];

const transitionProps = [
genAttribute('name', u, 'Used to automatically generate transition CSS class names. Default: "v"'),
genAttribute('appear', 'b', 'Whether to apply transition on initial render. Default: false'),
genAttribute(
getAttribute('name', undefined, 'Used to automatically generate transition CSS class names. Default: "v"'),
getAttribute('appear', 'b', 'Whether to apply transition on initial render. Default: false'),
getAttribute(
'css',
'b',
'Whether to apply CSS transition classes. Defaults: true. If set to false, will only trigger JavaScript hooks registered via component events.'
),
genAttribute(
getAttribute(
'type',
'transType',
'The event, "transition" or "animation", to determine end timing. Default: the type that has a longer duration.'
),
genAttribute(
getAttribute(
'mode',
'transMode',
'Controls the timing sequence of leaving/entering transitions. Available modes are "out-in" and "in-out"; Defaults to simultaneous.'
Expand All @@ -72,35 +84,48 @@ const transitionProps = [
].map(t => genAttribute(t))
);

function genTag(tag: string, doc: string, attributes: Attribute[]) {
return new HTMLTagSpecification(doc + '\n\n' + `[API Reference](https://vuejs.org/v2/api/#${tag})`, attributes);
}

const vueTags = {
component: new HTMLTagSpecification(
component: genTag(
'component',
'A meta component for rendering dynamic components. The actual component to render is determined by the `is` prop.',
[
genAttribute('is', u, 'the actual component to render'),
genAttribute('is', undefined, 'the actual component to render'),
genAttribute('inline-template', 'v', 'treat inner content as its template rather than distributed content')
]
),
transition: new HTMLTagSpecification(
transition: genTag(
'transition',
'<transition> serves as transition effects for single element/component. It applies the transition behavior to the wrapped content inside.',
transitionProps
),
'transition-group': new HTMLTagSpecification(
'transition-group': genTag(
'transition-group',
'transition group serves as transition effects for multiple elements/components. It renders a <span> by default and can render user specified element via `tag` attribute.',
transitionProps.concat(genAttribute('tag'), genAttribute('move-class'))
),
'keep-alive': new HTMLTagSpecification(
'keep-alive': genTag(
'keep-alive',
'When wrapped around a dynamic component, <keep-alive> caches the inactive component instances without destroying them.',
['include', 'exclude'].map(t => genAttribute(t))
),
slot: new HTMLTagSpecification(
slot: genTag(
'slot',
'<slot> serve as content distribution outlets in component templates. <slot> itself will be replaced.',
[genAttribute('name', u, 'Used for named slot')]
[genAttribute('name', undefined, 'Used for named slot')]
),
template: new HTMLTagSpecification(
'The template element is used to declare fragments of HTML that can be cloned and inserted in the document by script.',
[
genAttribute('scope', u, '(deprecated) a temporary variable that holds the props object passed from the child'),
genAttribute('slot', u, 'the name of scoped slot')
genAttribute(
'scope',
undefined,
'(deprecated) a temporary variable that holds the props object passed from the child'
),
genAttribute('slot', undefined, 'the name of scoped slot')
]
)
};
Expand Down
31 changes: 29 additions & 2 deletions test/completionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { CompletionItem, MarkdownString } from 'vscode';
import { showFile } from './editorHelper';

export interface ExpectedCompletionItem extends CompletionItem {
/**
* Documentation has to start with this string
*/
documentationStart?: string;
/**
* Documentation has to include this string
*/
documentationFragment?: string;
}

export async function testCompletion(
Expand Down Expand Up @@ -67,9 +74,29 @@ export async function testCompletion(

if (ei.documentationStart) {
if (typeof match.documentation === 'string') {
assert.ok(match.documentation.startsWith(ei.documentationStart));
assert.ok(
match.documentation.startsWith(ei.documentationStart),
`${match.documentation}\ndoes not start with\n${ei.documentationStart}`
);
} else {
assert.ok((match.documentation as vscode.MarkdownString).value.startsWith(ei.documentationStart));
assert.ok(
(match.documentation as vscode.MarkdownString).value.startsWith(ei.documentationStart),
`${(match.documentation as vscode.MarkdownString).value}\ndoes not start with\n${ei.documentationStart}`
);
}
}

if (ei.documentationFragment) {
if (typeof match.documentation === 'string') {
assert.ok(
match.documentation.includes(ei.documentationFragment),
`${match.documentation}\ndoes not include\n${ei.documentationFragment}`
);
} else {
assert.ok(
(match.documentation as vscode.MarkdownString).value.includes(ei.documentationFragment),
`${(match.documentation as vscode.MarkdownString).value}\ndoes not include\n${ei.documentationFragment}`
);
}
}
}
Expand Down
30 changes: 29 additions & 1 deletion test/componentData/features/completion/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,40 @@ describe('Should complete frameworks', () => {
const vueRouterUri = getDocUri('completion/VueRouter.vue');

it('completes vue-router tags', async () => {
await testCompletion(vueRouterUri, position(3, 5), ['router-link', 'router-view']);
await testCompletion(vueRouterUri, position(4, 5), ['router-link', 'router-view']);
});

it('completes vue-router attributes', async () => {
await testCompletion(vueRouterUri, position(2, 17), ['replace']);
});

it('completes vue-router attribute values', async () => {
await testCompletion(vueRouterUri, position(3, 37), ['page', 'step']);
});
});

describe('Should complete vue/vue-router with documentation', () => {
const linkUri = getDocUri('completion/Link.vue');

it('completes attributes with URI to API docs', async () => {
await testCompletion(linkUri, position(3, 5), [
{
label: 'component',
documentationFragment: '[API Reference](https://vuejs.org/v2/api/#component)'
}
]);

await testCompletion(linkUri, position(2, 17), [
{
label: 'replace',
documentationFragment: '[API Reference](https://router.vuejs.org/api/#replace)'
},
{
label: 'v-if',
documentationFragment: '[API Reference](https://vuejs.org/v2/api/#v-if)'
}
]);
});
});

describe('Should complete element-ui components (devDependency, loaded from bundled JSON)', () => {
Expand Down
6 changes: 6 additions & 0 deletions test/componentData/fixture/completion/Link.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<div>
<router-link ></router-link>
<
</div>
</template>
1 change: 1 addition & 0 deletions test/componentData/fixture/completion/VueRouter.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div>
<router-link ></router-link>
<router-link aria-current-value=""></router-link>
<
</div>
</template>
2 changes: 1 addition & 1 deletion test/componentData/fixture/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"dependencies": {
"quasar": "^1.12.13",
"vue": "^2.6.11",
"vue-router": "^3.3.4"
"vue-router": "3.3.4"
},
"devDependencies": {
"element-ui": "^2.13.2"
Expand Down
8 changes: 4 additions & 4 deletions test/componentData/fixture/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ throttle-debounce@^1.0.1:
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-1.1.0.tgz#51853da37be68a155cb6e827b3514a3c422e89cd"
integrity sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==

vue-router@^3.3.4:
version "3.4.2"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.2.tgz#541221d7ac467786c1c9381bcf36019d883b9cf8"
integrity sha512-n3Ok70hW0EpcJF4lcWIwSHAQbFTnIOLl/fhO8+oTs4jHNtBNsovcVvPZeTOyKEd8C3xF1Crft2ASuOiVT5K1mw==
[email protected]:
version "3.3.4"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.3.4.tgz#4e38abc34a11c41b6c3d8244449a2e363ba6250b"
integrity sha512-SdKRBeoXUjaZ9R/8AyxsdTqkOfMcI5tWxPZOUX5Ie1BTL5rPSZ0O++pbiZCeYeythiZIdLEfkDiQPKIaWk5hDg==

vue@^2.6.11:
version "2.6.11"
Expand Down

0 comments on commit 449e7c6

Please sign in to comment.