Skip to content

Commit

Permalink
feat: parse component import into stories
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Feb 26, 2020
1 parent b17ba66 commit 04c986a
Show file tree
Hide file tree
Showing 16 changed files with 1,505 additions and 177 deletions.
85 changes: 85 additions & 0 deletions core/instrument/src/babel/extract-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import traverse from '@babel/traverse';
import {
StoriesGroup,
StoryArguments,
StoryComponent,
} from '@component-controls/specification';

const componentFromParams = (
parameters?: StoryArguments,
): string | undefined => {
if (parameters) {
let component = parameters.find(p => p.name === 'component');
if (!component) {
const params = parameters.find(p => p.name === 'parameters');
if (params) {
component = (params.value as StoryArguments).find(
p => p.name === 'component',
);
}
}
if (component) {
if (typeof component.value === 'string') {
return component.value as string;
}
if (
Array.isArray(component.value) &&
component.value.length > 0 &&
typeof component.value[0].value === 'string'
) {
return component.value[0].value;
}
}
}
return undefined;
};

const findComponentImport = (
ast: any,
componentName: string,
): StoryComponent | undefined => {
let result: StoryComponent | undefined = undefined;
traverse(ast, {
ImportDeclaration: (path: any) => {
const node = path.node;
for (let i = 0; i < node.specifiers.length; i += 1) {
const specifier = node.specifiers[i];
if (specifier.local && specifier.local.name === componentName) {
result = {
name: specifier.local.name,
imported: specifier.imported
? specifier.imported.name
: specifier.type === 'ImportDefaultSpecifier'
? 'default'
: undefined,
from: node.source ? node.source.value : undefined,
loc: node.loc,
};
path.skip();
break;
}
}
},
});
return result;
};

export const extractComponent = (ast: any, kind: StoriesGroup) => {
const componentName = componentFromParams(kind.parameters);
if (componentName) {
const component = findComponentImport(ast, componentName);
if (component) {
kind.component = component;
}
}
Object.keys(kind.stories).forEach(name => {
const story = kind.stories[name];
const componentName = componentFromParams(story.parameters);
if (componentName) {
const component = findComponentImport(ast, componentName);
if (component) {
story.component = component;
}
}
});
};
104 changes: 60 additions & 44 deletions core/instrument/src/babel/extract-properties.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,65 @@
import { StoryArguments } from '@component-controls/specification';
import {
StoryArguments,
StoryArgument,
} from '@component-controls/specification';
import { sourceLocation } from './utils';

