Skip to content

Commit

Permalink
feat(plugin-code-blocks): add @inline-codeblock macro
Browse files Browse the repository at this point in the history
  • Loading branch information
GerkinDev committed Mar 4, 2022
1 parent 314f173 commit 6d5dff4
Show file tree
Hide file tree
Showing 19 changed files with 172 additions and 122 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Expand Up @@ -23,19 +23,19 @@ describe( 'Real behavior', () => {

const pkgA = await readFile( resolve( rootDir, 'docs/modules/pkg_a.html' ), 'utf-8' );
const domA = new JSDOM( pkgA );
checkDef( domA, 'testInProjA', formatExpanded( './packages/a/blocks/test.json', testJsonA ) );
checkDef( domA, 'testInProjB', formatExpanded( './packages/b/blocks/test.json', testJsonB ) );
checkDef( domA, 'testNoPrefixImplicitInBlocks', formatExpanded( './packages/a/blocks/test.json', testJsonA ) );
checkDef( domA, 'testNoPrefixInBlocks', formatExpanded( './packages/a/blocks/test.json', testJsonA ) );
checkDef( domA, 'testInProjA', formatExpanded( './packages/a/examples/test.json', testJsonA ) );
checkDef( domA, 'testInProjB', formatExpanded( './packages/b/examples/test.json', testJsonB ) );
checkDef( domA, 'testNoPrefixImplicitInExamples', formatExpanded( './packages/a/examples/test.json', testJsonA ) );
checkDef( domA, 'testNoPrefixInExamples', formatExpanded( './packages/a/examples/test.json', testJsonA ) );
expect( pkgA ).toMatch( /<link\s+rel="stylesheet"\s+href="([^"]*?\/)?assets\/code-blocks\.css"\s*\/>/ );
expect( formatHtml( pkgA ) ).toMatchSnapshot();

const pkgB = await readFile( resolve( rootDir, 'docs/modules/pkg_b.html' ), 'utf-8' );
const domB = new JSDOM( pkgB );
checkDef( domB, 'testInProjA', formatExpanded( './packages/a/blocks/test.json', testJsonA ) );
checkDef( domB, 'testInProjB', formatExpanded( './packages/b/blocks/test.json', testJsonB ) );
checkDef( domB, 'testNoPrefixImplicitInBlocks', formatExpanded( './packages/b/blocks/test.json', testJsonB ) );
checkDef( domB, 'testNoPrefixInBlocks', formatExpanded( './packages/b/blocks/test.json', testJsonB ) );
checkDef( domB, 'testInProjA', formatExpanded( './packages/a/examples/test.json', testJsonA ) );
checkDef( domB, 'testInProjB', formatExpanded( './packages/b/examples/test.json', testJsonB ) );
checkDef( domB, 'testNoPrefixImplicitInExamples', formatExpanded( './packages/b/examples/test.json', testJsonB ) );
checkDef( domB, 'testNoPrefixInExamples', formatExpanded( './packages/b/examples/test.json', testJsonB ) );
expect( pkgB ).toMatch( /<link\s+rel="stylesheet"\s+href="([^"]*?\/)?assets\/code-blocks\.css"\s*\/>/ );
expect( formatHtml( pkgB ) ).toMatchSnapshot();
} );
Expand Down
Expand Up @@ -21,10 +21,10 @@ describe( 'Real behavior', () => {
'<span class="hl-0">{</span><span class="hl-1">"Hello"</span><span class="hl-0">: </span><span class="hl-2">"World"</span><span class="hl-0">}</span>\n'+
'</code></pre>';
const testJsonFooBar = testJson.replace( 'Hello', 'Foo' ).replace( 'World', 'Bar' );
checkDef( dom, 'testProjImplicitInBlocks', formatExpanded( './blocks/test.json', testJson ) );
checkDef( dom, 'testProjInBlocks', formatExpanded( './blocks/test.json', testJson ) );
checkDef( dom, 'testNoPrefixImplicitInBlocks', formatExpanded( './blocks/test.json', testJson ) );
checkDef( dom, 'testNoPrefixInBlocks', formatExpanded( './blocks/test.json', testJson ) );
checkDef( dom, 'testProjImplicitInExamples', formatExpanded( './examples/test.json', testJson ) );
checkDef( dom, 'testProjInExamples', formatExpanded( './examples/test.json', testJson ) );
checkDef( dom, 'testNoPrefixImplicitInExamples', formatExpanded( './examples/test.json', testJson ) );
checkDef( dom, 'testNoPrefixInExamples', formatExpanded( './examples/test.json', testJson ) );
checkDef( dom, 'testRel', formatExpanded( './src/test.json', testJsonFooBar ) );
expect( c ).toMatch( /<link\s+rel="stylesheet"\s+href="([^"]*?\/)?assets\/code-blocks\.css"\s*\/>/ );
expect( formatHtml( c ) ).toMatchSnapshot();
Expand Down
Expand Up @@ -18,16 +18,16 @@ export const testInProjB = stub;

// #region inPackage
/**
* A test code block for unprefixed path implicitly in blocks directory
* A test code block for unprefixed path implicitly in `examples` directory
*
* {@codeblock test.json}
*/
export const testNoPrefixImplicitInBlocks = stub;
export const testNoPrefixImplicitInExamples = stub;

/**
* A test code block for unprefixed path in blocks directory
* A test code block for unprefixed path in `examples` directory
*
* {@codeblock blocks/test.json}
* {@codeblock examples/test.json}
*/
export const testNoPrefixInBlocks = stub;
export const testNoPrefixInExamples = stub;
// #endregion
Expand Up @@ -18,16 +18,16 @@ export const testInProjB = stub;

// #region inPackage
/**
* A test code block for unprefixed path implicitly in blocks directory
* A test code block for unprefixed path implicitly in `examples` directory
*
* {@codeblock test.json}
*/
export const testNoPrefixImplicitInBlocks = stub;
export const testNoPrefixImplicitInExamples = stub;

/**
* A test code block for unprefixed path in blocks directory
* A test code block for unprefixed path in `examples` directory
*
* {@codeblock blocks/test.json}
* {@codeblock examples/test.json}
*/
export const testNoPrefixInBlocks = stub;
export const testNoPrefixInExamples = stub;
// #endregion
@@ -1,7 +1,6 @@
module.exports = {
'entryPoints': [
entryPoints: [
'packages/*',
],
'entryPointStrategy': 'packages',
'pluginCodeBlocks:source': 'blocks',
entryPointStrategy: 'packages',
};

This file was deleted.

26 changes: 13 additions & 13 deletions packages/plugin-code-blocks/__tests__/mock-fs/simple/src/test.ts
Expand Up @@ -9,32 +9,32 @@ const stub = () => 1;
export const testRel = stub;
// #endregion

// #region projPath
/**
* A test code block for unprefixed path implicitly in blocks directory
* A test code block for unprefixed path implicitly in `examples` directory
*
* {@codeblock test.json}
*/
export const testNoPrefixImplicitInBlocks = stub;
export const testNoPrefixImplicitInExamples = stub;

/**
* A test code block for unprefixed path in blocks directory
* A test code block for unprefixed path in `examples` directory
*
* {@codeblock blocks/test.json}
* {@codeblock examples/test.json}
*/
export const testNoPrefixInBlocks = stub;
export const testNoPrefixInExamples = stub;

// #region projPath
/**
* A test code block for project path in blocks directory
* A test code block for project path implicitly in `examples` directory
*
* {@codeblock ~~/blocks/test.json}
* {@codeblock ~~/test.json}
*/
export const testProjInBlocks = stub;
export const testProjImplicitInExamples = stub;
// #endregion

/**
* A test code block for project path implicitly in blocks directory
* A test code block for project path in `examples` directory
*
* {@codeblock ~~/test.json}
* {@codeblock ~~/examples/test.json}
*/
export const testProjImplicitInBlocks = stub;
// #endregion
export const testProjInExamples = stub;
@@ -1,6 +1,5 @@
module.exports = {
'entryPoints': [
entryPoints: [
'./src/test.ts',
],
'pluginCodeBlocks:source': 'blocks',
};
Expand Up @@ -5,7 +5,7 @@ import { DefaultTheme, JSX, PageEvent, RendererEvent } from 'typedoc';

import type { CodeBlockPlugin } from '../plugin';
import { ICodeBlocksPluginThemeMethods } from '../theme';
import { EBlockMode, ICodeBlock } from '../types';
import { EBlockMode, ICodeBlock, IInlineCodeBlock } from '../types';

const CSS_FILE_NAME = 'assets/code-blocks.css';
export class DefaultCodeBlockRenderer implements ICodeBlocksPluginThemeMethods {
Expand All @@ -16,17 +16,23 @@ export class DefaultCodeBlockRenderer implements ICodeBlocksPluginThemeMethods {
renderer.hooks.on( 'head.end', context => <link rel="stylesheet" href={context.relativeURL( CSS_FILE_NAME )} /> );
}

public readonly renderCodeBlock = ( { asFile, sourceFile, mode, content, url }: ICodeBlock ) => {
const header = <p>
From {url ? <a href={url}>{asFile}</a> : <>{asFile}</>}
</p>;
public readonly renderInlineCodeBlock = ( { fileName, markdownCode, mode }: IInlineCodeBlock ) => this._wrapCode(
<>From {fileName}</>,
this.theme.getRenderContext( new PageEvent( fileName ) ).markdown( markdownCode ),
mode,
);

content = content.replace( /\\/g, '\\\\' ).replace( /`/g, '\\`' );
content = `\`\`\`${extname( sourceFile ).slice( 1 )}
${content}
\`\`\``;
const code = <JSX.Raw html={this.theme.getRenderContext( new PageEvent( asFile ) ).markdown( content )}></JSX.Raw>;
public readonly renderCodeBlock = ( { asFile, sourceFile, mode, content, url }: ICodeBlock ) => this._wrapCode(
<>From {url ? <a href={url}>{asFile}</a> : <>{asFile}</>}</>,
this.theme.getRenderContext( new PageEvent( asFile ) ).markdown( `\`\`\`${extname( sourceFile ).slice( 1 )}
${content.replace( /\\/g, '\\\\' ).replace( /`/g, '\\`' )}
\`\`\`` ),
mode,
);

private readonly _wrapCode = ( header: string | JSX.Element, codeHighlighted: string, mode: EBlockMode ) => {
header = <p>{header}</p>;
const code = <JSX.Raw html={codeHighlighted}></JSX.Raw>;
switch( mode ){
case EBlockMode.DEFAULT: {
return <div class="code-block">
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-code-blocks/src/index.ts
@@ -1,4 +1,4 @@
export { load } from './load';
export { ICodeBlocksPluginTheme, ICodeBlocksPluginThemeMethods } from './theme';
export { EInvalidBlockLinkHandling } from './options';
export { EBlockMode, ICodeBlock } from './types';
export { EBlockMode, ICodeBlock, IInlineCodeBlock } from './types';
5 changes: 3 additions & 2 deletions packages/plugin-code-blocks/src/plugin.spec.ts
Expand Up @@ -14,7 +14,7 @@ const { DEFAULT_BLOCK_NAME, readCodeSample: readCodeSampleMock } = require( './c
/* eslint-enable @typescript-eslint/no-var-requires */

import { CodeBlockPlugin } from './plugin';
import { EBlockMode, ICodeBlock } from './types';
import { EBlockMode, ICodeBlock, IInlineCodeBlock } from './types';

class FakeGitHub {
public static readonly REPO_URL = 'https://example.repo.com';
Expand Down Expand Up @@ -58,7 +58,8 @@ describe( 'Behavior', () => {
const uuid = new Date().toISOString();
const elem = factory( uuid );
const renderCodeBlock = jest.fn<JSX.Element, [ICodeBlock]>().mockReturnValue( elem );
getCodeBlockRendererMock.mockReturnValue( { renderCodeBlock } );
const renderInlineCodeBlock = jest.fn<JSX.Element, [IInlineCodeBlock]>().mockReturnValue( elem );
getCodeBlockRendererMock.mockReturnValue( { renderCodeBlock, renderInlineCodeBlock } );
return { renderCodeBlock, elem, elemStr: JSX.renderElement( elem ) };
};
const sourceFile = resolve( rootDir, file );
Expand Down
46 changes: 43 additions & 3 deletions packages/plugin-code-blocks/src/plugin.ts
@@ -1,7 +1,7 @@
import assert from 'assert';
import { relative } from 'path';

import { once } from 'lodash';
import { escapeRegExp, isString, once } from 'lodash';
import { Application, JSX, LogLevel, PageEvent, Reflection } from 'typedoc';

import { ABasePlugin, CurrentPageMemo, EventsExtra, MarkdownReplacer, PathReflectionResolver } from '@knodes/typedoc-pluginutils';
Expand All @@ -12,7 +12,8 @@ import { buildOptions } from './options';
import { CodeBlockReflection } from './reflections';
import { EBlockMode } from './types';

const EXTRACT_CODE_BLOCKS_REGEX = /\{@codeblock\s+(\S+?\w+?)(?:#(.+?))?(?:\s+(\w+))?(?:\s*\|\s*(.*?))?\}/g;
const EXTRACT_CODE_BLOCKS_REGEX = /\{\\?@codeblock\s+(\S+?\w+?)(?:#(.+?))?(?:\s+(\w+))?(?:\s*\|\s*(.*?))?\s*\}/g;
const EXTRACT_INLINE_CODE_BLOCKS_REGEX = /\{\\?@inline-codeblock\s+(\S+?\w+?)(?:\s+(\w+))?\s*}\n(\s*)(```.*?```)/gs;
/**
* Pages plugin for integrating your own pages into documentation output
*/
Expand All @@ -35,7 +36,8 @@ export class CodeBlockPlugin extends ABasePlugin {
*/
public initialize(): void {
// Hook over each markdown events to replace code blocks
this._markdownReplacer.bindReplace( EXTRACT_CODE_BLOCKS_REGEX, this._replaceCodeBlock.bind( this ), 'replace code blocks' );
this._markdownReplacer.bindReplace( EXTRACT_CODE_BLOCKS_REGEX, this._replaceCodeBlock.bind( this ), '{@codeblock}' );
this._markdownReplacer.bindReplace( EXTRACT_INLINE_CODE_BLOCKS_REGEX, this._replaceInlineCodeBlock.bind( this ), '{@inline-codeblock}' );
this._currentPageMemo.initialize();
EventsExtra.for( this.application )
.onThemeReady( this._codeBlockRenderer.bind( this ) )
Expand All @@ -44,6 +46,44 @@ export class CodeBlockPlugin extends ABasePlugin {
} );
}

/**
* Transform the parsed inline code block.
*
* @param capture - The captured infos.
* @param sourceHint - The best guess to the source of the match,
* @returns the replaced content.
*/
private _replaceInlineCodeBlock(
{ captures, fullMatch }: Parameters<MarkdownReplacer.ReplaceCallback>[0],
sourceHint: Parameters<MarkdownReplacer.ReplaceCallback>[1],
): ReturnType<MarkdownReplacer.ReplaceCallback> {
// Support escaped tags
if( fullMatch.startsWith( '{\\@' ) ){
this.logger.verbose( () => `Found an escaped tag "${fullMatch}" in "${sourceHint()}"` );
return fullMatch.replace( '{\\@', '{@' );
}
// Extract informations
const [ fileName, blockModeStr, blockIndent, markdownCodeSource ] = captures;
const blockMode = blockModeStr ?
EBlockMode[blockModeStr.toUpperCase() as keyof typeof EBlockMode] ?? assert.fail( `Invalid block mode "${blockModeStr}".` ) :
this.pluginOptions.getValue().defaultBlockMode ?? EBlockMode.EXPANDED;
assert.ok( fileName );
assert.ok( markdownCodeSource );
assert.ok( isString( blockIndent ) );
const markdownCode = markdownCodeSource.replace( new RegExp( `^${escapeRegExp( blockIndent )}`, 'gm' ), '' );

// Render
const rendered = this._codeBlockRenderer().renderInlineCodeBlock( {
fileName,
markdownCode,
mode: blockMode,
} );
if( typeof rendered === 'string' ){
return rendered;
} else {
return JSX.renderElement( rendered );
}
}

/**
* Transform the parsed code block.
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-code-blocks/src/theme.ts
@@ -1,9 +1,10 @@
import { RenderTemplate, Theme } from 'typedoc';

import { ICodeBlock } from './types';
import { ICodeBlock, IInlineCodeBlock } from './types';

export interface ICodeBlocksPluginThemeMethods {
renderCodeBlock: RenderTemplate<ICodeBlock>;
renderInlineCodeBlock: RenderTemplate<IInlineCodeBlock>;
}
export interface ICodeBlocksPluginTheme extends Theme {
codeBlocksPlugin: ICodeBlocksPluginThemeMethods;
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-code-blocks/src/types.ts
Expand Up @@ -10,3 +10,8 @@ export interface ICodeBlock {
content: string;
url?: string;
}
export interface IInlineCodeBlock {
mode: EBlockMode;
fileName: string;
markdownCode: string;
}

0 comments on commit 6d5dff4

Please sign in to comment.