Skip to content

Commit

Permalink
fix: recursive object analysis (fix #118) (#119)
Browse files Browse the repository at this point in the history
* Fix recursive object analysis

* Add seen set
  • Loading branch information
hugoattal authored Jun 6, 2022
1 parent 6cb9aca commit 88ecbbf
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 16 deletions.
11 changes: 10 additions & 1 deletion examples/vue3/src/components/ComplexParameter.story.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<script lang="ts" setup>
import ComplexParameter from './ComplexParameter.vue'
const parent = { name: 'hello' } as { name: string } & Record<string, unknown>
const child = {} as Record<string, unknown>
parent.child = child
child.parent = parent
const myParameter = [
{ action: () => { console.log('Hello world!') } },
]
Expand All @@ -11,6 +17,9 @@ const myParameter = [
title="ComplexParameter"
:layout="{type: 'single', iframe: false}"
>
<ComplexParameter :complex-parameter="myParameter" />
<ComplexParameter
:complex-parameter="myParameter"
:recursive-parameter="parent"
/>
</Story>
</template>
4 changes: 4 additions & 0 deletions examples/vue3/src/components/ComplexParameter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineProps } from 'vue'
const props = defineProps<{
complexParameter: Array<{action: () => void}>
recursiveParameter: {name: string} & Record<string, unknown>
}>()
</script>

Expand All @@ -13,4 +14,7 @@ const props = defineProps<{
>
Custom click
</div>
<div>
{{ recursiveParameter.name }}
</div>
</template>
34 changes: 24 additions & 10 deletions packages/histoire/src/client/app/codegen/serialize-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ interface Line {
}

export function serializeJs (value: any): string {
const seen = new Set()

if (value === undefined) {
return 'undefined'
}
Expand All @@ -20,10 +22,10 @@ export function serializeJs (value: any): string {
return value ? 'true' : 'false'
}
if (Array.isArray(value)) {
return printLines(arrayToSourceLines(value))
return printLines(arrayToSourceLines(value, seen))
}
if (typeof value === 'object') {
return printLines(objectToSourceLines(value))
return printLines(objectToSourceLines(value, seen))
}
if (value?.__autoBuildingObject) {
return value
Expand All @@ -38,7 +40,13 @@ function printLines (lines: Line[]) {
return lines.map(line => ' '.repeat(line.spaces) + line.line).join('\n')
}

function objectToSourceLines (object, indentCount = 0) {
function objectToSourceLines (object, seen: Set<unknown>, indentCount = 0) {
if (seen.has(object)) {
object = {}
} else {
seen.add(object)
}

return createLines(indentCount, lines => {
lines.push('{')
lines.push(...createLines(1, lines => {
Expand All @@ -50,18 +58,24 @@ function objectToSourceLines (object, indentCount = 0) {
printedKey = `'${printedKey}'`
}

addLinesFromValue(lines, value, `${printedKey}: `, ',')
addLinesFromValue(lines, value, `${printedKey}: `, ',', seen)
}
}))
lines.push('}')
})
}

function arrayToSourceLines (array: any[], indentCount = 0) {
function arrayToSourceLines (array: any[], seen: Set<unknown>, indentCount = 0): Array<Line> {
if (seen.has(array)) {
array = []
} else {
seen.add(array)
}

return createLines(indentCount, lines => {
const contentLines = createLines(1, lines => {
for (const value of array) {
addLinesFromValue(lines, value, '', ',')
addLinesFromValue(lines, value, '', ',', seen)
}
})
if (contentLines.length === 0) {
Expand All @@ -78,7 +92,7 @@ function arrayToSourceLines (array: any[], indentCount = 0) {
})
}

function createLines (indentCount: number, handler: (lines: any[]) => unknown) {
function createLines (indentCount: number, handler: (lines: any[]) => unknown): Array<Line> {
const lines: any[] = []
handler(lines)
return lines.map(line => {
Expand All @@ -90,13 +104,13 @@ function createLines (indentCount: number, handler: (lines: any[]) => unknown) {
})
}

function addLinesFromValue (lines: Line[], value, before, after) {
function addLinesFromValue (lines: Line[], value, before, after, seen) {
let result
if (Array.isArray(value)) {
lines.push(...wrap(arrayToSourceLines(value), before, after))
lines.push(...wrap(arrayToSourceLines(value, seen), before, after))
return
} else if (value && typeof value === 'object') {
lines.push(...wrap(objectToSourceLines(value), before, after))
lines.push(...wrap(objectToSourceLines(value, seen), before, after))
return
} else if (typeof value === 'string') {
result = value.includes('\'') ? `\`${value}\`` : `'${value}'`
Expand Down
16 changes: 11 additions & 5 deletions packages/histoire/src/client/app/util/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@ import type { Variant } from '../types'

const isObject = (val) => val !== null && typeof val === 'object'

export function toRawDeep (val) {
export function toRawDeep (val, seen = new Set()) {
const unwrappedValue = isRef(val) ? unref(val) : val

if (seen.has(unwrappedValue)) {
return Array.isArray(unwrappedValue) ? [] : {}
}

seen.add(unwrappedValue)

if (!isObject(unwrappedValue)) {
return unwrappedValue
}

if (Array.isArray(unwrappedValue)) {
return unwrappedValue.map(toRawDeep)
return unwrappedValue.map(value => toRawDeep(value, seen))
}

return toRawObject(unwrappedValue)
return toRawObject(unwrappedValue, seen)
}

const toRawObject = (obj: Record<any, any>) => Object.keys(obj).reduce((raw, key) => {
raw[key] = toRawDeep(obj[key])
const toRawObject = (obj: Record<any, any>, seen = new Set()) => Object.keys(obj).reduce((raw, key) => {
raw[key] = toRawDeep(obj[key], seen)
return raw
}, {})

Expand Down

0 comments on commit 88ecbbf

Please sign in to comment.