├── .github └── workflows │ └── npm-publish.yml ├── README.md ├── index.d.ts ├── index.js └── package.json /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: 12 18 | registry-url: https://registry.npmjs.org/ 19 | 20 | - name: Set env 21 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 22 | 23 | - run: | 24 | sudo apt update 25 | sudo apt install jq -y 26 | 27 | - run: | 28 | git config --local user.email "melvin.vermeer@gmail.com" 29 | git config --local user.name "Melvin Vermeer [ci]" 30 | git fetch 31 | git checkout main 32 | jq --arg version ${{ env.RELEASE_VERSION }} '.version = $version' package.json > package.json.tmp 33 | mv package.json.tmp package.json 34 | git add package.json 35 | git commit --no-verify -m "update package version ${{ env.RELEASE_VERSION }} [skip ci]" 36 | 37 | - run: npm publish 38 | env: 39 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 40 | 41 | - run: git push origin main --no-verify 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-no-relative-import-paths 2 | 3 | Moving a file to different folder, could result in changing all imports statement in that file. This will not happen is the import paths are absolute. The eslint rule helps enforcing having absolute import paths. 4 | Support eslint --fix to automatically change imports to absolute paths. 5 | 6 | # Installation 7 | 8 | Install [ESLint](https://www.github.com/eslint/eslint) either locally or globally. (Note that locally, per project, is strongly preferred) 9 | 10 | ```sh 11 | $ npm install eslint --save-dev 12 | ``` 13 | 14 | If you installed `ESLint` globally, you have to install this plugin globally too. Otherwise, install it locally. 15 | 16 | ```sh 17 | $ npm install eslint-plugin-no-relative-import-paths --save-dev 18 | ``` 19 | 20 | # Configuration 21 | 22 | Add the plugin to the plugins section, and configure the rule options. 23 | 24 | ```json 25 | { 26 | "plugins": ["no-relative-import-paths"], 27 | "rules": { 28 | "no-relative-import-paths/no-relative-import-paths": [ 29 | "warn", 30 | { "allowSameFolder": true } 31 | ] 32 | } 33 | } 34 | ``` 35 | 36 | ## Using the new flat config 37 | 38 | Here is an example of how to use with the new flat config. 39 | 40 | ```ts 41 | import noRelativeImportPaths from 'eslint-plugin-no-relative-import-paths'; 42 | 43 | export default [ 44 | { 45 | plugins: { 46 | 'no-relative-import-paths': noRelativeImportPaths, 47 | }, 48 | rules: { 49 | 'no-relative-import-paths/no-relative-import-paths': 'error', 50 | }, 51 | }, 52 | ] 53 | ``` 54 | 55 | ## Rule options 56 | 57 | ```json 58 | ... 59 | "no-relative-import-paths/no-relative-import-paths": [ 60 | "warn", 61 | { "allowSameFolder": true, "rootDir": "src", "prefix": "" } 62 | ] 63 | ... 64 | ``` 65 | 66 | - `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. 67 | - `ignorePureComponents`: optional boolean set to `true` to allow relative import paths for imported files from the same folder (default to `false`). 68 | 69 | ### `allowSameFolder` 70 | 71 | When `true` the rule will ignore relative import paths for imported files from the same folder 72 | 73 | Examples of code for this rule: 74 | 75 | ```js 76 | // when true this will be ignored 77 | // when false this will generate a warning 78 | import Something from "./something"; 79 | 80 | // will always generate a warning 81 | import Something from "../modules/something"; 82 | ``` 83 | 84 | ### `rootDir` 85 | 86 | Useful when auto-fixing and the rootDir should not be included in the absolute path. 87 | 88 | Examples of code for this rule: 89 | 90 | ```js 91 | // when not configured: 92 | import Something from "../../components/something"; 93 | 94 | // will result in 95 | import Something from "src/components/something"; 96 | ``` 97 | 98 | ```js 99 | // when configured as { "rootDir": "src" } 100 | import Something from "../../components/something"; 101 | 102 | // will result in 103 | import Something from "components/something"; 104 | // ^- no 'src/' prefix is added 105 | ``` 106 | 107 | ### `prefix` 108 | 109 | Useful when auto-fixing and a prefix should be included in the absolute path. 110 | 111 | Examples of code for this rule: 112 | 113 | ```js 114 | // when not configured: 115 | import Something from "../../components/something"; 116 | 117 | // will result in 118 | import Something from "src/components/something"; 119 | ``` 120 | 121 | ```js 122 | // when configured as { "prefix": "@" } 123 | import Something from "../../components/something"; 124 | 125 | // will result in 126 | import Something from "@/components/something"; 127 | ``` 128 | 129 | ### `allowedDepth` 130 | 131 | Used to allow some relative imports of certain depths. 132 | 133 | Examples of code for this rule: 134 | 135 | ```js 136 | // when configured as { "allowedDepth": 1 } 137 | 138 | // will NOT generate a warning 139 | import Something from "../components/something"; 140 | 141 | // will generate a warning 142 | import Something from "../../components/something"; 143 | ``` 144 | 145 | ```js 146 | // when configured as { "allowedDepth": 2 } 147 | 148 | // will NOT generate a warning 149 | import Something from "../../components/something"; 150 | 151 | // will generate a warning 152 | import Something from "../../../components/something"; 153 | ``` 154 | 155 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'eslint'; 2 | 3 | declare const plugin: { 4 | rules: { 5 | [key: string]: Rule.RuleModule; 6 | }; 7 | }; 8 | 9 | export = plugin; 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | function isParentFolder(relativeFilePath, context, rootDir) { 4 | const absoluteRootPath = path.join(context.getCwd(), rootDir); 5 | const absoluteFilePath = path.join(path.dirname(context.getFilename()), relativeFilePath) 6 | 7 | return relativeFilePath.startsWith("../") && ( 8 | rootDir === '' || 9 | (absoluteFilePath.startsWith(absoluteRootPath) && 10 | context.getFilename().startsWith(absoluteRootPath)) 11 | ); 12 | } 13 | 14 | function isSameFolder(path) { 15 | return path.startsWith("./"); 16 | } 17 | 18 | function getRelativePathDepth(path) { 19 | let depth = 0; 20 | while (path.startsWith('../')) { 21 | depth += 1; 22 | path = path.substring(3) 23 | } 24 | return depth; 25 | } 26 | 27 | function getAbsolutePath(relativePath, context, rootDir, prefix) { 28 | return [ 29 | prefix, 30 | ...path 31 | .relative( 32 | path.join(context.getCwd(), rootDir), 33 | path.join(path.dirname(context.getFilename()), relativePath) 34 | ) 35 | .split(path.sep) 36 | ].filter(String).join("/"); 37 | } 38 | 39 | const message = "import statements should have an absolute path"; 40 | 41 | module.exports = { 42 | rules: { 43 | "no-relative-import-paths": { 44 | meta: { 45 | type: "layout", 46 | fixable: "code", 47 | schema: { 48 | type: "array", 49 | minItems: 0, 50 | maxItems: 1, 51 | items: [ 52 | { 53 | type: "object", 54 | properties: { 55 | allowSameFolder: { type: "boolean" }, 56 | rootDir: { type: "string" }, 57 | prefix: { type: "string" }, 58 | allowedDepth: { type: "number" }, 59 | }, 60 | additionalProperties: false, 61 | }, 62 | ], 63 | }, 64 | }, 65 | create: function (context) { 66 | const { allowedDepth, allowSameFolder, rootDir, prefix } = { 67 | allowedDepth: context.options[0]?.allowedDepth, 68 | allowSameFolder: context.options[0]?.allowSameFolder || false, 69 | rootDir: context.options[0]?.rootDir || '', 70 | prefix: context.options[0]?.prefix || '', 71 | }; 72 | 73 | return { 74 | ImportDeclaration: function (node) { 75 | const path = node.source.value; 76 | if (isParentFolder(path, context, rootDir)) { 77 | if (typeof allowedDepth === 'undefined' || getRelativePathDepth(path) > allowedDepth) { 78 | context.report({ 79 | node, 80 | message: message, 81 | fix: function (fixer) { 82 | return fixer.replaceTextRange( 83 | [node.source.range[0] + 1, node.source.range[1] - 1], 84 | getAbsolutePath(path, context, rootDir, prefix) 85 | ); 86 | }, 87 | }); 88 | } 89 | } 90 | 91 | if (isSameFolder(path) && !allowSameFolder) { 92 | context.report({ 93 | node, 94 | message: message, 95 | fix: function (fixer) { 96 | return fixer.replaceTextRange( 97 | [node.source.range[0] + 1, node.source.range[1] - 1], 98 | getAbsolutePath(path, context, rootDir, prefix) 99 | ); 100 | }, 101 | }); 102 | } 103 | }, 104 | }; 105 | }, 106 | }, 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-no-relative-import-paths", 3 | "version": "v1.6.1", 4 | "description": "", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/MelvinVermeer/eslint-plugin-no-relative-import-paths" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [], 15 | "author": "Melvin Vermeer ", 16 | "license": "ISC" 17 | } 18 | --------------------------------------------------------------------------------