diff --git a/core/instrument/.eslintignore b/core/instrument/.eslintignore index 53c37a166..1521c8b76 100644 --- a/core/instrument/.eslintignore +++ b/core/instrument/.eslintignore @@ -1 +1 @@ -dist \ No newline at end of file +dist diff --git a/core/instrument/src/babel/extract-exports.ts b/core/instrument/src/babel/extract-exports.ts index 29e5c0e52..8754ea3e4 100644 --- a/core/instrument/src/babel/extract-exports.ts +++ b/core/instrument/src/babel/extract-exports.ts @@ -3,11 +3,11 @@ import traverse from '@babel/traverse'; import { CodeLocation } from '@component-controls/specification'; import { sourceLocation } from './utils'; -interface ExportType { +export interface ExportType { path: any; node: any; name: string; - exportedAs?: string; + internalName?: string; loc: CodeLocation; type: 'function' | 'indentifier'; } @@ -35,6 +35,7 @@ export const traverseExports = (results: ExportTypes) => { const exportType: ExportType = { loc: sourceLocation(declaration.init.body.loc), name, + internalName: name, path, node: path.node, type: 'function', @@ -53,30 +54,6 @@ export const traverseExports = (results: ExportTypes) => { type: 'indentifier', }; }, - AssignmentExpression: (path: any) => { - const node = path.node; - if ( - node.left.type === 'MemberExpression' && - node.left.property.type === 'Identifier' && - node.left.property.name === 'story' && - node.right.type === 'ObjectExpression' - ) { - const name = node.left.object.name; - globals[name] = { - loc: sourceLocation(node.loc), - path, - node: node.right, - name, - type: 'indentifier', - }; - - const parsed = results.named[name]; - - if (parsed) { - parsed.name = name; - } - } - }, VariableDeclaration: (path: any) => { const { declarations } = path.node; if (Array.isArray(declarations) && declarations.length > 0) { @@ -98,10 +75,12 @@ export const traverseExports = (results: ExportTypes) => { const localName = node.local.name; const exportedName = node.exported.name; const namedExport = localExports[localName]; + if (namedExport) { + namedExport.internalName = namedExport.name; + namedExport.name = exportedName; const global = globals[localName]; if (global) { - namedExport.name = global.name; namedExport.path = global.path; namedExport.node = global.node; namedExport.loc = global.loc; @@ -121,6 +100,7 @@ export const traverseExports = (results: ExportTypes) => { const name = namedExport.name || ''; const global = globals[name]; if (global) { + namedExport.internalName = global.name; namedExport.name = global.name; namedExport.path = global.path; namedExport.node = global.node; @@ -139,7 +119,7 @@ const cleanExportType = (exportType?: ExportType) => { return exportType ? { name: exportType.name, - exportedAs: exportType.exportedAs, + internalName: exportType.internalName, loc: exportType.loc, } : undefined; diff --git a/core/instrument/src/babel/extract-imports.ts b/core/instrument/src/babel/extract-imports.ts new file mode 100644 index 000000000..36b590b15 --- /dev/null +++ b/core/instrument/src/babel/extract-imports.ts @@ -0,0 +1,58 @@ +import * as parser from '@babel/parser'; +import traverse from '@babel/traverse'; + +export interface ImportType { + name: string; + importedName: 'default' | 'namespace' | string; +} + +export interface ImportTypes { + [key: string]: ImportType[]; +} + +export const traverseImports = (results: ImportTypes) => { + return { + ImportDeclaration: (path: any) => { + const node = path.node; + const imports: ImportType[] = node.specifiers.map((specifier: any) => { + let importedName; + switch (specifier.type) { + case 'ImportDefaultSpecifier': + importedName = 'default'; + break; + case 'ImportNamespaceSpecifier': + importedName = 'namespace'; + break; + + default: + importedName = specifier.imported + ? specifier.imported.name + : specifier.local.name; + } + return { + name: specifier.local.name, + importedName, + }; + }); + if (Array.isArray(results[node.source.value])) { + results[node.source.value] = [ + ...results[node.source.value], + ...imports, + ]; + } else { + results[node.source.value] = imports; + } + }, + }; +}; + +export const extractImports = ( + source: string, + parserOptions?: parser.ParserOptions, +) => { + const results: ImportTypes = {}; + const ast = parser.parse(source, parserOptions); + + traverse(ast, traverseImports(results)); + return results; +}; diff --git a/core/instrument/test/__snapshots__/extract-exports.test.js.snap b/core/instrument/test/__snapshots__/extract-exports.test.js.snap index d747d1faf..2af33ed3b 100644 --- a/core/instrument/test/__snapshots__/extract-exports.test.js.snap +++ b/core/instrument/test/__snapshots__/extract-exports.test.js.snap @@ -1,9 +1,31 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`extract-exports props argument 1`] = ` +exports[`extract-exports named const export 1`] = ` +Object { + "default": undefined, + "named": Array [ + Object { + "internalName": "myStory", + "loc": Object { + "end": Object { + "column": 24, + "line": 1, + }, + "start": Object { + "column": 22, + "line": 1, + }, + }, + "name": "myStory", + }, + ], +} +`; + +exports[`extract-exports named export 1`] = ` Object { "default": Object { - "exportedAs": undefined, + "internalName": undefined, "loc": Object { "end": Object { "column": 28, @@ -18,7 +40,7 @@ Object { }, "named": Array [ Object { - "exportedAs": undefined, + "internalName": "namedExport", "loc": Object { "end": Object { "column": 40, @@ -34,3 +56,32 @@ Object { ], } `; + +exports[`extract-exports named export alias 1`] = ` +Object { + "default": undefined, + "named": Array [ + Object { + "internalName": "myStory", + "loc": Object { + "end": Object { + "column": 24, + "line": 1, + }, + "start": Object { + "column": 22, + "line": 1, + }, + }, + "name": "exportedStory", + }, + ], +} +`; + +exports[`extract-exports re-exported name 1`] = ` +Object { + "default": undefined, + "named": Array [], +} +`; diff --git a/core/instrument/test/__snapshots__/extract-imports.test.js.snap b/core/instrument/test/__snapshots__/extract-imports.test.js.snap new file mode 100644 index 000000000..cc3bdd743 --- /dev/null +++ b/core/instrument/test/__snapshots__/extract-imports.test.js.snap @@ -0,0 +1,103 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`extract-imports all imports 1`] = ` +Object { + "./buttons": Array [ + Object { + "importedName": "Button", + "name": "Btn", + }, + ], + "buttons": Array [ + Object { + "importedName": "default", + "name": "Button1", + }, + Object { + "importedName": "namespace", + "name": "Button2", + }, + Object { + "importedName": "Button", + "name": "Button", + }, + ], + "react": Array [ + Object { + "importedName": "default", + "name": "React", + }, + Object { + "importedName": "FC", + "name": "FC", + }, + Object { + "importedName": "MouseEvent", + "name": "MouseEvent", + }, + ], +} +`; + +exports[`extract-imports default import 1`] = ` +Object { + "buttons": Array [ + Object { + "importedName": "default", + "name": "Button", + }, + ], +} +`; + +exports[`extract-imports mixed import 1`] = ` +Object { + "./react": Array [ + Object { + "importedName": "default", + "name": "React", + }, + Object { + "importedName": "FC", + "name": "FC", + }, + Object { + "importedName": "MouseEvent", + "name": "MouseEvent", + }, + ], +} +`; + +exports[`extract-imports named alias import 1`] = ` +Object { + "buttons": Array [ + Object { + "importedName": "Button", + "name": "Btn", + }, + ], +} +`; + +exports[`extract-imports named import 1`] = ` +Object { + "buttons": Array [ + Object { + "importedName": "Button", + "name": "Button", + }, + ], +} +`; + +exports[`extract-imports namespace import 1`] = ` +Object { + "buttons": Array [ + Object { + "importedName": "namespace", + "name": "Button", + }, + ], +} +`; diff --git a/core/instrument/test/examples/imports/all-imports.js b/core/instrument/test/examples/imports/all-imports.js new file mode 100644 index 000000000..b0661f129 --- /dev/null +++ b/core/instrument/test/examples/imports/all-imports.js @@ -0,0 +1,5 @@ +import Button1 from 'buttons'; +import * as Button2 from 'buttons'; +import { Button } from 'buttons'; +import React, { FC, MouseEvent } from 'react'; +import { Button as Btn } from './buttons'; diff --git a/core/instrument/test/examples/imports/default-import.js b/core/instrument/test/examples/imports/default-import.js new file mode 100644 index 000000000..df681e98e --- /dev/null +++ b/core/instrument/test/examples/imports/default-import.js @@ -0,0 +1 @@ +import Button from 'buttons'; diff --git a/core/instrument/test/examples/imports/mixed-import.js b/core/instrument/test/examples/imports/mixed-import.js new file mode 100644 index 000000000..4988aa37f --- /dev/null +++ b/core/instrument/test/examples/imports/mixed-import.js @@ -0,0 +1 @@ +import React, { FC, MouseEvent } from './react'; diff --git a/core/instrument/test/examples/imports/named-alias-import.js b/core/instrument/test/examples/imports/named-alias-import.js new file mode 100644 index 000000000..0ad19df87 --- /dev/null +++ b/core/instrument/test/examples/imports/named-alias-import.js @@ -0,0 +1 @@ +import { Button as Btn } from 'buttons'; diff --git a/core/instrument/test/examples/imports/named-import.js b/core/instrument/test/examples/imports/named-import.js new file mode 100644 index 000000000..495dc0c12 --- /dev/null +++ b/core/instrument/test/examples/imports/named-import.js @@ -0,0 +1 @@ +import { Button } from 'buttons'; diff --git a/core/instrument/test/examples/imports/namespace-import.js b/core/instrument/test/examples/imports/namespace-import.js new file mode 100644 index 000000000..5072467dc --- /dev/null +++ b/core/instrument/test/examples/imports/namespace-import.js @@ -0,0 +1 @@ +import * as Button from 'buttons'; diff --git a/core/instrument/test/examples/named-const-export.js b/core/instrument/test/examples/named-const-export.js new file mode 100644 index 000000000..8646b7fed --- /dev/null +++ b/core/instrument/test/examples/named-const-export.js @@ -0,0 +1,3 @@ +const myStory = () => {}; + +export { myStory }; diff --git a/core/instrument/test/examples/named-export-alias.js b/core/instrument/test/examples/named-export-alias.js new file mode 100644 index 000000000..069b71ffa --- /dev/null +++ b/core/instrument/test/examples/named-export-alias.js @@ -0,0 +1,3 @@ +const myStory = () => {}; + +export { myStory as exportedStory }; diff --git a/core/instrument/test/examples/main.js b/core/instrument/test/examples/named-export.js similarity index 100% rename from core/instrument/test/examples/main.js rename to core/instrument/test/examples/named-export.js diff --git a/core/instrument/test/examples/re-exported-name.js b/core/instrument/test/examples/re-exported-name.js new file mode 100644 index 000000000..9ea254c6a --- /dev/null +++ b/core/instrument/test/examples/re-exported-name.js @@ -0,0 +1,3 @@ +import { myStory } from 'stories.tsx'; + +export { myStory }; diff --git a/core/instrument/test/extract-exports.test.js b/core/instrument/test/extract-exports.test.js index b3ac1c8a8..fa8dec4a1 100644 --- a/core/instrument/test/extract-exports.test.js +++ b/core/instrument/test/extract-exports.test.js @@ -8,7 +8,26 @@ describe('extract-exports', () => { const content = fs.readFileSync(path.join(__dirname, fileName), 'utf8'); return extractExports(content, defaultParserOptions); }; - it('props argument', () => { - expect(extractExportsForFile('./examples/main.js')).toMatchSnapshot(); + it('named export', () => { + expect( + extractExportsForFile('./examples/named-export.js'), + ).toMatchSnapshot(); + }); + it('named const export', () => { + expect( + extractExportsForFile('./examples/named-const-export.js'), + ).toMatchSnapshot(); + }); + + it('named export alias', () => { + expect( + extractExportsForFile('./examples/named-export-alias.js'), + ).toMatchSnapshot(); + }); + + it('re-exported name', () => { + expect( + extractExportsForFile('./examples/re-exported-name.js'), + ).toMatchSnapshot(); }); }); diff --git a/core/instrument/test/extract-imports.test.js b/core/instrument/test/extract-imports.test.js new file mode 100644 index 000000000..00d2b1cc8 --- /dev/null +++ b/core/instrument/test/extract-imports.test.js @@ -0,0 +1,44 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import { defaultParserOptions } from '../src/index'; +import { extractImports } from '../src/babel/extract-imports'; + +describe('extract-imports', () => { + const extractImportsForFile = fileName => { + const content = fs.readFileSync(path.join(__dirname, fileName), 'utf8'); + return extractImports(content, defaultParserOptions); + }; + it('default import', () => { + expect( + extractImportsForFile('./examples/imports/default-import.js'), + ).toMatchSnapshot(); + }); + + it('namespace import', () => { + expect( + extractImportsForFile('./examples/imports/namespace-import.js'), + ).toMatchSnapshot(); + }); + it('named import', () => { + expect( + extractImportsForFile('./examples/imports/named-import.js'), + ).toMatchSnapshot(); + }); + + it('named alias import', () => { + expect( + extractImportsForFile('./examples/imports/named-alias-import.js'), + ).toMatchSnapshot(); + }); + + it('mixed import', () => { + expect( + extractImportsForFile('./examples/imports/mixed-import.js'), + ).toMatchSnapshot(); + }); + it('all imports', () => { + expect( + extractImportsForFile('./examples/imports/all-imports.js'), + ).toMatchSnapshot(); + }); +});