Skip to content

Commit

Permalink
feat: extract-exports
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Mar 3, 2020
1 parent 1398460 commit bc6c9f6
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 50 deletions.
1 change: 1 addition & 0 deletions core/instrument/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@storybook/csf": "^0.0.1",
"crypto": "^1.0.1",
"hosted-git-info": "^3.0.4",
"resolve": "^1.15.1",
"prettier": "^1.19.1",
"typescript": "3.5.3",
"loader-utils": "^1.2.3",
Expand Down
14 changes: 3 additions & 11 deletions core/instrument/src/babel/csf-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import traverse from '@babel/traverse';
import { extractFunctionParameters } from './get-function-parameters';
import { extractProperties } from './extract-properties';
import { sourceLocation } from './utils';

export const extractCSFStories = (stories: StoriesStore) => {
const globals: Stories = {};
Expand All @@ -23,18 +24,9 @@ export const extractCSFStories = (stories: StoriesStore) => {
const el = declaration.init.body;
const name = declaration.id.name;
const story: Story = {
loc: {
start: {
column: el.loc.start.column,
line: el.loc.start.line,
},
end: {
column: el.loc.end.column,
line: el.loc.end.line,
},
},
loc: sourceLocation(el.loc),
name,
};
story.name = name;
traverse(path.node, extractFunctionParameters(story), path.scope, path);
return story;
}
Expand Down
55 changes: 20 additions & 35 deletions core/instrument/src/babel/extract-component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'fs';
import nodePath from 'path';
import * as resolve from 'resolve';
import traverse from '@babel/traverse';
import {
StoriesStore,
Expand Down Expand Up @@ -44,6 +44,7 @@ const componentFromParams = (
const findComponentImport = (
ast: any,
componentName: string,
resolveOptions: resolve.SyncOpts,
filePath?: string,
): StoryComponent | undefined => {
let result: StoryComponent | undefined = undefined;
Expand All @@ -65,38 +66,11 @@ const findComponentImport = (
};
if (node.source.value && filePath) {
const folderName = nodePath.dirname(filePath);
let fileName: string | undefined;
// console.log(folderName, node.source.value);
try {
fileName = require.resolve(node.source.value, {
paths: [nodePath.relative(process.cwd(), folderName)],
});
} catch (e) {
// node10 - paths option does not work
const imported = nodePath.parse(node.source.value);
var files = fs.readdirSync(
nodePath.resolve(folderName, imported.dir),
);
const importedExt = imported.ext.length > 0;
const importedName = nodePath.format({
name: imported.name || 'index',
ext: imported.ext,
});
for (let i = 0; i < files.length; i += 1) {
const file = files[i];
let found = false;
if (importedExt) {
found = file === importedName;
} else {
found = nodePath.parse(file).name === importedName;
}
if (found) {
fileName = nodePath.join(folderName, file);
break;
}
}
}
console.log(fileName);
const resolveName = resolve.sync(node.source.value, {
...resolveOptions,
basedir: folderName,
});
console.log(resolveName);
}
path.skip();
break;
Expand All @@ -110,14 +84,20 @@ const findComponentImport = (
export const extractComponent = async (
ast: any,
store: StoriesStore,
resolveOptions: resolve.SyncOpts,
filePath?: string,
) => {
const kinds = Object.keys(store.kinds);
if (kinds.length > 0) {
const kind = store.kinds[kinds[0]];
const componentName = componentFromParams(kind.parameters);
if (componentName) {
const component = findComponentImport(ast, componentName, filePath);
const component = findComponentImport(
ast,
componentName,
resolveOptions,
filePath,
);
if (component) {
store.components[componentName] = component;
kind.component = componentName;
Expand All @@ -128,7 +108,12 @@ export const extractComponent = async (
const story = store.stories[name];
const componentName = componentFromParams(story.parameters);
if (componentName) {
const component = findComponentImport(ast, componentName, filePath);
const component = findComponentImport(
ast,
componentName,
resolveOptions,
filePath,
);
if (component) {
store.components[componentName] = component;
story.component = componentName;
Expand Down
163 changes: 163 additions & 0 deletions core/instrument/src/babel/extract-exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import * as parser from '@babel/parser';
import traverse from '@babel/traverse';
import { CodeLocation } from '@component-controls/specification';
import { sourceLocation } from './utils';

interface ExportType {
path: any;
node: any;
name: string;
exportedAs?: string;
loc: CodeLocation;
type: 'function' | 'indentifier';
}

export interface NamedExportTypes {
[key: string]: ExportType;
}
export interface ExportTypes {
default?: ExportType;
named: NamedExportTypes;
}
export const traverseExports = (results: ExportTypes) => {
const globals: NamedExportTypes = {};
const localExports: NamedExportTypes = {};

const extractArrowFunction = (
path: any,
declaration: any,
): ExportType | undefined => {
if (
declaration.init &&
declaration.init.type === 'ArrowFunctionExpression'
) {
const name = declaration.id.name;
const exportType: ExportType = {
loc: sourceLocation(declaration.init.body.loc),
name,
path,
node: path.node,
type: 'function',
};
return exportType;
}
return undefined;
};
return {
ExportDefaultDeclaration: (path: any) => {
results.default = {
name: 'default',
path,
node: path.node.declaration,
loc: sourceLocation(path.node.loc),
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) {
const declaration = declarations[0];
if (declaration) {
const name = declaration.id.name;
//check if it was a named export
if (!results.named[name]) {
const namedExport = extractArrowFunction(path, declaration);
if (namedExport && namedExport.name) {
localExports[namedExport.name] = namedExport;
}
}
}
}
},
ExportSpecifier: (path: any) => {
const { node } = path;
const localName = node.local.name;
const exportedName = node.exported.name;
const namedExport = localExports[localName];
if (namedExport) {
const global = globals[localName];
if (global) {
namedExport.name = global.name;
namedExport.path = global.path;
namedExport.node = global.node;
namedExport.loc = global.loc;
}
results.named[exportedName] = namedExport;
}
},
ExportNamedDeclaration: (path: any) => {
const { declaration } = path.node;
if (declaration) {
const { declarations } = declaration;

if (Array.isArray(declarations)) {
declarations.forEach(declaration => {
const namedExport = extractArrowFunction(path, declaration);
if (namedExport) {
const name = namedExport.name || '';
const global = globals[name];
if (global) {
namedExport.name = global.name;
namedExport.path = global.path;
namedExport.node = global.node;
namedExport.loc = global.loc;
}
results.named[name] = namedExport;
}
});
}
}
},
};
};

const cleanExportType = (exportType?: ExportType) => {
return exportType
? {
name: exportType.name,
exportedAs: exportType.exportedAs,
loc: exportType.loc,
}
: undefined;
};
export const extractExports = (
source: string,
parserOptions?: parser.ParserOptions,
) => {
const results: ExportTypes = {
named: {},
};
const ast = parser.parse(source, parserOptions);

traverse(ast, traverseExports(results));
return {
default: cleanExportType(results.default),
named: Object.keys(results.named).map(key =>
cleanExportType(results.named[key]),
),
};
};
32 changes: 29 additions & 3 deletions core/instrument/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const mdx = require('@mdx-js/mdx');
import { toId, storyNameFromExport } from '@storybook/csf';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import * as resolve from 'resolve';
import prettier, { Options, ResolveConfigOptions } from 'prettier';
import parserBabel from 'prettier/parser-babylon';
import { StoriesStore, Story } from '@component-controls/specification';
Expand All @@ -17,12 +18,23 @@ type TraverseFn = (stories: StoriesStore) => any;
export type PrettierOptions = Options & {
resolveConfigOptions?: ResolveConfigOptions;
};

export const defaultResolveOptions: resolve.SyncOpts = {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.mjs', '.es', '.es6'],
};

export const defaultParserOptions: parser.ParserOptions = {
sourceType: 'module',
plugins: ['jsx', 'typescript'],
};
const parseSource = async (
code: string,
traverseFn: TraverseFn,
originalSource: string,
filePath?: string,
parserOptions?: parser.ParserOptions,
prettierOptions?: PrettierOptions,
resolveOptions?: resolve.SyncOpts,
): Promise<StoriesStore> => {
const prettify = async (c: string): Promise<string> => {
if (prettierOptions !== false) {
Expand All @@ -47,8 +59,8 @@ const parseSource = async (
};
const source = await prettify(code);
const ast = parser.parse(source, {
sourceType: 'module',
plugins: ['jsx', 'typescript'],
...defaultParserOptions,
...parserOptions,
});
const store: StoriesStore = {
stories: {},
Expand All @@ -75,7 +87,13 @@ const parseSource = async (
{},
);
}
await extractComponent(ast, store, filePath);

await extractComponent(
ast,
store,
{ ...defaultResolveOptions, ...resolveOptions },
filePath,
);
await packageInfo(store, filePath);
/*
if (filePath && stories.component && stories.component.from) {
Expand Down Expand Up @@ -125,21 +143,27 @@ const parseSource = async (
export const parseCSF = async (
source: string,
filePath?: string,
parserOptions?: parser.ParserOptions,
prettierOptions?: PrettierOptions,
resolveOptions?: resolve.SyncOpts,
): Promise<StoriesStore> => {
return await parseSource(
source,
extractCSFStories,
source,
filePath,
parserOptions,
prettierOptions,
resolveOptions,
);
};

export const parseMDX = async (
source: string,
filePath?: string,
parserOptions?: parser.ParserOptions,
prettierOptions?: PrettierOptions,
resolveOptions?: resolve.SyncOpts,
): Promise<StoriesStore> => {
const code = await mdx(source);

Expand All @@ -157,6 +181,8 @@ export const parseMDX = async (
extractMDXStories,
source,
filePath,
parserOptions,
prettierOptions,
resolveOptions,
);
};
Loading

0 comments on commit bc6c9f6

Please sign in to comment.