Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions packages/babel-plugin-codegen/__tests__/codegen-load-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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-local
* @format
*/

class ModuleNotFoundError extends Error {
code: string = 'MODULE_NOT_FOUND';
}

function missingLibError(): Error {
return new ModuleNotFoundError(
"Cannot find module '@react-native/codegen/lib/parsers/flow/parser'",
);
}

// Failure thrown by the virtual `lib/` mock; tests override it per case.
let mockLibLoadError: Error = missingLibError();

// `lib/` is gitignored build output and may not exist on disk, so it is
// mocked virtually for every test. This stays a top-level `jest.mock`
// because only it accepts the options argument under this repo's Flow libdef.
jest.mock(
'@react-native/codegen/lib/parsers/flow/parser',
() => {
throw mockLibLoadError;
},
{virtual: true},
);

describe('codegen loading', () => {
beforeEach(() => {
mockLibLoadError = missingLibError();
});

it('throws an actionable error when @react-native/codegen is not built', () => {
jest.isolateModules(() => {
jest.doMock('@react-native/codegen/src/parsers/flow/parser', () => {
throw new SyntaxError("Unexpected token '{'");
});
expect(() => {
// $FlowFixMe[untyped-import] `../index.js` is `@noflow`.
require('../index.js');
}).toThrow(/never built.*yarn workspace @react-native\/codegen build/s);
});
});

it('throws an actionable error when @react-native/codegen is not installed', () => {
jest.isolateModules(() => {
jest.doMock('@react-native/codegen/src/parsers/flow/parser', () => {
throw new ModuleNotFoundError('Cannot find module');
});
expect(() => {
// $FlowFixMe[untyped-import] `../index.js` is `@noflow`.
require('../index.js');
}).toThrow(/could not be found/);
});
});

it('throws an actionable error when the built lib is broken', () => {
jest.isolateModules(() => {
mockLibLoadError = new SyntaxError("Unexpected token '{'");
jest.doMock('@react-native/codegen/src/parsers/flow/parser', () => {
throw new ModuleNotFoundError('Cannot find module');
});
expect(() => {
// $FlowFixMe[untyped-import] `../index.js` is `@noflow`.
require('../index.js');
}).toThrow(/failed to load.*Rebuild/s);
});
});
});
45 changes: 36 additions & 9 deletions packages/babel-plugin-codegen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,49 @@ let FlowParser, TypeScriptParser, RNCodegen;
const {cheap: traverseCheap} = require('@babel/traverse').default;
const {basename} = require('node:path');

function getCodegenLoadError(srcError, libError) {
if (libError != null && libError.code !== 'MODULE_NOT_FOUND') {
return (
'Unable to load @react-native/codegen: its built `lib/` output failed ' +
'to load, so NativeComponent specs cannot be transformed. Rebuild it, ' +
'e.g. `yarn workspace @react-native/codegen build`, then retry.'
);
}
if (srcError != null && srcError.code !== 'MODULE_NOT_FOUND') {
return (
'Unable to load @react-native/codegen: the package is checked out ' +
'from source but was never built (its `lib/` output is missing), so ' +
'NativeComponent specs cannot be transformed. Build it first, e.g. ' +
'`yarn workspace @react-native/codegen build`, then retry.'
);
}
return (
'Unable to load @react-native/codegen: the package could not be found, ' +
'so NativeComponent specs cannot be transformed. Install dependencies ' +
'first, then retry.'
);
}

try {
FlowParser =
require('@react-native/codegen/src/parsers/flow/parser').FlowParser;
TypeScriptParser =
require('@react-native/codegen/src/parsers/typescript/parser').TypeScriptParser;
RNCodegen = require('@react-native/codegen/src/generators/RNCodegen');
} catch (e) {
// Fallback to lib when source doesn't exit (e.g. when installed as a dev dependency)
FlowParser =
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
TypeScriptParser =
} catch (srcError) {
// Fallback to lib when source isn't usable (e.g. when installed as a dev dependency)
try {
FlowParser =
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
TypeScriptParser =
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
} catch (libError) {
throw new Error(getCodegenLoadError(srcError, libError), {cause: libError});
}
}

const flowParser = new FlowParser();
Expand Down
Loading