Skip to content

Commit

Permalink
docs: check api references (#2024)
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT authored Apr 20, 2023
1 parent 2098b4d commit a001ac5
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 198 deletions.
2 changes: 1 addition & 1 deletion scripts/apidoc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generate } from './apidoc/generate';
import { initMarkdownRenderer } from './apidoc/signature';
import { initMarkdownRenderer } from './apidoc/markdown';

async function build(): Promise<void> {
await initMarkdownRenderer();
Expand Down
6 changes: 3 additions & 3 deletions scripts/apidoc/fakerClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { ReflectionKind } from 'typedoc';
import type { Method } from '../../docs/.vitepress/components/api-docs/method';
import { writeApiDocsModule } from './apiDocsWriter';
import { processModuleMethods } from './moduleMethods';
import { analyzeSignature, toBlock } from './signature';
import { selectApiSignature } from './typedoc';
import { analyzeSignature } from './signature';
import { extractDescription, selectApiSignature } from './typedoc';
import type { ModuleSummary } from './utils';

export function processFakerClass(project: ProjectReflection): ModuleSummary {
Expand All @@ -21,7 +21,7 @@ export function processFakerClass(project: ProjectReflection): ModuleSummary {

function processClass(fakerClass: DeclarationReflection): ModuleSummary {
console.log(`Processing Faker class`);
const comment = toBlock(fakerClass.comment);
const comment = extractDescription(fakerClass);
const methods: Method[] = [];

console.debug(`- constructor`);
Expand Down
68 changes: 68 additions & 0 deletions scripts/apidoc/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import sanitizeHtml from 'sanitize-html';
import type { MarkdownRenderer } from 'vitepress';
import { createMarkdownRenderer } from 'vitepress';
import vitepressConfig from '../../docs/.vitepress/config';
import { pathOutputDir } from './utils';

let markdown: MarkdownRenderer;

export async function initMarkdownRenderer(): Promise<void> {
markdown = await createMarkdownRenderer(
pathOutputDir,
vitepressConfig.markdown,
'/'
);
}

const htmlSanitizeOptions: sanitizeHtml.IOptions = {
allowedTags: [
'a',
'button',
'code',
'div',
'li',
'p',
'pre',
'span',
'strong',
'ul',
],
allowedAttributes: {
a: ['href', 'target', 'rel'],
button: ['class', 'title'],
div: ['class'],
pre: ['class', 'tabindex', 'v-pre'],
span: ['class', 'style'],
},
selfClosing: [],
};

function comparableSanitizedHtml(html: string): string {
return html
.replace(/&gt;/g, '>')
.replace(/ /g, '')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
}

/**
* Converts Markdown to an HTML string and sanitizes it.
* @param md The markdown to convert.
* @param inline Whether to render the markdown as inline, without a wrapping `<p>` tag. Defaults to `false`.
* @returns The converted HTML string.
*/
export function mdToHtml(md: string, inline: boolean = false): string {
const rawHtml = inline ? markdown.renderInline(md) : markdown.render(md);

const safeHtml: string = sanitizeHtml(rawHtml, htmlSanitizeOptions);
// Revert some escaped characters for comparison.
if (comparableSanitizedHtml(rawHtml) === comparableSanitizedHtml(safeHtml)) {
return safeHtml.replace(/https:\/\/(next.)?fakerjs.dev\//g, '/');
}

console.debug('Rejected unsafe md:', md);
console.error('Rejected unsafe html:', rawHtml);
console.error('Rejected unsafe html:', comparableSanitizedHtml(rawHtml));
console.error('Expected safe html:', comparableSanitizedHtml(safeHtml));
throw new Error('Found unsafe html');
}
5 changes: 3 additions & 2 deletions scripts/apidoc/moduleMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import type {
} from 'typedoc';
import type { Method } from '../../docs/.vitepress/components/api-docs/method';
import { writeApiDocsModule } from './apiDocsWriter';
import { analyzeSignature, stripAbsoluteFakerUrls, toBlock } from './signature';
import { analyzeSignature } from './signature';
import {
extractDeprecated,
extractDescription,
extractModuleFieldName,
extractModuleName,
selectApiMethodSignatures,
Expand Down Expand Up @@ -35,7 +36,7 @@ function processModule(module: DeclarationReflection): ModuleSummary {
const moduleName = extractModuleName(module);
const moduleFieldName = extractModuleFieldName(module);
console.log(`Processing Module ${moduleName}`);
const comment = stripAbsoluteFakerUrls(toBlock(module.comment));
const comment = extractDescription(module);
const deprecated = extractDeprecated(module);
const methods = processModuleMethods(module, `faker.${moduleFieldName}.`);

Expand Down
87 changes: 6 additions & 81 deletions scripts/apidoc/signature.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sanitizeHtml from 'sanitize-html';
import type {
Comment,
DeclarationReflection,
Expand All @@ -10,101 +9,27 @@ import type {
Type,
} from 'typedoc';
import { ReflectionFlag, ReflectionKind } from 'typedoc';
import type { MarkdownRenderer } from 'vitepress';
import { createMarkdownRenderer } from 'vitepress';
import type {
Method,
MethodParameter,
} from '../../docs/.vitepress/components/api-docs/method';
import vitepressConfig from '../../docs/.vitepress/config';
import { formatTypescript } from './format';
import { mdToHtml } from './markdown';
import {
extractDeprecated,
extractDescription,
extractRawDefault,
extractRawExamples,
extractSeeAlsos,
extractSince,
extractSourcePath,
extractThrows,
joinTagParts,
toBlock,
} from './typedoc';
import { pathOutputDir } from './utils';

const code = '```';

export const MISSING_DESCRIPTION = 'Missing';

export function toBlock(comment?: Comment): string {
return joinTagParts(comment?.summary) || MISSING_DESCRIPTION;
}

export function stripAbsoluteFakerUrls(markdown: string): string {
return markdown.replace(/https:\/\/(next.)?fakerjs.dev\//g, '/');
}

let markdown: MarkdownRenderer;

export async function initMarkdownRenderer(): Promise<void> {
markdown = await createMarkdownRenderer(
pathOutputDir,
vitepressConfig.markdown,
'/'
);
}

const htmlSanitizeOptions: sanitizeHtml.IOptions = {
allowedTags: [
'a',
'button',
'code',
'div',
'li',
'p',
'pre',
'span',
'strong',
'ul',
],
allowedAttributes: {
a: ['href', 'target', 'rel'],
button: ['class', 'title'],
div: ['class'],
pre: ['class', 'tabindex', 'v-pre'],
span: ['class', 'style'],
},
selfClosing: [],
};

function comparableSanitizedHtml(html: string): string {
return html
.replace(/&gt;/g, '>')
.replace(/ /g, '')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
}

/**
* Converts Markdown to an HTML string and sanitizes it.
* @param md The markdown to convert.
* @param inline Whether to render the markdown as inline, without a wrapping `<p>` tag. Defaults to `false`.
* @returns The converted HTML string.
*/
function mdToHtml(md: string, inline: boolean = false): string {
const rawHtml = inline ? markdown.renderInline(md) : markdown.render(md);

const safeHtml: string = sanitizeHtml(rawHtml, htmlSanitizeOptions);
// Revert some escaped characters for comparison.
if (comparableSanitizedHtml(rawHtml) === comparableSanitizedHtml(safeHtml)) {
return safeHtml;
}

console.debug('Rejected unsafe md:', md);
console.error('Rejected unsafe html:', rawHtml);
console.error('Rejected unsafe html:', comparableSanitizedHtml(rawHtml));
console.error('Expected safe html:', comparableSanitizedHtml(safeHtml));
throw new Error('Found unsafe html');
}

export function analyzeSignature(
signature: SignatureReflection,
accessor: string,
Expand All @@ -120,7 +45,7 @@ export function analyzeSignature(
parameters.push({
name: `<${parameter.name}>`,
type: parameter.type ? typeToText(parameter.type) : undefined,
description: mdToHtml(toBlock(parameter.comment)),
description: mdToHtml(extractDescription(parameter)),
});
}

Expand Down Expand Up @@ -166,7 +91,7 @@ export function analyzeSignature(

return {
name: methodName,
description: mdToHtml(toBlock(signature.comment)),
description: mdToHtml(extractDescription(signature)),
parameters: parameters,
since: extractSince(signature),
sourcePath: extractSourcePath(signature),
Expand Down Expand Up @@ -200,7 +125,7 @@ function analyzeParameter(parameter: ParameterReflection): {
name: declarationName,
type: typeToText(type, true),
default: defaultValue,
description: mdToHtml(toBlock(parameter.comment)),
description: mdToHtml(extractDescription(parameter)),
},
];
parameters.push(...analyzeParameterOptions(name, type));
Expand Down
11 changes: 11 additions & 0 deletions scripts/apidoc/typedoc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
Comment,
CommentDisplayPart,
CommentTag,
DeclarationReflection,
Expand Down Expand Up @@ -149,6 +150,16 @@ export function extractModuleFieldName(module: DeclarationReflection): string {
return moduleName.substring(0, 1).toLowerCase() + moduleName.substring(1);
}

export const MISSING_DESCRIPTION = 'Missing';

export function toBlock(comment?: Comment): string {
return joinTagParts(comment?.summary) || MISSING_DESCRIPTION;
}

export function extractDescription(reflection: Reflection): string {
return toBlock(reflection.comment);
}

/**
* Extracts the source url from the jsdocs.
*
Expand Down
2 changes: 1 addition & 1 deletion src/modules/image/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Unsplash } from './providers/unsplash';
*
* ### Overview
*
* For a random image, use [`url()`](https://next.fakerjs.dev/api/image.html#url). This will not return the image directly but a URL pointing to an image from one of two demo image providers "Picsum" and "LoremFlickr". You can request an image specifically from one of two providers using [`urlLoremFlickr()`](https://next.fakerjs.dev/api/image.html#urlloremflickr) or [`urlPicsum()`](https://next.fakerjs.dev/api/image.html#urlpicsum).
* For a random image, use [`url()`](https://next.fakerjs.dev/api/image.html#url). This will not return the image directly but a URL pointing to an image from one of two demo image providers "Picsum" and "LoremFlickr". You can request an image specifically from one of two providers using [`urlLoremFlickr()`](https://next.fakerjs.dev/api/image.html#urlloremflickr) or [`urlPicsumPhotos()`](https://next.fakerjs.dev/api/image.html#urlpicsumphotos).
*
* For a random placeholder image containing only solid color and text, use [`urlPlaceholder()`](https://next.fakerjs.dev/api/image.html#urlplaceholder) (uses a third-party service) or [`dataUri()`](https://next.fakerjs.dev/api/image.html#datauri) (returns a SVG string).
*
Expand Down
6 changes: 2 additions & 4 deletions test/scripts/apidoc/signature.debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
* This file exists, because vitest doesn't allow me to debug code outside of src and test.
* And it's easier to test these features independently from the main project.
*/
import {
analyzeSignature,
initMarkdownRenderer,
} from '../../../scripts/apidoc/signature';
import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown';
import { analyzeSignature } from '../../../scripts/apidoc/signature';
import { loadExampleMethods } from './utils';

/* Run with `pnpm tsx test/scripts/apidoc/signature.debug.ts` */
Expand Down
10 changes: 3 additions & 7 deletions test/scripts/apidoc/signature.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { beforeAll, describe, expect, it } from 'vitest';
import {
analyzeSignature,
initMarkdownRenderer,
} from '../../../scripts/apidoc/signature';
import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown';
import { analyzeSignature } from '../../../scripts/apidoc/signature';
import { SignatureTest } from './signature.example';
import { loadExampleMethods } from './utils';

describe('signature', () => {
describe('analyzeSignature()', () => {
const methods = loadExampleMethods();

beforeAll(async () => {
await initMarkdownRenderer();
});
beforeAll(initMarkdownRenderer);

it('dummy dependency to rerun the test if the example changes', () => {
expect(new SignatureTest()).toBeTruthy();
Expand Down
15 changes: 11 additions & 4 deletions test/scripts/apidoc/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { SignatureReflection, TypeDocOptions } from 'typedoc';
import type {
DeclarationReflection,
SignatureReflection,
TypeDocOptions,
} from 'typedoc';
import {
loadProject,
selectApiMethodSignatures,
Expand All @@ -12,12 +16,15 @@ import { mapByName } from '../../../scripts/apidoc/utils';
export function loadProjectModules(
options?: Partial<TypeDocOptions>,
includeTestModules = false
): Record<string, Record<string, SignatureReflection>> {
): Record<
string,
[DeclarationReflection, Record<string, SignatureReflection>]
> {
const [, project] = loadProject(options);

const modules = selectApiModules(project, includeTestModules);

return mapByName(modules, selectApiMethodSignatures);
return mapByName(modules, (m) => [m, selectApiMethodSignatures(m)]);
}

/**
Expand All @@ -30,5 +37,5 @@ export function loadExampleMethods(): Record<string, SignatureReflection> {
tsconfig: 'test/scripts/apidoc/tsconfig.json',
},
true
)['SignatureTest'];
)['SignatureTest'][1];
}
Loading

0 comments on commit a001ac5

Please sign in to comment.