Skip to content

Commit

Permalink
Merge pull request #3 from opencreek/no-relative-imports
Browse files Browse the repository at this point in the history
✨ Add option to allow relative imports
  • Loading branch information
reckter authored Nov 24, 2021
2 parents faf5d51 + 44eb84d commit d3ba8cc
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 33 deletions.
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# eslint-plugin-no-relative-base-url-imports
# @opencreek/eslint-plugin-ts

Disalows relative path across the baseUrl of your tsconfig

Expand All @@ -10,19 +10,23 @@ You'll first need to install [ESLint](https://eslint.org/):
npm i eslint --save-dev
```

Next, install `eslint-plugin-no-relative-base-url-imports`:
Next, install `@opencreek/eslint-plugin-ts`:

```sh
npm install eslint-plugin-no-relative-base-url-imports --save-dev
npm install @opencreek/eslint-plugin-ts --save-dev
```

```sh
yarn add --dev @opencreek/eslint-plugin-ts
```

## Usage

Add `no-relative-base-url-imports` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
Add `@opencreek/ts` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:

```json
{
"plugins": ["no-relative-base-url-imports"]
"plugins": ["@opencreek/ts"]
}
```

Expand All @@ -31,11 +35,28 @@ Then configure the rules you want to use under the rules section.
```json
{
"rules": {
"no-relative-base-url-imports/rule-name": 2
"@opencreek/ts/no-relative-imports": [
"error",
{
"baseUrl": "./src"
}
]
}
}
```

## Supported Rules

- Fill in provided rules here
### `@opencreek/ts/no-relative-imports` Disable relative imports.

Config options

```ts
{
"baseUrl": "./src", // The base url that you have set in the tsconfig
"allowLocalImports": "local" // possible values: "local" | "in-base-path".
// "local": Allows local imports (eg.: "./test")
// "in-base-path": Allows everything that does not go back to the base url level (eg: "../../test" in "src/a/b/c/test.ts")
}

