Skip to content

Commit

Permalink
fix: fix events order, bind TypeDoc prototypes to watch custom events
Browse files Browse the repository at this point in the history
Related to option set, after theme loaded, before options freeze
  • Loading branch information
GerkinDev committed Mar 2, 2022
1 parent 57e5e39 commit 83ee577
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 85 deletions.
2 changes: 1 addition & 1 deletion packages/typedoc-plugin-code-blocks/src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe( 'Behavior', () => {
},
} );
} );
it( 'should not affect text if no code block', () => {
it.only( 'should not affect text if no code block', () => {
const text = 'Hello world' ;
expect( markdownReplacerTestbed.runMarkdownReplace( text ) ).toEqual( text );
} );
Expand Down
14 changes: 9 additions & 5 deletions packages/typedoc-plugin-code-blocks/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import assert from 'assert';
import { relative } from 'path';

import { once } from 'lodash';
import { Application, JSX, RendererEvent } from 'typedoc';
import { Application, JSX, LogLevel } from 'typedoc';

import { ABasePlugin, CurrentPageMemo, MarkdownReplacer, PathReflectionResolver } from '@knodes/typedoc-pluginutils';
import { ABasePlugin, CurrentPageMemo, EventsExtra, MarkdownReplacer, PathReflectionResolver } from '@knodes/typedoc-pluginutils';

import { getCodeBlockRenderer } from './code-blocks';
import { DEFAULT_BLOCK_NAME, ICodeSample, readCodeSample } from './code-sample-file';
Expand Down Expand Up @@ -33,10 +33,14 @@ export class CodeBlockPlugin extends ABasePlugin {
* @see {@link import('@knodes/typedoc-pluginutils').autoload}.
*/
public initialize(): void {
this.application.renderer.on( RendererEvent.BEGIN, this._codeBlockRenderer.bind( this ) );
// Hook over each markdown events to replace code blocks
this._markdownReplacer.bindReplace( EXTRACT_CODE_BLOCKS_REGEX, this._replaceCodeBlock.bind( this ) );
this._markdownReplacer.bindReplace( EXTRACT_CODE_BLOCKS_REGEX, this._replaceCodeBlock.bind( this ), 'replace code blocks' );
this._currentPageMemo.initialize();
EventsExtra.for( this.application )
.onThemeReady( this._codeBlockRenderer.bind( this ) )
.onSetOption( `${this.optionsPrefix}:logLevel`, v => {
this.logger.level = v as LogLevel;
} );
}


Expand Down Expand Up @@ -74,7 +78,7 @@ export class CodeBlockPlugin extends ABasePlugin {
this.logger.error( () => `In "${sourceHint()}", could not resolve file "${file}" from ${this._currentPageMemo.currentReflection.name}` );
return fullMatch;
} else {
this.logger.verbose( () => `Created a code block to ${resolvedFile} from "${sourceHint()}"` );
this.logger.verbose( () => `Created a code block to ${this.relativeToRoot( resolvedFile )} from "${sourceHint()}"` );
}
if( !this._fileSamples.has( resolvedFile ) ){
this._fileSamples.set( resolvedFile, readCodeSample( resolvedFile ) );
Expand Down
23 changes: 16 additions & 7 deletions packages/typedoc-plugin-pages/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import assert from 'assert';

import { isString, once } from 'lodash';
import { Application, JSX, RendererEvent } from 'typedoc';
import { Application, JSX, LogLevel, RendererEvent } from 'typedoc';

import { ABasePlugin, CurrentPageMemo, MarkdownReplacer, PathReflectionResolver } from '@knodes/typedoc-pluginutils';
import { ABasePlugin, CurrentPageMemo, EventsExtra, MarkdownReplacer, PathReflectionResolver } from '@knodes/typedoc-pluginutils';

import { buildOptions } from './options';
import { NodeReflection } from './reflections';
Expand All @@ -26,18 +26,27 @@ export class PagesPlugin extends ABasePlugin {
public override initialize(){
const opts = this.pluginOptions.getValue();
this.logger.level = opts.logLevel;
this._markdownReplacer.bindReplace( EXTRACT_PAGE_LINK_REGEX, this._replacePageLink.bind( this ) );
this._currentPageMemo.initialize();
this.application.renderer.on( RendererEvent.BEGIN, this._pageTreeBuilder.bind( this ) );
this.application.renderer.on( RendererEvent.BEGIN, this.addPagesToProject.bind( this ) );
this.application.renderer.on( RendererEvent.BEGIN, this._addPagesToProject.bind( this ) );

EventsExtra.for( this.application )
.beforeOptionsFreeze( () => {
if( this.pluginOptions.getValue().enablePageLinks ){
this._markdownReplacer.bindReplace( EXTRACT_PAGE_LINK_REGEX, this._replacePageLink.bind( this ), 'replace page links' );
}
} )
.onThemeReady( this._pageTreeBuilder.bind( this ) )
.onSetOption( `${this.optionsPrefix}:logLevel`, v => {
this.logger.level = v as LogLevel;
} );
}

/**
* Generate pages mappings & append {@link NodeReflection} to the project.
*
* @param event - The renderer event emitted at {@link RendererEvent.BEGIN}.
*/
public addPagesToProject( event: RendererEvent ){
private _addPagesToProject( event: RendererEvent ){
const opts = this.pluginOptions.getValue();
this._pageTreeBuilder().appendToProject( event, opts );
this.application.logger.info( `Generating ${this._pageTreeBuilder().mappings.length} pages` );
Expand Down Expand Up @@ -79,7 +88,7 @@ export class PagesPlugin extends ABasePlugin {
.map( m => this.relativeToRoot( m.model.sourceFilePath ) ) )}` );
return fullMatch;
} else {
this.logger.verbose( () => `Created a link from "${sourceHint()}" to "${mapping.model.name}" (resolved as "${resolvedFile}")` );
this.logger.verbose( () => `Created a link from "${sourceHint()}" to "${mapping.model.name}" (resolved as "${this.relativeToRoot( resolvedFile )}")` );
}
const link = builder.renderPageLink( { label: label ?? undefined, mapping } );
if( typeof link === 'string' ){
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/typedoc/src/lib/output/plugins/JavascriptIndexPlugin.ts b/packages/typedoc-plugin-pages/src/theme-plugins/search/default-pages-javascript-index-plugin.GENERATED.ts
index 8770e6fc..7f310a75 100755
index 8770e6fc..c6598447 100755
--- a/typedoc/src/lib/output/plugins/JavascriptIndexPlugin.ts
+++ b/packages/typedoc-plugin-pages/src/theme-plugins/search/default-pages-javascript-index-plugin.GENERATED.ts
@@ -1,29 +1,65 @@
Expand Down Expand Up @@ -67,7 +67,7 @@ index 8770e6fc..7f310a75 100755
+ if( !( reflection instanceof ANodeReflection ) ){
+ return { reflection, isPage: false };
+ }
+ if( reflection instanceof MenuReflection ){
+ if( !this._plugin.pluginOptions.getValue().enableSearch || reflection instanceof MenuReflection ){
+ return { reflection: null, isPage: false };
+ }
+ const name = [
Expand All @@ -83,7 +83,7 @@ index 8770e6fc..7f310a75 100755
}

/**
@@ -31,10 +67,7 @@ export class JavascriptIndexPlugin extends RendererComponent {
@@ -31,29 +67,27 @@ export class JavascriptIndexPlugin extends RendererComponent {
*
* @param event - An event object describing the current render operation.
*/
Expand All @@ -95,7 +95,8 @@ index 8770e6fc..7f310a75 100755
if ( event.isDefaultPrevented ) {
return;
}
@@ -42,18 +75,18 @@ export class JavascriptIndexPlugin extends RendererComponent {

+ this._plugin.logger.verbose( `${this._plugin.pluginOptions.getValue().enableSearch ? 'Enabling' : 'Disabling'} search for pages` );
const rows: any[] = [];
const kinds: { [K in ReflectionKind]?: string } = {};

Expand All @@ -120,15 +121,15 @@ index 8770e6fc..7f310a75 100755
) {
continue;
}
@@ -69,6 +102,7 @@ export class JavascriptIndexPlugin extends RendererComponent {
@@ -69,6 +103,7 @@ export class JavascriptIndexPlugin extends RendererComponent {
name: reflection.name,
url: reflection.url,
classes: reflection.cssClasses,
+ isPage,
};

if ( parent ) {
@@ -76,8 +110,8 @@ export class JavascriptIndexPlugin extends RendererComponent {
@@ -76,8 +111,8 @@ export class JavascriptIndexPlugin extends RendererComponent {
}

if ( !kinds[reflection.kind] ) {
Expand All @@ -139,7 +140,7 @@ index 8770e6fc..7f310a75 100755
);
}

@@ -91,14 +125,14 @@ export class JavascriptIndexPlugin extends RendererComponent {
@@ -91,14 +126,14 @@ export class JavascriptIndexPlugin extends RendererComponent {
builder.field( 'name', { boost: 10 } );
builder.field( 'parent' );

Expand All @@ -157,7 +158,7 @@ index 8770e6fc..7f310a75 100755
);
const jsonData = JSON.stringify( {
kinds,
@@ -106,9 +140,10 @@ export class JavascriptIndexPlugin extends RendererComponent {
@@ -106,9 +141,10 @@ export class JavascriptIndexPlugin extends RendererComponent {
index,
} );

Expand Down
8 changes: 5 additions & 3 deletions packages/typedoc-plugintestbed/src/mock-markdown-replacer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from 'assert';

import { isNil } from 'lodash';
import { MarkdownEvent, Renderer } from 'typedoc';

import { setupCaptureEvent } from './capture-event';
Expand All @@ -8,11 +9,12 @@ export const setupMockMarkdownReplacer = () => {
const capture = setupCaptureEvent( Renderer, MarkdownEvent.PARSE );
return {
captureEventRegistration: capture.captureEventRegistration,
runMarkdownReplace: ( text: string ) => {
runMarkdownReplace: ( text: string, listenerIndex?: number ) => {
const listeners = capture.getListeners();
assert.equal( listeners.length, 1, `Invalid listeners count for event ${MarkdownEvent.PARSE}` );
const markdownEvent = new MarkdownEvent( MarkdownEvent.PARSE, text, text );
listeners[0]( markdownEvent );
assert( listeners.length >= 1, `Invalid listeners count for event ${MarkdownEvent.PARSE}` );
const listenersToTrigger = isNil( listenerIndex ) ? listeners : [ listeners[listenerIndex] ];
listenersToTrigger.forEach( l => l( markdownEvent ) );
return markdownEvent.parsedText;
},
};
Expand Down
29 changes: 19 additions & 10 deletions packages/typedoc-plugintestbed/src/mock-page-memo.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import assert from 'assert';

import { MarkdownEvent, PageEvent, ProjectReflection, Reflection, RenderTemplate, Renderer, SourceFile } from 'typedoc';
import { isNil } from 'lodash';
import { PageEvent, ProjectReflection, Reflection, RenderTemplate, Renderer, SourceFile } from 'typedoc';

import { setupCaptureEvent } from './capture-event';

export const setupMockPageMemo = () => {
const capture = setupCaptureEvent( Renderer, Renderer.EVENT_BEGIN_PAGE );
return {
captureEventRegistration: capture.captureEventRegistration,
setCurrentPage: <T extends Reflection>( url: string, source: string, model: T, template: RenderTemplate<PageEvent<T>> = () => '', project = new ProjectReflection( 'Fake' ) ) => {
setCurrentPage: <T extends Reflection>(
url: string,
source: string,
model: T,
template: RenderTemplate<PageEvent<T>> = () => '',
project = new ProjectReflection( 'Fake' ),
listenerIndex?: number,
) => {
const listeners = capture.getListeners();
assert.equal( listeners.length, 1, `Invalid listeners count for event ${MarkdownEvent.PARSE}` );
const event = new PageEvent<T>( PageEvent.BEGIN );
event.project = project;
event.url = url;
event.model = model;
event.template = template ?? ( () => '' );
event.filename = url;
assert( listeners.length >= 1, `Invalid listeners count for event ${Renderer.EVENT_BEGIN_PAGE}` );
const pageEvent = new PageEvent<T>( PageEvent.BEGIN );
pageEvent.project = project;
pageEvent.url = url;
pageEvent.model = model;
pageEvent.template = template ?? ( () => '' );
pageEvent.filename = url;
Object.defineProperty( model, 'project', { value: project } );
model.sources = [
...( model.sources ?? [] ),
{ fileName: source, character: 1, line: 1, file: new SourceFile( source ) },
];
listeners[0]( event );
const listenersToTrigger = isNil( listenerIndex ) ? listeners : [ listeners[listenerIndex] ];
listenersToTrigger.forEach( l => l( pageEvent ) );
},
};
};
9 changes: 8 additions & 1 deletion packages/typedoc-pluginutils/src/current-page-memo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ import { ABasePlugin } from './base-plugin';

export class CurrentPageMemo {
private _currentPage?: PageEvent<Reflection>;
private _initialized = false;
public get initialized(){
return this._initialized;
}

public constructor( protected readonly plugin: ABasePlugin ){}


/**
* Start watching for pages event.
*/
public initialize(){
if( this._initialized ){
return;
}
this._initialized = true;
this.plugin.application.renderer.on( Renderer.EVENT_BEGIN_PAGE, ( e: PageEvent<Reflection> ) => {
this._currentPage = e;
} );
Expand Down
113 changes: 113 additions & 0 deletions packages/typedoc-pluginutils/src/events-extra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import assert from 'assert';

import { Application } from 'typedoc';

type Fn = ( ...args: any[] ) => any;
type MethodKeys<T> = {[k in keyof T]: T[k] extends Fn ? k : never}[keyof T] & string
type Params<T> = T extends Fn ? Parameters<T> : unknown[];
type Ret<T> = T extends Fn ? ReturnType<T> : unknown;
export class EventsExtra {
private static readonly _apps = new WeakMap<Application, EventsExtra>();

/**
* Get events extra for the given application.
*
* @param application - The application to bind.
* @returns the events extra instance.
*/
public static for( application: Application ){
const e = this._apps.get( application ) ?? new EventsExtra( application );
this._apps.set( application, e );
return e;
}

private constructor( private readonly application: Application ){}

/**
* Execute a function after the option {@link name} has been set.
*
* @param name - The option name to watch.
* @param cb - The function to execute.
* @returns this.
*/
public onSetOption( name: string, cb: ( value: unknown ) => void ){
// eslint-disable-next-line @typescript-eslint/dot-notation -- Private property
this._hookInstanceAfter( this.application.options['_setOptions'] as Set<string>, 'add', ( set, v ) => {
if( v === name ){
cb( this.application.options.getValue( name ) );
}
return set;
} );
return this;
}

/**
* Execute a function just after theme have been set.
*
* @param cb - The function to execute.
* @returns this.
*/
public onThemeReady( cb: () => void ){
this._hookInstanceAfter( this.application.renderer, 'prepareTheme' as any, ( success: boolean ) => {
if( success ){
cb();
}
return success;
} );
return this;
}

/**
* Execute a function just before options freezing.
*
* @param cb - The function to execute.
* @returns this.
*/
public beforeOptionsFreeze( cb: () => void ){
this._hookInstanceBefore( this.application.options, 'freeze', ( ...args ) => {
cb();
return args;
} );
return this;
}

/**
* Replace the method {@link key} of {@link instance} with a method calling the original method, then the custom {@link hook}.
* The original method return value is passed as the 1st parameter of the hook.
*
* @param instance - The instance to bind.
* @param key - The method name.
* @param hook - The function to execute after the original one.
*/
private _hookInstanceAfter<
T extends {}, // eslint-disable-line @typescript-eslint/ban-types -- Inspired from jest `spyOn` types.
K extends MethodKeys<T>,
>( instance: T, key: K, hook: ( initialRet: Ret<T[K]>, ...args: Params<T[K]> ) => Ret<T[K]> ){
const bck = ( instance[key] as T[K] & Fn ).bind( instance ) as T[K] & Fn;
assert( bck );
( instance[key] as any ) = ( ...args: Params<T[K]> ) => {
const ret = bck( ...args );
return hook( ret, ...args );
};
}

/**
* Replace the method {@link key} of {@link instance} with a method calling the the custom {@link hook}, then the original method.
* The hook should return arguments to pass to the original method.
*
* @param instance - The instance to bind.
* @param key - The method name.
* @param hook - The function to execute before the original one.
*/
private _hookInstanceBefore<
T extends {}, // eslint-disable-line @typescript-eslint/ban-types -- Inspired from jest `spyOn` types.
K extends MethodKeys<T>,
>( instance: T, key: K, hook: ( ...args: Params<T[K]> ) => Params<T[K]> ){
const bck = ( instance[key] as T[K] & Fn ).bind( instance ) as T[K] & Fn;
assert( bck );
( instance[key] as any ) = ( ...args: Params<T[K]> ) => {
const newArgs = hook( ...args );
return bck( ...newArgs );
};
}
}
1 change: 1 addition & 0 deletions packages/typedoc-pluginutils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './autoload';
export * from './base-plugin';
export * from './current-page-memo';
export * from './events-extra';
export * from './markdown-replacer';
export * from './options';
export * from './path-reflection-resolver';
Expand Down
Loading

0 comments on commit 83ee577

Please sign in to comment.