jsdoc-to-markdown
jsdoc-to-markdown copied to clipboard
Maximum call stack size exceeded
I had a whole bunch of source files that caused jsdoc2md to throw RangeError: Maximum call stack size exceeded.
when I attempted to render them. I managed to reduce the issue down to this minimal reproduction (with v 5.0.3):
Foo.js:
/** */
export class Foo {
constructor() { }
}
template.hbs:
{{#class name="Foo"}}{{>docs}}{{/class}}
Result:
$ jsdoc2md --files ./Foo.js --template ./template.hbs
RangeError: Maximum call stack size exceeded
It doesn't appear to matter whether or not there is content in the jsdoc block, it seems like the only things required to trigger it are a doc block before the class and the class must have a constructor.
More info, if it's helpful...
$ jsdoc2md --version
5.0.3
$ jsdoc2md --files ./Foo.js --template ./template.hbs --config
{
"files": [
"./Foo.js"
],
"template": "./template.hbs"
}
$ jsdoc2md --files ./Foo.js --template ./template.hbs --json
[
{
"id": "Foo",
"longname": "Foo",
"name": "Foo",
"kind": "class",
"scope": "instance",
"memberof": "Foo",
"meta": {
"lineno": 1,
"filename": "Foo.js",
"path": "/Users/jasonk/jsdoc2md-repro"
},
"order": 0
}
]
$ jsdoc2md --files ./Foo.js --template ./template.hbs --jsdoc
[
{
"comment": "/** */",
"meta": {
"range": [
7,
44
],
"filename": "Foo.js",
"lineno": 1,
"columnno": 7,
"path": "/Users/jasonk/jsdoc2md-repro",
"code": {
"id": "astnode100000002",
"name": "exports.Foo",
"type": "ClassDeclaration"
}
},
"name": "Foo",
"longname": "Foo",
"kind": "class",
"scope": "global",
"undocumented": true
},
{
"comment": "",
"meta": {
"range": [
14,
44
],
"filename": "Foo.js",
"lineno": 1,
"columnno": 14,
"path": "/Users/jasonk/jsdoc2md-repro",
"code": {
"id": "astnode100000003",
"name": "Foo",
"type": "ClassDeclaration",
"paramnames": []
}
},
"undocumented": true,
"name": "Foo",
"longname": "Foo",
"kind": "class",
"scope": "global"
},
{
"comment": "",
"meta": {
"range": [
26,
42
],
"filename": "Foo.js",
"lineno": 1,
"columnno": 26,
"path": "/Users/jasonk/jsdoc2md-repro",
"code": {
"id": "astnode100000006",
"name": "exports.Foo",
"type": "MethodDefinition",
"paramnames": []
},
"vars": {
"": null
}
},
"undocumented": true,
"name": "Foo",
"longname": "Foo#Foo",
"kind": "class",
"memberof": "Foo",
"scope": "instance",
"params": []
},
{
"comment": "",
"meta": {
"range": [
26,
42
],
"filename": "Foo.js",
"lineno": 1,
"columnno": 26,
"path": "/Users/jasonk/jsdoc2md-repro",
"code": {
"id": "astnode100000006",
"name": "exports.Foo",
"type": "MethodDefinition",
"paramnames": []
}
},
"name": "Foo",
"longname": "Foo",
"kind": "class",
"memberof": "Foo",
"scope": "instance"
},
{
"kind": "package",
"longname": "package:undefined",
"files": [
"/Users/jasonk/jsdoc2md-repro/Foo.js"
]
}
]
After some more debugging I managed to get a stack trace. Looks like it might actually be a dmd
issue:
RangeError: Maximum call stack size exceeded
at Function.keys (<anonymous>)
at testValue (/Users/jasonk/jsdoc2md-repro/node_modules/test-value/index.js:23:19)
at /Users/jasonk/jsdoc2md-repro/node_modules/test-value/index.js:81:12
at Array.filter (<anonymous>)
at _identifiers (/Users/jasonk/jsdoc2md-repro/node_modules/dmd/helpers/ddata.js:460:38)
at Object._children (/Users/jasonk/jsdoc2md-repro/node_modules/dmd/helpers/ddata.js:478:16)
at /Users/jasonk/jsdoc2md-repro/node_modules/dmd/helpers/ddata.js:506:27
at Array.forEach (<anonymous>)
at iterate (/Users/jasonk/jsdoc2md-repro/node_modules/dmd/helpers/ddata.js:504:20)
at /Users/jasonk/jsdoc2md-repro/node_modules/dmd/helpers/ddata.js:506:9
I've tracked this down as far as the descendants
function in dmd/helpers/ddata.js
. Maybe it's because it's late and I'll figure it out tomorrow after sleeping on it, but at the moment I'm not really following what this function is trying to do. The function itself looks like this:
function descendants (options) {
var min = typeof options.hash.min !== 'undefined' ? options.hash.min : 2
delete options.hash.min
options.hash.memberof = this.id
var output = []
function iterate (childrenList) {
if (childrenList.length) {
childrenList.forEach(function (child) {
output.push(child)
iterate(_children.call(child, options))
})
}
}
iterate(_children.call(this, options))
if (output.length >= (min || 0)) return output
}
The problem is that the call to _children.call
that is inside the iterate
function is returning exactly the same thing that the one outside the iterate
function is returning, so once it enters this function that iterate
gets called recursively on the same object a few thousand times until it blows the stack..
I'm not yet sure what is causing the stack issue (i haven't traced it) but as is stressed in the wiki, an ESM module must include a @module
declaration at the top.
This should work.
/**
* @module something
*/
/**
* A description.
*/
export class Foo {
constructor() {}
}
@jasonk did @75lb suggestion work for you? I have gone through my library (that incidentally has been building jsdoc2md
successfully many months) and added the suggested @module
but it is still not building for me. (Throws Max Stack exception)
i get the "maximum call stack size exceeded" error when i do this
/**
* @module something
*/
/**
* A description.
* @alias module:something
* @typicalname othersomething
*/
export class Foo {
constructor() {}
}
if change the export syntax, no more error
class Foo {
constructor() {}
}
export {Foo}
using [email protected]
I hastily refactored the function yesterday, avoiding the recursion, which fixes the issue here:
/**
return a flat list containing all decendants
@param [sortBy] {string} - "kind"
@param [min] {number} - only returns if there are `min` children
@this {identifier}
@returns {identifier[]}
@static
*/
function descendants (options) {
var min = typeof options.hash.min !== 'undefined' ? options.hash.min : 2
delete options.hash.min
options.hash.memberof = this.id
var output = []
var newList = [this];
while(newList.length) {
const oldList = newList;
newList = [];
for(let el of oldList) {
for(let c of _children.call(el, options)) {
if(c.id === this.id) continue;
output.push(c);
newList.push(c)
}
}
}
if (output.length >= (min || 0)) return output
}
Only to hit the same issue at another point in code, though -- probably sig()
in ddata.js
In case it helps: when removing export
, the issue is avoided.