Skip to content

Commit

Permalink
feat(pluginutils): mutualize markdown replacement exclusion, modify p…
Browse files Browse the repository at this point in the history
…ackages

Closes #126
  • Loading branch information
GerkinDev committed Jul 24, 2022
1 parent 6c33488 commit e45dd87
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 50 deletions.
2 changes: 1 addition & 1 deletion packages/plugin-code-blocks/pages/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ typedoc --help
* `logLevel`: The plugin log level.\
Type: `LogLevel`\
Default to the application log level.
* `excludeMarkdownTags`: A list of markdown captures to omit. Includes the tag name, but without the leading `@`.\
* `excludeMarkdownTags`: A list of markdown captures to omit. Should have the form `{@....}`.\
Type: `string[]`

> See {@link IPluginOptions}
4 changes: 2 additions & 2 deletions packages/plugin-code-blocks/src/options/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const buildOptions = ( plugin: CodeBlockPlugin ) => OptionGroup.factory<I
defaultValue: plugin.application.logger.level,
} )
.add( 'excludeMarkdownTags', {
help: 'A list of markdown captures to omit. Includes the tag name, but without the leading `@`.',
help: 'A list of markdown captures to omit. Should have the form `{@....}`.',
type: ParameterType.Array,
validate: patterns => patterns?.forEach( p => assert( !p.startsWith( '@' ), `Pattern ${JSON.stringify( p )} should not start with a leading '@'` ) ),
validate: patterns => patterns?.forEach( p => assert.match( p, /^\{@.*\}$/, `Pattern ${JSON.stringify( p )} should match "{@...}"` ) ),
}, v => v ?? [] )
.build();
2 changes: 1 addition & 1 deletion packages/plugin-code-blocks/src/options/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface IPluginOptions {
logLevel: LogLevel;

/**
* A list of markdown captures to omit. Includes the tag name, but without the leading `@`.
* A list of markdown captures to omit. Should have the form `{@....}`.
*/
excludeMarkdownTags?: string[];
}
Expand Down
16 changes: 4 additions & 12 deletions packages/plugin-code-blocks/src/output/markdown-code-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,23 @@ export class MarkdownCodeBlocks implements IPluginComponent<CodeBlockPlugin>{
private readonly _markdownReplacer = new MarkdownReplacer( this );
private readonly _fileSamples = new Map<string, Map<string, ICodeSample>>();
public constructor( public readonly plugin: CodeBlockPlugin, private readonly _themeMethods: ICodeBlocksPluginThemeMethods ){
this._markdownReplacer.registerMarkdownTag( '@codeblock', EXTRACT_CODE_BLOCKS_REGEX, this._replaceCodeBlock.bind( this ) );
this._markdownReplacer.registerMarkdownTag( '@inlineCodeblock', EXTRACT_INLINE_CODE_BLOCKS_REGEX, this._replaceInlineCodeBlock.bind( this ) );
const opts = { excludedMatches: this.plugin.pluginOptions.getValue().excludeMarkdownTags };
this._markdownReplacer.registerMarkdownTag( '@codeblock', EXTRACT_CODE_BLOCKS_REGEX, this._replaceCodeBlock.bind( this ), opts );
this._markdownReplacer.registerMarkdownTag( '@inlineCodeblock', EXTRACT_INLINE_CODE_BLOCKS_REGEX, this._replaceInlineCodeBlock.bind( this ), opts );
this._currentPageMemo.initialize();
}

/**
* Transform the parsed inline code block.
*
* @param match - The match infos.
* @param sourceHint - The best guess to the source of the match,
* @returns the replaced content.
*/
private _replaceInlineCodeBlock( match: MarkdownReplacer.Match, sourceHint: MarkdownReplacer.SourceHint ) {
private _replaceInlineCodeBlock( match: MarkdownReplacer.Match ) {
// Avoid recursion in code blocks
if( this._currentPageMemo.currentReflection instanceof DeclarationReflection && this._currentPageMemo.currentReflection.kind === CODEBLOCK_KIND ){
return;
}
if( ( this.plugin.pluginOptions.getValue().excludeMarkdownTags ?? [] ).includes( match.fullMatch ) ){
this._logger.verbose( () => `Skipping excluded markup ${JSON.stringify( match.fullMatch )} from "${sourceHint()}"` );
return;
}
const [ fileName, blockModeStr, markdownCode ] = match.captures;
assert.ok( fileName );
assert.ok( markdownCode );
Expand All @@ -76,10 +72,6 @@ export class MarkdownCodeBlocks implements IPluginComponent<CodeBlockPlugin>{
if( this._currentPageMemo.currentReflection instanceof DeclarationReflection && this._currentPageMemo.currentReflection.kind === CODEBLOCK_KIND ){
return;
}
if( ( this.plugin.pluginOptions.getValue().excludeMarkdownTags ?? [] ).includes( match.fullMatch ) ){
this._logger.verbose( () => `Skipping excluded markup ${JSON.stringify( match.fullMatch )} from "${sourceHint()}"` );
return;
}
const [ file, block, blockModeStr, fakedFileName ] = match.captures;
try {
const { resolvedFile, fileSample } = this._resolveCodeBlock( file );
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-pages/pages/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedoc --help
* `logLevel`: The plugin log level.\
Type: `LogLevel`\
Default to the application log level.
* `excludeMarkdownTags`: A list of markdown captures to omit. Includes the tag name, but without the leading `@`.\
* `excludeMarkdownTags`: A list of markdown captures to omit. Should have the form `{@....}`.\
Type: `string[]`

> See {@link IPluginOptions}
4 changes: 2 additions & 2 deletions packages/plugin-pages/src/options/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export const buildOptions = ( plugin: PagesPlugin ) => OptionGroup.factory<IPlug
defaultValue: plugin.application.logger.level,
} )
.add( 'excludeMarkdownTags', {
help: 'A list of markdown captures to omit. Includes the tag name, but without the leading `@`.',
help: 'A list of markdown captures to omit. Should have the form `{@....}`.',
type: ParameterType.Array,
validate: patterns => patterns?.forEach( p => assert( !p.startsWith( '@' ), `Pattern ${JSON.stringify( p )} should not start with a leading '@'` ) ),
validate: patterns => patterns?.forEach( p => assert.match( p, /^\{@.*\}$/, `Pattern ${JSON.stringify( p )} should match "{@...}"` ) ),
}, v => v ?? [] )
.build();
2 changes: 1 addition & 1 deletion packages/plugin-pages/src/options/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export interface IPluginOptions {
logLevel: LogLevel;

/**
* A list of markdown captures to omit. Includes the tag name, but without the leading `@`.
* A list of markdown captures to omit. Should have the form `{@....}`.
*/
excludeMarkdownTags?: string[];
}
Expand Down
10 changes: 4 additions & 6 deletions packages/plugin-pages/src/output/markdown-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class MarkdownPagesLinks implements IPluginComponent<PagesPlugin> {
const nodeReflections = event.project.getReflectionsByKind( PagesPluginReflectionKind.PAGE as any );
assert( nodeReflections.every( ( v ): v is PageReflection => v instanceof PageReflection ) );
this._nodesReflections = nodeReflections;
this._markdownReplacer.registerMarkdownTag( '@page', EXTRACT_PAGE_LINK_REGEX, this._replacePageLink.bind( this ) );
this._markdownReplacer.registerMarkdownTag( '@page', EXTRACT_PAGE_LINK_REGEX, this._replacePageLink.bind( this ), {
excludedMatches: this.plugin.pluginOptions.getValue().excludeMarkdownTags,
} );
this._currentPageMemo.initialize();
}

Expand All @@ -32,13 +34,9 @@ class MarkdownPagesLinks implements IPluginComponent<PagesPlugin> {
* @returns the replaced content.
*/
private _replacePageLink(
{ captures, fullMatch }: Parameters<MarkdownReplacer.ReplaceCallback>[0],
{ captures }: Parameters<MarkdownReplacer.ReplaceCallback>[0],
sourceHint: Parameters<MarkdownReplacer.ReplaceCallback>[1],
): ReturnType<MarkdownReplacer.ReplaceCallback> {
if( ( this.plugin.pluginOptions.getValue().excludeMarkdownTags ?? [] ).includes( fullMatch ) ){
this._logger.verbose( () => `Skipping excluded markup ${JSON.stringify( fullMatch )} from "${sourceHint()}"` );
return;
}
const [ page, label ] = captures;

const targetPage = this._resolvePageLink( page );
Expand Down
54 changes: 36 additions & 18 deletions packages/pluginutils/src/text-replacers/markdown-replacer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,44 @@ describe( MarkdownReplacer.name, () => {
plugin = new TestPlugin();
replacer = new MarkdownReplacer( plugin );
} );
describe( 'Inline tag', () => {
it( 'should replace correctly inline tag in markdown event', () =>{
// Arrange
mockCurrentPage( 'Test', resolve( 'hello.ts' ), 1, 1 );
const fn = jest.fn().mockReturnValueOnce( 'REPLACE 1' ).mockReturnValueOnce( 'REPLACE 2' );
replacer.registerMarkdownTag( '@test', /(foo)?/g, fn );
const source = 'Hello {@test foo} {@test} @test';
const event = new MarkdownEvent( MarkdownEvent.PARSE, source, source );
const listeners = getMarkdownEventParseListeners( plugin );
expect( listeners ).toHaveLength( 1 );
it( 'should replace correctly inline tag in markdown event', () =>{
// Arrange
mockCurrentPage( 'Test', resolve( 'hello.ts' ), 1, 1 );
const fn = jest.fn().mockReturnValueOnce( 'REPLACE 1' ).mockReturnValueOnce( 'REPLACE 2' );
replacer.registerMarkdownTag( '@test', /(foo)?/g, fn );
const source = 'Hello {@test foo} {@test} @test';
const event = new MarkdownEvent( MarkdownEvent.PARSE, source, source );
const listeners = getMarkdownEventParseListeners( plugin );
expect( listeners ).toHaveLength( 1 );

// Act
listeners[0]( event );
// Act
listeners[0]( event );

// Assert
expect( event.parsedText ).toEqual( 'Hello REPLACE 1 REPLACE 2 @test' );
expect( fn ).toHaveBeenCalledTimes( 2 );
expect( fn ).toHaveBeenNthCalledWith( 1, { captures: [ 'foo' ], fullMatch: 'test foo', event }, expect.toBeFunction() );
expect( fn ).toHaveBeenNthCalledWith( 2, { captures: [ undefined ], fullMatch: 'test', event }, expect.toBeFunction() );
} );
// Assert
expect( event.parsedText ).toEqual( 'Hello REPLACE 1 REPLACE 2 @test' );
expect( fn ).toHaveBeenCalledTimes( 2 );
expect( fn ).toHaveBeenNthCalledWith( 1, { captures: [ 'foo' ], fullMatch: 'test foo', event }, expect.toBeFunction() );
expect( fn ).toHaveBeenNthCalledWith( 2, { captures: [ undefined ], fullMatch: 'test', event }, expect.toBeFunction() );
} );
it( 'should ignore excluded matches', () =>{
// Arrange
mockCurrentPage( 'Test', resolve( 'hello.ts' ), 1, 1 );
const fn = jest.fn().mockReturnValueOnce( '1' ).mockReturnValueOnce( '2' ).mockReturnValueOnce( '3' );
replacer.registerMarkdownTag( '@test', /(foo\d?)?/g, fn, { excludedMatches: [ '{@test foo1}', '{@test foo4}' ] } );
const source = 'Hello {@test} {@test foo1} {@test foo2} {@test foo3} {@test foo4} @test';
const event = new MarkdownEvent( MarkdownEvent.PARSE, source, source );
const listeners = getMarkdownEventParseListeners( plugin );
expect( listeners ).toHaveLength( 1 );

// Act
listeners[0]( event );

// Assert
expect( event.parsedText ).toEqual( 'Hello 1 {@test foo1} 2 3 {@test foo4} @test' );
expect( fn ).toHaveBeenCalledTimes( 3 );
expect( fn ).toHaveBeenNthCalledWith( 1, { captures: [], fullMatch: 'test', event }, expect.toBeFunction() );
expect( fn ).toHaveBeenNthCalledWith( 2, { captures: [ 'foo2' ], fullMatch: 'test foo2', event }, expect.toBeFunction() );
expect( fn ).toHaveBeenNthCalledWith( 3, { captures: [ 'foo3' ], fullMatch: 'test foo3', event }, expect.toBeFunction() );
} );
describe( 'Source map', () => {
describe( 'Once', () => {
Expand Down
35 changes: 30 additions & 5 deletions packages/pluginutils/src/text-replacers/markdown-replacer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,30 @@ export class MarkdownReplacer implements IPluginComponent {
* @param tagName - The name of the tag to match.
* @param paramsRegExp - An optional regex to capture params.
* @param callback - The callback to execute to replace the match.
* @param options - Extra options.
*/
public registerMarkdownTag( tagName: Tag, paramsRegExp: RegExp | null, callback: MarkdownReplacer.ReplaceCallback ){
public registerMarkdownTag( tagName: Tag, paramsRegExp: RegExp | null, callback: MarkdownReplacer.ReplaceCallback, options: MarkdownReplacer.IRegisterOptions = {} ){
const mdRegexBase = buildMarkdownRegExp( tagName, paramsRegExp );
const tagRegex = new RegExp( `\\{${mdRegexBase.source}\\s*?\\}`, mdRegexBase.flags );
this._currentPageMemo.initialize();
this.plugin.application.renderer.on( MarkdownEvent.PARSE, this._processMarkdown.bind( this, tagRegex, ( { fullMatch, captures, event }, sourceHint ) => {
const newFullMatch = fullMatch.slice( 2 ).slice( 0, -1 );
return callback( { fullMatch: newFullMatch, captures, event }, sourceHint );
}, tagName ), undefined, 100 );
const { excludedMatches, priority } = {
excludedMatches: [],
priority: 100,
...options,
};
this.plugin.application.renderer.on(
MarkdownEvent.PARSE,
this._processMarkdown.bind(
this,
tagRegex,
( { fullMatch, captures, event }, sourceHint ) => {
const newFullMatch = fullMatch.slice( 2 ).slice( 0, -1 );
return callback( { fullMatch: newFullMatch, captures, event }, sourceHint );
},
tagName,
excludedMatches ),
undefined,
priority );
}


Expand All @@ -97,12 +112,14 @@ export class MarkdownReplacer implements IPluginComponent {
* @param regex - The regex to match.
* @param callback - The callback to execute with fullMatch, captures, & a source hint.
* @param label - The replacer name.
* @param excludeMatches - A list of matches to skip.
* @param event - The event to modify.
*/
private _processMarkdown(
regex: RegExp,
callback: MarkdownReplacer.ReplaceCallback,
label: string,
excludeMatches: string[] | undefined,
event: MarkdownEvent,
) {
const mapContainers = MarkdownReplacer._getEventMapContainers( event );
Expand All @@ -117,6 +134,9 @@ export class MarkdownReplacer implements IPluginComponent {
regex,
( ...args ) => {
const { captures, fullMatch, index } = spitArgs( ...args );
if( excludeMatches?.includes( fullMatch ) ){
return fullMatch;
}
const getSourceHint = () => {
if( !relativeSource ){
return 'UNKNOWN SOURCE';
Expand Down Expand Up @@ -190,4 +210,9 @@ export namespace MarkdownReplacer {
event: MarkdownEvent;
}
export type ReplaceCallback = ( match: Match, sourceHint: SourceHint ) => string | JSX.Element | undefined;

export interface IRegisterOptions {
excludedMatches?: string[];
priority?: number;
}
}
2 changes: 1 addition & 1 deletion typedoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ module.exports = {
// #region pagesConfig-3
],
// #endregion
excludeMarkdownTags: [ 'page <path-to-file>[ link label]', 'page ...' ],
excludeMarkdownTags: [ '{@page <path-to-file>[ link label]}', '{@page ...}' ],
// #region pagesConfig-4
},
// #endregion
Expand Down

0 comments on commit e45dd87

Please sign in to comment.