109 строки
3.2 KiB
JavaScript
109 строки
3.2 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
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const RN_ROOT_DIRECTORY = path.resolve(__dirname, '..', '..', '..');
|
|
const RN_IMPORT_PATTERN = /^react-native(?:\/(?<relativeImport>.+))?$/;
|
|
const RN_IMPORT_EXTS = ['.android.js', '.ios.js', '.js'];
|
|
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: "Disallow importing from 'react-native'",
|
|
recommended: true,
|
|
},
|
|
fixable: 'code',
|
|
messages: {
|
|
rnImport: "Use relative paths when importing within 'react-native'",
|
|
},
|
|
schema: [],
|
|
},
|
|
|
|
create(context) {
|
|
return {
|
|
CallExpression(node) {
|
|
if (
|
|
node.callee.type === 'Identifier' &&
|
|
node.callee.name === 'require' &&
|
|
node.arguments.length === 1
|
|
) {
|
|
processSource(node.arguments[0]);
|
|
}
|
|
},
|
|
ImportExpression(node) {
|
|
processSource(node.source);
|
|
},
|
|
ImportDeclaration(node) {
|
|
processSource(node.source);
|
|
},
|
|
};
|
|
|
|
function processSource(source) {
|
|
if (source.type !== 'Literal' || typeof source.value !== 'string') {
|
|
return;
|
|
}
|
|
const match = source.value.match(RN_IMPORT_PATTERN);
|
|
if (match == null) {
|
|
return;
|
|
}
|
|
let fix;
|
|
// NOTE: We only attempt to resolve `react-native/<relativeImport>`. We
|
|
// cannot safely fix named imports from `react-native` because the index
|
|
// does arbitrary things like access the `default` property, etc.
|
|
if (match.groups.relativeImport != null) {
|
|
const suggestion = resolveRelativeImport(match.groups.relativeImport);
|
|
if (suggestion != null) {
|
|
fix = fixer => {
|
|
const delimiter = source.raw[0];
|
|
const replacement = [delimiter, suggestion, delimiter].join('');
|
|
return fixer.replaceText(source, replacement);
|
|
};
|
|
}
|
|
}
|
|
context.report({
|
|
node: source,
|
|
messageId: 'rnImport',
|
|
fix,
|
|
});
|
|
}
|
|
|
|
function resolveRelativeImport(relativeImport) {
|
|
const importPath = path.resolve(RN_ROOT_DIRECTORY, relativeImport);
|
|
if (!isValidImportPath(importPath)) {
|
|
return null;
|
|
}
|
|
const sourcePath = path.dirname(context.getPhysicalFilename());
|
|
const relativePath = path.relative(sourcePath, importPath);
|
|
// If importing from the same directory, prepend `./` to source path.
|
|
const relativePathWithLeadingSlash = relativePath.includes(path.sep)
|
|
? relativePath
|
|
: ['.', path.sep, relativePath].join('');
|
|
// Normalize backslash (on Windows) to forward slash.
|
|
return relativePathWithLeadingSlash.replace(/\\/g, '/');
|
|
}
|
|
|
|
function isValidImportPath(importPath) {
|
|
const candidatePaths = [];
|
|
if (importPath.endsWith('.js')) {
|
|
candidatePaths.push(importPath);
|
|
} else {
|
|
for (const extension of RN_IMPORT_EXTS) {
|
|
candidatePaths.push(importPath + extension);
|
|
}
|
|
}
|
|
return candidatePaths.some(candidatePath => fs.existsSync(candidatePath));
|
|
}
|
|
},
|
|
};
|