chore(feat): add no-package-imports rule

This commit is contained in:
Wlad 2021-04-20 13:37:43 +02:00
parent f237319379
commit 6cc821f540
8 changed files with 1735 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# @foomo/eslint-plugin
eslint utility rules to effectively handle monorepo setups
## Installation
You'll first need to install [ESLint](http://eslint.org):
```bash
$npm i eslint --save-dev
```
Next, install `@foomo/eslint-plugin`:
```bash
yarn add -D @foomo/eslint-plugin
```
## Usage
Add `@foomo/eslint-plugin` to the plugins section of your `.eslintrc` configuration file:
```json
{
"plugins": ["@foomo/eslint-plugin"]
}
```
Then configure the rules you want to use under the rules section.
```json
{****
"rules": {
"@foomo/no-package-imports": ["error", {
"options": [
{ "invalidPrefix": "packages", "invalidSuffix": "src", "monorepoRoot": "@organization" },
{ "invalidPrefix": "packages", "invalidSuffix": "src", "template": "@organization/$1/custom-prefix/$2" }
]
}]
}
}
```
## Supported Rules
- no-package-imports:
fixes forbidden import paths

View File

@ -0,0 +1,55 @@
# Fix relative and absolute imports to invalid locations (no-package-imports)
Monorepo setups may introduce valid yet unwanted ES imports picked up by the IDE.
This rule aims at finding and fixing these problems.
## Rule Details
This rule detects invalid imports via the option configuration. For example you want to avoid imports from `../packages/*/src` and
replace these with `@organization/*`
Examples of **incorrect** code for this rule:
```js
import { Button } from "../../packages/components/src/Button";
```
Examples of **correct** code for this rule:
```js
import { Button } from "@organization/components/Button";
```
### Options
The rule accepts a list of **replacements**, below is an example of the configuration for the above example
```js
{
"@foomo/no-package-imports": ["error", {
options: [{
invalidPrefix: "packages",
invalidSuffix: "src",
monorepoRoot: "@organization",
}],
}
]
}
```
#### Custom templates
One can also provide a custom replacement template **$1** is the package name **$2** is the import suffix e.g. everything after the **invalidSuffix**. Please note **$2** includes a slash so it can be omitted from the template.
```js
{
"@foomo/no-package-imports": ["error", {
options: [{
invalidPrefix: "packages",
invalidSuffix: "src",
template: "@testprefix/$1/somepath$2",
}],
}
]
}
```

22
lib/index.js Normal file
View File

@ -0,0 +1,22 @@
/**
* @fileoverview eslint plugin to effectively handle monorepo setups
* @author @gosticks
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var requireIndex = require("requireindex");
//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------
// import all rules in lib/rules
module.exports.rules = requireIndex(__dirname + "/rules");

View File

@ -0,0 +1,101 @@
/**
* @fileoverview Fix relative and absolute imports to invalid locations
* @author @gosticks
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "Fix relative and absolute imports to invalid locations",
category: "Possible Errors",
recommended: false,
},
fixable: "code", // or "code" or "whitespace"
schema: [
{
type: "object",
properties: {
options: {
type: "array",
items: {
anyOf: [
{
type: "object",
properties: {
invalidPrefix: {
type: "string",
},
invalidSuffix: {
type: "string",
},
monorepoRoot: {
type: "string",
},
template: {
type: "string",
},
},
additionalProperties: true,
},
],
},
},
},
},
],
messages: {
incorrectImport: "Import is invalid within the current monorepo setup",
},
},
create: function (context) {
// create a replacer regex and a target string
const replacers = context.options[0].options.map((option) => [
// match all string [../]*$invalidPrefix/anything/$invalidSuffix[/anything]
new RegExp(
`(?:(?:..\\/)*${option.invalidPrefix})+\\/([^\\/]+)\\/${option.invalidSuffix}(\\/(.+))?`
),
option.template ?? `${option.monorepoRoot}/$1$2`,
]);
/**
* report an error.
* @param {ASTNode} node the node to report.
* @param {[RegExp, string]} replacer regex to match and template for substitution
* @returns {void}
*/
function report(node, [replacer, str]) {
context.report({
node,
messageId: "incorrectImport",
fix: function (fixer) {
return fixer.replaceTextRange(
[node.source.range[0] + 1, node.source.range[1] - 1],
`${node.source.value.replace(replacer, str)}`
);
},
});
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
ImportDeclaration(node) {
// iterate over each replacer pair and substitute import source if needed
replacers.forEach(([replacer, str]) => {
const matchResults = node.source.value.match(replacer);
if (matchResults) {
report(node, [replacer, str]);
}
});
},
};
},
};

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "@foomo/eslint-plugin",
"version": "0.1.0",
"description": "eslint utility rules for monorepo project setup",
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin"
],
"author": "@gosticks",
"main": "lib/index.js",
"scripts": {
"test": "mocha tests --recursive"
},
"dependencies": {
"requireindex": "~1.1.0"
},
"devDependencies": {
"eslint": "^7.1.0",
"mocha": "^7.2.0"
},
"engines": {
"node": ">=0.10.0"
},
"license": "MIT"
}

View File

@ -0,0 +1,89 @@
/**
* @fileoverview Fix relative and absolute imports to invalid locations
* @author @gosticks
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
var rule = require("../../../lib/rules/no-package-imports"),
RuleTester = require("eslint").RuleTester;
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
const incorrectImport = { messageId: "incorrectImport" };
const options = [
{
options: [
{
invalidPrefix: "packages",
invalidSuffix: "src",
monorepoRoot: "@monorepo",
},
],
},
];
var ruleTester = new RuleTester();
ruleTester.run("no-package-imports", rule, {
valid: [
{
code: 'import Button from "@monorepo/components/Button"',
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options,
},
],
invalid: [
{
code: 'import { Button } from "../../packages/components/src/Button"',
output: 'import { Button } from "@monorepo/components/Button"',
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options,
errors: [incorrectImport],
},
{
code:
'import { Button } from "../../packages/components-combined/src/Button"',
output: 'import { Button } from "@monorepo/components-combined/Button"',
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options,
errors: [incorrectImport],
},
{
code: 'import Button from "../../packages/components/src/Button"',
output: 'import Button from "@testprefix/components/somepath/Button"',
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: [
{
options: [
{
invalidPrefix: "packages",
invalidSuffix: "src",
template: "@testprefix/$1/somepath$2",
},
],
},
],
errors: [incorrectImport],
},
{
code: 'import Button from "../../../packages/components/src/Button"',
output: 'import Button from "@monorepo/components/Button"',
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options,
errors: [incorrectImport],
},
{
code: 'import Button from "packages/components/src/Button"',
output: 'import Button from "@monorepo/components/Button"',
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options,
errors: [incorrectImport],
},
],
});

1394
yarn.lock Normal file

File diff suppressed because it is too large Load Diff