mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
c1168cf919
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52365 Adds type imports autofix support and pass `fb_internal` paths in react native deep imports eslint rule. The rule fixes imports with all matched types with static API mapping to prevent splits. Changelog: [Internal] Reviewed By: huntie Differential Revision: D77445445 fbshipit-source-id: cd5b75b4b3b53792117b8297352dddc4d63dbf70
185 lines
5.4 KiB
JavaScript
185 lines
5.4 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.
|
|
*
|
|
* @format
|
|
* @noflow
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
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/');
|
|
}
|
|
},
|
|
};
|