Skip to content

Commit

Permalink
DON'T TOUCH ANYTHING
Browse files Browse the repository at this point in the history
  • Loading branch information
erickzhao committed Nov 28, 2024
1 parent e6a3144 commit 378a0d6
Showing 1 changed file with 167 additions and 176 deletions.
343 changes: 167 additions & 176 deletions src/transformers/api-structure-previews.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logger from '@docusaurus/logger';
import { visitParents } from 'unist-util-visit-parents';
import { filter } from 'unist-util-filter';
import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import { Node, Parent } from 'unist';
import type { InlineCode, Link, LinkReference, Text } from 'mdast';
import { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
Expand All @@ -20,22 +20,176 @@ const fileContent = new Map<
string,
{ promise: Promise<Parent>; resolve?: (value: Parent) => void }
>();
const structureDefinitions = new Map<string, string>();
const modifiers = new Set<Promise<void>>();

const EXCLUDE_LIST = ['browser-window-options', 'web-preferences'];

export default function attacher() {
return transformer;
}

async function transformer(tree: Parent, file: VFile) {
// While transforming API structures, put the unmodified content
// (we don't want previews on previews or deadlocks) into a map
// so that when other docs are processed they can grab the content
// which Docusaurus has already processed (important for links).
// Since we don't want to depend on the order in which docs are
// transformed, if other docs are waiting on the content of an
// API structure there will be a promise resolver in the map and
// the other docs will be awaiting the associated promise.
const structureDefinitions = new Map<string, string>();
const mutations = new Set<Promise<void>>();
/**
* This function is the test function for the first pass of the tree visitor.
* Any values returning 'true' will run replaceLinkWithPreview().
*
* As a side effect, this function also puts all reference-style links (definitions)
* for API structures into a Map, which will be used on the second pass.
*/
const checkLinksandDefinitions = (node: Node): node is Link => {
if (isDefinition(node) && node.url.includes('/api/structures/')) {
structureDefinitions.set(node.identifier, node.url);
}
if (isLink(node) && node.url.includes('/api/structures/')) {
return EXCLUDE_LIST.every(
(excludedFile) => !node.url.endsWith(`/api/structures/${excludedFile}`)
);
}

return false;
};

/**
* This function is the test function from the second pass of the tree visitor.
* Any values returning 'true' will run replaceLinkWithPreview().
*/
function isStructureLinkReference(node: Node): node is LinkReference {
return isLinkReference(node) && structureDefinitions.has(node.identifier);
}

function replaceLinkWithPreview(
node: Link | LinkReference,
parents: Parent[]
) {
// depending on if the node is a direct link or a reference-style link,
// we get its URL differently.
let relativeStructureUrl: string;
let isInline = false;
if (isLink(node)) {
relativeStructureUrl = node.url;
} else if (isLinkReference(node)) {
relativeStructureUrl = structureDefinitions.get(node.identifier);
} else {
return;
}

if (relativeStructureUrl.endsWith('?inline')) {
relativeStructureUrl = relativeStructureUrl.split('?inline')[0];
isInline = true;
}

const relativeStructurePath = `${relativeStructureUrl}.md`;

// No file content promise available, so add one and then wait
// on it being resolved when the structure doc is processed
if (!fileContent.has(relativeStructurePath)) {
let resolve: (value: Parent) => void;
let reject: (err: Error) => void;

// Set a timeout as a backstop so we don't deadlock forever if something
// causes content to never be resolved - in theory an upstream change in
// Docusaurus could cause that if they limited how many files are being
// processed in parallel such that too many docs are awaiting others
const timeoutId = setTimeout(() => {
// links in translated locale [xy] have their paths prefixed with /xy/
const isTranslatedDoc = !relativeStructurePath.startsWith('/docs/');

if (isTranslatedDoc) {
// If we're running locally we might not have translations downloaded
// so if we don't find it locally just supply the default locale
const [_fullPath, locale, docPath] = relativeStructurePath.match(
/\/([a-z][a-z])\/docs\/(.*)/
);
const defaultLocalePath = `/docs/${docPath}`;
const localeDir = path.join(__dirname, '..', '..', 'i18n', locale);

if (!fs.existsSync(localeDir)) {
if (fileContent.has(defaultLocalePath)) {
const { promise } = fileContent.get(defaultLocalePath);
promise.then((content) => resolve(content));
return;
}
}
}

reject(
new Error(
`Timed out waiting for API structure content from ${relativeStructurePath}`
)
);
}, 60000000);

const promise = new Promise<Parent>((resolve_, reject_) => {
resolve = (value: Parent) => {
clearTimeout(timeoutId);
resolve_(value);
};
reject = reject_;
});

fileContent.set(relativeStructurePath, { promise, resolve });
}

const { promise: targetStructure } = fileContent.get(relativeStructurePath);

if (
(Array.isArray(node.children) &&
node.children.length > 0 &&
isText(node.children[0])) ||
isInlineCode(node.children[0])
) {
mutations.add(
targetStructure
.then((structureContent) => {
if (isInline) {
const siblings = parents[parents.length - 1].children;
const filtered = filter(
structureContent,
(node) => node.type !== 'mdxjsEsm' && node.type !== 'heading'
);
siblings.push(filtered);
} else {
// replace the Link node with an MDX element in-place
const title = (node.children[0] as Text | InlineCode).value;
const previewNode = node as unknown as MdxJsxFlowElement;
previewNode.type = 'mdxJsxFlowElement';
previewNode.name = 'APIStructurePreview';
previewNode.children = [];
previewNode.data = {
_mdxExplicitJsx: true,
};
previewNode.attributes = [
{
type: 'mdxJsxAttribute',
name: 'url',
value: `${relativeStructureUrl}`,
},
{
type: 'mdxJsxAttribute',
name: 'title',
value: title,
},
{
type: 'mdxJsxAttribute',
name: 'content',
value: JSON.stringify(structureContent),
},
];
}
})
.catch((err) => {
logger.error(err);
// NOTE - if build starts failing, comment the throw out
throw err;
})
);
}
}
visitParents(tree, checkLinksandDefinitions, replaceLinkWithPreview);
visitParents(tree, isStructureLinkReference, replaceLinkWithPreview);
await Promise.all(Array.from(mutations));

if (file.path.includes('/api/structures/')) {
let relativePath = `/${path.relative(file.cwd, file.path)}`;

Expand All @@ -57,171 +211,8 @@ async function transformer(tree: Parent, file: VFile) {
fileContent.set(relativePath, { promise: Promise.resolve(tree) });
}
}
structureDefinitions.clear();
modifiers.clear();
visitParents(tree, checkLinksandDefinitions, replaceLinkWithPreview);
visitParents(tree, isStructureLinkReference, replaceLinkWithPreview);
const importNode = getJSXImport('APIStructurePreview');
if (modifiers.size) {
if (mutations.size) {
tree.children.unshift(importNode);
}
await Promise.all(Array.from(modifiers));
}

/**
* This function is the test function for the first pass of the tree visitor.
* Any values returning 'true' will run replaceLinkWithPreview().
*
* As a side effect, this function also puts all reference-style links (definitions)
* for API structures into a Map, which will be used on the second pass.
*/
const checkLinksandDefinitions = (node: Node): node is Link => {
if (isDefinition(node) && node.url.includes('/api/structures/')) {
structureDefinitions.set(node.identifier, node.url);
}
if (isLink(node) && node.url.includes('/api/structures/')) {
return true;
}

return false;
};

/**
* This function is the test function from the second pass of the tree visitor.
* Any values returning 'true' will run replaceLinkWithPreview().
*/
function isStructureLinkReference(node: Node): node is LinkReference {
return isLinkReference(node) && structureDefinitions.has(node.identifier);
}

function replaceLinkWithPreview(node: Link | LinkReference, parents: Parent[]) {
// depending on if the node is a direct link or a reference-style link,
// we get its URL differently.
let relativeStructureUrl: string;
let isInline = false;
if (isLink(node)) {
relativeStructureUrl = node.url;
} else if (isLinkReference(node)) {
relativeStructureUrl = structureDefinitions.get(node.identifier);
} else {
return;
}

if (relativeStructureUrl.endsWith('?inline')) {
relativeStructureUrl = relativeStructureUrl.split('?inline')[0];
isInline = true;
}

const relativeStructurePath = `${relativeStructureUrl}.md`;

// No file content promise available, so add one and then wait
// on it being resolved when the structure doc is processed
if (!fileContent.has(relativeStructurePath)) {
let resolve: (value: Parent) => void;
let reject: (err: Error) => void;

// Set a timeout as a backstop so we don't deadlock forever if something
// causes content to never be resolved - in theory an upstream change in
// Docusaurus could cause that if they limited how many files are being
// processed in parallel such that too many docs are awaiting others
const timeoutId = setTimeout(() => {
// links in translated locale [xy] have their paths prefixed with /xy/
const isTranslatedDoc = !relativeStructurePath.startsWith('/docs/');

if (isTranslatedDoc) {
// If we're running locally we might not have translations downloaded
// so if we don't find it locally just supply the default locale
const [_fullPath, locale, docPath] = relativeStructurePath.match(
/\/([a-z][a-z])\/docs\/(.*)/
);
const defaultLocalePath = `/docs/${docPath}`;
const localeDir = path.join(__dirname, '..', '..', 'i18n', locale);

if (!fs.existsSync(localeDir)) {
if (fileContent.has(defaultLocalePath)) {
const { promise } = fileContent.get(defaultLocalePath);
promise.then((content) => resolve(content));
return;
}
}
}

reject(
new Error(
`Timed out waiting for API structure content from ${relativeStructurePath}`
)
);
}, 60000);

const promise = new Promise<Parent>((resolve_, reject_) => {
resolve = (value: Parent) => {
clearTimeout(timeoutId);
resolve_(value);
};
reject = reject_;
});

fileContent.set(relativeStructurePath, { promise, resolve });
}

const { promise } = fileContent.get(relativeStructurePath);

if (
(Array.isArray(node.children) &&
node.children.length > 0 &&
isText(node.children[0])) ||
isInlineCode(node.children[0])
) {
modifiers.add(
promise
.then((content) => {
const title = (node.children[0] as Text | InlineCode).value;

if (isInline) {
const siblings = parents[parents.length - 1].children;
const filtered = filter(
content,
(node) => node.type !== 'mdxjsEsm' && node.type !== 'heading'
);
visitParents(
filtered,
checkLinksandDefinitions,
replaceLinkWithPreview
);
siblings.push(filtered);
} else {
// replace the Link node with an MDX element in-place
const previewNode = node as unknown as MdxJsxFlowElement;
previewNode.type = 'mdxJsxFlowElement';
previewNode.name = 'APIStructurePreview';
previewNode.children = [];
previewNode.data = {
_mdxExplicitJsx: true,
};
previewNode.attributes = [
{
type: 'mdxJsxAttribute',
name: 'url',
value: `${relativeStructureUrl}`,
},
{
type: 'mdxJsxAttribute',
name: 'title',
value: title,
},
{
type: 'mdxJsxAttribute',
name: 'content',
value: JSON.stringify(content),
},
];
}
})
.catch((err) => {
logger.error(err);
// NOTE - if build starts failing, comment the throw out
throw err;
})
);
}
}

0 comments on commit 378a0d6

Please sign in to comment.