Skip to content

Commit 5b004c6

Browse files
authored
Merge pull request #2018 from pikax/feat/allow_loading_customTags_from_package.json
feat: allow load customTags from ./package.json
2 parents 438bac0 + 8df8c95 commit 5b004c6

File tree

9 files changed

+129
-42
lines changed

9 files changed

+129
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- `vls` now only supports Node `>=10`, as Prettier 2.0 drops support for Node 8.
66
- Upgrade to prettier 2.0. #1925 and #1794.
77
- Add [prettier/plugin-pug](https://github.com/prettier/plugin-pug) as default formatter for `pug`. #527.
8+
- 🙌 Cusom tags IntelliSense for local `tags.json`/`attributes.json`. [Usage Docs](https://vuejs.github.io/vetur/framework.html#workspace-custom-tags). Thanks to contribution from [Carlos Rodrigues](https://github.com/pikax). #1364 and #2018.
89
- 🙌 Detect tags from @nuxt/components. Thanks to contribution from [pooya parsa](https://github.com/pi0). #1921.
910
- 🙌 Fix VTI crash by passing correct PID to language server. Thanks to contribution from [Daniil Yastremskiy](@TheBeastOfCaerbannog). #1699 and #1805.
1011
- 🙌 Fix template interpolation hover info of v-for readonly array item. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #1788.

docs/framework.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Framework Support
22

3-
Vue frameworks can define custom components used in `<template>` region. For example, `vue-router` provides `<router-link>` component that could have attributes such as `to` and `replace`. Vetur currently provides autocomplete support for the component names and attributes.
3+
Vue libraries or frameworks can define custom components used in `<template>` region. For example, [`vue-router`](https://router.vuejs.org/) provides [`<router-link>`](https://router.vuejs.org/api/#router-link) component that could have attributes such as `to` and `replace`. Vetur currently provides autocomplete support for the component names and attributes.
44

55
Vetur currently provides framework support for the following vue libraries:
66

@@ -14,6 +14,8 @@ Vetur currently provides framework support for the following vue libraries:
1414
- [Quasar Framework](https://quasar.dev/)
1515
- [Gridsome](https://gridsome.org/)
1616

17+
🚧 The data format is not specified yet. 🚧
18+
1719
## Usage
1820

1921
Vetur reads the `package.json` **in your project root** to determine if it should offer tags & attributes completions. Here are the exact dependencies and sources of their definitions.
@@ -48,6 +50,26 @@ If a package listed in `dependencies` has a `vetur` key, then Vetur will try to
4850

4951
By bundling the tags / attributes definitions together with the framework library, you ensure that users will always get the matching tags / attributes with the specific version of your library they are using.
5052

53+
## Workspace Custom Tags
54+
55+
You can define custom tags/attributes for your workspace by specifying a `vetur` key in package.json. For example, to get auto completion for tag `<foo-tag>`, all you need to do is:
56+
57+
- Create a file `tags.json` with:
58+
59+
```json
60+
{ "foo-bar": { "description": "A foo tag" } }
61+
```
62+
63+
- Add this line to `package.json`:
64+
65+
```json
66+
{
67+
"vetur": { "tags": "./tags.json" }
68+
}
69+
```
70+
71+
- Reload VS Code. You'll get `foo-bar` when completing `<|`.
72+
5173
## Adding a Framework
5274

5375
If your Vue UI framework has a lot of users, we might consider bundling its support in Vetur. However, this means Vetur's definition for the framework might become outdated.

server/src/modes/template/tagProviders/externalTagProviders.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,53 @@ export const onsenTagProvider = getExternalTagProvider('onsen', onsenTags, onsen
2121
export const bootstrapTagProvider = getExternalTagProvider('bootstrap', bootstrapTags, bootstrapAttributes);
2222
export const gridsomeTagProvider = getExternalTagProvider('gridsome', gridsomeTags, gridsomeAttributes);
2323

24-
export function getRuntimeTagProvider(workspacePath: string, pkg: any): IHTMLTagProvider | null {
25-
if (!pkg.vetur) {
24+
/**
25+
* Get tag providers specified in workspace root's packaage.json
26+
*/
27+
export function getWorkspaceTagProvider(workspacePath: string, rootPkgJson: any): IHTMLTagProvider | null {
28+
if (!rootPkgJson.vetur) {
29+
return null;
30+
}
31+
32+
const tagsPath = ts.findConfigFile(workspacePath, ts.sys.fileExists, rootPkgJson.vetur.tags);
33+
const attrsPath = ts.findConfigFile(workspacePath, ts.sys.fileExists, rootPkgJson.vetur.attributes);
34+
35+
try {
36+
if (tagsPath && attrsPath) {
37+
const tagsJson = JSON.parse(fs.readFileSync(tagsPath, 'utf-8'));
38+
const attrsJson = JSON.parse(fs.readFileSync(attrsPath, 'utf-8'));
39+
return getExternalTagProvider(rootPkgJson.name, tagsJson, attrsJson);
40+
}
41+
return null;
42+
} catch (err) {
43+
return null;
44+
}
45+
}
46+
47+
/**
48+
* Get tag providers specified in packaage.json's `vetur` key
49+
*/
50+
export function getDependencyTagProvider(workspacePath: string, depPkgJson: any): IHTMLTagProvider | null {
51+
if (!depPkgJson.vetur) {
2652
return null;
2753
}
2854

2955
const tagsPath = ts.findConfigFile(
3056
workspacePath,
3157
ts.sys.fileExists,
32-
path.join('node_modules/', pkg.name, pkg.vetur.tags)
58+
path.join('node_modules/', depPkgJson.name, depPkgJson.vetur.tags)
3359
);
3460
const attrsPath = ts.findConfigFile(
3561
workspacePath,
3662
ts.sys.fileExists,
37-
path.join('node_modules/', pkg.name, pkg.vetur.attributes)
63+
path.join('node_modules/', depPkgJson.name, depPkgJson.vetur.attributes)
3864
);
3965

4066
try {
4167
if (tagsPath && attrsPath) {
4268
const tagsJson = JSON.parse(fs.readFileSync(tagsPath, 'utf-8'));
4369
const attrsJson = JSON.parse(fs.readFileSync(attrsPath, 'utf-8'));
44-
return getExternalTagProvider(pkg.name, tagsJson, attrsJson);
70+
return getExternalTagProvider(depPkgJson.name, tagsJson, attrsJson);
4571
}
4672
return null;
4773
} catch (err) {

server/src/modes/template/tagProviders/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
onsenTagProvider,
88
bootstrapTagProvider,
99
gridsomeTagProvider,
10-
getRuntimeTagProvider
10+
getDependencyTagProvider,
11+
getWorkspaceTagProvider
1112
} from './externalTagProviders';
1213
export { getComponentInfoTagProvider as getComponentTags } from './componentInfoTagProvider';
1314
export { IHTMLTagProvider } from './common';
@@ -55,9 +56,9 @@ export function getTagProviderSettings(workspacePath: string | null | undefined)
5556
return settings;
5657
}
5758

58-
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
59-
const dependencies = packageJson.dependencies || {};
60-
const devDependencies = packageJson.devDependencies || {};
59+
const rootPkgJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
60+
const dependencies = rootPkgJson.dependencies || {};
61+
const devDependencies = rootPkgJson.devDependencies || {};
6162

6263
if (dependencies['vue-router']) {
6364
settings['router'] = true;
@@ -108,28 +109,33 @@ export function getTagProviderSettings(workspacePath: string | null | undefined)
108109
settings['gridsome'] = true;
109110
}
110111

112+
const workspaceTagProvider = getWorkspaceTagProvider(workspacePath, rootPkgJson);
113+
if (workspaceTagProvider) {
114+
allTagProviders.push(workspaceTagProvider);
115+
}
116+
111117
for (const dep in dependencies) {
112-
const runtimePkgPath = ts.findConfigFile(
118+
const runtimePkgJsonPath = ts.findConfigFile(
113119
workspacePath,
114120
ts.sys.fileExists,
115121
join('node_modules', dep, 'package.json')
116122
);
117123

118-
if (!runtimePkgPath) {
124+
if (!runtimePkgJsonPath) {
119125
continue;
120126
}
121127

122-
const runtimePkg = JSON.parse(fs.readFileSync(runtimePkgPath, 'utf-8'));
123-
if (!runtimePkg) {
128+
const runtimePkgJson = JSON.parse(fs.readFileSync(runtimePkgJsonPath, 'utf-8'));
129+
if (!runtimePkgJson) {
124130
continue;
125131
}
126132

127-
const tagProvider = getRuntimeTagProvider(workspacePath, runtimePkg);
128-
if (!tagProvider) {
133+
const depTagProvider = getDependencyTagProvider(workspacePath, runtimePkgJson);
134+
if (!depTagProvider) {
129135
continue;
130136
}
131137

132-
allTagProviders.push(tagProvider);
138+
allTagProviders.push(depTagProvider);
133139
settings[dep] = true;
134140
}
135141
} catch (e) {}

test/lsp/completion/template.test.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,99 +3,113 @@ import { position, getDocUri } from '../util';
33
import { testCompletion } from './helper';
44

55
describe('Should autocomplete for <template>', () => {
6-
const templateDocUri = getDocUri('client/completion/template/Basic.vue');
7-
const templateFrameworkDocUri = getDocUri('client/completion/template/Framework.vue');
8-
const templateQuasarDocUri = getDocUri('client/completion/template/Quasar.vue');
9-
const templateVuetifyDocUri = getDocUri('client/completion/template/Vuetify.vue');
6+
const basicUri = getDocUri('client/completion/template/Basic.vue');
7+
const elementUri = getDocUri('client/completion/template/Element.vue');
8+
const quasarUri = getDocUri('client/completion/template/Quasar.vue');
9+
const vuetifyUri = getDocUri('client/completion/template/Vuetify.vue');
10+
const workspaceCustomTagsUri = getDocUri('client/completion/template/WorkspaceCustomTags.vue');
1011

1112
before('activate', async () => {
1213
await activateLS();
13-
await showFile(templateDocUri);
14-
await showFile(templateFrameworkDocUri);
14+
await showFile(basicUri);
15+
await showFile(elementUri);
16+
await showFile(quasarUri);
17+
await showFile(vuetifyUri);
18+
await showFile(workspaceCustomTagsUri);
1519
await sleep(FILE_LOAD_SLEEP_TIME);
1620
});
1721

1822
describe('Should complete <template> section', () => {
1923
it('completes directives such as v-if', async () => {
20-
await testCompletion(templateDocUri, position(1, 8), ['v-if', 'v-cloak']);
24+
await testCompletion(basicUri, position(1, 8), ['v-if', 'v-cloak']);
2125
});
2226

2327
it('completes html tags', async () => {
24-
await testCompletion(templateDocUri, position(2, 6), ['img', 'iframe']);
28+
await testCompletion(basicUri, position(2, 6), ['img', 'iframe']);
2529
});
2630

2731
it('completes imported components', async () => {
28-
await testCompletion(templateDocUri, position(2, 6), ['item']);
32+
await testCompletion(basicUri, position(2, 6), ['item']);
2933
});
3034

3135
it('completes event modifiers when attribute startsWith @', async () => {
32-
await testCompletion(templateDocUri, position(3, 17), ['stop', 'prevent', 'capture']);
36+
await testCompletion(basicUri, position(3, 17), ['stop', 'prevent', 'capture']);
3337
});
3438

3539
it('completes event modifiers when attribute startsWith v-on', async () => {
36-
await testCompletion(templateDocUri, position(4, 21), ['stop', 'prevent', 'capture']);
40+
await testCompletion(basicUri, position(4, 21), ['stop', 'prevent', 'capture']);
3741
});
3842

3943
it('completes key modifiers when keyEvent', async () => {
40-
await testCompletion(templateDocUri, position(5, 21), ['enter', 'space', 'right']);
44+
await testCompletion(basicUri, position(5, 21), ['enter', 'space', 'right']);
4145
});
4246

4347
it('completes system modifiers when keyEvent', async () => {
44-
await testCompletion(templateDocUri, position(6, 26), ['ctrl', 'shift', 'exact']);
48+
await testCompletion(basicUri, position(6, 26), ['ctrl', 'shift', 'exact']);
4549
});
4650

4751
it('completes mouse modifiers when MouseEvent', async () => {
48-
await testCompletion(templateDocUri, position(7, 19), ['left', 'right', 'middle']);
52+
await testCompletion(basicUri, position(7, 19), ['left', 'right', 'middle']);
4953
});
5054

5155
it('completes system modifiers when MouseEvent', async () => {
52-
await testCompletion(templateDocUri, position(8, 25), ['ctrl', 'shift', 'exact']);
56+
await testCompletion(basicUri, position(8, 25), ['ctrl', 'shift', 'exact']);
5357
});
5458

5559
it('completes prop modifiers when attribute startsWith :', async () => {
56-
await testCompletion(templateDocUri, position(9, 17), ['sync']);
60+
await testCompletion(basicUri, position(9, 17), ['sync']);
5761
});
5862

5963
it('completes prop modifiers when attribute startsWith v-bind', async () => {
60-
await testCompletion(templateDocUri, position(10, 23), ['sync']);
64+
await testCompletion(basicUri, position(10, 23), ['sync']);
6165
});
6266

6367
it('completes vModel modifiers when attribute startsWith v-model', async () => {
64-
await testCompletion(templateDocUri, position(11, 19), ['lazy', 'number', 'trim']);
68+
await testCompletion(basicUri, position(11, 19), ['lazy', 'number', 'trim']);
6569
});
6670

6771
it('completes modifiers when have attribute value', async () => {
68-
await testCompletion(templateDocUri, position(12, 19), ['stop', 'prevent', 'capture']);
72+
await testCompletion(basicUri, position(12, 19), ['stop', 'prevent', 'capture']);
6973
});
7074
});
7175

7276
describe('Should complete element-ui components', () => {
7377
it('completes <el-button> and <el-card>', async () => {
74-
await testCompletion(templateFrameworkDocUri, position(2, 5), ['el-button', 'el-card']);
78+
await testCompletion(elementUri, position(2, 5), ['el-button', 'el-card']);
7579
});
7680

7781
it('completes attributes for <el-button>', async () => {
78-
await testCompletion(templateFrameworkDocUri, position(1, 14), ['size', 'type', 'plain']);
82+
await testCompletion(elementUri, position(1, 14), ['size', 'type', 'plain']);
7983
});
8084
});
8185

8286
describe('Should complete Quasar components', () => {
8387
it('completes <q-btn>', async () => {
84-
await testCompletion(templateQuasarDocUri, position(2, 5), ['q-btn']);
88+
await testCompletion(quasarUri, position(2, 5), ['q-btn']);
8589
});
8690

8791
it('completes attributes for <q-btn>', async () => {
88-
await testCompletion(templateQuasarDocUri, position(1, 10), ['label', 'icon']);
92+
await testCompletion(quasarUri, position(1, 10), ['label', 'icon']);
8993
});
9094
});
9195

9296
describe('Should complete Vuetify components', () => {
9397
it('completes <v-btn>', async () => {
94-
await testCompletion(templateVuetifyDocUri, position(2, 5), ['v-btn']);
98+
await testCompletion(vuetifyUri, position(2, 5), ['v-btn']);
9599
});
96100

97101
it('completes attributes for <v-btn>', async () => {
98-
await testCompletion(templateVuetifyDocUri, position(1, 10), ['color', 'fab']);
102+
await testCompletion(vuetifyUri, position(1, 10), ['color', 'fab']);
103+
});
104+
});
105+
106+
describe('Should complete tags defined in workspace', () => {
107+
it('completes <foo-tag>', async () => {
108+
await testCompletion(workspaceCustomTagsUri, position(2, 6), ['foo-tag']);
109+
});
110+
111+
it('completes attributes for <foo-bar>', async () => {
112+
await testCompletion(workspaceCustomTagsUri, position(1, 12), ['foo-attr']);
99113
});
100114
});
101115
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<template>
2+
<foo-tag f></foo-tag>
3+
<foo
4+
</template>

test/lsp/fixture/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,8 @@
5656
"prettier-eslint-cli": "^5.0.0",
5757
"typescript": "^3.8.3",
5858
"vue-template-compiler": "^2.6.11"
59+
},
60+
"vetur": {
61+
"tags": "./tags.json"
5962
}
6063
}

test/lsp/fixture/tags.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"foo-tag": {
3+
"attributes": [
4+
{
5+
"name": "foo-attr",
6+
"description": "A foo attribute"
7+
}
8+
],
9+
"description": "A foo tag"
10+
}
11+
}

0 commit comments

Comments
 (0)