Skip to content

Commit addf4da

Browse files
yungstersfacebook-github-bot
authored andcommitted
RN: Strict Static View Config Validator
Summary: Creates a new `StaticViewConfigValidator` module that does strict, bidirectional validation. This is notably different from `verifyComponentAttributeEquivalence`, which is undirectional validation. This will enforce that two configs are equivalent so we can start addressing the inconsistencies (especially per platform). It also improves upon the reporting format by providing more details about the invalidations. It is hidden behind a `strict` runtime configuration parameter. Changelog: [Internal] Reviewed By: RSNara Differential Revision: D29024229 fbshipit-source-id: 10271945e089183f505205bd41de5e01faea7568
1 parent eebc829 commit addf4da

File tree

3 files changed

+362
-10
lines changed

3 files changed

+362
-10
lines changed

Libraries/NativeComponent/NativeComponentRegistry.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* @format
99
*/
1010

11+
import * as StaticViewConfigValidator from './StaticViewConfigValidator';
1112
import {createViewConfig} from './ViewConfig';
1213
import UIManager from '../ReactNative/UIManager';
1314
import type {
@@ -36,6 +37,7 @@ export function setRuntimeConfigProvider(
3637
name: string,
3738
) => ?{
3839
native: boolean,
40+
strict: boolean,
3941
verify: boolean,
4042
},
4143
): void {
@@ -57,8 +59,9 @@ export function get<Config>(
5759
viewConfigProvider: () => PartialViewConfig,
5860
): HostComponent<Config> {
5961
ReactNativeViewConfigRegistry.register(name, () => {
60-
const {native, verify} = getRuntimeConfig?.(name) ?? {
62+
const {native, strict, verify} = getRuntimeConfig?.(name) ?? {
6163
native: true,
64+
strict: false,
6265
verify: false,
6366
};
6467

@@ -67,16 +70,33 @@ export function get<Config>(
6770
: createViewConfig(viewConfigProvider());
6871

6972
if (verify) {
70-
if (native) {
71-
verifyComponentAttributeEquivalence(
72-
viewConfig,
73-
createViewConfig(viewConfigProvider()),
74-
);
73+
if (strict) {
74+
const results = native
75+
? StaticViewConfigValidator.validate(
76+
name,
77+
viewConfig,
78+
createViewConfig(viewConfigProvider()),
79+
)
80+
: StaticViewConfigValidator.validate(
81+
name,
82+
getNativeComponentAttributes(name),
83+
viewConfig,
84+
);
85+
if (results != null) {
86+
console.error(results);
87+
}
7588
} else {
76-
verifyComponentAttributeEquivalence(
77-
getNativeComponentAttributes(name),
78-
viewConfig,
79-
);
89+
if (native) {
90+
verifyComponentAttributeEquivalence(
91+
viewConfig,
92+
createViewConfig(viewConfigProvider()),
93+
);
94+
} else {
95+
verifyComponentAttributeEquivalence(
96+
getNativeComponentAttributes(name),
97+
viewConfig,
98+
);
99+
}
80100
}
81101
}
82102

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
* @format
9+
*/
10+
11+
import {type ViewConfig} from '../Renderer/shims/ReactNativeTypes';
12+
13+
type Difference = {
14+
path: $ReadOnlyArray<string>,
15+
type: 'missing' | 'unequal' | 'unexpected',
16+
};
17+
18+
/**
19+
* During the migration from native view configs to static view configs, this is
20+
* used to validate that the two are equivalent.
21+
*/
22+
export function validate(
23+
name: string,
24+
nativeViewConfig: ViewConfig,
25+
staticViewConfig: ViewConfig,
26+
): ?string {
27+
const differences = [];
28+
accumulateDifferences(
29+
differences,
30+
[],
31+
{
32+
bubblingEventTypes: nativeViewConfig.bubblingEventTypes,
33+
directEventTypes: nativeViewConfig.directEventTypes,
34+
uiViewClassName: nativeViewConfig.uiViewClassName,
35+
validAttributes: nativeViewConfig.validAttributes,
36+
},
37+
{
38+
bubblingEventTypes: staticViewConfig.bubblingEventTypes,
39+
directEventTypes: staticViewConfig.directEventTypes,
40+
uiViewClassName: staticViewConfig.uiViewClassName,
41+
validAttributes: staticViewConfig.validAttributes,
42+
},
43+
);
44+
if (differences.length === 0) {
45+
return null;
46+
}
47+
return [
48+
`StaticViewConfigValidator: Invalid static view config for '${name}'.`,
49+
'',
50+
...differences.map(({path, type}) => {
51+
switch (type) {
52+
case 'missing':
53+
return `- '${path.join('.')}' is missing.`;
54+
case 'unequal':
55+
return `- '${path.join('.')}' is the wrong value.`;
56+
case 'unexpected':
57+
return `- '${path.join('.')}' is present but not expected to be.`;
58+
}
59+
}),
60+
'',
61+
].join('\n');
62+
}
63+
64+
function accumulateDifferences(
65+
differences: Array<Difference>,
66+
path: Array<string>,
67+
nativeObject: {...},
68+
staticObject: {...},
69+
): void {
70+
for (const nativeKey in nativeObject) {
71+
const nativeValue = nativeObject[nativeKey];
72+
73+
if (!staticObject.hasOwnProperty(nativeKey)) {
74+
differences.push({path: [...path, nativeKey], type: 'missing'});
75+
continue;
76+
}
77+
78+
const staticValue = staticObject[nativeKey];
79+
80+
const nativeValueIfObject = ifObject(nativeValue);
81+
if (nativeValueIfObject != null) {
82+
const staticValueIfObject = ifObject(staticValue);
83+
if (staticValueIfObject != null) {
84+
path.push(nativeKey);
85+
accumulateDifferences(
86+
differences,
87+
path,
88+
nativeValueIfObject,
89+
staticValueIfObject,
90+
);
91+
path.pop();
92+
continue;
93+
}
94+
}
95+
96+
if (nativeValue !== staticValue) {
97+
differences.push({path: [...path, nativeKey], type: 'unequal'});
98+
}
99+
}
100+
101+
for (const staticKey in staticObject) {
102+
if (!nativeObject.hasOwnProperty(staticKey)) {
103+
differences.push({path: [...path, staticKey], type: 'unexpected'});
104+
}
105+
}
106+
}
107+
108+
function ifObject(value: mixed): ?{...} {
109+
return typeof value === 'object' && !Array.isArray(value) ? value : null;
110+
}

0 commit comments

Comments
 (0)