UNPKG

@react-native/eslint-plugin-specs

Version:
198 lines (168 loc) • 5.09 kB
/** * 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 withBabelRegister = require('./with-babel-register'); const path = require('path'); // We use the prepack hook before publishing package to set this value to true const PACKAGE_USAGE = true; const ERRORS = { misnamedHasteModule(hasteModuleName) { return `Module ${hasteModuleName}: All files using TurboModuleRegistry must start with Native.`; }, }; let RNModuleParser; let RNParserUtils; let RNFlowParser; let RNParserCommons; let RNFlowParserUtils; function requireModuleParser() { if ( RNModuleParser == null || RNParserUtils == null || RNFlowParser == null || RNParserCommons == null || RNFlowParserUtils == null ) { // If using this externally, we leverage @react-native/codegen as published form if (!PACKAGE_USAGE) { const config = { only: [/react-native-codegen\/src\//], plugins: [require('@babel/plugin-transform-flow-strip-types').default], }; withBabelRegister(config, () => { RNModuleParser = require('@react-native/codegen/src/parsers/flow/modules'); RNParserUtils = require('@react-native/codegen/src/parsers/utils'); RNFlowParser = require('@react-native/codegen/src/parsers/flow/parser'); RNParserCommons = require('@react-native/codegen/src/parsers/parsers-commons'); RNFlowParserUtils = require('@react-native/codegen/src/parsers/flow/utils'); }); } else { const config = { only: [/@react-native\/codegen\/lib\//], plugins: [require('@babel/plugin-transform-flow-strip-types').default], }; withBabelRegister(config, () => { RNModuleParser = require('@react-native/codegen/lib/parsers/flow/modules'); RNParserUtils = require('@react-native/codegen/lib/parsers/utils'); RNFlowParser = require('@react-native/codegen/lib/parsers/flow/parser'); RNParserCommons = require('@react-native/codegen/lib/parsers/parsers-commons'); RNFlowParserUtils = require('@react-native/codegen/lib/parsers/flow/utils'); }); } } return { buildModuleSchema: RNParserCommons.buildModuleSchema, createParserErrorCapturer: RNParserUtils.createParserErrorCapturer, parser: new RNFlowParser.FlowParser(), translateTypeAnnotation: RNModuleParser.flowTranslateTypeAnnotation, }; } const VALID_SPEC_NAMES = /^Native\S+$/; function isModuleRequire(node) { if (node.type !== 'CallExpression') { return false; } const callExpression = node; if (callExpression.callee.type !== 'MemberExpression') { return false; } const memberExpression = callExpression.callee; if ( !( memberExpression.object.type === 'Identifier' && memberExpression.object.name === 'TurboModuleRegistry' ) ) { return false; } if ( !( memberExpression.property.type === 'Identifier' && (memberExpression.property.name === 'get' || memberExpression.property.name === 'getEnforcing') ) ) { return false; } return true; } function isGeneratedFile(context) { return ( context .getSourceCode() .getText() .indexOf('@' + 'generated SignedSource<<') !== -1 ); } /** * A lint rule to guide best practices in writing type safe React NativeModules. */ function rule(context) { const filename = context.getFilename(); const hasteModuleName = path.basename(filename).replace(/\.js$/, ''); if (isGeneratedFile(context)) { return {}; } let isModule = false; return { 'Program:exit': function (node) { if (!isModule) { return; } // Report invalid file names if (!VALID_SPEC_NAMES.test(hasteModuleName)) { context.report({ node, message: ERRORS.misnamedHasteModule(hasteModuleName), }); } const { buildModuleSchema, createParserErrorCapturer, parser, translateTypeAnnotation, } = requireModuleParser(); const [parsingErrors, tryParse] = createParserErrorCapturer(); const sourceCode = context.getSourceCode().getText(); const ast = parser.getAst(sourceCode, filename); tryParse(() => { buildModuleSchema( hasteModuleName, ast, tryParse, parser, translateTypeAnnotation, ); }); parsingErrors.forEach(error => { error.nodes.forEach(flowNode => { context.report({ loc: flowNode.loc, message: error.message, }); }); }); }, CallExpression(node) { if (!isModuleRequire(node)) { return; } isModule = true; }, InterfaceExtends(node) { if (node.id.name !== 'TurboModule') { return; } isModule = true; }, }; } rule.errors = ERRORS; module.exports = rule;