Skip to content

Commit

Permalink
Merge pull request #13263 from storybookjs/13242-fix-cyclic-args
Browse files Browse the repository at this point in the history
Core: Detect arg inference for cyclic args and warn
  • Loading branch information
shilman committed Nov 25, 2020
2 parents bfa65b6 + 11eb614 commit 2f059cb
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 9 deletions.
11 changes: 11 additions & 0 deletions examples/official-storybook/stories/addon-controls.stories.tsx
Expand Up @@ -33,3 +33,14 @@ CustomControls.argTypes = {
};

export const NoArgs = () => <Button>no args</Button>;

const hasCycle: any = {};
hasCycle.cycle = hasCycle;

export const CyclicArgs = Template.bind({});
CyclicArgs.args = {
hasCycle,
};
CyclicArgs.parameters = {
chromatic: { disable: true },
};
36 changes: 27 additions & 9 deletions lib/client-api/src/inferArgTypes.ts
@@ -1,33 +1,51 @@
import mapValues from 'lodash/mapValues';
import dedent from 'ts-dedent';
import { logger } from '@storybook/client-logger';
import { SBType, ArgTypesEnhancer } from './types';
import { combineParameters } from './parameters';

const inferType = (value?: any): SBType => {
const inferType = (value: any, name: string, visited: Set<any>): SBType => {
const type = typeof value;
switch (type) {
case 'boolean':
case 'string':
case 'number':
case 'function':
return { name: type };
case 'symbol':
return { name: 'other', value: 'symbol' };
default:
break;
}
if (Array.isArray(value)) {
const childType: SBType =
value.length > 0 ? inferType(value[0]) : { name: 'other', value: 'unknown' };
return { name: 'array', value: childType };
}
if (value) {
const fieldTypes = mapValues(value, (field) => inferType(field));
if (visited.has(value)) {
logger.warn(dedent`
We've detected a cycle in arg '${name}'. Args should be JSON-serializable (-ish, functions are ok).
More info: https://storybook.js.org/docs/react/essentials/controls#fully-custom-args
`);
return { name: 'other', value: 'cyclic object' };
}
visited.add(value);
if (Array.isArray(value)) {
const childType: SBType =
value.length > 0
? inferType(value[0], name, new Set(visited))
: { name: 'other', value: 'unknown' };
return { name: 'array', value: childType };
}
const fieldTypes = mapValues(value, (field) => inferType(field, name, new Set(visited)));
return { name: 'object', value: fieldTypes };
}
return { name: 'object', value: {} };
};

export const inferArgTypes: ArgTypesEnhancer = (context) => {
const { argTypes: userArgTypes = {}, args = {} } = context.parameters;
const { id, parameters } = context;
const { argTypes: userArgTypes = {}, args = {} } = parameters;
if (!args) return userArgTypes;
const argTypes = mapValues(args, (arg) => ({ type: inferType(arg) }));
const argTypes = mapValues(args, (arg, key) => ({
type: inferType(arg, `${id}.${key}`, new Set()),
}));
return combineParameters(argTypes, userArgTypes);
};

0 comments on commit 2f059cb

Please sign in to comment.