Skip to content

Commit

Permalink
Implement filtering for platform specific spec files
Browse files Browse the repository at this point in the history
Summary:
This diff helps the library maintainer to keep their spec file platform specific if some specs make no sense in one platform or in the other.

We are filtering the spec files when we need to create the Schema.

The diff modifies also the call sites in the `scripts` (for iOS) and in the `gradle-plugin` (for Android).

It also adds tests for the new functions in the CLI.

The change is completely additive and it should not change any pre-existing behaviour.

## Changelog
[General][Added] - Add support for platform-specific specs

Reviewed By: cortinico

Differential Revision: D40008581

fbshipit-source-id: b7fcf6d38f85fe10e4e00002d3c6f2910abdbe35
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Oct 7, 2022
1 parent 00b7956 commit 7680bde
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* 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
* @oncall react_native
*/

'use-strict';

const {parseArgs, filterJSFile} = require('../combine-utils.js');

describe('parseArgs', () => {
const nodeBin = 'node';
const combineApp = 'app';
const schemaJson = 'schema.json';
const specFile1 = 'NativeSpec.js';
const specFile2 = 'SpecNativeComponent.js';

describe('when no platform provided', () => {
it('returns null platform, schema and fileList', () => {
const {platform, outfile, fileList} = parseArgs([
nodeBin,
combineApp,
schemaJson,
specFile1,
specFile2,
]);

expect(platform).toBeNull();
expect(outfile).toBe(schemaJson);
expect(fileList).toStrictEqual([specFile1, specFile2]);
});
});

describe('when platform passed with --platform', () => {
it('returns the platform, the schema and the fileList', () => {
const {platform, outfile, fileList} = parseArgs([
nodeBin,
combineApp,
'--platform',
'ios',
schemaJson,
specFile1,
specFile2,
]);

expect(platform).toBe('ios');
expect(outfile).toBe(schemaJson);
expect(fileList).toStrictEqual([specFile1, specFile2]);
});
});

describe('when platform passed with -p', () => {
it('returns the platform, the schema and the fileList', () => {
const {platform, outfile, fileList} = parseArgs([
nodeBin,
combineApp,
'-p',
'android',
schemaJson,
specFile1,
specFile2,
]);

expect(platform).toBe('android');
expect(outfile).toBe(schemaJson);
expect(fileList).toStrictEqual([specFile1, specFile2]);
});
});
});

