UNPKG

@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
"use strict"; 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]); }