Skip to content

Commit

Permalink
fix: refactor exports util to support broader formatting and export t…
Browse files Browse the repository at this point in the history
…ypes (#1051)

[![PR App][icn]][demo] | Fix RM-11826
:-------------------:|:----------:

## 🧰 Changes
I realized that the previous implementation did not capture multiple
exports if they were separated by only a single newline `\n`.
- refactors `exports` to traverse into each `mdxjsEsm` node and look for
each type of declared export declaration: `VariableDeclaration,
FunctionDeclaration, ClassDeclaration`
- updates test cases

## 🧬 QA & Testing

- [Broken on production][prod].
- [Working in this PR app][demo].


[demo]: https://markdown-pr-PR_NUMBER.herokuapp.com
[prod]: https://SUBDOMAIN.readme.io
[icn]:
https://user-images.githubusercontent.com/886627/160426047-1bee9488-305a-4145-bb2b-09d8b757d38a.svg
  • Loading branch information
davinhazard authored Jan 29, 2025
1 parent fe93bba commit 61e1a3b
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 7 deletions.
2 changes: 1 addition & 1 deletion __tests__/lib/exports/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ describe('export tags', () => {
});
it('returns different types of export names', () => {

expect(exports(weirdExportsMdx)).toStrictEqual(['Foo', 'bar', 'doSomethingFunction', 'YELLING']);
expect(exports(weirdExportsMdx)).toStrictEqual(['Foo', 'bar', 'doSomethingFunction', 'YELLING', 'SingleNewlinesAreAnnoying', 'x', 'MyClass']);
});
});
5 changes: 4 additions & 1 deletion __tests__/lib/exports/input/weirdExports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ export function Foo() {
export const bar = () => {
return <Foo />;
}

export function doSomethingFunction(input) {
return input.trim();
}

export const
YELLING = () => {}
export const SingleNewlinesAreAnnoying = () => "hey";
export let x = 2;
export class MyClass {
}

## Hey there
<Foo />
66 changes: 61 additions & 5 deletions lib/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,75 @@ import mdast from './mdast';
import { isMDXEsm } from '../processor/utils';
import { MdxjsEsm } from 'mdast-util-mdx';

const EXPORT_NAME_REGEX = /export\s+(?:const|let|var|function)\s+(\w+)/;
/* Example mdast structures to find first export name in a mdxjsEsm node:
There are three types of export declarations that we need to consider:
1. VARIABLE DECLARATION
"type": "mdxjsEsm",
"value": "export const Foo = () => <div>Hello world</div>\nexport const Bar = () => <div>hello darkness my old friend</div>",
"data": {
"estree": {
"type": "Program",
"body": [
{
"type": "ExportNamedDeclaration",
"declaration": {
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "Foo" // --------> This is the export name
},
...
2/3. FUNCTION DECLARATION & CLASS DECLARATION
"estree": {
"type": "Program",
"body": [
{
"type": "ExportNamedDeclaration",
"declaration": {
"type": "ClassDeclaration" | "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "Foo" // --------> This is the export name
},
*/

const exports = (doc: string) => {
const set = new Set<string>();

visit(mdast(doc), isMDXEsm, (node: MdxjsEsm) => {
if (node.value?.match(EXPORT_NAME_REGEX)) {
const [, name] = node.value.match(EXPORT_NAME_REGEX);
set.add(name);
// Once inside an mdxjsEsm node, we need to check for one or more declared exports within data.estree.body
// This is because single newlines \n are not considered as a new block, so there may be more than one export statement in a single mdxjsEsm node
const body = node.data?.estree.body;
if (!body) return;

for (const child of body) {
if (child.type === 'ExportNamedDeclaration') {
// There are three types of ExportNamedDeclaration that we need to consider: VariableDeclaration, FunctionDeclaration, ClassDeclaration
const declaration = child.declaration;
// FunctionDeclaration and ClassDeclaration have the same structure
if (declaration.type !== 'VariableDeclaration') {
// Note: declaration.id.type is always 'Identifier' for FunctionDeclarations and ClassDeclarations
set.add(declaration.id.name);
}
else {
const declarations = declaration.declarations;
for (const declaration of declarations) {
const id = declaration.id;
if (id.type === 'Identifier') {
set.add(id.name);
}
}
}
}
}

});

return Array.from(set);
};

export default exports;
export default exports;

0 comments on commit 61e1a3b

Please sign in to comment.