mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41956 By default, generated Cxx sources for components all end up in same directory. However the include declarations in them look like this: ``` #include <react/renderer/components/${libraryName}/ShadowNodes.h> ``` And not like this: ``` #include "ShadowNodes.h" ``` This works fine with Buck because it supports header prefixes. To get this working with CocoaPods we define additional `HEADER_SEARCH_PATHS` for our `React-Codegen` pod. This approach will not work if we want to generate code at the library level and check in the artifacts. That's because we don't have control over the Podspec there, and can't inject those additional `HEADER_SEARCH_PATHS`. This diff adds the `headerPrefix` argument to the codegen entry point. It is `react/renderer/components/${libraryName}` by default, but can become empty if we want to generate code at the library level, and don't want to deal with this nested header structure. *Note:* `RNCodegen` runs all the generators [in a loop](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/generators/RNCodegen.js#L263-L275), assuming that the all have same function signature So I had to add the `headerPrefix` argument to all the generators, even to the ones that don't really need it. Changelog: [General][Added] - Introduce "headerPrefix" codegen option. Reviewed By: zeyap Differential Revision: D51811596 fbshipit-source-id: c5c3e1e571c7c4ea2f5354eb9a7b0df6b917fc0c
222 lines
5.9 KiB
JavaScript
222 lines
5.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
import type {ComponentShape, PropTypeAnnotation} from '../../CodegenSchema';
|
|
import type {SchemaType} from '../../CodegenSchema';
|
|
|
|
const {toSafeCppString} = require('../Utils');
|
|
const {getImports} = require('./CppHelpers');
|
|
|
|
type FilesOutput = Map<string, string>;
|
|
type PropValueType = string | number | boolean;
|
|
|
|
type TestCase = $ReadOnly<{
|
|
propName: string,
|
|
propValue: ?PropValueType,
|
|
testName?: string,
|
|
raw?: boolean,
|
|
}>;
|
|
|
|
const FileTemplate = ({
|
|
libraryName,
|
|
imports,
|
|
componentTests,
|
|
}: {
|
|
libraryName: string,
|
|
imports: string,
|
|
componentTests: string,
|
|
}) =>
|
|
`
|
|
/**
|
|
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
*
|
|
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
* once the code is regenerated.
|
|
*
|
|
* ${'@'}generated by codegen project: GenerateTests.js
|
|
* */
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <react/renderer/core/PropsParserContext.h>
|
|
#include <react/renderer/components/${libraryName}/Props.h>
|
|
${imports}
|
|
|
|
using namespace facebook::react;
|
|
${componentTests}
|
|
`.trim();
|
|
|
|
const TestTemplate = ({
|
|
componentName,
|
|
testName,
|
|
propName,
|
|
propValue,
|
|
}: {
|
|
componentName: string,
|
|
testName: string,
|
|
propName: string,
|
|
propValue: string,
|
|
}) => `
|
|
TEST(${componentName}_${testName}, etc) {
|
|
auto propParser = RawPropsParser();
|
|
propParser.prepare<${componentName}>();
|
|
auto const &sourceProps = ${componentName}();
|
|
auto const &rawProps = RawProps(folly::dynamic::object("${propName}", ${propValue}));
|
|
|
|
ContextContainer contextContainer{};
|
|
PropsParserContext parserContext{-1, contextContainer};
|
|
|
|
rawProps.parse(propParser, parserContext);
|
|
${componentName}(parserContext, sourceProps, rawProps);
|
|
}
|
|
`;
|
|
|
|
function getTestCasesForProp(
|
|
propName: string,
|
|
typeAnnotation: PropTypeAnnotation,
|
|
) {
|
|
const cases = [];
|
|
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
|
|
typeAnnotation.options.forEach(option =>
|
|
cases.push({
|
|
propName,
|
|
testName: `${propName}_${toSafeCppString(option)}`,
|
|
propValue: option,
|
|
}),
|
|
);
|
|
} else if (typeAnnotation.type === 'StringTypeAnnotation') {
|
|
cases.push({
|
|
propName,
|
|
propValue:
|
|
typeAnnotation.default != null && typeAnnotation.default !== ''
|
|
? typeAnnotation.default
|
|
: 'foo',
|
|
});
|
|
} else if (typeAnnotation.type === 'BooleanTypeAnnotation') {
|
|
cases.push({
|
|
propName: propName,
|
|
propValue: typeAnnotation.default != null ? typeAnnotation.default : true,
|
|
});
|
|
// $FlowFixMe[incompatible-type]
|
|
} else if (typeAnnotation.type === 'IntegerTypeAnnotation') {
|
|
cases.push({
|
|
propName,
|
|
propValue: typeAnnotation.default || 10,
|
|
});
|
|
} else if (typeAnnotation.type === 'FloatTypeAnnotation') {
|
|
cases.push({
|
|
propName,
|
|
propValue: typeAnnotation.default != null ? typeAnnotation.default : 0.1,
|
|
});
|
|
} else if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
|
|
if (typeAnnotation.name === 'ColorPrimitive') {
|
|
cases.push({
|
|
propName,
|
|
propValue: 1,
|
|
});
|
|
} else if (typeAnnotation.name === 'PointPrimitive') {
|
|
cases.push({
|
|
propName,
|
|
propValue: 'folly::dynamic::object("x", 1)("y", 1)',
|
|
raw: true,
|
|
});
|
|
} else if (typeAnnotation.name === 'ImageSourcePrimitive') {
|
|
cases.push({
|
|
propName,
|
|
propValue: 'folly::dynamic::object("url", "testurl")',
|
|
raw: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
return cases;
|
|
}
|
|
|
|
function generateTestsString(name: string, component: ComponentShape) {
|
|
function createTest({testName, propName, propValue, raw = false}: TestCase) {
|
|
const value =
|
|
!raw && typeof propValue === 'string' ? `"${propValue}"` : propValue;
|
|
|
|
return TestTemplate({
|
|
componentName: name,
|
|
testName: testName != null ? testName : propName,
|
|
propName,
|
|
propValue: String(value),
|
|
});
|
|
}
|
|
|
|
const testCases = component.props.reduce((cases: Array<TestCase>, prop) => {
|
|
return cases.concat(getTestCasesForProp(prop.name, prop.typeAnnotation));
|
|
}, []);
|
|
|
|
const baseTest = {
|
|
testName: 'DoesNotDie',
|
|
propName: 'xx_invalid_xx',
|
|
propValue: 'xx_invalid_xx',
|
|
};
|
|
|
|
return [baseTest, ...testCases].map(createTest).join('');
|
|
}
|
|
|
|
module.exports = {
|
|
generate(
|
|
libraryName: string,
|
|
schema: SchemaType,
|
|
packageName?: string,
|
|
assumeNonnull: boolean = false,
|
|
headerPrefix?: string,
|
|
): FilesOutput {
|
|
const fileName = 'Tests.cpp';
|
|
const allImports = new Set([
|
|
'#include <react/renderer/core/propsConversions.h>',
|
|
'#include <react/renderer/core/RawProps.h>',
|
|
'#include <react/renderer/core/RawPropsParser.h>',
|
|
]);
|
|
|
|
const componentTests = Object.keys(schema.modules)
|
|
.map(moduleName => {
|
|
const module = schema.modules[moduleName];
|
|
if (module.type !== 'Component') {
|
|
return;
|
|
}
|
|
|
|
const {components} = module;
|
|
if (components == null) {
|
|
return null;
|
|
}
|
|
|
|
return Object.keys(components)
|
|
.map(componentName => {
|
|
const component = components[componentName];
|
|
const name = `${componentName}Props`;
|
|
|
|
const imports = getImports(component.props);
|
|
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
|
imports.forEach(allImports.add, allImports);
|
|
|
|
return generateTestsString(name, component);
|
|
})
|
|
.join('');
|
|
})
|
|
.filter(Boolean)
|
|
.join('');
|
|
|
|
const imports = Array.from(allImports).sort().join('\n').trim();
|
|
|
|
const replacedTemplate = FileTemplate({
|
|
imports,
|
|
libraryName,
|
|
componentTests,
|
|
});
|
|
|
|
return new Map([[fileName, replacedTemplate]]);
|
|
},
|
|
};
|