Skip to content

Commit

Permalink
refactor(kit): refactor property checks (vuejs#554)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1030 authored Jul 31, 2024
1 parent b217a03 commit 5e60d64
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 22 deletions.
10 changes: 7 additions & 3 deletions packages/devtools-kit/src/core/component/state/custom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InspectorState, customTypeEnums } from '../types'
import { getComponentName, getInstanceName } from '../utils'
import { ensurePropertyExists, getComponentName, getInstanceName } from '../utils'
import { processInstanceState } from './process'
import { escape, getSetupStateType, toRaw } from './util'

Expand Down Expand Up @@ -225,7 +225,11 @@ export function getObjectDetails(object: Record<string, any>) {
if (isState) {
const stateTypeName = info.computed ? 'Computed' : info.ref ? 'Ref' : info.reactive ? 'Reactive' : null
const value = toRaw(info.reactive ? object : object._value)
const raw = object.effect?.raw?.toString() || object.effect?.fn?.toString()

const raw = ensurePropertyExists(object, 'effect')
? object.effect?.raw?.toString() || object.effect?.fn?.toString()
: null

return {
_custom: {
type: stateTypeName?.toLowerCase(),
Expand All @@ -236,7 +240,7 @@ export function getObjectDetails(object: Record<string, any>) {
}
}

if (typeof object.__asyncLoader === 'function') {
if (ensurePropertyExists(object, '__asyncLoader') && typeof object.__asyncLoader === 'function') {
return {
_custom: {
type: 'component-definition' satisfies customTypeEnums,
Expand Down
14 changes: 11 additions & 3 deletions packages/devtools-kit/src/core/component/state/is.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
export function isVueInstance(value: Record<string, unknown>) {
return value._ && Object.keys(value._).includes('vnode')
import { ensurePropertyExists } from '../utils'

export function isVueInstance(value: any) {
if (!ensurePropertyExists(value, '_')) {
return false
}
if (!isPlainObject(value._)) {
return false
}
return Object.keys(value._).includes('vnode')
}

export function isPlainObject(obj: unknown) {
export function isPlainObject(obj: unknown): obj is object {
return Object.prototype.toString.call(obj) === '[object Object]'
}

Expand Down
10 changes: 6 additions & 4 deletions packages/devtools-kit/src/core/component/state/process.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { camelize } from '@vue/devtools-shared'
import type { VueAppInstance } from '../../../types'
import type { InspectorState } from '../types'
import { returnError } from '../utils'
import { ensurePropertyExists, returnError } from '../utils'
import { vueBuiltins } from './constants'
import { getPropType, getSetupStateType, toRaw } from './util'

Expand Down Expand Up @@ -151,8 +151,8 @@ function processSetupState(instance: VueAppInstance) {
let result: Partial<InspectorState>

let isOtherType = typeof value === 'function'
|| typeof value?.render === 'function' // Components
|| typeof value?.__asyncLoader === 'function' // Components
|| (ensurePropertyExists(value, 'render') && typeof value.render === 'function') // Components
|| (ensurePropertyExists(value, '__asyncLoader') && typeof value.__asyncLoader === 'function') // Components
|| (typeof value === 'object' && value && ('setup' in value || 'props' in value)) // Components
|| /^v[A-Z]/.test(key) // Directives

Expand All @@ -161,7 +161,9 @@ function processSetupState(instance: VueAppInstance) {

const { stateType, stateTypeName } = getStateTypeAndName(info)
const isState = info.ref || info.computed || info.reactive
const raw = rawData.effect?.raw?.toString() || rawData.effect?.fn?.toString()
const raw = ensurePropertyExists(rawData, 'effect')
? rawData.effect?.raw?.toString() || rawData.effect?.fn?.toString()
: null

if (stateType)
isOtherType = false
Expand Down
13 changes: 5 additions & 8 deletions packages/devtools-kit/src/core/component/state/replacer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ensurePropertyExists } from '../utils'
import { INFINITY, MAX_ARRAY_SIZE, MAX_STRING_SIZE, NAN, NEGATIVE_INFINITY, UNDEFINED } from './constants'
import { getBigIntDetails, getComponentDefinitionDetails, getDateDetails, getFunctionDetails, getHTMLElementDetails, getInstanceDetails, getMapDetails, getObjectDetails, getRouterDetails, getSetDetails, getStoreDetails } from './custom'
import { isVueInstance } from './is'
Expand Down Expand Up @@ -69,8 +70,7 @@ export function stringifyReplacer(key: string | number, _value: any, depth?: num
else if (proto === '[object Error]') {
return `[native Error ${(val as Error).message}<>${(val as Error).stack}]`
}
// @ts-expect-error skip type check
else if (val.state && val._vm) {
else if (ensurePropertyExists(val, 'state', true) && ensurePropertyExists(val, '_vm', true)) {
return getStoreDetails(val)
}
else if (val.constructor && val.constructor.name === 'VueRouter') {
Expand All @@ -85,8 +85,7 @@ export function stringifyReplacer(key: string | number, _value: any, depth?: num
seenInstance?.set(val, depth!)
return componentVal
}
// @ts-expect-error skip type check
else if (typeof val.render === 'function') {
else if (ensurePropertyExists(val, 'render', true) && typeof val.render === 'function') {
return getComponentDefinitionDetails(val)
}
else if (val.constructor && val.constructor.name === 'VNode') {
Expand All @@ -96,12 +95,10 @@ export function stringifyReplacer(key: string | number, _value: any, depth?: num
else if (typeof HTMLElement !== 'undefined' && val instanceof HTMLElement) {
return getHTMLElementDetails(val)
}
// @ts-expect-error skip type check
else if (val.constructor?.name === 'Store' && val._wrappedGetters) {
else if (val.constructor?.name === 'Store' && '_wrappedGetters' in val) {
return '[object Store]'
}
// @ts-expect-error skip type check
else if (val.currentRoute) {
else if (ensurePropertyExists(val, 'currentRoute', true)) {
return '[object Router]'
}
const customDetails = getObjectDetails(val)
Expand Down
9 changes: 9 additions & 0 deletions packages/devtools-kit/src/core/component/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,12 @@ export function getComponentInstance(appRecord: AppRecord, instanceId: string |
// @TODO: find a better way to handle it
return instance || appRecord.instanceMap.get(':root')
}

// #542, should use 'in' operator to check if the key exists in the object
export function ensurePropertyExists<R = Record<string, unknown>>(obj: unknown, key: string, skipObjCheck = false): obj is R {
return skipObjCheck
? key in (obj as object)
: typeof obj === 'object' && obj !== null
? key in obj
: false
}
8 changes: 4 additions & 4 deletions packages/devtools-kit/src/shared/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ function encode(data: unknown, replacer: Replacer | null, list: unknown[], seen:
const keys = Object.keys(data)
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i]
// fix vue warn for compilerOptions passing-options-to-vuecompiler-sfc
// @TODO: need to check if it will cause any other issues
if (key === 'compilerOptions')
return index
value = data[key]
const isVm = value != null && isObject(value, Object.prototype.toString.call(data)) && isVueInstance(value)
try {
// fix vue warn for compilerOptions passing-options-to-vuecompiler-sfc
// @TODO: need to check if it will cause any other issues
if (key === 'compilerOptions')
return index
if (replacer) {
value = replacer.call(data, key, value, depth, seenVueInstance)
}
Expand Down

0 comments on commit 5e60d64

Please sign in to comment.