declapract-typescript-ehmpathy 0.44.1 → 0.45.0
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/dist/practices/typescript/bad-practices/relative-imports/.declapract.readme.md +27 -0
- package/dist/practices/typescript/bad-practices/relative-imports/src/<star><star>/<star>.ts.declapract.ts +59 -0
- package/dist/practices/typescript/best-practice/.declapract.readme.md +5 -0
- package/dist/practices/typescript/best-practice/package.json +2 -1
- package/dist/practices/typescript/best-practice/tsconfig.json +4 -0
- package/package.json +4 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
this bad practice detects relative imports that traverse up directories (`../`) and can be replaced with the `@src/` path alias.
|
|
2
|
+
|
|
3
|
+
why this matters:
|
|
4
|
+
- relative paths are fragile and break when files are moved or reorganized
|
|
5
|
+
- `@src/` alias provides stable, absolute imports from the src root
|
|
6
|
+
- enables easier refactoring and code navigation
|
|
7
|
+
- imports remain correct regardless of file location
|
|
8
|
+
|
|
9
|
+
what it catches:
|
|
10
|
+
- imports using `../` (e.g., `from '../utils/helper'`)
|
|
11
|
+
- deeply nested relative imports (e.g., `from '../../../domain/objects'`)
|
|
12
|
+
|
|
13
|
+
what it ignores:
|
|
14
|
+
- same-directory imports (`./`) which are appropriate for local modules
|
|
15
|
+
- files outside `src/` directory
|
|
16
|
+
- imports already using `@src/` alias
|
|
17
|
+
|
|
18
|
+
the fix automatically rewrites:
|
|
19
|
+
```typescript
|
|
20
|
+
// before
|
|
21
|
+
import { helper } from '../utils/helper';
|
|
22
|
+
import { thing } from '../../shared/thing';
|
|
23
|
+
|
|
24
|
+
// after
|
|
25
|
+
import { helper } from '@src/utils/helper';
|
|
26
|
+
import { thing } from '@src/shared/thing';
|
|
27
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type FileCheckFunction, type FileFixFunction } from 'declapract';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* .what = detects relative imports that should use @src alias
|
|
5
|
+
* .why = relative paths are fragile and break when files move
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// matches relative imports that go up directories (../)
|
|
9
|
+
const RELATIVE_IMPORT_PATTERN = /from\s+['"](\.\.\/)+(.*?)['"]/g;
|
|
10
|
+
|
|
11
|
+
export const check: FileCheckFunction = (contents, { relativeFilePath }) => {
|
|
12
|
+
if (!contents) throw new Error('does not match bad practice');
|
|
13
|
+
|
|
14
|
+
// skip if file is not in src/
|
|
15
|
+
if (!relativeFilePath?.startsWith('src/')) {
|
|
16
|
+
throw new Error('does not match bad practice');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// check for relative imports that go up directories (../)
|
|
20
|
+
const matches = contents.match(RELATIVE_IMPORT_PATTERN);
|
|
21
|
+
if (matches && matches.length > 0) {
|
|
22
|
+
return; // matches bad practice
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error('does not match bad practice');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const fix: FileFixFunction = (contents, { relativeFilePath }) => {
|
|
29
|
+
if (!contents) return {};
|
|
30
|
+
if (!relativeFilePath?.startsWith('src/')) return {};
|
|
31
|
+
|
|
32
|
+
// calculate the path parts of the current file
|
|
33
|
+
const pathParts = relativeFilePath.split('/');
|
|
34
|
+
const srcIndex = pathParts.indexOf('src');
|
|
35
|
+
|
|
36
|
+
// replace relative imports with @src imports
|
|
37
|
+
const fixed = contents.replace(
|
|
38
|
+
/from\s+['"]((\.\.\/)+)(.*?)['"]/g,
|
|
39
|
+
(match, dots, _, importPath) => {
|
|
40
|
+
// count how many ../ we have
|
|
41
|
+
const upCount = (dots.match(/\.\.\//g) || []).length;
|
|
42
|
+
|
|
43
|
+
// get the directories between src/ and the current file
|
|
44
|
+
const currentDirParts = pathParts.slice(srcIndex + 1, -1);
|
|
45
|
+
|
|
46
|
+
// if going up more levels than we have directories, just use @src/importPath
|
|
47
|
+
if (upCount >= currentDirParts.length) {
|
|
48
|
+
return `from '@src/${importPath}'`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// otherwise, calculate the remaining path after going up
|
|
52
|
+
const remainingParts = currentDirParts.slice(0, -upCount);
|
|
53
|
+
const targetPath = [...remainingParts, importPath].join('/');
|
|
54
|
+
return `from '@src/${targetPath}'`;
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return { contents: fixed };
|
|
59
|
+
};
|
|
@@ -8,3 +8,8 @@ namely:
|
|
|
8
8
|
- strict mode on tsc prevents a _ton_ of errors
|
|
9
9
|
- without it, `Type | null` -> `Type` -> so you have a bunch of bugs whenever a value could be null but you dont expect it to be / realize it could be
|
|
10
10
|
- it defeats the point of typechecking, really
|
|
11
|
+
- absolute imports via @src/* path alias
|
|
12
|
+
- eliminates fragile relative paths (../../..)
|
|
13
|
+
- imports are stable regardless of file location
|
|
14
|
+
- easier refactoring and code navigation
|
|
15
|
+
- uses tsc-alias post-compilation to rewrite paths for node runtime
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"devDependencies": {
|
|
3
3
|
"typescript": "@declapract{check.minVersion('5.4.5')}",
|
|
4
|
+
"tsc-alias": "@declapract{check.minVersion('1.8.10')}",
|
|
4
5
|
"@tsconfig/node20": "@declapract{check.minVersion('20.1.5')}",
|
|
5
6
|
"@tsconfig/strictest": "@declapract{check.minVersion('2.0.5')}",
|
|
6
7
|
"@types/node": "@declapract{check.minVersion('22.15.21')}"
|
|
7
8
|
},
|
|
8
9
|
"scripts": {
|
|
9
10
|
"build:clean": "rm dist/ -rf",
|
|
10
|
-
"build:compile": "tsc -p ./tsconfig.build.json",
|
|
11
|
+
"build:compile": "tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
|
|
11
12
|
"build": "npm run build:clean && npm run build:compile && npm run build:artifact",
|
|
12
13
|
"test:types": "tsc -p ./tsconfig.json --noEmit"
|
|
13
14
|
}
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
"@tsconfig/node20/tsconfig.json"
|
|
5
5
|
],
|
|
6
6
|
"compilerOptions": {
|
|
7
|
+
"baseUrl": ".",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@src/*": ["src/*"]
|
|
10
|
+
},
|
|
7
11
|
"importsNotUsedAsValues": "remove",
|
|
8
12
|
"noPropertyAccessFromIndexSignature": false,
|
|
9
13
|
"noUnusedLocals": false, // this is something a linter should warn on, not something a compiler should fail on
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "declapract-typescript-ehmpathy",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "declapract best practices declarations for typescript",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.45.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"repository": "ehmpathy/declapract-typescript-ehmpathy",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"preversion": "npm run prepush",
|
|
40
40
|
"postversion": "git push origin HEAD --tags --no-verify",
|
|
41
41
|
"prepare:husky": "npx husky install && chmod ug+x .husky/*",
|
|
42
|
-
"prepare": "[ -
|
|
42
|
+
"prepare": "[ -e .git ] && npm run prepare:husky || exit 0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"chalk": "4.1.2",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"expect": "29.4.2",
|
|
48
48
|
"flat": "5.0.2",
|
|
49
49
|
"helpful-errors": "1.5.3",
|
|
50
|
+
"rhachet": "1.13.1",
|
|
51
|
+
"rhachet-roles-ehmpathy": "1.13.7",
|
|
50
52
|
"simple-log-methods": "0.5.0",
|
|
51
53
|
"yaml": "1.10.2"
|
|
52
54
|
},
|