Skip to content

Commit c7b4d9b

Browse files
committed
feat(experimental): class properties
1 parent e2689dd commit c7b4d9b

File tree

8 files changed

+170
-10
lines changed

8 files changed

+170
-10
lines changed

src/Doc/ClassPropertyDoc.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import AbstractDoc from './AbstractDoc.js';
2+
import MethodDoc from './MethodDoc.js';
3+
import ParamParser from '../Parser/ParamParser.js';
4+
5+
/**
6+
* Doc Class from ClassProperty AST node.
7+
*/
8+
export default class ClassPropertyDoc extends AbstractDoc {
9+
/**
10+
* apply own tag.
11+
* @private
12+
*/
13+
_apply() {
14+
super._apply();
15+
16+
Reflect.deleteProperty(this._value, 'export');
17+
Reflect.deleteProperty(this._value, 'importPath');
18+
Reflect.deleteProperty(this._value, 'importStyle');
19+
}
20+
21+
/** specify ``member`` to kind. */
22+
_$kind() {
23+
super._$kind();
24+
this._value.kind = 'member';
25+
}
26+
27+
/** take out self name from self node */
28+
_$name() {
29+
super._$name();
30+
this._value.name = this._node.key.name;
31+
}
32+
33+
/** borrow {@link MethodDoc#@_memberof} */
34+
_$memberof() {
35+
Reflect.apply(MethodDoc.prototype._$memberof, this, []);
36+
}
37+
38+
/** if @type is not exists, guess type by using self node */
39+
_$type() {
40+
super._$type();
41+
if (this._value.type) return;
42+
43+
this._value.type = ParamParser.guessType(this._node.value);
44+
}
45+
}

