@ckeditor/ckeditor5-dev-docs 32.0.1 → 32.1.0

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/lib/build.js CHANGED
@@ -5,98 +5,60 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const fs = require( 'fs-extra' );
9
- const tmp = require( 'tmp' );
10
- const glob = require( 'fast-glob' );
11
- const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
12
-
13
8
  /**
14
9
  * Builds CKEditor 5 documentation.
15
10
  *
16
- * @param {Object} config
17
- * @param {Array.<String>} config.sourceFiles Glob pattern with source files.
18
- * @param {String} config.readmePath Path to `README.md`.
19
- * @param {Boolean} [config.validateOnly=false] Whether JSDoc should only validate the documentation and finish
20
- * with error code `1`. If not passed, the errors will be printed to the console but the task will finish with `0`.
21
- * @param {Boolean} [config.strict=false] If `true`, errors found during the validation will finish the process
22
- * and exit code will be changed to `1`.
23
- * @param {String} [config.outputPath] A path to the place where extracted doclets will be saved.
24
- * @param {String} [config.extraPlugins] An array of path to extra plugins that will be added to JSDoc.
25
- *
11
+ * @param {JSDocConfig|TypedocConfig} config
26
12
  * @returns {Promise}
27
13
  */
28
14
  module.exports = async function build( config ) {
29
- const sourceFilePatterns = [
30
- config.readmePath,
31
- ...config.sourceFiles
32
- ];
33
-
34
- const extraPlugins = config.extraPlugins || [];
35
- const outputPath = config.outputPath || 'docs/api/output.json';
36
- const validateOnly = config.validateOnly || false;
37
- const strictCheck = config.strict || false;
38
-
39
- // Pass options to plugins via env variables.
40
- // Since plugins are added using `require` calls other forms are currently impossible.
41
- process.env.JSDOC_OUTPUT_PATH = outputPath;
42
-
43
- if ( validateOnly ) {
44
- process.env.JSDOC_VALIDATE_ONLY = 'true';
15
+ const type = config.type || 'jsdoc';
16
+
17
+ if ( type === 'jsdoc' ) {
18
+ return require( './buildjsdoc' )( config );
19
+ } else if ( type === 'typedoc' ) {
20
+ return require( './buildtypedoc' )( config );
21
+ } else {
22
+ throw new Error( `Unknown documentation tool (${ type }).` );
45
23
  }
24
+ };
46
25
 
47
- if ( strictCheck ) {
48
- process.env.JSDOC_STRICT_CHECK = 'true';
49
- }
50
-
51
- const files = await glob( sourceFilePatterns );
52
-
53
- const jsDocConfig = {
54
- plugins: [
55
- require.resolve( 'jsdoc/plugins/markdown' ),
56
- require.resolve( '@ckeditor/jsdoc-plugins/lib/purge-private-api-docs' ),
57
- require.resolve( '@ckeditor/jsdoc-plugins/lib/export-fixer/export-fixer' ),
58
- require.resolve( '@ckeditor/jsdoc-plugins/lib/custom-tags/error' ),
59
- require.resolve( '@ckeditor/jsdoc-plugins/lib/custom-tags/observable' ),
60
- require.resolve( '@ckeditor/jsdoc-plugins/lib/observable-event-provider' ),
61
- require.resolve( '@ckeditor/jsdoc-plugins/lib/longname-fixer/longname-fixer' ),
62
- require.resolve( '@ckeditor/jsdoc-plugins/lib/fix-code-snippets' ),
63
- require.resolve( '@ckeditor/jsdoc-plugins/lib/relation-fixer' ),
64
- require.resolve( '@ckeditor/jsdoc-plugins/lib/event-extender/event-extender' ),
65
- require.resolve( '@ckeditor/jsdoc-plugins/lib/cleanup' ),
66
- require.resolve( '@ckeditor/jsdoc-plugins/lib/validator/validator' ),
67
- require.resolve( '@ckeditor/jsdoc-plugins/lib/utils/doclet-logger' ),
68
- ...extraPlugins
69
- ],
70
- source: {
71
- include: files
72
- },
73
- opts: {
74
- encoding: 'utf8',
75
- recurse: true,
76
- access: 'all',
77
- template: 'templates/silent'
78
- }
79
- };
80
-
81
- const tmpConfig = tmp.fileSync();
82
-
83
- await fs.writeFile( tmpConfig.name, JSON.stringify( jsDocConfig ) );
84
-
85
- console.log( 'JSDoc started...' );
86
-
87
- try {
88
- // Not so beautiful API as for 2020...
89
- // See more in https://github.com/jsdoc/jsdoc/issues/938.
90
- const cmd = require.resolve( 'jsdoc/jsdoc.js' );
91
-
92
- // The `node` command is used for explicitly needed for Windows.
93
- // See https://github.com/ckeditor/ckeditor5/issues/7212.
94
- tools.shExec( `node ${ cmd } -c ${ tmpConfig.name }`, { verbosity: 'info' } );
95
- } catch ( error ) {
96
- console.error( 'An error was thrown by JSDoc:' );
97
-
98
- throw error;
99
- }
26
+ /**
27
+ * @typedef {Object} JSDocConfig
28
+ *
29
+ * @property {'jsdoc'} type
30
+ *
31
+ * @property {Array.<String>} sourceFiles Glob pattern with source files.
32
+ *
33
+ * @property {String} readmePath Path to `README.md`.
34
+ *
35
+ * @property {Boolean} [validateOnly=false] Whether JSDoc should only validate the documentation and finish
36
+ * with error code `1`. If not passed, the errors will be printed to the console but the task will finish with `0`.
37
+ *
38
+ * @property {Boolean} [strict=false] If `true`, errors found during the validation will finish the process
39
+ * and exit code will be changed to `1`.
40
+ *
41
+ * @property {String} [outputPath='docs/api/output.json'] A path to the place where extracted doclets will be saved.
42
+ *
43
+ * @property {String} [extraPlugins=[]] An array of path to extra plugins that will be added to JSDoc.
44
+ */
100
45
 
101
- console.log( `Documented ${ files.length } files!` );
102
- };
46
+ /**
47
+ * @typedef {Object} TypedocConfig
48
+ *
49
+ * @property {'typedoc'} type
50
+ *
51
+ * @property {Object} config
52
+ *
53
+ * @property {String} cwd
54
+ *
55
+ * @property {String} tsconfig
56
+ *
57
+ * @property {Array.<String>} sourceFiles Glob pattern with source files.
58
+ *
59
+ * @property {Boolean} [strict=false] If `true`, errors found during the validation will finish the process
60
+ * and exit code will be changed to `1`.
61
+ * @property {String} [outputPath] A path to the place where extracted doclets will be saved. Is an optional value due to tests.
62
+ *
63
+ * @property {String} [extraPlugins=[]] An array of path to extra plugins that will be added to Typedoc.
64
+ */
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const fs = require( 'fs-extra' );
9
+ const tmp = require( 'tmp' );
10
+ const glob = require( 'fast-glob' );
11
+ const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
12
+
13
+ /**
14
+ * Builds CKEditor 5 documentation using `jsdoc`.
15
+ *
16
+ * @param {JSDocConfig} config
17
+ * @returns {Promise}
18
+ */
19
+ module.exports = async function build( config ) {
20
+ const sourceFilePatterns = [
21
+ config.readmePath,
22
+ ...config.sourceFiles
23
+ ];
24
+
25
+ const extraPlugins = config.extraPlugins || [];
26
+ const outputPath = config.outputPath || 'docs/api/output.json';
27
+ const validateOnly = config.validateOnly || false;
28
+ const strictCheck = config.strict || false;
29
+
30
+ // Pass options to plugins via env variables.
31
+ // Since plugins are added using `require` calls other forms are currently impossible.
32
+ process.env.JSDOC_OUTPUT_PATH = outputPath;
33
+
34
+ if ( validateOnly ) {
35
+ process.env.JSDOC_VALIDATE_ONLY = 'true';
36
+ }
37
+
38
+ if ( strictCheck ) {
39
+ process.env.JSDOC_STRICT_CHECK = 'true';
40
+ }
41
+
42
+ const files = await glob( sourceFilePatterns );
43
+
44
+ const jsDocConfig = {
45
+ plugins: [
46
+ require.resolve( 'jsdoc/plugins/markdown' ),
47
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/purge-private-api-docs' ),
48
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/export-fixer/export-fixer' ),
49
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/custom-tags/error' ),
50
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/custom-tags/observable' ),
51
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/observable-event-provider' ),
52
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/longname-fixer/longname-fixer' ),
53
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/fix-code-snippets' ),
54
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/relation-fixer' ),
55
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/event-extender/event-extender' ),
56
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/cleanup' ),
57
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/validator/validator' ),
58
+ require.resolve( '@ckeditor/jsdoc-plugins/lib/utils/doclet-logger' ),
59
+ ...extraPlugins
60
+ ],
61
+ source: {
62
+ include: files
63
+ },
64
+ opts: {
65
+ encoding: 'utf8',
66
+ recurse: true,
67
+ access: 'all',
68
+ template: 'templates/silent'
69
+ }
70
+ };
71
+
72
+ const tmpConfig = tmp.fileSync();
73
+
74
+ await fs.writeFile( tmpConfig.name, JSON.stringify( jsDocConfig ) );
75
+
76
+ console.log( 'JSDoc started...' );
77
+
78
+ try {
79
+ // Not so beautiful API as for 2020...
80
+ // See more in https://github.com/jsdoc/jsdoc/issues/938.
81
+ const cmd = require.resolve( 'jsdoc/jsdoc.js' );
82
+
83
+ // The `node` command is used for explicitly needed for Windows.
84
+ // See https://github.com/ckeditor/ckeditor5/issues/7212.
85
+ tools.shExec( `node ${ cmd } -c ${ tmpConfig.name }`, { verbosity: 'info' } );
86
+ } catch ( error ) {
87
+ console.error( 'An error was thrown by JSDoc:' );
88
+
89
+ throw error;
90
+ }
91
+
92
+ console.log( `Documented ${ files.length } files!` );
93
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const glob = require( 'fast-glob' );
9
+ const TypeDoc = require( 'typedoc' );
10
+ const validators = require( './validators' );
11
+
12
+ /**
13
+ * Builds CKEditor 5 documentation using `typedoc`.
14
+ *
15
+ * @param {TypedocConfig} config
16
+ * @returns {Promise}
17
+ */
18
+ module.exports = async function build( config ) {
19
+ const sourceFilePatterns = config.sourceFiles.filter( Boolean );
20
+ const strictMode = config.strict || false;
21
+ const extraPlugins = config.extraPlugins || [];
22
+
23
+ const files = await glob( sourceFilePatterns );
24
+ const typeDoc = new TypeDoc.Application();
25
+
26
+ typeDoc.options.addReader( new TypeDoc.TSConfigReader() );
27
+ typeDoc.options.addReader( new TypeDoc.TypeDocReader() );
28
+
29
+ typeDoc.bootstrap( {
30
+ tsconfig: config.tsconfig,
31
+ excludeExternals: true,
32
+ entryPoints: files,
33
+ logLevel: 'Warn',
34
+ basePath: config.cwd,
35
+ blockTags: [
36
+ '@eventName'
37
+ ],
38
+ inlineTags: [
39
+ '@link',
40
+ '@glink'
41
+ ],
42
+ modifierTags: [
43
+ '@publicApi',
44
+ '@skipSource'
45
+ ],
46
+ plugin: [
47
+ // Fixes `"name": 'default" in the output project.
48
+ 'typedoc-plugin-rename-defaults',
49
+
50
+ require.resolve( '@ckeditor/typedoc-plugins/lib/module-fixer' ),
51
+ require.resolve( '@ckeditor/typedoc-plugins/lib/symbol-fixer' ),
52
+ require.resolve( '@ckeditor/typedoc-plugins/lib/interface-augmentation-fixer' ),
53
+ require.resolve( '@ckeditor/typedoc-plugins/lib/tag-error' ),
54
+ require.resolve( '@ckeditor/typedoc-plugins/lib/tag-event' ),
55
+ require.resolve( '@ckeditor/typedoc-plugins/lib/tag-observable' ),
56
+ require.resolve( '@ckeditor/typedoc-plugins/lib/purge-private-api-docs' ),
57
+
58
+ // The `event-inheritance-fixer` plugin must be loaded after `tag-event` plugin, as it depends on its output.
59
+ require.resolve( '@ckeditor/typedoc-plugins/lib/event-inheritance-fixer' ),
60
+
61
+ // The `event-param-fixer` plugin must be loaded after `tag-event` and `tag-observable` plugins, as it depends on their output.
62
+ require.resolve( '@ckeditor/typedoc-plugins/lib/event-param-fixer' ),
63
+
64
+ ...extraPlugins
65
+ ]
66
+ } );
67
+
68
+ console.log( 'Typedoc started...' );
69
+
70
+ const conversionResult = typeDoc.convert();
71
+
72
+ if ( !conversionResult ) {
73
+ throw 'Something went wrong with TypeDoc.';
74
+ }
75
+
76
+ const validationResult = validators.validate( conversionResult, typeDoc );
77
+
78
+ if ( !validationResult && strictMode ) {
79
+ throw 'Something went wrong with TypeDoc.';
80
+ }
81
+
82
+ if ( config.outputPath ) {
83
+ await typeDoc.generateJson( conversionResult, config.outputPath );
84
+ }
85
+
86
+ console.log( `Documented ${ files.length } files!` );
87
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ReflectionKind } = require( 'typedoc' );
9
+ const { isReflectionValid, isIdentifierValid, isAbsoluteIdentifier } = require( '../utils' );
10
+
11
+ /**
12
+ * Validates the output produced by TypeDoc.
13
+ *
14
+ * It checks if the event in the "@fires" tag exists.
15
+ *
16
+ * @param {Object} project Generated output from TypeDoc to validate.
17
+ * @param {Function} onError A callback that is executed when a validation error is detected.
18
+ */
19
+ module.exports = function validate( project, onError ) {
20
+ const reflections = project.getReflectionsByKind( ReflectionKind.Class | ReflectionKind.CallSignature ).filter( isReflectionValid );
21
+
22
+ for ( const reflection of reflections ) {
23
+ const identifiers = getIdentifiersFromFiresTag( reflection );
24
+
25
+ if ( !identifiers.length ) {
26
+ continue;
27
+ }
28
+
29
+ for ( const identifier of identifiers ) {
30
+ const isValid = isIdentifierValid( reflection, identifier );
31
+
32
+ if ( !isValid ) {
33
+ const eventName = identifier.replace( /^#event:/, '' );
34
+
35
+ onError( `Incorrect event name: "${ eventName }" in the @fires tag`, reflection );
36
+ }
37
+ }
38
+ }
39
+ };
40
+
41
+ function getIdentifiersFromFiresTag( reflection ) {
42
+ if ( !reflection.comment ) {
43
+ return [];
44
+ }
45
+
46
+ return reflection.comment.getTags( '@fires' )
47
+ .flatMap( tag => tag.content.map( item => item.text.trim() ) )
48
+ .map( identifier => {
49
+ if ( isAbsoluteIdentifier( identifier ) ) {
50
+ return identifier;
51
+ }
52
+
53
+ return '#event:' + identifier;
54
+ } );
55
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const seeValidator = require( './see-validator' );
9
+ const linkValidator = require( './link-validator' );
10
+ const firesValidator = require( './fires-validator' );
11
+ const overloadsValidator = require( './overloads-validator' );
12
+ const moduleValidator = require( './module-validator' );
13
+ const { getNode } = require( './utils' );
14
+
15
+ /**
16
+ * Validates the CKEditor 5 documentation.
17
+ *
18
+ * @param {Object} project Generated output from TypeDoc to validate.
19
+ * @param {Object} typeDoc A TypeDoc application instance.
20
+ * @returns {Boolean}
21
+ */
22
+ module.exports = {
23
+ validate( project, typeDoc ) {
24
+ const validators = [
25
+ seeValidator,
26
+ linkValidator,
27
+ firesValidator,
28
+ overloadsValidator,
29
+ moduleValidator
30
+ ];
31
+
32
+ typeDoc.logger.info( 'Starting validation...' );
33
+
34
+ // The same error can be reported twice:
35
+ //
36
+ // 1. When processing types and events (comments are copied from a type to an event).
37
+ // 2. When a parent class defines an invalid link, inherited members link to the invalid link too.
38
+ const errors = new Map();
39
+
40
+ for ( const validator of validators ) {
41
+ validator( project, ( error, reflection ) => {
42
+ const node = getNode( reflection );
43
+
44
+ errors.set( node, { error, node } );
45
+ } );
46
+ }
47
+
48
+ errors.forEach( ( { error, node } ) => typeDoc.logger.warn( error, node ) );
49
+
50
+ typeDoc.logger.info( 'Validation completed.' );
51
+
52
+ return !errors.size;
53
+ }
54
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ReflectionKind } = require( 'typedoc' );
9
+ const { isReflectionValid, isIdentifierValid } = require( '../utils' );
10
+
11
+ /**
12
+ * Validates the output produced by TypeDoc.
13
+ *
14
+ * It checks if the identifier in the "@link" tag points to an existing doclet.
15
+ *
16
+ * @param {Object} project Generated output from TypeDoc to validate.
17
+ * @param {Function} onError A callback that is executed when a validation error is detected.
18
+ */
19
+ module.exports = function validate( project, onError ) {
20
+ const reflections = project.getReflectionsByKind( ReflectionKind.All ).filter( isReflectionValid );
21
+
22
+ for ( const reflection of reflections ) {
23
+ const identifiers = getIdentifiersFromLinkTag( reflection );
24
+
25
+ if ( !identifiers.length ) {
26
+ continue;
27
+ }
28
+
29
+ for ( const identifier of identifiers ) {
30
+ const isValid = isIdentifierValid( reflection, identifier );
31
+
32
+ if ( !isValid ) {
33
+ onError( `Incorrect link: "${ identifier }"`, reflection );
34
+ }
35
+ }
36
+ }
37
+ };
38
+
39
+ function getIdentifiersFromLinkTag( reflection ) {
40
+ if ( !reflection.comment ) {
41
+ return [];
42
+ }
43
+
44
+ // The "@link" tag can be located in the comment summary or it can be nested in other block tags.
45
+ const parts = [
46
+ ...reflection.comment.summary,
47
+ ...reflection.comment.blockTags.flatMap( tag => tag.content )
48
+ ];
49
+
50
+ return parts
51
+ .filter( part => part.kind === 'inline-tag' && part.tag === '@link' )
52
+ .map( part => {
53
+ // The "@link" tag may contain the actual identifier and the display name after a space.
54
+ // Split by space to extract only the identifier from the whole tag.
55
+ const [ identifier ] = part.text.split( ' ' );
56
+
57
+ return identifier;
58
+ } );
59
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ReflectionKind } = require( 'typedoc' );
9
+ const { getNode } = require( '../utils' );
10
+
11
+ /**
12
+ * Validates the output produced by TypeDoc.
13
+ *
14
+ * It checks if the module name matches the path to the file where the module is defined.
15
+ *
16
+ * @param {Object} project Generated output from TypeDoc to validate.
17
+ * @param {Function} onError A callback that is executed when a validation error is detected.
18
+ */
19
+ module.exports = function validate( project, onError ) {
20
+ const reflections = project.getReflectionsByKind( ReflectionKind.Module );
21
+
22
+ for ( const reflection of reflections ) {
23
+ const [ packageName, ...moduleName ] = reflection.name.split( '/' );
24
+
25
+ // If there is no module name after the package name, skip it.
26
+ if ( !moduleName.length ) {
27
+ continue;
28
+ }
29
+
30
+ const filePath = getNode( reflection ).fileName;
31
+ const expectedFilePath = `ckeditor5-${ packageName }/src/${ moduleName.join( '/' ) }.ts`;
32
+
33
+ if ( !filePath.endsWith( expectedFilePath ) ) {
34
+ onError( `Invalid module name: "${ reflection.name }"`, reflection );
35
+ }
36
+ }
37
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ReflectionKind } = require( 'typedoc' );
9
+ const { isReflectionValid } = require( '../utils' );
10
+
11
+ /**
12
+ * Validates the output produced by TypeDoc.
13
+ *
14
+ * It checks if overloaded methods and functions are described with the mandatory "@label" tag.
15
+ *
16
+ * Also, it prevents using the same name twice for overloaded structures.
17
+ *
18
+ * @param {Object} project Generated output from TypeDoc to validate.
19
+ * @param {Function} onError A callback that is executed when a validation error is detected.
20
+ */
21
+ module.exports = function validate( project, onError ) {
22
+ const kinds = ReflectionKind.Method | ReflectionKind.Constructor | ReflectionKind.Function;
23
+ const reflections = project.getReflectionsByKind( kinds ).filter( isReflectionValid );
24
+
25
+ for ( const reflection of reflections ) {
26
+ // Omit non-overloaded structures.
27
+ if ( reflection.signatures.length === 1 ) {
28
+ continue;
29
+ }
30
+
31
+ const uniqueValues = new Set();
32
+
33
+ const isInherited = !!reflection.inheritedFrom;
34
+ const errorMessageSuffix = isInherited ? ' due the inherited structure' : '';
35
+
36
+ for ( const signature of reflection.signatures ) {
37
+ // Check if a signature has a label...
38
+ if ( signature.comment && signature.comment.getTag( '@label' ) ) {
39
+ const [ { text: label } ] = signature.comment.getTag( '@label' ).content;
40
+
41
+ // ...and whether it is a unique value.
42
+ if ( uniqueValues.has( label ) ) {
43
+ onError( `Duplicated name: "${ label }" in the @label tag` + errorMessageSuffix, signature );
44
+ } else {
45
+ uniqueValues.add( label );
46
+ }
47
+ } else {
48
+ onError( 'Overloaded signature misses the @label tag' + errorMessageSuffix, signature );
49
+ }
50
+ }
51
+ }
52
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ReflectionKind } = require( 'typedoc' );
9
+ const { isReflectionValid, isIdentifierValid } = require( '../utils' );
10
+
11
+ /**
12
+ * Validates the output produced by TypeDoc.
13
+ *
14
+ * It checks if the identifier in the "@see" tag points to an existing doclet.
15
+ *
16
+ * @param {Object} project Generated output from TypeDoc to validate.
17
+ * @param {Function} onError A callback that is executed when a validation error is detected.
18
+ */
19
+ module.exports = function validate( project, onError ) {
20
+ const reflections = project.getReflectionsByKind( ReflectionKind.All ).filter( isReflectionValid );
21
+
22
+ for ( const reflection of reflections ) {
23
+ const identifiers = getIdentifiersFromSeeTag( reflection );
24
+
25
+ if ( !identifiers.length ) {
26
+ continue;
27
+ }
28
+
29
+ for ( const identifier of identifiers ) {
30
+ const isValid = isIdentifierValid( reflection, identifier );
31
+
32
+ if ( !isValid ) {
33
+ onError( `Incorrect link: "${ identifier }"`, reflection );
34
+ }
35
+ }
36
+ }
37
+ };
38
+
39
+ function getIdentifiersFromSeeTag( reflection ) {
40
+ if ( !reflection.comment ) {
41
+ return [];
42
+ }
43
+
44
+ return reflection.comment.getTags( '@see' )
45
+ .flatMap( tag => tag.content.map( item => item.text.trim() ) )
46
+ .filter( text => {
47
+ // Remove list markers (e.g. "-").
48
+ if ( text.length <= 1 ) {
49
+ return false;
50
+ }
51
+
52
+ // Remove external links.
53
+ if ( /^https?:\/\//.test( text ) ) {
54
+ return false;
55
+ }
56
+
57
+ return true;
58
+ } );
59
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * Common utils for TypeDoc validators.
10
+ */
11
+ module.exports = {
12
+ isReflectionValid,
13
+ isIdentifierValid,
14
+ isAbsoluteIdentifier,
15
+ getNode
16
+ };
17
+
18
+ /**
19
+ * Checks if the reflection can be considered as "valid" (supported). Only reflections that are not nested inside a type are supported.
20
+ *
21
+ * @param {require('typedoc').Reflection} reflection The reflection to check if it is valid.
22
+ * @returns {Boolean}
23
+ */
24
+ function isReflectionValid( reflection ) {
25
+ if ( reflection.name === '__type' ) {
26
+ return false;
27
+ }
28
+
29
+ if ( reflection.parent ) {
30
+ return isReflectionValid( reflection.parent );
31
+ }
32
+
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Checks if the name (identifier) that is provided for a tag, points to an existing reflection in the whole project.
38
+ * The identifier can be either a relative or an absolute one.
39
+ *
40
+ * @param {require('typedoc').Reflection} reflection The reflection that contain given identifier.
41
+ * @param {String} identifier An identifier to check.
42
+ * @returns {Boolean}
43
+ */
44
+ function isIdentifierValid( reflection, identifier ) {
45
+ const absoluteIdentifier = isAbsoluteIdentifier( identifier ) ?
46
+ identifier :
47
+ toAbsoluteIdentifier( reflection, identifier );
48
+
49
+ return hasTarget( reflection.project, absoluteIdentifier );
50
+ }
51
+
52
+ /**
53
+ * Checks if the identifier is an absolute one.
54
+ *
55
+ * @param {String} identifier An identifier to check.
56
+ * @returns {Boolean}
57
+ */
58
+ function isAbsoluteIdentifier( identifier ) {
59
+ return identifier.startsWith( 'module:' );
60
+ }
61
+
62
+ /**
63
+ * Converts a relative identifier into an absolute one.
64
+ *
65
+ * @param {require('typedoc').Reflection} reflection The reflection that contain given identifier.
66
+ * @param {String} identifier An identifier to convert.
67
+ * @returns {String}
68
+ */
69
+ function toAbsoluteIdentifier( reflection, identifier ) {
70
+ const separator = identifier[ 0 ];
71
+ const parts = getLongNameParts( reflection );
72
+
73
+ return separator === '~' ?
74
+ 'module:' + parts[ 0 ] + identifier :
75
+ 'module:' + parts[ 0 ] + '~' + parts[ 1 ] + identifier;
76
+ }
77
+
78
+ /**
79
+ * Returns a longname for a reflection, divided into separate parts.
80
+ *
81
+ * @param {require('typedoc').Reflection} reflection A reflection for which we want to get its longname.
82
+ * @returns {Array.<String>}
83
+ */
84
+ function getLongNameParts( reflection ) {
85
+ // Kinds of reflection that affect the longname format.
86
+ const kinds = [
87
+ 'Module',
88
+ 'Class',
89
+ 'Function',
90
+ 'Interface',
91
+ 'Type alias',
92
+ 'Accessor',
93
+ 'Variable',
94
+ 'Method',
95
+ 'Property',
96
+ 'Event'
97
+ ];
98
+
99
+ const parts = [];
100
+
101
+ while ( reflection ) {
102
+ if ( kinds.includes( reflection.kindString ) ) {
103
+ parts.unshift( reflection.name );
104
+ }
105
+
106
+ reflection = reflection.parent;
107
+ }
108
+
109
+ return parts;
110
+ }
111
+
112
+ /**
113
+ * Returns the TypeScript node from the reflection.
114
+ *
115
+ * @param {require('typedoc').Reflection} reflection A reflection for which we want to get its TypeScript node.
116
+ * @returns {Object}
117
+ */
118
+ function getNode( reflection ) {
119
+ let symbol = reflection.project.getSymbolFromReflection( reflection );
120
+ let declarationIndex = 0;
121
+
122
+ if ( !symbol ) {
123
+ // The TypeDoc project does not store symbols for signatures. To get the TypeScript node from a signature, we need to get the
124
+ // symbol from its parent, which contains all nodes for each signature.
125
+ symbol = reflection.project.getSymbolFromReflection( reflection.parent );
126
+ declarationIndex = reflection.parent.signatures ? reflection.parent.signatures.indexOf( reflection ) : 0;
127
+ }
128
+
129
+ return symbol.declarations[ declarationIndex ];
130
+ }
131
+
132
+ /**
133
+ * Checks if the provided identifier targets an existing reflection within the whole project.
134
+ *
135
+ * @param {require('typedoc').ProjectReflection} project The project reflection.
136
+ * @param {String} identifier The absolute identifier to locate the target reflection.
137
+ * @returns {Boolean}
138
+ */
139
+ function hasTarget( project, identifier ) {
140
+ const parts = identifier
141
+ // Remove leading "module:" prefix from the doclet longname.
142
+ .substring( 'module:'.length )
143
+ // Then, split the rest of the longname into separate parts.
144
+ .split( /#|~|\./ );
145
+
146
+ // The last part of the longname may contain a colon, which can be either a part of the event name, or it indicates that the name
147
+ // targets a labeled signature.
148
+ const lastPart = parts.pop();
149
+ const [ lastPartName, lastPartLabel ] = lastPart.split( ':' );
150
+
151
+ const isIdentifierEvent = lastPart.startsWith( 'event:' );
152
+ const isIdentifierLabeledSignature = !isIdentifierEvent && lastPart.includes( ':' );
153
+
154
+ if ( isIdentifierLabeledSignature ) {
155
+ // If the identifier is a labeled signature, just use the method/function name and the labeled signature will be searched later.
156
+ parts.push( lastPartName );
157
+ } else {
158
+ // Otherwise, restore the original identifier part.
159
+ parts.push( lastPart );
160
+ }
161
+
162
+ const targetReflection = project.getChildByName( parts );
163
+
164
+ if ( !targetReflection ) {
165
+ return false;
166
+ }
167
+
168
+ // Now, when the target reflection is found, do some checks whether it matches the identifier.
169
+ // (1) Check if the labeled signature targets an existing signature.
170
+ if ( isIdentifierLabeledSignature ) {
171
+ if ( !targetReflection.signatures ) {
172
+ return false;
173
+ }
174
+
175
+ return targetReflection.signatures.some( signature => {
176
+ if ( !signature.comment ) {
177
+ return false;
178
+ }
179
+
180
+ const labelTag = signature.comment.getTag( '@label' );
181
+
182
+ if ( !labelTag ) {
183
+ return false;
184
+ }
185
+
186
+ return labelTag.content[ 0 ].text === lastPartLabel;
187
+ } );
188
+ }
189
+
190
+ const isIdentifierStatic = identifier.includes( '.' );
191
+ const isTargetReflectionStatic = Boolean( targetReflection.flags && targetReflection.flags.isStatic );
192
+
193
+ // (2) Check if the static/non-static reflection flag matches the separator used in the identifier.
194
+ if ( isIdentifierStatic !== isTargetReflectionStatic ) {
195
+ return false;
196
+ }
197
+
198
+ return true;
199
+ }
package/package.json CHANGED
@@ -1,16 +1,24 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-dev-docs",
3
- "version": "32.0.1",
3
+ "version": "32.1.0",
4
4
  "description": "Tasks used to build and verify the documentation for CKEditor 5.",
5
5
  "keywords": [],
6
6
  "main": "lib/index.js",
7
7
  "dependencies": {
8
- "@ckeditor/ckeditor5-dev-utils": "^32.0.1",
9
- "@ckeditor/jsdoc-plugins": "^32.0.1",
8
+ "@ckeditor/ckeditor5-dev-utils": "^32.1.0",
9
+ "@ckeditor/jsdoc-plugins": "^32.1.0",
10
+ "@ckeditor/typedoc-plugins": "^32.1.0",
10
11
  "fast-glob": "^3.2.4",
11
12
  "fs-extra": "^9.0.0",
12
13
  "jsdoc": "ckeditor/jsdoc#fixed-trailing-comment-doclets",
13
- "tmp": "^0.2.1"
14
+ "tmp": "^0.2.1",
15
+ "typedoc": "^0.23.15",
16
+ "typedoc-plugin-rename-defaults": "^0.6.4"
17
+ },
18
+ "devDependencies": {
19
+ "chai": "^4.2.0",
20
+ "proxyquire": "^2.1.3",
21
+ "sinon": "^9.2.4"
14
22
  },
15
23
  "engines": {
16
24
  "node": ">=14.0.0",