Custom eslint rule for disallowing cross-package references
Summary: Added infra for writing and using custom eslint rules and created a rule for disallowing cross-package file imports. Such imports are anti-pattern and they also break standalone plugin bundling. We still have a bunch of places where Flipper core references code directly from plugins. I've ignored these places for now and added task T71355623 to revisit them. Reviewed By: passy Differential Revision: D22998955 fbshipit-source-id: d04cff8fc115ba1300a7e6830306ec134046e927
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6989fa608d
commit
4a1c2a9ece
21
desktop/eslint-plugin-flipper/LICENSE
Normal file
21
desktop/eslint-plugin-flipper/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
8
desktop/eslint-plugin-flipper/jestconfig.json
Normal file
8
desktop/eslint-plugin-flipper/jestconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": ["/node_modules/", "/lib/"],
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||
}
|
||||
42
desktop/eslint-plugin-flipper/package.json
Normal file
42
desktop/eslint-plugin-flipper/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "eslint-plugin-flipper",
|
||||
"version": "0.52.1",
|
||||
"private": true,
|
||||
"description": "Custom ESLint rules for Flipper",
|
||||
"repository": "facebook/flipper",
|
||||
"main": "lib/index.js",
|
||||
"flipperBundlerEntry": "src",
|
||||
"types": "lib/index.d.ts",
|
||||
"license": "MIT",
|
||||
"bugs": "https://github.com/facebook/flipper/issues",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/experimental-utils": "^3.8.0",
|
||||
"fs-extra": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "26",
|
||||
"@typescript-eslint/parser": "^3.8.0",
|
||||
"flipper-test-utils": "0.52.1",
|
||||
"jest": "^26",
|
||||
"metro-memory-fs": "^0.61.0",
|
||||
"prettier": "^2.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.0.0",
|
||||
"ts-node": "^8",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"reset": "rimraf lib *.tsbuildinfo",
|
||||
"build": "tsc -b",
|
||||
"prepack": "yarn reset && yarn build"
|
||||
},
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"src/**/*"
|
||||
],
|
||||
"homepage": "https://github.com/facebook/flipper",
|
||||
"keywords": [
|
||||
"Flipper"
|
||||
],
|
||||
"author": "Facebook, Inc"
|
||||
}
|
||||
18
desktop/eslint-plugin-flipper/src/index.ts
Normal file
18
desktop/eslint-plugin-flipper/src/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import noRelativeImportsAcrossPackages, {
|
||||
RULE_NAME as noRelativeImportsAcrossPackagesRuleName,
|
||||
} from './rules/noRelativeImportsAcrossPackages';
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
[noRelativeImportsAcrossPackagesRuleName]: noRelativeImportsAcrossPackages,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {TSESLint} from '@typescript-eslint/experimental-utils';
|
||||
import rule, {RULE_NAME} from '../noRelativeImportsAcrossPackages';
|
||||
|
||||
const tester = new TSESLint.RuleTester({
|
||||
parser: require.resolve('@typescript-eslint/parser'),
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
});
|
||||
|
||||
tester.run(RULE_NAME, rule, {
|
||||
valid: [
|
||||
{
|
||||
code: `import * as testUtils from 'flipper-test-utils';`,
|
||||
filename: __filename,
|
||||
},
|
||||
{
|
||||
code: `const testUtils = require('flipper-test-utils');`,
|
||||
filename: __filename,
|
||||
},
|
||||
{
|
||||
code: `import rule, {RULE_NAME} from '../noRelativeImportsAcrossPackages';`,
|
||||
filename: __filename,
|
||||
},
|
||||
{
|
||||
code: `require('../noRelativeImportsAcrossPackages');`,
|
||||
filename: __filename,
|
||||
},
|
||||
{
|
||||
code: `import m from './subdir/module';`,
|
||||
filename: __filename,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `import * as pathUtils from '../../../../test-utils/src/pathUtils';`,
|
||||
filename: __filename,
|
||||
errors: [{messageId: 'noRelativeImportsAcrossPackages'}],
|
||||
},
|
||||
{
|
||||
code: `require('../../../../test-utils/src/pathUtils');`,
|
||||
filename: __filename,
|
||||
errors: [{messageId: 'noRelativeImportsAcrossPackages'}],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {resolve, dirname, join} from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import {TSESTree} from '@typescript-eslint/experimental-utils';
|
||||
import {createESLintRule} from '../utils/createEslintRule';
|
||||
|
||||
const rootDirs = new Map<string, string>();
|
||||
function findRootDir(path: string): string {
|
||||
const cachedRoot = rootDirs.get(path);
|
||||
if (cachedRoot) {
|
||||
return cachedRoot;
|
||||
}
|
||||
if (fs.pathExistsSync(join(path, 'package.json'))) {
|
||||
rootDirs.set(path, path);
|
||||
return path;
|
||||
}
|
||||
const parentRoot = findRootDir(dirname(path));
|
||||
rootDirs.set(path, parentRoot);
|
||||
return parentRoot;
|
||||
}
|
||||
|
||||
type Options = [];
|
||||
|
||||
export type MessageIds = 'noRelativeImportsAcrossPackages';
|
||||
export const RULE_NAME = 'no-relative-imports-across-packages';
|
||||
|
||||
export default createESLintRule<Options, MessageIds>({
|
||||
name: RULE_NAME,
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: `Ensure that package boundaries are respected within monorepo`,
|
||||
category: 'Possible Errors',
|
||||
recommended: 'error',
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
noRelativeImportsAcrossPackages: `Attempted to require "{{requiredPath}}" from file "{{filename}}" resolved to "{{resolvedPath}}" which is outside the package root dir "{{root}}".`,
|
||||
},
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
const filename = context.getFilename();
|
||||
const dir = dirname(filename);
|
||||
const root = findRootDir(dir);
|
||||
return {
|
||||
ImportDeclaration(node: TSESTree.ImportDeclaration) {
|
||||
if (typeof node.source.value === 'string') {
|
||||
const requiredPath = node.source.value;
|
||||
const resolvedPath = resolve(dir, requiredPath);
|
||||
if (
|
||||
// imported a file not a package
|
||||
requiredPath.startsWith('.') &&
|
||||
// the resolved path for the imported file is outside the package root
|
||||
!resolvedPath.startsWith(root)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noRelativeImportsAcrossPackages',
|
||||
data: {
|
||||
filename,
|
||||
root,
|
||||
requiredPath,
|
||||
resolvedPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
CallExpression(node: TSESTree.CallExpression) {
|
||||
const args = node.arguments || [];
|
||||
if (
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'require' &&
|
||||
args.length === 1 &&
|
||||
args[0].type === 'Literal' &&
|
||||
typeof args[0].value === 'string'
|
||||
) {
|
||||
const requiredPath = args[0].value;
|
||||
const resolvedPath = resolve(dir, requiredPath);
|
||||
if (
|
||||
// required a file not a package
|
||||
requiredPath.startsWith('.') &&
|
||||
// the resolved path for the required file is outside the package root
|
||||
!resolvedPath.startsWith(root)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noRelativeImportsAcrossPackages',
|
||||
data: {
|
||||
filename,
|
||||
root,
|
||||
requiredPath,
|
||||
resolvedPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
12
desktop/eslint-plugin-flipper/src/utils/createEslintRule.ts
Normal file
12
desktop/eslint-plugin-flipper/src/utils/createEslintRule.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {ESLintUtils} from '@typescript-eslint/experimental-utils';
|
||||
|
||||
export const createESLintRule = ESLintUtils.RuleCreator(() => ``);
|
||||
9
desktop/eslint-plugin-flipper/tsconfig.json
Normal file
9
desktop/eslint-plugin-flipper/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
}
|
||||
7
desktop/eslint-plugin-flipper/tslint.json
Normal file
7
desktop/eslint-plugin-flipper/tslint.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"interface-name": false,
|
||||
"variable-name": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user