Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,76 @@ describe('extractHeadings', () => {

assert.equal(result.length, 2);
assert.equal(result[0].slug, 'fs-readfile');
assert.equal(result[0].value, 'fs.readFile()');
assert.equal(result[0].depth, 2);
assert.equal(result[0].stability, 2);
assert.equal(result[1].stability, 2);
});

it('keeps method table of contents labels compact', () => {
const entries = [
{
heading: {
depth: 3,
data: {
text: '`crypto.createHash(algorithm[, options])`',
name: 'createHash',
slug: 'crypto-createhash',
type: 'method',
},
},
},
];

const [result] = extractHeadings(entries);

assert.equal(result.value, 'crypto.createHash()');
});

it('keeps full method labels when compact labels would collide', () => {
const entries = [
{
heading: {
depth: 3,
data: {
text: '`url.format(urlObject)`',
name: 'format',
slug: 'url-format-urlobject',
type: 'method',
},
},
},
{
heading: {
depth: 3,
data: {
text: '`url.format(urlString)`',
name: 'format',
slug: 'url-format-urlstring',
type: 'method',
},
},
},
{
heading: {
depth: 3,
data: {
text: '`url.parse(urlString)`',
name: 'parse',
slug: 'url-parse-urlstring',
type: 'method',
},
},
},
];

const result = extractHeadings(entries);

assert.equal(result[0].value, 'url.format(urlObject)');
assert.equal(result[1].value, 'url.format(urlString)');
assert.equal(result[2].value, 'url.parse()');
});

it('filters out entries with empty heading text', () => {
const entries = [
{
Expand Down
85 changes: 69 additions & 16 deletions src/generators/jsx-ast/utils/buildBarProps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ import { visit } from 'unist-util-visit';

import { TOC_MAX_HEADING_DEPTH } from '../constants.mjs';

const SHORT_SIGNATURE_TYPES = new Set(['classMethod', 'ctor', 'method']);

/**
* Formats a full heading label for the table of contents.
* @param {import('../../metadata/types').HeadingData} data
*/
const formatHeading = data =>
data.text
// Remove any containing code blocks
.replace(/`/g, '')
// Remove any prefixes (i.e. 'Class:')
.replace(/^[^:]+:/, '')
// Trim the remaining whitespace
.trim();

/**
* Shortens method-like headings so the table of contents remains scannable.
* @param {import('../../metadata/types').HeadingData} data
*/
const formatCodeHeading = data => {
const code = data.text.match(/`([^`]+)`/)?.[1] ?? data.text;
const signatureStart = code.indexOf('(');

if (signatureStart === -1) {
return code.replace(/`/g, '').trim();
}

return `${code.slice(0, signatureStart).replace(/^new\s+/, '')}()`;
};

/**
* Generate a combined plain text string from all MDAST entries for estimating reading time.
*
Expand Down Expand Up @@ -31,23 +61,11 @@ const shouldIncludeEntryInToC = ({ heading }) =>
/**
* Extracts and formats heading information from an API documentation entry.
* @param {import('../../metadata/types').MetadataEntry} entry
* @param {string} heading
*/
const extractHeading = entry => {
const extractHeading = (entry, heading) => {
const data = entry.heading.data;

const cliFlagOrEnv = [...data.text.matchAll(/`(-[\w-]+|[A-Z0-9_]+=)/g)];

const heading =
cliFlagOrEnv.length > 0
? cliFlagOrEnv.at(-1)[1]
: data.text
// Remove any containing code blocks
.replace(/`/g, '')
// Remove any prefixes (i.e. 'Class:')
.replace(/^[^:]+:/, '')
// Trim the remaining whitespace
.trim();

return {
depth: entry.heading.depth,
value: heading,
Expand All @@ -57,10 +75,45 @@ const extractHeading = entry => {
};
};

/**
* Extracts both the full heading and the optional compact heading candidate.
* @param {import('../../metadata/types').MetadataEntry} entry
*/
const prepareHeading = entry => {
const data = entry.heading.data;
const cliFlagOrEnv = [...data.text.matchAll(/`(-[\w-]+|[A-Z0-9_]+=)/g)];

if (cliFlagOrEnv.length > 0) {
return { entry, heading: cliFlagOrEnv.at(-1)[1], compactHeading: null };
}

return {
entry,
heading: formatHeading(data),
compactHeading: SHORT_SIGNATURE_TYPES.has(data.type)
? formatCodeHeading(data)
: null,
};
};

/**
* Build the list of heading metadata for sidebar navigation.
*
* @param {Array<import('../../metadata/types').MetadataEntry>} entries - All API metadata entries
*/
export const extractHeadings = entries =>
entries.filter(shouldIncludeEntryInToC).map(extractHeading);
export const extractHeadings = entries => {
const headings = entries.filter(shouldIncludeEntryInToC).map(prepareHeading);
const compactHeadingCounts = Map.groupBy(
headings.filter(({ compactHeading }) => compactHeading),
({ compactHeading }) => compactHeading
);

return headings.map(({ entry, heading, compactHeading }) =>
extractHeading(
entry,
compactHeading && compactHeadingCounts.get(compactHeading).length === 1
? compactHeading
: heading
)
);
};
Loading