2017-07-11 15:57:26 +00:00
|
|
|
const fs = require('fs');
|
|
|
|
const path = require('path');
|
2017-07-07 16:36:45 +00:00
|
|
|
const esprima = require('esprima');
|
|
|
|
const ESTreeWalker = require('../../third_party/chromium/ESTreeWalker');
|
|
|
|
const Documentation = require('./Documentation');
|
|
|
|
|
|
|
|
class JSOutline {
|
|
|
|
constructor(text) {
|
|
|
|
this.classes = [];
|
|
|
|
this._currentClassName = null;
|
2017-07-14 05:52:02 +00:00
|
|
|
this._currentClassMembers = [];
|
2017-07-07 16:36:45 +00:00
|
|
|
|
|
|
|
this._text = text;
|
|
|
|
let ast = esprima.parseScript(this._text, {loc: true, range: true});
|
|
|
|
let walker = new ESTreeWalker(node => {
|
|
|
|
if (node.type === 'ClassDeclaration')
|
|
|
|
this._onClassDeclaration(node);
|
|
|
|
else if (node.type === 'MethodDefinition')
|
|
|
|
this._onMethodDefinition(node);
|
|
|
|
});
|
|
|
|
walker.walk(ast);
|
|
|
|
this._flushClassIfNeeded();
|
|
|
|
}
|
|
|
|
|
|
|
|
_onClassDeclaration(node) {
|
|
|
|
this._flushClassIfNeeded();
|
2017-07-11 15:57:26 +00:00
|
|
|
this._currentClassName = this._extractText(node.id);
|
2017-07-07 16:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_onMethodDefinition(node) {
|
|
|
|
console.assert(this._currentClassName !== null);
|
2017-07-11 15:57:26 +00:00
|
|
|
console.assert(node.value.type === 'FunctionExpression');
|
2017-07-14 06:11:39 +00:00
|
|
|
let methodName = this._extractText(node.key);
|
|
|
|
if (node.kind === 'get') {
|
|
|
|
let property = Documentation.Member.createProperty(methodName);
|
|
|
|
this._currentClassMembers.push(property);
|
|
|
|
return;
|
|
|
|
}
|
2017-07-13 22:54:37 +00:00
|
|
|
// Async functions have return value.
|
|
|
|
let hasReturn = node.value.async;
|
2017-07-12 15:01:21 +00:00
|
|
|
// Extract properties from constructor.
|
|
|
|
if (node.kind === 'constructor') {
|
2017-07-13 22:54:37 +00:00
|
|
|
// Extract properties from constructor.
|
2017-07-12 15:01:21 +00:00
|
|
|
let walker = new ESTreeWalker(node => {
|
|
|
|
if (node.type !== 'AssignmentExpression')
|
|
|
|
return;
|
|
|
|
node = node.left;
|
|
|
|
if (node.type === 'MemberExpression' && node.object &&
|
|
|
|
node.object.type === 'ThisExpression' && node.property &&
|
|
|
|
node.property.type === 'Identifier')
|
2017-07-14 05:52:02 +00:00
|
|
|
this._currentClassMembers.push(Documentation.Member.createProperty(node.property.name));
|
2017-07-12 15:01:21 +00:00
|
|
|
});
|
|
|
|
walker.walk(node);
|
2017-07-13 22:54:37 +00:00
|
|
|
} else if (!hasReturn) {
|
|
|
|
let walker = new ESTreeWalker(node => {
|
|
|
|
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
|
|
|
|
return ESTreeWalker.SkipSubtree;
|
|
|
|
if (node.type === 'ReturnStatement')
|
|
|
|
hasReturn = hasReturn || !!node.argument;
|
|
|
|
});
|
|
|
|
walker.walk(node.value.body);
|
2017-07-12 15:01:21 +00:00
|
|
|
}
|
2017-07-13 22:54:37 +00:00
|
|
|
const args = [];
|
|
|
|
for (let param of node.value.params)
|
|
|
|
args.push(new Documentation.Argument(this._extractText(param)));
|
|
|
|
let method = Documentation.Member.createMethod(methodName, args, hasReturn, node.value.async);
|
|
|
|
this._currentClassMembers.push(method);
|
2017-07-12 15:01:21 +00:00
|
|
|
return ESTreeWalker.SkipSubtree;
|
|
|
|
}
|
|
|
|
|
2017-07-07 16:36:45 +00:00
|
|
|
_flushClassIfNeeded() {
|
|
|
|
if (this._currentClassName === null)
|
|
|
|
return;
|
2017-07-14 05:52:02 +00:00
|
|
|
let jsClass = new Documentation.Class(this._currentClassName, this._currentClassMembers);
|
2017-07-07 16:36:45 +00:00
|
|
|
this.classes.push(jsClass);
|
|
|
|
this._currentClassName = null;
|
2017-07-14 05:52:02 +00:00
|
|
|
this._currentClassMembers = [];
|
2017-07-07 16:36:45 +00:00
|
|
|
}
|
|
|
|
|
2017-07-11 15:57:26 +00:00
|
|
|
_extractText(node) {
|
2017-07-07 16:36:45 +00:00
|
|
|
if (!node)
|
|
|
|
return null;
|
|
|
|
let text = this._text.substring(node.range[0], node.range[1]).trim();
|
2017-07-11 15:57:26 +00:00
|
|
|
return text;
|
2017-07-07 16:36:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-11 15:57:26 +00:00
|
|
|
/**
|
|
|
|
* @param {!Array<string>} dirPath
|
|
|
|
* @return {!Promise<!Documentation>}
|
|
|
|
*/
|
|
|
|
module.exports = async function(dirPath) {
|
|
|
|
let filePaths = fs.readdirSync(dirPath)
|
|
|
|
.filter(fileName => fileName.endsWith('.js'))
|
|
|
|
.map(fileName => path.join(dirPath, fileName));
|
|
|
|
let classes = [];
|
|
|
|
for (let filePath of filePaths) {
|
|
|
|
let outline = new JSOutline(fs.readFileSync(filePath, 'utf8'));
|
|
|
|
classes.push(...outline.classes);
|
|
|
|
}
|
|
|
|
return new Documentation(classes);
|
|
|
|
};
|
|
|
|
|