describe('filterJSFile', () => {
describe('When the file is not a Spec file', () => {
it('when no platform is passed, return false', () => {
const file = 'anyJSFile.js';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});

it('when ios is passed and the file is iOS specific, return false', () => {
const file = 'anyJSFile.ios.js';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});

it('when android is passed and the file is android specific, return false', () => {
const file = 'anyJSFile.android.js';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
});

describe('When the file is NativeUIManager', () => {
it('returns false', () => {
const file = 'NativeUIManager.js';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
});

describe('When the file is NativeSampleTurboModule', () => {
it('returns false', () => {
const file = 'NativeSampleTurboModule.js';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
});

describe('When the file is a test file', () => {
it('returns false', () => {
const file = '__tests__/NativeModule-test.js';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
});

describe('When the file is a TS type def', () => {
it('returns false', () => {
const file = 'NativeModule.d.ts';
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
});

describe('When the file is valid and it is platform agnostic', () => {
const file = 'NativeModule.js';
it('if the platform is null, returns true', () => {
const result = filterJSFile(file);
expect(result).toBeTruthy();
});
it('if the platform is ios, returns true', () => {
const result = filterJSFile(file, 'ios');
expect(result).toBeTruthy();
});
it('if the platform is android, returns true', () => {
const result = filterJSFile(file, 'android');
expect(result).toBeTruthy();
});
it('if the platform is windows, returns false', () => {
const result = filterJSFile(file, 'windows');
expect(result).toBeTruthy();
});
});

describe('When the file is valid and it is iOS specific', () => {
const file = 'MySampleNativeComponent.ios.js';
it('if the platform is null, returns false', () => {
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
it('if the platform is ios, returns true', () => {
const result = filterJSFile(file, 'ios');
expect(result).toBeTruthy();
});
it('if the platform is android, returns false', () => {
const result = filterJSFile(file, 'android');
expect(result).toBeFalsy();
});
it('if the platform is windows, returns false', () => {
const result = filterJSFile(file, 'windows');
expect(result).toBeFalsy();
});
});

describe('When the file is valid and it is Android specific', () => {
const file = 'MySampleNativeComponent.android.js';
it('if the platform is null, returns false', () => {
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
it('if the platform is ios, returns false', () => {
const result = filterJSFile(file, 'ios');
expect(result).toBeFalsy();
});
it('if the platform is android, returns true', () => {
const result = filterJSFile(file, 'android');
expect(result).toBeTruthy();
});
it('if the platform is windows, returns false', () => {
const result = filterJSFile(file, 'windows');
expect(result).toBeFalsy();
});
});

describe('When the file is valid and it is Windows specific', () => {
const file = 'MySampleNativeComponent.windows.js';
it('if the platform is null, returns false', () => {
const result = filterJSFile(file);
expect(result).toBeFalsy();
});
it('if the platform is ios, returns false', () => {
const result = filterJSFile(file, 'ios');
expect(result).toBeFalsy();
});
it('if the platform is android, returns false', () => {
const result = filterJSFile(file, 'android');
expect(result).toBeFalsy();
});
it('if the platform is windows, returns true', () => {
const result = filterJSFile(file, 'windows');
expect(result).toBeTruthy();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,9 @@
const combine = require('./combine-js-to-schema');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const {parseArgs, filterJSFile} = require('./combine-utils');

const [outfile, ...fileList] = process.argv.slice(2);

function filterJSFile(file: string) {
return (
/^(Native.+|.+NativeComponent)/.test(path.basename(file)) &&
// NativeUIManager will be deprecated by Fabric UIManager.
// For now, ignore this spec completely because the types are not fully supported.
!file.endsWith('NativeUIManager.js') &&
// NativeSampleTurboModule is for demo purpose. It should be added manually to the
// app for now.
!file.endsWith('NativeSampleTurboModule.js') &&
!file.includes('__tests') &&
// Ignore TypeScript type declaration files.
!file.endsWith('.d.ts')
);
}
const {platform, outfile, fileList} = parseArgs(process.argv);

const allFiles = [];
fileList.forEach(file => {
Expand All @@ -40,7 +25,7 @@ fileList.forEach(file => {
.sync(`${file}/**/*.{js,ts,tsx}`, {
nodir: true,
})
.filter(filterJSFile);
.filter(element => filterJSFile(element, platform));
allFiles.push(...dirFiles);
} else if (filterJSFile(file)) {
allFiles.push(file);
Expand Down
85 changes: 85 additions & 0 deletions packages/react-native-codegen/src/cli/combine/combine-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* 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
* @oncall react_native
*/

'use strict';

const path = require('path');

function parseArgs(args: string[]): {
platform: ?string,
outfile: string,
fileList: string[],
} {
if (args.length > 2 && ['-p', '--platform'].indexOf(args[2]) >= 0) {
const [outfile, ...fileList] = args.slice(4);
return {
platform: args[3],
outfile,
fileList,
};
}

const [outfile, ...fileList] = args.slice(2);
return {
platform: null,
outfile,
fileList,
};
}

/**
* This function is used by the CLI to decide whether a JS/TS file has to be processed or not by the Codegen.
* Parameters:
* - file: the path to the file
* - currentPlatform: the current platform for which we are creating the specs
* Returns: `true` if the file can be used to generate some code; `false` otherwise
*
*/
function filterJSFile(file: string, currentPlatform: ?string): boolean {
const isSpecFile = /^(Native.+|.+NativeComponent)/.test(path.basename(file));
const isNotNativeUIManager = !file.endsWith('NativeUIManager.js');
const isNotNativeSampleTurboModule = !file.endsWith(
'NativeSampleTurboModule.js',
);
const isNotTest = !file.includes('__tests');
const isNotTSTypeDefinition = !file.endsWith('.d.ts');

const isValidCandidate =
isSpecFile &&
isNotNativeUIManager &&
isNotNativeSampleTurboModule &&
isNotTest &&
isNotTSTypeDefinition;

const filenameComponents = path.basename(file).split('.');
const isPlatformAgnostic = filenameComponents.length === 2;

if (currentPlatform == null) {
// need to accept only files that are platform agnostic
return isValidCandidate && isPlatformAgnostic;
}

// If a platform is passed, accept both platform agnostic specs...
if (isPlatformAgnostic) {
return isValidCandidate;
}

// ...and specs that share the same platform as the one passed.
// specfiles must follow the pattern: <filename>[.<platform>].(js|ts|tsx)
const filePlatform =
filenameComponents.length > 2 ? filenameComponents[1] : 'unknown';
return isValidCandidate && currentPlatform === filePlatform;
}

module.exports = {
parseArgs,
filterJSFile,
};
65 changes: 65 additions & 0 deletions packages/react-native-codegen/src/parsers/__tests__/utils-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* 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.
*
* @format
* @oncall react_native
*/

'use strict';
const {extractNativeModuleName} = require('../utils.js');

describe('extractnativeModuleName', () => {
it('return filename when it ends with .js', () => {
const filename = '/some_folder/NativeModule.js';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .ts', () => {
const filename = '/some_folder/NativeModule.ts';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .tsx', () => {
const filename = '/some_folder/NativeModule.tsx';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .android.js', () => {
const filename = '/some_folder/NativeModule.android.js';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .android.ts', () => {
const filename = '/some_folder/NativeModule.android.ts';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .android.tsx', () => {
const filename = '/some_folder/NativeModule.android.tsx';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .ios.js', () => {
const filename = '/some_folder/NativeModule.ios.ts';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .ios.ts', () => {
const filename = '/some_folder/NativeModule.ios.ts';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .ios.tsx', () => {
const filename = '/some_folder/NativeModule.ios.tsx';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
it('return filename when it ends with .windows.js', () => {
const filename = '/some_folder/NativeModule.windows.js';
const nativeModuleName = extractNativeModuleName(filename);
expect(nativeModuleName).toBe('NativeModule');
});
});
Loading

0 comments on commit 7680bde

Please sign in to comment.