export const extractProperties = (node: any): StoryArguments | undefined => {
if (node && node.properties) {
const properties: StoryArguments = node.properties
.map((property: any) => {
if (property.value) {
switch (property.value.type) {
case 'BooleanLiteral':
case 'NumericLiteral':
case 'StringLiteral': {
return {
value: property.value.value,
name: property.key.name,
loc: property.loc,
};
}
case 'Identifier':
return {
value: property.value.name,
name: property.key.name,
loc: property.loc,
};
case 'MemberExpression':
return {
value: `${property.value.object.name}.${property.value.property.name}`,
name: property.key.name,
loc: property.loc,
};

case 'ObjectExpression': {
return {
value: extractProperties(property.value),
name: property.key.name,
loc: property.loc,
};
}
default:
// console.log(property.value);
return null;
}
const nodeToParameter = (node: any): StoryArgument | undefined => {
const value = node.value || node;
const name = node.key ? node.key.name : node.name;
if (value) {
switch (value.type) {
case 'BooleanLiteral':
case 'NumericLiteral':
case 'StringLiteral': {
return {
value: value.value,
name,
loc: sourceLocation(node.loc),
};
}
case 'Identifier':
return {
value: value.name,
name,
loc: sourceLocation(node.loc),
};
case 'MemberExpression':
return {
value: `${value.object.name}.${value.property.name}`,
name,
loc: sourceLocation(node.loc),
};
case 'ObjectExpression': {
const val = extractProperties(value);
if (val) {
return {
value: val,
name,
loc: sourceLocation(node.loc),
};
}
return null;
})
.filter((p: any) => p);
return properties;
break;
}
default:
// console.log(property.value);
return undefined;
}
}
return undefined;
};
export const extractProperties = (node: any): StoryArguments | undefined => {
if (node) {
if (node.properties) {
const properties: StoryArguments = node.properties
.map((propNode: any) => nodeToParameter(propNode))
.filter((p: any) => p);
return properties;
}
const parameter = nodeToParameter(node);
if (parameter) {
return [parameter];
}
}
return undefined;
};
30 changes: 10 additions & 20 deletions core/instrument/src/babel/mdx-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
} from '@component-controls/specification';
import traverse from '@babel/traverse';
import { extractFunctionParameters } from './get-function-parameters';
import { extractProperties } from './extract-properties';
import { sourceLocation } from './utils';

export const extractMDXStories = (stories: StoriesGroup) => {
return {
Expand All @@ -14,23 +16,19 @@ export const extractMDXStories = (stories: StoriesGroup) => {
if (['Meta', 'Story'].indexOf(node.name.name) > -1) {
const parameters: StoryArguments = node.attributes
.map((attribute: any) => {
const loc: CodeLocation = {
start: {
column: attribute.loc?.start.column,
line: attribute.loc?.start.line,
},
end: {
column: attribute.loc?.end.column,
line: attribute.loc?.end.line,
},
};
const loc: CodeLocation = sourceLocation(attribute.loc);
if (attribute.value.type === 'StringLiteral') {
return {
value: attribute.value?.value,
name: attribute.name?.name,
loc,
};
} else if (attribute.value.type === 'JSXExpressionContainer') {
return {
name: attribute.name?.name,
value: extractProperties(attribute.value.expression),
loc,
};
}
return null;
})
Expand All @@ -39,16 +37,7 @@ export const extractMDXStories = (stories: StoriesGroup) => {
switch (node.name.name) {
case 'Story': {
const story: Story = {
loc: {
start: {
column: path.node.loc.start.column,
line: path.node.loc.start.line,
},
end: {
column: path.node.loc.end.column,
line: path.node.loc.end.line,
},
},
loc: sourceLocation(path.node.loc),
};
const name = parameters.find(p => p.name === 'name');

Expand All @@ -60,6 +49,7 @@ export const extractMDXStories = (stories: StoriesGroup) => {
path,
);
story.name = name.value;
story.parameters = parameters;
stories.stories[name.value] = story;
}
break;
Expand Down
5 changes: 5 additions & 0 deletions core/instrument/src/babel/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ export const adjustSourceLocation = (
},
};
};

export const sourceLocation = (loc: CodeLocation): CodeLocation => ({
start: { ...loc.start },
end: { ...loc.end },
});
5 changes: 3 additions & 2 deletions core/instrument/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { StoriesGroup, Story } from '@component-controls/specification';
import { extractCSFStories } from './babel/csf-stories';
import { extractMDXStories } from './babel/mdx-stories';
import { removeMDXAttributes } from './babel/remove-mdx-attributes';
import { extractComponent } from './babel/extract-component';

type TraverseFn = (stories: StoriesGroup) => any;

Expand Down Expand Up @@ -41,16 +42,16 @@ const parseSource = async (
} else {
source = code;
}

const ast = parser.parse(source, {
sourceType: 'module',
plugins: ['jsx', 'typescript'],
});
const stories = {
const stories: StoriesGroup = {
stories: {},
source: originalSource,
};
traverse(ast, traverseFn(stories));
extractComponent(ast, stories);
Object.keys(stories.stories).forEach((key: string) => {
//@ts-ignore
const story: Story = stories.stories[key];
Expand Down
Loading

0 comments on commit 04c986a

Please sign in to comment.