Skip to content

Commit d57fc72

Browse files
authored
fix(parser): JavaScript files are not all interpreted as a Vue component anymore (#137)
* fix(parser): don't interpret all .js files a Vue component * test: add tests to prevent regression * style
1 parent 4a6a464 commit d57fc72

File tree

8 files changed

+231
-46
lines changed

8 files changed

+231
-46
lines changed

cypress/integration/templates/default.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,30 @@ describe('Template JS: default', () => {
330330
cy.contains('created()').should('not.exist');
331331
});
332332
});
333+
334+
describe('JS files rendered or not as Vue Component', () => {
335+
before(() => {
336+
cy.visit('/../../../example/docs/index.html');
337+
cy.screenshot();
338+
});
339+
340+
it('NotVueComponent should not be rendered as Vue component', () => {
341+
cy.get('nav > ul > li a[href="global.html#helloWorld"]').contains('helloWorld');
342+
cy.get('nav > ul > li a[href="module-NotVueComponent.html"]').should('not.exist');
343+
});
344+
345+
it('NotVueComponent2 should not be rendered as Vue component', () => {
346+
cy.visit('/../../../example/docs/module-NotVueComponent2.html');
347+
348+
cy.get('nav > ul > li a[href="module-NotVueComponent2.html"]').contains('NotVueComponent2');
349+
cy
350+
.contains('h3', 'Methods')
351+
.should('have.attr', 'class', 'subsection-title')
352+
.next()
353+
.contains('theMethod')
354+
.next('h5')
355+
.next('.params')
356+
.next('.details')
357+
.contains('a[href="js_NotVueComponent2.js.html#line11"]', 'line 11');
358+
});
359+
});

cypress/integration/templates/docstrap.spec.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
describe('Template: docstrap', () => {
44
before(() => {
5-
cy.visit('/../../../example/docs-docstrap//module-better-components_BetterCounter.html');
5+
cy.visit('/../../../example/docs-docstrap/module-better-components_BetterCounter.html');
66
cy.screenshot();
77
});
88

@@ -167,7 +167,7 @@ describe('Template: docstrap', () => {
167167

168168
describe('Template JS: docstrap', () => {
169169
before(() => {
170-
cy.visit('/../../../example/docs-docstrap//module-CounterJS.html');
170+
cy.visit('/../../../example/docs-docstrap/module-CounterJS.html');
171171
cy.screenshot();
172172
});
173173

@@ -329,3 +329,33 @@ describe('Template JS: docstrap', () => {
329329
cy.contains('created()').should('not.exist');
330330
});
331331
});
332+
333+
334+
describe('JS files rendered or not as Vue Component', () => {
335+
before(() => {
336+
cy.visit('/../../../example/docs-docstrap/index.html');
337+
cy.screenshot();
338+
});
339+
340+
it('NotVueComponent should not be rendered as Vue component', () => {
341+
cy.get('.nav .dropdown a[href="global.html#helloWorld"]').contains('helloWorld');
342+
cy.get('.nav .dropdown a[href="module-NotVueComponent.html"]').should('not.exist');
343+
});
344+
345+
it('NotVueComponent2 should not be rendered as Vue component', () => {
346+
cy.visit('/../../../example/docs-docstrap/module-NotVueComponent2.html');
347+
348+
cy.get('.nav .dropdown a[href="module-NotVueComponent2.html"]').contains('NotVueComponent2');
349+
350+
cy
351+
.contains('h3', 'Methods')
352+
.should('have.attr', 'class', 'subsection-title')
353+
.next('dl')
354+
.find('h4')
355+
.contains('theMethod')
356+
.parent()
357+
.next('dd')
358+
.find('.details')
359+
.contains('a[href="js_NotVueComponent2.js.html#sunlight-1-line-11"]', 'line 11');
360+
});
361+
});

cypress/integration/templates/minami.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,30 @@ describe('Template JS: minami', () => {
327327
cy.contains('created()').should('not.exist');
328328
});
329329
});
330+
331+
332+
describe('JS files rendered or not as Vue Component', () => {
333+
before(() => {
334+
cy.visit('/../../../example/docs-minami/index.html');
335+
cy.screenshot();
336+
});
337+
338+
it('NotVueComponent should not be rendered as Vue component', () => {
339+
cy.get('.nav-item-name a[href="global.html#helloWorld"]').contains('helloWorld');
340+
cy.get('.nav-item-name a[href="module-NotVueComponent.html"]').should('not.exist');
341+
});
342+
343+
it('NotVueComponent2 should not be rendered as Vue component', () => {
344+
cy.visit('/../../../example/docs-minami/module-NotVueComponent2.html');
345+
346+
cy.get('.nav-item-name a[href="module-NotVueComponent2.html"]').contains('NotVueComponent2');
347+
cy
348+
.contains('h3', 'Methods')
349+
.should('have.attr', 'class', 'subsection-title')
350+
.next('.section-method')
351+
.find('h4.name')
352+
.contains('theMethod')
353+
.next('dl.details')
354+
.contains('a[href="js_NotVueComponent2.js.html#line11"]', 'line 11');
355+
});
356+
});

cypress/integration/templates/tui.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,30 @@ describe('Template JS: tui', () => {
319319
cy.contains('created()').should('not.exist');
320320
});
321321
});
322+
323+
324+
describe('JS files rendered or not as Vue Component', () => {
325+
before(() => {
326+
cy.visit('/../../../example/docs-tui/index.html');
327+
cy.screenshot();
328+
});
329+
330+
it('NotVueComponent should not be rendered as Vue component', () => {
331+
cy.get('.lnb-api a[href="global.html#helloWorld"]').contains('helloWorld');
332+
cy.get('.lnb-api a[href="module-NotVueComponent.html"]').should('not.exist');
333+
});
334+
335+
it('NotVueComponent2 should not be rendered as Vue component', () => {
336+
cy.visit('/../../../example/docs-tui/module-NotVueComponent2.html');
337+
338+
cy.get('.lnb-api a[href="module-NotVueComponent2.html"]').contains('NotVueComponent2');
339+
cy
340+
.contains('h3', 'Methods')
341+
.should('have.attr', 'class', 'subsection-title')
342+
.next('dl')
343+
.find('.name')
344+
.contains('theMethod')
345+
.find('.container-source.members')
346+
.contains('a[href="js_NotVueComponent2.js.html#line11"]', 'line 11');
347+
});
348+
});

