umberto 10.1.1 → 10.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -15
- package/package.json +12 -4
- package/scripts/renderer/markdown-it-plus.cjs +201 -0
- package/scripts/utils/markdown-it-toc-and-anchor.cjs +322 -0
- package/src/api-builder/classes/doc-data-factory.js +1 -2
- package/src/data-converter/middlewares/relation-fixer/add-missing-doclets.js +112 -0
- package/src/data-converter/middlewares/relation-fixer/build-relations.js +136 -0
- package/src/data-converter/middlewares/relation-fixer/doclet-collection.js +62 -0
- package/src/data-converter/middlewares/relation-fixer/get-missing-doclets-data.js +191 -0
- package/src/data-converter/middlewares/relation-fixer.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
## [10.1.2](https://github.com/cksource/umberto/compare/v10.1.1...v10.1.2) (March 9, 2026)
|
|
5
|
+
|
|
6
|
+
### Bug fixes
|
|
7
|
+
|
|
8
|
+
* Removed unmaintained `hexo-renderer-markdown-it-plus` and `@ckeditor/jsdoc-plugins` dependencies by replacing them with maintained and local implementations, resolving the reported `markdown-it`-related vulnerabilities in Umberto's dependency tree.
|
|
9
|
+
|
|
10
|
+
|
|
4
11
|
## [10.1.1](https://github.com/cksource/umberto/compare/v10.1.0...v10.1.1) (March 4, 2026)
|
|
5
12
|
|
|
6
13
|
### Bug fixes
|
|
@@ -43,21 +50,6 @@ Changelog
|
|
|
43
50
|
|
|
44
51
|
* Reduced the size of API docs sidebars by only rendering the full navigation tree for the current package. Other packages now link to their landing pages, significantly lowering total link count and improving build size and performance.
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
## [9.3.0](https://github.com/cksource/umberto/compare/v9.2.0...v9.3.0) (January 27, 2026)
|
|
48
|
-
|
|
49
|
-
### Features
|
|
50
|
-
|
|
51
|
-
* The API documentation builder now generates dedicated pages for [`Enum`](https://www.typescriptlang.org/docs/handbook/enums.html) types, presented in a format similar to object definitions.
|
|
52
|
-
|
|
53
|
-
### Bug fixes
|
|
54
|
-
|
|
55
|
-
* Improved rendering of `typedef` pages for union types by displaying their supported values, which were previously not shown.
|
|
56
|
-
|
|
57
|
-
### Other changes
|
|
58
|
-
|
|
59
|
-
* Improved rendering of [`templateLiteral`](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) types that reference a module. They are now rendered as clickable links pointing to the corresponding page.
|
|
60
|
-
|
|
61
53
|
---
|
|
62
54
|
|
|
63
55
|
To see all releases, visit the [release page](https://github.com/cksource/umberto/releases).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "umberto",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.2",
|
|
4
4
|
"description": "CKSource Documentation builder",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/core": "^7.18.10",
|
|
19
19
|
"@babel/preset-env": "^7.18.10",
|
|
20
|
-
"@ckeditor/jsdoc-plugins": "^43.0.0",
|
|
21
20
|
"@minify-html/node": "^0.17.1",
|
|
21
|
+
"@vscode/markdown-it-katex": "^1.1.2",
|
|
22
22
|
"babel-loader": "^10.0.0",
|
|
23
23
|
"css-select": "^6.0.0",
|
|
24
24
|
"dom-serializer": "^2.0.0",
|
|
@@ -33,16 +33,24 @@
|
|
|
33
33
|
"hexo-generator-category": "^2.0.0",
|
|
34
34
|
"hexo-generator-index": "^4.0.0",
|
|
35
35
|
"hexo-generator-tag": "^2.0.0",
|
|
36
|
-
"hexo-
|
|
36
|
+
"hexo-util": "^4.0.0",
|
|
37
37
|
"hexo-renderer-pug": "^3.0.0",
|
|
38
|
+
"highlight.js": "^11.11.1",
|
|
38
39
|
"htmlparser2": "^10.0.0",
|
|
39
40
|
"javascript-stringify": "^2.1.0",
|
|
40
41
|
"jquery": "~3.7.1",
|
|
41
42
|
"js-beautify": "^1.14.4",
|
|
42
43
|
"lodash": "^4.17.21",
|
|
44
|
+
"markdown-it-abbr": "^1.0.4",
|
|
45
|
+
"markdown-it-deflist": "^2.1.0",
|
|
46
|
+
"markdown-it-emoji": "^2.0.2",
|
|
43
47
|
"markdown-it": "^14.1.1",
|
|
44
48
|
"markdown-it-expand-tabs": "^1.0.13",
|
|
45
|
-
"markdown-it-
|
|
49
|
+
"markdown-it-footnote": "^3.0.3",
|
|
50
|
+
"markdown-it-ins": "^3.0.1",
|
|
51
|
+
"markdown-it-mark": "^3.0.1",
|
|
52
|
+
"markdown-it-sub": "^1.0.0",
|
|
53
|
+
"markdown-it-sup": "^1.0.0",
|
|
46
54
|
"medium-zoom": "^1.0.6",
|
|
47
55
|
"minimatch": "^10.0.1",
|
|
48
56
|
"moment": "^2.29.4",
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const hljs = require( 'highlight.js' );
|
|
9
|
+
const MarkdownIt = require( 'markdown-it' );
|
|
10
|
+
const markdownItTocAndAnchor = require( '../utils/markdown-it-toc-and-anchor.cjs' );
|
|
11
|
+
|
|
12
|
+
const LEGACY_KATEX_PLUGIN_NAME = '@iktakahiro/markdown-it-katex';
|
|
13
|
+
const KATEX_PLUGIN_NAME = '@vscode/markdown-it-katex';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_PLUGINS = [
|
|
16
|
+
'markdown-it-emoji',
|
|
17
|
+
'markdown-it-sub',
|
|
18
|
+
'markdown-it-sup',
|
|
19
|
+
'markdown-it-deflist',
|
|
20
|
+
'markdown-it-abbr',
|
|
21
|
+
'markdown-it-footnote',
|
|
22
|
+
'markdown-it-ins',
|
|
23
|
+
'markdown-it-mark',
|
|
24
|
+
LEGACY_KATEX_PLUGIN_NAME,
|
|
25
|
+
'markdown-it-toc-and-anchor'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function checkValue( config, result, key, trueValue, falseValue ) {
|
|
29
|
+
result[ key ] =
|
|
30
|
+
config[ key ] === true || config[ key ] === undefined || config[ key ] === null ?
|
|
31
|
+
trueValue :
|
|
32
|
+
falseValue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function escapeHtml( value ) {
|
|
36
|
+
return value.replace( /[&<>"']/g, match => {
|
|
37
|
+
return {
|
|
38
|
+
'&': '&',
|
|
39
|
+
'<': '<',
|
|
40
|
+
'>': '>',
|
|
41
|
+
'"': '"',
|
|
42
|
+
'\'': '''
|
|
43
|
+
}[ match ];
|
|
44
|
+
} );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function checkConfig( config ) {
|
|
48
|
+
const result = {};
|
|
49
|
+
const hljsClass = config.hljs_class;
|
|
50
|
+
const preClass = config.pre_class || 'highlight';
|
|
51
|
+
|
|
52
|
+
checkValue( config, result, 'highlight', ( str, lang ) => {
|
|
53
|
+
if ( lang && hljs.getLanguage( lang ) ) {
|
|
54
|
+
try {
|
|
55
|
+
const highlighted = hljs.highlight( str, {
|
|
56
|
+
language: lang,
|
|
57
|
+
ignoreIllegals: true
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
return `<pre class="${ preClass }"><code class="${ lang } ${ hljsClass || '' }">${ highlighted.value }</code></pre>`;
|
|
61
|
+
} catch {
|
|
62
|
+
return `<pre class="${ preClass }"><code class="${ lang }">${ escapeHtml( str ) }</code></pre>`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `<pre class="${ preClass }"><code class="${ lang || '' }">${ escapeHtml( str ) }</code></pre>`;
|
|
67
|
+
}, ( str, lang ) => {
|
|
68
|
+
return `<pre class="${ preClass }"><code class="${ lang || '' }">${ escapeHtml( str ) }</code></pre>`;
|
|
69
|
+
} );
|
|
70
|
+
checkValue( config, result, 'html', true, false );
|
|
71
|
+
checkValue( config, result, 'xhtmlOut', true, false );
|
|
72
|
+
checkValue( config, result, 'breaks', true, false );
|
|
73
|
+
checkValue( config, result, 'linkify', true, false );
|
|
74
|
+
checkValue( config, result, 'typographer', true, false );
|
|
75
|
+
|
|
76
|
+
result.langPrefix = config.langPrefix || '';
|
|
77
|
+
result.quotes = config.quotes || '\u201c\u201d\u2018\u2019';
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function checkPlugins( plugins ) {
|
|
83
|
+
const defaultPlugins = Object.fromEntries( DEFAULT_PLUGINS.map( pluginName => {
|
|
84
|
+
return [ pluginName, {
|
|
85
|
+
name: pluginName,
|
|
86
|
+
enable: true
|
|
87
|
+
} ];
|
|
88
|
+
} ) );
|
|
89
|
+
const customPlugins = [];
|
|
90
|
+
|
|
91
|
+
for ( const pluginConfig of plugins ) {
|
|
92
|
+
if ( !( pluginConfig instanceof Object ) || !( pluginConfig.plugin instanceof Object ) ) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pluginName = pluginConfig.plugin.name;
|
|
97
|
+
|
|
98
|
+
if ( !pluginName ) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if ( pluginConfig.plugin.enable == null || pluginConfig.plugin.enable !== true ) {
|
|
103
|
+
pluginConfig.plugin.enable = false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if ( defaultPlugins[ pluginName ] ) {
|
|
107
|
+
defaultPlugins[ pluginName ] = pluginConfig.plugin;
|
|
108
|
+
} else {
|
|
109
|
+
customPlugins.push( pluginConfig.plugin );
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for ( let i = DEFAULT_PLUGINS.length - 1; i >= 0; i-- ) {
|
|
114
|
+
customPlugins.unshift( defaultPlugins[ DEFAULT_PLUGINS[ i ] ] );
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return customPlugins;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function loadPlugin( pluginName ) {
|
|
121
|
+
if ( pluginName === 'markdown-it-toc-and-anchor' ) {
|
|
122
|
+
return markdownItTocAndAnchor;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if ( pluginName === LEGACY_KATEX_PLUGIN_NAME ) {
|
|
126
|
+
return require( KATEX_PLUGIN_NAME );
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return require( pluginName );
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getPluginOptions( plugin ) {
|
|
133
|
+
if ( plugin.name !== 'markdown-it-toc-and-anchor' ) {
|
|
134
|
+
return plugin.options;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const options = plugin.options || {};
|
|
138
|
+
|
|
139
|
+
if ( !options.anchorLinkSymbol ) {
|
|
140
|
+
options.anchorLinkSymbol = '';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if ( !options.tocFirstLevel ) {
|
|
144
|
+
options.tocFirstLevel = 2;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return options;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function markdownItPlusRenderer( data ) {
|
|
151
|
+
let config = this.config.markdown_it_plus;
|
|
152
|
+
|
|
153
|
+
if ( !( config instanceof Object ) ) {
|
|
154
|
+
config = {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const parserConfig = checkConfig( config );
|
|
158
|
+
let markdownIt = new MarkdownIt( parserConfig );
|
|
159
|
+
|
|
160
|
+
if ( config.plugins == null ) {
|
|
161
|
+
config.plugins = [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const plugins = checkPlugins( config.plugins );
|
|
165
|
+
|
|
166
|
+
markdownIt = plugins.reduce( ( markdownInstance, plugin ) => {
|
|
167
|
+
if ( !plugin.enable ) {
|
|
168
|
+
return markdownInstance;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let loadedPlugin = loadPlugin( plugin.name );
|
|
172
|
+
|
|
173
|
+
if ( typeof loadedPlugin !== 'function' && typeof loadedPlugin.default === 'function' ) {
|
|
174
|
+
loadedPlugin = loadedPlugin.default;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const options = getPluginOptions( plugin );
|
|
178
|
+
|
|
179
|
+
if ( options ) {
|
|
180
|
+
return markdownInstance.use( loadedPlugin, options );
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return markdownInstance.use( loadedPlugin );
|
|
184
|
+
}, markdownIt );
|
|
185
|
+
|
|
186
|
+
return markdownIt.render( data.text );
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const markdownExtensions = [
|
|
190
|
+
'md',
|
|
191
|
+
'markdown',
|
|
192
|
+
'mkd',
|
|
193
|
+
'mkdn',
|
|
194
|
+
'mdwn',
|
|
195
|
+
'mdtxt',
|
|
196
|
+
'mdtext'
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for ( const extension of markdownExtensions ) {
|
|
200
|
+
hexo.extend.renderer.register( extension, 'html', markdownItPlusRenderer, true );
|
|
201
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const cloneDeep = require( 'lodash/cloneDeep' );
|
|
9
|
+
const { slugize } = require( 'hexo-util' );
|
|
10
|
+
|
|
11
|
+
class Token {
|
|
12
|
+
constructor( type, tag, nesting ) {
|
|
13
|
+
this.type = type;
|
|
14
|
+
this.tag = tag;
|
|
15
|
+
this.nesting = nesting;
|
|
16
|
+
this.attrs = null;
|
|
17
|
+
this.map = null;
|
|
18
|
+
this.level = 0;
|
|
19
|
+
this.children = null;
|
|
20
|
+
this.content = '';
|
|
21
|
+
this.markup = '';
|
|
22
|
+
this.info = '';
|
|
23
|
+
this.meta = null;
|
|
24
|
+
this.block = false;
|
|
25
|
+
this.hidden = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TOC = '@[toc]';
|
|
30
|
+
const TOC_RE = /^@\[toc\]/i;
|
|
31
|
+
|
|
32
|
+
const repeat = ( string, num ) => new Array( num + 1 ).join( string );
|
|
33
|
+
|
|
34
|
+
const defaultSlugify = title => slugize( title, { transform: 1 } );
|
|
35
|
+
|
|
36
|
+
const makeSafe = ( string, localHeadingIds, slugifyFn ) => {
|
|
37
|
+
const key = slugifyFn( string );
|
|
38
|
+
|
|
39
|
+
if ( !localHeadingIds[ key ] ) {
|
|
40
|
+
localHeadingIds[ key ] = 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
localHeadingIds[ key ]++;
|
|
44
|
+
|
|
45
|
+
return key + ( localHeadingIds[ key ] > 1 ? `-${ localHeadingIds[ key ] }` : '' );
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const space = () => {
|
|
49
|
+
return {
|
|
50
|
+
...new Token( 'text', '', 0 ),
|
|
51
|
+
content: ' '
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const renderAnchorLinkSymbol = options => {
|
|
56
|
+
if ( options.anchorLinkSymbolClassName ) {
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
...new Token( 'span_open', 'span', 1 ),
|
|
60
|
+
attrs: [ [ 'class', options.anchorLinkSymbolClassName ] ]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
...new Token( 'text', '', 0 ),
|
|
64
|
+
content: options.anchorLinkSymbol
|
|
65
|
+
},
|
|
66
|
+
new Token( 'span_close', 'span', -1 )
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
...new Token( 'text', '', 0 ),
|
|
73
|
+
content: options.anchorLinkSymbol
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const renderAnchorLink = ( anchor, options, tokens, idx ) => {
|
|
79
|
+
const attrs = [];
|
|
80
|
+
|
|
81
|
+
if ( options.anchorClassName != null ) {
|
|
82
|
+
attrs.push( [ 'class', options.anchorClassName ] );
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
attrs.push( [ 'href', `#${ anchor }` ] );
|
|
86
|
+
|
|
87
|
+
const openLinkToken = {
|
|
88
|
+
...new Token( 'link_open', 'a', 1 ),
|
|
89
|
+
attrs
|
|
90
|
+
};
|
|
91
|
+
const closeLinkToken = new Token( 'link_close', 'a', -1 );
|
|
92
|
+
|
|
93
|
+
if ( options.wrapHeadingTextInAnchor ) {
|
|
94
|
+
tokens[ idx + 1 ].children.unshift( openLinkToken );
|
|
95
|
+
tokens[ idx + 1 ].children.push( closeLinkToken );
|
|
96
|
+
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const linkTokens = [
|
|
101
|
+
openLinkToken,
|
|
102
|
+
...renderAnchorLinkSymbol( options ),
|
|
103
|
+
closeLinkToken
|
|
104
|
+
];
|
|
105
|
+
const actionOnArray = {
|
|
106
|
+
false: 'push',
|
|
107
|
+
true: 'unshift'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if ( options.anchorLinkSpace ) {
|
|
111
|
+
linkTokens[ actionOnArray[ !options.anchorLinkBefore ] ]( space() );
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
tokens[ idx + 1 ].children[ actionOnArray[ options.anchorLinkBefore ] ]( ...linkTokens );
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const treeToMarkdownBulletList = ( tree, indent = 0 ) => {
|
|
118
|
+
return tree
|
|
119
|
+
.map( item => {
|
|
120
|
+
const indentation = ' ';
|
|
121
|
+
let node = `${ repeat( indentation, indent ) }*`;
|
|
122
|
+
|
|
123
|
+
if ( item.heading.content ) {
|
|
124
|
+
const contentWithoutAnchor = item.heading.content.replace( /\[([^\]]*)\]\([^)]*\)/g, '$1' );
|
|
125
|
+
|
|
126
|
+
node += ` [${ contentWithoutAnchor }](#${ item.heading.anchor })\n`;
|
|
127
|
+
} else {
|
|
128
|
+
node += '\n';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if ( item.nodes.length ) {
|
|
132
|
+
node += treeToMarkdownBulletList( item.nodes, indent + 1 );
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return node;
|
|
136
|
+
} )
|
|
137
|
+
.join( '' );
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const generateTocMarkdownFromArray = ( headings, options ) => {
|
|
141
|
+
const tree = {
|
|
142
|
+
nodes: []
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
for ( const heading of headings ) {
|
|
146
|
+
if ( heading.level < options.tocFirstLevel || heading.level > options.tocLastLevel ) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let i = 1;
|
|
151
|
+
let lastItem = tree;
|
|
152
|
+
|
|
153
|
+
for ( ; i < heading.level - options.tocFirstLevel + 1; i++ ) {
|
|
154
|
+
if ( lastItem.nodes.length === 0 ) {
|
|
155
|
+
lastItem.nodes.push( {
|
|
156
|
+
heading: {},
|
|
157
|
+
nodes: []
|
|
158
|
+
} );
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
lastItem = lastItem.nodes[ lastItem.nodes.length - 1 ];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lastItem.nodes.push( {
|
|
165
|
+
heading,
|
|
166
|
+
nodes: []
|
|
167
|
+
} );
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return treeToMarkdownBulletList( tree.nodes );
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
module.exports = function markdownItTocAndAnchor( md, options ) {
|
|
174
|
+
options = {
|
|
175
|
+
toc: true,
|
|
176
|
+
tocClassName: 'markdownIt-TOC',
|
|
177
|
+
tocFirstLevel: 1,
|
|
178
|
+
tocLastLevel: 6,
|
|
179
|
+
tocCallback: null,
|
|
180
|
+
anchorLink: true,
|
|
181
|
+
anchorLinkSymbol: '#',
|
|
182
|
+
anchorLinkBefore: true,
|
|
183
|
+
anchorClassName: 'markdownIt-Anchor',
|
|
184
|
+
resetIds: true,
|
|
185
|
+
anchorLinkSpace: true,
|
|
186
|
+
anchorLinkSymbolClassName: null,
|
|
187
|
+
wrapHeadingTextInAnchor: false,
|
|
188
|
+
...options
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const markdownItSecondInstance = cloneDeep( md );
|
|
192
|
+
let headingIds = {};
|
|
193
|
+
let tocHtml = '';
|
|
194
|
+
|
|
195
|
+
md.core.ruler.push( 'init_toc', state => {
|
|
196
|
+
const tokens = state.tokens;
|
|
197
|
+
|
|
198
|
+
if ( options.resetIds ) {
|
|
199
|
+
headingIds = {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const tocArray = [];
|
|
203
|
+
let tocMarkdown = '';
|
|
204
|
+
let tocTokens = [];
|
|
205
|
+
|
|
206
|
+
const slugifyFn = typeof options.slugify === 'function' ? options.slugify : defaultSlugify;
|
|
207
|
+
|
|
208
|
+
for ( let i = 0; i < tokens.length; i++ ) {
|
|
209
|
+
if ( tokens[ i ].type !== 'heading_close' ) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const heading = tokens[ i - 1 ];
|
|
214
|
+
const headingClose = tokens[ i ];
|
|
215
|
+
|
|
216
|
+
if ( heading.type !== 'inline' ) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let content;
|
|
221
|
+
|
|
222
|
+
if ( heading.children?.length > 0 && heading.children[ 0 ].type === 'link_open' ) {
|
|
223
|
+
content = heading.children[ 1 ].content;
|
|
224
|
+
heading._tocAnchor = makeSafe( content, headingIds, slugifyFn );
|
|
225
|
+
} else {
|
|
226
|
+
content = heading.content;
|
|
227
|
+
heading._tocAnchor = makeSafe(
|
|
228
|
+
heading.children.reduce( ( acc, token ) => acc + token.content, '' ),
|
|
229
|
+
headingIds,
|
|
230
|
+
slugifyFn
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if ( options.anchorLinkPrefix ) {
|
|
235
|
+
heading._tocAnchor = options.anchorLinkPrefix + heading._tocAnchor;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
tocArray.push( {
|
|
239
|
+
content,
|
|
240
|
+
anchor: heading._tocAnchor,
|
|
241
|
+
level: Number( headingClose.tag.slice( 1, 2 ) )
|
|
242
|
+
} );
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
tocMarkdown = generateTocMarkdownFromArray( tocArray, options );
|
|
246
|
+
tocTokens = markdownItSecondInstance.parse( tocMarkdown, {} );
|
|
247
|
+
|
|
248
|
+
if ( typeof tocTokens[ 0 ] === 'object' && tocTokens[ 0 ].type === 'bullet_list_open' ) {
|
|
249
|
+
const attrs = ( tocTokens[ 0 ].attrs = tocTokens[ 0 ].attrs || [] );
|
|
250
|
+
|
|
251
|
+
if ( options.tocClassName != null ) {
|
|
252
|
+
attrs.push( [ 'class', options.tocClassName ] );
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
tocHtml = markdownItSecondInstance.renderer.render( tocTokens, markdownItSecondInstance.options );
|
|
257
|
+
|
|
258
|
+
if ( typeof state.env.tocCallback === 'function' ) {
|
|
259
|
+
state.env.tocCallback.call( undefined, tocMarkdown, tocArray, tocHtml );
|
|
260
|
+
} else if ( typeof options.tocCallback === 'function' ) {
|
|
261
|
+
options.tocCallback.call( undefined, tocMarkdown, tocArray, tocHtml );
|
|
262
|
+
} else if ( typeof md.options.tocCallback === 'function' ) {
|
|
263
|
+
md.options.tocCallback.call( undefined, tocMarkdown, tocArray, tocHtml );
|
|
264
|
+
}
|
|
265
|
+
} );
|
|
266
|
+
|
|
267
|
+
md.inline.ruler.after( 'emphasis', 'toc', ( state, silent ) => {
|
|
268
|
+
if (
|
|
269
|
+
state.src.charCodeAt( state.pos ) !== 0x40 ||
|
|
270
|
+
state.src.charCodeAt( state.pos + 1 ) !== 0x5b ||
|
|
271
|
+
silent
|
|
272
|
+
) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const currentSource = state.src.slice( state.pos );
|
|
277
|
+
const match = TOC_RE.exec( currentSource );
|
|
278
|
+
|
|
279
|
+
if ( !match ) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let token = state.push( 'toc_open', 'toc', 1 );
|
|
284
|
+
|
|
285
|
+
token.markup = TOC;
|
|
286
|
+
token = state.push( 'toc_body', '', 0 );
|
|
287
|
+
token = state.push( 'toc_close', 'toc', -1 );
|
|
288
|
+
|
|
289
|
+
state.pos += match[ 0 ].length;
|
|
290
|
+
|
|
291
|
+
return true;
|
|
292
|
+
} );
|
|
293
|
+
|
|
294
|
+
const originalHeadingOpen = md.renderer.rules.heading_open || ( ( ...args ) => {
|
|
295
|
+
const [ tokens, idx, rendererOptions, , self ] = args;
|
|
296
|
+
|
|
297
|
+
return self.renderToken( tokens, idx, rendererOptions );
|
|
298
|
+
} );
|
|
299
|
+
|
|
300
|
+
md.renderer.rules.heading_open = function( ...args ) {
|
|
301
|
+
const [ tokens, idx ] = args;
|
|
302
|
+
|
|
303
|
+
const attrs = ( tokens[ idx ].attrs = tokens[ idx ].attrs || [] );
|
|
304
|
+
const anchor = tokens[ idx + 1 ]._tocAnchor;
|
|
305
|
+
|
|
306
|
+
attrs.push( [ 'id', anchor ] );
|
|
307
|
+
|
|
308
|
+
if ( options.anchorLink ) {
|
|
309
|
+
renderAnchorLink( anchor, options, ...args );
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return originalHeadingOpen.apply( this, args );
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
md.renderer.rules.toc_open = () => '';
|
|
316
|
+
md.renderer.rules.toc_close = () => '';
|
|
317
|
+
md.renderer.rules.toc_body = () => '';
|
|
318
|
+
|
|
319
|
+
if ( options.toc ) {
|
|
320
|
+
md.renderer.rules.toc_body = () => tocHtml;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
@@ -188,8 +188,7 @@ export class DocDataFactory {
|
|
|
188
188
|
|
|
189
189
|
/* eslint-disable @stylistic/max-len */
|
|
190
190
|
// For all "typedef" doclets - copy property name as its id. Thanks to that we will be able to make a directly link to specified property. See #796.
|
|
191
|
-
// "member-" as a static string must
|
|
192
|
-
// See: https://github.com/ckeditor/ckeditor5-dev/blob/aa905a702d337db001445efc1036ab2b33069d44/packages/jsdoc-plugins/lib/relation-fixer/addtypedefproperties.js#L95
|
|
191
|
+
// "member-" as a static string must stay in sync with IDs generated for typedef properties in the data conversion pipeline.
|
|
193
192
|
/* eslint-enable @stylistic/max-len */
|
|
194
193
|
if ( doclet.kind == 'typedef' ) {
|
|
195
194
|
for ( const property of properties ) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getMissingDocletsData } from './get-missing-doclets-data.js';
|
|
7
|
+
import { DocletCollection } from './doclet-collection.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Adds missing doclets for members coming from implemented interfaces, extended classes and mixins.
|
|
11
|
+
* Supports inheriting static members, which are not supported by JSDoc.
|
|
12
|
+
*
|
|
13
|
+
* @param {Doclet[]} doclets
|
|
14
|
+
* @returns {Doclet[]}
|
|
15
|
+
*/
|
|
16
|
+
export function addMissingDoclets( doclets ) {
|
|
17
|
+
const docletCollection = new DocletCollection();
|
|
18
|
+
|
|
19
|
+
for ( const doclet of doclets ) {
|
|
20
|
+
docletCollection.add( `memberof:${ doclet.memberof }`, doclet );
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const extensibleDoclets = doclets.filter( doclet => {
|
|
24
|
+
return doclet.kind === 'class' || doclet.kind === 'interface' || doclet.kind === 'mixin';
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
const newDocletsToAdd = [];
|
|
28
|
+
const docletsToIgnore = [];
|
|
29
|
+
const options = [
|
|
30
|
+
{
|
|
31
|
+
relation: 'augmentsNested'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
relation: 'mixesNested',
|
|
35
|
+
onlyImplicitlyInherited: true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
relation: 'implementsNested'
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const docletMap = createDocletMap( docletCollection );
|
|
43
|
+
|
|
44
|
+
for ( const extensibleDoclet of extensibleDoclets ) {
|
|
45
|
+
for ( const option of options ) {
|
|
46
|
+
const missingDocletsData = getMissingDocletsData(
|
|
47
|
+
docletMap,
|
|
48
|
+
docletCollection,
|
|
49
|
+
extensibleDoclet,
|
|
50
|
+
option
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
newDocletsToAdd.push( ...missingDocletsData.newDoclets );
|
|
54
|
+
docletsToIgnore.push( ...missingDocletsData.docletsWhichShouldBeIgnored );
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for ( const docletToIgnore of docletsToIgnore ) {
|
|
59
|
+
docletToIgnore.ignore = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const filteredDoclets = doclets.filter( doclet => !doclet.ignore );
|
|
63
|
+
const docletToAddMap = new Map(
|
|
64
|
+
newDocletsToAdd
|
|
65
|
+
.filter( doclet => !doclet.ignore )
|
|
66
|
+
.map( doclet => [ doclet.longname, doclet ] )
|
|
67
|
+
);
|
|
68
|
+
const existingDoclets = new Map( filteredDoclets.map( doclet => [ doclet.longname, doclet ] ) );
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
...filteredDoclets.filter( doclet => {
|
|
72
|
+
const willDocletBeAdded = docletToAddMap.has( doclet.longname );
|
|
73
|
+
|
|
74
|
+
if ( willDocletBeAdded && doclet.inheritdoc === undefined ) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
} ),
|
|
80
|
+
...Array.from( docletToAddMap.values() ).filter( doclet => {
|
|
81
|
+
const existingDoclet = existingDoclets.get( doclet.longname );
|
|
82
|
+
|
|
83
|
+
if ( !existingDoclet ) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ( existingDoclet.inheritdoc === undefined ) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
} )
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates a <longname, doclet> map.
|
|
98
|
+
*
|
|
99
|
+
* @param {DocletCollection} doclets
|
|
100
|
+
* @returns {Object.<String, Doclet>}
|
|
101
|
+
*/
|
|
102
|
+
function createDocletMap( doclets ) {
|
|
103
|
+
const docletMap = {};
|
|
104
|
+
|
|
105
|
+
for ( const doclet of doclets.getAll() ) {
|
|
106
|
+
if ( !docletMap[ doclet.longname ] ) {
|
|
107
|
+
docletMap[ doclet.longname ] = doclet;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return docletMap;
|
|
112
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DocletCollection } from './doclet-collection.js';
|
|
7
|
+
|
|
8
|
+
const RELATIONS = {
|
|
9
|
+
implements: 'implementsNested',
|
|
10
|
+
mixes: 'mixesNested',
|
|
11
|
+
augments: 'augmentsNested'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks ascendants of every doclet and adds them to relation arrays.
|
|
16
|
+
* Handles nested inheritance, mixins and implementation of interfaces.
|
|
17
|
+
* Also adds descendants to doclets.
|
|
18
|
+
*
|
|
19
|
+
* @param {Doclet[]} doclets
|
|
20
|
+
* @returns {Doclet[]}
|
|
21
|
+
*/
|
|
22
|
+
export function buildRelations( doclets ) {
|
|
23
|
+
const processedDoclets = structuredClone( doclets );
|
|
24
|
+
const docletCollection = new DocletCollection();
|
|
25
|
+
|
|
26
|
+
for ( const doclet of processedDoclets ) {
|
|
27
|
+
docletCollection.add( doclet.longname, doclet );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const subjectDoclets = processedDoclets.filter( item => {
|
|
31
|
+
return (
|
|
32
|
+
item.kind === 'class' ||
|
|
33
|
+
item.kind === 'interface' ||
|
|
34
|
+
item.kind === 'mixin' ||
|
|
35
|
+
item.kind === 'typedef'
|
|
36
|
+
);
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
for ( const doclet of subjectDoclets ) {
|
|
40
|
+
const related = getAncestors( docletCollection, doclet, {
|
|
41
|
+
relations: [ 'augments', 'implements', 'mixes' ]
|
|
42
|
+
} );
|
|
43
|
+
|
|
44
|
+
for ( const relation of Object.keys( related ) ) {
|
|
45
|
+
related[ relation ] = [ ...new Set( related[ relation ] ) ];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Object.assign( doclet, related );
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for ( const doclet of subjectDoclets ) {
|
|
52
|
+
doclet.descendants = [ ...new Set( getDescendants( subjectDoclets, doclet ) ) ];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return processedDoclets;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets longnames of the current doclet ancestors.
|
|
60
|
+
*
|
|
61
|
+
* @param {DocletCollection} docletCollection
|
|
62
|
+
* @param {Doclet} currentDoclet
|
|
63
|
+
* @param {Object} options
|
|
64
|
+
* @param {Array.<'augments'|'implements'|'mixes'>} options.relations
|
|
65
|
+
* @returns {Object}
|
|
66
|
+
*/
|
|
67
|
+
function getAncestors( docletCollection, currentDoclet, options ) {
|
|
68
|
+
const { relations } = options;
|
|
69
|
+
const resultRelations = {};
|
|
70
|
+
|
|
71
|
+
for ( const baseRelation of relations ) {
|
|
72
|
+
resultRelations[ RELATIONS[ baseRelation ] ] = [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for ( const baseRelation of relations ) {
|
|
76
|
+
const relation = RELATIONS[ baseRelation ];
|
|
77
|
+
|
|
78
|
+
if ( isEmpty( currentDoclet[ baseRelation ] ) ) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resultRelations[ relation ].push( ...currentDoclet[ baseRelation ] );
|
|
83
|
+
|
|
84
|
+
for ( const longname of currentDoclet[ baseRelation ] ) {
|
|
85
|
+
const ancestors = docletCollection.get( longname );
|
|
86
|
+
|
|
87
|
+
for ( const ancestor of ancestors ) {
|
|
88
|
+
const ancestorsResultRelations = getAncestors( docletCollection, ancestor, {
|
|
89
|
+
relations
|
|
90
|
+
} );
|
|
91
|
+
|
|
92
|
+
for ( const key of Object.keys( resultRelations ) ) {
|
|
93
|
+
if ( key === 'augmentsNested' && ancestor.kind !== currentDoclet.kind ) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
resultRelations[ key ].push( ...ancestorsResultRelations[ key ] );
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return resultRelations;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {Array|undefined} arr
|
|
108
|
+
* @returns {Boolean}
|
|
109
|
+
*/
|
|
110
|
+
function isEmpty( arr ) {
|
|
111
|
+
return !arr || arr.length === 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gets longnames of descendants - entities that extend, implement or mix a doclet.
|
|
116
|
+
*
|
|
117
|
+
* @param {Doclet[]} searchedDoclets
|
|
118
|
+
* @param {Doclet} currentDoclet
|
|
119
|
+
* @returns {String[]}
|
|
120
|
+
*/
|
|
121
|
+
function getDescendants( searchedDoclets, currentDoclet ) {
|
|
122
|
+
const descendants = new Set();
|
|
123
|
+
|
|
124
|
+
for ( const doclet of searchedDoclets ) {
|
|
125
|
+
for ( const baseRelation in RELATIONS ) {
|
|
126
|
+
const relation = RELATIONS[ baseRelation ];
|
|
127
|
+
|
|
128
|
+
if ( !isEmpty( doclet[ relation ] ) && doclet[ relation ].includes( currentDoclet.longname ) ) {
|
|
129
|
+
descendants.add( doclet.longname );
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return Array.from( descendants );
|
|
136
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Collection of doclets as <String, Doclet[]> pairs. Also stores all doclets and their longnames as arrays.
|
|
8
|
+
*/
|
|
9
|
+
export class DocletCollection {
|
|
10
|
+
constructor() {
|
|
11
|
+
this._data = {};
|
|
12
|
+
this._allData = [];
|
|
13
|
+
this._allLongnames = [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adds doclet to collection.
|
|
18
|
+
*
|
|
19
|
+
* @param {String} category
|
|
20
|
+
* @param {Doclet} doclet
|
|
21
|
+
*/
|
|
22
|
+
add( category, doclet ) {
|
|
23
|
+
if ( !this._data[ category ] ) {
|
|
24
|
+
this._data[ category ] = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this._data[ category ].push( doclet );
|
|
28
|
+
this._allData.push( doclet );
|
|
29
|
+
|
|
30
|
+
if ( doclet.longname ) {
|
|
31
|
+
this._allLongnames.push( doclet.longname );
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns doclets filtered by category.
|
|
37
|
+
*
|
|
38
|
+
* @param {String} category
|
|
39
|
+
* @returns {Doclet[]}
|
|
40
|
+
*/
|
|
41
|
+
get( category ) {
|
|
42
|
+
return this._data[ category ] || [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns all doclets.
|
|
47
|
+
*
|
|
48
|
+
* @returns {Doclet[]}
|
|
49
|
+
*/
|
|
50
|
+
getAll() {
|
|
51
|
+
return this._allData;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns all longnames.
|
|
56
|
+
*
|
|
57
|
+
* @returns {String[]}
|
|
58
|
+
*/
|
|
59
|
+
getAllLongnames() {
|
|
60
|
+
return this._allLongnames;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2017-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Gets missing doclets of members coming from implemented interfaces, extended classes and mixins.
|
|
8
|
+
* Returns also doclets which should be ignored.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object.<String, Doclet>} docletMap
|
|
11
|
+
* @param {DocletCollection} docletCollection
|
|
12
|
+
* @param {Doclet} interfaceClassOrMixinDoclet
|
|
13
|
+
* @param {Options} options
|
|
14
|
+
* @returns {{newDoclets: Doclet[], docletsWhichShouldBeIgnored: Doclet[]}}
|
|
15
|
+
*/
|
|
16
|
+
export function getMissingDocletsData( docletMap, docletCollection, interfaceClassOrMixinDoclet, options ) {
|
|
17
|
+
const newDoclets = [];
|
|
18
|
+
const docletsWhichShouldBeIgnored = [];
|
|
19
|
+
|
|
20
|
+
const docletsToAdd = getDocletsToAdd( docletCollection, interfaceClassOrMixinDoclet, options );
|
|
21
|
+
|
|
22
|
+
for ( const docletToAdd of docletsToAdd ) {
|
|
23
|
+
const clonedDoclet = structuredClone( docletToAdd );
|
|
24
|
+
|
|
25
|
+
clonedDoclet.longname = getLongnameForNewDoclet( docletToAdd, interfaceClassOrMixinDoclet );
|
|
26
|
+
clonedDoclet.memberof = interfaceClassOrMixinDoclet.longname;
|
|
27
|
+
|
|
28
|
+
const relationProperty = getRelationProperty( docletMap, interfaceClassOrMixinDoclet, docletToAdd, options.relation );
|
|
29
|
+
|
|
30
|
+
if ( relationProperty ) {
|
|
31
|
+
clonedDoclet[ relationProperty ] = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const docletsOfSameMember = docletCollection.get( `memberof:${ clonedDoclet.memberof }` ).filter( doclet => {
|
|
35
|
+
if ( doclet.kind !== clonedDoclet.kind ) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const docletName = doclet.name.replace( /^[#.]/, '' );
|
|
40
|
+
const clonedDocletName = clonedDoclet.name.replace( /^[#.]/, '' );
|
|
41
|
+
|
|
42
|
+
return docletName === clonedDocletName;
|
|
43
|
+
} );
|
|
44
|
+
|
|
45
|
+
if ( docletsOfSameMember.length === 0 ) {
|
|
46
|
+
newDoclets.push( clonedDoclet );
|
|
47
|
+
} else if ( doAllParentsExplicitlyInherit( docletsOfSameMember ) && !options.onlyImplicitlyInherited ) {
|
|
48
|
+
docletsWhichShouldBeIgnored.push( ...docletsOfSameMember );
|
|
49
|
+
newDoclets.push( clonedDoclet );
|
|
50
|
+
} else if ( docletsOfSameMember.length >= 2 ) {
|
|
51
|
+
const correctDoclet = structuredClone( docletsOfSameMember[ 0 ] );
|
|
52
|
+
|
|
53
|
+
if ( relationProperty ) {
|
|
54
|
+
correctDoclet[ relationProperty ] = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
docletsWhichShouldBeIgnored.push( ...docletsOfSameMember );
|
|
58
|
+
newDoclets.push( correctDoclet );
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
newDoclets,
|
|
64
|
+
docletsWhichShouldBeIgnored
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets doclets from related entities and matching criteria from options.
|
|
70
|
+
*
|
|
71
|
+
* @param {DocletCollection} docletCollection
|
|
72
|
+
* @param {Doclet} childDoclet
|
|
73
|
+
* @param {Object} options
|
|
74
|
+
* @returns {Doclet[]}
|
|
75
|
+
*/
|
|
76
|
+
function getDocletsToAdd( docletCollection, childDoclet, options ) {
|
|
77
|
+
const ancestors = childDoclet[ options.relation ] || [];
|
|
78
|
+
const docletsToAdd = [];
|
|
79
|
+
|
|
80
|
+
for ( const ancestor of ancestors ) {
|
|
81
|
+
docletsToAdd.push(
|
|
82
|
+
...docletCollection
|
|
83
|
+
.get( `memberof:${ ancestor }` )
|
|
84
|
+
.filter( doclet => shouldDocletBeAdded( doclet, options ) )
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return docletsToAdd;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {Doclet} doclet
|
|
93
|
+
* @param {Object} options
|
|
94
|
+
* @returns {Boolean}
|
|
95
|
+
*/
|
|
96
|
+
function shouldDocletBeAdded( doclet, options ) {
|
|
97
|
+
if ( doclet.ignore || doclet.undocumented || typeof doclet.inheritdoc === 'string' ) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for ( const key of Object.keys( options.filter || {} ) ) {
|
|
102
|
+
if ( doclet[ key ] !== options.filter[ key ] ) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {Doclet} parentDoclet
|
|
112
|
+
* @param {Doclet} childDoclet
|
|
113
|
+
* @returns {String}
|
|
114
|
+
*/
|
|
115
|
+
function getLongnameForNewDoclet( parentDoclet, childDoclet ) {
|
|
116
|
+
const dotIndex = parentDoclet.longname.lastIndexOf( '.' );
|
|
117
|
+
const hashIndex = parentDoclet.longname.lastIndexOf( '#' );
|
|
118
|
+
const name = parentDoclet.longname.slice( Math.max( dotIndex, hashIndex ) );
|
|
119
|
+
|
|
120
|
+
return childDoclet.longname + name;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Gets property which should be added to the new doclet.
|
|
125
|
+
*
|
|
126
|
+
* @param {Object.<String, Doclet>} docletMap
|
|
127
|
+
* @param {Doclet} childDoclet
|
|
128
|
+
* @param {Doclet} memberDoclet
|
|
129
|
+
* @param {'augmentsNested'|'mixesNested'|'implementsNested'} relation
|
|
130
|
+
* @returns {'inherited'|'mixed'|null}
|
|
131
|
+
*/
|
|
132
|
+
function getRelationProperty( docletMap, childDoclet, memberDoclet, relation ) {
|
|
133
|
+
if ( relation === 'augmentsNested' ) {
|
|
134
|
+
return 'inherited';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if ( relation === 'mixesNested' ) {
|
|
138
|
+
return 'mixed';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const memberDocletParent = docletMap[ memberDoclet.memberof ];
|
|
142
|
+
|
|
143
|
+
let isInherited = false;
|
|
144
|
+
let isMixed = false;
|
|
145
|
+
|
|
146
|
+
for ( const longname of memberDocletParent.descendants || [] ) {
|
|
147
|
+
const doclet = docletMap[ longname ];
|
|
148
|
+
|
|
149
|
+
if ( !doclet || !doclet.descendants || !doclet.descendants.includes( childDoclet.longname ) ) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ( doclet.kind === 'mixin' ) {
|
|
154
|
+
isMixed = true;
|
|
155
|
+
} else if ( doclet.kind === 'class' ) {
|
|
156
|
+
isInherited = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if ( isMixed ) {
|
|
161
|
+
return 'mixed';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ( isInherited ) {
|
|
165
|
+
return 'inherited';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {Doclet[]} doclets
|
|
173
|
+
* @returns {Boolean}
|
|
174
|
+
*/
|
|
175
|
+
function doAllParentsExplicitlyInherit( doclets ) {
|
|
176
|
+
for ( const doclet of doclets ) {
|
|
177
|
+
if ( doclet.inheritdoc === undefined && doclet.overrides === undefined ) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @typedef {Object} Options
|
|
187
|
+
*
|
|
188
|
+
* @property {'augmentsNested'|'mixesNested'|'implementsNested'} relation
|
|
189
|
+
* @property {Partial<Doclet>} [filter]
|
|
190
|
+
* @property {Boolean} [onlyImplicitlyInherited]
|
|
191
|
+
*/
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import buildRelations from '
|
|
7
|
-
import addMissingDoclets from '
|
|
6
|
+
import { buildRelations } from './relation-fixer/build-relations.js';
|
|
7
|
+
import { addMissingDoclets } from './relation-fixer/add-missing-doclets.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Builds relation chains between doclets and adds missing derived doclets.
|