Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
fix(transformer): fixed a few issues and reduced contend size
Browse files Browse the repository at this point in the history
  • Loading branch information
ovflowd committed Sep 12, 2022
1 parent 0972d13 commit 14fdb75
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 57 deletions.
10 changes: 0 additions & 10 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
{
"generatorOpts": {
"compact": true,
"comments": false
},
"overrides": [
{
"test": "**/**.md",
"compact": true
}
],
"plugins": [
[
"prismjs",
Expand Down
6 changes: 3 additions & 3 deletions src/components/ApiComponents/Components/ApiLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ interface Props {
}

// As we replace during the API generation any Heading with prefix of `Class: ` or `Event: `
// Or Headings of H3-to-H5 with `code prefix` into a `<DataTag>` component
// Which causes MDX erroneously to generate the links including the DataTag Component as it
// Or Headings of H3-to-H5 with `code prefix` into a `<Tag>` component
// Which causes MDX erroneously to generate the links including the Tag Component as it
// was part of the heading itself.
export const replaceDataTag = (href: string) =>
(href || '').replace(/datatag-(tagc|tagm|tage)--/, '');
(href || '').replace(/tag-(tagc|tagm|tage)--/, '');

const ApiLink = ({ href, ...props }: Props) => (
// eslint-disable-next-line react/jsx-props-no-spreading
Expand Down
7 changes: 4 additions & 3 deletions src/components/ApiComponents/Components/SourceLink/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { FormattedMessage } from 'react-intl';
import styles from './index.module.scss';

interface Props {
sourceName: string;
sourceLink: string;
}

const SourceLink = ({ sourceName, sourceLink }: Props) => (
const SourceLink = ({ sourceLink }: Props) => (
<p className={styles.sourceLinkComponent}>
<FormattedMessage id="docs.api.sourceLink" />{' '}
<a href={sourceLink}>{sourceName}</a>
<a href={`https://github.com/nodejs/node/blob/${sourceLink}`}>
{sourceLink.split('/')[1]}
</a>
</p>
);

Expand Down
28 changes: 11 additions & 17 deletions src/components/ApiComponents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,25 @@ import * as Components from './Components';

interface Props {
data: ApiComponentData;
version: string;
}

const Metadata = ({ data, version }: Props): JSX.Element | null => {
if (data.changes && data.update) {
return <Components.Changes changes={data.changes} update={data.update} />;
const Metadata = ({ data }: Props): JSX.Element | null => {
const { changes, update, stability, source_link: sourceLink } = data;

if (changes && update) {
return <Components.Changes changes={changes} update={update} />;
}

if (data.update) {
return (
<Components.Span version={data.update.version} type={data.update.type} />
);
if (update) {
return <Components.Span version={update.version} type={update.type} />;
}

if (data.stability) {
return <Components.Stability stability={data.stability} />;
if (stability) {
return <Components.Stability stability={stability} />;
}

if (data.source_link) {
return (
<Components.SourceLink
sourceName={data.source_link}
sourceLink={`https://github.com/nodejs/node/blob/${version}/${data.source_link}`}
/>
);
if (sourceLink) {
return <Components.SourceLink sourceLink={sourceLink} />;
}

return null;
Expand Down
4 changes: 2 additions & 2 deletions src/components/Codebox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ const Codebox = ({ children: { props } }: Props): JSX.Element => {
const [parsedCode, setParsedCode] = useState('');

// eslint-disable-next-line react/prop-types
const className = props.className || '';
const className = props.className || 'text';

// Language Matches in class
const matches = className.match(/language-(?<lang>.*)/);

// Language name from classname
const language = matches?.groups?.lang || '';
const language = matches?.groups?.lang || 'text';

// Actual Code into a stringified format
const stringCode = props.children?.toString() || '';
Expand Down
6 changes: 2 additions & 4 deletions src/components/TableOfContents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ const prefix = (t: string) => {

const removeApiSpanTagFromItem = (item: TableOfContentsItem) => ({
...item,
url: item.url
? item.url.replace(/datatag-(tagc|tagm|tage)--/, '')
: undefined,
url: item.url ? item.url.replace(/tag-(tagc|tagm|tage)--/, '') : undefined,
title: item.title
? item.title.replace(/<DataTag tag="(M|C|E)" \/> /, (_, t) => prefix(t))
? item.title.replace(/<Tag tag="(M|C|E)" \/> /, (_, t) => prefix(t))
: undefined,
});

Expand Down
4 changes: 2 additions & 2 deletions src/templates/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ interface Props {
}

const components = {
Metadata,
DataTag,
MC: Metadata,
Tag: DataTag,
a: Components.ApiLink,
h3: Components.H3,
h4: Components.H4,
Expand Down
117 changes: 101 additions & 16 deletions util-node/apiDocsTransformUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const FEATURES_REGEX = {
metadataComponents: /^<!--([\s\S]*?)-->/,
// ReGeX to match the {Type}<Type> (Structure Type metadatas)
// eslint-disable-next-line no-useless-escape
structureType: /(\{|<)[a-zA-Z.|\[\]\\]+(\}|>)/gm,
structureType: /(\{|<)[a-z0-9.| \n\[\]\\]+(\}|>)/gim,
// ReGeX for transforming `<pre>` into JSX snippets
removePreCodes: /<pre>|<\/pre>/gi,
// ReGeX for increasing the heading level
Expand All @@ -20,6 +20,10 @@ const FEATURES_REGEX = {
classEventHeading: /^(#{3,5}) (Class:|Event:|`.+`)/,
// ReGeX for the Stability Index
stabilityIndex: /^> (.*:)\s*(\d)([\s\S]*)/,
// ReGeX for non-valid Markdown Links
fixLinks: /<(https:\/\/.+)>/gm,
// ReGeX for escaping inline {code}
inlineCode: /\{.+\}/gm,
};

const YAML_FEATURES = {
Expand Down Expand Up @@ -131,9 +135,15 @@ function createApiDocsFrontmatter(firstLine, { version, name, fullVersion }) {
// into a proper metadata with the information (index number) and any other accompanying text
function replaceStabilityIndex(metadata) {
return (_, __, level, text) => {
const data = JSON.stringify({ stability: { level: Number(level), text } });
const sanitizedText = text
.replace('\n>', '')
.replace(/\[(.+)\]\[\]/g, (___, piece) => piece);

return `<Metadata version="${metadata.fullVersion}" data={${data}} />`;
const data = JSON.stringify({
stability: { level: Number(level), text: sanitizedText },
});

return `<MC version="${metadata.fullVersion}" data={${data}} />`;
};
}

Expand All @@ -147,25 +157,31 @@ function increaseHeadingLevel() {
};
}

// This utility inserts the `<DataTag>` component as prefix of the Heading
// This utility inserts the `<Tag>` component as prefix of the Heading
// It allows us to render the small "round tags" that identify if the entry
// is a class, event, method or something else
function addClassEventHeading(_, navigationCreator) {
const getTagPerName = name => {
switch (name) {
case 'Class:':
navigationCreator.addType('class');
return `<DataTag tag="C" />`;
return `<Tag tag="C" />`;
case 'Event:':
return `<DataTag tag="E" />`;
return `<Tag tag="E" />`;
default:
return `<DataTag tag="M" /> ${name}`;
return `<Tag tag="M" /> ${name}`;
}
};

return (_m, prefix, name) => `${prefix} ${getTagPerName(name)}`;
}

// This Utility replace links in the <https://example.org>
// Into [https://example.org](https://example.oirg)
function replaceLinksToMarkdownLinks() {
return (_, link) => `(${link})[${link}]`;
}

// This utility replaces any encounter of `{String}`, {Object}` etc
// Within a proper link referencing either to its MDN specification
// Or if it is another API doc (e.g.: `fs.something`) then to its
Expand Down Expand Up @@ -213,13 +229,17 @@ function replaceMarkdownMetadata(metadata, navigationCreator) {
}
});

if (parsedYaml.source_link) {
parsedYaml.source_link = `${metadata.fullVersion}/${parsedYaml.source_link}`;
}

if (YAML_FEATURES.allowedNavigationTypes.includes(parsedYaml.type)) {
navigationCreator.addType(parsedYaml.type);
}

const stringifiedData = JSON.stringify(parsedYaml);

return `<Metadata version="${metadata.fullVersion}" data={${stringifiedData}} />`;
return `<MC data={${stringifiedData}} />`;
}
} catch (e) {
// eslint-disable-next-line no-console
Expand All @@ -236,14 +256,55 @@ function replaceUrlReferences() {
`${reference} (${apiPath}${file}${hash || ''})`;
}

// This utility fixes inline code tags and it safely avoids escaping code inside code
function replaceInlineCodeTags() {
return match => `\`\`\`${match}\`\`\``;
}

function calculateCodeBlockIntersection() {
let codeBlockEndingIndex = -1;

return (lines, index) => {
const lineStartsWithACodeblock = lines.startsWith('```');
const lineEndsTheCodeblock = lines.endsWith('```');

if (lineStartsWithACodeblock) {
if (!lineEndsTheCodeblock) {
// If the current block starts with a codeblock but doesn't end with one
// We have a multiple line code block then
codeBlockEndingIndex = index;
}

return true;
}

if (codeBlockEndingIndex !== -1) {
// This means we're currently iterating inside a code block
// We should ignore parsing all lines until we reach the
// end of the code block
if (lineEndsTheCodeblock) {
// If we have a ending code block, stop ignoring the code loop
// And go back to normal business starting the next block
codeBlockEndingIndex = -1;
}

return true;
}

return false;
};
}

// This function creates our Markdown parser that parses the whole MDX/Markdown file
// By updating and removing contents to the way we need them to be
function createMarkdownParser(markdownContent, metadata) {
const invalidLinkFormat = replaceLinksToMarkdownLinks(metadata);
const stabilityIndex = replaceStabilityIndex(metadata);
const urlReferences = replaceUrlReferences(metadata);
const headingLevel = increaseHeadingLevel(metadata);
const structureType = replaceTypeToLinks(metadata);
const codeTags = cleanseCodeTags(metadata);
const inlineTags = replaceInlineCodeTags(metadata);

const { navigationCreator, getNavigationEntries } =
createNavigationCreator(metadata);
Expand All @@ -262,6 +323,8 @@ function createMarkdownParser(markdownContent, metadata) {
moduleCreator.addType('module');
moduleCreator.create();

const calculateBlockIntersection = calculateCodeBlockIntersection();

// Iterate between chunks of paragraphs instead of the whole document
// As this is way more perfomatic for regex queries
const [, ...parsedContent] = [firstLines, ...markdownContents].map(
Expand All @@ -275,23 +338,45 @@ function createMarkdownParser(markdownContent, metadata) {
navigation
);

const parsedLines = lines
.replace(FEATURES_REGEX.structureType, structureType)
.replace(FEATURES_REGEX.stabilityIndex, stabilityIndex)
.replace(FEATURES_REGEX.increaseHeadingLevel, headingLevel)
.replace(FEATURES_REGEX.classEventHeading, classEventHeading)
.replace(FEATURES_REGEX.removePreCodes, codeTags)
.replace(FEATURES_REGEX.metadataComponents, metadataComponents)
.replace(FEATURES_REGEX.markdownFootnoteUrls, urlReferences);
// This verifies if the current lines are part of a multi-line code block
// This allows us to simply ignore any modification during this time
if (calculateBlockIntersection(lines, index)) {
return lines;
}

// In this case the lines are either a YAML metadata or a code block
if (lines.startsWith('<!--') || lines.startsWith('<pre>')) {
return lines
.replace(FEATURES_REGEX.removePreCodes, codeTags)
.replace(FEATURES_REGEX.metadataComponents, metadataComponents);
}

// If the current item is a Heading then we add it itself
if (lines.startsWith('#')) {
const parsedLines = lines
// This means the current line is a Heading
.replace(FEATURES_REGEX.increaseHeadingLevel, headingLevel)
.replace(FEATURES_REGEX.classEventHeading, classEventHeading);

navigation.addHeading(lines);
navigation.create();

return parsedLines;
}

if (lines.startsWith('> ')) {
// This means the current line is a Stability Index
return lines.replace(FEATURES_REGEX.stabilityIndex, stabilityIndex);
}

const parsedLines = lines
// This is the last scenario where lines are text
.replace(FEATURES_REGEX.structureType, structureType)
.replace(FEATURES_REGEX.stabilityIndex, stabilityIndex)
.replace(FEATURES_REGEX.markdownFootnoteUrls, urlReferences)
.replace(FEATURES_REGEX.fixLinks, invalidLinkFormat)
.replace(FEATURES_REGEX.inlineCode, inlineTags);

// Otherwise it might be in the previous lines (Maximum depth of 3)
// As the header should be on maximum 3 levels away from the current item
// We use index + 1 as reference for markdownContents as the firstLine is not part
Expand Down

0 comments on commit 14fdb75

Please sign in to comment.