```
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const rules = {
export const configs = {
recommended: {
rules: {
"@opencreek/opencreek/no-relative-imports": "error",
"@opencreek/ts/no-relative-imports": "error",
},
},
}
79 changes: 66 additions & 13 deletions lib/rules/no-relative-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ const creator = RuleCreator((rule) => rule)

export type Options = {
baseUrl?: string
allowLocalImports?: "inside-base-path" | "local"
}[]
export type MessageIds = "standard-message"

export type MessageIds = "no-relative-import"
export default creator<Options, MessageIds>({
name: "no-relative-imports",
meta: {
Expand All @@ -17,8 +19,7 @@ export default creator<Options, MessageIds>({
},
fixable: "code",
messages: {
"standard-message":
"No relative imports, that go to back to the baseURl",
"no-relative-import": "No relative imports",
},
schema: [
{
Expand All @@ -35,6 +36,23 @@ export default creator<Options, MessageIds>({
create(context) {
return {
ImportDeclaration(node) {
const options = context.options?.[0] ?? {}

// no relative import
if (!node.source.value.startsWith(".")) {
return
}

if (
options.allowLocalImports === "local" ||
options.allowLocalImports === "inside-base-path"
) {
// We start with a "./" followed by a different char, so it's a local import
if (/^\.\/[^.]/.test(node.source.value)) {
return
}
}

const fileName = context.getPhysicalFilename?.()
if (fileName == undefined) {
console.error("Got no physical file name ?!")
Expand All @@ -43,17 +61,30 @@ export default creator<Options, MessageIds>({

const basePath = path.resolve(
process.cwd(),
context.options?.[0]?.baseUrl ?? "."
options.baseUrl ?? "."
)

const fileNameInsideBasePath = fileName.replace(basePath, "")
const levels = fileNameInsideBasePath.split("/").length - 2
const levelImport = "../".repeat(levels)

const filePath = path.dirname(fileName)

const absoluteImportPath = path.resolve(
filePath,
node.source.value
)
const isInBasePath = filePath.startsWith(basePath)
const absoluteImportPathInsideBasePath =
absoluteImportPath.replace(basePath + "/", "")

if (!fileName.startsWith(basePath)) {
// we import something outside of the basePath
if (!absoluteImportPath.startsWith(basePath)) {
return
}
const relativeFileName = fileName.replace(basePath, "")
const levels = relativeFileName.split("/").length - 2
const levelImport = "../".repeat(levels)

if (node.source.value.startsWith(levelImport)) {
// always remove imports that go past the base URL
if (node.source.value.startsWith(levelImport) && isInBasePath) {
const withoutLevels = node.source.value.replace(
levelImport,
""
Expand All @@ -64,18 +95,40 @@ export default creator<Options, MessageIds>({

context.report({
node: node.source,
messageId: "standard-message",
messageId: "no-relative-import",
data: {},
fix: (fixer) => {
return fixer.replaceText(
node.source,
'"' +
node.source.value.replace(levelImport, "") +
'"'
`"${absoluteImportPathInsideBasePath}"`
)
},
})
return
}

// We report the error, if we disallow relative imports
// Or we cross INTO the baseUrl boundary
if (
options.allowLocalImports == undefined &&
!absoluteImportPath.startsWith(basePath)
) {
return
}

context.report({
node: node.source,
messageId: "no-relative-import",
data: {},
fix: (fixer) => {
return fixer.replaceText(
node.source,
`"${absoluteImportPathInsideBasePath}"`
)
},
})

return
},
}
},
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@opencreek/eslint-plugin-opencreek",
"name": "@opencreek/eslint-plugin-ts",
"version": "0.2.0",
"description": "Disalows relative path across the baseUrl of your tsconfig",
"description": "Disallows relative path across the baseUrl of your tsconfig",
"type": "module",
"keywords": [
"eslint",
Expand All @@ -13,7 +13,7 @@
"build/**"
],
"author": "Opencreek Technology<oss@opencreek.tech>",
"repository": "https://github.com/opencreek/provider-stack",
"repository": "https://github.com/opencreek/eslint-plugin-ts",
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
Expand Down
124 changes: 118 additions & 6 deletions tests/lib/rules/no-relative-imports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,146 @@ spy.mockReturnValue("/")
ruleTester.run("my-rule", noRelativeImports, {
valid: [
{
options: [{ baseUrl: "./src" }],
options: [{ baseUrl: "./src", allowLocalImports: "local" }],
code: 'import {foo} from "bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [{ baseUrl: "./src" }],
options: [{ baseUrl: "./src", allowLocalImports: "local" }],
code: 'import {foo} from "./bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [{ baseUrl: "./src", allowLocalImports: "local" }],
code: 'import {foo} from "../../../bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "../bla/test"',
options: [
{ baseUrl: "./src", allowLocalImports: "inside-base-path" },
],
code: 'import {foo} from "/bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [
{ baseUrl: "./src", allowLocalImports: "inside-base-path" },
],
code: 'import {foo} from "./bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [
{ baseUrl: "./src", allowLocalImports: "inside-base-path" },
],
code: 'import {foo} from "../../../bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "../../../bla/test"',
filename: "/test/nested/deep/test.js",
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "nested/bla/test"',
filename: "/src/nested/deep/test.js",
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "../../../bla/test"',
filename: "/src/nested/deep/test.js",
},
],
invalid: [
{
options: [{ baseUrl: "./src", allowLocalImports: "local" }],
code: 'import {foo} from "../test2"',
filename: "/src/nested/deep/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "nested/test2"',
},
{
options: [{ baseUrl: "./src", allowLocalImports: "local" }],
code: 'import {foo} from "../../bla/test2"',
filename: "/src/nested/deep/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "bla/test2"',
},
{
options: [{ baseUrl: "./src", allowLocalImports: "local" }],
code: 'import {foo} from "../../../src/nested/test2"',
filename: "/test/nested/deep/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "nested/test2"',
},
{
options: [
{ baseUrl: "./src", allowLocalImports: "inside-base-path" },
],
code: 'import {foo} from "../../bla/test2"',
filename: "/src/nested/deep/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "bla/test2"',
},
{
options: [
{ baseUrl: "./src", allowLocalImports: "inside-base-path" },
],
code: 'import {foo} from "../../../src/bla/test2"',
filename: "/test/nested/deep/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "bla/test2"',
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "../../bla/test"',
code: 'import {foo} from "./bla/test"',
filename: "/src/nested/deep/test.js",
errors: [
{
messageId: "standard-message",
messageId: "no-relative-import",
},
],
output: 'import {foo} from "nested/deep/bla/test"',
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "../bla/test"',
filename: "/src/nested/deep/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "nested/bla/test"',
},
{
options: [{ baseUrl: "./src" }],
code: 'import {foo} from "../../src/bla/test"',
filename: "/somewhere/else/test.js",
errors: [
{
messageId: "no-relative-import",
},
],
output: 'import {foo} from "bla/test"',
Expand Down

0 comments on commit d3ba8cc

Please sign in to comment.