diff --git a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts index 4b563e3f86db..46b22f908d65 100644 --- a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts +++ b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts @@ -136,13 +136,6 @@ describe('type', () => { required: true, }, }, - { - key: 'prop5', - value: { - name: 'string', - required: true, - }, - }, { key: 'prop6', value: { @@ -364,3 +357,710 @@ describe('type', () => { expect(type.summary).toBe('number & string'); }); }); + +const properties1 = [ + { + key: 'prop1', + value: { name: 'string', required: true }, + }, +]; +const properties2 = [ + { + key: 'prop2', + value: { name: 'string', required: true }, + }, + { + key: 'prop3', + value: { name: 'string', required: true }, + }, + { + key: 'prop4', + value: { name: 'string', required: true }, + }, + { + key: 'prop5', + value: { name: 'string', required: true }, + }, + { + key: 'prop6', + value: { name: 'string', required: true }, + }, +]; +const properties3 = [ + { + key: 'prop7', + value: { name: 'string', required: false }, + }, +]; +const elements1 = [ + { name: 'literal', value: '"north"' }, + { name: 'literal', value: '"south"' }, + { name: 'literal', value: '"east"' }, + { name: 'literal', value: '"west"' }, +]; +const elements2 = [ + { name: 'literal', value: '"northeast"' }, + { name: 'literal', value: '"northwest"' }, + { name: 'literal', value: '"southeast"' }, + { name: 'literal', value: '"southwest"' }, +]; +const elements3 = [ + { + name: 'signature', + type: 'object', + signature: { + properties: properties1, + }, + }, +]; +const arguments1 = [ + { + name: 'arg1', + type: { name: 'string' }, + }, +]; + +describe('summary', () => { + it('prefers generated summary when not too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: 'Array', + elements: [ + { + name: 'union', + elements: elements1, + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array<"north" | "south" | "east" | "west">'); + }); + + it('uses `raw` for summary if generated details are too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: 'Array', + elements: [ + { + name: 'union', + elements: [...elements1, ...elements2], + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + }); + + it('falls back to `type` for summary if details/raw are too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: + '{ prop1: string, prop2: string, prop3: string, prop4: string, prop5: string, prop6: string }', + signature: { + properties: [...properties1, ...properties2], + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('object'); + }); + + it('falls back to `type` for summary if no `raw` & details are too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + signature: { + properties: [...properties1, ...properties2], + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('object'); + }); + + it('falls back to `name` for summary if no `type` & details/raw are too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: + 'Array<"north" | "northeast" | "northwest" | "south" | "southeast" | "southwest" | "east" | "west">', + elements: [ + { + name: 'union', + elements: [...elements1, ...elements2], + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + }); + + it('falls back to `name` for summary if no `type` or `raw` & details are too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + elements: [ + { + name: 'union', + elements: [...elements1, ...elements2], + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + }); +}); + +describe('literals', () => { + it('falls back to `name` if no value', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'literal', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('literal'); + expect(type.detail).toBeUndefined(); + }); + + it('uses the `value` if available', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'literal', + value: '"foobar"', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('"foobar"'); + expect(type.detail).toBeUndefined(); + }); +}); + +describe('union generation', () => { + it('falls back to `raw` if no elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: 'Directions', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Directions'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `raw` if empty elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: 'Directions', + elements: [], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Directions'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `name` if no elements nor raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('union'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `name` if empty elements & no raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + elements: [], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('union'); + expect(type.detail).toBeUndefined(); + }); + + it('detects characters that will make it "unsafe to split" in ArgsTable', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: 'Directions | { prop1: string }', + elements: [...elements1, ...elements3], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Directions | { prop1: string }'); + expect(type.detail).toBe('"north" | "south" | "east" | "west" | { prop1: string }'); + }); + + // This is because they are split into tokens by ArgsTable + it('never checks the generated details for being too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: 'Directions', + elements: [...elements1, ...elements2], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe( + '"north" | "south" | "east" | "west" | "northeast" | "northwest" | "southeast" | "southwest"' + ); + expect(type.detail).toBeUndefined(); + }); +}); + +describe('intersection generation', () => { + it('falls back to `raw` if no elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'intersection', + raw: 'Directions', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Directions'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `raw` if empty elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'intersection', + raw: 'Directions', + elements: [], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Directions'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `name` if no elements nor raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'intersection', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('intersection'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `name` if empty elements & no raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'intersection', + elements: [], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('intersection'); + expect(type.detail).toBeUndefined(); + }); + + it('never checks the generated details for being too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'intersection', + raw: 'Directions', + elements: [...elements1, ...elements2], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe( + '"north" & "south" & "east" & "west" & "northeast" & "northwest" & "southeast" & "southwest"' + ); + expect(type.detail).toBeUndefined(); + }); +}); + +describe('array generation', () => { + it('falls back to `raw` if no elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: 'Array', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `raw` if empty elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: 'Array', + elements: [], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `name` if no elements nor raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to `name` if empty elements & no raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + elements: [], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + expect(type.detail).toBeUndefined(); + }); + + it('can use generated value for summary', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: 'Array', + elements: [ + { + name: 'union', + elements: elements1, + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array<"north" | "south" | "east" | "west">'); + expect(type.detail).toBeUndefined(); + }); + + it('uses generated value for details if too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'Array', + raw: 'Array', + elements: [ + { + name: 'union', + elements: [...elements1, ...elements2], + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('Array'); + expect(type.detail).toBe( + 'Array<"north" | "south" | "east" | "west" | "northeast" | "northwest" | "southeast" | "southwest">' + ); + }); +}); + +describe('object generation', () => { + it('falls back to `raw` if no signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{}', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{}'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to empty object literal if no signature nor raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{}'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to empty object literal if no parameters in signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{\n\n}', + signature: {}, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{}'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to empty object literal if empty parameters in signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{\n\n}', + signature: { + parameters: [], + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{}'); + expect(type.detail).toBeUndefined(); + }); + + it('can use generated value for summary', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{ prop1: StringAlias }', + signature: { + properties: properties1, + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{ prop1: string }'); + expect(type.detail).toBeUndefined(); + }); + + it('uses generated value for details if too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{ prop1: string, ...otherProps }', + signature: { + properties: [...properties1, ...properties2], + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{ prop1: string, ...otherProps }'); + expect(type.detail).toBe( + '{ prop1: string, prop2: string, prop3: string, prop4: string, prop5: string, prop6: string }' + ); + }); + + it('also generates requiredness on properties', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{ prop1: string, prop7?: string }', + signature: { + properties: [...properties1, ...properties3], + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{ prop1: string, prop7?: string }'); + expect(type.detail).toBeUndefined(); + }); +}); + +describe('function generation', () => { + it('falls back to `raw` if no signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + raw: '(foo) => bar', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('(foo) => bar'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to void arrow function if no signature nor raw', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('() => void'); + expect(type.detail).toBeUndefined(); + }); + + it('falls back to void arrow function if no arguments or return in signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + raw: '(foo) => bar', + signature: {}, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('() => void'); + expect(type.detail).toBeUndefined(); + }); + + it('does not need arguments in signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + raw: '() => bar', + signature: { + return: { name: 'string' }, + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('() => string'); + expect(type.detail).toBeUndefined(); + }); + + it('can use generated value for summary', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + raw: '(foo) => bar', + signature: { + arguments: arguments1, + return: { name: 'string' }, + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('(arg1: string) => string'); + expect(type.detail).toBeUndefined(); + }); + + it('uses generated value for details if too long', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + raw: '(arg: BigObjProp) => string', + signature: { + arguments: [ + { + name: 'arg', + type: { + name: 'signature', + type: 'object', + raw: '{ prop1: string, ...otherProps }', + signature: { + properties: [...properties1, ...properties2], + }, + }, + }, + ], + return: { name: 'string' }, + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('(arg: BigObjProp) => string'); + expect(type.detail).toBe( + '(arg: { prop1: string, prop2: string, prop3: string, prop4: string, prop5: string, prop6: string }) => string' + ); + }); +}); diff --git a/addons/docs/src/lib/docgen/flow/createType.ts b/addons/docs/src/lib/docgen/flow/createType.ts index 2e9d86a93a4f..e297b9e24685 100644 --- a/addons/docs/src/lib/docgen/flow/createType.ts +++ b/addons/docs/src/lib/docgen/flow/createType.ts @@ -1,94 +1,120 @@ import { PropType } from '@storybook/components'; import { DocgenFlowType } from '../types'; -import { createSummaryValue, isTooLongForTypeSummary } from '../../utils'; +import { createSummaryValue, isTooLongForTypeSummary, isUnsafeToSplit } from '../../utils'; enum FlowTypesType { UNION = 'union', + INTERSECTION = 'intersection', SIGNATURE = 'signature', + LITERAL = 'literal', + ARRAY = 'Array', } -interface DocgenFlowUnionElement { - name: string; - value?: string; - elements?: DocgenFlowUnionElement[]; - raw?: string; +interface FlowTypeObjectParameters { + key: string | DocgenFlowType; + value: DocgenFlowType; } -interface DocgenFlowUnionType extends DocgenFlowType { - elements: DocgenFlowUnionElement[]; +interface FlowTypeFunctionArguments { + name?: string; + type: DocgenFlowType; } -function generateUnionElement({ name, value, elements, raw }: DocgenFlowUnionElement): string { - if (value != null) { - return value; +function getSummary({ raw, type, name }: DocgenFlowType) { + if (raw == null || isTooLongForTypeSummary(raw)) { + return type || name; } - if (elements != null) { - return elements.map(generateUnionElement).join(' | '); - } - - if (raw != null) { - return raw; - } - - return name; + return raw.replace(/^\|\s*/, ''); } -function generateUnion({ name, raw, elements }: DocgenFlowUnionType): PropType { - if (elements != null) { - return createSummaryValue(elements.map(generateUnionElement).join(' | ')); +function generateObjectDetail({ signature, raw }: DocgenFlowType): string { + if (!signature) { + return raw || '{}'; } - if (raw != null) { - // Flow Unions can be defined with or without a leading `|` character, so try to remove it. - return createSummaryValue(raw.replace(/^\|\s*/, '')); + const { constructor, properties = [] } = signature; + const generatedProperties = properties.map(({ key, value }: FlowTypeObjectParameters) => { + let propKey = key; + if (typeof key !== 'string') { + propKey = `[${generateDetail(key)}]`; + } + const requiredness = value.required === false ? '?' : ''; + return `${propKey}${requiredness}: ${generateDetail(value)}`; + }); + + // The constructor property is different than other signatures so we just use the raw value + if (constructor && constructor.raw) { + generatedProperties.unshift(constructor.raw); } - return createSummaryValue(name); + return generatedProperties.length ? `{ ${generatedProperties.join(', ')} }` : '{}'; } -function generateFuncSignature({ type, raw }: DocgenFlowType): PropType { - if (raw != null) { - return createSummaryValue(raw); +function generateFuncDetail({ signature, raw }: DocgenFlowType): string { + if (!signature) { + return raw || '() => void'; } - return createSummaryValue(type); + const { arguments: argumentsValues = [], return: returnValue = { name: 'void' } } = signature; + const generatedArguments = argumentsValues.map(({ name, type }: FlowTypeFunctionArguments) => { + return name ? `${name}: ${generateDetail(type)}` : generateDetail(type); + }); + + return `(${generatedArguments.join(', ')}) => ${generateDetail(returnValue)}`; } -function generateObjectSignature({ type, raw }: DocgenFlowType): PropType { - if (raw != null) { - return !isTooLongForTypeSummary(raw) ? createSummaryValue(raw) : createSummaryValue(type, raw); +function generateArrayDetail({ elements }: DocgenFlowType): string { + if (!Array.isArray(elements) || elements.length === 0) { + return ''; } - return createSummaryValue(type); + return `Array<${elements.map(generateDetail).join(' | ')}>`; } -function generateSignature(flowType: DocgenFlowType): PropType { - const { type } = flowType; - - return type === 'object' ? generateObjectSignature(flowType) : generateFuncSignature(flowType); -} +function generateDetail(flowType: DocgenFlowType): string | null { + const { name, type, value, raw, elements = [] } = flowType; -function generateDefault({ name, raw }: DocgenFlowType): PropType { - if (raw != null) { - return !isTooLongForTypeSummary(raw) ? createSummaryValue(raw) : createSummaryValue(name, raw); + switch (name) { + case FlowTypesType.LITERAL: + return value; + case FlowTypesType.UNION: + return elements.map(generateDetail).join(' | '); + case FlowTypesType.INTERSECTION: + return elements.map(generateDetail).join(' & '); + case FlowTypesType.SIGNATURE: + return type === 'object' ? generateObjectDetail(flowType) : generateFuncDetail(flowType); + case FlowTypesType.ARRAY: + return generateArrayDetail(flowType); + default: + return raw || type || name; } - - return createSummaryValue(name); } -export function createType(type: DocgenFlowType): PropType { +export function createType(flowType: DocgenFlowType): PropType { // A type could be null if a defaultProp has been provided without a type definition. - if (type == null) { + if (flowType == null) { return null; } - switch (type.name) { + const summary = getSummary(flowType); + const detail = generateDetail(flowType); + + if (!detail) { + return createSummaryValue(summary); + } + + switch (flowType.name) { case FlowTypesType.UNION: - return generateUnion(type as DocgenFlowUnionType); + return isUnsafeToSplit(detail) + ? createSummaryValue(summary, detail) + : createSummaryValue(detail); case FlowTypesType.SIGNATURE: - return generateSignature(type); + case FlowTypesType.ARRAY: + return isTooLongForTypeSummary(detail) + ? createSummaryValue(summary, detail) + : createSummaryValue(detail); default: - return generateDefault(type); + return createSummaryValue(detail); } } diff --git a/addons/docs/src/lib/docgen/types.ts b/addons/docs/src/lib/docgen/types.ts index f48dd0e3a6e0..710b503e36fe 100644 --- a/addons/docs/src/lib/docgen/types.ts +++ b/addons/docs/src/lib/docgen/types.ts @@ -23,6 +23,7 @@ export interface DocgenFlowType extends DocgenType { raw?: string; signature?: any; elements?: any[]; + value?: any; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/addons/docs/src/lib/utils.test.ts b/addons/docs/src/lib/utils.test.ts index 0cd887200ded..8e119958b791 100644 --- a/addons/docs/src/lib/utils.test.ts +++ b/addons/docs/src/lib/utils.test.ts @@ -1,4 +1,4 @@ -import { createSummaryValue } from './utils'; +import { createSummaryValue, isUnsafeToSplit } from './utils'; describe('createSummaryValue', () => { it('creates an object with just summary if detail is not passed', () => { @@ -18,3 +18,51 @@ describe('createSummaryValue', () => { expect(createSummaryValue(summary, detail)).toEqual({ summary }); }); }); + +describe('isUnsafeToSplit', () => { + describe('reports false', () => { + it('basic union', () => { + const value = '"foo" | "bar" | "baz"'; + expect(isUnsafeToSplit(value)).toEqual(false); + }); + }); + + describe('reports true', () => { + it('union including an object', () => { + const value = '"foo" | "bar" | { baz: string }'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + + it('union including a function', () => { + const value = '"foo" | "bar" | () => void'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + + it('union including an array', () => { + const value = '"foo" | "bar" | string[]'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + + it('union including a generic', () => { + const value = '"foo" | "bar" | Class'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + }); + + describe('false positives', () => { + it('function with an internal union', () => { + const value = '() => "foo" | "bar"'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + + it('object with an internal union', () => { + const value = '{ foo: "bar" | "baz" }'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + + it('array with an internal union', () => { + const value = 'Array<"bar" | "baz">'; + expect(isUnsafeToSplit(value)).toEqual(true); + }); + }); +}); diff --git a/addons/docs/src/lib/utils.ts b/addons/docs/src/lib/utils.ts index b623c12e19b7..a0d647453295 100644 --- a/addons/docs/src/lib/utils.ts +++ b/addons/docs/src/lib/utils.ts @@ -11,6 +11,10 @@ export function isTooLongForDefaultValueSummary(value: string): boolean { return value.length > MAX_DEFAULT_VALUE_SUMMARY_LENGTH; } +export function isUnsafeToSplit(summary: string): boolean { + return /[(){}[\]<>]/.test(summary); +} + export function createSummaryValue(summary: string, detail?: string): PropSummaryValue { if (summary === detail) { return { summary };