Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: recursive object analysis (fix #118) #119

Merged
merged 2 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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