src/Doc/ClassDoc.js
import fs from 'fs-extra';
import AbstractDoc from './AbstractDoc.js';
import ParamParser from '../Parser/ParamParser.js';
import NamingUtil from '../Util/NamingUtil.js';
/**
* Doc Class from Class Declaration AST node.
*/
export default class ClassDoc extends AbstractDoc {
/**
* apply own tag.
* @private
*/
_apply() {
super._apply();
this['@interface']();
this['@extends']();
this['@implements']();
}
/** specify ``class`` to kind. */
['@_kind']() {
super['@_kind']();
if (this._value.kind) return;
this._value.kind = 'class';
}
/** take out self name from self node */
['@_name']() {
super['@_name']();
if (this._value.name) return;
if (this._node.id) {
this._value.name = this._node.id.name;
} else {
this._value.name = NamingUtil.filePathToName(this._pathResolver.filePath);
}
}
/** take out self memberof from file path. */
['@_memberof']() {
super['@_memberof']();
if (this._value.memberof) return;
this._value.memberof = this._pathResolver.filePath;
}
/** for @interface */
['@interface']() {
let tag = this._find(['@interface']);
if (tag) {
this._value.interface = ['', 'true', true].includes(tag.tagValue);
} else {
this._value.interface = false;
}
}
/** for @extends, does not need to use this tag. */
['@extends']() {
let values = this._findAllTagValues(['@extends', '@extend']);
if (values) {
this._value.extends = [];
for (let value of values) {
let {typeText} = ParamParser.parseParamValue(value, true, false, false);
this._value.extends.push(typeText);
}
return;
}
if (this._node.superClass) {
let node = this._node;
let longnames = [];
let targets = [];
if (node.superClass.type === 'CallExpression') {
targets.push(node.superClass.callee, ...node.superClass.arguments);
} else {
targets.push(node.superClass);
}
for (let target of targets) {
switch (target.type) {
case 'Identifier':
longnames.push(this._resolveLongname(target.name));
break;
case 'MemberExpression':
let fullIdentifier = this._flattenMemberExpression(target);
let rootIdentifier = fullIdentifier.split('.')[0];
let rootLongname = this._resolveLongname(rootIdentifier);
let filePath = rootLongname.replace(/~.*/, '');
longnames.push(`${filePath}~${fullIdentifier}`);
break;
}
}
if (node.superClass.type === 'CallExpression') {
// expression extends may have non-class, so filter only class by name rule.
longnames = longnames.filter((v)=> v.match(/^[A-Z]|^[$_][A-Z]/));
let filePath = this._pathResolver.fileFullPath;
let line = node.superClass.loc.start.line;
let start = node.superClass.loc.start.column;
let end = node.superClass.loc.end.column;
this._value.expressionExtends = this._readSelection(filePath, line, start, end);
}
if (longnames.length) this._value.extends = longnames;
}
}
/** for @implements */
['@implements'](){
let values = this._findAllTagValues(['@implements', '@implement']);
if (!values) return;
this._value.implements = [];
for (let value of values) {
let {typeText} = ParamParser.parseParamValue(value, true, false, false);
this._value.implements.push(typeText);
}
}
/**
* read selection text in file.
* @param {string} filePath - target file full path.
* @param {number} line - line number (one origin).
* @param {number} startColumn - start column number (one origin).
* @param {number} endColumn - end column number (one origin).
* @returns {string} selection text
* @private
*/
_readSelection(filePath, line, startColumn, endColumn) {
let code = fs.readFileSync(filePath).toString();
let lines = code.split('\n');
let selectionLine = lines[line - 1];
let tmp = [];
for (let i = startColumn; i < endColumn; i++) {
tmp.push(selectionLine.charAt(i));
}
return tmp.join('');
}
}