@wix/zero-config-implementation 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/component-loader.d.ts +42 -0
- package/dist/component-renderer.d.ts +31 -0
- package/dist/converters/data-item-builder.d.ts +15 -0
- package/dist/converters/index.d.ts +1 -0
- package/dist/converters/to-editor-component.d.ts +3 -0
- package/dist/converters/utils.d.ts +16 -0
- package/dist/errors.d.ts +230 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +51978 -0
- package/dist/information-extractors/css/index.d.ts +3 -0
- package/dist/information-extractors/css/parse.d.ts +7 -0
- package/dist/information-extractors/css/selector-matcher.d.ts +3 -0
- package/dist/information-extractors/css/types.d.ts +49 -0
- package/dist/information-extractors/react/extractors/core/index.d.ts +6 -0
- package/dist/information-extractors/react/extractors/core/runner.d.ts +19 -0
- package/dist/information-extractors/react/extractors/core/store.d.ts +17 -0
- package/dist/information-extractors/react/extractors/core/tree-builder.d.ts +15 -0
- package/dist/information-extractors/react/extractors/core/types.d.ts +40 -0
- package/dist/information-extractors/react/extractors/css-properties.d.ts +20 -0
- package/dist/information-extractors/react/extractors/index.d.ts +11 -0
- package/dist/information-extractors/react/extractors/prop-tracker.d.ts +24 -0
- package/dist/information-extractors/react/index.d.ts +9 -0
- package/dist/information-extractors/react/types.d.ts +51 -0
- package/dist/information-extractors/react/utils/mock-generator.d.ts +9 -0
- package/dist/information-extractors/react/utils/prop-spy.d.ts +10 -0
- package/dist/information-extractors/ts/components.d.ts +9 -0
- package/dist/information-extractors/ts/css-imports.d.ts +2 -0
- package/dist/information-extractors/ts/index.d.ts +3 -0
- package/dist/information-extractors/ts/types.d.ts +47 -0
- package/dist/information-extractors/ts/utils/semantic-type-resolver.d.ts +3 -0
- package/dist/jsx-runtime-interceptor.d.ts +42 -0
- package/dist/jsx-runtime-interceptor.js +63 -0
- package/dist/jsx-runtime-loader.d.ts +23 -0
- package/dist/jsx-runtime-loader.js +7 -0
- package/dist/manifest-pipeline.d.ts +33 -0
- package/dist/schema.d.ts +167 -0
- package/dist/ts-compiler.d.ts +13 -0
- package/package.json +81 -0
- package/src/component-loader.test.ts +277 -0
- package/src/component-loader.ts +256 -0
- package/src/component-renderer.ts +192 -0
- package/src/converters/data-item-builder.ts +354 -0
- package/src/converters/index.ts +1 -0
- package/src/converters/to-editor-component.ts +167 -0
- package/src/converters/utils.ts +21 -0
- package/src/errors.ts +103 -0
- package/src/index.ts +223 -0
- package/src/information-extractors/css/README.md +3 -0
- package/src/information-extractors/css/index.ts +3 -0
- package/src/information-extractors/css/parse.ts +450 -0
- package/src/information-extractors/css/selector-matcher.ts +88 -0
- package/src/information-extractors/css/types.ts +56 -0
- package/src/information-extractors/react/extractors/core/index.ts +6 -0
- package/src/information-extractors/react/extractors/core/runner.ts +89 -0
- package/src/information-extractors/react/extractors/core/store.ts +36 -0
- package/src/information-extractors/react/extractors/core/tree-builder.ts +273 -0
- package/src/information-extractors/react/extractors/core/types.ts +48 -0
- package/src/information-extractors/react/extractors/css-properties.ts +214 -0
- package/src/information-extractors/react/extractors/index.ts +27 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +132 -0
- package/src/information-extractors/react/index.ts +53 -0
- package/src/information-extractors/react/types.ts +70 -0
- package/src/information-extractors/react/utils/mock-generator.ts +331 -0
- package/src/information-extractors/react/utils/prop-spy.ts +168 -0
- package/src/information-extractors/ts/components.ts +300 -0
- package/src/information-extractors/ts/css-imports.ts +26 -0
- package/src/information-extractors/ts/index.ts +3 -0
- package/src/information-extractors/ts/types.ts +56 -0
- package/src/information-extractors/ts/utils/semantic-type-resolver.ts +377 -0
- package/src/jsx-runtime-interceptor.ts +146 -0
- package/src/jsx-runtime-loader.ts +38 -0
- package/src/manifest-pipeline.ts +362 -0
- package/src/schema.ts +174 -0
- package/src/ts-compiler.ts +41 -0
- package/tsconfig.json +17 -0
- package/typedoc.json +18 -0
- package/vite.config.ts +45 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Value Generator - Creates mock values from PropInfo types for DOM coupling analysis
|
|
3
|
+
* Uses @faker-js/faker for realistic mock data generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { faker } from '@faker-js/faker'
|
|
7
|
+
import type { ComponentInfo, DefaultValue, PropInfo, ResolvedType } from '../../ts/types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reset faker's seed and internal state for reproducible results
|
|
11
|
+
*/
|
|
12
|
+
export function resetMockCounter(): void {
|
|
13
|
+
faker.seed(42)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate mock props object from ComponentInfo
|
|
18
|
+
*/
|
|
19
|
+
export function generateMockProps(componentInfo: ComponentInfo): Record<string, unknown> {
|
|
20
|
+
const mockProps: Record<string, unknown> = {}
|
|
21
|
+
|
|
22
|
+
for (const [propName, propInfo] of Object.entries(componentInfo.props)) {
|
|
23
|
+
mockProps[propName] = generateMockValue(propInfo, propName)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return mockProps
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate a mock value based on PropInfo
|
|
31
|
+
*/
|
|
32
|
+
function generateMockValue(propInfo: PropInfo, propName: string): unknown {
|
|
33
|
+
// If there's a default value, use it
|
|
34
|
+
if (propInfo.defaultValue !== undefined) {
|
|
35
|
+
return extractDefaultValueValue(propInfo.defaultValue)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return generateValueFromResolvedType(propInfo.resolvedType, propName)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract the actual value from a DefaultValue object
|
|
43
|
+
*/
|
|
44
|
+
function extractDefaultValueValue(defaultValue: DefaultValue): unknown {
|
|
45
|
+
switch (defaultValue.kind) {
|
|
46
|
+
case 'string':
|
|
47
|
+
case 'number':
|
|
48
|
+
case 'boolean':
|
|
49
|
+
case 'null':
|
|
50
|
+
return defaultValue.value
|
|
51
|
+
case 'unresolved':
|
|
52
|
+
// For unresolved values, return the raw string (e.g., MY_CONST)
|
|
53
|
+
return defaultValue.value
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate a mock value from a ResolvedType
|
|
59
|
+
*/
|
|
60
|
+
function generateValueFromResolvedType(resolvedType: ResolvedType, propName: string): unknown {
|
|
61
|
+
const kind = resolvedType.kind
|
|
62
|
+
|
|
63
|
+
// Handle semantic types (from React or Wix packages)
|
|
64
|
+
if (kind === 'semantic') {
|
|
65
|
+
return generateSemanticValue(resolvedType.value as string, propName)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle structural types
|
|
69
|
+
switch (kind) {
|
|
70
|
+
case 'primitive':
|
|
71
|
+
return generatePrimitiveValue(resolvedType.value as string, propName)
|
|
72
|
+
|
|
73
|
+
case 'literal':
|
|
74
|
+
return resolvedType.value
|
|
75
|
+
|
|
76
|
+
case 'union':
|
|
77
|
+
return generateUnionValue(resolvedType, propName)
|
|
78
|
+
|
|
79
|
+
case 'intersection':
|
|
80
|
+
return generateIntersectionValue(resolvedType, propName)
|
|
81
|
+
|
|
82
|
+
case 'array':
|
|
83
|
+
return generateArrayValue(resolvedType, propName)
|
|
84
|
+
|
|
85
|
+
case 'object':
|
|
86
|
+
return generateObjectValue(resolvedType, propName)
|
|
87
|
+
|
|
88
|
+
case 'enum':
|
|
89
|
+
return generateEnumValue(resolvedType)
|
|
90
|
+
|
|
91
|
+
case 'function':
|
|
92
|
+
return generateMockFunction(propName)
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
// Default to a string for unknown types
|
|
96
|
+
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate a mock value for semantic types (React or Wix types)
|
|
102
|
+
*/
|
|
103
|
+
function generateSemanticValue(semanticType: string, propName: string): unknown {
|
|
104
|
+
switch (semanticType) {
|
|
105
|
+
// Wix semantic types
|
|
106
|
+
case 'Image':
|
|
107
|
+
case 'ImageUri':
|
|
108
|
+
return generateMockImage()
|
|
109
|
+
case 'Video':
|
|
110
|
+
case 'VideoUri':
|
|
111
|
+
return generateMockVideo()
|
|
112
|
+
case 'Link':
|
|
113
|
+
return generateMockLink()
|
|
114
|
+
case 'A11y':
|
|
115
|
+
return generateMockA11y()
|
|
116
|
+
case 'VectorArt':
|
|
117
|
+
return generateMockVectorArt()
|
|
118
|
+
case 'Audio':
|
|
119
|
+
case 'AudioUri':
|
|
120
|
+
return generateMockAudio()
|
|
121
|
+
case 'RichText':
|
|
122
|
+
return `<p>${faker.lorem.paragraph()}</p>`
|
|
123
|
+
case 'LocalDate':
|
|
124
|
+
return faker.date.recent().toISOString().split('T')[0]
|
|
125
|
+
case 'LocalTime':
|
|
126
|
+
return faker.date.recent().toISOString().split('T')[1].split('.')[0]
|
|
127
|
+
case 'LocalDateTime':
|
|
128
|
+
return faker.date.recent().toISOString().replace('Z', '')
|
|
129
|
+
case 'WebUrl':
|
|
130
|
+
return faker.internet.url()
|
|
131
|
+
case 'Direction':
|
|
132
|
+
return faker.helpers.arrayElement(['ltr', 'rtl'])
|
|
133
|
+
case 'MenuItems':
|
|
134
|
+
return generateMockMenuItems()
|
|
135
|
+
case 'Container':
|
|
136
|
+
return null // React children - typically null for testing
|
|
137
|
+
|
|
138
|
+
// React types
|
|
139
|
+
case 'ReactNode':
|
|
140
|
+
case 'ReactElement':
|
|
141
|
+
return null // Return null for React nodes in mock testing
|
|
142
|
+
|
|
143
|
+
case 'CSSProperties':
|
|
144
|
+
return {
|
|
145
|
+
color: faker.color.rgb(),
|
|
146
|
+
fontSize: faker.number.int({ min: 12, max: 24 }),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
// Default to a string for unknown semantic types
|
|
151
|
+
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate a primitive value
|
|
157
|
+
*/
|
|
158
|
+
function generatePrimitiveValue(primitiveType: string, propName: string): unknown {
|
|
159
|
+
switch (primitiveType) {
|
|
160
|
+
case 'string':
|
|
161
|
+
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
162
|
+
case 'number':
|
|
163
|
+
return faker.number.int({ min: 1, max: 100 })
|
|
164
|
+
case 'boolean':
|
|
165
|
+
return faker.datatype.boolean()
|
|
166
|
+
case 'null':
|
|
167
|
+
return null
|
|
168
|
+
case 'undefined':
|
|
169
|
+
return undefined
|
|
170
|
+
default:
|
|
171
|
+
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate a value from a union type (pick the first non-null option)
|
|
177
|
+
*/
|
|
178
|
+
function generateUnionValue(resolvedType: ResolvedType, propName: string): unknown {
|
|
179
|
+
const types = resolvedType.types ?? []
|
|
180
|
+
|
|
181
|
+
// Prefer string literals for textEnum-like unions
|
|
182
|
+
const stringLiteral = types.find((t) => t.kind === 'literal' && typeof t.value === 'string')
|
|
183
|
+
if (stringLiteral) {
|
|
184
|
+
return stringLiteral.value
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Otherwise, use the first type
|
|
188
|
+
if (types.length > 0) {
|
|
189
|
+
return generateValueFromResolvedType(types[0], propName)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate a value from an intersection type (merge object properties)
|
|
197
|
+
*/
|
|
198
|
+
function generateIntersectionValue(resolvedType: ResolvedType, propName: string): unknown {
|
|
199
|
+
const types = resolvedType.types ?? []
|
|
200
|
+
const merged: Record<string, unknown> = {}
|
|
201
|
+
|
|
202
|
+
for (const type of types) {
|
|
203
|
+
if (type.kind === 'object' && type.properties) {
|
|
204
|
+
for (const [key, propInfo] of Object.entries(type.properties)) {
|
|
205
|
+
merged[key] = generateMockValue(propInfo, `${propName}.${key}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return Object.keys(merged).length > 0 ? merged : `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate an array value
|
|
215
|
+
*/
|
|
216
|
+
function generateArrayValue(resolvedType: ResolvedType, propName: string): unknown[] {
|
|
217
|
+
const elementType = resolvedType.elementType
|
|
218
|
+
|
|
219
|
+
if (!elementType) {
|
|
220
|
+
return [`mock_${propName}[0]_${faker.string.alphanumeric(6)}`]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Generate 2-3 items for the array
|
|
224
|
+
return [
|
|
225
|
+
generateValueFromResolvedType(elementType, `${propName}[0]`),
|
|
226
|
+
generateValueFromResolvedType(elementType, `${propName}[1]`),
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate an object value from properties
|
|
232
|
+
*/
|
|
233
|
+
function generateObjectValue(resolvedType: ResolvedType, propName: string): Record<string, unknown> {
|
|
234
|
+
const properties = resolvedType.properties ?? {}
|
|
235
|
+
const obj: Record<string, unknown> = {}
|
|
236
|
+
|
|
237
|
+
for (const [key, propInfo] of Object.entries(properties)) {
|
|
238
|
+
obj[key] = generateMockValue(propInfo, `${propName}.${key}`)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return obj
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Generate an enum value (pick the first option)
|
|
246
|
+
*/
|
|
247
|
+
function generateEnumValue(resolvedType: ResolvedType): unknown {
|
|
248
|
+
const types = resolvedType.types ?? []
|
|
249
|
+
const firstLiteral = types.find((t) => t.kind === 'literal')
|
|
250
|
+
|
|
251
|
+
if (firstLiteral) {
|
|
252
|
+
return firstLiteral.value
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return 'default'
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Generate a mock function
|
|
260
|
+
*/
|
|
261
|
+
function generateMockFunction(propName: string): (...args: unknown[]) => void {
|
|
262
|
+
const fn = function mockFn(): void {
|
|
263
|
+
// No-op mock function
|
|
264
|
+
}
|
|
265
|
+
Object.defineProperty(fn, 'name', { value: `mock_${propName}` })
|
|
266
|
+
return fn
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
270
|
+
// Wix Semantic Type Generators (built from faker primitives)
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
function generateMockImage(): Record<string, unknown> {
|
|
274
|
+
return {
|
|
275
|
+
uri: faker.system.fileName({ extensionCount: 1 }),
|
|
276
|
+
url: faker.image.url(),
|
|
277
|
+
width: faker.number.int({ min: 100, max: 1920 }),
|
|
278
|
+
height: faker.number.int({ min: 100, max: 1080 }),
|
|
279
|
+
name: faker.lorem.words(2),
|
|
280
|
+
alt: faker.lorem.sentence(),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function generateMockVideo(): Record<string, unknown> {
|
|
285
|
+
return {
|
|
286
|
+
url: `${faker.internet.url()}/video.mp4`,
|
|
287
|
+
width: faker.number.int({ min: 640, max: 1920 }),
|
|
288
|
+
height: faker.number.int({ min: 480, max: 1080 }),
|
|
289
|
+
duration: faker.number.int({ min: 10, max: 300 }),
|
|
290
|
+
name: faker.lorem.words(2),
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function generateMockLink(): Record<string, unknown> {
|
|
295
|
+
return {
|
|
296
|
+
href: faker.internet.url(),
|
|
297
|
+
target: faker.helpers.arrayElement(['_blank', '_self']),
|
|
298
|
+
rel: 'noopener noreferrer',
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function generateMockA11y(): Record<string, unknown> {
|
|
303
|
+
return {
|
|
304
|
+
ariaLabel: faker.lorem.sentence(),
|
|
305
|
+
role: faker.helpers.arrayElement(['button', 'link', 'img', 'region']),
|
|
306
|
+
tabIndex: faker.helpers.arrayElement([0, -1]),
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function generateMockVectorArt(): Record<string, unknown> {
|
|
311
|
+
return {
|
|
312
|
+
svgId: faker.string.uuid(),
|
|
313
|
+
url: `${faker.internet.url()}/icon.svg`,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function generateMockAudio(): Record<string, unknown> {
|
|
318
|
+
return {
|
|
319
|
+
url: `${faker.internet.url()}/audio.mp3`,
|
|
320
|
+
duration: faker.number.int({ min: 30, max: 600 }),
|
|
321
|
+
name: faker.music.songName(),
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function generateMockMenuItems(): unknown[] {
|
|
326
|
+
return Array.from({ length: faker.number.int({ min: 2, max: 5 }) }, () => ({
|
|
327
|
+
id: faker.string.uuid(),
|
|
328
|
+
label: faker.lorem.words(2),
|
|
329
|
+
link: generateMockLink(),
|
|
330
|
+
}))
|
|
331
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { PropSpy, PropSpyMeta, TrackingStores } from '../types'
|
|
2
|
+
import { PROP_SPY_SYMBOL } from '../types'
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// Types
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export interface PropSpyContext {
|
|
9
|
+
createAuditedProps: <T extends object | null>(
|
|
10
|
+
target: T,
|
|
11
|
+
stores: TrackingStores,
|
|
12
|
+
getComponent: () => string,
|
|
13
|
+
basePath?: string,
|
|
14
|
+
) => T
|
|
15
|
+
getSpyMetadataByUniqueId: (id: string) => PropSpyMeta | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
// Factory
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates an encapsulated prop spy context.
|
|
24
|
+
* Each context has its own ID counter and metadata map, avoiding global state.
|
|
25
|
+
*/
|
|
26
|
+
export function createPropSpyContext(): PropSpyContext {
|
|
27
|
+
let idCounter = 0
|
|
28
|
+
const metadataMap = new Map<string, PropSpyMeta>()
|
|
29
|
+
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Helpers
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const isPropSpy = (val: unknown): val is PropSpy => !!(val && typeof val === 'object' && PROP_SPY_SYMBOL in val)
|
|
35
|
+
|
|
36
|
+
const isReactInternal = (val: unknown): boolean => {
|
|
37
|
+
if (!val || typeof val !== 'object') return false
|
|
38
|
+
const obj = val as Record<string, unknown>
|
|
39
|
+
return !!(obj.$$typeof || obj._owner || obj._store || (obj.prototype as Record<string, unknown>)?.isReactComponent)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const logRead = (stores: TrackingStores, path: string, component: string, value: unknown) => {
|
|
43
|
+
const record = stores.reads.get(path) ?? { components: new Set(), value }
|
|
44
|
+
record.components.add(component)
|
|
45
|
+
stores.reads.set(path, record)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const createMeta = (
|
|
49
|
+
path: string,
|
|
50
|
+
propName: string,
|
|
51
|
+
originalValue: unknown,
|
|
52
|
+
): { meta: PropSpyMeta; uniqueId: string } => {
|
|
53
|
+
const uniqueId = `__spy_${++idCounter}__`
|
|
54
|
+
const meta = { path, propName, uniqueId, originalValue }
|
|
55
|
+
return { meta, uniqueId }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
// Primitive Wrapping
|
|
60
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
const wrapPrimitiveOrFunction = (
|
|
63
|
+
value: string | number | boolean | ((...args: unknown[]) => unknown),
|
|
64
|
+
path: string,
|
|
65
|
+
propName: string,
|
|
66
|
+
stores: TrackingStores,
|
|
67
|
+
getComponent: () => string,
|
|
68
|
+
) => {
|
|
69
|
+
// Functions: Wrap to log execution
|
|
70
|
+
if (typeof value === 'function') {
|
|
71
|
+
const spy = function (this: unknown, ...args: unknown[]) {
|
|
72
|
+
logRead(stores, path, getComponent(), value)
|
|
73
|
+
return value.apply(this, args)
|
|
74
|
+
}
|
|
75
|
+
Object.defineProperties(spy, {
|
|
76
|
+
name: { value: value.name || propName },
|
|
77
|
+
length: { value: value.length },
|
|
78
|
+
})
|
|
79
|
+
return spy
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { meta, uniqueId } = createMeta(path, propName, value)
|
|
83
|
+
|
|
84
|
+
// Strings: Embed markers
|
|
85
|
+
if (typeof value === 'string') {
|
|
86
|
+
metadataMap.set(uniqueId, meta)
|
|
87
|
+
return `${uniqueId}${value}${uniqueId}`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Numbers/Booleans: Wrapper Objects
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
|
|
92
|
+
type SpyWrapper = (number | boolean) & { [PROP_SPY_SYMBOL]?: boolean; __meta?: PropSpyMeta }
|
|
93
|
+
let spy: SpyWrapper | undefined
|
|
94
|
+
if (typeof value === 'number') spy = new Number(value) as SpyWrapper
|
|
95
|
+
if (typeof value === 'boolean') spy = new Boolean(value) as SpyWrapper
|
|
96
|
+
|
|
97
|
+
if (spy) {
|
|
98
|
+
spy[PROP_SPY_SYMBOL] = true
|
|
99
|
+
spy.__meta = meta
|
|
100
|
+
return spy
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return value
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
// Core Logic
|
|
108
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
const createAuditedProps = <T extends object | null>(
|
|
111
|
+
target: T,
|
|
112
|
+
stores: TrackingStores,
|
|
113
|
+
getComponent: () => string,
|
|
114
|
+
basePath = 'props',
|
|
115
|
+
): T => {
|
|
116
|
+
if (!target || typeof target !== 'object' || isPropSpy(target)) return target
|
|
117
|
+
|
|
118
|
+
return new Proxy(target, {
|
|
119
|
+
get(obj, prop, receiver) {
|
|
120
|
+
const value = Reflect.get(obj, prop, receiver)
|
|
121
|
+
const propKey = String(prop)
|
|
122
|
+
|
|
123
|
+
// Ignore symbols and known React internal properties
|
|
124
|
+
if (typeof prop === 'symbol' || propKey === '$$typeof' || propKey.startsWith('_')) {
|
|
125
|
+
return value
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const path = Array.isArray(target) ? `${basePath}[${propKey}]` : `${basePath}.${propKey}`
|
|
129
|
+
const component = getComponent()
|
|
130
|
+
|
|
131
|
+
// 1. Handle React internals or nulls (Log but don't wrap)
|
|
132
|
+
if (value == null || isReactInternal(value)) {
|
|
133
|
+
logRead(stores, path, component, value)
|
|
134
|
+
return value
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 2. Handle Primitives & Functions (Wrap & Log)
|
|
138
|
+
if (typeof value !== 'object' || typeof value === 'function') {
|
|
139
|
+
const wrapped = wrapPrimitiveOrFunction(
|
|
140
|
+
value as string | number | boolean | ((...args: unknown[]) => unknown),
|
|
141
|
+
path,
|
|
142
|
+
propKey,
|
|
143
|
+
stores,
|
|
144
|
+
getComponent,
|
|
145
|
+
)
|
|
146
|
+
logRead(stores, path, component, wrapped)
|
|
147
|
+
return wrapped
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 3. Handle Objects/Arrays (Recurse Proxy & Log)
|
|
151
|
+
const audited = createAuditedProps(value, stores, getComponent, path)
|
|
152
|
+
logRead(stores, path, component, audited)
|
|
153
|
+
return audited
|
|
154
|
+
},
|
|
155
|
+
ownKeys: (obj) => Reflect.ownKeys(obj),
|
|
156
|
+
getOwnPropertyDescriptor: (obj, prop) => Reflect.getOwnPropertyDescriptor(obj, prop),
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const getSpyMetadataByUniqueId = (id: string): PropSpyMeta | null => {
|
|
161
|
+
return metadataMap.get(id) ?? null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
createAuditedProps,
|
|
166
|
+
getSpyMetadataByUniqueId,
|
|
167
|
+
}
|
|
168
|
+
}
|