@simplysm/lint 13.0.99 → 14.0.1
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/eslint-plugin.js +7 -10
- package/dist/eslint-plugin.js.map +1 -6
- package/dist/eslint-recommended.d.ts +2 -1
- package/dist/eslint-recommended.d.ts.map +1 -1
- package/dist/eslint-recommended.js +147 -244
- package/dist/eslint-recommended.js.map +1 -6
- package/dist/rules/no-hard-private.d.ts +6 -6
- package/dist/rules/no-hard-private.js +109 -88
- package/dist/rules/no-hard-private.js.map +1 -6
- package/dist/rules/no-subpath-imports-from-simplysm.d.ts +5 -5
- package/dist/rules/no-subpath-imports-from-simplysm.js +72 -60
- package/dist/rules/no-subpath-imports-from-simplysm.js.map +1 -6
- package/dist/rules/ts-no-throw-not-implemented-error.d.ts +5 -5
- package/dist/rules/ts-no-throw-not-implemented-error.js +92 -58
- package/dist/rules/ts-no-throw-not-implemented-error.js.map +1 -6
- package/dist/utils/create-rule.d.ts +3 -3
- package/dist/utils/create-rule.js +19 -7
- package/dist/utils/create-rule.js.map +1 -6
- package/package.json +9 -12
- package/src/eslint-recommended.ts +28 -115
- package/src/rules/no-hard-private.ts +23 -23
- package/src/rules/no-subpath-imports-from-simplysm.ts +13 -13
- package/src/rules/ts-no-throw-not-implemented-error.ts +14 -14
- package/src/utils/create-rule.ts +3 -3
- package/README.md +0 -81
- package/tests/no-hard-private.spec.ts +0 -888
- package/tests/no-subpath-imports-from-simplysm.spec.ts +0 -311
- package/tests/recommended.spec.ts +0 -145
- package/tests/ts-no-throw-not-implemented-error.spec.ts +0 -245
- package/tests/vitest.setup.ts +0 -10
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/lint",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "14.0.1",
|
|
4
|
+
"description": "심플리즘 패키지 - ESLint plugin",
|
|
5
|
+
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
-
"src"
|
|
16
|
-
"tests"
|
|
15
|
+
"src"
|
|
17
16
|
],
|
|
18
17
|
"sideEffects": false,
|
|
19
18
|
"exports": {
|
|
@@ -27,19 +26,17 @@
|
|
|
27
26
|
}
|
|
28
27
|
},
|
|
29
28
|
"dependencies": {
|
|
30
|
-
"@typescript-eslint/utils": "^8.57.
|
|
29
|
+
"@typescript-eslint/utils": "^8.57.2",
|
|
30
|
+
"angular-eslint": "^21.3.1",
|
|
31
31
|
"eslint": "^9.39.4",
|
|
32
32
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
33
33
|
"eslint-plugin-import": "^2.32.0",
|
|
34
|
-
"eslint-plugin-solid": "^0.14.5",
|
|
35
|
-
"eslint-plugin-tailwindcss": "^3.18.2",
|
|
36
34
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
37
|
-
"globals": "^17.4.0",
|
|
38
35
|
"typescript": "^5.9.3",
|
|
39
|
-
"typescript-eslint": "^8.57.
|
|
36
|
+
"typescript-eslint": "^8.57.2"
|
|
40
37
|
},
|
|
41
38
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
39
|
+
"@typescript-eslint/rule-tester": "^8.57.2",
|
|
40
|
+
"@types/node": "^20.19.37"
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
import globals from "globals";
|
|
2
1
|
import tseslint, { type FlatConfig } from "typescript-eslint";
|
|
2
|
+
import angular from "angular-eslint";
|
|
3
3
|
import plugin from "./eslint-plugin";
|
|
4
4
|
import importPlugin from "eslint-plugin-import";
|
|
5
5
|
import unusedImportsPlugin from "eslint-plugin-unused-imports";
|
|
6
|
-
import solidPlugin from "eslint-plugin-solid";
|
|
7
|
-
import tailwindcssPlugin from "eslint-plugin-tailwindcss";
|
|
8
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
9
6
|
import { ESLint } from "eslint";
|
|
10
7
|
import { fileURLToPath } from "url";
|
|
11
8
|
|
|
12
|
-
//#region Common rules configuration
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Common rules for JS/TS
|
|
16
|
-
* - no-console: Prohibit console usage in production code (prevent performance degradation)
|
|
17
|
-
* - no-warning-comments: Warn about TODO/FIXME comments (to check for incomplete code)
|
|
18
|
-
* - eqeqeq: Enforce `===` usage (allow `== null` for null checks)
|
|
19
|
-
* - no-self-compare: Prevent typos like `x === x`
|
|
20
|
-
* - array-callback-return: Prevent missing return in map/filter, etc.
|
|
21
|
-
*/
|
|
22
9
|
const commonRules: FlatConfig.Rules = {
|
|
23
10
|
"no-console": "error",
|
|
24
11
|
"no-warning-comments": "warn",
|
|
@@ -32,7 +19,7 @@ const noNodeBuiltinsRules: FlatConfig.Rules = {
|
|
|
32
19
|
"error",
|
|
33
20
|
{
|
|
34
21
|
name: "Buffer",
|
|
35
|
-
message: "
|
|
22
|
+
message: "Uint8Array를 사용하세요. 복잡한 연산에는 @simplysm/core-common의 BytesUtils를 사용하세요.",
|
|
36
23
|
},
|
|
37
24
|
],
|
|
38
25
|
"no-restricted-imports": [
|
|
@@ -42,26 +29,21 @@ const noNodeBuiltinsRules: FlatConfig.Rules = {
|
|
|
42
29
|
{
|
|
43
30
|
name: "buffer",
|
|
44
31
|
message:
|
|
45
|
-
"
|
|
32
|
+
"Uint8Array를 사용하세요. 복잡한 연산에는 @simplysm/core-common의 BytesUtils를 사용하세요.",
|
|
46
33
|
},
|
|
47
34
|
{
|
|
48
35
|
name: "events",
|
|
49
|
-
message: "
|
|
36
|
+
message: "@simplysm/core-common의 EventEmitter를 사용하세요.",
|
|
50
37
|
},
|
|
51
38
|
{
|
|
52
39
|
name: "eventemitter3",
|
|
53
|
-
message: "
|
|
40
|
+
message: "@simplysm/core-common의 EventEmitter를 사용하세요.",
|
|
54
41
|
},
|
|
55
42
|
],
|
|
56
43
|
},
|
|
57
44
|
],
|
|
58
45
|
};
|
|
59
46
|
|
|
60
|
-
/**
|
|
61
|
-
* Unused import handling rules
|
|
62
|
-
* - Auto-remove unused imports
|
|
63
|
-
* - Allow unused variables/parameters with `_` prefix (e.g., `_unused`)
|
|
64
|
-
*/
|
|
65
47
|
const unusedImportsRules: FlatConfig.Rules = {
|
|
66
48
|
"unused-imports/no-unused-imports": "error",
|
|
67
49
|
"unused-imports/no-unused-vars": [
|
|
@@ -75,29 +57,24 @@ const unusedImportsRules: FlatConfig.Rules = {
|
|
|
75
57
|
],
|
|
76
58
|
};
|
|
77
59
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
60
|
+
export default tseslint.config(
|
|
61
|
+
{
|
|
62
|
+
ignores: [
|
|
63
|
+
// directory/** 형태의 순회 자체를 건너뜀
|
|
64
|
+
"**/node_modules/**",
|
|
65
|
+
"**/dist/**",
|
|
66
|
+
"**/.*/**",
|
|
67
|
+
"**/_*/**",
|
|
68
|
+
],
|
|
69
|
+
},
|
|
88
70
|
{
|
|
89
71
|
languageOptions: {
|
|
90
|
-
globals: {
|
|
91
|
-
...globals.node,
|
|
92
|
-
...globals.es2024,
|
|
93
|
-
...globals.browser,
|
|
94
|
-
},
|
|
95
72
|
ecmaVersion: 2024,
|
|
96
73
|
sourceType: "module",
|
|
97
74
|
},
|
|
98
75
|
},
|
|
99
76
|
{
|
|
100
|
-
files: ["**/*.js", "**/*.
|
|
77
|
+
files: ["**/*.js", "**/*.mjs", "**/*.cjs"],
|
|
101
78
|
plugins: {
|
|
102
79
|
"import": importPlugin,
|
|
103
80
|
"@simplysm": plugin as unknown as ESLint.Plugin,
|
|
@@ -126,15 +103,17 @@ export default defineConfig([
|
|
|
126
103
|
},
|
|
127
104
|
],
|
|
128
105
|
|
|
129
|
-
//
|
|
106
|
+
// JS/TS 공통 규칙
|
|
130
107
|
"@simplysm/no-subpath-imports-from-simplysm": "error",
|
|
131
108
|
"@simplysm/no-hard-private": "error",
|
|
132
109
|
|
|
133
110
|
...noNodeBuiltinsRules,
|
|
134
111
|
},
|
|
135
112
|
},
|
|
113
|
+
...angular.configs.tsRecommended,
|
|
136
114
|
{
|
|
137
|
-
files: ["**/*.ts"
|
|
115
|
+
files: ["**/*.ts"],
|
|
116
|
+
processor: angular.processInlineTemplates,
|
|
138
117
|
plugins: {
|
|
139
118
|
"@typescript-eslint": tseslint.plugin,
|
|
140
119
|
"@simplysm": plugin as unknown as ESLint.Plugin,
|
|
@@ -187,16 +166,11 @@ export default defineConfig([
|
|
|
187
166
|
],
|
|
188
167
|
"@typescript-eslint/prefer-readonly": "error",
|
|
189
168
|
|
|
190
|
-
// Prevent mistakes: passing async function to void callback (prevent error loss)
|
|
191
|
-
// - arguments: false → allow socket.on("event", async () => {}) (handled with internal try-catch)
|
|
192
|
-
// - attributes: false → allow JSX event handlers (SolidJS compatible)
|
|
193
169
|
"@typescript-eslint/no-misused-promises": [
|
|
194
170
|
"error",
|
|
195
|
-
{ checksVoidReturn: { arguments: false
|
|
171
|
+
{ checksVoidReturn: { arguments: false } },
|
|
196
172
|
],
|
|
197
|
-
// Prevent mistakes: throwing non-Error objects (prevent stack trace loss)
|
|
198
173
|
"@typescript-eslint/only-throw-error": "error",
|
|
199
|
-
// Prevent mistakes: using delete on arrays (prevent sparse array bugs)
|
|
200
174
|
"@typescript-eslint/no-array-delete": "error",
|
|
201
175
|
|
|
202
176
|
"@simplysm/no-hard-private": "error",
|
|
@@ -206,80 +180,19 @@ export default defineConfig([
|
|
|
206
180
|
...unusedImportsRules,
|
|
207
181
|
...noNodeBuiltinsRules,
|
|
208
182
|
|
|
209
|
-
"import/no-extraneous-dependencies":
|
|
210
|
-
"error",
|
|
211
|
-
{
|
|
212
|
-
devDependencies: [
|
|
213
|
-
"**/lib/**",
|
|
214
|
-
"**/eslint.config.ts",
|
|
215
|
-
"**/simplysm.ts",
|
|
216
|
-
"**/vitest.config.ts",
|
|
217
|
-
"**/vitest-e2e.config.ts",
|
|
218
|
-
"**/vitest.setup.ts",
|
|
219
|
-
"**/vitest.setup.ts",
|
|
220
|
-
],
|
|
221
|
-
},
|
|
222
|
-
],
|
|
183
|
+
"import/no-extraneous-dependencies": "error",
|
|
223
184
|
},
|
|
224
185
|
},
|
|
225
|
-
// Test folders: allow root devDependencies (vitest, etc.)
|
|
226
186
|
{
|
|
227
|
-
files: [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
],
|
|
187
|
+
files: ["**/*.html"],
|
|
188
|
+
extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
files: ["**/tests/**/*.ts"],
|
|
233
192
|
rules: {
|
|
234
193
|
"no-console": "off",
|
|
235
194
|
"import/no-extraneous-dependencies": "off",
|
|
236
195
|
"@simplysm/ts-no-throw-not-implemented-error": "off",
|
|
237
196
|
},
|
|
238
197
|
},
|
|
239
|
-
|
|
240
|
-
{
|
|
241
|
-
files: ["**/*.ts", "**/*.tsx"],
|
|
242
|
-
plugins: {
|
|
243
|
-
solid: solidPlugin as unknown as ESLint.Plugin,
|
|
244
|
-
tailwindcss: tailwindcssPlugin as unknown as ESLint.Plugin,
|
|
245
|
-
},
|
|
246
|
-
settings: {
|
|
247
|
-
tailwindcss: {
|
|
248
|
-
// Support template literal tags: recognize clsx`py-0.5 px-1.5` form
|
|
249
|
-
tags: ["clsx"],
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
rules: {
|
|
253
|
-
// ─── Prevent mistakes ───
|
|
254
|
-
"solid/reactivity": "off",
|
|
255
|
-
"solid/no-destructure": "error", // Props destructuring → reactivity loss
|
|
256
|
-
"solid/components-return-once": "error", // early return → bugs
|
|
257
|
-
"solid/jsx-no-duplicate-props": "error", // Duplicate props
|
|
258
|
-
"solid/jsx-no-undef": ["error", { typescriptEnabled: true }], // Undefined variables
|
|
259
|
-
"solid/no-react-deps": "error", // React dependency array mistakes
|
|
260
|
-
"solid/no-react-specific-props": "error", // React props mistakes (className, etc.)
|
|
261
|
-
|
|
262
|
-
// ─── Security ───
|
|
263
|
-
"solid/no-innerhtml": "error", // Prevent XSS
|
|
264
|
-
"solid/jsx-no-script-url": "error", // Prevent javascript: URLs
|
|
265
|
-
|
|
266
|
-
// ─── Tooling support ───
|
|
267
|
-
"solid/jsx-uses-vars": "error", // Prevent false positives in unused import detection
|
|
268
|
-
|
|
269
|
-
// ─── Conventions ───
|
|
270
|
-
"solid/prefer-for": "error", // Recommend For component
|
|
271
|
-
"solid/event-handlers": "error", // Event handler naming
|
|
272
|
-
"solid/imports": "error", // Import consistency
|
|
273
|
-
"solid/style-prop": "error", // style prop format
|
|
274
|
-
"solid/self-closing-comp": "error", // Self-closing tags
|
|
275
|
-
|
|
276
|
-
// ─── Tailwind CSS ───
|
|
277
|
-
"tailwindcss/classnames-order": "warn", // Auto-sort class order
|
|
278
|
-
"tailwindcss/enforces-negative-arbitrary-values": "error", // Unify negative arbitrary value format
|
|
279
|
-
"tailwindcss/enforces-shorthand": "error", // Recommend shorthand usage
|
|
280
|
-
"tailwindcss/no-contradicting-classname": "error", // Prohibit conflicting classes (p-2 p-4, etc.)
|
|
281
|
-
"tailwindcss/no-custom-classname": "error", // Prohibit custom classes not in Tailwind
|
|
282
|
-
"tailwindcss/no-unnecessary-arbitrary-value": "error", // Prohibit unnecessary arbitrary values
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
]);
|
|
198
|
+
);
|
|
@@ -18,27 +18,27 @@ function isClassMemberWithAccessibility(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* ECMAScript private 필드(`#field`)를 제한하고 TypeScript `private` 키워드 사용을 강제하는 ESLint 규칙.
|
|
22
22
|
*
|
|
23
23
|
* @remarks
|
|
24
|
-
*
|
|
25
|
-
* -
|
|
26
|
-
* -
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
24
|
+
* 이 규칙이 검사하는 항목:
|
|
25
|
+
* - 클래스 필드 선언: `#field`
|
|
26
|
+
* - 클래스 메서드 선언: `#method()`
|
|
27
|
+
* - 클래스 접근자 선언: `accessor #field`
|
|
28
|
+
* - 멤버 접근 표현식: `this.#field`
|
|
29
29
|
*/
|
|
30
30
|
export default createRule({
|
|
31
31
|
name: "no-hard-private",
|
|
32
32
|
meta: {
|
|
33
33
|
type: "problem",
|
|
34
34
|
docs: {
|
|
35
|
-
description: '
|
|
35
|
+
description: 'hard private 필드(#) 대신 TypeScript "private _" 스타일을 강제합니다.',
|
|
36
36
|
},
|
|
37
37
|
messages: {
|
|
38
38
|
preferSoftPrivate:
|
|
39
|
-
'
|
|
39
|
+
'hard private 필드(#)는 사용할 수 없습니다. "private _" 스타일을 사용하세요.',
|
|
40
40
|
nameConflict:
|
|
41
|
-
'
|
|
41
|
+
'hard private 필드 "#{{name}}"을(를) "_{{name}}"(으)로 변환할 수 없습니다. 동일한 이름의 멤버가 이미 존재합니다.',
|
|
42
42
|
},
|
|
43
43
|
fixable: "code",
|
|
44
44
|
schema: [],
|
|
@@ -46,11 +46,11 @@ export default createRule({
|
|
|
46
46
|
defaultOptions: [],
|
|
47
47
|
create(context) {
|
|
48
48
|
const sourceCode = context.sourceCode;
|
|
49
|
-
//
|
|
49
|
+
// 중첩 클래스 지원을 위한 스택 구조
|
|
50
50
|
const classStack: Set<string>[] = [];
|
|
51
51
|
|
|
52
52
|
return {
|
|
53
|
-
// 0.
|
|
53
|
+
// 0. 클래스 진입 시 멤버 이름 수집
|
|
54
54
|
"ClassBody"(node: TSESTree.ClassBody) {
|
|
55
55
|
const memberNames = new Set<string>();
|
|
56
56
|
for (const member of node.body) {
|
|
@@ -67,7 +67,7 @@ export default createRule({
|
|
|
67
67
|
classStack.pop();
|
|
68
68
|
},
|
|
69
69
|
|
|
70
|
-
// 1.
|
|
70
|
+
// 1. 선언 감지 (PropertyDefinition, MethodDefinition, AccessorProperty)
|
|
71
71
|
"PropertyDefinition > PrivateIdentifier, MethodDefinition > PrivateIdentifier, AccessorProperty > PrivateIdentifier"(
|
|
72
72
|
node: TSESTree.PrivateIdentifier,
|
|
73
73
|
) {
|
|
@@ -76,11 +76,11 @@ export default createRule({
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const identifierName = node.name; //
|
|
79
|
+
const identifierName = node.name; // '#' 문자를 제외한 이름
|
|
80
80
|
const targetName = `_${identifierName}`;
|
|
81
81
|
const currentClassMembers = classStack.at(-1);
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// 이름 충돌 확인
|
|
84
84
|
if (currentClassMembers?.has(targetName)) {
|
|
85
85
|
context.report({
|
|
86
86
|
node,
|
|
@@ -96,25 +96,25 @@ export default createRule({
|
|
|
96
96
|
fix(fixer) {
|
|
97
97
|
const fixes: RuleFix[] = [];
|
|
98
98
|
|
|
99
|
-
// 1-1.
|
|
99
|
+
// 1-1. 이름 변경 (#a -> _a)
|
|
100
100
|
fixes.push(fixer.replaceText(node, targetName));
|
|
101
101
|
|
|
102
|
-
// 1-2.
|
|
102
|
+
// 1-2. 'private' 접근 제어자를 추가할 위치 계산
|
|
103
103
|
if (parent.accessibility == null) {
|
|
104
|
-
//
|
|
104
|
+
// 기본 삽입 위치: 부모 노드의 시작 (static, async 등 포함)
|
|
105
105
|
let tokenToInsertBefore = sourceCode.getFirstToken(parent);
|
|
106
106
|
|
|
107
|
-
//
|
|
107
|
+
// 데코레이터가 있으면 마지막 데코레이터 다음 토큰 앞에 삽입
|
|
108
108
|
// (@Deco private static _foo)
|
|
109
109
|
if (parent.decorators.length > 0) {
|
|
110
110
|
const lastDecorator = parent.decorators.at(-1)!;
|
|
111
111
|
tokenToInsertBefore = sourceCode.getTokenAfter(lastDecorator);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
// tokenToInsertBefore
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
114
|
+
// tokenToInsertBefore는 현재 'static', 'async', 'readonly', 또는 변수명('_foo')입니다.
|
|
115
|
+
// 앞에 'private '를 삽입하면 자연스럽게 'private static ...' 순서가 됩니다.
|
|
116
|
+
// tokenToInsertBefore가 null이면 AST 파싱 오류 등의 예외 상황을 나타냅니다.
|
|
117
|
+
// 이런 경우 이름만 변경되는 불완전한 수정을 방지하기 위해 전체 수정을 건너뜁니다.
|
|
118
118
|
if (tokenToInsertBefore == null) {
|
|
119
119
|
return [];
|
|
120
120
|
}
|
|
@@ -126,7 +126,7 @@ export default createRule({
|
|
|
126
126
|
});
|
|
127
127
|
},
|
|
128
128
|
|
|
129
|
-
// 2.
|
|
129
|
+
// 2. 사용 감지 (this.#field)
|
|
130
130
|
"MemberExpression > PrivateIdentifier"(node: TSESTree.PrivateIdentifier) {
|
|
131
131
|
const identifierName = node.name;
|
|
132
132
|
context.report({
|
|
@@ -2,13 +2,13 @@ import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/utils";
|
|
|
2
2
|
import { createRule } from "../utils/create-rule";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* `@simplysm/*` 패키지에서 'src' 하위 경로 import를 금지하는 ESLint 규칙.
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
8
|
+
* 이 규칙이 검사하는 항목:
|
|
9
|
+
* - 정적 import 문: `import ... from '...'`
|
|
10
|
+
* - 동적 import: `import('...')`
|
|
11
|
+
* - 재내보내기 문: `export { ... } from '...'`, `export * from '...'`
|
|
12
12
|
*/
|
|
13
13
|
export default createRule({
|
|
14
14
|
name: "no-subpath-imports-from-simplysm",
|
|
@@ -16,13 +16,13 @@ export default createRule({
|
|
|
16
16
|
type: "problem",
|
|
17
17
|
docs: {
|
|
18
18
|
description:
|
|
19
|
-
"
|
|
19
|
+
"@simplysm 패키지에서 'src' 하위 경로 import를 금지합니다. (예: @simplysm/pkg/src/x → 금지)",
|
|
20
20
|
},
|
|
21
21
|
fixable: "code",
|
|
22
22
|
schema: [],
|
|
23
23
|
messages: {
|
|
24
24
|
noSubpathImport:
|
|
25
|
-
"
|
|
25
|
+
"'@simplysm/{{pkg}}' 패키지에서 'src' 하위 경로를 import할 수 없습니다: '{{importPath}}'",
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
defaultOptions: [],
|
|
@@ -32,8 +32,8 @@ export default createRule({
|
|
|
32
32
|
|
|
33
33
|
const parts = importPath.split("/");
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
35
|
+
// 허용: @simplysm/pkg, @simplysm/pkg/xxx, @simplysm/pkg/xxx/yyy
|
|
36
|
+
// 금지: @simplysm/pkg/src, @simplysm/pkg/src/xxx
|
|
37
37
|
if (parts.length >= 3 && parts[2] === "src") {
|
|
38
38
|
const fixedPath = `@simplysm/${parts[1]}`;
|
|
39
39
|
context.report({
|
|
@@ -52,12 +52,12 @@ export default createRule({
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
|
-
//
|
|
55
|
+
// 정적 import: import { x } from '...'
|
|
56
56
|
ImportDeclaration(node) {
|
|
57
57
|
checkAndReport(node.source, node.source.value);
|
|
58
58
|
},
|
|
59
59
|
|
|
60
|
-
//
|
|
60
|
+
// 동적 import: import('...')
|
|
61
61
|
ImportExpression(node) {
|
|
62
62
|
if (node.source.type !== AST_NODE_TYPES.Literal) return;
|
|
63
63
|
const importPath = node.source.value;
|
|
@@ -65,13 +65,13 @@ export default createRule({
|
|
|
65
65
|
checkAndReport(node.source, importPath);
|
|
66
66
|
},
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// 재내보내기: export { x } from '...'
|
|
69
69
|
ExportNamedDeclaration(node) {
|
|
70
70
|
if (!node.source) return;
|
|
71
71
|
checkAndReport(node.source, node.source.value);
|
|
72
72
|
},
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// 전체 재내보내기: export * from '...'
|
|
75
75
|
ExportAllDeclaration(node) {
|
|
76
76
|
checkAndReport(node.source, node.source.value);
|
|
77
77
|
},
|
|
@@ -2,25 +2,25 @@ import { AST_NODE_TYPES, ASTUtils, type TSESTree } from "@typescript-eslint/util
|
|
|
2
2
|
import { createRule } from "../utils/create-rule";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* `@simplysm/core-common`의 `NotImplementedError` 사용을 감지하고 경고하는 ESLint 규칙.
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* `@simplysm/core-common`에서 import한 `NotImplementedError`를 `new`로 인스턴스화하는 코드를 감지합니다.
|
|
9
|
+
* 미구현 코드가 프로덕션에 포함되는 것을 방지합니다.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* 지원하는 import 형태:
|
|
12
12
|
* - named import: `import { NotImplementedError } from "@simplysm/core-common"`
|
|
13
13
|
* - aliased import: `import { NotImplementedError as NIE } from "@simplysm/core-common"`
|
|
14
14
|
* - namespace import: `import * as CC from "@simplysm/core-common"` → `new CC.NotImplementedError()`
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* 동적 import (`await import(...)`)는 감지하지 않습니다.
|
|
17
17
|
*/
|
|
18
18
|
export default createRule({
|
|
19
19
|
name: "ts-no-throw-not-implemented-error",
|
|
20
20
|
meta: {
|
|
21
21
|
type: "suggestion",
|
|
22
22
|
docs: {
|
|
23
|
-
description: "
|
|
23
|
+
description: "'NotImplementedError' 사용에 대해 경고합니다",
|
|
24
24
|
},
|
|
25
25
|
schema: [],
|
|
26
26
|
messages: {
|
|
@@ -30,10 +30,10 @@ export default createRule({
|
|
|
30
30
|
defaultOptions: [],
|
|
31
31
|
create(context) {
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
* @param identifier -
|
|
35
|
-
* @param expectedImportedName -
|
|
36
|
-
* @returns
|
|
33
|
+
* 식별자가 @simplysm/core-common에서 import되었는지 확인합니다.
|
|
34
|
+
* @param identifier - 확인할 식별자
|
|
35
|
+
* @param expectedImportedName - named import에서 확인할 원래 이름 (namespace import의 경우 undefined)
|
|
36
|
+
* @returns import 소스가 @simplysm/core-common이면 true, 아니면 false
|
|
37
37
|
*/
|
|
38
38
|
function isImportedFromSimplysm(
|
|
39
39
|
identifier: TSESTree.Identifier,
|
|
@@ -48,7 +48,7 @@ export default createRule({
|
|
|
48
48
|
if (def.parent.type !== AST_NODE_TYPES.ImportDeclaration) continue;
|
|
49
49
|
if (def.parent.source.value !== "@simplysm/core-common") continue;
|
|
50
50
|
|
|
51
|
-
// named/aliased import: import { NotImplementedError }
|
|
51
|
+
// named/aliased import: import { NotImplementedError } 또는 import { NotImplementedError as NIE }
|
|
52
52
|
if (def.node.type === AST_NODE_TYPES.ImportSpecifier && expectedImportedName != null) {
|
|
53
53
|
const imported = def.node.imported;
|
|
54
54
|
if (
|
|
@@ -75,12 +75,12 @@ export default createRule({
|
|
|
75
75
|
NewExpression(node: TSESTree.NewExpression) {
|
|
76
76
|
let shouldReport = false;
|
|
77
77
|
|
|
78
|
-
//
|
|
78
|
+
// 케이스 1: new NotImplementedError() 또는 new NIE() (named/aliased import)
|
|
79
79
|
if (node.callee.type === AST_NODE_TYPES.Identifier) {
|
|
80
80
|
shouldReport = isImportedFromSimplysm(node.callee, "NotImplementedError");
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// 케이스 2: new CC.NotImplementedError() (namespace import)
|
|
84
84
|
else if (
|
|
85
85
|
node.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
86
86
|
node.callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
@@ -92,7 +92,7 @@ export default createRule({
|
|
|
92
92
|
|
|
93
93
|
if (!shouldReport) return;
|
|
94
94
|
|
|
95
|
-
let msg = "
|
|
95
|
+
let msg = "미구현";
|
|
96
96
|
const firstArg = node.arguments.at(0);
|
|
97
97
|
if (
|
|
98
98
|
firstArg?.type === AST_NODE_TYPES.Literal &&
|
package/src/utils/create-rule.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* ESLint 규칙을 생성하는 팩토리 함수.
|
|
5
5
|
*
|
|
6
6
|
* @remarks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* `@typescript-eslint/utils`의 `RuleCreator`를 래핑하며
|
|
8
|
+
* 규칙 문서 URL을 자동으로 생성합니다.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```typescript
|
package/README.md
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# @simplysm/lint
|
|
2
|
-
|
|
3
|
-
Simplysm package - Lint configuration (ESLint). Provides an ESLint plugin with custom rules and a recommended flat config for TypeScript, SolidJS, and Tailwind CSS projects.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @simplysm/lint
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Entrypoints
|
|
12
|
-
|
|
13
|
-
This package has two entrypoints:
|
|
14
|
-
|
|
15
|
-
| Entrypoint | Import Path | Description |
|
|
16
|
-
|------------|-------------|-------------|
|
|
17
|
-
| ESLint Plugin | `@simplysm/lint/eslint-plugin` | Custom ESLint rules |
|
|
18
|
-
| Recommended Config | `@simplysm/lint/eslint-recommended` | Full recommended flat config |
|
|
19
|
-
|
|
20
|
-
## API Overview
|
|
21
|
-
|
|
22
|
-
### ESLint Plugin (`@simplysm/lint/eslint-plugin`)
|
|
23
|
-
|
|
24
|
-
Default export is an ESLint plugin object with `rules`:
|
|
25
|
-
|
|
26
|
-
| Rule | Type | Fixable | Description |
|
|
27
|
-
|------|------|---------|-------------|
|
|
28
|
-
| `no-hard-private` | problem | Yes | Enforces TypeScript `private _` style instead of hard private fields (`#`). Auto-fixes `#field` to `private _field` and `this.#field` to `this._field`. |
|
|
29
|
-
| `no-subpath-imports-from-simplysm` | problem | Yes | Prohibits `src` subpath imports from `@simplysm/*` packages (e.g., `@simplysm/pkg/src/x` is prohibited). Auto-fixes to package root import. |
|
|
30
|
-
| `ts-no-throw-not-implemented-error` | suggestion | No | Warns about `NotImplementedError` usage from `@simplysm/core-common`. Detects named, aliased, and namespace imports. |
|
|
31
|
-
|
|
32
|
-
### Recommended Config (`@simplysm/lint/eslint-recommended`)
|
|
33
|
-
|
|
34
|
-
Default export is a full ESLint flat config array that includes:
|
|
35
|
-
|
|
36
|
-
- **Global ignores**: `node_modules`, `dist`, dotfiles, underscore-prefixed directories
|
|
37
|
-
- **JS/JSX rules**: Common rules + unused imports + import dependency checks + custom `@simplysm` rules
|
|
38
|
-
- **TS/TSX rules**: TypeScript-specific rules (strict boolean expressions, no floating promises, prefer readonly, etc.) + all JS rules
|
|
39
|
-
- **Test overrides**: Relaxed rules for `tests/` and `tests-e2e/` directories (allows console, no extraneous deps check)
|
|
40
|
-
- **SolidJS rules**: No destructure, components-return-once, no-innerhtml, prefer-for, etc.
|
|
41
|
-
- **Tailwind CSS rules**: Classnames order, enforces shorthand, no contradicting classname, no custom classname
|
|
42
|
-
|
|
43
|
-
#### Included Plugins
|
|
44
|
-
|
|
45
|
-
| Plugin | Prefix | Purpose |
|
|
46
|
-
|--------|--------|---------|
|
|
47
|
-
| `@typescript-eslint` | `@typescript-eslint/` | TypeScript type-aware linting |
|
|
48
|
-
| `@simplysm` (this package) | `@simplysm/` | Custom Simplysm rules |
|
|
49
|
-
| `eslint-plugin-import` | `import/` | Import/export validation |
|
|
50
|
-
| `eslint-plugin-unused-imports` | `unused-imports/` | Auto-remove unused imports |
|
|
51
|
-
| `eslint-plugin-solid` | `solid/` | SolidJS best practices |
|
|
52
|
-
| `eslint-plugin-tailwindcss` | `tailwindcss/` | Tailwind CSS class validation |
|
|
53
|
-
|
|
54
|
-
## Usage Examples
|
|
55
|
-
|
|
56
|
-
### Use the recommended config
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
// eslint.config.ts
|
|
60
|
-
import recommended from "@simplysm/lint/eslint-recommended";
|
|
61
|
-
|
|
62
|
-
export default recommended;
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Use the plugin with custom rules
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
// eslint.config.ts
|
|
69
|
-
import plugin from "@simplysm/lint/eslint-plugin";
|
|
70
|
-
|
|
71
|
-
export default [
|
|
72
|
-
{
|
|
73
|
-
plugins: { "@simplysm": plugin },
|
|
74
|
-
rules: {
|
|
75
|
-
"@simplysm/no-hard-private": "error",
|
|
76
|
-
"@simplysm/no-subpath-imports-from-simplysm": "error",
|
|
77
|
-
"@simplysm/ts-no-throw-not-implemented-error": "warn",
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
```
|