Skip to content

Commit

Permalink
feat: external stories with full source code
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed May 19, 2020
1 parent f776227 commit f4ca871
Show file tree
Hide file tree
Showing 60 changed files with 1,823 additions and 651 deletions.
5 changes: 3 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@
{
"type": "node",
"request": "launch",
"name": "jest - current file",
"name": "jest test",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"cwd": "${workspaceFolder}/core/instrument",
"args": [
"${fileBasenameNoExtension}",
"mdx-story-source",
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
Expand Down
40 changes: 27 additions & 13 deletions core/instrument/src/babel/csf-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { File } from '@babel/types';
import traverse from '@babel/traverse';
import { extractFunctionParameters } from './extract-function-parameters';
import { extractAttributes } from './extract-attributes';
import { followStoryImport } from './follow-imports';
import { componentsFromParams } from '../misc/component-attributes';
import { sourceLocation } from '../misc/source-location';
import { ParseStorieReturnType, InstrumentOptions } from '../types';

export const extractCSFStories = (
ast: File,
_options: InstrumentOptions,
{ source, filePath }: { source: string; filePath: string },
): ParseStorieReturnType => {
const globals: Stories = {};
const localStories: Stories = {};
Expand All @@ -23,19 +25,30 @@ export const extractCSFStories = (
path: any,
declaration: any,
): Story | undefined => {
if (
declaration.init &&
declaration.init.type === 'ArrowFunctionExpression'
) {
const el = declaration.init.body;
const name = declaration.id.name;
const story: Story = {
loc: sourceLocation(el.loc),
name,
id: name,
};
traverse(path.node, extractFunctionParameters(story), path.scope, path);
return story;
if (declaration.init) {
switch (declaration.init.type) {
case 'ArrowFunctionExpression': {
const el = declaration.init.body;
const name = declaration.id.name;
const story: Story = {
loc: sourceLocation(el.loc),
name,
id: name,
};
traverse(path.node, extractFunctionParameters(story), path.scope);
return story;
}
case 'Identifier': {
return followStoryImport(
declaration.id.name,
declaration.init.name,
filePath,
source,
_options,
ast,
);
}
}
}
return undefined;
};
Expand Down Expand Up @@ -105,6 +118,7 @@ export const extractCSFStories = (
},
VariableDeclaration: (path: any) => {
const { declarations } = path.node;

if (Array.isArray(declarations) && declarations.length > 0) {
const declaration = declarations[0];
if (declaration) {
Expand Down
17 changes: 13 additions & 4 deletions core/instrument/src/babel/extract-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface ExportType {
* specifies the import from statememnt
*/
from?: string;
node?: any;
path?: any;
}

export const EXPORT_ALL = '*';
Expand All @@ -28,12 +30,18 @@ export const traverseExports = (results: ExportTypes) => {
const globals: NamedExportTypes = {};
const localExports: NamedExportTypes = {};

const extractExportVariable = (declaration: any): ExportType | undefined => {
const extractExportVariable = (
path: any,
declaration: any,
): ExportType | undefined => {
if (declaration.init && declaration.id.name) {
const name = declaration.id.name;
const exportType: ExportType = {
node: declaration,
path,
loc: sourceLocation(
declaration.init.body
declaration.init.body &&
declaration.init.type !== 'ArrowFunctionExpression'
? declaration.init.body.loc
: declaration.init.loc,
),
Expand Down Expand Up @@ -81,7 +89,7 @@ export const traverseExports = (results: ExportTypes) => {
const name = declaration.id.name;
//check if it was a named export
if (!results.named[name]) {
const namedExport = extractExportVariable(declaration);
const namedExport = extractExportVariable(path, declaration);

if (namedExport && namedExport.name) {
localExports[namedExport.name] = namedExport;
Expand Down Expand Up @@ -142,14 +150,15 @@ export const traverseExports = (results: ExportTypes) => {
const { declarations } = declaration;
if (Array.isArray(declarations)) {
declarations.forEach(declaration => {
const namedExport = extractExportVariable(declaration);
const namedExport = extractExportVariable(path, declaration);
if (namedExport) {
const name = namedExport.name || '';
const global = globals[name];
if (global) {
namedExport.internalName = global.name;
namedExport.name = global.name;
namedExport.loc = global.loc;
namedExport.node = global.node;
}
results.named[name] = namedExport;
}
Expand Down
4 changes: 2 additions & 2 deletions core/instrument/src/babel/extract-function-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
extractArgumentsUsage,
addArgumentUsage,
} from './extract-arguments-usage';
import { ExportType } from '../types';
import { MDXExportType } from '../types';

interface KeyType {
name: string;
Expand All @@ -25,7 +25,7 @@ interface ASTPropNode {
}
export const extractFunctionParameters = (
story: Story,
exports?: ExportType,
exports?: MDXExportType,
) => ({
ArrowFunctionExpression: (path: any) => {
const node = path.node;
Expand Down
49 changes: 43 additions & 6 deletions core/instrument/src/babel/follow-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import * as parser from '@babel/parser';
import * as path from 'path';
import { File } from '@babel/types';
import traverse from '@babel/traverse';
import { CodeLocation, Imports } from '@component-controls/specification';
import {
CodeLocation,
Imports,
Story,
} from '@component-controls/specification';
import { getASTSource } from '@component-controls/core';
import { extractFunctionParameters } from './extract-function-parameters';
import { sourceLocation } from '../misc/source-location';
import { ImportTypes, traverseImports } from './extract-imports';

import {
traverseExports,
ExportTypes,
ExportType,
EXPORT_ALL,
} from './extract-exports';
import { InstrumentOptions } from '../types';
import { InstrumentOptions, MDXExportType } from '../types';

export interface FollowImportType {
exportedAs: string;
Expand All @@ -24,6 +32,8 @@ export interface FollowImportType {
source?: string;
imported?: string;
imports?: Imports;
node?: any;
path?: any;
}

export const followImports = (
Expand Down Expand Up @@ -66,10 +76,10 @@ export const followImports = (
exportedAs: findExport.name,
from: '',
};
if (components?.storeSourceFile) {
result.loc = findExport.loc;
result.source = source ? source : undefined;
}
result.loc = findExport.loc;
result.source = source ? source : undefined;
result.node = findExport.node;
result.path = findExport.path;
const externalImports = Object.keys(imports)
.filter(key => !imports[key].from.startsWith('.'))
.reduce(
Expand Down Expand Up @@ -171,3 +181,30 @@ export const followImports = (
}
return undefined;
};

export const followStoryImport = (
storyName: string,
importedName: string,
filePath: string,
source: string,
options: InstrumentOptions,
ast: File,
exports?: MDXExportType,
): Story | undefined => {
const follow = followImports(importedName, filePath, source, options, ast);
if (follow) {
const story: Story = { name: storyName, id: storyName };
if (follow.loc) {
const loc = sourceLocation(follow.loc);
story.loc = loc;
story.source = getASTSource(follow.source, loc);
traverse(
follow.node,
extractFunctionParameters(story, exports),
follow.path.scope,
);
}
return story;
}
return undefined;
};
72 changes: 66 additions & 6 deletions core/instrument/src/babel/mdx-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,32 @@ import {
Story,
StoriesKind,
StoryParameters,
CodeLocation,
} from '@component-controls/specification';
import { getASTSource } from '@component-controls/core';
import camelCase from 'camelcase';
import { File } from '@babel/types';
import traverse from '@babel/traverse';
import { extractFunctionParameters } from './extract-function-parameters';
import { followStoryImport } from './follow-imports';
import { extractAttributes } from './extract-attributes';
import { sourceLocation } from '../misc/source-location';
import { ParseStorieReturnType, InstrumentOptions, ExportType } from '../types';
import {
ParseStorieReturnType,
InstrumentOptions,
MDXExportType,
} from '../types';

import { componentsFromParams } from '../misc/component-attributes';

export const extractMDXStories = (
ast: File,
_options: Required<InstrumentOptions>,
{ source, filePath }: { source: string; filePath: string },
): ParseStorieReturnType => {
const collectAttributes = (
node: any,
exports?: ExportType,
exports?: MDXExportType,
): StoryParameters => {
return node.attributes.reduce((acc: StoryParameters, attribute: any) => {
if (exports) {
Expand Down Expand Up @@ -92,21 +100,73 @@ export const extractMDXStories = (
const { name } = attributes;
if (typeof name === 'string') {
if (store.stories[name]) {
throw new Error(`Dusplaicated story name ${name}`);
throw new Error(`Duplicated story name ${name}`);
}
const id = camelCase(name);
const story: Story = {
const expression =
Array.isArray(path.node.children) &&
path.node.children.find(
(child: any) => child.type === 'JSXExpressionContainer',
);
let story: Story = {
...attributes,
name,
id,
loc: sourceLocation(path.node.loc),
};
if (
expression &&
(expression.expression.type === 'CallExpression' ||
(expression.expression.type === 'ArrowFunctionExpression' &&
expression.expression.body &&
expression.expression.body.callee))
) {
const importedName =
expression.expression.type === 'CallExpression'
? expression.expression.callee.name
: expression.expression.body.callee.name;
const followStory = followStoryImport(
name,
importedName,
filePath,
source,
_options,
ast,
exports,
);
story = { ...followStory, ...story };
} else {
const loc: CodeLocation = path.node.children.length
? expression
? expression.expression.loc
: {
start: path.node.children[0].loc.start,
end:
path.node.children[path.node.children.length - 1].loc
.end,
}
: path.node.loc;
// adjust for source code wraping issues
const storySource = getASTSource(source, loc);
const storyLines = storySource?.split('\n');
//remove as many spaces as there are in the first non-empty line
const firstLine = storyLines?.find(
line => line.trim().length > 0,
);
const whiteSpaces = firstLine ? firstLine.search(/\S/) : 0;
story.loc = sourceLocation(loc);
story.source = storyLines
? storyLines
.map(line => line.substr(whiteSpaces))
.join('\n')
.trim()
: storySource;
}
const component = collectComponent(attributes);
if (component) {
story.component = component;
}
traverse(
path.node,
expression || path.node,
extractFunctionParameters(story, exports),
path.scope,
path,
Expand Down
Loading

0 comments on commit f4ca871

Please sign in to comment.