@react-native/compatibility-check
Version:
Check a React Native app's boundary between JS and Native for incompatibilities
1,386 lines (1,384 loc) • 40.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.addedUnionMessage =
exports.addedPropertiesMessage =
exports.addedIntersectionMessage =
exports.addedEnumMessage =
void 0;
exports.assessComparisonResult = assessComparisonResult;
exports.buildSchemaDiff = buildSchemaDiff;
exports.fromNativeVoidChangeMessage = void 0;
exports.hasCodegenUpdatesTypes = hasCodegenUpdatesTypes;
exports.hasUpdatesTypes = hasUpdatesTypes;
exports.stricterPropertiesMessage =
exports.removedUnionMessage =
exports.removedPropertiesMessage =
exports.removedIntersectionMessage =
exports.removedEnumMessage =
void 0;
exports.summarizeDiffSet = summarizeDiffSet;
exports.typeNullableChangeMessage =
exports.typeNonNullableChangeMessage =
exports.tooOptionalPropertiesMessage =
exports.toNativeVoidChangeMessage =
void 0;
var _ComparisonResult = require("./ComparisonResult.js");
var _convertPropToBasicTypes = _interopRequireDefault(
require("./convertPropToBasicTypes"),
);
var codegenTypeDiffing = _interopRequireWildcard(require("./TypeDiffing"));
function _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function wrapErrorWithPathContext(error, message, pathContext, propertyName) {
const wrappedError =
pathContext?.type === "TypeAnnotationComparisonError" ||
pathContext?.type === "TypeInformationComparisonError"
? (0, _ComparisonResult.typeAnnotationComparisonError)(
message,
pathContext.newerAnnotation,
pathContext.olderAnnotation,
error.errorInformation,
)
: error.errorInformation;
if (propertyName != null) {
return {
...error,
errorInformation: (0, _ComparisonResult.propertyComparisonError)(
"Object contained a property with a type mismatch",
[
{
property: propertyName,
fault: wrappedError,
},
],
),
};
}
return {
...error,
errorInformation: wrappedError,
};
}
function nestedPropertiesCheck(
typeName,
result,
check,
inverseCheck,
pathContext,
) {
switch (result.status) {
case "error":
case "matching":
case "skipped":
throw new Error(
"Internal error: nested property change " + result.status,
);
case "properties":
let finalResult = check(
result.propertyLog,
null,
null,
null,
typeName,
pathContext ?? null,
);
if (result.propertyLog.nestedPropertyChanges) {
const nestedErrors = result.propertyLog.nestedPropertyChanges.flatMap(
([propertyName, nestedResult]) => {
const errors = nestedPropertiesCheck(
typeName,
nestedResult,
check,
inverseCheck,
nestedResult.errorLog ?? null,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
"has conflicting type changes",
nestedResult.errorLog,
propertyName,
),
);
},
);
finalResult = finalResult.concat(nestedErrors);
}
if (result.propertyLog.madeOptional) {
const furtherNestedProps = result.propertyLog.madeOptional.filter(
(optionalProp) => optionalProp.furtherChanges,
);
if (furtherNestedProps && furtherNestedProps.length > 0) {
const nestedErrors = furtherNestedProps.flatMap((optionalProp) => {
if (optionalProp.furtherChanges) {
const errors = nestedPropertiesCheck(
typeName,
optionalProp.furtherChanges,
check,
inverseCheck,
optionalProp.furtherChanges.errorLog ?? null,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
"has conflicting type changes",
optionalProp.furtherChanges?.errorLog,
optionalProp.property,
),
);
}
throw new Error("Internal error, filter failed");
});
finalResult = finalResult.concat(nestedErrors);
}
}
return finalResult;
case "members":
return check(null, null, null, result.memberLog, typeName, null);
case "functionChange":
let returnTypeResult = [];
if (result.functionChangeLog.returnType) {
returnTypeResult = nestedPropertiesCheck(
typeName,
result.functionChangeLog.returnType,
check,
inverseCheck,
result.functionChangeLog.returnType.errorLog ?? null,
);
}
if (result.functionChangeLog.parameterTypes) {
const parameterErrors =
result.functionChangeLog.parameterTypes.nestedChanges.flatMap(
([_oldParameterNumber, newParameterNumber, nestedResult]) => {
const errors = nestedPropertiesCheck(
typeName,
nestedResult,
inverseCheck,
check,
nestedResult.errorLog ?? null,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
`Parameter at index ${newParameterNumber} did not match`,
nestedResult.errorLog,
),
);
},
);
return returnTypeResult.concat(parameterErrors);
}
return returnTypeResult;
case "positionalTypeChange":
const changeLog = result.changeLog;
const currentPositionalCheck = check(
null,
changeLog,
null,
null,
typeName,
null,
);
const positionalNestedErrors = changeLog.nestedChanges.flatMap(
([_oldIndex, newIndex, nestedResult]) => {
const errors = nestedPropertiesCheck(
typeName,
nestedResult,
check,
inverseCheck,
nestedResult.errorLog ?? null,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
`Element ${newIndex} of ${changeLog.typeKind} did not match`,
nestedResult.errorLog,
),
);
},
);
return currentPositionalCheck.concat(positionalNestedErrors);
case "nullableChange":
const currentCheck = check(
null,
null,
result.nullableLog,
null,
typeName,
null,
);
if (result.nullableLog.interiorLog) {
const interiorLog = result.nullableLog.interiorLog;
switch (interiorLog.status) {
case "matching":
return currentCheck;
case "properties":
case "functionChange":
case "positionalTypeChange":
case "nullableChange":
return currentCheck.concat(
nestedPropertiesCheck(
typeName,
interiorLog,
check,
inverseCheck,
interiorLog.errorLog ?? null,
),
);
default:
throw new Error(
"Internal error: nested with error or skipped status",
);
}
}
return currentCheck;
default:
result.status;
return [];
}
}
function checkOptionalityAndSetError(
typeName,
properties,
msg,
errorCode,
pathContext,
) {
const requiredProperties = properties.filter(
(objectTypeProperty) => !objectTypeProperty.optional,
);
if (requiredProperties.length > 0) {
return [
{
typeName,
errorCode,
errorInformation: (0, _ComparisonResult.propertyComparisonError)(
msg,
requiredProperties.map((property) => ({
property: property.name,
})),
pathContext ?? undefined,
),
},
];
}
return [];
}
const removedPropertiesMessage = (exports.removedPropertiesMessage =
"Object removed required properties expected by native");
function checkForUnsafeRemovedProperties(
propertyChange,
_postionalChange,
_nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (propertyChange && propertyChange.missingProperties) {
return checkOptionalityAndSetError(
typeName,
propertyChange.missingProperties,
removedPropertiesMessage,
"removedProps",
pathContext,
);
}
return [];
}
const addedPropertiesMessage = (exports.addedPropertiesMessage =
"Object added required properties, which native will not provide");
function checkForUnsafeAddedProperties(
propertyChange,
_positionalChange,
_nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (propertyChange && propertyChange.addedProperties) {
return checkOptionalityAndSetError(
typeName,
propertyChange.addedProperties,
addedPropertiesMessage,
"addedProps",
pathContext,
);
}
return [];
}
const stricterPropertiesMessage = (exports.stricterPropertiesMessage =
"Property made strict, but native may not provide it");
function checkForUnSafeMadeStrictProperties(
propertyChange,
_positionalChange,
_nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (
propertyChange &&
propertyChange.madeStrict &&
propertyChange.madeStrict.length > 0
) {
const err = (0, _ComparisonResult.propertyComparisonError)(
stricterPropertiesMessage,
propertyChange.madeStrict.map((property) => ({
property: property.property,
})),
pathContext ?? undefined,
);
return [
{
typeName,
errorCode: "requiredProps",
errorInformation: err,
},
];
}
return [];
}
const tooOptionalPropertiesMessage = (exports.tooOptionalPropertiesMessage =
"Property made optional, but native requires it");
function checkForUnSafeMadeOptionalProperties(
propertyChange,
_positionalChange,
_nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (
propertyChange &&
propertyChange.madeOptional &&
propertyChange.madeOptional.length > 0
) {
const err = (0, _ComparisonResult.propertyComparisonError)(
tooOptionalPropertiesMessage,
propertyChange.madeOptional.map((property) => ({
property: property.property,
})),
pathContext ?? undefined,
);
return [
{
typeName,
errorCode: "optionalProps",
errorInformation: err,
},
];
}
return [];
}
const removedEnumMessage = (exports.removedEnumMessage =
"Enum removed items, but native may still provide them");
const removedUnionMessage = (exports.removedUnionMessage =
"Union removed items, but native may still provide them");
function checkForUnsafeRemovedMemberItems(
_propertyChange,
_positionalChange,
_nullableChange,
memberChange,
typeName,
pathContext,
) {
if (memberChange == null) {
return [];
}
if (memberChange.memberKind === "enum") {
const missingMembers = memberChange.missingMembers;
if (missingMembers && missingMembers.length > 0) {
return [
{
typeName,
errorCode: "removedMemberCases",
errorInformation: (0, _ComparisonResult.memberComparisonError)(
removedEnumMessage,
missingMembers.map((member) => ({
member: member.name,
})),
pathContext ?? undefined,
),
},
];
}
} else {
const missingMembers = memberChange.missingMembers;
if (missingMembers && missingMembers.length > 0) {
return [
{
typeName,
errorCode: "removedMemberCases",
errorInformation: (0, _ComparisonResult.memberComparisonError)(
removedUnionMessage,
missingMembers.map((member) => ({
member: getTypeAnnotationLabel(member),
})),
pathContext ?? undefined,
),
},
];
}
}
return [];
}
const addedEnumMessage = (exports.addedEnumMessage =
"Enum added items, but native will not expect/support them");
const addedUnionMessage = (exports.addedUnionMessage =
"Union added items, but native will not expect/support them");
function checkForUnsafeAddedMemberItems(
_propertyChange,
_positionalChange,
_nullableChange,
memberChange,
typeName,
pathContext,
) {
if (memberChange == null) {
return [];
}
if (memberChange.memberKind === "enum") {
const addedMembers = memberChange.addedMembers;
if (addedMembers && addedMembers.length > 0) {
return [
{
typeName,
errorCode: "addedMemberCases",
errorInformation: (0, _ComparisonResult.memberComparisonError)(
addedEnumMessage,
addedMembers.map((member) => ({
member: member.name,
})),
pathContext ?? undefined,
),
},
];
}
} else {
const addedMembers = memberChange.addedMembers;
if (addedMembers && addedMembers.length > 0) {
return [
{
typeName,
errorCode: "addedMemberCases",
errorInformation: (0, _ComparisonResult.memberComparisonError)(
addedUnionMessage,
addedMembers.map((member) => ({
member: getTypeAnnotationLabel(member),
})),
pathContext ?? undefined,
),
},
];
}
}
return [];
}
function getTypeAnnotationLabel(type) {
if (type.type === "StringLiteralTypeAnnotation") {
return `"${type.value}"`;
} else if (type.type === "NumberLiteralTypeAnnotation") {
return String(type.value);
} else if (type.type === "BooleanLiteralTypeAnnotation") {
return String(type.value);
} else if (type.type === "NullableTypeAnnotation") {
return `?${getTypeAnnotationLabel(type.typeAnnotation)}`;
} else if (type.type === "TypeAliasTypeAnnotation") {
return type.name;
} else {
return type.type;
}
}
const removedIntersectionMessage = (exports.removedIntersectionMessage =
"Intersection removed items, but native may still require properties contained in them");
function checkForUnsafeRemovedIntersectionItems(
_propertyChange,
positionalChange,
_nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (
positionalChange &&
positionalChange.typeKind === "intersection" &&
positionalChange.removedElements &&
positionalChange.removedElements.length > 0
) {
return [
{
typeName,
errorCode: "removedIntersectCases",
errorInformation: (0, _ComparisonResult.positionalComparisonError)(
removedIntersectionMessage,
positionalChange.removedElements,
pathContext ?? undefined,
),
},
];
}
return [];
}
const addedIntersectionMessage = (exports.addedIntersectionMessage =
"Intersection added items, but native may not provide all required attributes");
function checkForUnsafeAddedIntersectionItems(
_propertyChange,
positionalChange,
_nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (
positionalChange &&
positionalChange.typeKind === "intersection" &&
positionalChange.addedElements &&
positionalChange.addedElements.length > 0
) {
return [
{
typeName,
errorCode: "addedIntersectCases",
errorInformation: (0, _ComparisonResult.positionalComparisonError)(
addedIntersectionMessage,
positionalChange.addedElements,
pathContext ?? undefined,
),
},
];
}
return [];
}
const toNativeVoidChangeMessage = (exports.toNativeVoidChangeMessage =
"Native may not be able to safely handle presence of type");
const typeNullableChangeMessage = (exports.typeNullableChangeMessage =
"Type made nullable, but native requires it");
function checkForUnsafeNullableToNativeChange(
_propertyChange,
_positionalChange,
nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (
nullableChange &&
!nullableChange.optionsReduced &&
nullableChange.newType &&
nullableChange.oldType
) {
return [
{
typeName,
errorCode: "nullableOfNonNull",
errorInformation: (0, _ComparisonResult.typeAnnotationComparisonError)(
nullableChange.typeRefined
? toNativeVoidChangeMessage
: typeNullableChangeMessage,
nullableChange.newType,
nullableChange.oldType,
pathContext ?? undefined,
),
},
];
}
return [];
}
const fromNativeVoidChangeMessage = (exports.fromNativeVoidChangeMessage =
"Type set to void but native may still provide a value");
const typeNonNullableChangeMessage = (exports.typeNonNullableChangeMessage =
"Type made non-nullable, but native might provide null still");
function checkForUnsafeNullableFromNativeChange(
_propertyChange,
_positionalChange,
nullableChange,
_memberChange,
typeName,
pathContext,
) {
if (
nullableChange &&
nullableChange.optionsReduced &&
nullableChange.newType &&
nullableChange.oldType
) {
return [
{
typeName,
errorCode: "nonNullableOfNull",
errorInformation: (0, _ComparisonResult.typeAnnotationComparisonError)(
nullableChange.typeRefined
? fromNativeVoidChangeMessage
: typeNonNullableChangeMessage,
nullableChange.newType,
nullableChange.oldType,
pathContext ?? undefined,
),
},
];
}
return [];
}
function chainPropertiesChecks(checks) {
return (
propertyChange,
positionalChange,
nullableChange,
memberChange,
typeName,
pathContext,
) =>
checks.reduce(
(errorStore, checker) =>
errorStore.concat(
checker(
propertyChange,
positionalChange,
nullableChange,
memberChange,
typeName,
pathContext,
),
),
[],
);
}
function compareFunctionTypesInContext(
typeName,
functionLog,
functionErrorLog,
check,
inversecheck,
result,
) {
if (functionLog.returnType) {
result = result.concat(
nestedPropertiesCheck(
typeName,
functionLog.returnType,
check,
inversecheck,
),
);
}
if (
functionLog.parameterTypes &&
functionLog.parameterTypes.nestedChanges.length > 0
) {
const parameterErrors = functionLog.parameterTypes.nestedChanges.flatMap(
([_oldPropertyNum, newPropertyNum, nestedResult]) => {
const errors = nestedPropertiesCheck(
typeName,
nestedResult,
inversecheck,
check,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
`Parameter at index ${newPropertyNum} did not match`,
nestedResult.errorLog,
),
);
},
);
result = result.concat(parameterErrors);
}
return result;
}
const checksForTypesFlowingToNative = chainPropertiesChecks([
checkForUnsafeRemovedProperties,
checkForUnSafeMadeOptionalProperties,
checkForUnsafeAddedMemberItems,
checkForUnsafeRemovedIntersectionItems,
checkForUnsafeNullableToNativeChange,
]);
const checksForTypesFlowingFromNative = chainPropertiesChecks([
checkForUnsafeAddedProperties,
checkForUnSafeMadeStrictProperties,
checkForUnsafeRemovedMemberItems,
checkForUnsafeAddedIntersectionItems,
checkForUnsafeNullableFromNativeChange,
]);
function assessComparisonResult(
newTypes,
deprecatedTypes,
incompatibleChanges,
objectTypeChanges,
) {
return (typeName, newType, oldType, difference, oldDirection) => {
switch (difference.status) {
case "matching":
break;
case "skipped":
newTypes.add({
typeName,
typeInformation: newType,
});
break;
case "members":
{
const memberChange = difference.memberLog;
const toNativeErrorResult = checksForTypesFlowingToNative(
null,
null,
null,
memberChange,
typeName,
difference.errorLog,
);
const fromNativeErrorResult = checksForTypesFlowingFromNative(
null,
null,
null,
memberChange,
typeName,
difference.errorLog,
);
switch (oldDirection) {
case "toNative":
toNativeErrorResult.forEach((error) =>
incompatibleChanges.add(error),
);
break;
case "fromNative":
fromNativeErrorResult.forEach((error) =>
incompatibleChanges.add(error),
);
break;
case "both":
toNativeErrorResult.forEach((error) =>
incompatibleChanges.add(error),
);
fromNativeErrorResult.forEach((error) =>
incompatibleChanges.add(error),
);
break;
}
}
break;
case "properties":
const propertyChange = difference.propertyLog;
const unsafeForToNative = nestedPropertiesCheck(
typeName,
difference,
checksForTypesFlowingToNative,
checksForTypesFlowingFromNative,
);
const unsafeForFromNative = nestedPropertiesCheck(
typeName,
difference,
checksForTypesFlowingFromNative,
checksForTypesFlowingToNative,
);
switch (oldDirection) {
case "toNative":
unsafeForToNative.forEach((error) =>
incompatibleChanges.add(error),
);
break;
case "fromNative":
unsafeForFromNative.forEach((error) =>
incompatibleChanges.add(error),
);
break;
case "both":
unsafeForToNative.forEach((error) =>
incompatibleChanges.add(error),
);
unsafeForFromNative.forEach((error) =>
incompatibleChanges.add(error),
);
break;
}
if (!oldType) {
throw new Error("Internal error: properties change with no old type");
}
objectTypeChanges.add({
typeName,
newType,
oldType,
propertyChange,
});
break;
case "error":
incompatibleChanges.add({
typeName,
errorCode: "incompatibleTypes",
errorInformation: difference.errorLog,
});
break;
case "functionChange":
const functionLog = difference.functionChangeLog;
let propertyErrors = [];
switch (oldDirection) {
case "toNative":
propertyErrors = compareFunctionTypesInContext(
typeName,
functionLog,
difference.errorLog,
checksForTypesFlowingToNative,
checksForTypesFlowingFromNative,
propertyErrors,
);
break;
case "fromNative":
propertyErrors = compareFunctionTypesInContext(
typeName,
functionLog,
difference.errorLog,
checksForTypesFlowingFromNative,
checksForTypesFlowingToNative,
propertyErrors,
);
break;
case "both":
propertyErrors = compareFunctionTypesInContext(
typeName,
functionLog,
difference.errorLog,
checksForTypesFlowingToNative,
checksForTypesFlowingFromNative,
propertyErrors,
);
propertyErrors = compareFunctionTypesInContext(
typeName,
functionLog,
difference.errorLog,
checksForTypesFlowingFromNative,
checksForTypesFlowingToNative,
propertyErrors,
);
break;
default:
throw new Error(
"Unsupported native boundary direction " + oldDirection,
);
}
propertyErrors.forEach((error) => incompatibleChanges.add(error));
break;
case "positionalTypeChange":
const changeLog = difference.changeLog;
if (
changeLog.nestedChanges.length > 0 ||
changeLog.addedElements ||
changeLog.removedElements
) {
const changes = changeLog.nestedChanges;
const toNativeBase = checksForTypesFlowingToNative(
null,
changeLog,
null,
null,
typeName,
null,
);
const toNativeNestedErrors = changes.flatMap(
([_oldIndex, newIndex, nestedResult]) => {
const errors = nestedPropertiesCheck(
typeName,
nestedResult,
checksForTypesFlowingToNative,
checksForTypesFlowingFromNative,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
`Element ${newIndex} of ${changeLog.typeKind} did not match`,
nestedResult.errorLog,
),
);
},
);
const toNativeResult = toNativeBase.concat(toNativeNestedErrors);
const fromNativeBase = checksForTypesFlowingFromNative(
null,
changeLog,
null,
null,
typeName,
null,
);
const fromNativeNestedErrors = changes.flatMap(
([_oldIndex, newIndex, nestedResult]) => {
const errors = nestedPropertiesCheck(
typeName,
nestedResult,
checksForTypesFlowingFromNative,
checksForTypesFlowingToNative,
);
return errors.map((error) =>
wrapErrorWithPathContext(
error,
`Element ${newIndex} of ${changeLog.typeKind} did not match`,
nestedResult.errorLog,
),
);
},
);
const fromNativeResult = fromNativeBase.concat(
fromNativeNestedErrors,
);
switch (oldDirection) {
case "toNative":
toNativeResult.forEach((error) => incompatibleChanges.add(error));
break;
case "fromNative":
fromNativeResult.forEach((error) =>
incompatibleChanges.add(error),
);
break;
case "both":
toNativeResult.forEach((error) => incompatibleChanges.add(error));
fromNativeResult.forEach((error) =>
incompatibleChanges.add(error),
);
break;
}
}
break;
case "nullableChange":
if (!oldType) {
throw new Error(
"Internal error: old type null or undefined, after nullableChange",
);
}
switch (oldDirection) {
case "toNative":
checkForUnsafeNullableToNativeChange(
null,
null,
difference.nullableLog,
null,
typeName,
difference.errorLog,
).forEach((error) => incompatibleChanges.add(error));
break;
case "fromNative":
checkForUnsafeNullableFromNativeChange(
null,
null,
difference.nullableLog,
null,
typeName,
difference.errorLog,
).forEach((error) => incompatibleChanges.add(error));
break;
case "both":
const err = (0, _ComparisonResult.typeInformationComparisonError)(
"Type may not change nullability, due to flowing to and from native",
newType,
oldType,
difference.errorLog,
);
incompatibleChanges.add({
typeName,
errorCode: "incompatibleTypes",
errorInformation: err,
});
break;
default:
throw new Error("Unknown direction : " + oldDirection);
}
if (difference.interiorLog) {
const log = difference.interiorLog;
assessComparisonResult(
newTypes,
deprecatedTypes,
incompatibleChanges,
objectTypeChanges,
)(typeName, newType, oldType, log, oldDirection);
}
break;
default:
difference.status;
throw new Error("Unsupported status: " + difference.status);
}
};
}
function buildNativeModulesDiff(newerNativeModule, olderNativeModule) {
const moduleErrors = new Set();
const nativeModuleName = newerNativeModule.moduleName;
if (olderNativeModule.moduleName !== newerNativeModule.moduleName) {
moduleErrors.add({
nativeSpecName: olderNativeModule.moduleName,
omitted: true,
errorCode: "removedModule",
});
}
const newTypes = new Set();
const deprecatedTypes = new Set();
const incompatibleChanges = new Set();
const objectTypeChanges = new Set();
const localAssessComparison = assessComparisonResult(
newTypes,
deprecatedTypes,
incompatibleChanges,
objectTypeChanges,
);
const newType = {
type: "ObjectTypeAnnotation",
properties: [
...newerNativeModule.spec.methods,
...newerNativeModule.spec.eventEmitters,
],
};
const oldType = {
type: "ObjectTypeAnnotation",
properties: [
...olderNativeModule.spec.methods,
...olderNativeModule.spec.eventEmitters,
],
};
const difference = codegenTypeDiffing.compareTypes(
newType,
olderNativeModule.moduleName === newerNativeModule.moduleName
? oldType
: null,
newerNativeModule.aliasMap,
olderNativeModule.aliasMap,
newerNativeModule.enumMap,
olderNativeModule.enumMap,
);
localAssessComparison(
nativeModuleName,
newType,
oldType,
difference,
"fromNative",
);
const typeUpdate = {
newTypes,
deprecatedTypes,
incompatibleChanges,
objectTypeChanges,
};
if (hasCodegenUpdatesTypes(typeUpdate)) {
moduleErrors.add({
nativeSpecName: nativeModuleName,
omitted: false,
errorCode: "incompatibleTypes",
changeInformation: typeUpdate,
});
}
return moduleErrors;
}
function buildNativeComponentsDiff(newerNativeSchema, olderNativeSchema) {
const componentErrors = new Set();
Object.entries(newerNativeSchema.components).forEach(
([newerComponentName, newerComponent]) => {
const olderComponent = olderNativeSchema.components[newerComponentName];
const newTypes = new Set();
const deprecatedTypes = new Set();
const incompatibleChanges = new Set();
const objectTypeChanges = new Set();
const localAssessComparison = assessComparisonResult(
newTypes,
deprecatedTypes,
incompatibleChanges,
objectTypeChanges,
);
newerComponent.commands.forEach((command) => {
const oldCommand = olderComponent.commands?.find(
(olderCommand) => olderCommand.name === command.name,
);
const newCommands = {
type: "ObjectTypeAnnotation",
properties: [command],
};
const oldCommands =
oldCommand != null
? {
type: "ObjectTypeAnnotation",
properties: [oldCommand],
}
: null;
const difference = codegenTypeDiffing.compareTypes(
newCommands,
oldCommands,
{},
{},
{},
{},
);
localAssessComparison(
newerComponentName,
newCommands,
oldCommands,
difference,
"fromNative",
);
});
olderComponent.commands?.forEach((command) => {
const newCommand = newerComponent.commands.find(
(newerCommand) => newerCommand.name === command.name,
);
if (newCommand == null) {
deprecatedTypes.add({
typeName: command.name,
typeInformation: {
type: "ObjectTypeAnnotation",
properties: [command],
},
});
}
});
const newConvertedProps = {
type: "ObjectTypeAnnotation",
properties: newerComponent.props.map((prop) => ({
name: prop.name,
optional: prop.optional,
typeAnnotation: (0, _convertPropToBasicTypes.default)(
prop.typeAnnotation,
),
})),
};
const oldConvertedProps = {
type: "ObjectTypeAnnotation",
properties: olderComponent.props.map((prop) => ({
name: prop.name,
optional: prop.optional,
typeAnnotation: (0, _convertPropToBasicTypes.default)(
prop.typeAnnotation,
),
})),
};
const propDifference = codegenTypeDiffing.compareTypes(
newConvertedProps,
oldConvertedProps,
{},
{},
{},
{},
);
localAssessComparison(
newerComponentName,
newConvertedProps,
oldConvertedProps,
propDifference,
"toNative",
);
const typeUpdate = {
newTypes,
deprecatedTypes,
incompatibleChanges,
objectTypeChanges,
};
if (hasCodegenUpdatesTypes(typeUpdate)) {
componentErrors.add({
nativeSpecName: newerComponentName,
omitted: false,
errorCode: "incompatibleTypes",
changeInformation: typeUpdate,
});
}
},
);
Object.keys(olderNativeSchema.components).forEach((olderComponentName) => {
const newerComponent = newerNativeSchema.components[olderComponentName];
if (newerComponent == null) {
componentErrors.add({
nativeSpecName: olderComponentName,
omitted: true,
errorCode: "removedComponent",
});
}
});
return componentErrors;
}
function hasUpdatesTypes(diff) {
return (
diff.newTypes.size > 0 ||
diff.deprecatedTypes.size > 0 ||
diff.objectTypeChanges.size > 0 ||
diff.incompatibleChanges.size > 0
);
}
function hasCodegenUpdatesTypes(diff) {
return (
diff.newTypes.size > 0 ||
diff.deprecatedTypes.size > 0 ||
diff.objectTypeChanges.size > 0 ||
diff.incompatibleChanges.size > 0
);
}
function buildSchemaDiff(newerSchemaSet, olderSchemaSet) {
const diff = new Set();
const newerSchema = newerSchemaSet.modules;
const olderSchema = olderSchemaSet.modules;
Object.keys(newerSchema).forEach((hasteModuleName) => {
const schemaEntry = newerSchema[hasteModuleName];
const olderSchemaEntry = olderSchema[hasteModuleName];
const framework = "ReactNative";
if (schemaEntry.type === "Component") {
if (olderSchemaEntry?.type === "Component") {
const incompatibleComponents = buildNativeComponentsDiff(
schemaEntry,
olderSchemaEntry,
);
const hasIncompatibleComponents = incompatibleComponents?.size > 0;
if (hasIncompatibleComponents) {
diff.add({
name: hasteModuleName,
framework: framework,
status: {
incompatibleSpecs: incompatibleComponents,
},
});
}
}
}
if (schemaEntry.type === "NativeModule") {
if (olderSchemaEntry?.type === "NativeModule") {
const incompatibleModules = buildNativeModulesDiff(
schemaEntry,
olderSchemaEntry,
);
const hasIncompatibleModules =
incompatibleModules != null && incompatibleModules.size;
if (hasIncompatibleModules) {
diff.add({
name: hasteModuleName,
framework: framework,
status: {
incompatibleSpecs: incompatibleModules,
},
});
}
}
}
if (olderSchemaEntry == null) {
diff.add({
name: hasteModuleName,
framework: framework,
status: "new",
});
}
});
Object.keys(olderSchema).forEach((hasteModuleName) => {
const newSchemaEntry = newerSchema[hasteModuleName];
const oldSchemaEntry = olderSchema[hasteModuleName];
if (oldSchemaEntry != null && newSchemaEntry == null) {
diff.add({
name: hasteModuleName,
framework: "ReactNative",
status: "deprecated",
});
}
});
return diff;
}
function summarizeSchemaDiff(diff) {
switch (diff.status) {
case "new":
return {
status: "patchable",
incompatibilityReport: {},
};
case "deprecated":
return {
status: "ok",
incompatibilityReport: {},
};
default:
const differs = diff.status;
if (!differs.incompatibleSpecs) {
return {
status: "patchable",
incompatibilityReport: {},
};
} else {
const incompatibleObject = {};
if (differs.incompatibleSpecs) {
const withErrors = Array.from(differs.incompatibleSpecs).filter(
(specError) =>
specError.errorInformation ||
(specError.changeInformation &&
specError.changeInformation.incompatibleChanges.size > 0),
);
if (withErrors.length > 0) {
if (incompatibleObject[diff.name]) {
incompatibleObject[diff.name].incompatibleSpecs = withErrors;
} else {
incompatibleObject[diff.name] = {
framework: diff.framework,
incompatibleSpecs: withErrors,
};
}
}
}
const incompatibleUnchanged =
Object.keys(incompatibleObject).length === 0;
return {
status: incompatibleUnchanged ? "ok" : "incompatible",
incompatibilityReport: incompatibleObject,
};
}
}
}
function combineSummaries(finalSummary, setSummary) {
switch (setSummary.status) {
case "ok":
return finalSummary;
case "patchable":
if (finalSummary.status === "ok") {
return setSummary;
} else {
return finalSummary;
}
default:
switch (finalSummary.status) {
case "ok":
case "patchable":
return setSummary;
default:
Object.keys(setSummary.incompatibilityReport).forEach(
(differingSchemaName) =>
(finalSummary.incompatibilityReport[differingSchemaName] =
setSummary.incompatibilityReport[differingSchemaName]),
);
return finalSummary;
}
}
}
function summarizeDiffSet(diffs) {
if (diffs.size === 0) {
return {
status: "ok",
incompatibilityReport: {},
};
}
const summary = [];
diffs.forEach((schemaDiff) => summary.push(summarizeSchemaDiff(schemaDiff)));
return summary.reduce(combineSummaries, summary[0]);
}