esdoc
Version:
Good Documentation Generator For JavaScript
369 lines (297 loc) • 10.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
var _fsExtra = require('fs-extra');
var _fsExtra2 = _interopRequireDefault(_fsExtra);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
var _colorLogger = require('color-logger');
var _colorLogger2 = _interopRequireDefault(_colorLogger);
var _ASTUtil = require('./Util/ASTUtil.js');
var _ASTUtil2 = _interopRequireDefault(_ASTUtil);
var _ESParser = require('./Parser/ESParser');
var _ESParser2 = _interopRequireDefault(_ESParser);
var _PathResolver = require('./Util/PathResolver.js');
var _PathResolver2 = _interopRequireDefault(_PathResolver);
var _DocFactory = require('./Factory/DocFactory.js');
var _DocFactory2 = _interopRequireDefault(_DocFactory);
var _InvalidCodeLogger = require('./Util/InvalidCodeLogger.js');
var _InvalidCodeLogger2 = _interopRequireDefault(_InvalidCodeLogger);
var _Plugin = require('./Plugin/Plugin.js');
var _Plugin2 = _interopRequireDefault(_Plugin);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* API Documentation Generator.
*
* @example
* let config = {source: './src', destination: './esdoc'};
* ESDoc.generate(config, (results, config)=>{
* console.log(results);
* });
*/
class ESDoc {
/**
* Generate documentation.
* @param {ESDocConfig} config - config for generation.
*/
static generate(config) {
(0, _assert2.default)(config.source);
(0, _assert2.default)(config.destination);
this._checkOldConfig(config);
_Plugin2.default.init(config.plugins);
_Plugin2.default.onStart();
config = _Plugin2.default.onHandleConfig(config);
this._setDefaultConfig(config);
_colorLogger2.default.debug = !!config.debug;
const includes = config.includes.map(v => new RegExp(v));
const excludes = config.excludes.map(v => new RegExp(v));
let packageName = null;
let mainFilePath = null;
if (config.package) {
try {
const packageJSON = _fsExtra2.default.readFileSync(config.package, { encode: 'utf8' });
const packageConfig = JSON.parse(packageJSON);
packageName = packageConfig.name;
mainFilePath = packageConfig.main;
} catch (e) {
// ignore
}
}
let results = [];
const asts = [];
const sourceDirPath = _path2.default.resolve(config.source);
this._walk(config.source, filePath => {
const relativeFilePath = _path2.default.relative(sourceDirPath, filePath);
let match = false;
for (const reg of includes) {
if (relativeFilePath.match(reg)) {
match = true;
break;
}
}
if (!match) return;
for (const reg of excludes) {
if (relativeFilePath.match(reg)) return;
}
console.log(`parse: ${filePath}`);
const temp = this._traverse(config.source, filePath, packageName, mainFilePath);
if (!temp) return;
results.push(...temp.results);
if (config.outputAST) {
asts.push({ filePath: `source${_path2.default.sep}${relativeFilePath}`, ast: temp.ast });
}
});
// config.index
if (config.index) {
results.push(this._generateForIndex(config));
}
// config.package
if (config.package) {
results.push(this._generateForPackageJSON(config));
}
results = this._resolveDuplication(results);
results = _Plugin2.default.onHandleDocs(results);
// index.json
{
const dumpPath = _path2.default.resolve(config.destination, 'index.json');
_fsExtra2.default.outputFileSync(dumpPath, JSON.stringify(results, null, 2));
}
// ast, array will be empty if config.outputAST is false - resulting in skipping the loop
for (const ast of asts) {
const json = JSON.stringify(ast.ast, null, 2);
const filePath = _path2.default.resolve(config.destination, `ast/${ast.filePath}.json`);
_fsExtra2.default.outputFileSync(filePath, json);
}
// publish
this._publish(config);
_Plugin2.default.onComplete();
}
/**
* check ESDoc config. and if it is old, exit with warning message.
* @param {ESDocConfig} config - check config
* @private
*/
static _checkOldConfig(config) {
let exit = false;
const keys = [['access', 'esdoc-standard-plugin'], ['autoPrivate', 'esdoc-standard-plugin'], ['unexportedIdentifier', 'esdoc-standard-plugin'], ['undocumentIdentifier', 'esdoc-standard-plugin'], ['builtinExternal', 'esdoc-standard-plugin'], ['coverage', 'esdoc-standard-plugin'], ['test', 'esdoc-standard-plugin'], ['title', 'esdoc-standard-plugin'], ['manual', 'esdoc-standard-plugin'], ['lint', 'esdoc-standard-plugin'], ['includeSource', 'esdoc-exclude-source-plugin'], ['styles', 'esdoc-inject-style-plugin'], ['scripts', 'esdoc-inject-script-plugin'], ['experimentalProposal', 'esdoc-ecmascript-proposal-plugin']];
for (const [key, plugin] of keys) {
if (key in config) {
console.log(`[31merror: config.${key} is invalid. Please use ${plugin}. how to migration: https://esdoc.org/manual/migration.html[0m`);
exit = true;
}
}
if (exit) process.exit(1);
}
/**
* set default config to specified config.
* @param {ESDocConfig} config - specified config.
* @private
*/
static _setDefaultConfig(config) {
if (!config.includes) config.includes = ['\\.js$'];
if (!config.excludes) config.excludes = ['\\.config\\.js$', '\\.test\\.js$'];
if (!config.index) config.index = './README.md';
if (!config.package) config.package = './package.json';
if (!('outputAST' in config)) config.outputAST = true;
}
/**
* walk recursive in directory.
* @param {string} dirPath - target directory path.
* @param {function(entryPath: string)} callback - callback for find file.
* @private
*/
static _walk(dirPath, callback) {
const entries = _fsExtra2.default.readdirSync(dirPath);
for (const entry of entries) {
const entryPath = _path2.default.resolve(dirPath, entry);
const stat = _fsExtra2.default.statSync(entryPath);
if (stat.isFile()) {
callback(entryPath);
} else if (stat.isDirectory()) {
this._walk(entryPath, callback);
}
}
}
/**
* traverse doc comment in JavaScript file.
* @param {string} inDirPath - root directory path.
* @param {string} filePath - target JavaScript file path.
* @param {string} [packageName] - npm package name of target.
* @param {string} [mainFilePath] - npm main file path of target.
* @returns {Object} - return document that is traversed.
* @property {DocObject[]} results - this is contained JavaScript file.
* @property {AST} ast - this is AST of JavaScript file.
* @private
*/
static _traverse(inDirPath, filePath, packageName, mainFilePath) {
_colorLogger2.default.i(`parsing: ${filePath}`);
let ast;
try {
ast = _ESParser2.default.parse(filePath);
} catch (e) {
_InvalidCodeLogger2.default.showFile(filePath, e);
return null;
}
const pathResolver = new _PathResolver2.default(inDirPath, filePath, packageName, mainFilePath);
const factory = new _DocFactory2.default(ast, pathResolver);
_ASTUtil2.default.traverse(ast, (node, parent) => {
try {
factory.push(node, parent);
} catch (e) {
_InvalidCodeLogger2.default.show(filePath, node);
throw e;
}
});
return { results: factory.results, ast: ast };
}
/**
* generate index doc
* @param {ESDocConfig} config
* @returns {Tag}
* @private
*/
static _generateForIndex(config) {
let indexContent = '';
if (_fsExtra2.default.existsSync(config.index)) {
indexContent = _fsExtra2.default.readFileSync(config.index, { encode: 'utf8' }).toString();
} else {
console.log(`[31mwarning: ${config.index} is not found. Please check config.index.[0m`);
}
const tag = {
kind: 'index',
content: indexContent,
longname: _path2.default.resolve(config.index),
name: config.index,
static: true,
access: 'public'
};
return tag;
}
/**
* generate package doc
* @param {ESDocConfig} config
* @returns {Tag}
* @private
*/
static _generateForPackageJSON(config) {
let packageJSON = '';
let packagePath = '';
try {
packageJSON = _fsExtra2.default.readFileSync(config.package, { encoding: 'utf-8' });
packagePath = _path2.default.resolve(config.package);
} catch (e) {
// ignore
}
const tag = {
kind: 'packageJSON',
content: packageJSON,
longname: packagePath,
name: _path2.default.basename(packagePath),
static: true,
access: 'public'
};
return tag;
}
/**
* resolve duplication docs
* @param {Tag[]} docs
* @returns {Tag[]}
* @private
*/
static _resolveDuplication(docs) {
const memberDocs = docs.filter(doc => doc.kind === 'member');
const removeIds = [];
for (const memberDoc of memberDocs) {
// member duplicate with getter/setter/method.
// when it, remove member.
// getter/setter/method are high priority.
const sameLongnameDoc = docs.find(doc => doc.longname === memberDoc.longname && doc.kind !== 'member');
if (sameLongnameDoc) {
removeIds.push(memberDoc.__docId__);
continue;
}
const dup = docs.filter(doc => doc.longname === memberDoc.longname && doc.kind === 'member');
if (dup.length > 1) {
const ids = dup.map(v => v.__docId__);
ids.sort((a, b) => {
return a < b ? -1 : 1;
});
ids.shift();
removeIds.push(...ids);
}
}
return docs.filter(doc => !removeIds.includes(doc.__docId__));
}
/**
* publish content
* @param {ESDocConfig} config
* @private
*/
static _publish(config) {
try {
const write = (filePath, content, option) => {
const _filePath = _path2.default.resolve(config.destination, filePath);
content = _Plugin2.default.onHandleContent(content, _filePath);
console.log(`output: ${_filePath}`);
_fsExtra2.default.outputFileSync(_filePath, content, option);
};
const copy = (srcPath, destPath) => {
const _destPath = _path2.default.resolve(config.destination, destPath);
console.log(`output: ${_destPath}`);
_fsExtra2.default.copySync(srcPath, _destPath);
};
const read = filePath => {
const _filePath = _path2.default.resolve(config.destination, filePath);
return _fsExtra2.default.readFileSync(_filePath).toString();
};
_Plugin2.default.onPublish(write, copy, read);
} catch (e) {
_InvalidCodeLogger2.default.showError(e);
process.exit(1);
}
}
}
exports.default = ESDoc;
;