From 5e60d64e25cf70e48643a081472c643a30ac6255 Mon Sep 17 00:00:00 2001 From: Alex <49969959+alexzhang1030@users.noreply.github.com> Date: Wed, 31 Jul 2024 23:41:26 +0800 Subject: [PATCH] refactor(kit): refactor property checks (#554) --- .../src/core/component/state/custom.ts | 10 +++++++--- .../devtools-kit/src/core/component/state/is.ts | 14 +++++++++++--- .../src/core/component/state/process.ts | 10 ++++++---- .../src/core/component/state/replacer.ts | 13 +++++-------- .../devtools-kit/src/core/component/utils/index.ts | 9 +++++++++ packages/devtools-kit/src/shared/transfer.ts | 8 ++++---- 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/packages/devtools-kit/src/core/component/state/custom.ts b/packages/devtools-kit/src/core/component/state/custom.ts index de9eca865..8fbd58a75 100644 --- a/packages/devtools-kit/src/core/component/state/custom.ts +++ b/packages/devtools-kit/src/core/component/state/custom.ts @@ -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' @@ -225,7 +225,11 @@ export function getObjectDetails(object: Record) { 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(), @@ -236,7 +240,7 @@ export function getObjectDetails(object: Record) { } } - if (typeof object.__asyncLoader === 'function') { + if (ensurePropertyExists(object, '__asyncLoader') && typeof object.__asyncLoader === 'function') { return { _custom: { type: 'component-definition' satisfies customTypeEnums, diff --git a/packages/devtools-kit/src/core/component/state/is.ts b/packages/devtools-kit/src/core/component/state/is.ts index a1619564f..ac8423578 100644 --- a/packages/devtools-kit/src/core/component/state/is.ts +++ b/packages/devtools-kit/src/core/component/state/is.ts @@ -1,8 +1,16 @@ -export function isVueInstance(value: Record) { - 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]' } diff --git a/packages/devtools-kit/src/core/component/state/process.ts b/packages/devtools-kit/src/core/component/state/process.ts index bb863a33b..aa070ee12 100644 --- a/packages/devtools-kit/src/core/component/state/process.ts +++ b/packages/devtools-kit/src/core/component/state/process.ts @@ -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' @@ -151,8 +151,8 @@ function processSetupState(instance: VueAppInstance) { let result: Partial 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 @@ -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 diff --git a/packages/devtools-kit/src/core/component/state/replacer.ts b/packages/devtools-kit/src/core/component/state/replacer.ts index 6205188a1..6fcbfa6d5 100644 --- a/packages/devtools-kit/src/core/component/state/replacer.ts +++ b/packages/devtools-kit/src/core/component/state/replacer.ts @@ -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' @@ -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') { @@ -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') { @@ -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) diff --git a/packages/devtools-kit/src/core/component/utils/index.ts b/packages/devtools-kit/src/core/component/utils/index.ts index d57edbe93..24eb58a25 100644 --- a/packages/devtools-kit/src/core/component/utils/index.ts +++ b/packages/devtools-kit/src/core/component/utils/index.ts @@ -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>(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 +} diff --git a/packages/devtools-kit/src/shared/transfer.ts b/packages/devtools-kit/src/shared/transfer.ts index e5d05f36a..08243f63a 100644 --- a/packages/devtools-kit/src/shared/transfer.ts +++ b/packages/devtools-kit/src/shared/transfer.ts @@ -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) }