src/Factory/DocFactory.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import CommentParser from '../Parser/CommentParser.js';
33
import FileDoc from '../Doc/FileDoc.js';
44
import ClassDoc from '../Doc/ClassDoc.js';
55
import MethodDoc from '../Doc/MethodDoc.js';
6+
import ClassProperty from '../Doc/ClassPropertyDoc';
67
import MemberDoc from '../Doc/MemberDoc.js';
78
import FunctionDoc from '../Doc/FunctionDoc.js';
89
import VariableDoc from '../Doc/VariableDoc.js';
@@ -374,6 +375,7 @@ export default class DocFactory {
374375
switch (type) {
375376
case 'Class': Clazz = ClassDoc; break;
376377
case 'Method': Clazz = MethodDoc; break;
378+
case 'ClassProperty': Clazz = ClassProperty; break;
377379
case 'Member': Clazz = MemberDoc; break;
378380
case 'Function': Clazz = FunctionDoc; break;
379381
case 'Variable': Clazz = VariableDoc; break;
@@ -416,8 +418,10 @@ export default class DocFactory {
416418
switch (node.type) {
417419
case 'ClassDeclaration':
418420
return this._decideClassDeclarationType(node);
419-
case 'ClassMethod': // for babylon
421+
case 'ClassMethod':
420422
return this._decideMethodDefinitionType(node);
423+
case 'ClassProperty':
424+
return this._decideClassPropertyType(node);
421425
case 'ExpressionStatement':
422426
return this._decideExpressionStatementType(node);
423427
case 'FunctionDeclaration':
@@ -461,6 +465,22 @@ export default class DocFactory {
461465
}
462466
}
463467

468+
/**
469+
* decide Doc type from class property node.
470+
* @param {ASTNode} node - target node that is classs property node.
471+
* @returns {{type: ?string, node: ?ASTNode}} decided type.
472+
* @private
473+
*/
474+
_decideClassPropertyType(node) {
475+
const classNode = this._findUp(node, ['ClassDeclaration', 'ClassExpression']);
476+
if (this._processedClassNodes.includes(classNode)) {
477+
return {type: 'ClassProperty', node: node};
478+
} else {
479+
logger.w('this class property is not in class', node);
480+
return {type: null, node: null};
481+
}
482+
}
483+
464484
/**
465485
* decide Doc type from function declaration node.
466486
* @param {ASTNode} node - target node that is function declaration node.

src/Parser/ESParser.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ export default class ESParser {
1616
* @returns {AST} AST of source code.
1717
*/
1818
static parse(config, filePath) {
19-
return this.parseWithBabylon(config, filePath);
19+
return this._parseWithBabylon(config, filePath);
2020
}
2121

22-
static parseWithBabylon(config, filePath) {
22+
/**
23+
* parse ECMAScript source code with babylon.
24+
* @param {ESDocConfig} config - config of esdoc.
25+
* @param {string} filePath - source code file path.
26+
* @returns {AST} AST of source code.
27+
*/
28+
static _parseWithBabylon(config, filePath) {
2329
let code = fs.readFileSync(filePath, {encode: 'utf8'}).toString();
2430
code = Plugin.onHandleCode(code, filePath);
2531
if (code.charAt(0) === '#') code = code.replace(/^#!/, '//');
2632

27-
const option = {
28-
sourceType: 'module',
29-
plugins: ['jsx']
30-
};
33+
const option = this._buildParserOptionForBabylon(config);
3134

3235
let parser = (code) => {
3336
return babylon.parse(code, option);
@@ -41,4 +44,25 @@ export default class ESParser {
4144

4245
return ast;
4346
}
47+
48+
/**
49+
* build babylon option.
50+
* @param {ESDocConfig} config - config of esdoc
51+
* @returns {{sourceType: string, plugins: string[]}} option of babylon.
52+
* @private
53+
*/
54+
static _buildParserOptionForBabylon(config) {
55+
const option = {
56+
sourceType: 'module',
57+
plugins: ['jsx']
58+
};
59+
60+
const experimental = config.experimentalProposal;
61+
62+
if (experimental) {
63+
if (experimental.classProperties) option.plugins.push('classProperties');
64+
}
65+
66+
return option;
67+
}
4468
}

src/Typedef/typedef.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
* @property {string[]} manual.example
2929
* @property {string[]} manual.faq
3030
* @property {string[]} manual.changelog
31+
* @property {Object} [experimentalProposal]
32+
* @property {boolean} experimentalProposal.classProperties
3133
* @see https://esdoc.org/config.html
3234
*/
3335

test/fixture/package/esdoc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@
2020
"example": ["./test/fixture/package/manual/example.md"],
2121
"faq": ["./test/fixture/package/manual/faq.md"],
2222
"changelog": ["./test/fixture/package/CHANGELOG.md"]
23+
},
24+
"experimentalProposal": {
25+
"classProperties": true
2326
}
2427
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* this is TestClassPropertyDefinition.
3+
* @todo test `Access`, `Deprecated`, `Desc`, `Duplication`, `Example`, `Experimental`, `Guess`, `Ignore`, `Link`, `See`, `Since`, `Todo` and `Version`.
4+
*/
5+
export default class TestClassPropertyDefinition {
6+
/**
7+
* this is static p1.
8+
* @type {number}
9+
*/
10+
static p1 = 123;
11+
12+
/**
13+
* this is p1.
14+
* @type {number}
15+
*/
16+
p1 = 123;
17+
}

test/src/HTMLTest/CoverageTest/CoverageTest.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ describe('test coverage', ()=> {
99
it('has coverage summary', ()=> {
1010
assert(badge.includes('79%'));
1111
assert.includes(doc, '[data-ice="coverageBadge"]', './badge.svg', 'src');
12-
assert.includes(doc, '[data-ice="totalCoverageCount"]', '263/330');
13-
assert.equal(doc.find('[data-ice="file"] [data-ice="coverage"]').length, 116);
12+
assert.includes(doc, '[data-ice="totalCoverageCount"]', '266/333');
13+
assert.equal(doc.find('[data-ice="file"] [data-ice="coverage"]').length, 117);
1414
});
1515

1616
/* eslint-disable max-statements */
@@ -33,6 +33,7 @@ describe('test coverage', ()=> {
3333
test('file/src/Async/Function.js.html', '100 %1/1');
3434
test('file/src/Async/Method.js.html', '100 %2/2');
3535
test('file/src/Class/Definition.js.html', '100 %8/8');
36+
test('file/src/ClassProperty/Definition.js.html', '100 %3/3');
3637
test('file/src/Computed/Method.js.html', '100 %11/11');
3738
test('file/src/Computed/Property.js.html', '100 %12/12');
3839
test('file/src/Deprecated/Class.js.html#errorLines=6', '75 %3/4');
@@ -140,6 +141,6 @@ describe('test coverage', ()=> {
140141
test('file/src/Version/Function.js.html', '100 %1/1');
141142
test('file/src/Version/Variable.js.html', '100 %1/1');
142143

143-
assert.equal(count, 116);
144+
assert.equal(count, 117);
144145
});
145146
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {readDoc, assert, find} from './../../../util.js';
2+
3+
/** @test {ClassDocBuilder} */
4+
describe('TestClassPropertyDefinition:', ()=> {
5+
const doc = readDoc('class/src/ClassProperty/Definition.js~TestClassPropertyDefinition.html');
6+
7+
/** @test {ClassDocBuilder#_buildClassDoc} */
8+
describe('in summary', ()=>{
9+
it('has static member', ()=>{
10+
find(doc, '[data-ice="staticMemberSummary"]', (doc)=>{
11+
find(doc, 'table[data-ice="summary"]:nth-of-type(1)', (doc)=>{
12+
assert.includes(doc, '[data-ice="target"]:nth-of-type(1)', 'public static p1: number this is static p1.');
13+
assert.includes(doc, '[data-ice="target"]:nth-of-type(1) [data-ice="name"] a', 'class/src/ClassProperty/Definition.js~TestClassPropertyDefinition.html#static-member-p1', 'href');
14+
});
15+
});
16+
});
17+
18+
it('has member.', ()=>{
19+
find(doc, '[data-ice="memberSummary"]', (doc)=>{
20+
find(doc, 'table[data-ice="summary"]:nth-of-type(1)', (doc)=> {
21+
assert.includes(doc, '[data-ice="target"]:nth-of-type(1)', 'public p1: number this is p1.');
22+
assert.includes(doc, '[data-ice="target"]:nth-of-type(1) [data-ice="name"] a', 'class/src/ClassProperty/Definition.js~TestClassPropertyDefinition.html#instance-member-p1', 'href');
23+
});
24+
});
25+
});
26+
});
27+
28+
/** @test {ClassDocBuilder#_buildClassDoc} */
29+
describe('in detail', ()=>{
30+
it('has static member.', ()=>{
31+
find(doc, '[data-ice="staticMemberDetails"]', (doc)=>{
32+
find(doc, '[data-ice="detail"]:nth-of-type(1)', (doc)=>{
33+
assert.includes(doc, '#static-member-p1', 'public static p1: number');
34+
assert.includes(doc, '[data-ice="description"]', 'this is static p1.');
35+
});
36+
});
37+
});
38+
39+
it('has member.', ()=>{
40+
find(doc, '[data-ice="memberDetails"]', (doc)=>{
41+
find(doc, '[data-ice="detail"]:nth-of-type(1)', (doc)=>{
42+
assert.includes(doc, '#instance-member-p1', 'public p1: number');
43+
assert.includes(doc, '#instance-member-p1 + [data-ice="description"]', 'this is p1.');
44+
});
45+
});
46+
});
47+
});
48+
});

0 commit comments

Comments
 (0)