@storybook/docs-mdx
Advanced tools
Comparing version 0.0.1-canary.9a7e7e1.0 to 0.0.1-canary.c6b5d29.0
@@ -6,12 +6,21 @@ import * as t from '@babel/types'; | ||
import { toEstree } from 'hast-util-to-estree'; | ||
import cloneDeep from 'lodash/cloneDeep'; | ||
const getAttr = (elt, what) => { | ||
const attr = elt.attributes.find(n => n.name.name === what); | ||
return attr === null || attr === void 0 ? void 0 : attr.value; | ||
return attr; | ||
}; | ||
const getAttrValue = (elt, what) => { | ||
var _getAttr; | ||
return (_getAttr = getAttr(elt, what)) === null || _getAttr === void 0 ? void 0 : _getAttr.value; | ||
}; | ||
const extractTitle = (root, varToImport) => { | ||
const result = { | ||
title: undefined, | ||
of: undefined | ||
of: undefined, | ||
name: undefined, | ||
isTemplate: false | ||
}; | ||
@@ -34,22 +43,32 @@ let contents; | ||
if (name === 'Meta') { | ||
if (result.title) { | ||
if (result.title || result.name || result.of) { | ||
throw new Error('Meta can only be declared once'); | ||
} | ||
const titleAttr = getAttr(child.openingElement, 'title'); | ||
const titleAttrValue = getAttrValue(child.openingElement, 'title'); | ||
if (titleAttr) { | ||
if (t.isStringLiteral(titleAttr)) { | ||
result.title = titleAttr.value; | ||
if (titleAttrValue) { | ||
if (t.isStringLiteral(titleAttrValue)) { | ||
result.title = titleAttrValue.value; | ||
} else { | ||
throw new Error(`Expected string literal title, received ${titleAttr.type}`); | ||
throw new Error(`Expected string literal title, received ${titleAttrValue.type}`); | ||
} | ||
} | ||
const ofAttr = getAttr(child.openingElement, 'of'); | ||
const nameAttrValue = getAttrValue(child.openingElement, 'name'); | ||
if (ofAttr) { | ||
if (t.isJSXExpressionContainer(ofAttr)) { | ||
const of = ofAttr.expression; | ||
if (nameAttrValue) { | ||
if (t.isStringLiteral(nameAttrValue)) { | ||
result.name = nameAttrValue.value; | ||
} else { | ||
throw new Error(`Expected string literal name, received ${nameAttrValue.type}`); | ||
} | ||
} | ||
const ofAttrValue = getAttrValue(child.openingElement, 'of'); | ||
if (ofAttrValue) { | ||
if (t.isJSXExpressionContainer(ofAttrValue)) { | ||
const of = ofAttrValue.expression; | ||
if (t.isIdentifier(of)) { | ||
@@ -67,8 +86,27 @@ const importName = varToImport[of.name]; | ||
} else { | ||
throw new Error(`Expected JSX expression, received ${ofAttr.type}`); | ||
throw new Error(`Expected JSX expression, received ${ofAttrValue.type}`); | ||
} | ||
} | ||
const isTemplateAttr = getAttr(child.openingElement, 'isTemplate'); | ||
if (isTemplateAttr) { | ||
if (!isTemplateAttr.value) { | ||
// no value, implicit true | ||
result.isTemplate = true; | ||
} else if (t.isJSXExpressionContainer(isTemplateAttr.value)) { | ||
const isTemplate = isTemplateAttr.value.expression; | ||
if (t.isBooleanLiteral(isTemplate)) { | ||
result.isTemplate = isTemplate.value; | ||
} else { | ||
throw new Error(`Expected boolean isTemplate, received ${isTemplate.type}`); | ||
} | ||
} else { | ||
throw new Error(`Expected JSX expression isTemplate, received ${isTemplateAttr.value.type}`); | ||
} | ||
} | ||
} | ||
} | ||
} else if (t.isJSXExpressionContainer(child) && t.isStringLiteral(child.expression)) {// Skip string literals | ||
} else if (t.isJSXExpressionContainer(child)) {// Skip string literals & other JSX expressions | ||
} else { | ||
@@ -152,13 +190,15 @@ throw new Error(`Unexpected JSX child: ${child.type}`); | ||
const estree = store.toEstree(root); // toBabel mutates root, bug we don't need to clone it because | ||
// we're not using it again | ||
// const clone = cloneDeep(estree); | ||
const babel = toBabel(estree); | ||
const estree = store.toEstree(root); | ||
const clone = cloneDeep(estree); | ||
const babel = toBabel(clone); | ||
const { | ||
title, | ||
of | ||
of, | ||
name, | ||
isTemplate | ||
} = extractTitle(babel, varToImport); | ||
store.title = title; | ||
store.of = of; | ||
store.name = name; | ||
store.isTemplate = isTemplate; | ||
store.imports = Array.from(new Set(Object.values(varToImport))); | ||
@@ -171,2 +211,4 @@ return root; | ||
of: undefined, | ||
name: undefined, | ||
isTemplate: false, | ||
imports: undefined, | ||
@@ -181,2 +223,4 @@ toEstree | ||
of, | ||
name, | ||
isTemplate, | ||
imports = [] | ||
@@ -187,4 +231,6 @@ } = store; | ||
of, | ||
name, | ||
isTemplate, | ||
imports | ||
}; | ||
}; |
@@ -10,9 +10,8 @@ import { dedent } from 'ts-dedent'; | ||
import { Meta } from '@storybook/blocks'; | ||
import meta, { Basic } from './Button.stories'; | ||
import * as ButtonStories from './Button.stories'; | ||
`); | ||
expect(extractImports(ast)).toMatchInlineSnapshot(` | ||
Object { | ||
"Basic": "./Button.stories", | ||
"ButtonStories": "./Button.stories", | ||
"Meta": "@storybook/blocks", | ||
"meta": "./Button.stories", | ||
} | ||
@@ -32,2 +31,4 @@ `); | ||
"imports": Array [], | ||
"isTemplate": false, | ||
"name": undefined, | ||
"of": undefined, | ||
@@ -46,10 +47,28 @@ "title": "foobar", | ||
}); | ||
it('duplicate titles', () => { | ||
}); | ||
describe('name', () => { | ||
it('string literal name', () => { | ||
const input = dedent` | ||
<Meta title="foobar" /> | ||
# hello | ||
<Meta title="bz" /> | ||
<Meta name="foobar" /> | ||
`; | ||
expect(() => analyze(input)).toThrowErrorMatchingInlineSnapshot(`"Meta can only be declared once"`); | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [], | ||
"isTemplate": false, | ||
"name": "foobar", | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
it('template literal name', () => { | ||
const input = dedent` | ||
# hello | ||
<Meta name={\`foobar\`} /> | ||
`; | ||
expect(() => analyze(input)).toThrowErrorMatchingInlineSnapshot(`"Expected string literal name, received JSXExpressionContainer"`); | ||
}); | ||
}); | ||
@@ -60,16 +79,18 @@ describe('of', () => { | ||
import { Meta } from '@storybook/blocks'; | ||
import meta, { Basic } from './Button.stories'; | ||
import * as ButtonStories from './Button.stories'; | ||
<Meta of={meta} /> | ||
<Meta of={ButtonStories} /> | ||
`; | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [ | ||
"@storybook/blocks", | ||
"./Button.stories", | ||
], | ||
"of": "./Button.stories", | ||
"title": undefined, | ||
} | ||
`); | ||
Object { | ||
"imports": Array [ | ||
"@storybook/blocks", | ||
"./Button.stories", | ||
], | ||
"isTemplate": false, | ||
"name": undefined, | ||
"of": "./Button.stories", | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
@@ -84,3 +105,3 @@ it('missing variable', () => { | ||
const input = dedent` | ||
import meta, { Basic } from './Button.stories'; | ||
import * as ButtonStories from './Button.stories'; | ||
@@ -92,2 +113,61 @@ <Meta of="foobar" /> | ||
}); | ||
describe('isTemplate', () => { | ||
it('boolean implicit', () => { | ||
const input = dedent` | ||
<Meta isTemplate /> | ||
`; | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [], | ||
"isTemplate": true, | ||
"name": undefined, | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
}); // For some reason these two tests throw with: | ||
// "TypeError: this[node.value.type] is not a function" | ||
// It's not clear why? | ||
it('boolean expression, true', () => { | ||
const input = dedent` | ||
<Meta isTemplate={true} /> | ||
`; | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [], | ||
"isTemplate": true, | ||
"name": undefined, | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
it('boolean expression, false', () => { | ||
const input = dedent` | ||
<Meta isTemplate={false} /> | ||
`; | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [], | ||
"isTemplate": false, | ||
"name": undefined, | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
it('string literal', () => { | ||
const input = dedent` | ||
<Meta isTemplate="foo" /> | ||
`; | ||
expect(() => analyze(input)).toThrowErrorMatchingInlineSnapshot(`"Expected JSX expression isTemplate, received StringLiteral"`); | ||
}); | ||
it('other expression', () => { | ||
const input = dedent` | ||
<Meta isTemplate={1} /> | ||
`; | ||
expect(() => analyze(input)).toThrowErrorMatchingInlineSnapshot(`"Expected boolean isTemplate, received NumericLiteral"`); | ||
}); | ||
}); | ||
describe('errors', () => { | ||
@@ -99,26 +179,68 @@ it('no title', () => { | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [], | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
Object { | ||
"imports": Array [], | ||
"isTemplate": false, | ||
"name": undefined, | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
it('Bad MDX formatting', () => { | ||
const input = dedent` | ||
import meta, { Basic } from './Button.stories'; | ||
import meta, { Basic } from './Button.stories'; | ||
<Meta of={meta} />/> | ||
`; | ||
<Meta of={meta} />/> | ||
`; | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [ | ||
"./Button.stories", | ||
], | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
Object { | ||
"imports": Array [ | ||
"./Button.stories", | ||
], | ||
"isTemplate": false, | ||
"name": undefined, | ||
"of": undefined, | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
it('duplicate meta, both title', () => { | ||
const input = dedent` | ||
<Meta title="foobar" /> | ||
<Meta title="bz" /> | ||
`; | ||
expect(() => analyze(input)).toThrowErrorMatchingInlineSnapshot(`"Meta can only be declared once"`); | ||
}); | ||
it('duplicate meta, different', () => { | ||
const input = dedent` | ||
import * as ButtonStories from './Button.stories'; | ||
<Meta title="foobar" /> | ||
<Meta of={ButtonStories} /> | ||
`; | ||
expect(() => analyze(input)).toThrowErrorMatchingInlineSnapshot(`"Meta can only be declared once"`); | ||
}); | ||
it('MDX comments', () => { | ||
const input = dedent` | ||
import meta, { Basic } from './Button.stories'; | ||
<Meta of={meta} /> | ||
{/* whatever */} | ||
`; | ||
expect(analyze(input)).toMatchInlineSnapshot(` | ||
Object { | ||
"imports": Array [ | ||
"./Button.stories", | ||
], | ||
"isTemplate": false, | ||
"name": undefined, | ||
"of": "./Button.stories", | ||
"title": undefined, | ||
} | ||
`); | ||
}); | ||
}); | ||
}); |
@@ -7,3 +7,5 @@ import * as t from '@babel/types'; | ||
of: any; | ||
name: any; | ||
isTemplate: any; | ||
imports: any; | ||
}; |
{ | ||
"name": "@storybook/docs-mdx", | ||
"version": "0.0.1-canary.9a7e7e1.0", | ||
"version": "0.0.1-canary.c6b5d29.0", | ||
"description": "Storybook Docs MDX analyzer", | ||
@@ -19,2 +19,3 @@ "repository": { | ||
"*.d.ts", | ||
"index.mjs", | ||
"index.cjs" | ||
@@ -39,5 +40,7 @@ ], | ||
"@babel/traverse": "^7.12.11", | ||
"@babel/types": "^7.14.8", | ||
"@mdx-js/mdx": "^2.0.0", | ||
"estree-to-babel": "^4.9.0", | ||
"hast-util-to-estree": "^2.0.2" | ||
"hast-util-to-estree": "^2.0.2", | ||
"lodash": "^4.17.21" | ||
}, | ||
@@ -50,5 +53,5 @@ "devDependencies": { | ||
"@babel/preset-typescript": "^7.13.0", | ||
"@babel/types": "^7.14.8", | ||
"@jest/types": "^27.0.6", | ||
"@types/jest": "^27.0.3", | ||
"@types/lodash": "^4.14.182", | ||
"@types/node": "^16.4.1", | ||
@@ -55,0 +58,0 @@ "auto": "^10.3.0", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
17193
11
437
0
6
+ Added@babel/types@^7.14.8
+ Addedlodash@^4.17.21
+ Addedlodash@4.17.21(transitive)