example/src/js/NotVueComponent.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import NotVueComponent2 from './NotVueComponent2';
2+
3+
NotVueComponent2.theMethod('123');
4+
5+
/**
6+
* @return {Boolean}
7+
*/
8+
const helloWorld = () => {
9+
console.log('Hello world');
10+
return false;
11+
};
12+
13+
export {
14+
helloWorld,
15+
};

example/src/js/NotVueComponent2.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @module NotVueComponent2
3+
*/
4+
module.exports = {
5+
state: {
6+
foo: 'bar',
7+
},
8+
/**
9+
* @param {String} i
10+
*/
11+
theMethod(i) {
12+
console.log(i);
13+
},
14+
};

index.js

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const config = require('./config');
33
const render = require('./lib/core/renderer');
44
const extractVueScript = require('./lib/core/vueScriptExtractor');
55
const seekExportDefaultLine = require('./lib/core/seekExportDefaultLine');
6+
const { isSingleFileComponent, isJSComponent } = require('./lib/core/issers');
67
const vueDataTag = require('./lib/tags/vue-data');
78
const vuePropTag = require('./lib/tags/vue-prop');
89
const vueComputedTag = require('./lib/tags/vue-computed');
@@ -19,58 +20,61 @@ exports.handlers = {
1920
}
2021
},
2122
newDoclet(e) {
22-
const isJs = e.doclet.meta.filename.endsWith('.js');
23-
const endsWith = e.doclet.meta.filename.endsWith('.vue') || e.doclet.meta.filename.endsWith('.js');
24-
if (endsWith) {
25-
const fullPath = join(e.doclet.meta.path, e.doclet.meta.filename);
26-
const componentName = e.doclet.meta.filename.replace(/\.vue$/, '').replace(/\.js$/, '');
23+
const fileIsSingleFileComponent = isSingleFileComponent(e.doclet);
24+
const fileIsJSComponent = isJSComponent(e.doclet);
2725

28-
// The main doclet before `export default {}`
29-
if (e.doclet.longname === 'module.exports') {
30-
e.doclet.kind = 'module';
31-
e.doclet.name = componentName;
32-
e.doclet.alias = componentName;
33-
e.doclet.longname = `module:${componentName}`;
34-
}
26+
if (!fileIsSingleFileComponent && !fileIsJSComponent) {
27+
return;
28+
}
3529

36-
if (
37-
!/[.~#]/.test(e.doclet.longname) // filter component's properties and member, not the best way but it werks
38-
&& e.doclet.longname.startsWith('module:')
39-
) {
40-
mainDocletLines[fullPath] = e.doclet.meta.lineno;
41-
}
30+
const fullPath = join(e.doclet.meta.path, e.doclet.meta.filename);
31+
const componentName = e.doclet.meta.filename.replace(/\.(vue|js)$/, '');
4232

43-
// It can be the main doclet before `export default {}`
44-
// with at least one `@vue-*` tag
45-
if (e.doclet._isVueDoc) {
46-
const { template } = config['jsdoc-vuejs'];
47-
const data = {
48-
props: e.doclet._vueProps || [],
49-
data: e.doclet._vueData || [],
50-
computed: e.doclet._vueComputed || [],
51-
};
33+
// The main doclet before `export default {}`
34+
if (e.doclet.longname === 'module.exports') {
35+
e.doclet.kind = 'module';
36+
e.doclet.name = componentName;
37+
e.doclet.alias = componentName;
38+
e.doclet.longname = `module:${componentName}`;
39+
}
5240

53-
render(template, data, (err, str) => {
54-
if (err) throw err;
41+
if (
42+
!/[.~#]/.test(e.doclet.longname) // filter component's properties and member, not the best way but it werks
43+
&& e.doclet.longname.startsWith('module:')
44+
) {
45+
mainDocletLines[fullPath] = e.doclet.meta.lineno;
46+
}
5547

56-
e.doclet.description = str;
57-
});
48+
// It can be the main doclet before `export default {}`
49+
// with at least one `@vue-*` tag
50+
if (e.doclet._isVueDoc) {
51+
const { template } = config['jsdoc-vuejs'];
52+
const data = {
53+
props: e.doclet._vueProps || [],
54+
data: e.doclet._vueData || [],
55+
computed: e.doclet._vueComputed || [],
56+
};
5857

59-
// Remove meta for not rendering source for this doclet
60-
delete e.doclet.meta;
61-
}
58+
render(template, data, (err, str) => {
59+
if (err) throw err;
60+
61+
e.doclet.description = str;
62+
});
63+
64+
// Remove meta for not rendering source for this doclet
65+
delete e.doclet.meta;
66+
}
6267

63-
// Methods and hooks
64-
if (e.doclet.kind === 'function' && 'memberof' in e.doclet) {
65-
if (e.doclet.memberof.endsWith('.methods')) {
66-
e.doclet.scope = 'instance';
67-
e.doclet.memberof = e.doclet.memberof.replace(/\.methods$/, ''); // force method to be displayed
68-
if (!isJs) {
69-
e.doclet.meta.lineno += exportDefaultLines[fullPath] - mainDocletLines[fullPath];
70-
}
71-
} else {
72-
e.doclet.memberof = null; // don't include Vue hooks
68+
// Methods and hooks
69+
if (e.doclet.kind === 'function' && 'memberof' in e.doclet) {
70+
if (e.doclet.memberof.endsWith('.methods')) {
71+
e.doclet.scope = 'instance';
72+
e.doclet.memberof = e.doclet.memberof.replace(/\.methods$/, ''); // force method to be displayed
73+
if (fileIsSingleFileComponent) {
74+
e.doclet.meta.lineno += exportDefaultLines[fullPath] - mainDocletLines[fullPath];
7375
}
76+
} else {
77+
e.doclet.memberof = null; // don't include Vue hooks
7478
}
7579
}
7680
},

lib/core/issers.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const path = require('path');
2+
3+
const hasVueTagRE = /\s*\*\s*@vue/;
4+
5+
// Useful because we use `@vue` tag from first doclet of a .js file
6+
// to determine if this file is a Vue Component.
7+
/**
8+
* @type {Object.<String, Boolean>}
9+
*/
10+
const jsComponentsCache = {};
11+
12+
/**
13+
* @param {Doclet} doclet
14+
*/
15+
function isSingleFileComponent(doclet) {
16+
return doclet.meta.filename.endsWith('.vue');
17+
}
18+
19+
/**
20+
* @param {Doclet} doclet
21+
*/
22+
function isJSComponent(doclet) {
23+
const fullPath = path.join(doclet.meta.path, doclet.meta.filename);
24+
25+
if (fullPath in jsComponentsCache) {
26+
return jsComponentsCache[fullPath];
27+
}
28+
29+
const res = doclet.meta.filename.endsWith('.js') && hasVueTagRE.test(doclet.comment || '');
30+
31+
if (res) {
32+
jsComponentsCache[fullPath] = true;
33+
}
34+
35+
return res;
36+
}
37+
38+
module.exports = {
39+
isSingleFileComponent,
40+
isJSComponent,
41+
};

0 commit comments

Comments
 (0)