@wix/zero-config-implementation 1.7.0 → 1.9.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/dist/converters/data-item-builder.d.ts +4 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +22432 -19408
- package/dist/information-extractors/react/extractors/core/index.d.ts +1 -1
- package/dist/information-extractors/react/extractors/core/runner.d.ts +5 -1
- package/dist/information-extractors/react/extractors/index.d.ts +1 -1
- package/dist/information-extractors/react/extractors/prop-tracker.d.ts +3 -6
- package/dist/information-extractors/react/index.d.ts +2 -3
- package/dist/information-extractors/react/types.d.ts +4 -17
- package/dist/information-extractors/react/utils/mock-generator.d.ts +13 -2
- package/dist/manifest-pipeline.d.ts +2 -2
- package/package.json +2 -2
- package/src/converters/data-item-builder.ts +120 -35
- package/src/converters/to-editor-component.ts +11 -6
- package/src/index.ts +5 -3
- package/src/information-extractors/react/extractors/core/index.ts +1 -1
- package/src/information-extractors/react/extractors/core/runner.ts +12 -7
- package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -1
- package/src/information-extractors/react/extractors/index.ts +1 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +49 -28
- package/src/information-extractors/react/index.ts +1 -7
- package/src/information-extractors/react/types.ts +4 -20
- package/src/information-extractors/react/utils/mock-generator.ts +99 -31
- package/src/manifest-pipeline.ts +18 -12
- package/dist/information-extractors/react/utils/prop-spy.d.ts +0 -10
- package/src/information-extractors/react/utils/prop-spy.ts +0 -168
|
@@ -8,17 +8,13 @@
|
|
|
8
8
|
import type { HTMLAttributes } from 'react'
|
|
9
9
|
import { TRACE_ATTR } from '../../../component-renderer'
|
|
10
10
|
import type { PropSpyMeta, TrackingStores } from '../types'
|
|
11
|
-
import { type
|
|
11
|
+
import { type PropSpyRegistrar, generateMockProps, resetMockCounter } from '../utils/mock-generator'
|
|
12
12
|
import type { CreateElementEvent, ReactExtractor, RenderContext } from './core/types'
|
|
13
13
|
|
|
14
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
15
|
// Types
|
|
16
16
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
-
const SPY_REGEX = /__spy_\d+__/g
|
|
19
|
-
|
|
20
|
-
export type GetSpyMetadataFn = (id: string) => PropSpyMeta | null
|
|
21
|
-
|
|
22
18
|
export interface PropTrackerData {
|
|
23
19
|
tag: string
|
|
24
20
|
role?: string
|
|
@@ -28,7 +24,6 @@ export interface PropTrackerData {
|
|
|
28
24
|
|
|
29
25
|
export interface PropTrackerExtractorState {
|
|
30
26
|
stores: TrackingStores
|
|
31
|
-
spyContext: PropSpyContext
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -37,28 +32,54 @@ export interface PropTrackerExtractorState {
|
|
|
37
32
|
|
|
38
33
|
/**
|
|
39
34
|
* Creates a prop tracker extractor that:
|
|
40
|
-
* 1.
|
|
35
|
+
* 1. Generates spy-instrumented mock props during beforeRender
|
|
41
36
|
* 2. Detects spy markers in element props during onCreateElement
|
|
42
|
-
* 3.
|
|
37
|
+
* 3. propUsages tracking data to the store namespaced by 'prop-tracker'
|
|
43
38
|
*/
|
|
44
39
|
export function createPropTrackerExtractor(): {
|
|
45
40
|
extractor: ReactExtractor
|
|
46
41
|
state: PropTrackerExtractorState
|
|
47
42
|
} {
|
|
48
|
-
const
|
|
43
|
+
const stringMeta = new Map<string, PropSpyMeta>()
|
|
44
|
+
const numberMeta = new Map<number, PropSpyMeta>()
|
|
45
|
+
const fnMeta = new WeakMap<(...args: unknown[]) => unknown, PropSpyMeta>()
|
|
49
46
|
const stores: TrackingStores = {
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
propUsages: new Map(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const registrar: PropSpyRegistrar = {
|
|
51
|
+
registerString(path, propName, value) {
|
|
52
|
+
stringMeta.set(value, { path, propName, originalValue: value })
|
|
53
|
+
},
|
|
54
|
+
registerNumber(path, propName, value) {
|
|
55
|
+
numberMeta.set(value, { path, propName, originalValue: value })
|
|
56
|
+
},
|
|
57
|
+
registerFunction(path, propName, value) {
|
|
58
|
+
fnMeta.set(value, { path, propName, originalValue: value })
|
|
59
|
+
},
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
const extractSpies = (
|
|
62
|
+
const extractSpies = (
|
|
63
|
+
value: unknown,
|
|
64
|
+
seen = new WeakSet<object>(),
|
|
65
|
+
): { propName: string; path: string; embedded: boolean }[] => {
|
|
55
66
|
if (typeof value === 'string') {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const results: { propName: string; path: string; embedded: boolean }[] = []
|
|
68
|
+
for (const [key, meta] of stringMeta) {
|
|
69
|
+
if (value.includes(key)) {
|
|
70
|
+
// embedded = spy marker was part of a longer string (e.g. `mailto:${email}`)
|
|
71
|
+
results.push({ propName: meta.propName, path: meta.path, embedded: value !== key })
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return results
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === 'number') {
|
|
77
|
+
const meta = numberMeta.get(value)
|
|
78
|
+
return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === 'function') {
|
|
81
|
+
const meta = fnMeta.get(value as (...args: unknown[]) => unknown)
|
|
82
|
+
return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
|
|
62
83
|
}
|
|
63
84
|
if (value && typeof value === 'object') {
|
|
64
85
|
// Prevent infinite recursion from circular references
|
|
@@ -79,8 +100,8 @@ export function createPropTrackerExtractor(): {
|
|
|
79
100
|
name: 'prop-tracker',
|
|
80
101
|
|
|
81
102
|
onBeforeRender(context: RenderContext): void {
|
|
82
|
-
|
|
83
|
-
context.props =
|
|
103
|
+
resetMockCounter()
|
|
104
|
+
context.props = generateMockProps(context.componentInfo, registrar)
|
|
84
105
|
},
|
|
85
106
|
|
|
86
107
|
onCreateElement(event: CreateElementEvent): void {
|
|
@@ -97,18 +118,18 @@ export function createPropTrackerExtractor(): {
|
|
|
97
118
|
|
|
98
119
|
spies.forEach((spy) => {
|
|
99
120
|
boundProps.add(spy.propName)
|
|
100
|
-
const path = spy.path
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!stores.writes.has(path)) {
|
|
104
|
-
stores.writes.set(path, entry)
|
|
121
|
+
const path = spy.path.replace(/\[\d+\]/g, '')
|
|
122
|
+
if (!stores.propUsages.has(path)) {
|
|
123
|
+
stores.propUsages.set(path, { elements: new Map(), attributes: new Map() })
|
|
105
124
|
}
|
|
125
|
+
const entry = stores.propUsages.get(path)!
|
|
106
126
|
|
|
107
127
|
entry.elements.set(traceId, { tag, elementId: traceId })
|
|
108
|
-
|
|
128
|
+
const isConcat = spies.length > 1 || spy.embedded
|
|
129
|
+
entry.attributes.set(`${traceId}:${key}`, { attr: key, concatenated: isConcat })
|
|
109
130
|
|
|
110
131
|
// Track concatenated attributes
|
|
111
|
-
if (
|
|
132
|
+
if (isConcat) {
|
|
112
133
|
concatenatedAttrs.set(key, spy.propName)
|
|
113
134
|
}
|
|
114
135
|
})
|
|
@@ -127,6 +148,6 @@ export function createPropTrackerExtractor(): {
|
|
|
127
148
|
|
|
128
149
|
return {
|
|
129
150
|
extractor,
|
|
130
|
-
state: { stores
|
|
151
|
+
state: { stores },
|
|
131
152
|
}
|
|
132
153
|
}
|
|
@@ -21,6 +21,7 @@ export {
|
|
|
21
21
|
export type {
|
|
22
22
|
// Core types
|
|
23
23
|
ExtractionResult,
|
|
24
|
+
RunExtractorsOptions,
|
|
24
25
|
ExtractedElement,
|
|
25
26
|
ReactExtractor,
|
|
26
27
|
RenderContext,
|
|
@@ -41,13 +42,6 @@ export type {
|
|
|
41
42
|
CoupledProp,
|
|
42
43
|
TrackingStores,
|
|
43
44
|
DOMBinding,
|
|
44
|
-
PropReadInfo,
|
|
45
45
|
PropWriteInfo,
|
|
46
46
|
PropSpyMeta,
|
|
47
47
|
} from './types'
|
|
48
|
-
|
|
49
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
|
-
// Utility Exports
|
|
51
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
export type { PropSpyContext } from './utils/prop-spy'
|
|
@@ -9,41 +9,23 @@ import type { ExtractedElement } from './extractors/core/tree-builder'
|
|
|
9
9
|
// Prop Spy (for tracking prop access)
|
|
10
10
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
export const PROP_SPY_SYMBOL = Symbol.for('__prop_spy__')
|
|
13
|
-
|
|
14
12
|
export interface PropSpyMeta {
|
|
15
13
|
path: string
|
|
16
14
|
propName: string
|
|
17
|
-
uniqueId: string
|
|
18
15
|
originalValue: unknown
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
export interface PropSpy<T = unknown> {
|
|
22
|
-
[PROP_SPY_SYMBOL]: true
|
|
23
|
-
__meta: PropSpyMeta
|
|
24
|
-
valueOf: () => T
|
|
25
|
-
toString: () => string
|
|
26
|
-
toJSON: () => T
|
|
27
|
-
[Symbol.toPrimitive]?: (hint: string) => T | string | number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
19
|
// Tracking Stores
|
|
32
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
21
|
|
|
34
|
-
export interface PropReadInfo {
|
|
35
|
-
components: Set<string>
|
|
36
|
-
value: unknown
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
export interface PropWriteInfo {
|
|
40
23
|
elements: Map<string, { tag: string; elementId: string }>
|
|
41
24
|
attributes: Map<string, { attr: string; concatenated: boolean }>
|
|
42
25
|
}
|
|
43
26
|
|
|
44
27
|
export interface TrackingStores {
|
|
45
|
-
|
|
46
|
-
writes: Map<string, PropWriteInfo>
|
|
28
|
+
propUsages: Map<string, PropWriteInfo>
|
|
47
29
|
}
|
|
48
30
|
|
|
49
31
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -58,7 +40,8 @@ export interface DOMBinding {
|
|
|
58
40
|
}
|
|
59
41
|
|
|
60
42
|
export interface CoupledProp extends PropInfo {
|
|
61
|
-
|
|
43
|
+
/** Full stores.propUsages key for this prop, e.g. "props.linkUrl". */
|
|
44
|
+
propPath: string
|
|
62
45
|
logicOnly: boolean
|
|
63
46
|
}
|
|
64
47
|
|
|
@@ -67,4 +50,5 @@ export interface CoupledComponentInfo {
|
|
|
67
50
|
props: Record<string, CoupledProp>
|
|
68
51
|
elements: ExtractedElement[]
|
|
69
52
|
innerElementProps?: Map<string, Record<string, CoupledProp>>
|
|
53
|
+
propUsages: TrackingStores['propUsages']
|
|
70
54
|
}
|
|
@@ -6,21 +6,49 @@
|
|
|
6
6
|
import { faker } from '@faker-js/faker'
|
|
7
7
|
import type { ComponentInfo, DefaultValue, PropInfo, ResolvedType } from '../../ts/types'
|
|
8
8
|
|
|
9
|
+
// Unique primes used as traceable number values. Each rendered number prop gets
|
|
10
|
+
// one; the value is distinct enough to be identified in DOM attributes later.
|
|
11
|
+
const TRACEABLE_PRIMES = [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
|
|
12
|
+
const FALLBACK_NUMBER_BASE = 101
|
|
13
|
+
let primeIndex = 0
|
|
14
|
+
|
|
15
|
+
function nextTraceableNumber(): number {
|
|
16
|
+
const withinPrimeList = primeIndex < TRACEABLE_PRIMES.length
|
|
17
|
+
if (withinPrimeList) {
|
|
18
|
+
return TRACEABLE_PRIMES[primeIndex++]
|
|
19
|
+
}
|
|
20
|
+
const overflowOffset = primeIndex++ - TRACEABLE_PRIMES.length
|
|
21
|
+
return FALLBACK_NUMBER_BASE + overflowOffset
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
25
|
* Reset faker's seed and internal state for reproducible results
|
|
11
26
|
*/
|
|
12
27
|
export function resetMockCounter(): void {
|
|
13
28
|
faker.seed(42)
|
|
29
|
+
primeIndex = 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Narrow interface for registering spy markers during mock generation.
|
|
34
|
+
* Satisfied structurally by PropSpyContext.
|
|
35
|
+
*/
|
|
36
|
+
export interface PropSpyRegistrar {
|
|
37
|
+
registerString(path: string, propName: string, value: string): void
|
|
38
|
+
registerNumber(path: string, propName: string, value: number): void
|
|
39
|
+
registerFunction(path: string, propName: string, value: (...args: unknown[]) => unknown): void
|
|
14
40
|
}
|
|
15
41
|
|
|
16
42
|
/**
|
|
17
|
-
* Generate mock props object from ComponentInfo
|
|
43
|
+
* Generate mock props object from ComponentInfo.
|
|
44
|
+
* When a registrar is provided, string and number values are spy-instrumented
|
|
45
|
+
* for DOM binding detection.
|
|
18
46
|
*/
|
|
19
|
-
export function generateMockProps(componentInfo: ComponentInfo): Record<string, unknown> {
|
|
47
|
+
export function generateMockProps(componentInfo: ComponentInfo, registrar?: PropSpyRegistrar): Record<string, unknown> {
|
|
20
48
|
const mockProps: Record<string, unknown> = {}
|
|
21
49
|
|
|
22
50
|
for (const [propName, propInfo] of Object.entries(componentInfo.props)) {
|
|
23
|
-
mockProps[propName] = generateMockValue(propInfo, propName)
|
|
51
|
+
mockProps[propName] = generateMockValue(propInfo, propName, `props.${propName}`, registrar)
|
|
24
52
|
}
|
|
25
53
|
|
|
26
54
|
return mockProps
|
|
@@ -29,13 +57,13 @@ export function generateMockProps(componentInfo: ComponentInfo): Record<string,
|
|
|
29
57
|
/**
|
|
30
58
|
* Generate a mock value based on PropInfo
|
|
31
59
|
*/
|
|
32
|
-
function generateMockValue(propInfo: PropInfo, propName: string): unknown {
|
|
33
|
-
//
|
|
34
|
-
if (propInfo.defaultValue !== undefined) {
|
|
60
|
+
function generateMockValue(propInfo: PropInfo, propName: string, path: string, registrar?: PropSpyRegistrar): unknown {
|
|
61
|
+
// In plain mode (no registrar), honour default values for realistic rendering
|
|
62
|
+
if (!registrar && propInfo.defaultValue !== undefined) {
|
|
35
63
|
return extractDefaultValueValue(propInfo.defaultValue)
|
|
36
64
|
}
|
|
37
65
|
|
|
38
|
-
return generateValueFromResolvedType(propInfo.resolvedType, propName)
|
|
66
|
+
return generateValueFromResolvedType(propInfo.resolvedType, propName, path, registrar)
|
|
39
67
|
}
|
|
40
68
|
|
|
41
69
|
/**
|
|
@@ -57,10 +85,15 @@ function extractDefaultValueValue(defaultValue: DefaultValue): unknown {
|
|
|
57
85
|
/**
|
|
58
86
|
* Generate a mock value from a ResolvedType
|
|
59
87
|
*/
|
|
60
|
-
function generateValueFromResolvedType(
|
|
88
|
+
function generateValueFromResolvedType(
|
|
89
|
+
resolvedType: ResolvedType,
|
|
90
|
+
propName: string,
|
|
91
|
+
path: string,
|
|
92
|
+
registrar?: PropSpyRegistrar,
|
|
93
|
+
): unknown {
|
|
61
94
|
const kind = resolvedType.kind
|
|
62
95
|
|
|
63
|
-
// Handle semantic types (from React or Wix packages)
|
|
96
|
+
// Handle semantic types (from React or Wix packages) — returned as plain objects
|
|
64
97
|
if (kind === 'semantic') {
|
|
65
98
|
return generateSemanticValue(resolvedType.value as string, propName)
|
|
66
99
|
}
|
|
@@ -68,28 +101,28 @@ function generateValueFromResolvedType(resolvedType: ResolvedType, propName: str
|
|
|
68
101
|
// Handle structural types
|
|
69
102
|
switch (kind) {
|
|
70
103
|
case 'primitive':
|
|
71
|
-
return generatePrimitiveValue(resolvedType.value as string, propName)
|
|
104
|
+
return generatePrimitiveValue(resolvedType.value as string, propName, path, registrar)
|
|
72
105
|
|
|
73
106
|
case 'literal':
|
|
74
107
|
return resolvedType.value
|
|
75
108
|
|
|
76
109
|
case 'union':
|
|
77
|
-
return generateUnionValue(resolvedType, propName)
|
|
110
|
+
return generateUnionValue(resolvedType, propName, path, registrar)
|
|
78
111
|
|
|
79
112
|
case 'intersection':
|
|
80
|
-
return generateIntersectionValue(resolvedType, propName)
|
|
113
|
+
return generateIntersectionValue(resolvedType, propName, path, registrar)
|
|
81
114
|
|
|
82
115
|
case 'array':
|
|
83
|
-
return generateArrayValue(resolvedType, propName)
|
|
116
|
+
return generateArrayValue(resolvedType, propName, path, registrar)
|
|
84
117
|
|
|
85
118
|
case 'object':
|
|
86
|
-
return generateObjectValue(resolvedType, propName)
|
|
119
|
+
return generateObjectValue(resolvedType, propName, path, registrar)
|
|
87
120
|
|
|
88
121
|
case 'enum':
|
|
89
122
|
return generateEnumValue(resolvedType)
|
|
90
123
|
|
|
91
124
|
case 'function':
|
|
92
|
-
return generateMockFunction(propName)
|
|
125
|
+
return generateMockFunction(propName, path, registrar)
|
|
93
126
|
|
|
94
127
|
default:
|
|
95
128
|
// Default to a string for unknown types
|
|
@@ -155,12 +188,23 @@ function generateSemanticValue(semanticType: string, propName: string): unknown
|
|
|
155
188
|
/**
|
|
156
189
|
* Generate a primitive value
|
|
157
190
|
*/
|
|
158
|
-
function generatePrimitiveValue(
|
|
191
|
+
function generatePrimitiveValue(
|
|
192
|
+
primitiveType: string,
|
|
193
|
+
propName: string,
|
|
194
|
+
path: string,
|
|
195
|
+
registrar?: PropSpyRegistrar,
|
|
196
|
+
): unknown {
|
|
159
197
|
switch (primitiveType) {
|
|
160
|
-
case 'string':
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
198
|
+
case 'string': {
|
|
199
|
+
const raw = `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
200
|
+
if (registrar) registrar.registerString(path, propName, raw)
|
|
201
|
+
return raw
|
|
202
|
+
}
|
|
203
|
+
case 'number': {
|
|
204
|
+
const val = nextTraceableNumber()
|
|
205
|
+
if (registrar) registrar.registerNumber(path, propName, val)
|
|
206
|
+
return val
|
|
207
|
+
}
|
|
164
208
|
case 'boolean':
|
|
165
209
|
return faker.datatype.boolean()
|
|
166
210
|
case 'null':
|
|
@@ -175,7 +219,12 @@ function generatePrimitiveValue(primitiveType: string, propName: string): unknow
|
|
|
175
219
|
/**
|
|
176
220
|
* Generate a value from a union type (pick the first non-null option)
|
|
177
221
|
*/
|
|
178
|
-
function generateUnionValue(
|
|
222
|
+
function generateUnionValue(
|
|
223
|
+
resolvedType: ResolvedType,
|
|
224
|
+
propName: string,
|
|
225
|
+
path: string,
|
|
226
|
+
registrar?: PropSpyRegistrar,
|
|
227
|
+
): unknown {
|
|
179
228
|
const types = resolvedType.types ?? []
|
|
180
229
|
|
|
181
230
|
// Prefer string literals for textEnum-like unions
|
|
@@ -186,7 +235,7 @@ function generateUnionValue(resolvedType: ResolvedType, propName: string): unkno
|
|
|
186
235
|
|
|
187
236
|
// Otherwise, use the first type
|
|
188
237
|
if (types.length > 0) {
|
|
189
|
-
return generateValueFromResolvedType(types[0], propName)
|
|
238
|
+
return generateValueFromResolvedType(types[0], propName, path, registrar)
|
|
190
239
|
}
|
|
191
240
|
|
|
192
241
|
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
@@ -195,14 +244,19 @@ function generateUnionValue(resolvedType: ResolvedType, propName: string): unkno
|
|
|
195
244
|
/**
|
|
196
245
|
* Generate a value from an intersection type (merge object properties)
|
|
197
246
|
*/
|
|
198
|
-
function generateIntersectionValue(
|
|
247
|
+
function generateIntersectionValue(
|
|
248
|
+
resolvedType: ResolvedType,
|
|
249
|
+
propName: string,
|
|
250
|
+
path: string,
|
|
251
|
+
registrar?: PropSpyRegistrar,
|
|
252
|
+
): unknown {
|
|
199
253
|
const types = resolvedType.types ?? []
|
|
200
254
|
const merged: Record<string, unknown> = {}
|
|
201
255
|
|
|
202
256
|
for (const type of types) {
|
|
203
257
|
if (type.kind === 'object' && type.properties) {
|
|
204
258
|
for (const [key, propInfo] of Object.entries(type.properties)) {
|
|
205
|
-
merged[key] = generateMockValue(propInfo, `${propName}.${key}
|
|
259
|
+
merged[key] = generateMockValue(propInfo, `${propName}.${key}`, `${path}.${key}`, registrar)
|
|
206
260
|
}
|
|
207
261
|
}
|
|
208
262
|
}
|
|
@@ -213,29 +267,38 @@ function generateIntersectionValue(resolvedType: ResolvedType, propName: string)
|
|
|
213
267
|
/**
|
|
214
268
|
* Generate an array value
|
|
215
269
|
*/
|
|
216
|
-
function generateArrayValue(
|
|
270
|
+
function generateArrayValue(
|
|
271
|
+
resolvedType: ResolvedType,
|
|
272
|
+
propName: string,
|
|
273
|
+
path: string,
|
|
274
|
+
registrar?: PropSpyRegistrar,
|
|
275
|
+
): unknown[] {
|
|
217
276
|
const elementType = resolvedType.elementType
|
|
218
277
|
|
|
219
278
|
if (!elementType) {
|
|
220
279
|
return [`mock_${propName}[0]_${faker.string.alphanumeric(6)}`]
|
|
221
280
|
}
|
|
222
281
|
|
|
223
|
-
// Generate 2-3 items for the array
|
|
224
282
|
return [
|
|
225
|
-
generateValueFromResolvedType(elementType, `${propName}[0]
|
|
226
|
-
generateValueFromResolvedType(elementType, `${propName}[1]
|
|
283
|
+
generateValueFromResolvedType(elementType, `${propName}[0]`, `${path}[0]`, registrar),
|
|
284
|
+
generateValueFromResolvedType(elementType, `${propName}[1]`, `${path}[1]`, registrar),
|
|
227
285
|
]
|
|
228
286
|
}
|
|
229
287
|
|
|
230
288
|
/**
|
|
231
289
|
* Generate an object value from properties
|
|
232
290
|
*/
|
|
233
|
-
function generateObjectValue(
|
|
291
|
+
function generateObjectValue(
|
|
292
|
+
resolvedType: ResolvedType,
|
|
293
|
+
propName: string,
|
|
294
|
+
path: string,
|
|
295
|
+
registrar?: PropSpyRegistrar,
|
|
296
|
+
): Record<string, unknown> {
|
|
234
297
|
const properties = resolvedType.properties ?? {}
|
|
235
298
|
const obj: Record<string, unknown> = {}
|
|
236
299
|
|
|
237
300
|
for (const [key, propInfo] of Object.entries(properties)) {
|
|
238
|
-
obj[key] = generateMockValue(propInfo, `${
|
|
301
|
+
obj[key] = generateMockValue(propInfo, key, `${path}.${key}`, registrar)
|
|
239
302
|
}
|
|
240
303
|
|
|
241
304
|
return obj
|
|
@@ -258,11 +321,16 @@ function generateEnumValue(resolvedType: ResolvedType): unknown {
|
|
|
258
321
|
/**
|
|
259
322
|
* Generate a mock function
|
|
260
323
|
*/
|
|
261
|
-
function generateMockFunction(
|
|
324
|
+
function generateMockFunction(
|
|
325
|
+
propName: string,
|
|
326
|
+
path: string,
|
|
327
|
+
registrar?: PropSpyRegistrar,
|
|
328
|
+
): (...args: unknown[]) => void {
|
|
262
329
|
const fn = function mockFn(): void {
|
|
263
330
|
// No-op mock function
|
|
264
331
|
}
|
|
265
332
|
Object.defineProperty(fn, 'name', { value: `mock_${propName}` })
|
|
333
|
+
if (registrar) registrar.registerFunction(path, propName, fn)
|
|
266
334
|
return fn
|
|
267
335
|
}
|
|
268
336
|
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { PropInfo } from './information-extractors/ts/types'
|
|
|
6
6
|
import {
|
|
7
7
|
type ExtractedElement,
|
|
8
8
|
type PropTrackerData,
|
|
9
|
+
type RunExtractorsOptions,
|
|
9
10
|
createCssPropertiesExtractor,
|
|
10
11
|
createPropTrackerExtractor,
|
|
11
12
|
runExtractors,
|
|
@@ -62,6 +63,7 @@ export function processComponent(
|
|
|
62
63
|
loadComponent: (componentName: string) => ComponentType<unknown> | null,
|
|
63
64
|
cssImportPaths: string[],
|
|
64
65
|
loaderHasError?: boolean,
|
|
66
|
+
options?: RunExtractorsOptions,
|
|
65
67
|
): ProcessComponentResult {
|
|
66
68
|
const warnings: ExtractionWarning[] = []
|
|
67
69
|
|
|
@@ -97,7 +99,7 @@ export function processComponent(
|
|
|
97
99
|
const { extractor: propTracker, state } = createPropTrackerExtractor()
|
|
98
100
|
const cssExtractor = createCssPropertiesExtractor()
|
|
99
101
|
|
|
100
|
-
const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor])
|
|
102
|
+
const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor], options)
|
|
101
103
|
html = result.html
|
|
102
104
|
extractedElements = result.elements
|
|
103
105
|
|
|
@@ -107,6 +109,7 @@ export function processComponent(
|
|
|
107
109
|
props: coupledProps,
|
|
108
110
|
elements: convertElements(extractedElements),
|
|
109
111
|
innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
|
|
112
|
+
propUsages: state.stores.propUsages,
|
|
110
113
|
}
|
|
111
114
|
} catch (error) {
|
|
112
115
|
warnings.push({
|
|
@@ -126,9 +129,13 @@ export function processComponent(
|
|
|
126
129
|
enhancedInfo = {
|
|
127
130
|
componentName: componentInfo.componentName,
|
|
128
131
|
props: Object.fromEntries(
|
|
129
|
-
Object.entries(componentInfo.props).map(([name, info]) => [
|
|
132
|
+
Object.entries(componentInfo.props).map(([name, info]) => [
|
|
133
|
+
name,
|
|
134
|
+
{ ...info, logicOnly: false, propPath: `props.${name}` },
|
|
135
|
+
]),
|
|
130
136
|
),
|
|
131
137
|
elements: [],
|
|
138
|
+
propUsages: new Map(),
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
|
|
@@ -173,14 +180,12 @@ function buildCoupledProps(
|
|
|
173
180
|
const result: Record<string, CoupledProp> = {}
|
|
174
181
|
|
|
175
182
|
for (const [name, info] of Object.entries(componentInfo.props)) {
|
|
176
|
-
const
|
|
177
|
-
const wasRead = stores.reads.has(path)
|
|
178
|
-
const writeInfo = stores.writes.get(path)
|
|
183
|
+
const topLevelWriteInfo = stores.propUsages.get(`props.${name}`)
|
|
179
184
|
|
|
180
185
|
result[name] = {
|
|
181
186
|
...info,
|
|
182
|
-
logicOnly:
|
|
183
|
-
|
|
187
|
+
logicOnly: !topLevelWriteInfo,
|
|
188
|
+
propPath: `props.${name}`,
|
|
184
189
|
}
|
|
185
190
|
}
|
|
186
191
|
|
|
@@ -192,7 +197,7 @@ function buildCoupledProps(
|
|
|
192
197
|
const ELEMENT_PROPS_PREFIX = 'props.elementProps.'
|
|
193
198
|
|
|
194
199
|
/**
|
|
195
|
-
* Processes stores.
|
|
200
|
+
* Processes stores.propUsages entries with paths starting with "props.elementProps."
|
|
196
201
|
* to extract inner element prop bindings grouped by elementId (traceId).
|
|
197
202
|
*/
|
|
198
203
|
function processElementPropsWrites(
|
|
@@ -204,7 +209,7 @@ function processElementPropsWrites(
|
|
|
204
209
|
const elementPropsInfo = componentInfo.props.elementProps
|
|
205
210
|
if (!elementPropsInfo) return result
|
|
206
211
|
|
|
207
|
-
for (const [path, writeInfo] of stores.
|
|
212
|
+
for (const [path, writeInfo] of stores.propUsages) {
|
|
208
213
|
if (!path.startsWith(ELEMENT_PROPS_PREFIX)) continue
|
|
209
214
|
|
|
210
215
|
// Resolve the PropInfo for this leaf prop by walking the type tree
|
|
@@ -228,10 +233,9 @@ function processElementPropsWrites(
|
|
|
228
233
|
propsForElement[leafName] = {
|
|
229
234
|
...propInfo,
|
|
230
235
|
logicOnly: false,
|
|
231
|
-
|
|
236
|
+
propPath: path,
|
|
232
237
|
}
|
|
233
238
|
}
|
|
234
|
-
propsForElement[leafName].bindings.push(binding)
|
|
235
239
|
}
|
|
236
240
|
}
|
|
237
241
|
|
|
@@ -266,7 +270,9 @@ function resolveInnerPropInfo(elementPropsPropInfo: PropInfo, relativePath: stri
|
|
|
266
270
|
return null
|
|
267
271
|
}
|
|
268
272
|
|
|
269
|
-
function extractBindings(
|
|
273
|
+
function extractBindings(
|
|
274
|
+
writeInfo: TrackingStores['propUsages'] extends Map<string, infer V> ? V : never,
|
|
275
|
+
): DOMBinding[] {
|
|
270
276
|
const bindings: DOMBinding[] = []
|
|
271
277
|
|
|
272
278
|
for (const [key, attrInfo] of writeInfo.attributes) {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { PropSpyMeta, TrackingStores } from '../types';
|
|
2
|
-
export interface PropSpyContext {
|
|
3
|
-
createAuditedProps: <T extends object | null>(target: T, stores: TrackingStores, getComponent: () => string, basePath?: string) => T;
|
|
4
|
-
getSpyMetadataByUniqueId: (id: string) => PropSpyMeta | null;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Creates an encapsulated prop spy context.
|
|
8
|
-
* Each context has its own ID counter and metadata map, avoiding global state.
|
|
9
|
-
*/
|
|
10
|
-
export declare function createPropSpyContext(): PropSpyContext;
|