Files
react-native/scripts/js-api/diff-api-snapshot/diffApiSnapshot.js
T
Alex Hunt 918f02dcc3 Consolidate JS API scripts under scripts/js-api/, update docs (#52469)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52469

Organise `scripts/build-types/` and `scripts/diff-api-snapshot/` into a single grouping `scripts/js-api/` parent dir — matching the newly relocated `scripts/cxx-api/`.

Changelog: [Internal]

Reviewed By: robhogan

Differential Revision: D77865488

fbshipit-source-id: 33754d9275e65c3bda686294f18d855221ec7bff
2025-07-07 15:04:37 -07:00

132 lines
3.6 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
* @format
* @oncall react_native
*/
const babel = require('@babel/core');
const t = require('@babel/types');
const Result = {
BREAKING: 'BREAKING',
POTENTIALLY_NON_BREAKING: 'POTENTIALLY_NON_BREAKING',
NON_BREAKING: 'NON_BREAKING',
} as const;
type Output = {
result: $Values<typeof Result>,
changedApis: Array<string>,
};
type Hash = string;
type APISpecifier = string;
function diffApiSnapshot(prevSnapshot: string, newSnapshot: string): Output {
const prevSnapshotAST = babel.parseSync(prevSnapshot, {
plugins: ['@babel/plugin-syntax-typescript'],
});
const newSnapshotAST = babel.parseSync(newSnapshot, {
plugins: ['@babel/plugin-syntax-typescript'],
});
const prevSpecHashPair = getExportedSymbols(prevSnapshotAST);
const newSpecHashPair = getExportedSymbols(newSnapshotAST);
if (prevSpecHashPair == null || newSpecHashPair == null) {
return {
result: Result.BREAKING,
changedApis: [],
};
}
return analyzeSpecHashPairs(prevSpecHashPair, newSpecHashPair);
}
function analyzeSpecHashPairs(
prevSpecHashPairs: Array<[APISpecifier, Hash]>,
newSpecHashPairs: Array<[APISpecifier, Hash]>,
): Output {
const output = {
result: Result.NON_BREAKING,
changedApis: [],
} as Output;
const newSpecHashMapping = Object.fromEntries(newSpecHashPairs);
for (const [name, hash] of prevSpecHashPairs) {
if (newSpecHashMapping[name]) {
const newHash = newSpecHashMapping[name];
if (hash !== newHash) {
// The hash has changed which means that the statement has changed
output.result = Result.BREAKING;
output.changedApis.push(name);
}
delete newSpecHashMapping[name];
} else {
// There is no statement of that name in the new rollup which means that:
// 1. This statement was entirely removed
// 2. This statement was renamed
// 3. It is not public anymore
output.result = Result.BREAKING;
output.changedApis.push(name);
}
}
if (
output.result === Result.NON_BREAKING &&
Object.keys(newSpecHashMapping).length > 0
) {
output.result = Result.POTENTIALLY_NON_BREAKING;
for (const name of Object.keys(newSpecHashMapping)) {
// saving new APIs
output.changedApis.push(name);
}
}
return output;
}
function getExportedSymbols(
ast: BabelNodeFile,
): Array<[APISpecifier, Hash]> | null {
for (const nodePath of ast.program.body) {
if (
t.isExportNamedDeclaration(nodePath) &&
!nodePath.declaration &&
nodePath.specifiers != null
) {
const specifiers = nodePath.specifiers;
const result: Array<[APISpecifier, Hash]> = [];
for (let i = 0; i < specifiers.length; i++) {
const specifier = specifiers[i];
const name = specifier.exported.name || '';
const comment =
specifier.leadingComments && specifier.leadingComments.length > 0
? specifier.leadingComments[0]?.value
: '';
if (i > 0) {
result[i - 1][1] = comment;
}
result.push([name, comment]);
}
const lastSpec = specifiers[specifiers.length - 1];
const comment =
lastSpec.trailingComments && lastSpec.trailingComments.length > 0
? lastSpec.trailingComments[0]?.value
: '';
result[result.length - 1][1] = comment;
return result;
}
}
return null;
}
module.exports = {diffApiSnapshot, Result};