@react-native/eslint-plugin
Version:
ESLint rules for @react-native/eslint-config
185 lines (167 loc) • 5.53 kB
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.
*
* @format
* @noflow
*/
;
const {publicAPIMapping} = require('./utils.js');
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Disallow deep imports from react native',
},
messages: {
deepImport:
"'{{importPath}}' React Native deep imports are deprecated. Please use the top level import instead.",
},
schema: [],
fixable: 'code',
},
create: function (context) {
return {
ImportDeclaration(node) {
if (
!isDeepReactNativeImport(node.source) ||
isInitializeCoreImport(node.source) ||
isFbInternalImport(node.source)
) {
return;
}
if (isDefaultImport(node)) {
const reactNativeSource = node.source.value.slice(
'react-native/'.length,
);
const publicAPIDefaultComponent = publicAPIMapping[reactNativeSource];
if (publicAPIDefaultComponent && publicAPIDefaultComponent.default) {
context.report({
...getStandardReport(node.source),
fix(fixer) {
return fixer.replaceText(
node,
`import {${publicAPIDefaultComponent.default}} from 'react-native';`,
);
},
});
} else {
context.report(getStandardReport(node.source));
}
} else if (isTypeImport(node)) {
const reactNativeSource = node.source.value.slice(
'react-native/'.length,
);
const publicAPIDefaultComponent = publicAPIMapping[reactNativeSource];
if (publicAPIDefaultComponent && publicAPIDefaultComponent.types) {
const typeNames = [];
for (const specifier of node.specifiers) {
const importedName = specifier.imported.name;
if (!publicAPIDefaultComponent.types.includes(importedName)) {
context.report(getStandardReport(node.source));
return;
}
typeNames.push(importedName);
}
context.report({
...getStandardReport(node.source),
fix(fixer) {
return fixer.replaceText(
node,
`import type {${typeNames.join(', ')}} from 'react-native';`,
);
},
});
}
} else {
context.report(getStandardReport(node.source));
}
},
CallExpression(node) {
if (
!isDeepRequire(node) ||
isInitializeCoreImport(node.arguments[0]) ||
isFbInternalImport(node.arguments[0])
) {
return;
}
const parent = node.parent;
const importPath = node.arguments[0].value;
if (
parent.type === 'VariableDeclarator' &&
parent.id.type === 'Identifier'
) {
const reactNativeSource = importPath.slice('react-native/'.length);
const publicAPIDefaultComponent = publicAPIMapping[reactNativeSource];
if (publicAPIDefaultComponent && publicAPIDefaultComponent.default) {
context.report({
...getStandardReport(node.arguments[0]),
fix(fixer) {
return fixer.replaceText(
parent,
`{${publicAPIDefaultComponent.default}} = require('react-native')`,
);
},
});
} else {
context.report(getStandardReport(node.arguments[0]));
}
} else {
context.report(getStandardReport(node.arguments[0]));
}
},
};
function getStandardReport(source) {
return {
node: source,
messageId: 'deepImport',
data: {
importPath: source.value,
},
};
}
function isDefaultImport(node) {
return (
node.specifiers.length === 1 &&
node.specifiers.some(
specifier => specifier.type === 'ImportDefaultSpecifier',
)
);
}
function isTypeImport(node) {
return node.type === 'ImportDeclaration' && node.importKind === 'type';
}
function isDeepRequire(node) {
return (
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments.length === 1 &&
node.arguments[0].type === 'Literal' &&
typeof node.arguments[0].value === 'string' &&
isDeepReactNativeImport(node.arguments[0])
);
}
function isDeepReactNativeImport(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}
const importPath = source.value;
const parts = importPath.split('/');
return parts.length > 1 && parts[0] === 'react-native';
}
function isInitializeCoreImport(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}
return source.value === 'react-native/Libraries/Core/InitializeCore';
}
function isFbInternalImport(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}
return source.value.startsWith('react-native/src/fb_internal/');
}
},
};