Skip to content

Commit

Permalink
Fix infinite loop with special characters in kind names (#6607)
Browse files Browse the repository at this point in the history
Fix infinite loop with special characters in kind names
  • Loading branch information
shilman authored Apr 26, 2019
2 parents 8aae29d + dabc161 commit a85decf
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 37 deletions.
32 changes: 13 additions & 19 deletions lib/api/src/modules/stories.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toId, sanitize } from '@storybook/router/dist/utils';
// FIXME: we shouldn't import from dist but there are no types otherwise
import { toId, sanitize, parseKind } from '@storybook/router';

import { Module } from '../index';
import merge from '../lib/merge';
Expand Down Expand Up @@ -27,11 +28,6 @@ export interface SubAPI {
getParameters: (storyId: StoryId, parameterName?: ParameterName) => Story['parameters'] | any;
}

interface SeparatorOptions {
rootSeparator: string | RegExp;
groupSeparator: string | RegExp;
}

interface Group {
id: StoryId;
name: string;
Expand Down Expand Up @@ -164,17 +160,6 @@ const initStoriesApi = ({
navigate(`/${viewMode || 'story'}/${result}`);
};

const splitPath = (kind: string, { rootSeparator, groupSeparator }: SeparatorOptions) => {
const [root, remainder] = kind.split(rootSeparator, 2);
const groups = (remainder || kind).split(groupSeparator).filter(i => !!i);

// when there's no remainder, it means the root wasn't found/split
return {
root: remainder ? root : null,
groups,
};
};

const toKey = (input: string) =>
input.replace(/[^a-z0-9]+([a-z0-9])/gi, (...params) => params[1].toUpperCase());

Expand All @@ -192,11 +177,11 @@ const initStoriesApi = ({
hierarchyRootSeparator: rootSeparator,
hierarchySeparator: groupSeparator,
} = (parameters && parameters.options) || {
hierarchyRootSeparator: '/',
hierarchyRootSeparator: '|',
hierarchySeparator: '/',
};

const { root, groups } = splitPath(kind, { rootSeparator, groupSeparator });
const { root, groups } = parseKind(kind, { rootSeparator, groupSeparator });

const rootAndGroups = []
.concat(root || [])
Expand All @@ -208,6 +193,15 @@ const initStoriesApi = ({
const { name } = group;
const parent = index > 0 && soFar[index - 1].id;
const id = sanitize(parent ? `${parent}-${name}` : name);
if (parent === id) {
throw new Error(
`
Invalid part '${name}', leading to id === parentId ('${id}'), inside kind '${kind}'
Did you create a path that uses the separator char accidentally, such as 'Vue <docs/>' where '/' is a separator char? See https://github.com/storybooks/storybook/issues/6128
`.trim()
);
}

const result: Group = {
...group,
Expand Down
3 changes: 1 addition & 2 deletions lib/client-api/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ClientApi, { defaultDecorateStory } from './client_api';
import StoryStore, { splitPath } from './story_store';
import StoryStore from './story_store';
import ConfigApi from './config_api';
import subscriptionsStore from './subscriptions_store';
import pathToId from './pathToId';
Expand All @@ -13,7 +13,6 @@ export {
subscriptionsStore,
defaultDecorateStory,
pathToId,
splitPath,
getQueryParams,
getQueryParam,
};
11 changes: 0 additions & 11 deletions lib/client-api/src/story_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@ const toKey = input =>

const toChild = it => ({ ...it });

export const splitPath = (path, { rootSeparator, groupSeparator }) => {
const [root, remainder] = path.split(rootSeparator, 2);
const groups = (remainder || path).split(groupSeparator).filter(i => !!i);

// when there's no remainder, it means the root wasn't found/split
return {
root: remainder ? root : null,
groups,
};
};

let count = 0;

function getId() {
Expand Down
4 changes: 2 additions & 2 deletions lib/router/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';

import { Link, Location, navigate, LocationProvider, RouteComponentProps } from '@reach/router';
import { ToggleVisibility } from './visibility';
import { queryFromString, storyDataFromString, getMatch } from './utils';
import { queryFromString, parsePath, getMatch } from './utils';

interface Other {
viewMode?: string;
Expand Down Expand Up @@ -56,7 +56,7 @@ const QueryLocation = ({ children }: QueryLocationProps) => (
<Location>
{({ location }: RouteComponentProps): React.ReactNode => {
const { path } = queryFromString(location.search);
const { viewMode, storyId } = storyDataFromString(path);
const { viewMode, storyId } = parsePath(path);
return children({ path, location, navigate: queryNavigate, viewMode, storyId });
}}
</Location>
Expand Down
22 changes: 19 additions & 3 deletions lib/router/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ interface StoryData {
storyId?: string;
}

interface SeparatorOptions {
rootSeparator: string | RegExp;
groupSeparator: string | RegExp;
}

export const knownNonViewModesRegex = /(settings)/;
const splitPath = /\/([^/]+)\/([^/]+)?/;
const splitPathRegex = /\/([^/]+)\/([^/]+)?/;

// Remove punctuation https://gist.github.com/davidjrice/9d2af51100e41c6c4b4a
export const sanitize = (string: string) => {
Expand All @@ -33,15 +38,15 @@ const sanitizeSafe = (string: string, part: string) => {
export const toId = (kind: string, name: string) =>
`${sanitizeSafe(kind, 'kind')}--${sanitizeSafe(name, 'name')}`;

export const storyDataFromString: (path?: string) => StoryData = memoize(1000)(
export const parsePath: (path?: string) => StoryData = memoize(1000)(
(path: string | undefined | null) => {
const result: StoryData = {
viewMode: undefined,
storyId: undefined,
};

if (path) {
const [, viewMode, storyId] = path.match(splitPath) || [undefined, undefined, undefined];
const [, viewMode, storyId] = path.match(splitPathRegex) || [undefined, undefined, undefined];
if (viewMode && !viewMode.match(knownNonViewModesRegex)) {
Object.assign(result, {
viewMode,
Expand Down Expand Up @@ -76,3 +81,14 @@ export const getMatch = memoize(1000)(
return null;
}
);

export const parseKind = (kind: string, { rootSeparator, groupSeparator }: SeparatorOptions) => {
const [root, remainder] = kind.split(rootSeparator, 2);
const groups = (remainder || kind).split(groupSeparator).filter(i => !!i);

// when there's no remainder, it means the root wasn't found/split
return {
root: remainder ? root : null,
groups,
};
};

1 comment on commit a85decf

@vercel
Copy link

@vercel vercel bot commented on a85decf Apr 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.