activo 0.4.4 → 0.5.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 +203 -1
- package/data/2026-03-04_20-54.json +181 -0
- package/data/2026-03-04_20-56.json +181 -0
- package/data/apex-rulesets/egov.yaml +469 -0
- package/data/apex-rulesets/modernize.yaml +687 -0
- package/data/apex-rulesets/quality.yaml +1677 -0
- package/data/apex-rulesets/rule-schema.yaml +587 -0
- package/data/apex-rulesets/secure.yaml +1688 -0
- package/data/apex-rulesets/spring.yaml +455 -0
- package/data/apex-rulesets/sql-format.yaml +99 -0
- package/data/apex-rulesets/sql-oracle.yaml +281 -0
- package/data/apex-rulesets/sql.yaml +1660 -0
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +32 -10
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/index.js +31 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.d.ts +3 -3
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +203 -384
- package/dist/core/agent.js.map +1 -1
- package/dist/core/commands.d.ts +2 -1
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +61 -9
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +41 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation.d.ts +2 -2
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/intentRouter.d.ts +43 -0
- package/dist/core/intentRouter.d.ts.map +1 -0
- package/dist/core/intentRouter.js +804 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts +24 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -0
- package/dist/core/llm/anthropic.js +226 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +5 -14
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +3 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/llm/types.d.ts +22 -0
- package/dist/core/llm/types.d.ts.map +1 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/llm/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +6 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +16 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/init.d.ts +12 -0
- package/dist/core/mcp/init.d.ts.map +1 -0
- package/dist/core/mcp/init.js +55 -0
- package/dist/core/mcp/init.js.map +1 -0
- package/dist/core/mcp/logger.d.ts +14 -0
- package/dist/core/mcp/logger.d.ts.map +1 -0
- package/dist/core/mcp/logger.js +50 -0
- package/dist/core/mcp/logger.js.map +1 -0
- package/dist/core/tools/analyzePatterns.d.ts +3 -0
- package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
- package/dist/core/tools/analyzePatterns.js +293 -0
- package/dist/core/tools/analyzePatterns.js.map +1 -0
- package/dist/core/tools/apexPaths.d.ts +14 -0
- package/dist/core/tools/apexPaths.d.ts.map +1 -0
- package/dist/core/tools/apexPaths.js +54 -0
- package/dist/core/tools/apexPaths.js.map +1 -0
- package/dist/core/tools/apexUtils.d.ts +36 -0
- package/dist/core/tools/apexUtils.d.ts.map +1 -0
- package/dist/core/tools/apexUtils.js +83 -0
- package/dist/core/tools/apexUtils.js.map +1 -0
- package/dist/core/tools/explainIssue.d.ts +3 -0
- package/dist/core/tools/explainIssue.d.ts.map +1 -0
- package/dist/core/tools/explainIssue.js +181 -0
- package/dist/core/tools/explainIssue.js.map +1 -0
- package/dist/core/tools/fixGen.d.ts +3 -0
- package/dist/core/tools/fixGen.d.ts.map +1 -0
- package/dist/core/tools/fixGen.js +338 -0
- package/dist/core/tools/fixGen.js.map +1 -0
- package/dist/core/tools/generateImprovements.d.ts +21 -0
- package/dist/core/tools/generateImprovements.d.ts.map +1 -0
- package/dist/core/tools/generateImprovements.js +602 -0
- package/dist/core/tools/generateImprovements.js.map +1 -0
- package/dist/core/tools/generateReport.d.ts +3 -0
- package/dist/core/tools/generateReport.d.ts.map +1 -0
- package/dist/core/tools/generateReport.js +315 -0
- package/dist/core/tools/generateReport.js.map +1 -0
- package/dist/core/tools/index.d.ts +7 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +62 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/recommendProfile.d.ts +3 -0
- package/dist/core/tools/recommendProfile.d.ts.map +1 -0
- package/dist/core/tools/recommendProfile.js +334 -0
- package/dist/core/tools/recommendProfile.js.map +1 -0
- package/dist/core/tools/ruleGen.d.ts +3 -0
- package/dist/core/tools/ruleGen.d.ts.map +1 -0
- package/dist/core/tools/ruleGen.js +1103 -0
- package/dist/core/tools/ruleGen.js.map +1 -0
- package/dist/core/tools/standards.d.ts.map +1 -1
- package/dist/core/tools/standards.js +7 -3
- package/dist/core/tools/standards.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +86 -35
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +1 -3
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +146 -5
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageList.d.ts +3 -1
- package/dist/ui/components/MessageList.d.ts.map +1 -1
- package/dist/ui/components/MessageList.js +13 -7
- package/dist/ui/components/MessageList.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +3 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolStatus.d.ts +3 -1
- package/dist/ui/components/ToolStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolStatus.js +19 -4
- package/dist/ui/components/ToolStatus.js.map +1 -1
- package/package.json +7 -1
- package/demo.gif +0 -0
- package/demo.tape +0 -53
- package/screenshot.png +0 -0
- package/src/cli/banner.ts +0 -38
- package/src/cli/headless.ts +0 -63
- package/src/cli/index.ts +0 -57
- package/src/core/agent.ts +0 -711
- package/src/core/commands.ts +0 -118
- package/src/core/config.ts +0 -98
- package/src/core/conversation.ts +0 -235
- package/src/core/llm/ollama.ts +0 -351
- package/src/core/mcp/client.ts +0 -143
- package/src/core/tools/analyzeAll.ts +0 -482
- package/src/core/tools/ast.ts +0 -826
- package/src/core/tools/builtIn.ts +0 -221
- package/src/core/tools/cache.ts +0 -570
- package/src/core/tools/cssAnalysis.ts +0 -324
- package/src/core/tools/dependencyAnalysis.ts +0 -363
- package/src/core/tools/embeddings.ts +0 -746
- package/src/core/tools/frontendAst.ts +0 -802
- package/src/core/tools/htmlAnalysis.ts +0 -466
- package/src/core/tools/index.ts +0 -160
- package/src/core/tools/javaAst.ts +0 -1030
- package/src/core/tools/javaQuality.integration.test.ts +0 -537
- package/src/core/tools/memory.ts +0 -655
- package/src/core/tools/mybatisAnalysis.ts +0 -322
- package/src/core/tools/openapiAnalysis.ts +0 -431
- package/src/core/tools/pythonAnalysis.ts +0 -477
- package/src/core/tools/sqlAnalysis.ts +0 -298
- package/src/core/tools/standards.test.ts +0 -186
- package/src/core/tools/standards.ts +0 -889
- package/src/core/tools/types.ts +0 -38
- package/src/ui/App.tsx +0 -334
- package/src/ui/components/InputBox.tsx +0 -37
- package/src/ui/components/MessageList.tsx +0 -80
- package/src/ui/components/StatusBar.tsx +0 -36
- package/src/ui/components/ToolStatus.tsx +0 -38
- package/tsconfig.json +0 -21
|
@@ -0,0 +1,1677 @@
|
|
|
1
|
+
# Code Quality Ruleset
|
|
2
|
+
version: "1.0"
|
|
3
|
+
profile: "quality"
|
|
4
|
+
|
|
5
|
+
languages:
|
|
6
|
+
- language: java
|
|
7
|
+
rules:
|
|
8
|
+
# ==================== 1. 클래스 명명규칙 ====================
|
|
9
|
+
- id: "quality-nc-001"
|
|
10
|
+
name: "Controller 클래스명 규칙 위반"
|
|
11
|
+
severity: "high"
|
|
12
|
+
category: "naming"
|
|
13
|
+
description: "Controller 클래스는 [표준단어조합]Controller 형식이어야 합니다"
|
|
14
|
+
enabled: true
|
|
15
|
+
pattern:
|
|
16
|
+
type: "regex"
|
|
17
|
+
regex: "class\\s+(?![A-Z][a-zA-Z0-9]*Controller\\b)\\w+Controller\\s+(extends|implements|\\{)"
|
|
18
|
+
custom:
|
|
19
|
+
rule_id: "NC-001"
|
|
20
|
+
expected_pattern: "[표준단어조합]Controller"
|
|
21
|
+
example: "UserManageController"
|
|
22
|
+
|
|
23
|
+
- id: "quality-nc-002"
|
|
24
|
+
name: "Service 인터페이스명 규칙 위반"
|
|
25
|
+
severity: "high"
|
|
26
|
+
category: "naming"
|
|
27
|
+
description: "Service 인터페이스는 [표준단어조합]Service 형식이어야 합니다"
|
|
28
|
+
enabled: true
|
|
29
|
+
pattern:
|
|
30
|
+
type: "regex"
|
|
31
|
+
regex: "interface\\s+(?![A-Z][a-zA-Z0-9]*Service\\b)\\w+Service\\s*(extends|\\{)"
|
|
32
|
+
custom:
|
|
33
|
+
rule_id: "NC-002"
|
|
34
|
+
expected_pattern: "[표준단어조합]Service"
|
|
35
|
+
example: "UserManageService"
|
|
36
|
+
|
|
37
|
+
- id: "quality-nc-004"
|
|
38
|
+
name: "Mapper 인터페이스명 규칙 위반"
|
|
39
|
+
severity: "high"
|
|
40
|
+
category: "naming"
|
|
41
|
+
description: "Mapper 인터페이스는 [표준단어조합]Mapper 형식이어야 합니다"
|
|
42
|
+
enabled: true
|
|
43
|
+
pattern:
|
|
44
|
+
type: "regex"
|
|
45
|
+
regex: "interface\\s+(?![A-Z][a-zA-Z0-9]*Mapper\\b)\\w+Mapper\\s*(extends|\\{)"
|
|
46
|
+
custom:
|
|
47
|
+
rule_id: "NC-004"
|
|
48
|
+
expected_pattern: "[표준단어조합]Mapper"
|
|
49
|
+
example: "UserManageMapper"
|
|
50
|
+
|
|
51
|
+
- id: "quality-nc-005"
|
|
52
|
+
name: "VO 클래스명 규칙 위반"
|
|
53
|
+
severity: "high"
|
|
54
|
+
category: "naming"
|
|
55
|
+
description: "VO 클래스는 [표준단어조합]Vo 형식이어야 합니다"
|
|
56
|
+
enabled: true
|
|
57
|
+
pattern:
|
|
58
|
+
type: "regex"
|
|
59
|
+
regex: "class\\s+(?![A-Z][a-zA-Z0-9]*Vo\\b)\\w+Vo\\s+(extends|implements|\\{)"
|
|
60
|
+
custom:
|
|
61
|
+
rule_id: "NC-005"
|
|
62
|
+
expected_pattern: "[표준단어조합]Vo"
|
|
63
|
+
example: "UserManageVo"
|
|
64
|
+
|
|
65
|
+
- id: "quality-nc-006"
|
|
66
|
+
name: "상수 클래스명 규칙 위반"
|
|
67
|
+
severity: "medium"
|
|
68
|
+
category: "naming"
|
|
69
|
+
description: "상수 클래스는 [표준단어조합]Const 형식이어야 합니다"
|
|
70
|
+
enabled: true
|
|
71
|
+
pattern:
|
|
72
|
+
type: "regex"
|
|
73
|
+
regex: "class\\s+(?![A-Z][a-zA-Z0-9]*Const\\b)\\w+Const\\s+(extends|implements|\\{)"
|
|
74
|
+
custom:
|
|
75
|
+
rule_id: "NC-006"
|
|
76
|
+
expected_pattern: "[표준단어조합]Const"
|
|
77
|
+
example: "UserConst"
|
|
78
|
+
|
|
79
|
+
- id: "quality-nc-007"
|
|
80
|
+
name: "유틸 클래스명 규칙 위반"
|
|
81
|
+
severity: "medium"
|
|
82
|
+
category: "naming"
|
|
83
|
+
description: "유틸 클래스는 [표준단어조합]Util 형식이어야 합니다"
|
|
84
|
+
enabled: true
|
|
85
|
+
pattern:
|
|
86
|
+
type: "regex"
|
|
87
|
+
regex: "class\\s+(?![A-Z][a-zA-Z0-9]*Util\\b)\\w+Util\\s+(extends|implements|\\{)"
|
|
88
|
+
custom:
|
|
89
|
+
rule_id: "NC-007"
|
|
90
|
+
expected_pattern: "[표준단어조합]Util"
|
|
91
|
+
example: "StringUtil"
|
|
92
|
+
|
|
93
|
+
- id: "quality-nc-008"
|
|
94
|
+
name: "클래스명 파스칼표기법 위반"
|
|
95
|
+
severity: "high"
|
|
96
|
+
category: "naming"
|
|
97
|
+
description: "클래스명은 대문자로 시작하는 파스칼 표기법을 사용해야 합니다"
|
|
98
|
+
enabled: true
|
|
99
|
+
pattern:
|
|
100
|
+
type: "regex"
|
|
101
|
+
regex: "class\\s+[a-z][a-zA-Z0-9]*\\s+"
|
|
102
|
+
custom:
|
|
103
|
+
rule_id: "NC-008"
|
|
104
|
+
expected_pattern: "PascalCase (대문자로 시작)"
|
|
105
|
+
|
|
106
|
+
# ==================== 2. 메서드 명명규칙 ====================
|
|
107
|
+
- id: "quality-nc-101"
|
|
108
|
+
name: "메서드 카멜표기법 위반"
|
|
109
|
+
severity: "high"
|
|
110
|
+
category: "naming"
|
|
111
|
+
description: "메서드명은 소문자로 시작하는 카멜 표기법을 사용해야 합니다"
|
|
112
|
+
enabled: true
|
|
113
|
+
pattern:
|
|
114
|
+
type: "regex"
|
|
115
|
+
regex: "(public|protected|private)\\s+[\\w<>,\\s]+\\s+[A-Z][a-zA-Z0-9]*\\s*\\("
|
|
116
|
+
custom:
|
|
117
|
+
rule_id: "NC-101"
|
|
118
|
+
expected_pattern: "camelCase (소문자로 시작)"
|
|
119
|
+
|
|
120
|
+
- id: "quality-nc-102"
|
|
121
|
+
name: "메서드 비표준동사 사용"
|
|
122
|
+
severity: "medium"
|
|
123
|
+
category: "naming"
|
|
124
|
+
description: "메서드명은 표준동사(inquire, retrieve, select, find, get, register, create, make, save, update, change, set, adjust, delete, execute, handle, apply, calculate, validate, check, certify, approve, reject, cancel, send, receive, transmit, encrypt, decrypt, upload, download 등)로 시작해야 합니다"
|
|
125
|
+
enabled: true
|
|
126
|
+
pattern:
|
|
127
|
+
type: "method-analysis"
|
|
128
|
+
conditions:
|
|
129
|
+
- "non-standard-verb-method"
|
|
130
|
+
custom:
|
|
131
|
+
rule_id: "NC-102"
|
|
132
|
+
standard_verbs: "inquire,retrieve,select,find,get,is,has,register,create,make,save,update,change,set,adjust,delete,execute,handle,apply,calculate,validate,check,certify,approve,reject,cancel,send,receive,transmit,encrypt,decrypt,upload,download,acquire,issue,manage,monitor,print,reserve,delay,extract,wait,delivery,inhouse,process,init,setup,configure,convert,parse,format,build,generate,load,read,write,open,close,start,stop,run,call,add,put,insert,remove,log,notify,publish,verify"
|
|
133
|
+
|
|
134
|
+
# ==================== 3. 변수 명명규칙 ====================
|
|
135
|
+
- id: "quality-nc-201"
|
|
136
|
+
name: "변수 카멜표기법 위반"
|
|
137
|
+
severity: "high"
|
|
138
|
+
category: "naming"
|
|
139
|
+
description: "변수명은 소문자로 시작하는 카멜 표기법을 사용해야 합니다"
|
|
140
|
+
enabled: true
|
|
141
|
+
pattern:
|
|
142
|
+
type: "regex"
|
|
143
|
+
regex: "(private|protected|public)\\s+(?!static\\s+final)[\\w<>,\\s]+\\s+[A-Z][a-zA-Z0-9]*\\s*(=|;)"
|
|
144
|
+
custom:
|
|
145
|
+
rule_id: "NC-201"
|
|
146
|
+
expected_pattern: "camelCase (소문자로 시작)"
|
|
147
|
+
|
|
148
|
+
- id: "quality-nc-202"
|
|
149
|
+
name: "단일문자 변수 사용"
|
|
150
|
+
severity: "high"
|
|
151
|
+
category: "naming"
|
|
152
|
+
description: "반복문/예외처리 외 단일문자 변수 사용은 금지됩니다"
|
|
153
|
+
enabled: false
|
|
154
|
+
pattern:
|
|
155
|
+
type: "regex"
|
|
156
|
+
regex: "(String|int|long|double|float|boolean|Object|Integer|Long|Double|Float|Boolean)\\s+[a-hlo-zA-Z]\\s*(=|;)"
|
|
157
|
+
custom:
|
|
158
|
+
rule_id: "NC-202"
|
|
159
|
+
allowed_single_chars: "i,j,k,m,n,c,d,e"
|
|
160
|
+
|
|
161
|
+
- id: "quality-nc-203"
|
|
162
|
+
name: "모호한 변수명 사용"
|
|
163
|
+
severity: "medium"
|
|
164
|
+
category: "naming"
|
|
165
|
+
description: "temp, tmp, data, obj, str, num, val, var 등 모호한 변수명 사용은 금지됩니다"
|
|
166
|
+
enabled: true
|
|
167
|
+
pattern:
|
|
168
|
+
type: "regex"
|
|
169
|
+
regex: "(\\w+(<[^>]+>)?(\\[\\])?)\\s+(temp|tmp|data|obj|str|num|val)\\s*(=|;|,)"
|
|
170
|
+
custom:
|
|
171
|
+
rule_id: "NC-203"
|
|
172
|
+
forbidden_variables: "temp,tmp,data,obj,str,num,val,var"
|
|
173
|
+
|
|
174
|
+
- id: "quality-nc-204"
|
|
175
|
+
name: "변수명 언더스코어/달러 시작"
|
|
176
|
+
severity: "medium"
|
|
177
|
+
category: "naming"
|
|
178
|
+
description: "변수명은 언더스코어(_) 또는 달러($)로 시작할 수 없습니다"
|
|
179
|
+
enabled: true
|
|
180
|
+
pattern:
|
|
181
|
+
type: "regex"
|
|
182
|
+
regex: "(private|protected|public)\\s+[\\w<>,\\s]+\\s+[_$][a-zA-Z0-9]*\\s*(=|;)"
|
|
183
|
+
custom:
|
|
184
|
+
rule_id: "NC-204"
|
|
185
|
+
|
|
186
|
+
# ==================== 4. 상수 명명규칙 ====================
|
|
187
|
+
- id: "quality-nc-301"
|
|
188
|
+
name: "상수 대문자 표기 위반"
|
|
189
|
+
severity: "high"
|
|
190
|
+
category: "naming"
|
|
191
|
+
description: "상수명은 모두 대문자를 사용해야 합니다 (serialVersionUID, log/logger 제외)"
|
|
192
|
+
enabled: true
|
|
193
|
+
pattern:
|
|
194
|
+
type: "regex"
|
|
195
|
+
regex: "static\\s+final\\s+[\\w<>]+\\s+[a-z][a-zA-Z0-9_]*\\s*(=|;)"
|
|
196
|
+
exclude:
|
|
197
|
+
- "serialVersionUID"
|
|
198
|
+
- "static\\s+final\\s+Logger\\s+"
|
|
199
|
+
- "static\\s+final\\s+Log\\s+"
|
|
200
|
+
custom:
|
|
201
|
+
rule_id: "NC-301"
|
|
202
|
+
expected_pattern: "UPPER_CASE"
|
|
203
|
+
|
|
204
|
+
- id: "quality-nc-302"
|
|
205
|
+
name: "상수 언더스코어 구분 위반"
|
|
206
|
+
severity: "high"
|
|
207
|
+
category: "naming"
|
|
208
|
+
description: "상수의 단어 간에는 언더스코어(_)로 구분해야 합니다"
|
|
209
|
+
enabled: true
|
|
210
|
+
pattern:
|
|
211
|
+
type: "regex"
|
|
212
|
+
regex: "static\\s+final\\s+[\\w<>]+\\s+[A-Z][A-Z0-9]*[a-z][A-Za-z0-9]*\\s*(=|;)"
|
|
213
|
+
custom:
|
|
214
|
+
rule_id: "NC-302"
|
|
215
|
+
expected_pattern: "UPPER_SNAKE_CASE"
|
|
216
|
+
|
|
217
|
+
- id: "quality-nc-303"
|
|
218
|
+
name: "상수 static final 누락"
|
|
219
|
+
severity: "high"
|
|
220
|
+
category: "naming"
|
|
221
|
+
description: "상수는 static final로 선언해야 합니다"
|
|
222
|
+
enabled: true
|
|
223
|
+
pattern:
|
|
224
|
+
type: "regex"
|
|
225
|
+
regex: "(public|private|protected)\\s+static\\s+(?!final)[\\w<>]+\\s+[A-Z][A-Z0-9_]*\\s*(=|;)"
|
|
226
|
+
custom:
|
|
227
|
+
rule_id: "NC-303"
|
|
228
|
+
|
|
229
|
+
# ==================== 5. 패키지 명명규칙 ====================
|
|
230
|
+
- id: "quality-nc-401"
|
|
231
|
+
name: "패키지명 대문자 사용"
|
|
232
|
+
severity: "high"
|
|
233
|
+
category: "naming"
|
|
234
|
+
description: "패키지명은 모두 소문자를 사용해야 합니다"
|
|
235
|
+
enabled: true
|
|
236
|
+
pattern:
|
|
237
|
+
type: "regex"
|
|
238
|
+
regex: "package\\s+[a-z0-9.]*[A-Z][a-zA-Z0-9.]*;"
|
|
239
|
+
custom:
|
|
240
|
+
rule_id: "NC-401"
|
|
241
|
+
expected_pattern: "소문자만 사용"
|
|
242
|
+
|
|
243
|
+
# ==================== 6. URL 명명규칙 ====================
|
|
244
|
+
- id: "quality-nc-502"
|
|
245
|
+
name: "예약 URL 사용"
|
|
246
|
+
severity: "critical"
|
|
247
|
+
category: "naming"
|
|
248
|
+
description: "/html, /css, /image, /js, /jsp, /static, /resources URL은 사용할 수 없습니다"
|
|
249
|
+
enabled: true
|
|
250
|
+
pattern:
|
|
251
|
+
type: "regex"
|
|
252
|
+
regex: "@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping)\\s*\\([^)]*\"/(html|css|image|js|jsp|static|resources)"
|
|
253
|
+
custom:
|
|
254
|
+
rule_id: "NC-502"
|
|
255
|
+
reserved_urls: "/html,/css,/image,/js,/jsp,/static,/resources"
|
|
256
|
+
|
|
257
|
+
# ==================== 7. 코딩 스타일 ====================
|
|
258
|
+
- id: "quality-cs-001"
|
|
259
|
+
name: "탭 문자 사용"
|
|
260
|
+
severity: "low"
|
|
261
|
+
category: "style"
|
|
262
|
+
description: "들여쓰기에 탭(tab) 대신 스페이스(space)를 사용해야 합니다. IDE 포맷터로 일괄 변환 가능."
|
|
263
|
+
enabled: true
|
|
264
|
+
pattern:
|
|
265
|
+
type: "regex"
|
|
266
|
+
regex: "^\t"
|
|
267
|
+
custom:
|
|
268
|
+
rule_id: "CS-001"
|
|
269
|
+
|
|
270
|
+
- id: "quality-cs-003"
|
|
271
|
+
name: "한 줄에 여러 문장"
|
|
272
|
+
severity: "high"
|
|
273
|
+
category: "style"
|
|
274
|
+
description: "한 줄에 하나의 statement만 기술해야 합니다"
|
|
275
|
+
enabled: false
|
|
276
|
+
pattern:
|
|
277
|
+
type: "regex"
|
|
278
|
+
regex: ";\\s*[a-zA-Z].*;"
|
|
279
|
+
custom:
|
|
280
|
+
rule_id: "CS-003"
|
|
281
|
+
|
|
282
|
+
- id: "quality-cs-007"
|
|
283
|
+
name: "이항연산자 공백 누락"
|
|
284
|
+
severity: "medium"
|
|
285
|
+
category: "style"
|
|
286
|
+
description: "이항연산자(=, +, -, *, /, %, ==, != 등) 양쪽에는 공백이 필요합니다"
|
|
287
|
+
enabled: false
|
|
288
|
+
pattern:
|
|
289
|
+
type: "regex"
|
|
290
|
+
regex: "[a-zA-Z0-9][=+\\-*/][a-zA-Z0-9]"
|
|
291
|
+
custom:
|
|
292
|
+
rule_id: "CS-007"
|
|
293
|
+
|
|
294
|
+
# ==================== 8. 로깅 규칙 ====================
|
|
295
|
+
- id: "quality-lg-001"
|
|
296
|
+
name: "System.out 사용"
|
|
297
|
+
severity: "critical"
|
|
298
|
+
category: "logging"
|
|
299
|
+
description: "System.out.println() 사용은 금지됩니다. Logback 프레임워크를 사용하세요."
|
|
300
|
+
enabled: true
|
|
301
|
+
pattern:
|
|
302
|
+
type: "ast-method-call"
|
|
303
|
+
method: "^(print|println|printf)$"
|
|
304
|
+
qualifier: "^System\\.(out|err)$"
|
|
305
|
+
custom:
|
|
306
|
+
rule_id: "LG-001"
|
|
307
|
+
fix: "Logback 프레임워크(logger.info/debug/error)를 사용하세요"
|
|
308
|
+
|
|
309
|
+
- id: "quality-lg-005"
|
|
310
|
+
name: "Logger 문자열 연결 사용"
|
|
311
|
+
severity: "high"
|
|
312
|
+
category: "logging"
|
|
313
|
+
description: "Logger에서 '+' 연산자로 문자열 연결은 금지됩니다. {} 플레이스홀더를 사용하세요."
|
|
314
|
+
enabled: true
|
|
315
|
+
pattern:
|
|
316
|
+
type: "ast-filtered-regex"
|
|
317
|
+
regex: "logger\\.(debug|info|warn|error)\\s*\\([^)]*\\+[^)]*\\)"
|
|
318
|
+
custom:
|
|
319
|
+
rule_id: "LG-005"
|
|
320
|
+
fix: "{} 플레이스홀더를 사용하세요: logger.info(\"message: {}\", value)"
|
|
321
|
+
|
|
322
|
+
- id: "quality-lg-006"
|
|
323
|
+
name: "Logger 플레이스홀더 미사용"
|
|
324
|
+
severity: "high"
|
|
325
|
+
category: "logging"
|
|
326
|
+
description: "변수 출력 시 {} 플레이스홀더를 사용해야 합니다"
|
|
327
|
+
enabled: true
|
|
328
|
+
pattern:
|
|
329
|
+
type: "ast-filtered-regex"
|
|
330
|
+
regex: "logger\\.(debug|info|warn|error)\\s*\\([^{]*\\+\\s*[a-zA-Z]"
|
|
331
|
+
custom:
|
|
332
|
+
rule_id: "LG-006"
|
|
333
|
+
fix: "{} 플레이스홀더를 사용하세요: logger.info(\"value: {}\", value)"
|
|
334
|
+
|
|
335
|
+
- id: "quality-lg-007"
|
|
336
|
+
name: "catch 외부에서 error 레벨 사용"
|
|
337
|
+
severity: "high"
|
|
338
|
+
category: "logging"
|
|
339
|
+
description: "logger.error()는 catch 블록 내부에서만 사용해야 합니다"
|
|
340
|
+
enabled: false
|
|
341
|
+
pattern:
|
|
342
|
+
type: "method-analysis"
|
|
343
|
+
conditions:
|
|
344
|
+
- "error-log-outside-catch"
|
|
345
|
+
custom:
|
|
346
|
+
rule_id: "LG-007"
|
|
347
|
+
|
|
348
|
+
# ==================== 9. 예외 처리 규칙 ====================
|
|
349
|
+
- id: "quality-ex-001"
|
|
350
|
+
name: "일반 Exception throw"
|
|
351
|
+
severity: "critical"
|
|
352
|
+
category: "exception"
|
|
353
|
+
description: "예외 발생 시 BusinessException만 사용해야 합니다"
|
|
354
|
+
enabled: false
|
|
355
|
+
pattern:
|
|
356
|
+
type: "regex"
|
|
357
|
+
regex: "throw\\s+new\\s+(?!BusinessException)[A-Z][a-zA-Z]*Exception\\s*\\("
|
|
358
|
+
custom:
|
|
359
|
+
rule_id: "EX-001"
|
|
360
|
+
|
|
361
|
+
- id: "quality-ex-002"
|
|
362
|
+
name: "Root Cause 미전달"
|
|
363
|
+
severity: "critical"
|
|
364
|
+
category: "exception"
|
|
365
|
+
description: "try-catch에서 예외 재정의 시 root cause 객체를 전달해야 합니다"
|
|
366
|
+
enabled: true
|
|
367
|
+
pattern:
|
|
368
|
+
type: "method-analysis"
|
|
369
|
+
conditions:
|
|
370
|
+
- "missing-root-cause"
|
|
371
|
+
custom:
|
|
372
|
+
rule_id: "EX-002"
|
|
373
|
+
|
|
374
|
+
- id: "quality-ex-004"
|
|
375
|
+
name: "빈 catch 블록"
|
|
376
|
+
severity: "critical"
|
|
377
|
+
category: "exception"
|
|
378
|
+
description: "catch 블록에서 예외를 무시하면 안 됩니다"
|
|
379
|
+
enabled: true
|
|
380
|
+
pattern:
|
|
381
|
+
type: "ast-try-catch"
|
|
382
|
+
custom:
|
|
383
|
+
rule_id: "EX-004"
|
|
384
|
+
catch_is_empty: true
|
|
385
|
+
fix: "catch 블록에 logger.error(\"message\", e)를 추가하세요"
|
|
386
|
+
|
|
387
|
+
# ==================== 10. 주석 규칙 ====================
|
|
388
|
+
- id: "quality-cmt-001"
|
|
389
|
+
name: "클래스 Javadoc 누락"
|
|
390
|
+
severity: "medium"
|
|
391
|
+
category: "documentation"
|
|
392
|
+
description: "모든 클래스에는 Javadoc 주석이 필요합니다"
|
|
393
|
+
enabled: true
|
|
394
|
+
pattern:
|
|
395
|
+
type: "method-analysis"
|
|
396
|
+
conditions:
|
|
397
|
+
- "class-without-javadoc"
|
|
398
|
+
custom:
|
|
399
|
+
rule_id: "CMT-001"
|
|
400
|
+
|
|
401
|
+
- id: "quality-cmt-002"
|
|
402
|
+
name: "public 메서드 Javadoc 누락"
|
|
403
|
+
severity: "medium"
|
|
404
|
+
category: "documentation"
|
|
405
|
+
description: "public 메서드에는 Javadoc 주석이 필요합니다"
|
|
406
|
+
enabled: true
|
|
407
|
+
pattern:
|
|
408
|
+
type: "method-analysis"
|
|
409
|
+
conditions:
|
|
410
|
+
- "public-method-without-javadoc"
|
|
411
|
+
custom:
|
|
412
|
+
rule_id: "CMT-002"
|
|
413
|
+
|
|
414
|
+
# ==================== 11. 코드 품질 규칙 ====================
|
|
415
|
+
- id: "quality-cq-001"
|
|
416
|
+
name: "코드 중복"
|
|
417
|
+
severity: "high"
|
|
418
|
+
category: "maintainability"
|
|
419
|
+
description: "불가피한 경우 외 코드 중복은 금지됩니다"
|
|
420
|
+
enabled: true
|
|
421
|
+
pattern:
|
|
422
|
+
type: "method-analysis"
|
|
423
|
+
conditions:
|
|
424
|
+
- "duplicate-code-block"
|
|
425
|
+
custom:
|
|
426
|
+
rule_id: "CQ-001"
|
|
427
|
+
|
|
428
|
+
# ==================== N+1 쿼리 (반복문 내 DB 작업) ====================
|
|
429
|
+
- id: "java-n-plus-one-query"
|
|
430
|
+
name: "N+1 쿼리 문제 (반복문 내 DB 작업)"
|
|
431
|
+
severity: "high"
|
|
432
|
+
category: "performance"
|
|
433
|
+
description: "반복문 내 DB 작업은 N+1 또는 N×M 성능 문제를 유발합니다"
|
|
434
|
+
enabled: true
|
|
435
|
+
|
|
436
|
+
- language: xml
|
|
437
|
+
rules:
|
|
438
|
+
# ==================== MyBatis 규칙 ====================
|
|
439
|
+
- id: "quality-mb-001"
|
|
440
|
+
name: "SQL_ID 주석 누락"
|
|
441
|
+
severity: "critical"
|
|
442
|
+
category: "mybatis"
|
|
443
|
+
description: "모든 SQL에 SQL_ID 주석이 필요합니다 (/* SQL_ID : [패키지명].[클래스명].[메소드명] */)"
|
|
444
|
+
enabled: true
|
|
445
|
+
pattern:
|
|
446
|
+
type: "regex"
|
|
447
|
+
regex: "<(select|insert|update|delete)\\s[^>]*>(?!\\s*/\\*\\s*SQL_ID)"
|
|
448
|
+
custom:
|
|
449
|
+
rule_id: "MB-001"
|
|
450
|
+
expected_format: "/* SQL_ID : [패키지명].[클래스명].[메소드명] */"
|
|
451
|
+
|
|
452
|
+
- id: "quality-mb-201"
|
|
453
|
+
name: "Mapper namespace 누락"
|
|
454
|
+
severity: "critical"
|
|
455
|
+
category: "mybatis"
|
|
456
|
+
description: "XML Mapper에 namespace가 필수입니다"
|
|
457
|
+
enabled: true
|
|
458
|
+
pattern:
|
|
459
|
+
type: "regex"
|
|
460
|
+
regex: "<mapper(?!\\s+namespace)"
|
|
461
|
+
custom:
|
|
462
|
+
rule_id: "MB-201"
|
|
463
|
+
|
|
464
|
+
# ==================== MyBatis 크로스파일 분석 ====================
|
|
465
|
+
- id: "quality-mybatis-unused-001"
|
|
466
|
+
name: "미사용 MyBatis SQL ID"
|
|
467
|
+
severity: "medium"
|
|
468
|
+
category: "dead-code"
|
|
469
|
+
description: "MyBatis XML의 SQL ID가 Java 코드에서 사용되지 않습니다. 불필요한 SQL 매핑을 제거하세요."
|
|
470
|
+
enabled: true
|
|
471
|
+
pattern:
|
|
472
|
+
type: "cross-file"
|
|
473
|
+
custom:
|
|
474
|
+
rule_id: "MYBATIS-UNUSED-001"
|
|
475
|
+
fix: "사용하지 않는 SQL 매핑을 제거하거나, 대응하는 Mapper 인터페이스 메서드를 추가하세요"
|
|
476
|
+
|
|
477
|
+
- id: "quality-mapper-no-xml-001"
|
|
478
|
+
name: "Mapper 메서드에 대응 XML SQL ID 없음"
|
|
479
|
+
severity: "high"
|
|
480
|
+
category: "cross-file"
|
|
481
|
+
description: "Mapper 인터페이스 메서드에 대응하는 XML SQL ID가 없습니다. 런타임 BindingException이 발생합니다."
|
|
482
|
+
enabled: true
|
|
483
|
+
pattern:
|
|
484
|
+
type: "cross-file"
|
|
485
|
+
custom:
|
|
486
|
+
rule_id: "MAPPER-NO-XML-001"
|
|
487
|
+
fix: "XML Mapper에 대응하는 <select/insert/update/delete id=\"메서드명\">을 추가하세요"
|
|
488
|
+
|
|
489
|
+
- id: "quality-resultmap-vo-001"
|
|
490
|
+
name: "resultMap property와 VO 필드 불일치"
|
|
491
|
+
severity: "high"
|
|
492
|
+
category: "cross-file"
|
|
493
|
+
description: "resultMap의 property가 VO/DTO 클래스에 존재하지 않습니다. 런타임에 매핑이 실패하거나 null이 됩니다."
|
|
494
|
+
enabled: true
|
|
495
|
+
pattern:
|
|
496
|
+
type: "cross-file"
|
|
497
|
+
custom:
|
|
498
|
+
rule_id: "RESULTMAP-VO-001"
|
|
499
|
+
fix: "VO 클래스에 해당 필드를 추가하거나, resultMap의 property명을 수정하세요"
|
|
500
|
+
|
|
501
|
+
- language: java
|
|
502
|
+
rules:
|
|
503
|
+
# ==================== 크로스파일 분석 (Java) ====================
|
|
504
|
+
- id: "quality-unused-service-001"
|
|
505
|
+
name: "미사용 Service 메서드"
|
|
506
|
+
severity: "medium"
|
|
507
|
+
category: "dead-code"
|
|
508
|
+
description: "Service 메서드가 다른 파일(Controller, 다른 Service)에서 호출되지 않습니다."
|
|
509
|
+
enabled: true
|
|
510
|
+
pattern:
|
|
511
|
+
type: "cross-file"
|
|
512
|
+
custom:
|
|
513
|
+
rule_id: "UNUSED-SERVICE-001"
|
|
514
|
+
fix: "사용하지 않는 Service 메서드를 제거하세요"
|
|
515
|
+
|
|
516
|
+
- id: "quality-unused-vo-001"
|
|
517
|
+
name: "미사용 VO/DTO 파일"
|
|
518
|
+
severity: "medium"
|
|
519
|
+
category: "dead-code"
|
|
520
|
+
description: "VO/DTO 클래스가 다른 파일에서 전혀 참조되지 않습니다."
|
|
521
|
+
enabled: true
|
|
522
|
+
pattern:
|
|
523
|
+
type: "cross-file"
|
|
524
|
+
custom:
|
|
525
|
+
rule_id: "UNUSED-VO-001"
|
|
526
|
+
fix: "사용하지 않는 VO/DTO 파일을 삭제하세요"
|
|
527
|
+
|
|
528
|
+
- id: "quality-unused-injection-001"
|
|
529
|
+
name: "미사용 DI 주입 필드"
|
|
530
|
+
severity: "medium"
|
|
531
|
+
category: "dead-code"
|
|
532
|
+
description: "@Autowired/@Resource로 주입된 필드가 클래스에서 사용되지 않습니다."
|
|
533
|
+
enabled: true
|
|
534
|
+
pattern:
|
|
535
|
+
type: "cross-file"
|
|
536
|
+
custom:
|
|
537
|
+
rule_id: "UNUSED-INJECTION-001"
|
|
538
|
+
fix: "사용하지 않는 주입 필드를 제거하세요"
|
|
539
|
+
|
|
540
|
+
- id: "quality-controller-direct-mapper-001"
|
|
541
|
+
name: "Controller→Mapper 직접 주입"
|
|
542
|
+
severity: "high"
|
|
543
|
+
category: "architecture"
|
|
544
|
+
description: "Controller에서 Mapper/Repository를 직접 주입하면 계층 구조(Controller→Service→Mapper) 원칙을 위반합니다."
|
|
545
|
+
enabled: true
|
|
546
|
+
pattern:
|
|
547
|
+
type: "cross-file"
|
|
548
|
+
custom:
|
|
549
|
+
rule_id: "CTRL-DIRECT-MAPPER-001"
|
|
550
|
+
fix: "Service 레이어를 통해 Mapper에 접근하세요"
|
|
551
|
+
|
|
552
|
+
- id: "quality-mapper-no-xml-file-001"
|
|
553
|
+
name: "Mapper 인터페이스에 대응 XML 파일 없음"
|
|
554
|
+
severity: "high"
|
|
555
|
+
category: "cross-file"
|
|
556
|
+
description: "Mapper 인터페이스에 대응하는 MyBatis XML 파일이 존재하지 않습니다."
|
|
557
|
+
enabled: true
|
|
558
|
+
pattern:
|
|
559
|
+
type: "cross-file"
|
|
560
|
+
custom:
|
|
561
|
+
rule_id: "MAPPER-NO-XML-FILE-001"
|
|
562
|
+
fix: "MyBatis XML 파일을 생성하고 namespace를 Mapper FQN과 일치시키세요"
|
|
563
|
+
|
|
564
|
+
- id: "quality-service-circular-001"
|
|
565
|
+
name: "Service 순환 의존성"
|
|
566
|
+
severity: "critical"
|
|
567
|
+
category: "architecture"
|
|
568
|
+
description: "Service 간 순환 의존성이 감지되었습니다. 초기화 실패나 유지보수 어려움을 초래합니다."
|
|
569
|
+
enabled: true
|
|
570
|
+
pattern:
|
|
571
|
+
type: "cross-file"
|
|
572
|
+
custom:
|
|
573
|
+
rule_id: "SERVICE-CIRCULAR-001"
|
|
574
|
+
fix: "의존성 역전(DIP), 이벤트 기반 통신, 또는 공통 서비스 추출로 순환을 제거하세요"
|
|
575
|
+
|
|
576
|
+
- language: xml
|
|
577
|
+
rules:
|
|
578
|
+
# ==================== MyBatis 크로스파일 분석 (추가) ====================
|
|
579
|
+
- id: "quality-mybatis-ns-mismatch-001"
|
|
580
|
+
name: "MyBatis XML namespace 불일치"
|
|
581
|
+
severity: "high"
|
|
582
|
+
category: "cross-file"
|
|
583
|
+
description: "MyBatis XML의 namespace에 대응하는 Java Mapper 클래스가 존재하지 않습니다."
|
|
584
|
+
enabled: true
|
|
585
|
+
pattern:
|
|
586
|
+
type: "cross-file"
|
|
587
|
+
custom:
|
|
588
|
+
rule_id: "MYBATIS-NS-MISMATCH-001"
|
|
589
|
+
fix: "XML namespace를 실제 Java Mapper 인터페이스의 FQN과 일치시키세요"
|
|
590
|
+
|
|
591
|
+
- id: "quality-duplicate-sql-id-001"
|
|
592
|
+
name: "중복 MyBatis SQL ID"
|
|
593
|
+
severity: "medium"
|
|
594
|
+
category: "cross-file"
|
|
595
|
+
description: "동일한 SQL ID가 여러 MyBatis XML에서 중복 사용됩니다."
|
|
596
|
+
enabled: true
|
|
597
|
+
pattern:
|
|
598
|
+
type: "cross-file"
|
|
599
|
+
custom:
|
|
600
|
+
rule_id: "DUPLICATE-SQL-ID-001"
|
|
601
|
+
fix: "SQL ID를 고유하게 변경하거나 namespace.id 형식으로 참조하세요"
|
|
602
|
+
|
|
603
|
+
# ==================== 크로스파일 규칙 (SuperClass/Interfaces 활용) ====================
|
|
604
|
+
|
|
605
|
+
- id: "quality-service-no-interface-001"
|
|
606
|
+
name: "ServiceImpl이 Service 인터페이스를 미구현"
|
|
607
|
+
severity: "medium"
|
|
608
|
+
category: "architecture"
|
|
609
|
+
description: "ServiceImpl 클래스가 Service 인터페이스를 구현하지 않습니다. 인터페이스 기반 설계 원칙 위반입니다."
|
|
610
|
+
enabled: true
|
|
611
|
+
pattern:
|
|
612
|
+
type: "cross-file"
|
|
613
|
+
custom:
|
|
614
|
+
fix: "Service 인터페이스를 생성하고 implements 절을 추가하세요"
|
|
615
|
+
|
|
616
|
+
- id: "quality-deep-inheritance-001"
|
|
617
|
+
name: "깊은 상속 체인 감지"
|
|
618
|
+
severity: "medium"
|
|
619
|
+
category: "architecture"
|
|
620
|
+
description: "상속 깊이가 3단계 이상입니다. 깊은 상속은 결합도를 높이고 유지보수를 어렵게 합니다."
|
|
621
|
+
enabled: true
|
|
622
|
+
pattern:
|
|
623
|
+
type: "cross-file"
|
|
624
|
+
custom:
|
|
625
|
+
max_depth: 3
|
|
626
|
+
fix: "상속 대신 컴포지션(Composition) 패턴을 사용하세요"
|
|
627
|
+
|
|
628
|
+
- id: "quality-vo-extends-001"
|
|
629
|
+
name: "VO/DTO 상속 감지"
|
|
630
|
+
severity: "low"
|
|
631
|
+
category: "architecture"
|
|
632
|
+
description: "VO/DTO 클래스가 다른 VO/DTO를 상속합니다. 데이터 결합도가 높아집니다."
|
|
633
|
+
enabled: true
|
|
634
|
+
pattern:
|
|
635
|
+
type: "cross-file"
|
|
636
|
+
custom:
|
|
637
|
+
fix: "컴포지션(필드로 포함)을 사용하거나 공통 필드를 각 VO에 직접 선언하세요"
|
|
638
|
+
|
|
639
|
+
- language: javascript
|
|
640
|
+
rules:
|
|
641
|
+
- id: "quality-js-console"
|
|
642
|
+
name: "console.log 사용"
|
|
643
|
+
severity: "medium"
|
|
644
|
+
category: "logging"
|
|
645
|
+
description: "프로덕션 코드에서 console.log 사용은 권장되지 않습니다"
|
|
646
|
+
enabled: true
|
|
647
|
+
pattern:
|
|
648
|
+
type: "regex"
|
|
649
|
+
regex: "console\\.(log|warn|error|info|debug)"
|
|
650
|
+
custom:
|
|
651
|
+
rule_id: "LG-001-JS"
|
|
652
|
+
|
|
653
|
+
- id: "quality-js-var"
|
|
654
|
+
name: "var 키워드 사용"
|
|
655
|
+
severity: "medium"
|
|
656
|
+
category: "style"
|
|
657
|
+
description: "var 대신 let 또는 const를 사용하세요"
|
|
658
|
+
enabled: true
|
|
659
|
+
pattern:
|
|
660
|
+
type: "regex"
|
|
661
|
+
regex: "\\bvar\\s+\\w+"
|
|
662
|
+
custom:
|
|
663
|
+
rule_id: "JS-001"
|
|
664
|
+
|
|
665
|
+
- id: "quality-js-equality"
|
|
666
|
+
name: "동등 연산자(==) 사용"
|
|
667
|
+
severity: "medium"
|
|
668
|
+
category: "style"
|
|
669
|
+
description: "== 대신 === (일치 연산자)를 사용하세요"
|
|
670
|
+
enabled: true
|
|
671
|
+
pattern:
|
|
672
|
+
type: "regex"
|
|
673
|
+
regex: "[^!=]==[^=]"
|
|
674
|
+
custom:
|
|
675
|
+
rule_id: "JS-002"
|
|
676
|
+
|
|
677
|
+
- id: "quality-js-fn-001"
|
|
678
|
+
name: "번호 붙은 함수명 (복사-붙여넣기 의심)"
|
|
679
|
+
severity: "medium"
|
|
680
|
+
category: "code-smell"
|
|
681
|
+
description: "함수명에 번호가 붙어있습니다 (fn_save2, fn_list3). 복사-붙여넣기로 생성된 중복 함수일 수 있습니다."
|
|
682
|
+
enabled: true
|
|
683
|
+
pattern:
|
|
684
|
+
type: "regex"
|
|
685
|
+
regex: "function\\s+\\w+\\d+\\s*\\("
|
|
686
|
+
custom:
|
|
687
|
+
rule_id: "JS-FN-001"
|
|
688
|
+
fix: "중복 함수를 파라미터화하여 하나로 통합하세요"
|
|
689
|
+
|
|
690
|
+
- id: "quality-js-callback-001"
|
|
691
|
+
name: "콜백 중첩 과다 (Callback Hell)"
|
|
692
|
+
severity: "medium"
|
|
693
|
+
category: "code-smell"
|
|
694
|
+
description: "콜백 함수가 4단계 이상 중첩되어 있습니다. Promise나 async/await로 리팩토링하세요."
|
|
695
|
+
enabled: true
|
|
696
|
+
pattern:
|
|
697
|
+
type: "regex-multiline"
|
|
698
|
+
regex: "function\\s*\\([^)]*\\)\\s*\\{[\\s\\S]{0,500}function\\s*\\([^)]*\\)\\s*\\{[\\s\\S]{0,500}function\\s*\\([^)]*\\)\\s*\\{[\\s\\S]{0,500}function\\s*\\("
|
|
699
|
+
custom:
|
|
700
|
+
rule_id: "JS-CALLBACK-001"
|
|
701
|
+
fix: "Promise 체이닝이나 async/await로 평탄화하세요"
|
|
702
|
+
|
|
703
|
+
- language: html
|
|
704
|
+
rules:
|
|
705
|
+
- id: "quality-html-inline-style"
|
|
706
|
+
name: "인라인 스타일 사용"
|
|
707
|
+
severity: "medium"
|
|
708
|
+
category: "style"
|
|
709
|
+
description: "인라인 style 속성 대신 CSS 파일을 사용하세요"
|
|
710
|
+
enabled: true
|
|
711
|
+
pattern:
|
|
712
|
+
type: "regex"
|
|
713
|
+
regex: "\\sstyle\\s*="
|
|
714
|
+
custom:
|
|
715
|
+
rule_id: "HTML-001"
|
|
716
|
+
|
|
717
|
+
- id: "quality-html-inline-script"
|
|
718
|
+
name: "인라인 스크립트 사용"
|
|
719
|
+
severity: "medium"
|
|
720
|
+
category: "style"
|
|
721
|
+
description: "인라인 스크립트 대신 외부 JS 파일을 사용하세요"
|
|
722
|
+
enabled: true
|
|
723
|
+
pattern:
|
|
724
|
+
type: "regex"
|
|
725
|
+
regex: "\\s(onclick|onchange|onsubmit|onload)\\s*="
|
|
726
|
+
custom:
|
|
727
|
+
rule_id: "HTML-002"
|
|
728
|
+
|
|
729
|
+
- language: css
|
|
730
|
+
rules:
|
|
731
|
+
- id: "quality-css-important"
|
|
732
|
+
name: "!important 남용"
|
|
733
|
+
severity: "medium"
|
|
734
|
+
category: "maintainability"
|
|
735
|
+
description: "!important의 과도한 사용은 유지보수성을 저하시킵니다"
|
|
736
|
+
enabled: true
|
|
737
|
+
pattern:
|
|
738
|
+
type: "regex"
|
|
739
|
+
regex: "!important"
|
|
740
|
+
custom:
|
|
741
|
+
rule_id: "CSS-001"
|
|
742
|
+
|
|
743
|
+
- language: properties
|
|
744
|
+
rules:
|
|
745
|
+
- id: "quality-prop-001"
|
|
746
|
+
name: "프로퍼티 형식 위반"
|
|
747
|
+
severity: "high"
|
|
748
|
+
category: "configuration"
|
|
749
|
+
description: "프로퍼티는 key=value 형식이어야 합니다"
|
|
750
|
+
enabled: false
|
|
751
|
+
pattern:
|
|
752
|
+
type: "regex"
|
|
753
|
+
regex: "^[^=#][^=]*[^=]$"
|
|
754
|
+
custom:
|
|
755
|
+
rule_id: "PROP-001"
|
|
756
|
+
|
|
757
|
+
# ==================== 리팩토링 권장 규칙 ====================
|
|
758
|
+
# 추가일: 2025-01-27
|
|
759
|
+
# 목적: Java Spring 비즈니스 로직 개발 시 코드 품질 향상
|
|
760
|
+
|
|
761
|
+
- language: java
|
|
762
|
+
rules:
|
|
763
|
+
# ==================== 성능 관련 규칙 ====================
|
|
764
|
+
- id: "quality-perf-001"
|
|
765
|
+
name: "String += 문자열 연결"
|
|
766
|
+
severity: "medium"
|
|
767
|
+
category: "performance"
|
|
768
|
+
description: "String += 연산은 루프에서 사용 시 성능 저하를 유발합니다. StringBuilder 사용을 권장합니다."
|
|
769
|
+
enabled: true
|
|
770
|
+
pattern:
|
|
771
|
+
type: "regex-multiline"
|
|
772
|
+
regex: "(for|while)\\s*\\([^)]*\\)\\s*\\{[^}]*\\w+\\s*\\+=\\s*\"[^\"]*\""
|
|
773
|
+
exclude:
|
|
774
|
+
- "StringBuilder"
|
|
775
|
+
- "StringBuffer"
|
|
776
|
+
custom:
|
|
777
|
+
rule_id: "PERF-001"
|
|
778
|
+
fix: "StringBuilder를 사용하세요"
|
|
779
|
+
example: "StringBuilder sb = new StringBuilder(); sb.append(str);"
|
|
780
|
+
|
|
781
|
+
# ==================== 멀티라인 성능 규칙 ====================
|
|
782
|
+
- id: "quality-perf-002"
|
|
783
|
+
name: "루프 내 컬렉션 객체 생성"
|
|
784
|
+
severity: "medium"
|
|
785
|
+
category: "performance"
|
|
786
|
+
description: "루프 내에서 ArrayList/HashMap 등 컬렉션을 생성하면 불필요한 객체 생성이 발생합니다."
|
|
787
|
+
enabled: true
|
|
788
|
+
pattern:
|
|
789
|
+
type: "regex-multiline"
|
|
790
|
+
regex: "(for|while)\\s*\\([^)]*\\)\\s*\\{[^}]*new\\s+(ArrayList|HashMap|HashSet|LinkedList|TreeMap|TreeSet)\\s*[<(]"
|
|
791
|
+
custom:
|
|
792
|
+
rule_id: "PERF-002"
|
|
793
|
+
fix: "루프 외부에서 컬렉션을 생성하고 clear()로 재사용하거나 Stream API 사용"
|
|
794
|
+
example: "List<Item> list = new ArrayList<>(); for(...) { list.clear(); ... }"
|
|
795
|
+
|
|
796
|
+
- id: "quality-perf-003"
|
|
797
|
+
name: "조회 메서드 @Transactional readOnly 누락"
|
|
798
|
+
severity: "medium"
|
|
799
|
+
category: "performance"
|
|
800
|
+
description: "조회 전용 메서드는 @Transactional(readOnly = true)를 사용하면 성능이 향상됩니다."
|
|
801
|
+
enabled: true
|
|
802
|
+
pattern:
|
|
803
|
+
type: "regex-multiline"
|
|
804
|
+
regex: "@Transactional\\s*\\((?![^)]*readOnly\\s*=\\s*true)[^)]*\\)\\s+(public|protected)[^{]*(find|get|select|search|inquiry|retrieve)[A-Z]\\w*\\s*\\("
|
|
805
|
+
custom:
|
|
806
|
+
rule_id: "PERF-003"
|
|
807
|
+
fix: "@Transactional(readOnly = true) 사용"
|
|
808
|
+
example: "@Transactional(readOnly = true) public User findById(Long id) { ... }"
|
|
809
|
+
|
|
810
|
+
# ==================== 설계/구조 관련 규칙 ====================
|
|
811
|
+
- id: "quality-di-001"
|
|
812
|
+
name: "@Autowired 필드 주입 사용"
|
|
813
|
+
severity: "medium"
|
|
814
|
+
category: "design"
|
|
815
|
+
description: "필드 주입보다 생성자 주입을 권장합니다. 테스트 용이성과 불변성이 향상됩니다."
|
|
816
|
+
enabled: false # spring-di-001과 중복 — spring 프로파일 규칙으로 통합
|
|
817
|
+
pattern:
|
|
818
|
+
type: "ast-annotation"
|
|
819
|
+
annotation: "^Autowired$"
|
|
820
|
+
custom:
|
|
821
|
+
rule_id: "DI-001"
|
|
822
|
+
target_element: "field"
|
|
823
|
+
fix: "생성자 주입으로 변경하세요"
|
|
824
|
+
example: "private final UserService userService; public MyClass(UserService userService) { this.userService = userService; }"
|
|
825
|
+
|
|
826
|
+
# ==================== 코드 품질 관련 규칙 ====================
|
|
827
|
+
- id: "quality-optional-001"
|
|
828
|
+
name: "Optional.get() 직접 호출"
|
|
829
|
+
severity: "high"
|
|
830
|
+
category: "code-quality"
|
|
831
|
+
description: "Optional.get()은 NoSuchElementException을 발생시킬 수 있습니다. orElse(), orElseThrow()를 사용하세요."
|
|
832
|
+
enabled: false
|
|
833
|
+
pattern:
|
|
834
|
+
type: "regex"
|
|
835
|
+
regex: "\\.get\\s*\\(\\s*\\)\\s*(?!\\.|;.*\\.)"
|
|
836
|
+
exclude:
|
|
837
|
+
- "request\\.get"
|
|
838
|
+
- "map\\.get"
|
|
839
|
+
- "list\\.get"
|
|
840
|
+
- "session\\.get"
|
|
841
|
+
custom:
|
|
842
|
+
rule_id: "OPT-001"
|
|
843
|
+
fix: "orElse(), orElseGet(), orElseThrow() 사용"
|
|
844
|
+
example: "optional.orElseThrow(() -> new BusinessException(\"Not found\"))"
|
|
845
|
+
|
|
846
|
+
- id: "quality-todo-001"
|
|
847
|
+
name: "TODO/FIXME 주석 잔존"
|
|
848
|
+
severity: "low"
|
|
849
|
+
category: "maintainability"
|
|
850
|
+
description: "TODO/FIXME 주석이 남아있습니다. 완료 후 제거하거나 이슈 트래커로 이동하세요."
|
|
851
|
+
enabled: true
|
|
852
|
+
pattern:
|
|
853
|
+
type: "regex"
|
|
854
|
+
regex: "//\\s*(TODO|FIXME|XXX|HACK)"
|
|
855
|
+
custom:
|
|
856
|
+
rule_id: "TODO-001"
|
|
857
|
+
fix: "작업 완료 후 주석 제거 또는 이슈 트래커 등록"
|
|
858
|
+
|
|
859
|
+
# ==================== 동시성 관련 규칙 ====================
|
|
860
|
+
- id: "quality-sync-001"
|
|
861
|
+
name: "synchronized 메서드 사용"
|
|
862
|
+
severity: "low"
|
|
863
|
+
category: "concurrency"
|
|
864
|
+
description: "synchronized 메서드보다 명시적 Lock 사용이 더 세밀한 제어가 가능합니다."
|
|
865
|
+
enabled: false
|
|
866
|
+
pattern:
|
|
867
|
+
type: "regex"
|
|
868
|
+
regex: "public\\s+synchronized\\s+\\w+\\s+\\w+\\s*\\("
|
|
869
|
+
custom:
|
|
870
|
+
rule_id: "SYNC-001"
|
|
871
|
+
fix: "ReentrantLock 또는 synchronized 블록 사용 검토"
|
|
872
|
+
example: "private final Lock lock = new ReentrantLock(); lock.lock(); try { ... } finally { lock.unlock(); }"
|
|
873
|
+
|
|
874
|
+
# ==================== 클래스/메서드 구조 규칙 (Stage 2) ====================
|
|
875
|
+
- id: "java-god-class"
|
|
876
|
+
name: "God Class (과대 클래스)"
|
|
877
|
+
severity: "medium"
|
|
878
|
+
category: "design"
|
|
879
|
+
description: "클래스가 너무 많은 메서드나 필드를 가지고 있습니다. 단일 책임 원칙(SRP)을 위반할 수 있습니다. VO/DTO/Mapper는 제외됩니다."
|
|
880
|
+
enabled: true
|
|
881
|
+
pattern:
|
|
882
|
+
type: "method-analysis"
|
|
883
|
+
conditions:
|
|
884
|
+
- "god-class-check"
|
|
885
|
+
custom:
|
|
886
|
+
rule_id: "DESIGN-001"
|
|
887
|
+
max_methods: "30"
|
|
888
|
+
max_fields: "25"
|
|
889
|
+
fix: "클래스를 더 작은 단위로 분리하세요"
|
|
890
|
+
|
|
891
|
+
- id: "java-parameter-count"
|
|
892
|
+
name: "파라미터 개수 초과"
|
|
893
|
+
severity: "medium"
|
|
894
|
+
category: "design"
|
|
895
|
+
description: "메서드 파라미터가 너무 많습니다. 파라미터 객체 패턴 사용을 고려하세요."
|
|
896
|
+
enabled: true
|
|
897
|
+
pattern:
|
|
898
|
+
type: "method-analysis"
|
|
899
|
+
conditions:
|
|
900
|
+
- "parameter-count-check"
|
|
901
|
+
custom:
|
|
902
|
+
rule_id: "DESIGN-002"
|
|
903
|
+
max_params: "5"
|
|
904
|
+
fix: "파라미터 객체(DTO)로 그룹화하거나 Builder 패턴 사용"
|
|
905
|
+
|
|
906
|
+
- id: "java-long-method"
|
|
907
|
+
name: "긴 메서드"
|
|
908
|
+
severity: "medium"
|
|
909
|
+
category: "maintainability"
|
|
910
|
+
description: "메서드가 너무 깁니다. 가독성과 유지보수성을 위해 분리하세요."
|
|
911
|
+
enabled: true
|
|
912
|
+
pattern:
|
|
913
|
+
type: "method-analysis"
|
|
914
|
+
conditions:
|
|
915
|
+
- "long-method-check"
|
|
916
|
+
custom:
|
|
917
|
+
rule_id: "DESIGN-003"
|
|
918
|
+
max_lines: "60"
|
|
919
|
+
fix: "메서드를 더 작은 단위로 분리하세요 (Extract Method 리팩토링)"
|
|
920
|
+
|
|
921
|
+
# ==================== 아키텍처/계층 구조 규칙 (Stage 3) ====================
|
|
922
|
+
- id: "java-controller-repository"
|
|
923
|
+
name: "Controller→Repository 직접 의존"
|
|
924
|
+
severity: "high"
|
|
925
|
+
category: "architecture"
|
|
926
|
+
description: "Controller에서 Repository/DAO를 직접 주입하면 계층 구조(Controller→Service→Repository) 원칙을 위반합니다."
|
|
927
|
+
enabled: true
|
|
928
|
+
pattern:
|
|
929
|
+
type: "method-analysis"
|
|
930
|
+
conditions:
|
|
931
|
+
- "controller-repository-dependency"
|
|
932
|
+
custom:
|
|
933
|
+
rule_id: "ARCH-001"
|
|
934
|
+
fix: "Service 레이어를 생성하고 비즈니스 로직을 Service로 이동하세요"
|
|
935
|
+
|
|
936
|
+
# ==================== Stream API 안티패턴 규칙 (Stage 3) ====================
|
|
937
|
+
- id: "quality-stream-001"
|
|
938
|
+
name: "불필요한 stream().forEach()"
|
|
939
|
+
severity: "low"
|
|
940
|
+
category: "performance"
|
|
941
|
+
description: "Collection에 대해 .stream().forEach()는 .forEach()로 간소화할 수 있습니다."
|
|
942
|
+
enabled: true
|
|
943
|
+
pattern:
|
|
944
|
+
type: "ast-filtered-regex"
|
|
945
|
+
regex: "\\.stream\\(\\)\\.forEach\\("
|
|
946
|
+
custom:
|
|
947
|
+
rule_id: "STREAM-001"
|
|
948
|
+
fix: "collection.forEach()를 직접 사용하세요"
|
|
949
|
+
|
|
950
|
+
- id: "quality-stream-002"
|
|
951
|
+
name: "Stream collect 후 재stream"
|
|
952
|
+
severity: "medium"
|
|
953
|
+
category: "performance"
|
|
954
|
+
description: "collect() 후 다시 stream()하는 것은 불필요한 중간 컬렉션을 생성합니다."
|
|
955
|
+
enabled: true
|
|
956
|
+
pattern:
|
|
957
|
+
type: "regex-multiline"
|
|
958
|
+
regex: "\\.collect\\([^)]*(?:\\([^)]*\\))*[^)]*\\)\\s*\\.stream\\(\\)"
|
|
959
|
+
custom:
|
|
960
|
+
rule_id: "STREAM-002"
|
|
961
|
+
fix: "중간 collect() 없이 stream 파이프라인을 연결하세요"
|
|
962
|
+
|
|
963
|
+
- id: "quality-stream-003"
|
|
964
|
+
name: "Stream에서 null 반환"
|
|
965
|
+
severity: "high"
|
|
966
|
+
category: "quality"
|
|
967
|
+
description: "Stream 연산에서 null을 반환하면 NullPointerException이 발생할 수 있습니다."
|
|
968
|
+
enabled: true
|
|
969
|
+
pattern:
|
|
970
|
+
type: "ast-filtered-regex"
|
|
971
|
+
regex: "\\.(map|flatMap)\\([^)]*->\\s*null\\s*\\)"
|
|
972
|
+
custom:
|
|
973
|
+
rule_id: "STREAM-003"
|
|
974
|
+
fix: "Optional 또는 Stream.empty()를 반환하세요"
|
|
975
|
+
|
|
976
|
+
# ==================== 아키텍처 규칙 (Stage 4) ====================
|
|
977
|
+
- id: "java-enhanced-layer-violation"
|
|
978
|
+
name: "계층 아키텍처 위반"
|
|
979
|
+
severity: "high"
|
|
980
|
+
category: "architecture"
|
|
981
|
+
description: "계층 구조(Controller→Service→Repository→Entity)를 위반하는 의존성이 감지되었습니다."
|
|
982
|
+
enabled: true
|
|
983
|
+
pattern:
|
|
984
|
+
type: "method-analysis"
|
|
985
|
+
conditions:
|
|
986
|
+
- "enhanced-layer-violation"
|
|
987
|
+
custom:
|
|
988
|
+
rule_id: "ARCH-002"
|
|
989
|
+
layers: "Controller→Service→Repository→Entity"
|
|
990
|
+
fix: "상위 계층만 하위 계층에 의존해야 합니다"
|
|
991
|
+
|
|
992
|
+
- id: "java-circular-dependency"
|
|
993
|
+
name: "순환 의존성 감지"
|
|
994
|
+
severity: "critical"
|
|
995
|
+
category: "architecture"
|
|
996
|
+
description: "순환 의존성(Circular Dependency)이 감지되었습니다. 애플리케이션 초기화 실패 또는 유지보수 어려움을 초래합니다."
|
|
997
|
+
enabled: true
|
|
998
|
+
pattern:
|
|
999
|
+
type: "method-analysis"
|
|
1000
|
+
conditions:
|
|
1001
|
+
- "circular-dependency"
|
|
1002
|
+
custom:
|
|
1003
|
+
rule_id: "ARCH-003"
|
|
1004
|
+
fix: "의존성 역전(DIP), 이벤트 기반 통신, 또는 공통 서비스 추출로 순환을 제거하세요"
|
|
1005
|
+
|
|
1006
|
+
# java-n-plus-one-query: 중복 제거 — Go 구현 버전(line 425)으로 통합
|
|
1007
|
+
# (이전: method-analysis 타입, PERF-004)
|
|
1008
|
+
|
|
1009
|
+
# ==================== 리팩토링 규칙 (sample-java 기반) ====================
|
|
1010
|
+
# 추가일: 2026-01-27
|
|
1011
|
+
# 목적: 전자정부 프레임워크 Spring 프로젝트에서 발견된 공통 코드 스멜 탐지
|
|
1012
|
+
|
|
1013
|
+
- id: "quality-str-001"
|
|
1014
|
+
name: "String == / != 비교"
|
|
1015
|
+
severity: "high"
|
|
1016
|
+
category: "code-quality"
|
|
1017
|
+
description: "Java에서 문자열을 ==, != 연산자로 비교하면 참조 비교가 되어 의도와 다른 결과가 발생합니다. .equals() 또는 Objects.equals()를 사용하세요."
|
|
1018
|
+
enabled: true
|
|
1019
|
+
pattern:
|
|
1020
|
+
type: "ast-filtered-regex"
|
|
1021
|
+
regex: "\\)\\s*[!=]=\\s*\"[^\"]*\""
|
|
1022
|
+
custom:
|
|
1023
|
+
rule_id: "STR-001"
|
|
1024
|
+
fix: ".equals() 또는 Objects.equals()를 사용하세요"
|
|
1025
|
+
example: "if (\"Y\".equals(str)) { ... }"
|
|
1026
|
+
|
|
1027
|
+
- id: "quality-deprecated-001"
|
|
1028
|
+
name: "SimpleDateFormat 사용"
|
|
1029
|
+
severity: "medium"
|
|
1030
|
+
category: "deprecated"
|
|
1031
|
+
description: "SimpleDateFormat은 thread-unsafe하고 Java 8 이후 권장되지 않습니다. DateTimeFormatter를 사용하세요."
|
|
1032
|
+
enabled: true
|
|
1033
|
+
pattern:
|
|
1034
|
+
type: "ast-filtered-regex"
|
|
1035
|
+
regex: "new\\s+SimpleDateFormat\\s*\\("
|
|
1036
|
+
custom:
|
|
1037
|
+
rule_id: "DEPR-001"
|
|
1038
|
+
fix: "java.time.format.DateTimeFormatter를 사용하세요"
|
|
1039
|
+
example: "DateTimeFormatter.ofPattern(\"yyyyMMddHHmmss\").format(LocalDateTime.now())"
|
|
1040
|
+
|
|
1041
|
+
- id: "quality-deprecated-003"
|
|
1042
|
+
name: "Hibernate @NotEmpty 사용"
|
|
1043
|
+
severity: "medium"
|
|
1044
|
+
category: "deprecated"
|
|
1045
|
+
description: "org.hibernate.validator.constraints.NotEmpty는 deprecated입니다. javax/jakarta.validation.constraints를 사용하세요."
|
|
1046
|
+
enabled: true
|
|
1047
|
+
pattern:
|
|
1048
|
+
type: "regex"
|
|
1049
|
+
regex: "import\\s+org\\.hibernate\\.validator\\.constraints\\.(NotEmpty|NotBlank)"
|
|
1050
|
+
custom:
|
|
1051
|
+
rule_id: "DEPR-003"
|
|
1052
|
+
fix: "javax.validation.constraints.NotEmpty 또는 jakarta.validation.constraints.NotEmpty를 사용하세요"
|
|
1053
|
+
|
|
1054
|
+
- id: "quality-refactor-001"
|
|
1055
|
+
name: "StringBuffer 사용"
|
|
1056
|
+
severity: "low"
|
|
1057
|
+
category: "performance"
|
|
1058
|
+
description: "StringBuffer는 synchronized로 인해 단일 스레드 환경에서 불필요한 성능 오버헤드가 있습니다. StringBuilder를 사용하세요."
|
|
1059
|
+
enabled: true
|
|
1060
|
+
pattern:
|
|
1061
|
+
type: "ast-filtered-regex"
|
|
1062
|
+
regex: "new\\s+StringBuffer\\s*\\("
|
|
1063
|
+
custom:
|
|
1064
|
+
rule_id: "REFACTOR-001"
|
|
1065
|
+
fix: "StringBuilder를 사용하세요"
|
|
1066
|
+
example: "StringBuilder sb = new StringBuilder();"
|
|
1067
|
+
|
|
1068
|
+
- id: "quality-refactor-002"
|
|
1069
|
+
name: "빈 else 블록"
|
|
1070
|
+
severity: "medium"
|
|
1071
|
+
category: "code-quality"
|
|
1072
|
+
description: "빈 else 블록은 불필요한 코드입니다. 제거하거나 주석으로 의도를 명시하세요."
|
|
1073
|
+
enabled: true
|
|
1074
|
+
pattern:
|
|
1075
|
+
type: "ast-filtered-regex"
|
|
1076
|
+
regex: "\\}\\s*else\\s*\\{\\s*\\}"
|
|
1077
|
+
custom:
|
|
1078
|
+
rule_id: "REFACTOR-002"
|
|
1079
|
+
fix: "빈 else 블록을 제거하세요"
|
|
1080
|
+
|
|
1081
|
+
- id: "quality-refactor-003"
|
|
1082
|
+
name: "DAO insert()로 update/delete 수행"
|
|
1083
|
+
severity: "high"
|
|
1084
|
+
category: "code-quality"
|
|
1085
|
+
description: "update/delete 메서드에서 insert()를 호출하면 시맨틱 불일치로 유지보수가 어렵습니다."
|
|
1086
|
+
enabled: true
|
|
1087
|
+
pattern:
|
|
1088
|
+
type: "regex-multiline"
|
|
1089
|
+
regex: "(update|delete|modify)\\w*\\([^)]*\\)\\s*(throws[^{]*)?\\{[^}]*\\binsert\\s*\\("
|
|
1090
|
+
custom:
|
|
1091
|
+
rule_id: "REFACTOR-003"
|
|
1092
|
+
fix: "update 메서드는 update(), delete 메서드는 delete()를 사용하세요"
|
|
1093
|
+
|
|
1094
|
+
- id: "quality-refactor-004"
|
|
1095
|
+
name: "하드코딩 업무코드"
|
|
1096
|
+
severity: "low"
|
|
1097
|
+
category: "maintainability"
|
|
1098
|
+
description: "업무코드(예: BZ1801, QA0401)가 하드코딩되어 있습니다. 상수(Enum/Constants)로 정의하세요."
|
|
1099
|
+
enabled: true
|
|
1100
|
+
pattern:
|
|
1101
|
+
type: "ast-filtered-regex"
|
|
1102
|
+
regex: "\"[A-Z]{2}\\d{4}\""
|
|
1103
|
+
custom:
|
|
1104
|
+
rule_id: "REFACTOR-004"
|
|
1105
|
+
fix: "업무코드를 상수 클래스(Enum 또는 Constants)로 정의하세요"
|
|
1106
|
+
example: "public enum BizCode { TEMP_SAVE(\"BZ1801\"), ... }"
|
|
1107
|
+
|
|
1108
|
+
# ==================== Map 남용 검출 ====================
|
|
1109
|
+
- id: "quality-map-param-001"
|
|
1110
|
+
name: "메서드 파라미터에 Map<String, Object> 사용"
|
|
1111
|
+
severity: "medium"
|
|
1112
|
+
category: "design"
|
|
1113
|
+
description: "메서드 파라미터로 Map<String, Object>를 사용하면 타입 안전성이 없고, 어떤 키가 필요한지 코드만으로 알 수 없습니다. VO/DTO 클래스를 정의하세요."
|
|
1114
|
+
enabled: true
|
|
1115
|
+
pattern:
|
|
1116
|
+
type: "regex"
|
|
1117
|
+
regex: "\\b(public|protected|private)\\s+\\S+\\s+\\w+\\s*\\(.*Map<String,\\s*Object>.*\\)"
|
|
1118
|
+
custom:
|
|
1119
|
+
rule_id: "MAP-001"
|
|
1120
|
+
fix: "Map<String, Object> 대신 전용 VO/DTO 클래스를 파라미터로 사용하세요"
|
|
1121
|
+
example: "public void updateUser(UserUpdateDto dto) { ... }"
|
|
1122
|
+
|
|
1123
|
+
- id: "quality-map-return-001"
|
|
1124
|
+
name: "메서드 반환 타입에 Map<String, Object> 사용"
|
|
1125
|
+
severity: "medium"
|
|
1126
|
+
category: "design"
|
|
1127
|
+
description: "반환 타입으로 Map<String, Object>를 사용하면 호출자가 어떤 키가 들어있는지 알 수 없어 런타임 오류 위험이 있습니다. VO/DTO 클래스를 정의하세요."
|
|
1128
|
+
enabled: true
|
|
1129
|
+
pattern:
|
|
1130
|
+
type: "regex"
|
|
1131
|
+
regex: "\\b(public|protected|private)\\s+Map<String,\\s*Object>\\s+\\w+\\s*\\("
|
|
1132
|
+
custom:
|
|
1133
|
+
rule_id: "MAP-002"
|
|
1134
|
+
fix: "Map<String, Object> 대신 전용 VO/DTO 클래스를 반환 타입으로 사용하세요"
|
|
1135
|
+
example: "public UserInfoDto selectUserInfo(Long userId) { ... }"
|
|
1136
|
+
|
|
1137
|
+
# ==================== AST 쿼리 선언적 규칙 ====================
|
|
1138
|
+
# 추가일: 2026-02-14
|
|
1139
|
+
# 목적: YAML만으로 AST 기반 검사 규칙 추가 (ast-method-call, ast-annotation, ast-import, ast-variable, ast-try-catch, ast-class)
|
|
1140
|
+
|
|
1141
|
+
# --- ast-method-call: 메서드 호출 패턴 ---
|
|
1142
|
+
|
|
1143
|
+
# quality-ast-001: 중복 제거 — java-n-plus-one-query(Go 구현)로 통합
|
|
1144
|
+
# (이전: ast-method-call 타입, inside-loop context)
|
|
1145
|
+
|
|
1146
|
+
- id: "quality-ast-002"
|
|
1147
|
+
name: "루프 내 DB 변경 호출"
|
|
1148
|
+
severity: "high"
|
|
1149
|
+
category: "performance"
|
|
1150
|
+
description: "루프 안에서 INSERT/UPDATE/DELETE를 개별 호출하면 성능이 저하됩니다. 배치 처리를 사용하세요."
|
|
1151
|
+
enabled: true
|
|
1152
|
+
pattern:
|
|
1153
|
+
type: "ast-method-call"
|
|
1154
|
+
method: "^(insert|update|delete|save|merge).*"
|
|
1155
|
+
qualifier: ".*(?i)(dao|repository|mapper)"
|
|
1156
|
+
context: "inside-loop"
|
|
1157
|
+
custom:
|
|
1158
|
+
fix: "Batch Insert/Update 또는 addBatch()/executeBatch()를 사용하세요"
|
|
1159
|
+
|
|
1160
|
+
- id: "quality-ast-003"
|
|
1161
|
+
name: "루프 내 원격 API 호출"
|
|
1162
|
+
severity: "high"
|
|
1163
|
+
category: "performance"
|
|
1164
|
+
description: "루프 안에서 외부 API를 호출하면 응답시간이 누적됩니다. 병렬 처리나 벌크 API를 사용하세요."
|
|
1165
|
+
enabled: true
|
|
1166
|
+
pattern:
|
|
1167
|
+
type: "ast-method-call"
|
|
1168
|
+
method: "^(exchange|execute|getForObject|getForEntity|postForObject|postForEntity|send|call).*"
|
|
1169
|
+
qualifier: ".*(?i)(restTemplate|webClient|httpClient|apiUtil|feignClient)"
|
|
1170
|
+
context: "inside-loop"
|
|
1171
|
+
custom:
|
|
1172
|
+
fix: "벌크 API 호출 또는 CompletableFuture 병렬 처리를 사용하세요"
|
|
1173
|
+
|
|
1174
|
+
# --- ast-import: import 패턴 ---
|
|
1175
|
+
|
|
1176
|
+
- id: "quality-ast-004"
|
|
1177
|
+
name: "java.util.Date import"
|
|
1178
|
+
severity: "medium"
|
|
1179
|
+
category: "deprecated"
|
|
1180
|
+
description: "java.util.Date는 thread-unsafe하고 설계 결함이 있습니다. Java 8+ java.time API를 사용하세요."
|
|
1181
|
+
enabled: true
|
|
1182
|
+
pattern:
|
|
1183
|
+
type: "ast-import"
|
|
1184
|
+
import: "^java\\.util\\.Date$"
|
|
1185
|
+
custom:
|
|
1186
|
+
fix: "java.time.LocalDate, LocalDateTime, ZonedDateTime 등을 사용하세요"
|
|
1187
|
+
|
|
1188
|
+
- id: "quality-ast-005"
|
|
1189
|
+
name: "commons-lang 구버전 import"
|
|
1190
|
+
severity: "medium"
|
|
1191
|
+
category: "deprecated"
|
|
1192
|
+
description: "org.apache.commons.lang 패키지는 EOL입니다. commons-lang3(org.apache.commons.lang3)를 사용하세요."
|
|
1193
|
+
enabled: true
|
|
1194
|
+
pattern:
|
|
1195
|
+
type: "ast-import"
|
|
1196
|
+
import: "^org\\.apache\\.commons\\.lang\\."
|
|
1197
|
+
custom:
|
|
1198
|
+
fix: "org.apache.commons.lang3 패키지로 마이그레이션하세요"
|
|
1199
|
+
|
|
1200
|
+
- id: "quality-ast-006"
|
|
1201
|
+
name: "sun.* 내부 API import"
|
|
1202
|
+
severity: "high"
|
|
1203
|
+
category: "deprecated"
|
|
1204
|
+
description: "sun.* 패키지는 JDK 내부 API로 호환성이 보장되지 않습니다."
|
|
1205
|
+
enabled: true
|
|
1206
|
+
pattern:
|
|
1207
|
+
type: "ast-import"
|
|
1208
|
+
import: "^sun\\."
|
|
1209
|
+
custom:
|
|
1210
|
+
fix: "공식 Java API 또는 표준 라이브러리를 사용하세요"
|
|
1211
|
+
|
|
1212
|
+
# --- ast-try-catch: 예외 처리 패턴 ---
|
|
1213
|
+
|
|
1214
|
+
- id: "quality-ast-007"
|
|
1215
|
+
name: "빈 catch 블록 (AST)"
|
|
1216
|
+
severity: "critical"
|
|
1217
|
+
category: "exception"
|
|
1218
|
+
description: "catch 블록이 비어있으면 예외가 무시됩니다. 최소한 로깅을 추가하세요."
|
|
1219
|
+
enabled: true
|
|
1220
|
+
pattern:
|
|
1221
|
+
type: "ast-try-catch"
|
|
1222
|
+
custom:
|
|
1223
|
+
catch_is_empty: true
|
|
1224
|
+
fix: "catch 블록에 logger.error(\"message\", e)를 추가하세요"
|
|
1225
|
+
|
|
1226
|
+
- id: "quality-ast-008"
|
|
1227
|
+
name: "IOException/SQLException try-with-resources 미사용"
|
|
1228
|
+
severity: "medium"
|
|
1229
|
+
category: "resource"
|
|
1230
|
+
description: "IO/DB 관련 예외를 처리할 때 try-with-resources를 사용하면 리소스 누수를 방지할 수 있습니다."
|
|
1231
|
+
enabled: true
|
|
1232
|
+
pattern:
|
|
1233
|
+
type: "ast-try-catch"
|
|
1234
|
+
exception_type: "IOException|SQLException|FileNotFoundException"
|
|
1235
|
+
custom:
|
|
1236
|
+
has_resources: false
|
|
1237
|
+
has_finally: false
|
|
1238
|
+
fix: "try-with-resources 구문을 사용하세요: try (Resource r = new Resource()) { ... }"
|
|
1239
|
+
|
|
1240
|
+
# --- ast-class: 클래스 구조 패턴 ---
|
|
1241
|
+
|
|
1242
|
+
- id: "quality-ast-009"
|
|
1243
|
+
name: "ServiceImpl 과대 클래스"
|
|
1244
|
+
severity: "medium"
|
|
1245
|
+
category: "design"
|
|
1246
|
+
description: "ServiceImpl 클래스의 메서드가 너무 많습니다. 책임을 분리하세요."
|
|
1247
|
+
enabled: true
|
|
1248
|
+
pattern:
|
|
1249
|
+
type: "ast-class"
|
|
1250
|
+
name_pattern: ".*ServiceImpl$"
|
|
1251
|
+
custom:
|
|
1252
|
+
max_methods: 20
|
|
1253
|
+
fix: "도메인별로 Service 클래스를 분리하세요"
|
|
1254
|
+
|
|
1255
|
+
- id: "quality-ast-010"
|
|
1256
|
+
name: "Controller 과대 클래스"
|
|
1257
|
+
severity: "medium"
|
|
1258
|
+
category: "design"
|
|
1259
|
+
description: "Controller 클래스의 메서드가 너무 많습니다. 기능별로 분리하세요."
|
|
1260
|
+
enabled: true
|
|
1261
|
+
pattern:
|
|
1262
|
+
type: "ast-class"
|
|
1263
|
+
name_pattern: ".*Controller$"
|
|
1264
|
+
custom:
|
|
1265
|
+
max_methods: 15
|
|
1266
|
+
fix: "기능 단위로 Controller를 분리하세요 (예: UserController, OrderController)"
|
|
1267
|
+
|
|
1268
|
+
# --- ast-annotation: 어노테이션 패턴 ---
|
|
1269
|
+
|
|
1270
|
+
- id: "quality-ast-011"
|
|
1271
|
+
name: "@RequestMapping method 속성 누락"
|
|
1272
|
+
severity: "medium"
|
|
1273
|
+
category: "design"
|
|
1274
|
+
description: "@RequestMapping에 method 속성이 없으면 모든 HTTP 메서드를 허용합니다. @GetMapping/@PostMapping 등 전용 어노테이션을 사용하세요."
|
|
1275
|
+
enabled: true
|
|
1276
|
+
pattern:
|
|
1277
|
+
type: "ast-annotation"
|
|
1278
|
+
annotation: "^RequestMapping$"
|
|
1279
|
+
missing_attr_name: "method"
|
|
1280
|
+
context: "method-name:.*"
|
|
1281
|
+
custom:
|
|
1282
|
+
fix: "@GetMapping, @PostMapping, @PutMapping, @DeleteMapping 전용 어노테이션을 사용하세요"
|
|
1283
|
+
|
|
1284
|
+
# ==================== 추가 품질 규칙 (2026-02-14) ====================
|
|
1285
|
+
|
|
1286
|
+
- id: "quality-legacy-001"
|
|
1287
|
+
name: "Vector 사용"
|
|
1288
|
+
severity: "medium"
|
|
1289
|
+
category: "deprecated"
|
|
1290
|
+
description: "Vector는 synchronized로 인해 단일 스레드 환경에서 불필요한 성능 오버헤드가 있습니다."
|
|
1291
|
+
enabled: true
|
|
1292
|
+
pattern:
|
|
1293
|
+
type: "ast-filtered-regex"
|
|
1294
|
+
regex: "new\\s+Vector\\s*[<(]"
|
|
1295
|
+
custom:
|
|
1296
|
+
rule_id: "LEGACY-001"
|
|
1297
|
+
fix: "ArrayList(단일스레드) 또는 CopyOnWriteArrayList(멀티스레드)를 사용하세요"
|
|
1298
|
+
|
|
1299
|
+
- id: "quality-legacy-002"
|
|
1300
|
+
name: "Hashtable 사용"
|
|
1301
|
+
severity: "medium"
|
|
1302
|
+
category: "deprecated"
|
|
1303
|
+
description: "Hashtable은 synchronized로 인해 불필요한 오버헤드가 있고 null 키/값을 허용하지 않습니다."
|
|
1304
|
+
enabled: true
|
|
1305
|
+
pattern:
|
|
1306
|
+
type: "ast-filtered-regex"
|
|
1307
|
+
regex: "new\\s+Hashtable\\s*[<(]"
|
|
1308
|
+
custom:
|
|
1309
|
+
rule_id: "LEGACY-002"
|
|
1310
|
+
fix: "HashMap(단일스레드) 또는 ConcurrentHashMap(멀티스레드)를 사용하세요"
|
|
1311
|
+
|
|
1312
|
+
- id: "quality-legacy-003"
|
|
1313
|
+
name: "StringTokenizer 사용"
|
|
1314
|
+
severity: "low"
|
|
1315
|
+
category: "deprecated"
|
|
1316
|
+
description: "StringTokenizer는 레거시 클래스입니다. String.split() 또는 Pattern을 사용하세요."
|
|
1317
|
+
enabled: true
|
|
1318
|
+
pattern:
|
|
1319
|
+
type: "ast-filtered-regex"
|
|
1320
|
+
regex: "new\\s+StringTokenizer\\s*\\("
|
|
1321
|
+
custom:
|
|
1322
|
+
rule_id: "LEGACY-003"
|
|
1323
|
+
fix: "String.split() 또는 java.util.regex.Pattern을 사용하세요"
|
|
1324
|
+
|
|
1325
|
+
- id: "quality-legacy-004"
|
|
1326
|
+
name: "HSSFWorkbook 사용 (Excel 97-2003)"
|
|
1327
|
+
severity: "medium"
|
|
1328
|
+
category: "deprecated"
|
|
1329
|
+
description: "HSSFWorkbook은 .xls(97-2003) 전용으로 65,536행 제한이 있습니다."
|
|
1330
|
+
enabled: true
|
|
1331
|
+
pattern:
|
|
1332
|
+
type: "ast-filtered-regex"
|
|
1333
|
+
regex: "new\\s+HSSFWorkbook\\s*\\("
|
|
1334
|
+
custom:
|
|
1335
|
+
rule_id: "LEGACY-004"
|
|
1336
|
+
fix: "SXSSFWorkbook(대용량 스트리밍) 또는 XSSFWorkbook(.xlsx)을 사용하세요"
|
|
1337
|
+
|
|
1338
|
+
- id: "quality-legacy-005"
|
|
1339
|
+
name: "Enumeration 인터페이스 사용"
|
|
1340
|
+
severity: "low"
|
|
1341
|
+
category: "deprecated"
|
|
1342
|
+
description: "Enumeration은 레거시 인터페이스입니다. Iterator 또는 for-each를 사용하세요."
|
|
1343
|
+
enabled: true
|
|
1344
|
+
pattern:
|
|
1345
|
+
type: "ast-filtered-regex"
|
|
1346
|
+
regex: "\\bEnumeration\\s*<"
|
|
1347
|
+
custom:
|
|
1348
|
+
rule_id: "LEGACY-005"
|
|
1349
|
+
fix: "Iterator, for-each, 또는 Stream API를 사용하세요"
|
|
1350
|
+
|
|
1351
|
+
- id: "quality-legacy-006"
|
|
1352
|
+
name: "log4j 1.x 직접 사용"
|
|
1353
|
+
severity: "high"
|
|
1354
|
+
category: "deprecated"
|
|
1355
|
+
description: "Apache Log4j 1.x는 EOL이며 보안 취약점이 있습니다. SLF4J + Logback을 사용하세요."
|
|
1356
|
+
enabled: true
|
|
1357
|
+
pattern:
|
|
1358
|
+
type: "ast-import"
|
|
1359
|
+
import: "^org\\.apache\\.log4j\\."
|
|
1360
|
+
custom:
|
|
1361
|
+
rule_id: "LEGACY-006"
|
|
1362
|
+
fix: "org.slf4j.Logger + Logback을 사용하세요"
|
|
1363
|
+
|
|
1364
|
+
- id: "quality-legacy-007"
|
|
1365
|
+
name: "BeanUtils.copyProperties() 사용"
|
|
1366
|
+
severity: "low"
|
|
1367
|
+
category: "performance"
|
|
1368
|
+
description: "BeanUtils.copyProperties()는 리플렉션 기반이라 성능이 낮고 타입 안전하지 않습니다."
|
|
1369
|
+
enabled: true
|
|
1370
|
+
pattern:
|
|
1371
|
+
type: "ast-filtered-regex"
|
|
1372
|
+
regex: "BeanUtils\\.copyProperties\\s*\\("
|
|
1373
|
+
custom:
|
|
1374
|
+
rule_id: "LEGACY-007"
|
|
1375
|
+
fix: "MapStruct(컴파일타임 매핑) 또는 명시적 setter 호출을 사용하세요"
|
|
1376
|
+
|
|
1377
|
+
- id: "quality-legacy-008"
|
|
1378
|
+
name: "static SimpleDateFormat 선언"
|
|
1379
|
+
severity: "high"
|
|
1380
|
+
category: "thread-safety"
|
|
1381
|
+
description: "SimpleDateFormat을 static 필드로 선언하면 멀티스레드 환경에서 파싱 오류가 발생합니다."
|
|
1382
|
+
enabled: true
|
|
1383
|
+
pattern:
|
|
1384
|
+
type: "ast-filtered-regex"
|
|
1385
|
+
regex: "static\\s+.*SimpleDateFormat\\s+\\w+"
|
|
1386
|
+
custom:
|
|
1387
|
+
rule_id: "LEGACY-008"
|
|
1388
|
+
fix: "DateTimeFormatter(thread-safe)를 사용하세요"
|
|
1389
|
+
|
|
1390
|
+
# ==================== 9. 메서드 구조 분석 (ast-method) ====================
|
|
1391
|
+
|
|
1392
|
+
- id: "quality-mth-001"
|
|
1393
|
+
name: "깊은 if-else 중첩 (Arrow Anti-Pattern)"
|
|
1394
|
+
severity: "high"
|
|
1395
|
+
category: "complexity"
|
|
1396
|
+
description: "if-else 중첩이 3단계를 초과합니다. Guard Clause나 early return으로 리팩토링하세요."
|
|
1397
|
+
enabled: true
|
|
1398
|
+
pattern:
|
|
1399
|
+
type: "ast-method"
|
|
1400
|
+
custom:
|
|
1401
|
+
max_if_depth: 3
|
|
1402
|
+
fix: "Guard Clause(early return) 패턴으로 중첩을 줄이세요"
|
|
1403
|
+
|
|
1404
|
+
- id: "quality-mth-002"
|
|
1405
|
+
name: "만능 if-else 분기 (과도한 조건 분기)"
|
|
1406
|
+
severity: "medium"
|
|
1407
|
+
category: "complexity"
|
|
1408
|
+
description: "if-else-if 분기가 5개를 초과합니다. 전략 패턴이나 Map 디스패치로 전환하세요."
|
|
1409
|
+
enabled: true
|
|
1410
|
+
pattern:
|
|
1411
|
+
type: "ast-method"
|
|
1412
|
+
custom:
|
|
1413
|
+
max_branches: 5
|
|
1414
|
+
fix: "전략 패턴(Strategy), Map 기반 디스패치, 또는 enum으로 전환하세요"
|
|
1415
|
+
|
|
1416
|
+
- id: "quality-mth-003"
|
|
1417
|
+
name: "만능 메서드 (Long Method)"
|
|
1418
|
+
severity: "high"
|
|
1419
|
+
category: "complexity"
|
|
1420
|
+
description: "메서드가 100라인을 초과합니다. 기능 단위로 분리하세요."
|
|
1421
|
+
enabled: true
|
|
1422
|
+
pattern:
|
|
1423
|
+
type: "ast-method"
|
|
1424
|
+
custom:
|
|
1425
|
+
max_lines: 100
|
|
1426
|
+
fix: "메서드를 조회/검증/처리/저장 등 기능 단위로 분리하세요"
|
|
1427
|
+
|
|
1428
|
+
- id: "quality-mth-004"
|
|
1429
|
+
name: "과도한 statement 수"
|
|
1430
|
+
severity: "medium"
|
|
1431
|
+
category: "complexity"
|
|
1432
|
+
description: "메서드 내 statement가 50개를 초과합니다."
|
|
1433
|
+
enabled: true
|
|
1434
|
+
pattern:
|
|
1435
|
+
type: "ast-method"
|
|
1436
|
+
custom:
|
|
1437
|
+
max_statements: 50
|
|
1438
|
+
fix: "private 메서드로 추출하여 가독성을 높이세요"
|
|
1439
|
+
|
|
1440
|
+
# ==================== 10. 레거시 코드 악취 (Legacy Code Smell) ====================
|
|
1441
|
+
|
|
1442
|
+
- id: "quality-magic-001"
|
|
1443
|
+
name: "매직 스트링 비교"
|
|
1444
|
+
severity: "medium"
|
|
1445
|
+
category: "code-smell"
|
|
1446
|
+
description: "숫자 문자열 리터럴을 직접 비교하고 있습니다. 상수를 정의하세요."
|
|
1447
|
+
enabled: true
|
|
1448
|
+
pattern:
|
|
1449
|
+
type: "ast-filtered-regex"
|
|
1450
|
+
regex: "\"[0-9]{1,3}\"\\s*\\.\\s*equals\\s*\\(|\\.equals\\s*\\(\\s*\"[0-9]{1,3}\"\\s*\\)"
|
|
1451
|
+
custom:
|
|
1452
|
+
rule_id: "MAGIC-001"
|
|
1453
|
+
fix: "static final 상수를 정의하거나 enum을 사용하세요"
|
|
1454
|
+
|
|
1455
|
+
- id: "quality-cmt-003"
|
|
1456
|
+
name: "주석 처리된 코드 블록"
|
|
1457
|
+
severity: "low"
|
|
1458
|
+
category: "code-smell"
|
|
1459
|
+
description: "3줄 이상 주석 처리된 코드가 있습니다. 불필요한 코드는 삭제하세요."
|
|
1460
|
+
enabled: true
|
|
1461
|
+
pattern:
|
|
1462
|
+
type: "regex-multiline"
|
|
1463
|
+
regex: "([ \\t]*//[^\\n]*[;{}][ \\t]*\\n){3,}"
|
|
1464
|
+
custom:
|
|
1465
|
+
rule_id: "CMT-003"
|
|
1466
|
+
fix: "VCS에 이력이 있으므로 주석 처리된 코드를 삭제하세요"
|
|
1467
|
+
|
|
1468
|
+
- id: "quality-ex-003"
|
|
1469
|
+
name: "거대 try 블록"
|
|
1470
|
+
severity: "medium"
|
|
1471
|
+
category: "code-smell"
|
|
1472
|
+
description: "try 블록이 50줄을 초과합니다. 예외 범위를 최소화하세요."
|
|
1473
|
+
enabled: true
|
|
1474
|
+
pattern:
|
|
1475
|
+
type: "ast-try-catch"
|
|
1476
|
+
custom:
|
|
1477
|
+
max_try_lines: 50
|
|
1478
|
+
fix: "try 범위를 최소화하고, 비즈니스 로직을 별도 메서드로 분리하세요"
|
|
1479
|
+
|
|
1480
|
+
- id: "quality-import-001"
|
|
1481
|
+
name: "사용하지 않는 import"
|
|
1482
|
+
severity: "low"
|
|
1483
|
+
category: "code-smell"
|
|
1484
|
+
description: "사용하지 않는 import가 있습니다. 코드 가독성을 위해 제거하세요."
|
|
1485
|
+
enabled: true
|
|
1486
|
+
pattern:
|
|
1487
|
+
type: "ast-import"
|
|
1488
|
+
custom:
|
|
1489
|
+
check_unused: true
|
|
1490
|
+
fix: "IDE의 Optimize Imports 기능으로 불필요한 import를 정리하세요"
|
|
1491
|
+
|
|
1492
|
+
- id: "quality-vo-001"
|
|
1493
|
+
name: "VO/DTO 필드 전부 String"
|
|
1494
|
+
severity: "medium"
|
|
1495
|
+
category: "code-smell"
|
|
1496
|
+
description: "VO/DTO 클래스의 모든 필드가 String입니다. 적절한 타입(int, long, LocalDate 등)을 사용하세요."
|
|
1497
|
+
enabled: true
|
|
1498
|
+
pattern:
|
|
1499
|
+
type: "ast-class"
|
|
1500
|
+
name_pattern: ".*(Vo|VO|Dto|DTO|RequestDto|ResponseDto)$"
|
|
1501
|
+
custom:
|
|
1502
|
+
all_fields_type: "String"
|
|
1503
|
+
fix: "숫자는 int/long, 날짜는 LocalDate/LocalDateTime, 불리언은 boolean으로 선언하세요"
|
|
1504
|
+
|
|
1505
|
+
# ==================== 11. 리팩토링 코드 스멜 ====================
|
|
1506
|
+
|
|
1507
|
+
- id: "quality-chain-001"
|
|
1508
|
+
name: "메서드 체인 과다 (Train Wreck)"
|
|
1509
|
+
severity: "medium"
|
|
1510
|
+
category: "code-smell"
|
|
1511
|
+
description: "메서드 체인이 4단계를 초과합니다. 디미터 법칙(Law of Demeter) 위반으로 결합도가 높아집니다."
|
|
1512
|
+
enabled: true
|
|
1513
|
+
pattern:
|
|
1514
|
+
type: "ast-filtered-regex"
|
|
1515
|
+
regex: "\\w+\\.\\w+\\([^)]*\\)\\.\\w+\\([^)]*\\)\\.\\w+\\([^)]*\\)\\.\\w+\\("
|
|
1516
|
+
exclude:
|
|
1517
|
+
- ".stream()"
|
|
1518
|
+
- ".builder()"
|
|
1519
|
+
- "StringBuilder"
|
|
1520
|
+
- "StringBuffer"
|
|
1521
|
+
- ".collect("
|
|
1522
|
+
- ".map("
|
|
1523
|
+
- ".filter("
|
|
1524
|
+
custom:
|
|
1525
|
+
rule_id: "CHAIN-001"
|
|
1526
|
+
fix: "중간 변수를 도입하거나, 위임 메서드를 만들어 체인을 줄이세요"
|
|
1527
|
+
|
|
1528
|
+
- id: "quality-instanceof-001"
|
|
1529
|
+
name: "instanceof 연쇄 (다형성 부재)"
|
|
1530
|
+
severity: "medium"
|
|
1531
|
+
category: "code-smell"
|
|
1532
|
+
description: "instanceof 검사가 3회 이상 연속됩니다. 다형성(Polymorphism)이나 Visitor 패턴을 검토하세요."
|
|
1533
|
+
enabled: true
|
|
1534
|
+
pattern:
|
|
1535
|
+
type: "regex-multiline"
|
|
1536
|
+
regex: "instanceof\\s+\\w+[\\s\\S]{0,300}instanceof\\s+\\w+[\\s\\S]{0,300}instanceof\\s+\\w+"
|
|
1537
|
+
custom:
|
|
1538
|
+
rule_id: "INSTANCEOF-001"
|
|
1539
|
+
fix: "다형성(Polymorphism), Visitor 패턴, 또는 Strategy 패턴으로 전환하세요"
|
|
1540
|
+
|
|
1541
|
+
- id: "quality-return-null-001"
|
|
1542
|
+
name: "return null 사용"
|
|
1543
|
+
severity: "low"
|
|
1544
|
+
category: "code-smell"
|
|
1545
|
+
description: "null을 반환하면 호출자에서 NPE 위험이 있습니다. Optional이나 빈 컬렉션을 반환하세요."
|
|
1546
|
+
enabled: true
|
|
1547
|
+
pattern:
|
|
1548
|
+
type: "ast-filtered-regex"
|
|
1549
|
+
regex: "return\\s+null\\s*;"
|
|
1550
|
+
custom:
|
|
1551
|
+
rule_id: "RETURN-NULL-001"
|
|
1552
|
+
fix: "Optional.empty(), Collections.emptyList(), 또는 빈 객체를 반환하세요"
|
|
1553
|
+
|
|
1554
|
+
- id: "quality-mutable-static-001"
|
|
1555
|
+
name: "가변 static 필드"
|
|
1556
|
+
severity: "medium"
|
|
1557
|
+
category: "thread-safety"
|
|
1558
|
+
description: "static 필드가 final이 아니면 멀티스레드 환경에서 데이터 경쟁이 발생합니다."
|
|
1559
|
+
enabled: true
|
|
1560
|
+
pattern:
|
|
1561
|
+
type: "ast-filtered-regex"
|
|
1562
|
+
regex: "(private|protected|public)\\s+static\\s+(?!final)\\w+[<\\[\\w>., ]*\\s+\\w+\\s*[=;]"
|
|
1563
|
+
exclude:
|
|
1564
|
+
- "Logger"
|
|
1565
|
+
- "LoggerFactory"
|
|
1566
|
+
- "log"
|
|
1567
|
+
custom:
|
|
1568
|
+
rule_id: "MUTABLE-STATIC-001"
|
|
1569
|
+
fix: "static final로 변경하거나, AtomicReference/ConcurrentHashMap 등 스레드 안전 타입을 사용하세요"
|
|
1570
|
+
|
|
1571
|
+
- id: "quality-cast-001"
|
|
1572
|
+
name: "Map.get() 타입 캐스팅"
|
|
1573
|
+
severity: "medium"
|
|
1574
|
+
category: "code-smell"
|
|
1575
|
+
description: "Map.get() 결과를 타입 캐스팅하면 런타임 ClassCastException 위험이 있습니다. VO/DTO를 사용하세요."
|
|
1576
|
+
enabled: true
|
|
1577
|
+
pattern:
|
|
1578
|
+
type: "ast-filtered-regex"
|
|
1579
|
+
regex: "\\(\\s*(String|Integer|Long|int|long|double|Double|Float|Boolean|Object)\\s*\\)\\s*\\w+\\.get\\s*\\("
|
|
1580
|
+
custom:
|
|
1581
|
+
rule_id: "CAST-001"
|
|
1582
|
+
fix: "Map<String, Object> 대신 VO/DTO를 사용하거나, 제네릭 Map<String, String>을 사용하세요"
|
|
1583
|
+
|
|
1584
|
+
- id: "quality-setter-chain-001"
|
|
1585
|
+
name: "연속 setter 호출 과다"
|
|
1586
|
+
severity: "low"
|
|
1587
|
+
category: "code-smell"
|
|
1588
|
+
description: "setter 호출이 10줄 이상 연속됩니다. Builder 패턴이나 MapStruct 사용을 고려하세요."
|
|
1589
|
+
enabled: true
|
|
1590
|
+
pattern:
|
|
1591
|
+
type: "regex-multiline"
|
|
1592
|
+
regex: "([ \\t]*\\w+\\.set[A-Z]\\w*\\s*\\([^;]*;\\s*\\n){10,}"
|
|
1593
|
+
custom:
|
|
1594
|
+
rule_id: "SETTER-CHAIN-001"
|
|
1595
|
+
fix: "Builder 패턴, MapStruct, 또는 ModelMapper를 사용하세요"
|
|
1596
|
+
|
|
1597
|
+
- id: "quality-rawtype-001"
|
|
1598
|
+
name: "Raw 타입 사용 (제네릭 누락)"
|
|
1599
|
+
severity: "medium"
|
|
1600
|
+
category: "code-smell"
|
|
1601
|
+
description: "제네릭이 없는 Raw 타입은 타입 안전성을 보장하지 못합니다."
|
|
1602
|
+
enabled: true
|
|
1603
|
+
pattern:
|
|
1604
|
+
type: "ast-filtered-regex"
|
|
1605
|
+
regex: "\\b(List|Map|Set|Collection|ArrayList|HashMap|HashSet|LinkedList)\\s+\\w+\\s*[=;]"
|
|
1606
|
+
custom:
|
|
1607
|
+
rule_id: "RAWTYPE-001"
|
|
1608
|
+
fix: "제네릭을 명시하세요: List<String>, Map<String, Object> 등"
|
|
1609
|
+
|
|
1610
|
+
# ==================== 12. AST 구조 분석 (Phase 2) ====================
|
|
1611
|
+
|
|
1612
|
+
- id: "quality-overload-001"
|
|
1613
|
+
name: "메서드 오버로딩 과다"
|
|
1614
|
+
severity: "medium"
|
|
1615
|
+
category: "design"
|
|
1616
|
+
description: "동일 이름의 메서드가 4개를 초과합니다. 메서드를 분리하거나 파라미터 객체를 사용하세요."
|
|
1617
|
+
enabled: true
|
|
1618
|
+
pattern:
|
|
1619
|
+
type: "ast-class"
|
|
1620
|
+
custom:
|
|
1621
|
+
max_overloads: 4
|
|
1622
|
+
rule_id: "OVERLOAD-001"
|
|
1623
|
+
fix: "메서드를 분리하거나 Builder/파라미터 객체 패턴을 사용하세요"
|
|
1624
|
+
|
|
1625
|
+
- id: "quality-inner-class-001"
|
|
1626
|
+
name: "내부 클래스 과다"
|
|
1627
|
+
severity: "medium"
|
|
1628
|
+
category: "design"
|
|
1629
|
+
description: "내부 클래스가 3개를 초과합니다. 별도 파일로 분리하세요."
|
|
1630
|
+
enabled: true
|
|
1631
|
+
pattern:
|
|
1632
|
+
type: "ast-class"
|
|
1633
|
+
custom:
|
|
1634
|
+
max_inner_classes: 3
|
|
1635
|
+
rule_id: "INNER-CLASS-001"
|
|
1636
|
+
fix: "내부 클래스를 별도 파일로 분리하세요"
|
|
1637
|
+
|
|
1638
|
+
- id: "quality-import-excess-001"
|
|
1639
|
+
name: "과도한 import (클래스 책임 과다 징후)"
|
|
1640
|
+
severity: "low"
|
|
1641
|
+
category: "design"
|
|
1642
|
+
description: "import가 30개를 초과합니다. 클래스가 너무 많은 책임을 가지고 있을 수 있습니다."
|
|
1643
|
+
enabled: true
|
|
1644
|
+
pattern:
|
|
1645
|
+
type: "ast-import"
|
|
1646
|
+
custom:
|
|
1647
|
+
max_imports: 30
|
|
1648
|
+
rule_id: "IMPORT-EXCESS-001"
|
|
1649
|
+
fix: "클래스를 기능별로 분리하여 import 수를 줄이세요"
|
|
1650
|
+
|
|
1651
|
+
# ==================== 13. Dead Code 감지 (Phase 3) ====================
|
|
1652
|
+
|
|
1653
|
+
- id: "quality-dead-method-001"
|
|
1654
|
+
name: "미사용 private 메서드"
|
|
1655
|
+
severity: "medium"
|
|
1656
|
+
category: "dead-code"
|
|
1657
|
+
description: "private 메서드가 같은 파일 내에서 호출되지 않습니다. 불필요한 코드를 제거하세요."
|
|
1658
|
+
enabled: true
|
|
1659
|
+
pattern:
|
|
1660
|
+
type: "ast-dead-code"
|
|
1661
|
+
custom:
|
|
1662
|
+
check_type: "private-method"
|
|
1663
|
+
rule_id: "DEAD-METHOD-001"
|
|
1664
|
+
fix: "사용하지 않는 private 메서드를 삭제하세요"
|
|
1665
|
+
|
|
1666
|
+
- id: "quality-dead-field-001"
|
|
1667
|
+
name: "미사용 private 필드"
|
|
1668
|
+
severity: "medium"
|
|
1669
|
+
category: "dead-code"
|
|
1670
|
+
description: "private 필드가 선언 외에 사용되지 않습니다. 불필요한 코드를 제거하세요."
|
|
1671
|
+
enabled: true
|
|
1672
|
+
pattern:
|
|
1673
|
+
type: "ast-dead-code"
|
|
1674
|
+
custom:
|
|
1675
|
+
check_type: "private-field"
|
|
1676
|
+
rule_id: "DEAD-FIELD-001"
|
|
1677
|
+
fix: "사용하지 않는 private 필드를 삭제하세요"
|