Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typedoc - event plugin should not guessing an event owner #853

Merged
merged 8 commits into from Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 11 additions & 9 deletions packages/ckeditor5-dev-docs/lib/buildtypedoc.js
Expand Up @@ -7,6 +7,8 @@

const glob = require( 'fast-glob' );
const TypeDoc = require( 'typedoc' );
const { plugins } = require( '@ckeditor/typedoc-plugins' );

const validators = require( './validators' );

/**
Expand Down Expand Up @@ -50,19 +52,19 @@ module.exports = async function build( config ) {
// Fixes `"name": 'default" in the output project.
'typedoc-plugin-rename-defaults',

require.resolve( '@ckeditor/typedoc-plugins/lib/module-fixer' ),
require.resolve( '@ckeditor/typedoc-plugins/lib/symbol-fixer' ),
require.resolve( '@ckeditor/typedoc-plugins/lib/interface-augmentation-fixer' ),
require.resolve( '@ckeditor/typedoc-plugins/lib/tag-error' ),
require.resolve( '@ckeditor/typedoc-plugins/lib/tag-event' ),
require.resolve( '@ckeditor/typedoc-plugins/lib/tag-observable' ),
require.resolve( '@ckeditor/typedoc-plugins/lib/purge-private-api-docs' ),
plugins[ 'typedoc-plugin-module-fixer' ],
plugins[ 'typedoc-plugin-symbol-fixer' ],
plugins[ 'typedoc-plugin-interface-augmentation-fixer' ],
plugins[ 'typedoc-plugin-tag-error' ],
plugins[ 'typedoc-plugin-tag-event' ],
plugins[ 'typedoc-plugin-tag-observable' ],
plugins[ 'typedoc-plugin-purge-private-api-docs' ],

// The `event-inheritance-fixer` plugin must be loaded after `tag-event` plugin, as it depends on its output.
require.resolve( '@ckeditor/typedoc-plugins/lib/event-inheritance-fixer' ),
plugins[ 'typedoc-plugin-event-inheritance-fixer' ],

// The `event-param-fixer` plugin must be loaded after `tag-event` and `tag-observable` plugins, as it depends on their output.
require.resolve( '@ckeditor/typedoc-plugins/lib/event-param-fixer' ),
plugins[ 'typedoc-plugin-event-param-fixer' ],

...extraPlugins
]
Expand Down
Expand Up @@ -6,7 +6,7 @@
'use strict';

const { ReflectionKind } = require( 'typedoc' );
const { isReflectionValid, isIdentifierValid, isAbsoluteIdentifier } = require( '../utils' );
const { utils } = require( '@ckeditor/typedoc-plugins' );

/**
* Validates the output produced by TypeDoc.
Expand All @@ -17,7 +17,9 @@ const { isReflectionValid, isIdentifierValid, isAbsoluteIdentifier } = require(
* @param {Function} onError A callback that is executed when a validation error is detected.
*/
module.exports = function validate( project, onError ) {
const reflections = project.getReflectionsByKind( ReflectionKind.Class | ReflectionKind.CallSignature ).filter( isReflectionValid );
const reflections = project
.getReflectionsByKind( ReflectionKind.Class | ReflectionKind.CallSignature )
.filter( utils.isReflectionValid );

for ( const reflection of reflections ) {
const identifiers = getIdentifiersFromFiresTag( reflection );
Expand All @@ -27,7 +29,7 @@ module.exports = function validate( project, onError ) {
}

for ( const identifier of identifiers ) {
const isValid = isIdentifierValid( reflection, identifier );
const isValid = utils.isIdentifierValid( reflection, identifier );

if ( !isValid ) {
const eventName = identifier.replace( /^#event:/, '' );
Expand All @@ -46,7 +48,7 @@ function getIdentifiersFromFiresTag( reflection ) {
return reflection.comment.getTags( '@fires' )
.flatMap( tag => tag.content.map( item => item.text.trim() ) )
.map( identifier => {
if ( isAbsoluteIdentifier( identifier ) ) {
if ( utils.isAbsoluteIdentifier( identifier ) ) {
return identifier;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-dev-docs/lib/validators/index.js
Expand Up @@ -5,12 +5,12 @@

'use strict';

const { utils } = require( '@ckeditor/typedoc-plugins' );
const seeValidator = require( './see-validator' );
const linkValidator = require( './link-validator' );
const firesValidator = require( './fires-validator' );
const moduleValidator = require( './module-validator' );
const overloadsValidator = require( './overloads-validator' );
const { getNode } = require( './utils' );

/**
* Validates the CKEditor 5 documentation.
Expand Down Expand Up @@ -43,7 +43,7 @@ module.exports = {

for ( const validator of validators ) {
validator( project, ( error, reflection ) => {
const node = getNode( reflection );
const node = utils.getNode( reflection );

errors.set( node, { error, node } );
} );
Expand Down
Expand Up @@ -6,7 +6,7 @@
'use strict';

const { ReflectionKind } = require( 'typedoc' );
const { isReflectionValid, isIdentifierValid } = require( '../utils' );
const { utils } = require( '@ckeditor/typedoc-plugins' );

/**
* Validates the output produced by TypeDoc.
Expand All @@ -17,7 +17,7 @@ const { isReflectionValid, isIdentifierValid } = require( '../utils' );
* @param {Function} onError A callback that is executed when a validation error is detected.
*/
module.exports = function validate( project, onError ) {
const reflections = project.getReflectionsByKind( ReflectionKind.All ).filter( isReflectionValid );
const reflections = project.getReflectionsByKind( ReflectionKind.All ).filter( utils.isReflectionValid );

for ( const reflection of reflections ) {
const identifiers = getIdentifiersFromLinkTag( reflection );
Expand All @@ -27,7 +27,7 @@ module.exports = function validate( project, onError ) {
}

for ( const identifier of identifiers ) {
const isValid = isIdentifierValid( reflection, identifier );
const isValid = utils.isIdentifierValid( reflection, identifier );

if ( !isValid ) {
onError( `Incorrect link: "${ identifier }"`, reflection );
Expand Down
Expand Up @@ -6,7 +6,7 @@
'use strict';

const { ReflectionKind } = require( 'typedoc' );
const { getNode } = require( '../utils' );
const { utils } = require( '@ckeditor/typedoc-plugins' );

/**
* Validates the output produced by TypeDoc.
Expand All @@ -27,7 +27,7 @@ module.exports = function validate( project, onError ) {
continue;
}

const filePath = getNode( reflection ).fileName;
const filePath = utils.getNode( reflection ).fileName;
const expectedFilePath = `ckeditor5-${ packageName }/src/${ moduleName.join( '/' ) }.ts`;

if ( !filePath.endsWith( expectedFilePath ) ) {
Expand Down
Expand Up @@ -6,7 +6,7 @@
'use strict';

const { ReflectionKind } = require( 'typedoc' );
const { isReflectionValid } = require( '../utils' );
const { utils } = require( '@ckeditor/typedoc-plugins' );

// The `@label` validator is currently not used.
// See: https://github.com/ckeditor/ckeditor5/issues/13591.
Expand All @@ -23,7 +23,7 @@ const { isReflectionValid } = require( '../utils' );
*/
module.exports = function validate( project, onError ) {
const kinds = ReflectionKind.Method | ReflectionKind.Constructor | ReflectionKind.Function;
const reflections = project.getReflectionsByKind( kinds ).filter( isReflectionValid );
const reflections = project.getReflectionsByKind( kinds ).filter( utils.isReflectionValid );

for ( const reflection of reflections ) {
// Omit non-overloaded structures.
Expand Down
Expand Up @@ -6,7 +6,7 @@
'use strict';

const { ReflectionKind } = require( 'typedoc' );
const { isReflectionValid, isIdentifierValid } = require( '../utils' );
const { utils } = require( '@ckeditor/typedoc-plugins' );

/**
* Validates the output produced by TypeDoc.
Expand All @@ -17,7 +17,7 @@ const { isReflectionValid, isIdentifierValid } = require( '../utils' );
* @param {Function} onError A callback that is executed when a validation error is detected.
*/
module.exports = function validate( project, onError ) {
const reflections = project.getReflectionsByKind( ReflectionKind.All ).filter( isReflectionValid );
const reflections = project.getReflectionsByKind( ReflectionKind.All ).filter( utils.isReflectionValid );

for ( const reflection of reflections ) {
const identifiers = getIdentifiersFromSeeTag( reflection );
Expand All @@ -27,7 +27,7 @@ module.exports = function validate( project, onError ) {
}

for ( const identifier of identifiers ) {
const isValid = isIdentifierValid( reflection, identifier );
const isValid = utils.isIdentifierValid( reflection, identifier );

if ( !isValid ) {
onError( `Incorrect link: "${ identifier }"`, reflection );
Expand Down
Expand Up @@ -28,7 +28,7 @@ export class ClassWithFires {
}

/**
* @eventName event-example
* @eventName ~ClassWithFires#event-example
*/
export type EventExample = {
name: string;
Expand Down
Expand Up @@ -24,7 +24,7 @@ export class ClassWithFiresConflictingNames {
}

/**
* @eventName event
* @eventName ~ClassWithFiresConflictingNames#event
*/
export type Event = {
name: string;
Expand Down
Expand Up @@ -94,7 +94,7 @@ export class ClassWithLinks {
* - valid one: {@link module:fixtures/links~ClassWithLinks#property link to a doclet},
* - invalid one: {@link module:non-existing/module~Foo#bar link to a doclet}.
*
* @eventName event-example
* @eventName ~ClassWithLinks#event-example
*/
export type EventExample = {
name: string;
Expand Down
Expand Up @@ -90,7 +90,7 @@ export class ClassWithSeeTags {
/**
* An example event with valid and invalid "@see" tags.
*
* @eventName event-example
* @eventName ~ClassWithSeeTags#event-example
* @see module:fixtures/see~ClassWithSeeTags#property
* @see module:non-existing/module~Foo#bar
*/
Expand Down
65 changes: 35 additions & 30 deletions packages/typedoc-plugins/lib/event-inheritance-fixer/index.js
Expand Up @@ -10,8 +10,8 @@ const { Converter, ReflectionKind, ReferenceType } = require( 'typedoc' );
/**
* The `typedoc-plugin-event-inheritance-fixer` takes care of inheriting events, which are not handled by TypeDoc by default.
*
* If a class fires an event and this class is a base class for another class, then all events from the base class are copied and inserted
* into each derived class.
* Event can be inherited from a class or from an interface. If a class or an interface fires an event and it is a base for another class or
* interface, then all events from the base reflection are copied and inserted into each derived reflection.
*/
module.exports = {
load( app ) {
Expand All @@ -20,40 +20,46 @@ module.exports = {
};

function onEventEnd( context ) {
const classReflections = context.project.getReflectionsByKind( ReflectionKind.Class );
// Event can be assigned as a child to a class or to an interface.
const reflections = context.project.getReflectionsByKind( ReflectionKind.Class | ReflectionKind.Interface );

for ( const classReflection of classReflections ) {
const eventReflections = classReflection.children.filter( reflection => reflection.kindString === 'Event' );
for ( const reflection of reflections ) {
// If an interface does not contain any children, skip it.
if ( !reflection.children ) {
continue;
}

// If class does not fire events, skip it.
const eventReflections = reflection.children.filter( reflection => reflection.kindString === 'Event' );

// If class or interface does not fire events, skip it.
if ( !eventReflections.length ) {
continue;
}

// Otherwise, find all derived classes in the whole inheritance chain.
const derivedClassReflections = getDerivedClasses( classReflection );
// Otherwise, find all derived classes and interfaces in the whole inheritance chain.
const derivedReflections = getDerivedReflections( reflection );

// If class is not extended by another class, skip it.
if ( !derivedClassReflections.length ) {
// If current reflection is not extended by another class, skip it.
if ( !derivedReflections.length ) {
continue;
}

// For each derived class...
for ( const derivedClass of derivedClassReflections ) {
// ...and for each event from the base class...
// For each derived reflection...
for ( const derivedReflection of derivedReflections ) {
// ...and for each event from the base reflection...
for ( const eventReflection of eventReflections ) {
// ...skip processing the event if derived class already has it.
const hasEvent = derivedClass.children
// ...skip processing the event if derived reflection already has it.
const hasEvent = derivedReflection.children
.some( child => child.kindString === 'Event' && child.name === eventReflection.name );

if ( hasEvent ) {
continue;
}

// Otherwise, create and insert new event reflection (with cloned event properties from the base class) as the child of the
// derived class.
// Otherwise, create and insert new event reflection (with cloned event properties from the base reflection) as the child
// of the derived reflection.
const clonedEventReflection = context
.withScope( derivedClass )
.withScope( derivedReflection )
.createDeclarationReflection(
ReflectionKind.ObjectLiteral,
undefined,
Expand All @@ -68,7 +74,7 @@ function onEventEnd( context ) {
clonedEventReflection.sources = [ ...eventReflection.sources ];

clonedEventReflection.inheritedFrom = ReferenceType.createResolvedReference(
`${ classReflection.name }.${ eventReflection.name }`,
`${ reflection.name }.${ eventReflection.name }`,
eventReflection,
context.project
);
Expand All @@ -82,25 +88,24 @@ function onEventEnd( context ) {
}

/**
* Finds all derived classes from the specified base class. It traverses the whole inheritance chain, up to the last derived class. If the
* base class is not extended, empty array is returned.
* Finds all derived classes and interfaces from the specified base reflection. It traverses the whole inheritance chain.
* If the base reflection is not extended or implemented by any other reflection, empty array is returned.
*
* @param {require('typedoc').Reflection} classReflection The base class from which the derived classes will be searched.
* @param {require('typedoc').Reflection} reflection The base reflection from which the derived ones will be searched.
* @returns {Array.<require('typedoc').Reflection>}
*/
function getDerivedClasses( classReflection ) {
if ( !classReflection.extendedBy ) {
return [];
}
function getDerivedReflections( reflection ) {
const extendedBy = reflection.extendedBy || [];
const implementedBy = reflection.implementedBy || [];

return classReflection.extendedBy
return [ ...extendedBy, ...implementedBy ]
.filter( entry => entry.reflection )
.flatMap( entry => {
const derivedClass = entry.reflection;
const derivedReflection = entry.reflection;

return [
derivedClass,
...getDerivedClasses( derivedClass )
derivedReflection,
...getDerivedReflections( derivedReflection )
];
} );
}
23 changes: 23 additions & 0 deletions packages/typedoc-plugins/lib/index.js
@@ -0,0 +1,23 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

'use strict';

const utils = require( './utils' );

module.exports = {
plugins: {
'typedoc-plugin-event-inheritance-fixer': require.resolve( './event-inheritance-fixer' ),
'typedoc-plugin-event-param-fixer': require.resolve( './event-param-fixer' ),
'typedoc-plugin-interface-augmentation-fixer': require.resolve( './interface-augmentation-fixer' ),
'typedoc-plugin-module-fixer': require.resolve( './module-fixer' ),
'typedoc-plugin-purge-private-api-docs': require.resolve( './purge-private-api-docs' ),
'typedoc-plugin-symbol-fixer': require.resolve( './symbol-fixer' ),
'typedoc-plugin-tag-error': require.resolve( './tag-error' ),
'typedoc-plugin-tag-event': require.resolve( './tag-event' ),
'typedoc-plugin-tag-observable': require.resolve( './tag-observable' )
},
utils
};