Skip to content

Commit a4d82c4

Browse files
robhoganfacebook-github-bot
authored andcommitted
Add private-deep-imports lint (disallow metro*/src/...) (#1528)
Summary: Pull Request resolved: #1528 Differential Revision: D77832800
1 parent 5e3a6bb commit a4d82c4

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

scripts/eslint/base.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ module.exports = {
5959
'flowtype/use-flow-type': 0,
6060
// flow handles this check for us, so it's not required
6161
'no-undef': 0,
62+
63+
'lint/metro-deep-imports': 'warn',
6264
},
6365
overrides: [
6466
{
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
'use strict';
13+
14+
const rule = require('../metro-deep-imports.js');
15+
const ESLintTester = require('eslint').RuleTester;
16+
17+
ESLintTester.setDefaultConfig({
18+
parser: require.resolve('hermes-eslint'),
19+
parserOptions: {
20+
ecmaVersion: 6,
21+
sourceType: 'module',
22+
},
23+
});
24+
25+
const eslintTester = new ESLintTester();
26+
27+
eslintTester.run('../metro-deep-imports', rule, {
28+
valid: [
29+
'require("metro")',
30+
'const Foo = require("metro-subpkg")',
31+
'require("metro/private/Bar")',
32+
'import Baz from "metro-baz/private/Baz"',
33+
'import NotMetro from "foo/src/bar"',
34+
35+
// metro-runtime allows subpath imports. We can't rely on package#exports
36+
// redirections as they may be disabled under Metro, and we must be able
37+
// to import single modules as polyfills are side-effectful.
38+
'import Polyfill from "metro-runtime/src/polyfills/foo"',
39+
'const Polyfill = require("metro-runtime/src/polyfills/foo")',
40+
],
41+
invalid: [
42+
{
43+
code: 'const myLib = require("metro/src/lib")',
44+
output: "const myLib = require('metro/private/lib')",
45+
},
46+
{
47+
code: "import MetroInternal from 'metro-pkg/src/internal'",
48+
output: "import MetroInternal from 'metro-pkg/private/internal'",
49+
},
50+
{
51+
code: "import type {Bar} from 'metro-types/src/bar'",
52+
output: "import type {Bar} from 'metro-types/private/bar'",
53+
},
54+
].map(obj => ({...obj, errors: [{messageId: 'METRO_DEEP_IMPORT'}]})),
55+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict-local
9+
*/
10+
11+
'use strict';
12+
13+
// $FlowExpectedError[untyped-type-import] - eslint not typed in OSS
14+
import type {RuleModule, SuggestionReportDescriptor} from 'eslint';
15+
import type {StringLiteral} from 'hermes-estree';
16+
17+
const METRO_DEEP_IMPORT_RE = /^(metro(?!-runtime)(?:-[a-z\-]+)?)\/src\//;
18+
const messageId = 'METRO_DEEP_IMPORT';
19+
20+
module.exports = {
21+
meta: {
22+
type: 'problem',
23+
docs: {
24+
description:
25+
'Deep imports from Metro must use explicitly-private subpaths',
26+
},
27+
messages: {
28+
METRO_DEEP_IMPORT:
29+
"Metro deep imports from src ('{{originalImport.value}}') are deprecated. Prefer top level imports, or replace '/src/' with '/private/'.",
30+
},
31+
schema: [] as const,
32+
fixable: 'code',
33+
},
34+
35+
create(context) {
36+
return {
37+
ImportDeclaration(node) {
38+
if (
39+
typeof node.source.value !== 'string' ||
40+
!METRO_DEEP_IMPORT_RE.test(node.source.value)
41+
) {
42+
return;
43+
}
44+
const stringNode = node.source;
45+
context.report({
46+
node: node.source,
47+
messageId,
48+
data: {originalImport: stringNode.value},
49+
fix: getFix(stringNode),
50+
});
51+
},
52+
CallExpression(node) {
53+
if (
54+
node.callee.type !== 'Identifier' ||
55+
node.callee.name !== 'require' ||
56+
node.arguments.length < 1 ||
57+
node.arguments[0].type !== 'Literal' ||
58+
node.arguments[0].literalType !== 'string' ||
59+
!METRO_DEEP_IMPORT_RE.test(node.arguments[0].value)
60+
) {
61+
return;
62+
}
63+
const stringNode = node.arguments[0];
64+
context.report({
65+
node,
66+
messageId,
67+
data: {originalImport: stringNode.value},
68+
fix: getFix(stringNode),
69+
});
70+
},
71+
};
72+
},
73+
} as RuleModule;
74+
75+
function getFix(
76+
nodeToReplace: StringLiteral,
77+
): SuggestionReportDescriptor['fix'] {
78+
return fixer =>
79+
fixer.replaceText(
80+
nodeToReplace,
81+
`'${nodeToReplace.value.replace(METRO_DEEP_IMPORT_RE, '$1/private/')}'`,
82+
);
83+
}

0 commit comments

Comments
 (0)