@jterrazz/typescript 5.2.0 → 6.0.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/README.md +123 -27
- package/bin/commands/check.sh +164 -0
- package/bin/commands/docs.sh +89 -0
- package/bin/merge-knip-config.js +240 -0
- package/bin/typescript.sh +20 -2
- package/package.json +48 -33
- package/presets/knip/base.json +5 -0
- package/presets/oxfmt/index.js +12 -0
- package/presets/oxlint/architectures/hexagonal-rules.js +39 -0
- package/presets/oxlint/architectures/hexagonal.js +13 -0
- package/presets/oxlint/base.js +128 -0
- package/presets/oxlint/expo.js +33 -0
- package/presets/oxlint/next.js +23 -0
- package/presets/oxlint/node.js +14 -0
- package/presets/oxlint/plugins/codestyle.js +194 -0
- package/presets/tsconfig/expo.json +18 -18
- package/presets/tsconfig/next.json +22 -22
- package/presets/tsconfig/node.json +14 -14
- package/presets/tsdown/build.js +10 -10
- package/presets/tsdown/bundle.js +10 -10
- package/src/index.js +16 -0
package/bin/typescript.sh
CHANGED
|
@@ -87,19 +87,37 @@ case "$COMMAND" in
|
|
|
87
87
|
--on-success "node --enable-source-maps dist/index.js"
|
|
88
88
|
;;
|
|
89
89
|
|
|
90
|
+
docs)
|
|
91
|
+
printf "${CYAN_BG}${BRIGHT_WHITE} TYPESCRIPT ${NC} Generating API docs...\n\n"
|
|
92
|
+
|
|
93
|
+
bash "$SCRIPT_DIR/commands/docs.sh" "$PROJECT_ROOT" "$PACKAGE_ROOT"
|
|
94
|
+
|
|
95
|
+
printf "\n${GREEN}Docs generated at .docs/${NC}\n"
|
|
96
|
+
;;
|
|
97
|
+
|
|
98
|
+
check|fix)
|
|
99
|
+
exec bash "$SCRIPT_DIR/commands/check.sh" "$COMMAND" "$@"
|
|
100
|
+
;;
|
|
101
|
+
|
|
90
102
|
*)
|
|
91
|
-
printf "${CYAN_BG}${BRIGHT_WHITE} TYPESCRIPT ${NC} TypeScript
|
|
103
|
+
printf "${CYAN_BG}${BRIGHT_WHITE} TYPESCRIPT ${NC} TypeScript toolchain\n\n"
|
|
92
104
|
printf "Usage: typescript <command>\n\n"
|
|
93
105
|
printf "Commands:\n"
|
|
94
106
|
printf " build Build application (ESM + types)\n"
|
|
95
107
|
printf " bundle Bundle library (ESM + CJS + types)\n"
|
|
96
108
|
printf " start Run the built application\n"
|
|
97
|
-
printf " dev Build, run, and rebuild on changes\n
|
|
109
|
+
printf " dev Build, run, and rebuild on changes\n"
|
|
110
|
+
printf " docs Generate API reference + llms.txt from TSDoc\n"
|
|
111
|
+
printf " check Check types, lint, formatting, and unused code\n"
|
|
112
|
+
printf " fix Auto-fix lint and formatting issues\n\n"
|
|
98
113
|
printf "Examples:\n"
|
|
99
114
|
printf " typescript build\n"
|
|
100
115
|
printf " typescript bundle\n"
|
|
101
116
|
printf " typescript start\n"
|
|
102
117
|
printf " typescript dev\n"
|
|
118
|
+
printf " typescript docs\n"
|
|
119
|
+
printf " typescript check\n"
|
|
120
|
+
printf " typescript fix\n"
|
|
103
121
|
exit 1
|
|
104
122
|
;;
|
|
105
123
|
esac
|
package/package.json
CHANGED
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
2
|
+
"name": "@jterrazz/typescript",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/jterrazz/package-typescript"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"typescript": "bin/typescript.sh"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"presets",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "src/index.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/index.js",
|
|
21
|
+
"./tsconfig/*": "./presets/tsconfig/*.json",
|
|
22
|
+
"./tsdown/*": "./presets/tsdown/*.js",
|
|
23
|
+
"./presets/tsconfig/*": "./presets/tsconfig/*.json",
|
|
24
|
+
"./presets/tsdown/*": "./presets/tsdown/*.js"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "# no build script",
|
|
31
|
+
"lint": "./bin/typescript.sh check --ignore-pattern '**/fixtures/**'",
|
|
32
|
+
"lint:fix": "./bin/typescript.sh fix --ignore-pattern '**/fixtures/**'",
|
|
33
|
+
"test": "vitest --run"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@typescript/native-preview": "^7.0.0-dev.20260327.2",
|
|
37
|
+
"eslint-plugin-perfectionist": "^5.7.0",
|
|
38
|
+
"knip": "^6.2.0",
|
|
39
|
+
"oxfmt": "^0.42.0",
|
|
40
|
+
"oxlint": "^1.57.0",
|
|
41
|
+
"tsdown": "^0.21.5",
|
|
42
|
+
"typedoc": "^0.28.19",
|
|
43
|
+
"typedoc-plugin-markdown": "^4.11.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@jterrazz/test": "^5.3.2",
|
|
47
|
+
"@types/node": "^25.5.0",
|
|
48
|
+
"vitest": "^4.1.2"
|
|
49
|
+
}
|
|
35
50
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
from: '/domain/',
|
|
4
|
+
disallow: [
|
|
5
|
+
'/application/',
|
|
6
|
+
'/infrastructure/',
|
|
7
|
+
'/presentation/',
|
|
8
|
+
'/di/',
|
|
9
|
+
'/config/',
|
|
10
|
+
'/generated/',
|
|
11
|
+
],
|
|
12
|
+
message: 'Domain layer must be pure - cannot import from other layers',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
from: '/application/use-cases/',
|
|
16
|
+
disallow: ['/infrastructure/', '/presentation/', '/di/'],
|
|
17
|
+
message: 'Use cases can only depend on domain and ports',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
from: '/application/ports/',
|
|
21
|
+
disallow: ['/infrastructure/', '/presentation/', '/di/'],
|
|
22
|
+
message: 'Ports are interfaces - cannot depend on implementations',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
from: '/infrastructure/inbound/',
|
|
26
|
+
disallow: ['/infrastructure/outbound/'],
|
|
27
|
+
message: 'Inbound adapters should not import outbound adapters - use DI',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
from: '/presentation/ui/(atoms|molecules)/',
|
|
31
|
+
disallow: ['/navigation/'],
|
|
32
|
+
message: 'Atoms and molecules must be pure - no navigation imports',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
from: '/presentation/features/',
|
|
36
|
+
disallow: ['/presentation/features/(?!common)'],
|
|
37
|
+
message: 'Features should be independent - use shared code in features/common',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'oxlint';
|
|
3
|
+
|
|
4
|
+
import defaultRules from './hexagonal-rules.js';
|
|
5
|
+
|
|
6
|
+
const pluginPath = resolve(import.meta.dirname, '../plugins/codestyle.js');
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
jsPlugins: [pluginPath],
|
|
10
|
+
rules: {
|
|
11
|
+
'codestyle/arch-hexagonal': ['error', { rules: defaultRules }],
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { defineConfig } from 'oxlint';
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const perfectionistPath = require.resolve('eslint-plugin-perfectionist');
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
plugins: ['typescript', 'import', 'oxc', 'unicorn'],
|
|
9
|
+
jsPlugins: [perfectionistPath],
|
|
10
|
+
categories: {
|
|
11
|
+
correctness: 'error',
|
|
12
|
+
suspicious: 'error',
|
|
13
|
+
perf: 'error',
|
|
14
|
+
pedantic: 'off',
|
|
15
|
+
style: 'error',
|
|
16
|
+
nursery: 'off',
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
// ============================================
|
|
20
|
+
// ENABLED RULES
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
'no-unused-vars': 'error',
|
|
24
|
+
|
|
25
|
+
// -- TypeScript --
|
|
26
|
+
'typescript/consistent-type-imports': [
|
|
27
|
+
'error',
|
|
28
|
+
{
|
|
29
|
+
disallowTypeAnnotations: true,
|
|
30
|
+
fixStyle: 'inline-type-imports',
|
|
31
|
+
prefer: 'type-imports',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
'typescript/no-unused-expressions': [
|
|
35
|
+
'error',
|
|
36
|
+
{
|
|
37
|
+
allowShortCircuit: true,
|
|
38
|
+
allowTernary: true,
|
|
39
|
+
allowTaggedTemplates: true,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
|
|
43
|
+
// -- Perfectionist (sorting/ordering) --
|
|
44
|
+
'perfectionist/sort-heritage-clauses': ['error', { type: 'natural' }],
|
|
45
|
+
'perfectionist/sort-intersection-types': ['error', { type: 'natural' }],
|
|
46
|
+
'perfectionist/sort-jsx-props': ['error', { type: 'natural' }],
|
|
47
|
+
'perfectionist/sort-named-exports': ['error', { type: 'natural' }],
|
|
48
|
+
'perfectionist/sort-named-imports': ['error', { type: 'natural' }],
|
|
49
|
+
'perfectionist/sort-union-types': ['error', { type: 'natural' }],
|
|
50
|
+
'perfectionist/sort-imports': [
|
|
51
|
+
'error',
|
|
52
|
+
{
|
|
53
|
+
type: 'alphabetical',
|
|
54
|
+
order: 'asc',
|
|
55
|
+
ignoreCase: true,
|
|
56
|
+
newlinesBetween: 1,
|
|
57
|
+
groups: [
|
|
58
|
+
['builtin', 'external'],
|
|
59
|
+
'internal',
|
|
60
|
+
['parent', 'sibling', 'index'],
|
|
61
|
+
'style',
|
|
62
|
+
'unknown',
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
|
|
67
|
+
// ============================================
|
|
68
|
+
// DISABLED - Conflicts with perfectionist
|
|
69
|
+
// ============================================
|
|
70
|
+
|
|
71
|
+
'sort-keys': 'off',
|
|
72
|
+
'sort-imports': 'off',
|
|
73
|
+
|
|
74
|
+
// ============================================
|
|
75
|
+
// DISABLED - Too strict / opinionated
|
|
76
|
+
// ============================================
|
|
77
|
+
|
|
78
|
+
'func-style': 'off',
|
|
79
|
+
'no-magic-numbers': 'off',
|
|
80
|
+
'no-ternary': 'off',
|
|
81
|
+
'init-declarations': 'off',
|
|
82
|
+
'id-length': 'off',
|
|
83
|
+
'max-statements': 'off',
|
|
84
|
+
'max-params': 'off',
|
|
85
|
+
'no-continue': 'off',
|
|
86
|
+
'arrow-body-style': 'off',
|
|
87
|
+
'prefer-destructuring': 'off',
|
|
88
|
+
'new-cap': 'off',
|
|
89
|
+
'no-await-in-loop': 'off',
|
|
90
|
+
'typescript/consistent-type-definitions': 'off',
|
|
91
|
+
'typescript/array-type': 'off',
|
|
92
|
+
'typescript/no-inferrable-types': 'off',
|
|
93
|
+
'unicorn/no-null': 'off',
|
|
94
|
+
'unicorn/no-array-sort': 'off',
|
|
95
|
+
'import/no-unassigned-import': 'off',
|
|
96
|
+
'import/no-named-default': 'off',
|
|
97
|
+
'import/no-nodejs-modules': 'off',
|
|
98
|
+
|
|
99
|
+
// ============================================
|
|
100
|
+
// ENABLED - Code style enforcement
|
|
101
|
+
// ============================================
|
|
102
|
+
|
|
103
|
+
'capitalized-comments': 'error',
|
|
104
|
+
curly: 'error',
|
|
105
|
+
'no-nested-ternary': 'error',
|
|
106
|
+
'unicorn/catch-error-name': 'error',
|
|
107
|
+
'unicorn/numeric-separators-style': 'error',
|
|
108
|
+
|
|
109
|
+
// ============================================
|
|
110
|
+
// DISABLED - Conflicts with other rules
|
|
111
|
+
// ============================================
|
|
112
|
+
|
|
113
|
+
'import/no-named-export': 'off',
|
|
114
|
+
'import/consistent-type-specifier-style': 'off',
|
|
115
|
+
'import/prefer-default-export': 'off',
|
|
116
|
+
'import/no-default-export': 'off',
|
|
117
|
+
'import/group-exports': 'off',
|
|
118
|
+
'import/no-anonymous-default-export': 'off',
|
|
119
|
+
|
|
120
|
+
// ============================================
|
|
121
|
+
// ENABLED - Clean imports
|
|
122
|
+
// ============================================
|
|
123
|
+
|
|
124
|
+
'import/first': 'error',
|
|
125
|
+
'import/no-namespace': 'error',
|
|
126
|
+
},
|
|
127
|
+
ignorePatterns: ['dist/**', 'node_modules/**'],
|
|
128
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'oxlint';
|
|
3
|
+
|
|
4
|
+
import base from './base.js';
|
|
5
|
+
|
|
6
|
+
const pluginPath = resolve(import.meta.dirname, 'plugins/codestyle.js');
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
extends: [base],
|
|
10
|
+
plugins: ['typescript', 'import', 'react'],
|
|
11
|
+
jsPlugins: [pluginPath],
|
|
12
|
+
rules: {
|
|
13
|
+
'typescript/no-require-imports': [
|
|
14
|
+
'error',
|
|
15
|
+
{
|
|
16
|
+
allow: [
|
|
17
|
+
String.raw`\.png$`,
|
|
18
|
+
String.raw`\.jpg$`,
|
|
19
|
+
String.raw`\.jpeg$`,
|
|
20
|
+
String.raw`\.gif$`,
|
|
21
|
+
String.raw`\.webp$`,
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
'codestyle/imports-without-ext': 'error',
|
|
26
|
+
'react/react-in-jsx-scope': 'off',
|
|
27
|
+
'react/jsx-props-no-spreading': 'off',
|
|
28
|
+
'react/jsx-boolean-value': 'off',
|
|
29
|
+
'react/jsx-handler-names': 'off',
|
|
30
|
+
'react/jsx-curly-brace-presence': 'off',
|
|
31
|
+
'unicorn/no-nested-ternary': 'off',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'oxlint';
|
|
3
|
+
|
|
4
|
+
import base from './base.js';
|
|
5
|
+
|
|
6
|
+
const pluginPath = resolve(import.meta.dirname, 'plugins/codestyle.js');
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
extends: [base],
|
|
10
|
+
plugins: ['typescript', 'import', 'react', 'nextjs'],
|
|
11
|
+
jsPlugins: [pluginPath],
|
|
12
|
+
ignorePatterns: ['dist/**', 'node_modules/**', '.next/**', 'next-env.d.ts'],
|
|
13
|
+
rules: {
|
|
14
|
+
'codestyle/imports-without-ext': 'error',
|
|
15
|
+
'react/react-in-jsx-scope': 'off',
|
|
16
|
+
'react/jsx-props-no-spreading': 'off',
|
|
17
|
+
'react/jsx-boolean-value': 'off',
|
|
18
|
+
'react/jsx-handler-names': 'off',
|
|
19
|
+
'react/jsx-curly-brace-presence': 'off',
|
|
20
|
+
'react/jsx-max-depth': 'off',
|
|
21
|
+
'unicorn/no-nested-ternary': 'off',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'oxlint';
|
|
3
|
+
|
|
4
|
+
import base from './base.js';
|
|
5
|
+
|
|
6
|
+
const pluginPath = resolve(import.meta.dirname, 'plugins/codestyle.js');
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
extends: [base],
|
|
10
|
+
jsPlugins: [pluginPath],
|
|
11
|
+
rules: {
|
|
12
|
+
'codestyle/imports-with-ext': 'error',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Codestyle plugin - custom rules for code quality
|
|
2
|
+
// Contains: architecture boundaries, import extensions
|
|
3
|
+
|
|
4
|
+
import defaultHexagonalRules from '../architectures/hexagonal-rules.js';
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// Hexagonal - Architecture boundary rules
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
function createHexagonalRule() {
|
|
11
|
+
return {
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Enforce hexagonal architecture layer boundaries',
|
|
16
|
+
category: 'Best Practices',
|
|
17
|
+
},
|
|
18
|
+
schema: [
|
|
19
|
+
{
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
rules: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
from: { type: 'string' },
|
|
28
|
+
disallow: { type: 'array', items: { type: 'string' } },
|
|
29
|
+
message: { type: 'string' },
|
|
30
|
+
},
|
|
31
|
+
required: ['from', 'disallow'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const options = context.options[0];
|
|
40
|
+
const rules = options && options.rules ? options.rules : defaultHexagonalRules;
|
|
41
|
+
const filename = context.getFilename();
|
|
42
|
+
|
|
43
|
+
function checkNode(node) {
|
|
44
|
+
if (!node.source || !node.source.value) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const importPath = node.source.value;
|
|
49
|
+
|
|
50
|
+
for (const rule of rules) {
|
|
51
|
+
const fromPattern = new RegExp(rule.from);
|
|
52
|
+
if (!fromPattern.test(filename)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const disallowPattern of rule.disallow) {
|
|
57
|
+
if (new RegExp(disallowPattern).test(importPath)) {
|
|
58
|
+
context.report({
|
|
59
|
+
node: node.source,
|
|
60
|
+
message: rule.message || 'Import violates architecture boundaries',
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
ImportDeclaration: checkNode,
|
|
70
|
+
ExportNamedDeclaration: checkNode,
|
|
71
|
+
ExportAllDeclaration: checkNode,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================
|
|
78
|
+
// Imports-with-ext - Require .js extensions
|
|
79
|
+
// ============================================
|
|
80
|
+
|
|
81
|
+
const importsWithExtRule = {
|
|
82
|
+
meta: {
|
|
83
|
+
type: 'problem',
|
|
84
|
+
docs: {
|
|
85
|
+
description: 'Require .js extension in imports for Node.js ESM compatibility',
|
|
86
|
+
category: 'Best Practices',
|
|
87
|
+
},
|
|
88
|
+
fixable: 'code',
|
|
89
|
+
schema: [],
|
|
90
|
+
},
|
|
91
|
+
create(context) {
|
|
92
|
+
const hasExtension = /\.[a-zA-Z0-9]+$/;
|
|
93
|
+
|
|
94
|
+
function checkNode(node) {
|
|
95
|
+
if (!node.source || !node.source.value) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const importPath = node.source.value;
|
|
100
|
+
|
|
101
|
+
// Only check relative imports
|
|
102
|
+
if (!importPath.startsWith('.')) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Skip if it already has an extension
|
|
107
|
+
if (hasExtension.test(importPath)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Skip type-only imports (they are erased at runtime)
|
|
112
|
+
if (node.importKind === 'type') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
context.report({
|
|
117
|
+
node: node.source,
|
|
118
|
+
message: 'Missing .js extension in import (required for Node.js ESM)',
|
|
119
|
+
fix(fixer) {
|
|
120
|
+
const newPath = `${importPath}.js`;
|
|
121
|
+
return fixer.replaceText(node.source, `'${newPath}'`);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
ImportDeclaration: checkNode,
|
|
128
|
+
ExportNamedDeclaration: checkNode,
|
|
129
|
+
ExportAllDeclaration: checkNode,
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ============================================
|
|
135
|
+
// Imports-without-ext - Remove extensions
|
|
136
|
+
// ============================================
|
|
137
|
+
|
|
138
|
+
const importsWithoutExtRule = {
|
|
139
|
+
meta: {
|
|
140
|
+
type: 'problem',
|
|
141
|
+
docs: {
|
|
142
|
+
description: 'Remove .js, .jsx, .ts, .tsx extensions from imports',
|
|
143
|
+
category: 'Best Practices',
|
|
144
|
+
},
|
|
145
|
+
fixable: 'code',
|
|
146
|
+
schema: [],
|
|
147
|
+
},
|
|
148
|
+
create(context) {
|
|
149
|
+
const extensionsToRemove = /\.(?:js|jsx|ts|tsx)$/;
|
|
150
|
+
|
|
151
|
+
function checkNode(node) {
|
|
152
|
+
if (!node.source || !node.source.value) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const importPath = node.source.value;
|
|
157
|
+
|
|
158
|
+
// Check both relative imports and path alias imports
|
|
159
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('@/')) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (extensionsToRemove.test(importPath)) {
|
|
164
|
+
const match = importPath.match(extensionsToRemove);
|
|
165
|
+
context.report({
|
|
166
|
+
node: node.source,
|
|
167
|
+
message: `Remove "${match[0]}" extension from import`,
|
|
168
|
+
fix(fixer) {
|
|
169
|
+
const newPath = importPath.replace(extensionsToRemove, '');
|
|
170
|
+
return fixer.replaceText(node.source, `'${newPath}'`);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
ImportDeclaration: checkNode,
|
|
178
|
+
ExportNamedDeclaration: checkNode,
|
|
179
|
+
ExportAllDeclaration: checkNode,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export default {
|
|
185
|
+
meta: {
|
|
186
|
+
name: 'codestyle',
|
|
187
|
+
version: '1.0.0',
|
|
188
|
+
},
|
|
189
|
+
rules: {
|
|
190
|
+
'arch-hexagonal': createHexagonalRule(),
|
|
191
|
+
'imports-with-ext': importsWithExtRule,
|
|
192
|
+
'imports-without-ext': importsWithoutExtRule,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"display": "Expo",
|
|
3
|
+
"include": ["../../../../**/*.ts", "../../../../**/*.tsx"],
|
|
4
|
+
"exclude": ["../../../../node_modules"],
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"baseUrl": ".",
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"jsx": "react-native",
|
|
11
|
+
"lib": ["DOM", "ESNext"],
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"paths": {
|
|
15
|
+
"@/*": ["src/*"]
|
|
16
|
+
},
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"target": "ESNext"
|
|
20
|
+
}
|
|
21
21
|
}
|