auto-coder 0.1.380__py3-none-any.whl → 0.1.382__py3-none-any.whl
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.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.380.dist-info → auto_coder-0.1.382.dist-info}/METADATA +1 -1
- {auto_coder-0.1.380.dist-info → auto_coder-0.1.382.dist-info}/RECORD +17 -14
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +2 -3
- autocoder/common/command_completer_v2.py +14 -14
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +48 -8
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +11 -3
- autocoder/linters/python_linter.py +2 -22
- autocoder/linters/reactjs_linter.py +8 -1
- autocoder/linters/test_python_linter.py +166 -0
- autocoder/linters/test_reactjs_linter.py +183 -0
- autocoder/linters/test_vue_linter.py +217 -0
- autocoder/linters/vue_linter.py +9 -1
- autocoder/version.py +1 -1
- {auto_coder-0.1.380.dist-info → auto_coder-0.1.382.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.380.dist-info → auto_coder-0.1.382.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.380.dist-info → auto_coder-0.1.382.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.380.dist-info → auto_coder-0.1.382.dist-info}/top_level.txt +0 -0
|
@@ -11,7 +11,7 @@ autocoder/command_parser.py,sha256=fx1g9E6GaM273lGTcJqaFQ-hoksS_Ik2glBMnVltPCE,1
|
|
|
11
11
|
autocoder/lang.py,sha256=PFtATuOhHRnfpqHQkXr6p4C893JvpsgwTMif3l-GEi0,14321
|
|
12
12
|
autocoder/models.py,sha256=pD5u6gcMKRwWaLxeVin18g25k-ERyeHOFsRpOgO_Ae0,13788
|
|
13
13
|
autocoder/run_context.py,sha256=IUfSO6_gp2Wt1blFWAmOpN0b0nDrTTk4LmtCYUBIoro,1643
|
|
14
|
-
autocoder/version.py,sha256=
|
|
14
|
+
autocoder/version.py,sha256=WsLgumnTuWnjn9FpRrgmPyKJQyLwiu2BVqZ5GJpgbT0,25
|
|
15
15
|
autocoder/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
autocoder/agent/agentic_filter.py,sha256=zlInIRhawKIYTJjCiJBWqPCOV5UtMbh5VnvszfTy2vo,39824
|
|
17
17
|
autocoder/agent/auto_demand_organizer.py,sha256=URAq0gSEiHeV_W4zwhOI_83kHz0Ryfj1gcfh5jwCv_w,6501
|
|
@@ -40,7 +40,7 @@ autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py,sha256
|
|
|
40
40
|
autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py,sha256=9a_qPihfm45Q22kmxmFwYrZrtNsgtEyBePL8D4SXfes,1487
|
|
41
41
|
autocoder/agent/base_agentic/tools/base_tool_resolver.py,sha256=Y0PmRPhBLLdZpr2DNV0W5goGfAqylM5m1bHxa3T6vAY,1009
|
|
42
42
|
autocoder/agent/base_agentic/tools/example_tool_resolver.py,sha256=8WBIqEH_76S0iBHBM3RhLkk14UpJ28lRAw6dNUT-0no,1388
|
|
43
|
-
autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py,sha256=
|
|
43
|
+
autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py,sha256=UL7eQz7Im4BhpglIxUDmEBZ9xwM_hhk1J9iY228uvPU,3691
|
|
44
44
|
autocoder/agent/base_agentic/tools/list_files_tool_resolver.py,sha256=SCAYAiqmgwuCLMwRwroUQFfVS_hbcuAoG9-jIJxiB1c,8061
|
|
45
45
|
autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py,sha256=hiLdMRknR_Aljd7Ic2bOWZKs11aFHdhkRTKTlP3IIgw,1461
|
|
46
46
|
autocoder/agent/base_agentic/tools/read_file_tool_resolver.py,sha256=egcxrXv32stvhB3QawE2k62-9W5U3TNuY3qoTyKKhs0,5076
|
|
@@ -81,7 +81,7 @@ autocoder/common/code_auto_merge_editblock.py,sha256=5PLH8Ey4GYsPNMGn36pSy_IgwgZ
|
|
|
81
81
|
autocoder/common/code_auto_merge_strict_diff.py,sha256=C35pFxhkgypsm50VDAFOBAT6YXMtzKTvIpEUH1GjZZg,13209
|
|
82
82
|
autocoder/common/code_modification_ranker.py,sha256=RwswmKhYae9ZPQHUFPUI-ZW51K5NNtwhFBdOH2I65ic,13592
|
|
83
83
|
autocoder/common/command_completer.py,sha256=MiQiJZZbtGUvREH3gUrQe9b1ElXnPTYh9uuVcUsNlTc,38659
|
|
84
|
-
autocoder/common/command_completer_v2.py,sha256=
|
|
84
|
+
autocoder/common/command_completer_v2.py,sha256=bnBjYAvo56mxzDleH-O3UxBYGjkcbwFZGZA5lh5uXmo,32337
|
|
85
85
|
autocoder/common/command_generator.py,sha256=YwB_E818vx0fQDOpLD61GivsSgLnrIoxKrY22ka49JU,2797
|
|
86
86
|
autocoder/common/command_templates.py,sha256=HjI-lo7H-XeQGsHKUtEI3vnhFH1QFibW9X7fahim8oE,8259
|
|
87
87
|
autocoder/common/computer_use.py,sha256=Z5RL-DgkcbF55YDsqnJ37loXGcm_1tzTheukjTTayJM,35816
|
|
@@ -179,14 +179,14 @@ autocoder/common/v2/agent/agentic_edit_tools/__init__.py,sha256=RbPZZcZg_VnGssL5
|
|
|
179
179
|
autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py,sha256=-HFXo3RR6CH8xXjDaE2mYV4XasTLAmvXe6WutL7qbwA,3208
|
|
180
180
|
autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py,sha256=82ZGKeRBSDKeead_XVBW4FxpiE-5dS7tBOk_3RZ6B5s,1511
|
|
181
181
|
autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py,sha256=Zid2m1uZd-2wVFGc_n_KAViXZyNjbdLSpI5n7ut1RUQ,1036
|
|
182
|
-
autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py,sha256=
|
|
182
|
+
autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py,sha256=8dCjSvpo09QG4Jj-kh5iOTQtikp87W0Z6q_6XyezQDo,5439
|
|
183
183
|
autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py,sha256=8QoMsADUDWliqiDt_dpguz31403syB8eeW0Pcw-qfb8,3842
|
|
184
184
|
autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py,sha256=1tJX9RYRU0zRjKZMzFlZzKm-4R32CzRnZ0UQJj4pxAk,8074
|
|
185
185
|
autocoder/common/v2/agent/agentic_edit_tools/list_package_info_tool_resolver.py,sha256=dIdV12VuczHpHuHgx2B1j_3BZYc9PL0jfHCuBk9ryk8,2005
|
|
186
186
|
autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py,sha256=lGT4_QYJK6Fa9f6HVSGo0cSsGK7qCsDYgJGUowNxPzk,1499
|
|
187
187
|
autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py,sha256=6JhjM3VXV3BGelh1dNcdr-M5FoVPoqLkls1-y8ND8_c,6721
|
|
188
188
|
autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py,sha256=ZTdImTBK7KTYH98JVUioBNtIz-dqSfhkmNEBhajt7hk,12686
|
|
189
|
-
autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=
|
|
189
|
+
autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=ZHWNoqmutGhEt2cPleu1fpcK12XqSCTC5d3XR1bfYpg,10046
|
|
190
190
|
autocoder/common/v2/agent/agentic_edit_tools/test_execute_command_tool_resolver.py,sha256=cG4TBqJX0RP1w67xElt_KH8XzgAhSUbIhuOQFvSnDDE,2864
|
|
191
191
|
autocoder/common/v2/agent/agentic_edit_tools/test_search_files_tool_resolver.py,sha256=9eBo3WLkrr77iNotwIwVmH1ZL3UY0JQgLpdAIc9wTTM,6127
|
|
192
192
|
autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py,sha256=ZWRPsJny_My4UMzovrB8J2_x5N0rEW-xx3DVI-kDRFI,15870
|
|
@@ -238,10 +238,13 @@ autocoder/linters/base_linter.py,sha256=1_0DPESnSyF3ZcQhoFkBYJylT5w-B61Rx-3A9uhu
|
|
|
238
238
|
autocoder/linters/linter_factory.py,sha256=BgGeXPdli7BgiN9BifWoosyn9BGeJnRwSqX0G1R8qvU,10471
|
|
239
239
|
autocoder/linters/models.py,sha256=GBdayu_p50KBxoRms4X68zrDK-OsKDEKKjo926FevwE,9838
|
|
240
240
|
autocoder/linters/normal_linter.py,sha256=ezToVW33psvBXsGhE7y1ng7ucf7yT_1YuIULns6TXYM,13011
|
|
241
|
-
autocoder/linters/python_linter.py,sha256=
|
|
242
|
-
autocoder/linters/reactjs_linter.py,sha256=
|
|
241
|
+
autocoder/linters/python_linter.py,sha256=VFuKoHo9FTo_2YyscCghDgb1RfnpdiHGAYBB6img9Cs,18165
|
|
242
|
+
autocoder/linters/reactjs_linter.py,sha256=MvJVYgNJi0WxiEveexwqhSfN0FcwHxJ_wVjNx8H_rT4,21106
|
|
243
243
|
autocoder/linters/shadow_linter.py,sha256=SKgRNVnTavNUviFC9osYMz18nGWCVOPOHx9LavEbnmc,15047
|
|
244
|
-
autocoder/linters/
|
|
244
|
+
autocoder/linters/test_python_linter.py,sha256=zYPliyjm1s0IJmlIadyWQedsLNOGbaz7Q68_LmREO_w,6288
|
|
245
|
+
autocoder/linters/test_reactjs_linter.py,sha256=72VnejikldyEH5PdvJzrgtcxhnlGkVZTe3hWRGnw4mA,7099
|
|
246
|
+
autocoder/linters/test_vue_linter.py,sha256=m031brPZdK66Yi-hvlPvL0RYyQxvY43lHu_ATVxrvQs,7592
|
|
247
|
+
autocoder/linters/vue_linter.py,sha256=MjF-SgSbgOucyv0hnp2r56CIfe8hqDibs0NKnvUUvLw,21225
|
|
245
248
|
autocoder/memory/__init__.py,sha256=5FeGvsesRViYL4BkFiHw9SdlyHeWlqALpTyqOpfnBRw,179
|
|
246
249
|
autocoder/memory/active_context_manager.py,sha256=nqWD4lBLNcskXDRERhPpqnmn_i1V7_CTfQSN3xAX6b8,32297
|
|
247
250
|
autocoder/memory/active_package.py,sha256=NHLLnncFSfFcOFLWILwJLuEVd4nOoL0mqzFev6QHgzU,25480
|
|
@@ -330,9 +333,9 @@ autocoder/utils/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
330
333
|
autocoder/utils/auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
331
334
|
autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=t902pKxQ5xM7zgIHiAOsTPLwxhE6VuvXAqPy751S7fg,14096
|
|
332
335
|
autocoder/utils/chat_auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
333
|
-
auto_coder-0.1.
|
|
334
|
-
auto_coder-0.1.
|
|
335
|
-
auto_coder-0.1.
|
|
336
|
-
auto_coder-0.1.
|
|
337
|
-
auto_coder-0.1.
|
|
338
|
-
auto_coder-0.1.
|
|
336
|
+
auto_coder-0.1.382.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
337
|
+
auto_coder-0.1.382.dist-info/METADATA,sha256=PVXxhMLwdBO7U8UTU5jHi6gAsMUOoETYAsWTeWNvldI,2796
|
|
338
|
+
auto_coder-0.1.382.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
339
|
+
auto_coder-0.1.382.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
|
|
340
|
+
auto_coder-0.1.382.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
|
|
341
|
+
auto_coder-0.1.382.dist-info/RECORD,,
|
|
@@ -19,7 +19,6 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
19
19
|
self.tool: ExecuteCommandTool = tool # For type hinting
|
|
20
20
|
|
|
21
21
|
def resolve(self) -> ToolResult:
|
|
22
|
-
printer = Printer()
|
|
23
22
|
command = self.tool.command
|
|
24
23
|
requires_approval = self.tool.requires_approval
|
|
25
24
|
source_dir = self.args.source_dir or "."
|
|
@@ -35,11 +34,11 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
35
34
|
# Approval mechanism (simplified)
|
|
36
35
|
if requires_approval:
|
|
37
36
|
# In a real scenario, this would involve user interaction
|
|
38
|
-
|
|
37
|
+
logger.info(f"Command requires approval: {command}")
|
|
39
38
|
# For now, let's assume approval is granted in non-interactive mode or handled elsewhere
|
|
40
39
|
pass
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
logger.info(f"Executing command: {command} in {os.path.abspath(source_dir)}")
|
|
43
42
|
try:
|
|
44
43
|
# 使用封装的run_cmd方法执行命令
|
|
45
44
|
if get_run_context().is_web():
|
|
@@ -14,24 +14,24 @@ from autocoder import models as models_module
|
|
|
14
14
|
# Define command structure in a more structured way if needed,
|
|
15
15
|
# but primarily rely on handlers for logic.
|
|
16
16
|
COMMAND_HIERARCHY = {
|
|
17
|
-
"/add_files": {"/group": {"/add", "/drop", "/reset", "/set"}, "/refresh": {}},
|
|
18
|
-
"/remove_files": {"/all"},
|
|
19
|
-
"/conf": {"/drop", "/export", "/import","/get"}, # Added list/get for clarity
|
|
20
|
-
"/coding": {"/apply", "/next"},
|
|
21
|
-
"/chat": {"/new", "/save", "/copy", "/mcp", "/rag", "/review", "/learn", "/no_context"},
|
|
22
|
-
"/mcp": {"/add", "/remove", "/list", "/list_running", "/refresh", "/info"},
|
|
23
|
-
"/lib": {"/add", "/remove", "/list", "/set-proxy", "/refresh", "/get"},
|
|
24
|
-
"/models": {"/chat", "/add", "/add_model", "/remove", "/list", "/speed", "/speed-test", "/input_price", "/output_price", "/activate"},
|
|
17
|
+
"/add_files": {"/group": {"/add": {}, "/drop": {}, "/reset": {}, "/set": {}}, "/refresh": {}},
|
|
18
|
+
"/remove_files": {"/all": {}},
|
|
19
|
+
"/conf": {"/drop": {}, "/export": {}, "/import": {}, "/get": {}}, # Added list/get for clarity
|
|
20
|
+
"/coding": {"/apply": {}, "/next": {}},
|
|
21
|
+
"/chat": {"/new": {}, "/save": {}, "/copy": {}, "/mcp": {}, "/rag": {}, "/review": {}, "/learn": {}, "/no_context": {}},
|
|
22
|
+
"/mcp": {"/add": {}, "/remove": {}, "/list": {}, "/list_running": {}, "/refresh": {}, "/info": {}},
|
|
23
|
+
"/lib": {"/add": {}, "/remove": {}, "/list": {}, "/set-proxy": {}, "/refresh": {}, "/get": {}},
|
|
24
|
+
"/models": {"/chat": {}, "/add": {}, "/add_model": {}, "/remove": {}, "/list": {}, "/speed": {}, "/speed-test": {}, "/input_price": {}, "/output_price": {}, "/activate": {}},
|
|
25
25
|
"/auto": {},
|
|
26
|
-
"/shell": {"/chat"},
|
|
27
|
-
"/active_context": {"/list", "/run"},
|
|
28
|
-
"/index": {"/query", "/build", "/export", "/import"},
|
|
29
|
-
"/exclude_files": {"/list", "/drop"},
|
|
26
|
+
"/shell": {"/chat": {}},
|
|
27
|
+
"/active_context": {"/list": {}, "/run": {}},
|
|
28
|
+
"/index": {"/query": {}, "/build": {}, "/export": {}, "/import": {}},
|
|
29
|
+
"/exclude_files": {"/list": {}, "/drop": {}},
|
|
30
30
|
"/exclude_dirs": {}, # No specific subcommands shown in V1, treat as simple list
|
|
31
31
|
"/commit": {}, # No specific subcommands shown in V1
|
|
32
32
|
"/revert": {},
|
|
33
33
|
"/ask": {},
|
|
34
|
-
"/design": {"/svg", "/sd", "/logo"},
|
|
34
|
+
"/design": {"/svg": {}, "/sd": {}, "/logo": {}},
|
|
35
35
|
"/summon": {},
|
|
36
36
|
"/mode": {}, # Simple value completion
|
|
37
37
|
"/voice_input": {},
|
|
@@ -41,7 +41,7 @@ COMMAND_HIERARCHY = {
|
|
|
41
41
|
"/clear": {},
|
|
42
42
|
"/cls": {},
|
|
43
43
|
"/debug": {},
|
|
44
|
-
"/rules": {"/list", "/get", "/remove", "/analyze", "/commit", "/help"},
|
|
44
|
+
"/rules": {"/list": {}, "/get": {}, "/remove": {}, "/analyze": {}, "/commit": {}, "/help": {}},
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
class CommandCompleterV2(Completer):
|
|
@@ -8,6 +8,9 @@ from autocoder.common import shells
|
|
|
8
8
|
from autocoder.common.printer import Printer
|
|
9
9
|
from loguru import logger
|
|
10
10
|
import typing
|
|
11
|
+
from autocoder.common.context_pruner import PruneContext
|
|
12
|
+
from autocoder.rag.token_counter import count_tokens
|
|
13
|
+
from autocoder.common import SourceCode
|
|
11
14
|
from autocoder.common import AutoCoderArgs
|
|
12
15
|
from autocoder.events.event_manager_singleton import get_event_manager
|
|
13
16
|
from autocoder.run_context import get_run_context
|
|
@@ -18,9 +21,42 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
18
21
|
def __init__(self, agent: Optional['AgenticEdit'], tool: ExecuteCommandTool, args: AutoCoderArgs):
|
|
19
22
|
super().__init__(agent, tool, args)
|
|
20
23
|
self.tool: ExecuteCommandTool = tool # For type hinting
|
|
24
|
+
self.context_pruner = PruneContext(
|
|
25
|
+
max_tokens=self.args.context_prune_safe_zone_tokens,
|
|
26
|
+
args=self.args,
|
|
27
|
+
llm=self.agent.context_prune_llm
|
|
28
|
+
)
|
|
21
29
|
|
|
22
|
-
def
|
|
23
|
-
|
|
30
|
+
def _prune_file_content(self, content: str, file_path: str) -> str:
|
|
31
|
+
"""对文件内容进行剪枝处理"""
|
|
32
|
+
if not self.context_pruner:
|
|
33
|
+
return content
|
|
34
|
+
|
|
35
|
+
# 计算 token 数量
|
|
36
|
+
tokens = count_tokens(content)
|
|
37
|
+
if tokens <= self.args.context_prune_safe_zone_tokens:
|
|
38
|
+
return content
|
|
39
|
+
|
|
40
|
+
# 创建 SourceCode 对象
|
|
41
|
+
source_code = SourceCode(
|
|
42
|
+
module_name=file_path,
|
|
43
|
+
source_code=content,
|
|
44
|
+
tokens=tokens
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 使用 context_pruner 进行剪枝
|
|
48
|
+
pruned_sources = self.context_pruner.handle_overflow(
|
|
49
|
+
file_sources=[source_code],
|
|
50
|
+
conversations=self.agent.current_conversations if self.agent else [],
|
|
51
|
+
strategy=self.args.context_prune_strategy
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if not pruned_sources:
|
|
55
|
+
return content
|
|
56
|
+
|
|
57
|
+
return pruned_sources[0].source_code
|
|
58
|
+
|
|
59
|
+
def resolve(self) -> ToolResult:
|
|
24
60
|
command = self.tool.command
|
|
25
61
|
requires_approval = self.tool.requires_approval
|
|
26
62
|
source_dir = self.args.source_dir or "."
|
|
@@ -36,11 +72,11 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
36
72
|
# Approval mechanism (simplified)
|
|
37
73
|
if requires_approval:
|
|
38
74
|
# In a real scenario, this would involve user interaction
|
|
39
|
-
|
|
75
|
+
logger.info(f"Command requires approval: {command}")
|
|
40
76
|
# For now, let's assume approval is granted in non-interactive mode or handled elsewhere
|
|
41
77
|
pass
|
|
42
78
|
|
|
43
|
-
|
|
79
|
+
logger.info(f"Executing command: {command} in {os.path.abspath(source_dir)}")
|
|
44
80
|
try:
|
|
45
81
|
# 使用封装的run_cmd方法执行命令
|
|
46
82
|
if get_run_context().is_web():
|
|
@@ -56,13 +92,17 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
56
92
|
logger.info(f"Command executed: {command}")
|
|
57
93
|
logger.info(f"Return Code: {exit_code}")
|
|
58
94
|
if output:
|
|
59
|
-
logger.info(f"Output
|
|
95
|
+
logger.info(f"Original Output (length: {len(output)} chars)") # Avoid logging potentially huge output directly
|
|
96
|
+
|
|
97
|
+
final_output = self._prune_file_content(output, "command_output")
|
|
60
98
|
|
|
61
99
|
if exit_code == 0:
|
|
62
|
-
return ToolResult(success=True, message="Command executed successfully.", content=
|
|
100
|
+
return ToolResult(success=True, message="Command executed successfully.", content=final_output)
|
|
63
101
|
else:
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
# For the human-readable error message, we might prefer the original full output.
|
|
103
|
+
# For the agent-consumable content, we provide the (potentially pruned) final_output.
|
|
104
|
+
error_message_for_human = f"Command failed with return code {exit_code}.\nOutput:\n{output}"
|
|
105
|
+
return ToolResult(success=False, message=error_message_for_human, content={"output": final_output, "returncode": exit_code})
|
|
66
106
|
|
|
67
107
|
except FileNotFoundError:
|
|
68
108
|
return ToolResult(success=False, message=f"Error: The command '{command.split()[0]}' was not found. Please ensure it is installed and in the system's PATH.")
|
|
@@ -172,8 +172,16 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
172
172
|
|
|
173
173
|
# Handle the case where the implementation returns a list instead of a ToolResult
|
|
174
174
|
if isinstance(result, list):
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
total_results = len(result)
|
|
176
|
+
# Limit results to 200 if needed
|
|
177
|
+
if total_results > 200:
|
|
178
|
+
truncated_results = result[:200]
|
|
179
|
+
message = f"Search completed. Found {total_results} matches, showing only the first 200."
|
|
180
|
+
logger.info(message)
|
|
181
|
+
return ToolResult(success=True, message=message, content=truncated_results)
|
|
182
|
+
else:
|
|
183
|
+
message = f"Search completed. Found {total_results} matches."
|
|
184
|
+
logger.info(message)
|
|
185
|
+
return ToolResult(success=True, message=message, content=result)
|
|
178
186
|
else:
|
|
179
187
|
return result
|
|
@@ -332,17 +332,7 @@ class PythonLinter(BaseLinter):
|
|
|
332
332
|
result['warning_count'] += pylint_result['warning_count']
|
|
333
333
|
except Exception as e:
|
|
334
334
|
if self.verbose:
|
|
335
|
-
print(f"Error running pylint: {str(e)}")
|
|
336
|
-
|
|
337
|
-
# Run flake8
|
|
338
|
-
try:
|
|
339
|
-
flake8_result = self._run_flake8(file_path)
|
|
340
|
-
result['issues'].extend(flake8_result['issues'])
|
|
341
|
-
result['error_count'] += flake8_result['error_count']
|
|
342
|
-
result['warning_count'] += flake8_result['warning_count']
|
|
343
|
-
except Exception as e:
|
|
344
|
-
if self.verbose:
|
|
345
|
-
print(f"Error running flake8: {str(e)}")
|
|
335
|
+
print(f"Error running pylint: {str(e)}")
|
|
346
336
|
|
|
347
337
|
# Mark as successful
|
|
348
338
|
result['success'] = True
|
|
@@ -400,17 +390,7 @@ class PythonLinter(BaseLinter):
|
|
|
400
390
|
except Exception as e:
|
|
401
391
|
if self.verbose:
|
|
402
392
|
print(f"Error running pylint: {str(e)}")
|
|
403
|
-
|
|
404
|
-
# Run flake8
|
|
405
|
-
try:
|
|
406
|
-
flake8_result = self._run_flake8(project_path)
|
|
407
|
-
result['issues'].extend(flake8_result['issues'])
|
|
408
|
-
result['error_count'] += flake8_result['error_count']
|
|
409
|
-
result['warning_count'] += flake8_result['warning_count']
|
|
410
|
-
except Exception as e:
|
|
411
|
-
if self.verbose:
|
|
412
|
-
print(f"Error running flake8: {str(e)}")
|
|
413
|
-
|
|
393
|
+
|
|
414
394
|
# Mark as successful
|
|
415
395
|
result['success'] = True
|
|
416
396
|
|
|
@@ -243,11 +243,18 @@ class ReactJSLinter(BaseLinter):
|
|
|
243
243
|
|
|
244
244
|
# Process individual messages
|
|
245
245
|
for message in file_result.get('messages', []):
|
|
246
|
+
severity = "info"
|
|
247
|
+
if message.get('severity', 1) == 2:
|
|
248
|
+
severity = "error"
|
|
249
|
+
elif message.get('severity', 1) == 1:
|
|
250
|
+
severity = "warning"
|
|
251
|
+
elif message.get('severity', 1) == 0:
|
|
252
|
+
severity = "info"
|
|
246
253
|
issue = {
|
|
247
254
|
'file': file_rel_path,
|
|
248
255
|
'line': message.get('line', 0),
|
|
249
256
|
'column': message.get('column', 0),
|
|
250
|
-
'severity':
|
|
257
|
+
'severity': severity,
|
|
251
258
|
'message': message.get('message', ''),
|
|
252
259
|
'rule': message.get('ruleId', 'unknown')
|
|
253
260
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from autocoder.linters.python_linter import PythonLinter
|
|
6
|
+
|
|
7
|
+
class TestPythonLinter:
|
|
8
|
+
"""测试PythonLinter类"""
|
|
9
|
+
|
|
10
|
+
def setup_method(self):
|
|
11
|
+
"""每个测试方法前的设置"""
|
|
12
|
+
self.linter = PythonLinter(verbose=True)
|
|
13
|
+
|
|
14
|
+
def test_lint_file_nonexistent_file(self):
|
|
15
|
+
"""测试不存在的文件"""
|
|
16
|
+
result = self.linter.lint_file("nonexistent_file.py")
|
|
17
|
+
|
|
18
|
+
assert result['success'] == False
|
|
19
|
+
assert "不存在" in result.get('error', '') or "does not exist" in result.get('error', '')
|
|
20
|
+
|
|
21
|
+
def test_lint_file_unsupported_file(self):
|
|
22
|
+
"""测试不支持的文件类型"""
|
|
23
|
+
# 创建一个临时文件
|
|
24
|
+
with open("temp.txt", "w") as f:
|
|
25
|
+
f.write("这不是Python文件")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = self.linter.lint_file("temp.txt")
|
|
29
|
+
|
|
30
|
+
assert result['success'] == False
|
|
31
|
+
assert "不支持" in result.get('error', '') or "Unsupported" in result.get('error', '')
|
|
32
|
+
finally:
|
|
33
|
+
# 清理
|
|
34
|
+
if os.path.exists("temp.txt"):
|
|
35
|
+
os.remove("temp.txt")
|
|
36
|
+
|
|
37
|
+
@patch.object(PythonLinter, '_check_dependencies')
|
|
38
|
+
@patch.object(PythonLinter, '_install_dependencies_if_needed')
|
|
39
|
+
def test_lint_file_missing_dependencies(self, mock_install, mock_check):
|
|
40
|
+
"""测试缺少依赖项的情况"""
|
|
41
|
+
# 模拟依赖检查失败
|
|
42
|
+
mock_check.return_value = False
|
|
43
|
+
# 模拟安装依赖失败
|
|
44
|
+
mock_install.return_value = False
|
|
45
|
+
|
|
46
|
+
# 创建一个临时Python文件
|
|
47
|
+
with open("temp.py", "w") as f:
|
|
48
|
+
f.write("print('Hello, World!')")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
result = self.linter.lint_file("temp.py")
|
|
52
|
+
|
|
53
|
+
assert result['success'] == False
|
|
54
|
+
assert "依赖" in result.get('error', '') or "dependencies" in result.get('error', '')
|
|
55
|
+
finally:
|
|
56
|
+
# 清理
|
|
57
|
+
if os.path.exists("temp.py"):
|
|
58
|
+
os.remove("temp.py")
|
|
59
|
+
|
|
60
|
+
@patch.object(PythonLinter, '_check_dependencies')
|
|
61
|
+
@patch.object(PythonLinter, '_run_pylint')
|
|
62
|
+
def test_lint_file_successful(self, mock_pylint, mock_check):
|
|
63
|
+
"""测试成功的lint过程"""
|
|
64
|
+
# 模拟依赖检查成功
|
|
65
|
+
mock_check.return_value = True
|
|
66
|
+
|
|
67
|
+
# 模拟pylint结果
|
|
68
|
+
mock_pylint.return_value = {
|
|
69
|
+
'error_count': 1,
|
|
70
|
+
'warning_count': 2,
|
|
71
|
+
'issues': [
|
|
72
|
+
{
|
|
73
|
+
'file': 'test.py',
|
|
74
|
+
'line': 10,
|
|
75
|
+
'column': 5,
|
|
76
|
+
'severity': 'error',
|
|
77
|
+
'message': '未定义变量',
|
|
78
|
+
'rule': 'undefined-variable',
|
|
79
|
+
'tool': 'pylint'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
'file': 'test.py',
|
|
83
|
+
'line': 15,
|
|
84
|
+
'column': 1,
|
|
85
|
+
'severity': 'warning',
|
|
86
|
+
'message': '不必要的空行',
|
|
87
|
+
'rule': 'trailing-whitespace',
|
|
88
|
+
'tool': 'pylint'
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# 创建一个临时Python文件
|
|
94
|
+
with open("test.py", "w") as f:
|
|
95
|
+
f.write("print('Hello, World!')")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
result = self.linter.lint_file("test.py")
|
|
99
|
+
|
|
100
|
+
assert result['success'] == True
|
|
101
|
+
assert result['language'] == 'python'
|
|
102
|
+
assert result['files_analyzed'] == 1
|
|
103
|
+
assert result['error_count'] == 1
|
|
104
|
+
assert result['warning_count'] == 2 # 只有pylint的警告
|
|
105
|
+
assert len(result['issues']) == 2 # 总共2个问题
|
|
106
|
+
finally:
|
|
107
|
+
# 清理
|
|
108
|
+
if os.path.exists("test.py"):
|
|
109
|
+
os.remove("test.py")
|
|
110
|
+
|
|
111
|
+
# python -m pytest src/autocoder/linters/test_python_linter.py::TestPythonLinter::test_lint_real_file -v -s
|
|
112
|
+
@pytest.mark.integration
|
|
113
|
+
def test_lint_real_file(self):
|
|
114
|
+
"""测试对实际文件的lint功能"""
|
|
115
|
+
# 实际存在的Python文件路径
|
|
116
|
+
file_path = "/Users/allwefantasy/projects/auto-coder/src/autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py"
|
|
117
|
+
|
|
118
|
+
# 检查文件是否存在,如果不存在则跳过测试
|
|
119
|
+
if not os.path.exists(file_path):
|
|
120
|
+
pytest.skip(f"测试文件 {file_path} 不存在")
|
|
121
|
+
|
|
122
|
+
# 执行lint操作
|
|
123
|
+
result = self.linter.lint_file(file_path)
|
|
124
|
+
|
|
125
|
+
# 基本断言
|
|
126
|
+
assert result['success'] == True
|
|
127
|
+
assert result['language'] == 'python'
|
|
128
|
+
assert result['files_analyzed'] == 1
|
|
129
|
+
|
|
130
|
+
# 打印完整的lint结果
|
|
131
|
+
print("\n==== Lint结果概述 ====")
|
|
132
|
+
print(f"文件: {file_path}")
|
|
133
|
+
print(f"错误数: {result['error_count']}")
|
|
134
|
+
print(f"警告数: {result['warning_count']}")
|
|
135
|
+
print(f"问题总数: {len(result['issues'])}")
|
|
136
|
+
|
|
137
|
+
print("\n==== 详细问题列表 ====")
|
|
138
|
+
if not result['issues']:
|
|
139
|
+
print("没有发现问题!")
|
|
140
|
+
else:
|
|
141
|
+
for i, issue in enumerate(result['issues'], 1):
|
|
142
|
+
print(f"\n问题 #{i}:")
|
|
143
|
+
print(f" 文件: {issue['file']}")
|
|
144
|
+
print(f" 位置: 第{issue['line']}行, 第{issue['column']}列")
|
|
145
|
+
print(f" 严重性: {issue['severity']}")
|
|
146
|
+
print(f" 信息: {issue['message']}")
|
|
147
|
+
print(f" 规则: {issue['rule']}")
|
|
148
|
+
print(f" 工具: {issue['tool']}")
|
|
149
|
+
|
|
150
|
+
# 可选:打印完整的JSON格式结果
|
|
151
|
+
print("\n==== 完整JSON结果 ====")
|
|
152
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
153
|
+
|
|
154
|
+
# 验证结果中包含issues字段,且为列表类型
|
|
155
|
+
assert 'issues' in result
|
|
156
|
+
assert isinstance(result['issues'], list)
|
|
157
|
+
|
|
158
|
+
# 如果有issues,验证其结构
|
|
159
|
+
if result['issues']:
|
|
160
|
+
issue = result['issues'][0]
|
|
161
|
+
assert 'file' in issue
|
|
162
|
+
assert 'line' in issue
|
|
163
|
+
assert 'severity' in issue
|
|
164
|
+
assert 'message' in issue
|
|
165
|
+
assert 'rule' in issue
|
|
166
|
+
assert 'tool' in issue
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from autocoder.linters.reactjs_linter import ReactJSLinter
|
|
6
|
+
|
|
7
|
+
# python -m pytest src/autocoder/linters/test_reactjs_linter.py::TestReactJSLinter::test_lint_real_file -v -s
|
|
8
|
+
class TestReactJSLinter:
|
|
9
|
+
"""测试ReactJSLinter类"""
|
|
10
|
+
|
|
11
|
+
def setup_method(self):
|
|
12
|
+
"""每个测试方法前的设置"""
|
|
13
|
+
self.linter = ReactJSLinter(verbose=True)
|
|
14
|
+
|
|
15
|
+
def test_lint_file_nonexistent_file(self):
|
|
16
|
+
"""测试不存在的文件"""
|
|
17
|
+
result = self.linter.lint_file("nonexistent_file.jsx")
|
|
18
|
+
|
|
19
|
+
assert result['success'] == False
|
|
20
|
+
assert "不存在" in result.get('error', '') or "does not exist" in result.get('error', '')
|
|
21
|
+
|
|
22
|
+
def test_lint_file_unsupported_file(self):
|
|
23
|
+
"""测试不支持的文件类型"""
|
|
24
|
+
# 创建一个临时文件
|
|
25
|
+
with open("temp.txt", "w") as f:
|
|
26
|
+
f.write("这不是React文件")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
result = self.linter.lint_file("temp.txt")
|
|
30
|
+
|
|
31
|
+
assert result['success'] == False
|
|
32
|
+
assert "不支持" in result.get('error', '') or "Unsupported" in result.get('error', '')
|
|
33
|
+
finally:
|
|
34
|
+
# 清理
|
|
35
|
+
if os.path.exists("temp.txt"):
|
|
36
|
+
os.remove("temp.txt")
|
|
37
|
+
|
|
38
|
+
@patch.object(ReactJSLinter, '_check_dependencies')
|
|
39
|
+
def test_lint_file_missing_dependencies(self, mock_check):
|
|
40
|
+
"""测试缺少依赖项的情况"""
|
|
41
|
+
# 模拟依赖检查失败
|
|
42
|
+
mock_check.return_value = False
|
|
43
|
+
|
|
44
|
+
# 创建一个临时React文件
|
|
45
|
+
with open("temp.jsx", "w") as f:
|
|
46
|
+
f.write("import React from 'react';\nfunction App() { return <div>Hello</div>; }")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
result = self.linter.lint_file("temp.jsx")
|
|
50
|
+
|
|
51
|
+
assert result['success'] == False
|
|
52
|
+
assert "依赖" in result.get('error', '') or "dependencies" in result.get('error', '')
|
|
53
|
+
finally:
|
|
54
|
+
# 清理
|
|
55
|
+
if os.path.exists("temp.jsx"):
|
|
56
|
+
os.remove("temp.jsx")
|
|
57
|
+
|
|
58
|
+
@patch.object(ReactJSLinter, '_detect_file_type')
|
|
59
|
+
def test_lint_file_not_react_file(self, mock_detect):
|
|
60
|
+
"""测试非React文件"""
|
|
61
|
+
# 模拟文件类型检测失败
|
|
62
|
+
mock_detect.return_value = False
|
|
63
|
+
|
|
64
|
+
# 创建一个临时JS文件
|
|
65
|
+
with open("temp.js", "w") as f:
|
|
66
|
+
f.write("function hello() { console.log('Hello'); }")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
result = self.linter.lint_file("temp.js")
|
|
70
|
+
|
|
71
|
+
assert result['success'] == False
|
|
72
|
+
assert "Not a React file" in result.get('error', '')
|
|
73
|
+
finally:
|
|
74
|
+
# 清理
|
|
75
|
+
if os.path.exists("temp.js"):
|
|
76
|
+
os.remove("temp.js")
|
|
77
|
+
|
|
78
|
+
@patch.object(ReactJSLinter, '_check_dependencies')
|
|
79
|
+
@patch.object(ReactJSLinter, '_detect_file_type')
|
|
80
|
+
@patch.object(ReactJSLinter, '_install_eslint_if_needed')
|
|
81
|
+
@patch('subprocess.run')
|
|
82
|
+
def test_lint_file_successful(self, mock_run, mock_install, mock_detect, mock_check):
|
|
83
|
+
"""测试成功的lint过程"""
|
|
84
|
+
# 模拟依赖检查成功
|
|
85
|
+
mock_check.return_value = True
|
|
86
|
+
# 模拟文件类型检测成功
|
|
87
|
+
mock_detect.return_value = True
|
|
88
|
+
# 模拟安装ESLint成功
|
|
89
|
+
mock_install.return_value = True
|
|
90
|
+
|
|
91
|
+
# 模拟subprocess.run的结果
|
|
92
|
+
mock_process = MagicMock()
|
|
93
|
+
mock_process.stdout = json.dumps([
|
|
94
|
+
{
|
|
95
|
+
'filePath': os.path.abspath('test.jsx'),
|
|
96
|
+
'errorCount': 1,
|
|
97
|
+
'warningCount': 2,
|
|
98
|
+
'messages': [
|
|
99
|
+
{
|
|
100
|
+
'line': 10,
|
|
101
|
+
'column': 5,
|
|
102
|
+
'severity': 2,
|
|
103
|
+
'message': '未使用的变量',
|
|
104
|
+
'ruleId': 'no-unused-vars'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
'line': 15,
|
|
108
|
+
'column': 1,
|
|
109
|
+
'severity': 1,
|
|
110
|
+
'message': '缺少分号',
|
|
111
|
+
'ruleId': 'semi'
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
'line': 20,
|
|
115
|
+
'column': 10,
|
|
116
|
+
'severity': 1,
|
|
117
|
+
'message': '建议使用解构赋值',
|
|
118
|
+
'ruleId': 'prefer-destructuring'
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
])
|
|
123
|
+
mock_run.return_value = mock_process
|
|
124
|
+
|
|
125
|
+
# 创建一个临时React文件
|
|
126
|
+
with open("test.jsx", "w") as f:
|
|
127
|
+
f.write("import React from 'react';\nfunction App() { return <div>Hello</div>; }")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
result = self.linter.lint_file("test.jsx")
|
|
131
|
+
|
|
132
|
+
assert result['success'] == True
|
|
133
|
+
assert result['framework'] == 'reactjs'
|
|
134
|
+
assert result['files_analyzed'] == 1
|
|
135
|
+
assert result['error_count'] == 1
|
|
136
|
+
assert result['warning_count'] == 2
|
|
137
|
+
assert len(result['issues']) == 3
|
|
138
|
+
finally:
|
|
139
|
+
# 清理
|
|
140
|
+
if os.path.exists("test.jsx"):
|
|
141
|
+
os.remove("test.jsx")
|
|
142
|
+
|
|
143
|
+
# python -m pytest src/autocoder/linters/test_reactjs_linter.py::TestReactJSLinter::test_lint_real_file -v -s
|
|
144
|
+
@pytest.mark.integration
|
|
145
|
+
def test_lint_real_file(self):
|
|
146
|
+
"""测试对实际文件的lint功能"""
|
|
147
|
+
# 检查是否有React示例文件可用
|
|
148
|
+
file_path = "/Users/allwefantasy/projects/auto-coder.web/frontend/src/components/Sidebar/ChatPanel.tsx"
|
|
149
|
+
|
|
150
|
+
# 执行lint操作
|
|
151
|
+
result = self.linter.lint_file(file_path)
|
|
152
|
+
|
|
153
|
+
# 基本断言
|
|
154
|
+
assert 'success' in result
|
|
155
|
+
assert 'framework' in result
|
|
156
|
+
assert 'files_analyzed' in result
|
|
157
|
+
|
|
158
|
+
# 打印完整的lint结果
|
|
159
|
+
print("\n==== Lint结果概述 ====")
|
|
160
|
+
print(f"文件: {file_path}")
|
|
161
|
+
print(f"成功: {result.get('success', False)}")
|
|
162
|
+
if 'error' in result:
|
|
163
|
+
print(f"错误: {result['error']}")
|
|
164
|
+
else:
|
|
165
|
+
print(f"错误数: {result.get('error_count', 0)}")
|
|
166
|
+
print(f"警告数: {result.get('warning_count', 0)}")
|
|
167
|
+
print(f"问题总数: {len(result.get('issues', []))}")
|
|
168
|
+
|
|
169
|
+
print("\n==== 详细问题列表 ====")
|
|
170
|
+
if not result.get('issues', []):
|
|
171
|
+
print("没有发现问题或执行失败!")
|
|
172
|
+
else:
|
|
173
|
+
for i, issue in enumerate(result['issues'], 1):
|
|
174
|
+
print(f"\n问题 #{i}:")
|
|
175
|
+
print(f" 文件: {issue['file']}")
|
|
176
|
+
print(f" 位置: 第{issue['line']}行, 第{issue['column']}列")
|
|
177
|
+
print(f" 严重性: {issue['severity']}")
|
|
178
|
+
print(f" 信息: {issue['message']}")
|
|
179
|
+
print(f" 规则: {issue['rule']}")
|
|
180
|
+
|
|
181
|
+
# 可选:打印完整的JSON格式结果
|
|
182
|
+
print("\n==== 完整JSON结果 ====")
|
|
183
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from autocoder.linters.vue_linter import VueLinter
|
|
6
|
+
|
|
7
|
+
class TestVueLinter:
|
|
8
|
+
"""测试VueLinter类"""
|
|
9
|
+
|
|
10
|
+
def setup_method(self):
|
|
11
|
+
"""每个测试方法前的设置"""
|
|
12
|
+
self.linter = VueLinter(verbose=True)
|
|
13
|
+
|
|
14
|
+
def test_lint_file_nonexistent_file(self):
|
|
15
|
+
"""测试不存在的文件"""
|
|
16
|
+
result = self.linter.lint_file("nonexistent_file.vue")
|
|
17
|
+
|
|
18
|
+
assert result['success'] == False
|
|
19
|
+
assert "不存在" in result.get('error', '') or "does not exist" in result.get('error', '')
|
|
20
|
+
|
|
21
|
+
def test_lint_file_unsupported_file(self):
|
|
22
|
+
"""测试不支持的文件类型"""
|
|
23
|
+
# 创建一个临时文件
|
|
24
|
+
with open("temp.txt", "w") as f:
|
|
25
|
+
f.write("这不是Vue文件")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = self.linter.lint_file("temp.txt")
|
|
29
|
+
|
|
30
|
+
assert result['success'] == False
|
|
31
|
+
assert "不支持" in result.get('error', '') or "Unsupported" in result.get('error', '')
|
|
32
|
+
finally:
|
|
33
|
+
# 清理
|
|
34
|
+
if os.path.exists("temp.txt"):
|
|
35
|
+
os.remove("temp.txt")
|
|
36
|
+
|
|
37
|
+
@patch.object(VueLinter, '_check_dependencies')
|
|
38
|
+
def test_lint_file_missing_dependencies(self, mock_check):
|
|
39
|
+
"""测试缺少依赖项的情况"""
|
|
40
|
+
# 模拟依赖检查失败
|
|
41
|
+
mock_check.return_value = False
|
|
42
|
+
|
|
43
|
+
# 创建一个临时Vue文件
|
|
44
|
+
with open("temp.vue", "w") as f:
|
|
45
|
+
f.write("<template><div>Hello</div></template>")
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = self.linter.lint_file("temp.vue")
|
|
49
|
+
|
|
50
|
+
assert result['success'] == False
|
|
51
|
+
assert "依赖" in result.get('error', '') or "dependencies" in result.get('error', '')
|
|
52
|
+
finally:
|
|
53
|
+
# 清理
|
|
54
|
+
if os.path.exists("temp.vue"):
|
|
55
|
+
os.remove("temp.vue")
|
|
56
|
+
|
|
57
|
+
@patch.object(VueLinter, '_detect_file_type')
|
|
58
|
+
def test_lint_file_not_vue_file(self, mock_detect):
|
|
59
|
+
"""测试非Vue文件"""
|
|
60
|
+
# 模拟文件类型检测失败
|
|
61
|
+
mock_detect.return_value = False
|
|
62
|
+
|
|
63
|
+
# 创建一个临时JS文件
|
|
64
|
+
with open("temp.js", "w") as f:
|
|
65
|
+
f.write("function hello() { console.log('Hello'); }")
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
result = self.linter.lint_file("temp.js")
|
|
69
|
+
|
|
70
|
+
assert result['success'] == False
|
|
71
|
+
assert "Not a Vue file" in result.get('error', '')
|
|
72
|
+
finally:
|
|
73
|
+
# 清理
|
|
74
|
+
if os.path.exists("temp.js"):
|
|
75
|
+
os.remove("temp.js")
|
|
76
|
+
|
|
77
|
+
@patch.object(VueLinter, '_check_dependencies')
|
|
78
|
+
@patch.object(VueLinter, '_detect_file_type')
|
|
79
|
+
@patch.object(VueLinter, '_install_eslint_if_needed')
|
|
80
|
+
@patch('subprocess.run')
|
|
81
|
+
def test_lint_file_successful(self, mock_run, mock_install, mock_detect, mock_check):
|
|
82
|
+
"""测试成功的lint过程"""
|
|
83
|
+
# 模拟依赖检查成功
|
|
84
|
+
mock_check.return_value = True
|
|
85
|
+
# 模拟文件类型检测成功
|
|
86
|
+
mock_detect.return_value = True
|
|
87
|
+
# 模拟安装ESLint成功
|
|
88
|
+
mock_install.return_value = True
|
|
89
|
+
|
|
90
|
+
# 模拟subprocess.run的结果
|
|
91
|
+
mock_process = MagicMock()
|
|
92
|
+
mock_process.stdout = json.dumps([
|
|
93
|
+
{
|
|
94
|
+
'filePath': os.path.abspath('test.vue'),
|
|
95
|
+
'errorCount': 1,
|
|
96
|
+
'warningCount': 2,
|
|
97
|
+
'messages': [
|
|
98
|
+
{
|
|
99
|
+
'line': 10,
|
|
100
|
+
'column': 5,
|
|
101
|
+
'severity': 2,
|
|
102
|
+
'message': '组件名称必须是多词的',
|
|
103
|
+
'ruleId': 'vue/multi-word-component-names'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
'line': 15,
|
|
107
|
+
'column': 1,
|
|
108
|
+
'severity': 1,
|
|
109
|
+
'message': 'props应该被详细定义',
|
|
110
|
+
'ruleId': 'vue/require-prop-types'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
'line': 20,
|
|
114
|
+
'column': 10,
|
|
115
|
+
'severity': 1,
|
|
116
|
+
'message': '缺少key属性',
|
|
117
|
+
'ruleId': 'vue/require-v-for-key'
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
])
|
|
122
|
+
mock_run.return_value = mock_process
|
|
123
|
+
|
|
124
|
+
# 创建一个临时Vue文件
|
|
125
|
+
with open("test.vue", "w") as f:
|
|
126
|
+
f.write("<template><div>Hello</div></template>")
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
result = self.linter.lint_file("test.vue")
|
|
130
|
+
|
|
131
|
+
assert result['success'] == True
|
|
132
|
+
assert result['framework'] == 'vue'
|
|
133
|
+
assert result['files_analyzed'] == 1
|
|
134
|
+
assert result['error_count'] == 1
|
|
135
|
+
assert result['warning_count'] == 2
|
|
136
|
+
assert len(result['issues']) == 3
|
|
137
|
+
finally:
|
|
138
|
+
# 清理
|
|
139
|
+
if os.path.exists("test.vue"):
|
|
140
|
+
os.remove("test.vue")
|
|
141
|
+
|
|
142
|
+
# python -m pytest src/autocoder/linters/test_vue_linter.py::TestVueLinter::test_lint_real_file -v -s
|
|
143
|
+
@pytest.mark.integration
|
|
144
|
+
def test_lint_real_file(self):
|
|
145
|
+
"""测试对实际文件的lint功能"""
|
|
146
|
+
# 检查是否有Vue示例文件可用
|
|
147
|
+
test_dir = os.path.dirname(os.path.abspath(__file__))
|
|
148
|
+
file_path = os.path.join(test_dir, "test_samples", "vue_component.vue")
|
|
149
|
+
|
|
150
|
+
if not os.path.exists(file_path):
|
|
151
|
+
# 如果没有测试示例文件,创建一个
|
|
152
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
153
|
+
with open(file_path, "w") as f:
|
|
154
|
+
f.write("""
|
|
155
|
+
<template>
|
|
156
|
+
<div class="test-component">
|
|
157
|
+
<h1>Hello, {{ name }}!</h1>
|
|
158
|
+
<p>This is a test Vue component.</p>
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
|
|
162
|
+
<script>
|
|
163
|
+
export default {
|
|
164
|
+
name: 'TestComponent',
|
|
165
|
+
props: {
|
|
166
|
+
name: {
|
|
167
|
+
type: String,
|
|
168
|
+
required: true,
|
|
169
|
+
default: 'World'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<style scoped>
|
|
176
|
+
.test-component {
|
|
177
|
+
padding: 20px;
|
|
178
|
+
border: 1px solid #ccc;
|
|
179
|
+
border-radius: 5px;
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
182
|
+
""")
|
|
183
|
+
|
|
184
|
+
# 执行lint操作
|
|
185
|
+
result = self.linter.lint_file(file_path)
|
|
186
|
+
|
|
187
|
+
# 基本断言
|
|
188
|
+
assert 'success' in result
|
|
189
|
+
assert 'framework' in result
|
|
190
|
+
assert 'files_analyzed' in result
|
|
191
|
+
|
|
192
|
+
# 打印完整的lint结果
|
|
193
|
+
print("\n==== Lint结果概述 ====")
|
|
194
|
+
print(f"文件: {file_path}")
|
|
195
|
+
print(f"成功: {result.get('success', False)}")
|
|
196
|
+
if 'error' in result:
|
|
197
|
+
print(f"错误: {result['error']}")
|
|
198
|
+
else:
|
|
199
|
+
print(f"错误数: {result.get('error_count', 0)}")
|
|
200
|
+
print(f"警告数: {result.get('warning_count', 0)}")
|
|
201
|
+
print(f"问题总数: {len(result.get('issues', []))}")
|
|
202
|
+
|
|
203
|
+
print("\n==== 详细问题列表 ====")
|
|
204
|
+
if not result.get('issues', []):
|
|
205
|
+
print("没有发现问题或执行失败!")
|
|
206
|
+
else:
|
|
207
|
+
for i, issue in enumerate(result['issues'], 1):
|
|
208
|
+
print(f"\n问题 #{i}:")
|
|
209
|
+
print(f" 文件: {issue['file']}")
|
|
210
|
+
print(f" 位置: 第{issue['line']}行, 第{issue['column']}列")
|
|
211
|
+
print(f" 严重性: {issue['severity']}")
|
|
212
|
+
print(f" 信息: {issue['message']}")
|
|
213
|
+
print(f" 规则: {issue['rule']}")
|
|
214
|
+
|
|
215
|
+
# 可选:打印完整的JSON格式结果
|
|
216
|
+
print("\n==== 完整JSON结果 ====")
|
|
217
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
autocoder/linters/vue_linter.py
CHANGED
|
@@ -320,13 +320,21 @@ class VueLinter(BaseLinter):
|
|
|
320
320
|
total_errors += file_result.get('errorCount', 0)
|
|
321
321
|
total_warnings += file_result.get('warningCount', 0)
|
|
322
322
|
|
|
323
|
+
severity = "info"
|
|
324
|
+
if message.get('severity', 1) == 2:
|
|
325
|
+
severity = "error"
|
|
326
|
+
elif message.get('severity', 1) == 1:
|
|
327
|
+
severity = "warning"
|
|
328
|
+
elif message.get('severity', 1) == 0:
|
|
329
|
+
severity = "info"
|
|
330
|
+
|
|
323
331
|
# Process individual messages
|
|
324
332
|
for message in file_result.get('messages', []):
|
|
325
333
|
issue = {
|
|
326
334
|
'file': file_rel_path,
|
|
327
335
|
'line': message.get('line', 0),
|
|
328
336
|
'column': message.get('column', 0),
|
|
329
|
-
'severity':
|
|
337
|
+
'severity': severity,
|
|
330
338
|
'message': message.get('message', ''),
|
|
331
339
|
'rule': message.get('ruleId', 'unknown')
|
|
332
340
|
}
|
autocoder/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.382"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|