feat(jsdoc-parse): add the source file's module type, if known, to the doclet meta info

This commit is contained in:
Jeff Williams 2023-12-25 11:57:37 -08:00
parent e8bbe5b694
commit 5a6998bce7
No known key found for this signature in database
4 changed files with 100 additions and 66 deletions

View File

@ -28,7 +28,7 @@ let uid = 100000000;
export const MODULE_TYPES = {
AMD: 'amd',
COMMON_JS: 'commonJs',
COMMON_JS: 'commonjs',
ES6: 'es6',
};

View File

@ -642,6 +642,7 @@ walkers[Syntax.YieldExpression] = (node, parent, state, cb) => {
function handleNode(node, parent, cbState) {
let currentScope;
const isScope = astNode.isScope(node);
let moduleType;
const { walker } = cbState;
astNode.addNodeProperties(node);
@ -657,7 +658,12 @@ function handleNode(node, parent, cbState) {
}
cbState.nodes.push(node);
cbState.moduleType ??= astNode.detectModuleType(node);
if (!cbState.moduleType) {
moduleType = cbState.moduleType = astNode.detectModuleType(node);
if (moduleType) {
cbState.moduleTypes.set(cbState.filename, moduleType);
}
}
if (!walker._walkers[node.type]) {
walker._logUnknownNodeType(node);
@ -675,8 +681,9 @@ function handleNode(node, parent, cbState) {
*/
export class Walker {
// TODO: docs
constructor(env, walkerFuncs = walkers) {
constructor(env, { moduleTypes }, walkerFuncs = walkers) {
this._log = env.log;
this._moduleTypes = moduleTypes;
this._walkers = walkerFuncs;
}
@ -692,6 +699,7 @@ export class Walker {
const state = {
filename: filename,
moduleType: null,
moduleTypes: this._moduleTypes,
nodes: [],
scopes: [],
walker: this,

View File

@ -13,6 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from 'node:events';
import fs from 'node:fs';
@ -75,8 +76,9 @@ export class Parser extends EventEmitter {
this._docletStore = new DocletStore(dependencies);
this._emitter = dependencies.get('emitter');
this._log = dependencies.get('log');
this.moduleTypes = new Map();
this._visitor = new Visitor();
this._walker = new Walker(dependencies);
this._walker = new Walker(dependencies, this);
this._visitor.setParser(this);
@ -242,7 +244,9 @@ export class Parser extends EventEmitter {
/** @private */
_walkAst(ast, visitor, sourceName) {
this._walker.recurse(ast, visitor, sourceName);
const { filename, moduleType } = this._walker.recurse(ast, visitor, sourceName);
this.moduleTypes.set(filename, moduleType);
}
// TODO: docs

View File

@ -13,6 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import path from 'node:path';
import { astNode, Syntax } from '@jsdoc/ast';
import { name } from '@jsdoc/core';
import { combineDoclets } from '@jsdoc/doclet';
@ -53,9 +56,8 @@ function isValidJsdoc(commentSrc) {
}
// TODO: docs
function getLeadingJsdocComment(node) {
function getLeadingJsdocComment({ leadingComments }) {
let comment = null;
let leadingComments = node.leadingComments;
if (Array.isArray(leadingComments) && leadingComments.length) {
// the attached comments may include line comments, which we don't want
@ -106,6 +108,7 @@ function getParentDocletFromEvent(parser, { doclet }) {
function makeInlineParamsFinisher(parser) {
return (e) => {
let documentedParams;
const { doclet: eventDoclet } = e;
let knownParams;
let param;
let parentDoclet;
@ -119,7 +122,7 @@ function makeInlineParamsFinisher(parser) {
// we only want to use the doclet if it's param-specific (but not, for example, if it's
// a param tagged with `@exports` in an AMD module)
if (e.doclet.kind !== 'param') {
if (eventDoclet.kind !== 'param') {
return;
}
@ -131,22 +134,22 @@ function makeInlineParamsFinisher(parser) {
param = documentedParams[i];
// is the param already documented? if so, we don't need to use the doclet
if (param?.name === e.doclet.name) {
e.doclet.undocumented = true;
if (param?.name === eventDoclet.name) {
eventDoclet.undocumented = true;
break;
}
// if we ran out of documented params, or we're at the parameter's actual position,
// splice in the param at the current index
if (!param || i === knownParams.indexOf(e.doclet.name)) {
if (!param || i === knownParams.indexOf(eventDoclet.name)) {
documentedParams.splice(i, 0, {
type: e.doclet.type || {},
type: eventDoclet.type ?? {},
description: '',
name: e.doclet.name,
name: eventDoclet.name,
});
// the doclet is no longer needed
e.doclet.undocumented = true;
eventDoclet.undocumented = true;
break;
}
@ -190,8 +193,7 @@ function findRestParam(params) {
* the parameter is repeatable.
*/
function makeRestParamFinisher() {
return (e) => {
const doclet = e.doclet;
return ({ code, doclet }) => {
let documentedParams;
let restNode;
@ -199,9 +201,9 @@ function makeRestParamFinisher() {
return;
}
documentedParams = doclet.params = doclet.params || [];
documentedParams = doclet.params ??= [];
restNode = findRestParam(
e.code.node.params || e.code.node.value?.params || e.code.node.init?.params || []
code.node.params || code.node.value?.params || code.node.init?.params || []
);
if (restNode) {
@ -252,9 +254,8 @@ function findDefaultParams(params) {
* parameters.
*/
function makeDefaultParamFinisher() {
return (e) => {
return ({ code, doclet }) => {
let defaultValues;
const doclet = e.doclet;
let documentedParams;
let paramName;
let params;
@ -263,8 +264,8 @@ function makeDefaultParamFinisher() {
return;
}
documentedParams = doclet.params = doclet.params || [];
params = e.code.node.params || e.code.node.value?.params || [];
documentedParams = doclet.params ??= [];
params = code.node.params ?? code.node.value?.params ?? [];
defaultValues = findDefaultParams(params);
for (let i = 0, j = 0, l = params.length; i < l; i++) {
@ -307,21 +308,20 @@ function makeDefaultParamFinisher() {
* @return {function} A function that merges the constructor's doclet into the class's doclet.
*/
function makeConstructorFinisher(parser) {
return (e) => {
return ({ code, doclet: eventDoclet }) => {
let combined;
let doclets;
const eventDoclet = e.doclet;
let nodeId;
let parentDoclet;
// for class declarations that are named module exports, the node that's documented is the
// ExportNamedDeclaration, not the ClassDeclaration
if (e.code.node.parent.parent.parent?.type === Syntax.ExportNamedDeclaration) {
nodeId = e.code.node.parent.parent.parent.nodeId;
if (code.node.parent.parent.parent?.type === Syntax.ExportNamedDeclaration) {
nodeId = code.node.parent.parent.parent.nodeId;
}
// otherwise, we want the ClassDeclaration
else {
nodeId = e.code.node.parent.parent.nodeId;
nodeId = code.node.parent.parent.nodeId;
}
doclets = parser._docletStore.docletsByNodeId.get(nodeId);
// Use the first documented doclet for the parent node.
@ -347,14 +347,12 @@ function makeConstructorFinisher(parser) {
* @return {function} A function that adds an `async` property to the doclet of async functions.
*/
function makeAsyncFunctionFinisher() {
return (e) => {
const doclet = e.doclet;
return ({ code, doclet }) => {
if (!doclet) {
return;
}
if (e.code.node.async || e.code.node.value?.async || e.code.node.init?.async) {
if (code.node.async || code.node.value?.async || code.node.init?.async) {
doclet.async = true;
}
};
@ -379,23 +377,45 @@ function makePrivatePropertyFinisher() {
* @return {function} A function that marks a doclet as a generator function.
*/
function makeGeneratorFinisher() {
return (e) => {
const doclet = e.doclet;
return ({ code, doclet }) => {
if (!doclet) {
return;
}
if (e.code.node.generator || e.code.node.init?.generator || e.code.node.value?.generator) {
if (code.node.generator || code.node.init?.generator || code.node.value?.generator) {
doclet.generator = true;
}
};
}
/**
* Creates a function that adds the source file's module type to a doclet.
*
* @private
* @return {function} A function that adds the module type to the doclet's `meta` info.
*/
function makeModuleTypeFinisher(moduleTypes) {
return ({ doclet }) => {
let filepath;
let type;
if (!doclet || !doclet.meta.filename || !doclet.meta.path) {
return;
}
filepath = path.join(doclet.meta.path, doclet.meta.filename);
type = moduleTypes.get(filepath);
if (type) {
doclet.meta.moduletype = type;
}
};
}
// TODO: docs
class SymbolFound {
// TODO: docs
constructor(node, filename, extras = {}) {
constructor(node, parser, filename, extras = {}) {
this.id = extras.id || node.nodeId;
this.comment = extras.comment || getLeadingJsdocComment(node) || '@undocumented';
this.lineno = extras.lineno || node.loc.start.line;
@ -406,6 +426,7 @@ class SymbolFound {
this.code = extras.code;
this.event = extras.event || 'symbolFound';
this.finishers = extras.finishers || [];
this.moduletype = null;
// make sure the event includes properties that don't have default values
Object.keys(extras).forEach((key) => {
@ -474,12 +495,13 @@ function makeSymbolFoundEvent(node, parser, filename) {
const extras = {
code: astNode.getInfo(node),
finishers: [makeModuleTypeFinisher(parser.moduleTypes)],
};
switch (node.type) {
// like: i = 0;
case Syntax.AssignmentExpression:
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
@ -495,8 +517,8 @@ function makeSymbolFoundEvent(node, parser, filename) {
parent = node.parent;
if (node.leadingComments && parent && astNode.isFunction(parent)) {
extras.finishers = [makeInlineParamsFinisher(parser)];
e = new SymbolFound(node, filename, extras);
extras.finishers.push(makeInlineParamsFinisher(parser));
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
}
@ -509,7 +531,7 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like: let MyClass = class {}
case Syntax.ClassExpression:
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
@ -519,23 +541,23 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like `#b = 1` in: class A { #b = 1; }
case Syntax.ClassPrivateProperty:
extras.finishers = [parser.resolveEnum, makePrivatePropertyFinisher()];
extras.finishers.push(parser.resolveEnum, makePrivatePropertyFinisher());
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
break;
// like `b = 1` in: class A { b = 1; }
case Syntax.ClassProperty:
extras.finishers = [parser.resolveEnum];
extras.finishers.push(parser.resolveEnum);
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
break;
// like: export * from 'foo'
case Syntax.ExportAllDeclaration:
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
break;
@ -550,7 +572,7 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like `foo as bar` in: export {foo as bar}
case Syntax.ExportSpecifier:
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
@ -566,7 +588,7 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like: var foo = function() {};
case Syntax.FunctionExpression:
extras.finishers = [
extras.finishers.push(
// handle cases where at least one parameter has a default value
makeDefaultParamFinisher(),
// handle rest parameters
@ -574,10 +596,10 @@ function makeSymbolFoundEvent(node, parser, filename) {
// handle async functions
makeAsyncFunctionFinisher(),
// handle generator functions
makeGeneratorFinisher(),
];
makeGeneratorFinisher()
);
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
@ -595,8 +617,8 @@ function makeSymbolFoundEvent(node, parser, filename) {
// function parameters with inline comments
if (node.leadingComments && parent && astNode.isFunction(parent)) {
extras.finishers = [makeInlineParamsFinisher(parser)];
e = new SymbolFound(node, filename, extras);
extras.finishers.push(makeInlineParamsFinisher(parser));
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
}
@ -608,7 +630,7 @@ function makeSymbolFoundEvent(node, parser, filename) {
// No need to fire an event unless the node is already commented.
case Syntax.MemberExpression:
if (node.leadingComments) {
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
}
break;
@ -616,7 +638,7 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like: foo() {}
// or: constructor() {}
case Syntax.MethodDefinition:
extras.finishers = [
extras.finishers.push(
// handle cases where at least one parameter has a default value
makeDefaultParamFinisher(),
// handle rest parameters
@ -624,20 +646,20 @@ function makeSymbolFoundEvent(node, parser, filename) {
// handle async functions
makeAsyncFunctionFinisher(),
// handle generator functions
makeGeneratorFinisher(),
];
makeGeneratorFinisher()
);
// for constructors, we attempt to merge the constructor's docs into the class's docs
if (node.kind === 'constructor') {
extras.finishers.push(makeConstructorFinisher(parser));
}
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
break;
// like `{}` in: function Foo = Class.create(/** @lends Foo */ {});
case Syntax.ObjectExpression:
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
break;
@ -645,10 +667,10 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like `get bar() {}` in: var foo = { get bar() {} };
case Syntax.Property:
if (node.kind !== 'get' && node.kind !== 'set') {
extras.finishers = [parser.resolveEnum];
extras.finishers.push(parser.resolveEnum);
}
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
break;
@ -657,8 +679,8 @@ function makeSymbolFoundEvent(node, parser, filename) {
parent = node.parent;
if (node.leadingComments && parent && astNode.isFunction(parent)) {
extras.finishers = [makeInlineParamsFinisher(parser)];
e = new SymbolFound(node, filename, extras);
extras.finishers.push(makeInlineParamsFinisher(parser));
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);
}
@ -667,7 +689,7 @@ function makeSymbolFoundEvent(node, parser, filename) {
// like: var i = 0;
case Syntax.VariableDeclarator:
extras.finishers = [
extras.finishers.push(
// handle cases where at least one parameter has a default value
makeDefaultParamFinisher(),
// handle rest parameters
@ -675,10 +697,10 @@ function makeSymbolFoundEvent(node, parser, filename) {
// handle async functions
makeAsyncFunctionFinisher(),
// handle generator functions
makeGeneratorFinisher(),
];
makeGeneratorFinisher()
);
e = new SymbolFound(node, filename, extras);
e = new SymbolFound(node, parser, filename, extras);
trackVars(parser, node, e);