@macklenc/eslint-plugin-absolute-imports 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Christopher Macklen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # @macklenc/eslint-plugin-absolute-imports
2
+
3
+ An ESLint plugin that automatically replaces relative imports with absolute imports based on your project's structure.
4
+
5
+ ## Installation
6
+
7
+ You'll first need to install [ESLint](https://eslint.org/):
8
+
9
+ ```sh
10
+ npm i eslint --save-dev
11
+ ```
12
+
13
+ Next, install `@macklenc/eslint-plugin-absolute-imports`:
14
+
15
+ ```sh
16
+ npm install @macklenc/eslint-plugin-absolute-imports --save-dev
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Add `absolute-imports` to the plugins section of your [ESLint configuration file](https://eslint.org/docs/latest/use/configure/configuration-files).
22
+
23
+ ### For ESLint v9+ (flat config)
24
+
25
+ ```js
26
+ import absoluteImports from "@macklenc/eslint-plugin-absolute-imports";
27
+
28
+ export default [
29
+ {
30
+ plugins: {
31
+ "absolute-imports": absoluteImports,
32
+ },
33
+ rules: {
34
+ "absolute-imports/absolute-imports": [
35
+ "error",
36
+ {
37
+ // Optional: specify the base path for your project
38
+ // basePath: "./src",
39
+ // Optional: specify the path to your tsconfig.json for path mapping
40
+ // tsconfigPath: "./tsconfig.json"
41
+ },
42
+ ],
43
+ },
44
+ },
45
+ ];
46
+ ```
47
+
48
+ ### For ESLint v8 (legacy config)
49
+
50
+ ```js
51
+ module.exports = {
52
+ plugins: ["absolute-imports"],
53
+ rules: {
54
+ "absolute-imports/absolute-imports": [
55
+ "error",
56
+ {
57
+ // Optional: specify the base path for your project
58
+ // basePath: "./src",
59
+ // Optional: specify the path to your tsconfig.json for path mapping
60
+ // tsconfigPath: "./tsconfig.json"
61
+ },
62
+ ],
63
+ },
64
+ };
65
+ ```
66
+
67
+ ## Rules
68
+
69
+ ### absolute-imports
70
+
71
+ This rule identifies relative imports in your code and suggests replacing them with absolute imports.
72
+
73
+ #### Options
74
+
75
+ The rule accepts an options object with the following properties:
76
+
77
+ - `basePath` (optional): The base path of your project. Defaults to the current working directory.
78
+ - `tsconfigPath` (optional): Path to your tsconfig.json file for path mapping. Defaults to `<projectRoot>/tsconfig.json`.
79
+
80
+ #### Examples
81
+
82
+ Given a project structure:
83
+
84
+ ```
85
+ /project
86
+ /src
87
+ /components
88
+ Component.ts
89
+ /pages
90
+ Page.ts
91
+ ```
92
+
93
+ With the rule configured as:
94
+
95
+ ```js
96
+ "absolute-imports/absolute-imports": ["error", { basePath: "./src" }]
97
+ ```
98
+
99
+ The following code in `/src/pages/Page.ts`:
100
+
101
+ ```js
102
+ import { Component } from "../components/Component";
103
+ ```
104
+
105
+ Will be converted to:
106
+
107
+ ```js
108
+ import { Component } from "/components/Component";
109
+ ```
110
+
111
+ #### When the rule is triggered
112
+
113
+ The rule will be triggered when:
114
+
115
+ - You use relative imports (starting with `./` or `../`)
116
+ - The import can be converted to an absolute path based on your project structure
117
+
118
+ The rule will not be triggered for:
119
+
120
+ - Already absolute imports
121
+ - Imports using aliases (e.g., `@components/Component`)
122
+ - External package imports (e.g., `react`, `lodash`)
123
+
124
+ ## Development
125
+
126
+ ### Commit Guidelines
127
+
128
+ This project follows [Conventional Commits](https://www.conventionalcommits.org/) for commit messages. This helps with automatic versioning and changelog generation.
129
+
130
+ To create a properly formatted commit, run:
131
+
132
+ ```sh
133
+ npm run commit
134
+ # or
135
+ pnpm run commit
136
+ ```
137
+
138
+ This will guide you through creating a conventional commit message.
139
+
140
+ ### Versioning and Releases
141
+
142
+ This project uses [standard-version](https://github.com/conventional-changelog/standard-version) for versioning and changelog generation based on commit messages.
143
+
144
+ To create a new release:
145
+
146
+ ```sh
147
+ # Automatically determine version bump based on commits
148
+ npm run release
149
+ # or
150
+ pnpm run release
151
+
152
+ # Specific version bumps
153
+ npm run release:patch # 0.0.1 -> 0.0.2
154
+ npm run release:minor # 0.0.1 -> 0.1.0
155
+ npm run release:major # 0.0.1 -> 1.0.0
156
+
157
+ # Preview what the release would do
158
+ npm run release:dry-run
159
+ ```
160
+
161
+ The release process will:
162
+
163
+ 1. Bump the version in package.json
164
+ 2. Update CHANGELOG.md with all notable changes
165
+ 3. Create a git tag for the new version
166
+ 4. Create a commit with these changes
167
+
168
+ ## License
169
+
170
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,205 @@
1
+ 'use strict';
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all) __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if ((from && typeof from === 'object') || typeof from === 'function') {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, {
16
+ get: () => from[key],
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
18
+ });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (
23
+ (target = mod != null ? __create(__getProtoOf(mod)) : {}),
24
+ __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule
30
+ ? __defProp(target, 'default', { value: mod, enumerable: true })
31
+ : target,
32
+ mod
33
+ )
34
+ );
35
+ var __toCommonJS = mod => __copyProps(__defProp({}, '__esModule', { value: true }), mod);
36
+
37
+ // index.ts
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ default: () => index_default,
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+ var import_fs = __toESM(require('fs'), 1);
44
+ var import_path = __toESM(require('path'), 1);
45
+
46
+ // lib/rules/absolute-imports.ts
47
+ var import_types = require('@typescript-eslint/types');
48
+ var import_utils = require('@typescript-eslint/utils');
49
+ var path = __toESM(require('path'), 1);
50
+ var fs = __toESM(require('fs'), 1);
51
+ var createRule = import_utils.ESLintUtils.RuleCreator(name => `absolute-imports/${name}`);
52
+ var absoluteImports = createRule({
53
+ name: 'absolute-imports',
54
+ meta: {
55
+ type: 'suggestion',
56
+ docs: {
57
+ description:
58
+ 'Convert relative imports to absolute imports based on a configured base path and tsconfig paths.',
59
+ },
60
+ messages: {
61
+ relativeImport:
62
+ "Relative import '{{ importPath }}' should be converted to absolute import '{{ absolutePath }}'.",
63
+ },
64
+ schema: [
65
+ {
66
+ type: 'object',
67
+ properties: {
68
+ basePath: {
69
+ type: 'string',
70
+ },
71
+ tsconfigPath: {
72
+ type: 'string',
73
+ },
74
+ },
75
+ additionalProperties: false,
76
+ },
77
+ ],
78
+ fixable: 'code',
79
+ },
80
+ defaultOptions: [{}],
81
+ create(context) {
82
+ const options = context.options[0] || {};
83
+ const basePath = options.basePath || process.cwd();
84
+ const tsconfigPath = options.tsconfigPath || path.join(process.cwd(), 'tsconfig.json');
85
+ const projectRoot = path.dirname(tsconfigPath);
86
+ let tsconfigPaths = null;
87
+ try {
88
+ const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
89
+ const tsconfigRaw = tsconfigContent.replace(
90
+ /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g,
91
+ (m, g) => (g ? '' : m)
92
+ );
93
+ const tsconfig = JSON.parse(tsconfigRaw);
94
+ if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
95
+ tsconfigPaths = tsconfig.compilerOptions.paths;
96
+ }
97
+ } catch (err) {
98
+ tsconfigPaths = null;
99
+ }
100
+ function tryAliasMapping(relativePath) {
101
+ if (!tsconfigPaths) return null;
102
+ let matchedAlias = null;
103
+ let longestMatch = -1;
104
+ for (const aliasPattern in tsconfigPaths) {
105
+ for (const targetPattern of tsconfigPaths[aliasPattern]) {
106
+ let normalizedTarget = targetPattern.replace(/^\.\//, '');
107
+ if (normalizedTarget.endsWith('/*')) {
108
+ normalizedTarget = normalizedTarget.slice(0, -2);
109
+ }
110
+ if (relativePath.startsWith(normalizedTarget)) {
111
+ if (normalizedTarget.length > longestMatch) {
112
+ longestMatch = normalizedTarget.length;
113
+ let aliasBase = aliasPattern;
114
+ if (aliasBase.endsWith('/*')) {
115
+ aliasBase = aliasBase.slice(0, -2);
116
+ }
117
+ const remainder = relativePath.slice(normalizedTarget.length);
118
+ const cleanRemainder = remainder.replace(/^\/+/, '');
119
+ matchedAlias = aliasBase + (cleanRemainder ? '/' + cleanRemainder : '');
120
+ }
121
+ }
122
+ }
123
+ }
124
+ return matchedAlias;
125
+ }
126
+ function toAbsolute(importPath, filePath) {
127
+ const fileDir = path.dirname(filePath);
128
+ const resolvedPath = path.resolve(fileDir, importPath);
129
+ let relativeToRoot = path.relative(projectRoot, resolvedPath);
130
+ relativeToRoot = relativeToRoot.replace(/\\/g, '/');
131
+ const aliasPath = tryAliasMapping(relativeToRoot);
132
+ if (aliasPath) {
133
+ return aliasPath;
134
+ }
135
+ let relativeToBase = path.relative(basePath, resolvedPath);
136
+ relativeToBase = relativeToBase.replace(/\\/g, '/');
137
+ if (!relativeToBase.startsWith('/')) {
138
+ relativeToBase = '/' + relativeToBase;
139
+ }
140
+ return relativeToBase;
141
+ }
142
+ return {
143
+ ImportDeclaration(node) {
144
+ if (node.source && typeof node.source.value === 'string') {
145
+ const importPath = node.source.value;
146
+ if (importPath.startsWith('.')) {
147
+ const fileName = context.getFilename();
148
+ const absolutePath = toAbsolute(importPath, fileName);
149
+ context.report({
150
+ node: node.source,
151
+ messageId: 'relativeImport',
152
+ data: { importPath, absolutePath },
153
+ fix(fixer) {
154
+ return fixer.replaceText(node.source, `'${absolutePath}'`);
155
+ },
156
+ });
157
+ }
158
+ }
159
+ },
160
+ // Optionally handle dynamic imports.
161
+ CallExpression(node) {
162
+ if (
163
+ // Check if this is a dynamic import
164
+ node.callee.name === 'import' &&
165
+ node.arguments.length === 1 &&
166
+ node.arguments[0].type === import_types.TSESTree.AST_NODE_TYPES.Literal &&
167
+ typeof node.arguments[0].value === 'string'
168
+ ) {
169
+ const importPath = node.arguments[0].value;
170
+ if (importPath.startsWith('.')) {
171
+ const fileName = context.getFilename();
172
+ const absolutePath = toAbsolute(importPath, fileName);
173
+ context.report({
174
+ node: node.arguments[0],
175
+ messageId: 'relativeImport',
176
+ data: { importPath, absolutePath },
177
+ fix(fixer) {
178
+ return fixer.replaceText(node.arguments[0], `'${absolutePath}'`);
179
+ },
180
+ });
181
+ }
182
+ }
183
+ },
184
+ };
185
+ },
186
+ });
187
+
188
+ // lib/rules/index.ts
189
+ var rules = {
190
+ 'absolute-imports': absoluteImports,
191
+ };
192
+
193
+ // index.ts
194
+ var pkg = JSON.parse(
195
+ import_fs.default.readFileSync(import_path.default.resolve(__dirname, './package.json'), 'utf8')
196
+ );
197
+ var plugin = {
198
+ meta: {
199
+ name: pkg.name,
200
+ version: pkg.version,
201
+ },
202
+ rules,
203
+ };
204
+ var index_default = plugin;
205
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts","../lib/rules/absolute-imports.ts","../lib/rules/index.ts"],"sourcesContent":["'use strict';\n\nimport fs from 'fs';\nimport path from 'path';\nimport { ESLint } from 'eslint';\nimport { RuleModule } from '@typescript-eslint/utils/ts-eslint';\nimport { rules } from '@/lib/rules/index.js';\n\ntype RuleKey = keyof typeof rules;\n\ninterface Plugin extends Omit<ESLint.Plugin, 'rules'> {\n rules: Record<RuleKey, RuleModule<any, any, any>>;\n}\n\nconst pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, './package.json'), 'utf8'));\n\nconst plugin: Plugin = {\n meta: {\n name: pkg.name,\n version: pkg.version,\n },\n rules,\n};\n\nexport default plugin;\n","import { TSESTree } from '@typescript-eslint/types';\nimport { ESLintUtils } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport * as fs from 'fs';\n\n// Define the options type\ntype Options = [\n {\n basePath?: string;\n tsconfigPath?: string;\n },\n];\n\nexport const createRule = ESLintUtils.RuleCreator(name => `absolute-imports/${name}`);\n\nexport const absoluteImports = createRule<Options, 'relativeImport'>({\n name: 'absolute-imports',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Convert relative imports to absolute imports based on a configured base path and tsconfig paths.',\n },\n messages: {\n relativeImport:\n \"Relative import '{{ importPath }}' should be converted to absolute import '{{ absolutePath }}'.\",\n },\n schema: [\n {\n type: 'object',\n properties: {\n basePath: {\n type: 'string',\n },\n tsconfigPath: {\n type: 'string',\n },\n },\n additionalProperties: false,\n },\n ],\n fixable: 'code',\n },\n defaultOptions: [{}],\n create(context) {\n const options = context.options[0] || {};\n // basePath is used for fallback when alias mapping doesn't apply.\n const basePath = options.basePath || process.cwd();\n // tsconfigPath defaults to <projectRoot>/tsconfig.json.\n const tsconfigPath = options.tsconfigPath || path.join(process.cwd(), 'tsconfig.json');\n\n // Use the directory of the tsconfig as the project root.\n const projectRoot = path.dirname(tsconfigPath);\n\n let tsconfigPaths: Record<string, string[]> | null = null;\n try {\n const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfigRaw = tsconfigContent.replace(\n /\\\\\"|\"(?:\\\\\"|[^\"])*\"|(\\/\\/.*|\\/\\*[\\s\\S]*?\\*\\/)/g,\n (m, g) => (g ? '' : m)\n );\n const tsconfig = JSON.parse(tsconfigRaw);\n if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {\n tsconfigPaths = tsconfig.compilerOptions.paths;\n }\n } catch (err) {\n // If tsconfig.json cannot be read, ignore alias mapping.\n tsconfigPaths = null;\n }\n\n /**\n * Given a file path relative to the project root (without a leading slash),\n * try to find a matching alias from tsconfig paths.\n */\n function tryAliasMapping(relativePath: string): string | null {\n if (!tsconfigPaths) return null;\n let matchedAlias: string | null = null;\n let longestMatch = -1;\n for (const aliasPattern in tsconfigPaths) {\n for (const targetPattern of tsconfigPaths[aliasPattern]) {\n // Normalize target: remove leading \"./\" if present.\n let normalizedTarget = targetPattern.replace(/^\\.\\//, '');\n // Remove trailing \"/*\" if present.\n if (normalizedTarget.endsWith('/*')) {\n normalizedTarget = normalizedTarget.slice(0, -2);\n }\n // If the file path starts with the target, it's a match.\n if (relativePath.startsWith(normalizedTarget)) {\n if (normalizedTarget.length > longestMatch) {\n longestMatch = normalizedTarget.length;\n let aliasBase = aliasPattern;\n if (aliasBase.endsWith('/*')) {\n aliasBase = aliasBase.slice(0, -2);\n }\n const remainder = relativePath.slice(normalizedTarget.length);\n const cleanRemainder = remainder.replace(/^\\/+/, '');\n matchedAlias = aliasBase + (cleanRemainder ? '/' + cleanRemainder : '');\n }\n }\n }\n }\n return matchedAlias;\n }\n\n /**\n * Convert the relative import path to an absolute one.\n * First, compute the file's path relative to the project root (from tsconfig).\n * If an alias is found via tsconfig paths, return that alias.\n * Otherwise, fall back to computing a path relative to the configured basePath.\n */\n function toAbsolute(importPath: string, filePath: string): string {\n const fileDir = path.dirname(filePath);\n const resolvedPath = path.resolve(fileDir, importPath);\n\n // Compute relative path from the project root.\n let relativeToRoot = path.relative(projectRoot, resolvedPath);\n relativeToRoot = relativeToRoot.replace(/\\\\/g, '/');\n const aliasPath = tryAliasMapping(relativeToRoot);\n if (aliasPath) {\n return aliasPath;\n }\n\n // Fallback: compute relative to the configured basePath.\n let relativeToBase = path.relative(basePath, resolvedPath);\n relativeToBase = relativeToBase.replace(/\\\\/g, '/');\n if (!relativeToBase.startsWith('/')) {\n relativeToBase = '/' + relativeToBase;\n }\n return relativeToBase;\n }\n\n return {\n ImportDeclaration(node) {\n if (node.source && typeof node.source.value === 'string') {\n const importPath = node.source.value;\n if (importPath.startsWith('.')) {\n const fileName = context.getFilename();\n const absolutePath = toAbsolute(importPath, fileName);\n context.report({\n node: node.source,\n messageId: 'relativeImport',\n data: { importPath, absolutePath },\n fix(fixer) {\n return fixer.replaceText(node.source, `'${absolutePath}'`);\n },\n });\n }\n }\n },\n // Optionally handle dynamic imports.\n CallExpression(node) {\n // For dynamic imports, node.callee is an Identifier with name 'import'\n if (\n // Check if this is a dynamic import\n (node.callee as any).name === 'import' &&\n node.arguments.length === 1 &&\n node.arguments[0].type === TSESTree.AST_NODE_TYPES.Literal &&\n typeof node.arguments[0].value === 'string'\n ) {\n const importPath = node.arguments[0].value;\n if (importPath.startsWith('.')) {\n const fileName = context.getFilename();\n const absolutePath = toAbsolute(importPath, fileName);\n context.report({\n node: node.arguments[0],\n messageId: 'relativeImport',\n data: { importPath, absolutePath },\n fix(fixer) {\n return fixer.replaceText(node.arguments[0], `'${absolutePath}'`);\n },\n });\n }\n }\n },\n };\n },\n});\n","import { absoluteImports } from './absolute-imports.js';\n\nexport const rules = {\n 'absolute-imports': absoluteImports,\n};\n"],"mappings":"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","names":["fs","path"]}
@@ -0,0 +1,11 @@
1
+ import { ESLint } from 'eslint';
2
+ import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
3
+ import { rules } from './lib/rules/index.cjs';
4
+
5
+ type RuleKey = keyof typeof rules;
6
+ interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
7
+ rules: Record<RuleKey, RuleModule<any, any, any>>;
8
+ }
9
+ declare const plugin: Plugin;
10
+
11
+ export { plugin as default };
@@ -0,0 +1,11 @@
1
+ import { ESLint } from 'eslint';
2
+ import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
3
+ import { rules } from './lib/rules/index.js';
4
+
5
+ type RuleKey = keyof typeof rules;
6
+ interface Plugin extends Omit<ESLint.Plugin, 'rules'> {
7
+ rules: Record<RuleKey, RuleModule<any, any, any>>;
8
+ }
9
+ declare const plugin: Plugin;
10
+
11
+ export { plugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,163 @@
1
+ // index.ts
2
+ import fs2 from 'fs';
3
+ import path2 from 'path';
4
+
5
+ // lib/rules/absolute-imports.ts
6
+ import { TSESTree } from '@typescript-eslint/types';
7
+ import { ESLintUtils } from '@typescript-eslint/utils';
8
+ import * as path from 'path';
9
+ import * as fs from 'fs';
10
+ var createRule = ESLintUtils.RuleCreator(name => `absolute-imports/${name}`);
11
+ var absoluteImports = createRule({
12
+ name: 'absolute-imports',
13
+ meta: {
14
+ type: 'suggestion',
15
+ docs: {
16
+ description:
17
+ 'Convert relative imports to absolute imports based on a configured base path and tsconfig paths.',
18
+ },
19
+ messages: {
20
+ relativeImport:
21
+ "Relative import '{{ importPath }}' should be converted to absolute import '{{ absolutePath }}'.",
22
+ },
23
+ schema: [
24
+ {
25
+ type: 'object',
26
+ properties: {
27
+ basePath: {
28
+ type: 'string',
29
+ },
30
+ tsconfigPath: {
31
+ type: 'string',
32
+ },
33
+ },
34
+ additionalProperties: false,
35
+ },
36
+ ],
37
+ fixable: 'code',
38
+ },
39
+ defaultOptions: [{}],
40
+ create(context) {
41
+ const options = context.options[0] || {};
42
+ const basePath = options.basePath || process.cwd();
43
+ const tsconfigPath = options.tsconfigPath || path.join(process.cwd(), 'tsconfig.json');
44
+ const projectRoot = path.dirname(tsconfigPath);
45
+ let tsconfigPaths = null;
46
+ try {
47
+ const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
48
+ const tsconfigRaw = tsconfigContent.replace(
49
+ /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g,
50
+ (m, g) => (g ? '' : m)
51
+ );
52
+ const tsconfig = JSON.parse(tsconfigRaw);
53
+ if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
54
+ tsconfigPaths = tsconfig.compilerOptions.paths;
55
+ }
56
+ } catch (err) {
57
+ tsconfigPaths = null;
58
+ }
59
+ function tryAliasMapping(relativePath) {
60
+ if (!tsconfigPaths) return null;
61
+ let matchedAlias = null;
62
+ let longestMatch = -1;
63
+ for (const aliasPattern in tsconfigPaths) {
64
+ for (const targetPattern of tsconfigPaths[aliasPattern]) {
65
+ let normalizedTarget = targetPattern.replace(/^\.\//, '');
66
+ if (normalizedTarget.endsWith('/*')) {
67
+ normalizedTarget = normalizedTarget.slice(0, -2);
68
+ }
69
+ if (relativePath.startsWith(normalizedTarget)) {
70
+ if (normalizedTarget.length > longestMatch) {
71
+ longestMatch = normalizedTarget.length;
72
+ let aliasBase = aliasPattern;
73
+ if (aliasBase.endsWith('/*')) {
74
+ aliasBase = aliasBase.slice(0, -2);
75
+ }
76
+ const remainder = relativePath.slice(normalizedTarget.length);
77
+ const cleanRemainder = remainder.replace(/^\/+/, '');
78
+ matchedAlias = aliasBase + (cleanRemainder ? '/' + cleanRemainder : '');
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return matchedAlias;
84
+ }
85
+ function toAbsolute(importPath, filePath) {
86
+ const fileDir = path.dirname(filePath);
87
+ const resolvedPath = path.resolve(fileDir, importPath);
88
+ let relativeToRoot = path.relative(projectRoot, resolvedPath);
89
+ relativeToRoot = relativeToRoot.replace(/\\/g, '/');
90
+ const aliasPath = tryAliasMapping(relativeToRoot);
91
+ if (aliasPath) {
92
+ return aliasPath;
93
+ }
94
+ let relativeToBase = path.relative(basePath, resolvedPath);
95
+ relativeToBase = relativeToBase.replace(/\\/g, '/');
96
+ if (!relativeToBase.startsWith('/')) {
97
+ relativeToBase = '/' + relativeToBase;
98
+ }
99
+ return relativeToBase;
100
+ }
101
+ return {
102
+ ImportDeclaration(node) {
103
+ if (node.source && typeof node.source.value === 'string') {
104
+ const importPath = node.source.value;
105
+ if (importPath.startsWith('.')) {
106
+ const fileName = context.getFilename();
107
+ const absolutePath = toAbsolute(importPath, fileName);
108
+ context.report({
109
+ node: node.source,
110
+ messageId: 'relativeImport',
111
+ data: { importPath, absolutePath },
112
+ fix(fixer) {
113
+ return fixer.replaceText(node.source, `'${absolutePath}'`);
114
+ },
115
+ });
116
+ }
117
+ }
118
+ },
119
+ // Optionally handle dynamic imports.
120
+ CallExpression(node) {
121
+ if (
122
+ // Check if this is a dynamic import
123
+ node.callee.name === 'import' &&
124
+ node.arguments.length === 1 &&
125
+ node.arguments[0].type === TSESTree.AST_NODE_TYPES.Literal &&
126
+ typeof node.arguments[0].value === 'string'
127
+ ) {
128
+ const importPath = node.arguments[0].value;
129
+ if (importPath.startsWith('.')) {
130
+ const fileName = context.getFilename();
131
+ const absolutePath = toAbsolute(importPath, fileName);
132
+ context.report({
133
+ node: node.arguments[0],
134
+ messageId: 'relativeImport',
135
+ data: { importPath, absolutePath },
136
+ fix(fixer) {
137
+ return fixer.replaceText(node.arguments[0], `'${absolutePath}'`);
138
+ },
139
+ });
140
+ }
141
+ }
142
+ },
143
+ };
144
+ },
145
+ });
146
+
147
+ // lib/rules/index.ts
148
+ var rules = {
149
+ 'absolute-imports': absoluteImports,
150
+ };
151
+
152
+ // index.ts
153
+ var pkg = JSON.parse(fs2.readFileSync(path2.resolve(__dirname, './package.json'), 'utf8'));
154
+ var plugin = {
155
+ meta: {
156
+ name: pkg.name,
157
+ version: pkg.version,
158
+ },
159
+ rules,
160
+ };
161
+ var index_default = plugin;
162
+ export { index_default as default };
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts","../lib/rules/absolute-imports.ts","../lib/rules/index.ts"],"sourcesContent":["'use strict';\n\nimport fs from 'fs';\nimport path from 'path';\nimport { ESLint } from 'eslint';\nimport { RuleModule } from '@typescript-eslint/utils/ts-eslint';\nimport { rules } from '@/lib/rules/index.js';\n\ntype RuleKey = keyof typeof rules;\n\ninterface Plugin extends Omit<ESLint.Plugin, 'rules'> {\n rules: Record<RuleKey, RuleModule<any, any, any>>;\n}\n\nconst pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, './package.json'), 'utf8'));\n\nconst plugin: Plugin = {\n meta: {\n name: pkg.name,\n version: pkg.version,\n },\n rules,\n};\n\nexport default plugin;\n","import { TSESTree } from '@typescript-eslint/types';\nimport { ESLintUtils } from '@typescript-eslint/utils';\nimport * as path from 'path';\nimport * as fs from 'fs';\n\n// Define the options type\ntype Options = [\n {\n basePath?: string;\n tsconfigPath?: string;\n },\n];\n\nexport const createRule = ESLintUtils.RuleCreator(name => `absolute-imports/${name}`);\n\nexport const absoluteImports = createRule<Options, 'relativeImport'>({\n name: 'absolute-imports',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Convert relative imports to absolute imports based on a configured base path and tsconfig paths.',\n },\n messages: {\n relativeImport:\n \"Relative import '{{ importPath }}' should be converted to absolute import '{{ absolutePath }}'.\",\n },\n schema: [\n {\n type: 'object',\n properties: {\n basePath: {\n type: 'string',\n },\n tsconfigPath: {\n type: 'string',\n },\n },\n additionalProperties: false,\n },\n ],\n fixable: 'code',\n },\n defaultOptions: [{}],\n create(context) {\n const options = context.options[0] || {};\n // basePath is used for fallback when alias mapping doesn't apply.\n const basePath = options.basePath || process.cwd();\n // tsconfigPath defaults to <projectRoot>/tsconfig.json.\n const tsconfigPath = options.tsconfigPath || path.join(process.cwd(), 'tsconfig.json');\n\n // Use the directory of the tsconfig as the project root.\n const projectRoot = path.dirname(tsconfigPath);\n\n let tsconfigPaths: Record<string, string[]> | null = null;\n try {\n const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfigRaw = tsconfigContent.replace(\n /\\\\\"|\"(?:\\\\\"|[^\"])*\"|(\\/\\/.*|\\/\\*[\\s\\S]*?\\*\\/)/g,\n (m, g) => (g ? '' : m)\n );\n const tsconfig = JSON.parse(tsconfigRaw);\n if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {\n tsconfigPaths = tsconfig.compilerOptions.paths;\n }\n } catch (err) {\n // If tsconfig.json cannot be read, ignore alias mapping.\n tsconfigPaths = null;\n }\n\n /**\n * Given a file path relative to the project root (without a leading slash),\n * try to find a matching alias from tsconfig paths.\n */\n function tryAliasMapping(relativePath: string): string | null {\n if (!tsconfigPaths) return null;\n let matchedAlias: string | null = null;\n let longestMatch = -1;\n for (const aliasPattern in tsconfigPaths) {\n for (const targetPattern of tsconfigPaths[aliasPattern]) {\n // Normalize target: remove leading \"./\" if present.\n let normalizedTarget = targetPattern.replace(/^\\.\\//, '');\n // Remove trailing \"/*\" if present.\n if (normalizedTarget.endsWith('/*')) {\n normalizedTarget = normalizedTarget.slice(0, -2);\n }\n // If the file path starts with the target, it's a match.\n if (relativePath.startsWith(normalizedTarget)) {\n if (normalizedTarget.length > longestMatch) {\n longestMatch = normalizedTarget.length;\n let aliasBase = aliasPattern;\n if (aliasBase.endsWith('/*')) {\n aliasBase = aliasBase.slice(0, -2);\n }\n const remainder = relativePath.slice(normalizedTarget.length);\n const cleanRemainder = remainder.replace(/^\\/+/, '');\n matchedAlias = aliasBase + (cleanRemainder ? '/' + cleanRemainder : '');\n }\n }\n }\n }\n return matchedAlias;\n }\n\n /**\n * Convert the relative import path to an absolute one.\n * First, compute the file's path relative to the project root (from tsconfig).\n * If an alias is found via tsconfig paths, return that alias.\n * Otherwise, fall back to computing a path relative to the configured basePath.\n */\n function toAbsolute(importPath: string, filePath: string): string {\n const fileDir = path.dirname(filePath);\n const resolvedPath = path.resolve(fileDir, importPath);\n\n // Compute relative path from the project root.\n let relativeToRoot = path.relative(projectRoot, resolvedPath);\n relativeToRoot = relativeToRoot.replace(/\\\\/g, '/');\n const aliasPath = tryAliasMapping(relativeToRoot);\n if (aliasPath) {\n return aliasPath;\n }\n\n // Fallback: compute relative to the configured basePath.\n let relativeToBase = path.relative(basePath, resolvedPath);\n relativeToBase = relativeToBase.replace(/\\\\/g, '/');\n if (!relativeToBase.startsWith('/')) {\n relativeToBase = '/' + relativeToBase;\n }\n return relativeToBase;\n }\n\n return {\n ImportDeclaration(node) {\n if (node.source && typeof node.source.value === 'string') {\n const importPath = node.source.value;\n if (importPath.startsWith('.')) {\n const fileName = context.getFilename();\n const absolutePath = toAbsolute(importPath, fileName);\n context.report({\n node: node.source,\n messageId: 'relativeImport',\n data: { importPath, absolutePath },\n fix(fixer) {\n return fixer.replaceText(node.source, `'${absolutePath}'`);\n },\n });\n }\n }\n },\n // Optionally handle dynamic imports.\n CallExpression(node) {\n // For dynamic imports, node.callee is an Identifier with name 'import'\n if (\n // Check if this is a dynamic import\n (node.callee as any).name === 'import' &&\n node.arguments.length === 1 &&\n node.arguments[0].type === TSESTree.AST_NODE_TYPES.Literal &&\n typeof node.arguments[0].value === 'string'\n ) {\n const importPath = node.arguments[0].value;\n if (importPath.startsWith('.')) {\n const fileName = context.getFilename();\n const absolutePath = toAbsolute(importPath, fileName);\n context.report({\n node: node.arguments[0],\n messageId: 'relativeImport',\n data: { importPath, absolutePath },\n fix(fixer) {\n return fixer.replaceText(node.arguments[0], `'${absolutePath}'`);\n },\n });\n }\n }\n },\n };\n },\n});\n","import { absoluteImports } from './absolute-imports.js';\n\nexport const rules = {\n 'absolute-imports': absoluteImports,\n};\n"],"mappings":"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","names":["fs","path","fs","path"]}