kcode-pi 0.1.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 +358 -0
- package/dist/cli/kcode.d.ts +15 -0
- package/dist/cli/kcode.js +153 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +7 -0
- package/docs/KCODE_DISTRIBUTION.md +91 -0
- package/extensions/kingdee-harness.ts +180 -0
- package/extensions/kingdee-header.ts +122 -0
- package/extensions/kingdee-tools.ts +379 -0
- package/knowledge/.backup/v1.0.0/version.json +10 -0
- package/knowledge/cangqiong/product-notes.md +15 -0
- package/knowledge/common/business-flows.md +115 -0
- package/knowledge/common/config-guides.md +110 -0
- package/knowledge/common/error-patterns.md +170 -0
- package/knowledge/common/implementation.md +144 -0
- package/knowledge/cosmic/hard-constraints.md +38 -0
- package/knowledge/cosmic/ksql-datafix.md +34 -0
- package/knowledge/cosmic/platform-baseline.md +32 -0
- package/knowledge/cosmic/plugin-decision-matrix.md +40 -0
- package/knowledge/cosmic/review-checklist.md +40 -0
- package/knowledge/cosmic/unittest.md +35 -0
- package/knowledge/enterprise/api-reference.md +186 -0
- package/knowledge/enterprise/code-patterns.md +217 -0
- package/knowledge/enterprise/plugin-lifecycle.md +188 -0
- package/knowledge/enterprise/tables.json +159 -0
- package/knowledge/flagship/api-reference.md +237 -0
- package/knowledge/flagship/code-patterns.md +246 -0
- package/knowledge/flagship/cosmic-platform-note.md +15 -0
- package/knowledge/flagship/plugin-lifecycle.md +248 -0
- package/knowledge/flagship/tables.json +159 -0
- package/knowledge/version.json +10 -0
- package/knowledge/xinghan/product-notes.md +15 -0
- package/package.json +71 -0
- package/prompts/kd-discuss.md +11 -0
- package/prompts/kd-execute.md +12 -0
- package/prompts/kd-plan.md +12 -0
- package/prompts/kd-ship.md +12 -0
- package/prompts/kd-spec.md +12 -0
- package/prompts/kd-verify.md +12 -0
- package/skills/kd-check/SKILL.md +26 -0
- package/skills/kd-cosmic-dev/SKILL.md +82 -0
- package/skills/kd-cosmic-review/SKILL.md +90 -0
- package/skills/kd-cosmic-unittest/SKILL.md +92 -0
- package/skills/kd-debug/SKILL.md +30 -0
- package/skills/kd-discuss/SKILL.md +24 -0
- package/skills/kd-execute/SKILL.md +22 -0
- package/skills/kd-gen/SKILL.md +34 -0
- package/skills/kd-ksql/SKILL.md +86 -0
- package/skills/kd-plan/SKILL.md +24 -0
- package/skills/kd-ship/SKILL.md +22 -0
- package/skills/kd-spec/SKILL.md +24 -0
- package/skills/kd-verify/SKILL.md +22 -0
- package/themes/kcode-dark.json +81 -0
- package/vendor/kingdee-skills/cosmic-unittest/SKILL.md +788 -0
- package/vendor/kingdee-skills/cosmic-unittest/author-cache.json +5 -0
- package/vendor/kingdee-skills/cosmic-unittest/cosmic-unittest-skill-overview.html +746 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/business-test.md +205 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/common-test.md +257 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/formplugin-test.md +560 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/op-plugin-test.md +231 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/validator-test.md +232 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/business-helper.md +184 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/common-module.md +355 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/convert-plugin.md +130 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/formplugin.md +235 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/op-plugin.md +226 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/validator.md +206 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +674 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/advanced-scenario-checklist.md +307 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/algox-performance-checklist.md +129 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/coding-standard-checklist.md +491 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/cosmic-api-checklist.md +285 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-access-checklist.md +261 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-transaction-checklist.md +390 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/domain-logic-checklist.md +295 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/form-plugin-checklist.md +508 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/infra-checklist.md +254 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ksql-checklist.md +305 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/lifecycle-checklist.md +298 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/operation-plugin-checklist.md +442 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/test-mock-checklist.md +120 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ui-performance-checklist.md +320 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +336 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +121 -0
- package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +295 -0
- package/vendor/kingdee-skills/ok-cosmic/README.md +460 -0
- package/vendor/kingdee-skills/ok-cosmic/SKILL.md +287 -0
- package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +17 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/BatchImportPluginTemplate.java +93 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/BillPlugInTemplate.java +156 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ConvertPlugInTemplate.java +255 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/FormPluginTemplate.java +597 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/IWorkflowPluginTemplate.java +91 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ListPluginTemplate.java +194 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/OpPluginTemplate.java +201 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/OpenApiControllerTemplate.java +103 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/PrintPluginTemplate.java +95 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ReportFormPluginTemplate.java +257 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ReportListDataPluginTemplate.java +70 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/StandardTreeListPluginTemplate.java +130 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/TaskTemplate.java +80 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/TreeListPluginTemplate.java +152 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/WriteBackPlugInTemplate.java +286 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/attachment/AttachmentUploadBindSample.java +93 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/BotpTracePushSample.java +168 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/SampleConvertPlugin.java +223 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/cache/SampleCacheUsage.java +218 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/concurrent/SampleThreadPoolBatch.java +156 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectCrudSample.java +205 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectOpsSample.java +100 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/BeforeOperationConfirmSample.java +217 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ConfirmDialogSample.java +131 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/EntryRowCalculateSample.java +116 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/F7FilterSample.java +134 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/GetAndSetValueSample.java +176 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/HyperlinkJumpSample.java +124 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/OpenBillModalSample.java +253 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ReturnParentDataSample.java +295 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/TreeControlSample.java +140 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ViewControlOpsSample.java +132 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPluginBasicSample.java +170 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPreOpenFilterSample.java +68 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/message/MessageNotifySample.java +95 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/SampleMQConsumer.java +198 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/sample_mq.xml +15 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OpAddValidatorsSample.java +137 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OperationOptionBridgeSample.java +228 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/package-info.java +19 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BaseDataQuerySample.java +194 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java +368 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/DataSetQueryStatSample.java +131 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportFormPlugin.java +179 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportListDataPlugin.java +616 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/snippets-guide.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/task/ScheduleTaskSample.java +160 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/workflow/SampleWorkflowPlugin.java +302 -0
- package/vendor/kingdee-skills/ok-cosmic/manifest.json +78 -0
- package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +903 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/attachment-api.md +114 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/botp-convert.md +98 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/dynamic-object.md +113 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/entity-metadata.md +123 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/event-lifecycle.md +184 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/flex-prop.md +114 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/form-utils.md +133 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/operate-chain.md +159 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/plugin-base.md +218 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/query-dataset.md +149 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/request-context.md +88 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/view-handler.md +157 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-bill.md +76 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-botp.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-form.md +165 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-import.md +69 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-list.md +227 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-openapi.md +112 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-operation.md +135 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-print.md +65 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-data.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-form.md +90 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-task.md +62 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-tree-list.md +71 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-workflow.md +82 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-writeback.md +71 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-algo.md +67 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-cache.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-model-svc.md +82 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-object.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-entity-model.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-exception.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-file.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-id.md +47 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-lock.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-log.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-network-control.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-orm-access.md +78 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-request-context.md +62 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-threadpool.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-tx.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-utils.md +67 -0
- package/vendor/kingdee-skills/ok-cosmic/requirements.txt +2 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +24 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +48 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/cheat-sheet.md +256 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +140 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +222 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +94 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/platform-baseline.md +69 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +109 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +204 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +910 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +359 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +181 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +389 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +856 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +262 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +293 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +2 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +393 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +176 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +375 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +434 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +36 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +186 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +40 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +142 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic-docs.db +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic.json +13 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +18 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +53 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
- package/vendor/kingdee-skills/ok-ksql/SKILL.md +81 -0
- package/vendor/kingdee-skills/ok-ksql/agents/openai.yaml +7 -0
- package/vendor/kingdee-skills/ok-ksql/manifest.json +14 -0
- package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +452 -0
- package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +363 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""公共数据结构与工具函数,所有检查模块共享。"""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
import tree_sitter
|
|
10
|
+
import tree_sitter_java
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ──────────────────────────────────────────────
|
|
14
|
+
# 数据结构
|
|
15
|
+
# ──────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
class Severity(str, Enum):
|
|
18
|
+
ERROR = "ERROR" # 必定错误
|
|
19
|
+
WARNING = "WARNING" # 场景错配 / 强烈不推荐
|
|
20
|
+
INFO = "INFO" # 风格偏好 / 建议
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class LintIssue:
|
|
25
|
+
file: str
|
|
26
|
+
line: int
|
|
27
|
+
severity: Severity
|
|
28
|
+
rule_id: str
|
|
29
|
+
message: str
|
|
30
|
+
fix_hint: str = ""
|
|
31
|
+
source_line: str = ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class LintReport:
|
|
36
|
+
total_files: int = 0
|
|
37
|
+
total_issues: int = 0
|
|
38
|
+
errors: int = 0
|
|
39
|
+
warnings: int = 0
|
|
40
|
+
infos: int = 0
|
|
41
|
+
issues: List[LintIssue] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
def add(self, issue: LintIssue):
|
|
44
|
+
self.issues.append(issue)
|
|
45
|
+
self.total_issues += 1
|
|
46
|
+
if issue.severity == Severity.ERROR:
|
|
47
|
+
self.errors += 1
|
|
48
|
+
elif issue.severity == Severity.WARNING:
|
|
49
|
+
self.warnings += 1
|
|
50
|
+
else:
|
|
51
|
+
self.infos += 1
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ──────────────────────────────────────────────
|
|
55
|
+
# 注释 / 字符串状态机
|
|
56
|
+
# ──────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
def classify_lines(lines: List[str]) -> List[bool]:
|
|
59
|
+
"""对每行标记是否为"纯注释或空行"(True=非代码行,应跳过 lint)。
|
|
60
|
+
|
|
61
|
+
使用状态机追踪多行注释块(/* ... */),
|
|
62
|
+
单行内同时识别行注释、字符串字面量,避免误判。
|
|
63
|
+
支持行中间开始的块注释(如 `code; /* comment start`)。
|
|
64
|
+
"""
|
|
65
|
+
result: List[bool] = []
|
|
66
|
+
in_block_comment = False
|
|
67
|
+
in_text_block = False
|
|
68
|
+
|
|
69
|
+
for line in lines:
|
|
70
|
+
if in_text_block:
|
|
71
|
+
end_idx = line.find('"""')
|
|
72
|
+
if end_idx >= 0:
|
|
73
|
+
in_text_block = False
|
|
74
|
+
remainder = line[end_idx + 3:].strip()
|
|
75
|
+
result.append(not bool(remainder))
|
|
76
|
+
else:
|
|
77
|
+
result.append(True)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if in_block_comment:
|
|
81
|
+
# 当前处于 /* ... */ 块注释中
|
|
82
|
+
end_idx = line.find("*/")
|
|
83
|
+
if end_idx >= 0:
|
|
84
|
+
in_block_comment = False
|
|
85
|
+
# 关闭后如果剩余部分有实质代码,视为代码行
|
|
86
|
+
remainder = line[end_idx + 2:].strip()
|
|
87
|
+
result.append(not bool(remainder))
|
|
88
|
+
else:
|
|
89
|
+
result.append(True)
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
stripped = line.strip()
|
|
93
|
+
|
|
94
|
+
# 检查是否进入 Text Block (""")
|
|
95
|
+
if '"""' in stripped and stripped.count('"""') % 2 != 0:
|
|
96
|
+
in_text_block = True
|
|
97
|
+
result.append(False) # 开启"""的这一行本身属于代码声明
|
|
98
|
+
continue
|
|
99
|
+
stripped = line.strip()
|
|
100
|
+
# 空行
|
|
101
|
+
if not stripped:
|
|
102
|
+
result.append(True)
|
|
103
|
+
continue
|
|
104
|
+
# 纯单行注释
|
|
105
|
+
if stripped.startswith("//"):
|
|
106
|
+
result.append(True)
|
|
107
|
+
continue
|
|
108
|
+
# Javadoc / 多行注释续行(行首 *)
|
|
109
|
+
if stripped.startswith("*") and not stripped.startswith("*/"):
|
|
110
|
+
result.append(True)
|
|
111
|
+
continue
|
|
112
|
+
# 检查块注释开始(行首或行中间)
|
|
113
|
+
if stripped.startswith("/*"):
|
|
114
|
+
close_idx = stripped.find("*/", 2)
|
|
115
|
+
if close_idx >= 0:
|
|
116
|
+
# 单行块注释 /* ... */,检查 */ 后是否有代码
|
|
117
|
+
remainder = stripped[close_idx + 2:].strip()
|
|
118
|
+
result.append(not bool(remainder))
|
|
119
|
+
else:
|
|
120
|
+
in_block_comment = True
|
|
121
|
+
result.append(True)
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# 代码行:检查行中间是否有未闭合的 /* 开始块注释
|
|
125
|
+
# 需要排除字符串字面量内的 /*
|
|
126
|
+
if _has_unclosed_block_comment_start(stripped):
|
|
127
|
+
in_block_comment = True
|
|
128
|
+
# 行本身有代码部分,标记为代码行
|
|
129
|
+
result.append(False)
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# 其他情况:代码行(可能包含行尾注释,但行本身有代码)
|
|
133
|
+
result.append(False)
|
|
134
|
+
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _has_unclosed_block_comment_start(line: str) -> bool:
|
|
139
|
+
"""检测行中间是否有未闭合的 /* 块注释开始(排除字符串内的 /*)。"""
|
|
140
|
+
i = 0
|
|
141
|
+
in_str = False
|
|
142
|
+
str_char = ''
|
|
143
|
+
while i < len(line):
|
|
144
|
+
c = line[i]
|
|
145
|
+
if in_str:
|
|
146
|
+
if c == '\\' and i + 1 < len(line):
|
|
147
|
+
i += 2 # 跳过转义字符
|
|
148
|
+
continue
|
|
149
|
+
if c == str_char:
|
|
150
|
+
in_str = False
|
|
151
|
+
i += 1
|
|
152
|
+
continue
|
|
153
|
+
if c in ('"', "'"):
|
|
154
|
+
in_str = True
|
|
155
|
+
str_char = c
|
|
156
|
+
i += 1
|
|
157
|
+
continue
|
|
158
|
+
if c == '/' and i + 1 < len(line):
|
|
159
|
+
if line[i + 1] == '/':
|
|
160
|
+
return False # 行注释,不可能有 /* 了
|
|
161
|
+
if line[i + 1] == '*':
|
|
162
|
+
# 找到 /*,检查本行内是否有 */
|
|
163
|
+
close_idx = line.find('*/', i + 2)
|
|
164
|
+
if close_idx >= 0:
|
|
165
|
+
# 同行闭合 /* ... */,跳过继续
|
|
166
|
+
i = close_idx + 2
|
|
167
|
+
continue
|
|
168
|
+
else:
|
|
169
|
+
return True # 未闭合的 /* 块注释开始
|
|
170
|
+
i += 1
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def is_comment_or_string(line: str) -> bool:
|
|
175
|
+
"""粗略判断当前行是否为注释或字符串(排除误报)。
|
|
176
|
+
|
|
177
|
+
注意:此函数为单行判断,无法追踪跨行 /* */ 状态。
|
|
178
|
+
对跨行精确判断,请使用 classify_lines() 代替。
|
|
179
|
+
"""
|
|
180
|
+
stripped = line.strip()
|
|
181
|
+
if not stripped:
|
|
182
|
+
return True
|
|
183
|
+
return (stripped.startswith("//")
|
|
184
|
+
or stripped.startswith("*")
|
|
185
|
+
or stripped.startswith("/*"))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def strip_line_comment(line: str) -> str:
|
|
189
|
+
"""移除单行注释部分,保留代码主体(正确跳过字符串字面量内的 //)。"""
|
|
190
|
+
i = 0
|
|
191
|
+
in_str = False
|
|
192
|
+
str_char = ''
|
|
193
|
+
while i < len(line):
|
|
194
|
+
c = line[i]
|
|
195
|
+
if in_str:
|
|
196
|
+
if c == '\\' and i + 1 < len(line):
|
|
197
|
+
i += 2
|
|
198
|
+
continue
|
|
199
|
+
if c == str_char:
|
|
200
|
+
in_str = False
|
|
201
|
+
i += 1
|
|
202
|
+
continue
|
|
203
|
+
if c in ('"', "'"):
|
|
204
|
+
in_str = True
|
|
205
|
+
str_char = c
|
|
206
|
+
i += 1
|
|
207
|
+
continue
|
|
208
|
+
if c == '/' and i + 1 < len(line) and line[i + 1] == '/':
|
|
209
|
+
return line[:i]
|
|
210
|
+
i += 1
|
|
211
|
+
return line
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def strip_string_literals(line: str) -> str:
|
|
215
|
+
"""移除字符串字面量,便于做结构分析。"""
|
|
216
|
+
return re.sub(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'', '""', line)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def code_for_structure(line: str) -> str:
|
|
220
|
+
"""用于结构分析的代码行:去掉行注释与字符串。"""
|
|
221
|
+
return strip_line_comment(strip_string_literals(line))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
METHOD_DECL_RE = re.compile(
|
|
225
|
+
r"^\s*(?:@\w+(?:\([^)]*\))?\s*)*"
|
|
226
|
+
r"(?:public|protected|private)\s+"
|
|
227
|
+
r"(?:static\s+|final\s+|synchronized\s+|abstract\s+|native\s+|default\s+)*"
|
|
228
|
+
r"[\w<>\[\], ?.@]+\s+([A-Za-z_]\w*)\s*"
|
|
229
|
+
r"\([^;{}]*\)\s*(?:throws\s+[^{]+)?\s*(?:\{)?\s*$"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
LOOP_HEADER_RE = re.compile(r"\bfor\s*\(|\bwhile\s*\(|^\s*do\b")
|
|
233
|
+
LAMBDA_ITER_RE = re.compile(r"\.\s*(forEach|map|flatMap|peek)\s*\(")
|
|
234
|
+
CLASS_DECL_RE = re.compile(r"\bclass\s+[A-Za-z_]\w*")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def looks_like_loop_header(line: str) -> bool:
|
|
238
|
+
"""判断当前行是否看起来是 loop 头。"""
|
|
239
|
+
return bool(LOOP_HEADER_RE.search(code_for_structure(line)))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def parse_java(lines: List[str]) -> Tuple[Any, Any]:
|
|
243
|
+
"""解析 Java 源码,返回 (tree, language) 供多处复用,避免重复解析。"""
|
|
244
|
+
lang = tree_sitter.Language(tree_sitter_java.language())
|
|
245
|
+
parser = tree_sitter.Parser(lang)
|
|
246
|
+
src_bytes = "\n".join(lines).encode('utf-8')
|
|
247
|
+
tree = parser.parse(src_bytes)
|
|
248
|
+
return tree, lang
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def analyze_java_context(lines: List[str], tree=None) -> Tuple[List[Optional[str]], List[bool]]:
|
|
252
|
+
"""
|
|
253
|
+
基于 AST 构建上下文:
|
|
254
|
+
- 每行所在方法名
|
|
255
|
+
- 每行是否位于循环体中
|
|
256
|
+
"""
|
|
257
|
+
if tree is None:
|
|
258
|
+
tree, _ = parse_java(lines)
|
|
259
|
+
|
|
260
|
+
method_ctx: List[Optional[str]] = [None] * len(lines)
|
|
261
|
+
loop_ctx: List[bool] = [False] * len(lines)
|
|
262
|
+
|
|
263
|
+
def walk(node, current_method=None, in_loop=False):
|
|
264
|
+
m_name = current_method
|
|
265
|
+
if node.type in ("method_declaration", "constructor_declaration"):
|
|
266
|
+
name_node = node.child_by_field_name("name")
|
|
267
|
+
if name_node:
|
|
268
|
+
m_name = name_node.text.decode('utf-8')
|
|
269
|
+
|
|
270
|
+
is_loop = in_loop
|
|
271
|
+
if node.type in ("for_statement", "enhanced_for_statement", "while_statement", "do_statement"):
|
|
272
|
+
is_loop = True
|
|
273
|
+
elif node.type == "method_invocation":
|
|
274
|
+
name_node = node.child_by_field_name("name")
|
|
275
|
+
if name_node and name_node.text.decode('utf-8') in ("forEach", "map", "flatMap", "peek"):
|
|
276
|
+
is_loop = True
|
|
277
|
+
|
|
278
|
+
start_row = node.start_point[0]
|
|
279
|
+
end_row = node.end_point[0]
|
|
280
|
+
for r in range(start_row, end_row + 1):
|
|
281
|
+
if r < len(lines):
|
|
282
|
+
method_ctx[r] = m_name
|
|
283
|
+
if is_loop:
|
|
284
|
+
loop_ctx[r] = True
|
|
285
|
+
|
|
286
|
+
for child in node.children:
|
|
287
|
+
walk(child, m_name, is_loop)
|
|
288
|
+
|
|
289
|
+
walk(tree.root_node)
|
|
290
|
+
return method_ctx, loop_ctx
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# 操作插件的类标识模式
|
|
294
|
+
OP_PLUGIN_MARKERS = [
|
|
295
|
+
r"extends\s+AbstractOperationServicePlugIn\b",
|
|
296
|
+
r"extends\s+AbstractOperationServicePlugInExt\b",
|
|
297
|
+
r"extends\s+AbstractValidatorExt\b",
|
|
298
|
+
r"implements\s+.*OperationServicePlugIn\b",
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
# UI 插件标识模式
|
|
302
|
+
UI_PLUGIN_MARKERS = [
|
|
303
|
+
r"extends\s+AbstractFormPluginExt\b",
|
|
304
|
+
r"extends\s+AbstractBillPlugInExt\b",
|
|
305
|
+
r"extends\s+AbstractListPluginExt\b",
|
|
306
|
+
r"extends\s+AbstractFormPlugin\b",
|
|
307
|
+
r"extends\s+AbstractBillPlugIn\b",
|
|
308
|
+
r"extends\s+AbstractListPlugin\b",
|
|
309
|
+
r"extends\s+AbstractTreeListPlugin\b",
|
|
310
|
+
r"extends\s+StandardTreeListPlugin\b",
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
# 其他继承型插件标识模式(非操作、非 UI,但仍需检查 super 调用)
|
|
314
|
+
OTHER_INHERITABLE_MARKERS = [
|
|
315
|
+
r"extends\s+AbstractConvertPlugIn\b",
|
|
316
|
+
r"extends\s+AbstractWriteBackPlugIn\b",
|
|
317
|
+
r"extends\s+AbstractReportFormPlugin\b",
|
|
318
|
+
r"extends\s+AbstractReportListDataPlugin\b",
|
|
319
|
+
r"extends\s+AbstractPrintPlugin\b",
|
|
320
|
+
r"extends\s+AbstractTask\b",
|
|
321
|
+
r"extends\s+BatchImportPlugin\b",
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def detect_plugin_type(lines: List[str]) -> Tuple[bool, bool, bool]:
|
|
326
|
+
"""检测文件是操作插件、UI 插件还是其他继承型插件,返回 (is_op, is_ui, is_other_inheritable)"""
|
|
327
|
+
full_text = "\n".join(lines)
|
|
328
|
+
is_op = any(re.search(p, full_text) for p in OP_PLUGIN_MARKERS)
|
|
329
|
+
is_ui = any(re.search(p, full_text) for p in UI_PLUGIN_MARKERS)
|
|
330
|
+
is_other = any(re.search(p, full_text) for p in OTHER_INHERITABLE_MARKERS)
|
|
331
|
+
return is_op, is_ui, is_other
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _detect_plugin_kind(text: str) -> Optional[str]:
|
|
335
|
+
"""基于类声明文本判断当前类属于哪种插件类型。"""
|
|
336
|
+
if any(re.search(p, text) for p in OP_PLUGIN_MARKERS):
|
|
337
|
+
return "op"
|
|
338
|
+
if any(re.search(p, text) for p in UI_PLUGIN_MARKERS):
|
|
339
|
+
return "ui"
|
|
340
|
+
if any(re.search(p, text) for p in OTHER_INHERITABLE_MARKERS):
|
|
341
|
+
return "other"
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def analyze_plugin_context(lines: List[str]) -> List[Optional[str]]:
|
|
346
|
+
"""
|
|
347
|
+
基于类声明与 brace 深度分析每一行所在的插件上下文:
|
|
348
|
+
- op: 操作插件类
|
|
349
|
+
- ui: UI 插件类
|
|
350
|
+
- other: 其他继承型插件类
|
|
351
|
+
- None: 非插件类 / 无插件上下文
|
|
352
|
+
"""
|
|
353
|
+
contexts: List[Optional[str]] = [None] * len(lines)
|
|
354
|
+
brace_depth = 0
|
|
355
|
+
class_stack: List[Tuple[int, Optional[str]]] = []
|
|
356
|
+
pending_class_decl: List[str] = []
|
|
357
|
+
|
|
358
|
+
for i, line in enumerate(lines):
|
|
359
|
+
code = code_for_structure(line)
|
|
360
|
+
stripped = code.strip()
|
|
361
|
+
contexts[i] = class_stack[-1][1] if class_stack else None
|
|
362
|
+
|
|
363
|
+
if pending_class_decl:
|
|
364
|
+
if stripped:
|
|
365
|
+
pending_class_decl.append(stripped)
|
|
366
|
+
if "{" in stripped:
|
|
367
|
+
class_text = " ".join(pending_class_decl)
|
|
368
|
+
class_stack.append((brace_depth + 1, _detect_plugin_kind(class_text)))
|
|
369
|
+
pending_class_decl = []
|
|
370
|
+
elif CLASS_DECL_RE.search(stripped):
|
|
371
|
+
pending_class_decl = [stripped]
|
|
372
|
+
if "{" in stripped:
|
|
373
|
+
class_text = " ".join(pending_class_decl)
|
|
374
|
+
class_stack.append((brace_depth + 1, _detect_plugin_kind(class_text)))
|
|
375
|
+
pending_class_decl = []
|
|
376
|
+
|
|
377
|
+
brace_depth += code.count("{") - code.count("}")
|
|
378
|
+
|
|
379
|
+
while class_stack and brace_depth < class_stack[-1][0]:
|
|
380
|
+
class_stack.pop()
|
|
381
|
+
|
|
382
|
+
return contexts
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def detect_listener_interfaces(lines: List[str]) -> List[str]:
|
|
386
|
+
"""检测文件实现了哪些 Listener 接口"""
|
|
387
|
+
listeners = []
|
|
388
|
+
for line in lines:
|
|
389
|
+
m = re.search(r"implements\s+(.+?)(?:\s*\{|$)", line)
|
|
390
|
+
if m:
|
|
391
|
+
ifaces = [s.strip() for s in m.group(1).split(",")]
|
|
392
|
+
listeners.extend(i for i in ifaces if "Listener" in i)
|
|
393
|
+
return listeners
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""资源管理检查 (RESOURCE-*) — 来源: coding-preferences.md"""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Dict, Optional, Set, Tuple
|
|
6
|
+
|
|
7
|
+
from .base import LintIssue, Severity, analyze_java_context, code_for_structure
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# 资源持有规则列表
|
|
11
|
+
RESOURCE_RULES = [
|
|
12
|
+
{
|
|
13
|
+
"pattern": r"(private|protected|public)\s+(DataSet|InputStream|OutputStream|Connection|ResultSet)\s+\w+\s*;",
|
|
14
|
+
"rule_id": "RESOURCE-001",
|
|
15
|
+
"severity": Severity.WARNING,
|
|
16
|
+
"message": "插件成员变量不应持有 DataSet/InputStream 等资源对象,有序列化问题",
|
|
17
|
+
"fix_hint": "在方法内使用后立即关闭,不持有引用",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"pattern": r"static\s+(?!final\b)\w+\s+\w+\s*=",
|
|
21
|
+
"rule_id": "RESOURCE-002",
|
|
22
|
+
"severity": Severity.WARNING,
|
|
23
|
+
"message": "不应使用非 final 的 static 变量存储状态,多实例会冲突",
|
|
24
|
+
"fix_hint": "使用 PageCache 或实例变量",
|
|
25
|
+
"exclude_pattern": r"(static\s+final|static\s+\w+\s+[A-Z_]+\s*=|LogFactory|getLogger|Log\s+log)",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"pattern": r"static\s+final\s+String\s+\w+\s*=\s*ResManager\s*\.\s*loadKDString\s*\(",
|
|
29
|
+
"rule_id": "RESOURCE-003",
|
|
30
|
+
"severity": Severity.WARNING,
|
|
31
|
+
"message": "不要将 ResManager.loadKDString(...) 固化为 static final 常量,运行时语言切换会失效",
|
|
32
|
+
"fix_hint": "改为方法内实时调用 ResManager.loadKDString(...)",
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
DATASET_VAR_DECL_PATTERN = re.compile(r"\bDataSet\s+([A-Za-z_]\w*)\s*=")
|
|
37
|
+
DATASET_CLOSE_PATTERN = re.compile(r"\b([A-Za-z_]\w*)\s*\.\s*close\s*\(")
|
|
38
|
+
DATASET_TRY_WITH_RESOURCE_PATTERN = re.compile(r"\btry\s*\(\s*DataSet\s+([A-Za-z_]\w*)\s*=")
|
|
39
|
+
DATASET_RETURN_PATTERN = re.compile(r"\breturn\s+([A-Za-z_]\w*)\s*;")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _collect_method_ranges(
|
|
43
|
+
method_context: List[Optional[str]], total: int
|
|
44
|
+
) -> List[Tuple[Optional[str], int, int]]:
|
|
45
|
+
"""从 method_context 收集连续同方法行范围 [(method_name, start, end), ...]。"""
|
|
46
|
+
ranges: List[Tuple[Optional[str], int, int]] = []
|
|
47
|
+
if not method_context:
|
|
48
|
+
return ranges
|
|
49
|
+
cur_method = method_context[0]
|
|
50
|
+
start = 0
|
|
51
|
+
for i in range(1, total):
|
|
52
|
+
if method_context[i] != cur_method:
|
|
53
|
+
ranges.append((cur_method, start, i))
|
|
54
|
+
cur_method = method_context[i]
|
|
55
|
+
start = i
|
|
56
|
+
ranges.append((cur_method, start, total))
|
|
57
|
+
return ranges
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def check(filepath: str, lines: List[str]) -> List[LintIssue]:
|
|
61
|
+
"""执行资源管理检查,返回问题列表。"""
|
|
62
|
+
issues: List[LintIssue] = []
|
|
63
|
+
method_context, _ = analyze_java_context(lines)
|
|
64
|
+
|
|
65
|
+
# ── 逐行:RESOURCE-001 / 002 / 003(文件级即可) ──
|
|
66
|
+
for i, line in enumerate(lines):
|
|
67
|
+
lineno = i + 1
|
|
68
|
+
code_line = code_for_structure(line)
|
|
69
|
+
for rule in RESOURCE_RULES:
|
|
70
|
+
exclude = rule.get("exclude_pattern")
|
|
71
|
+
if exclude and re.search(exclude, code_line):
|
|
72
|
+
continue
|
|
73
|
+
if re.search(rule["pattern"], code_line):
|
|
74
|
+
issues.append(LintIssue(
|
|
75
|
+
file=filepath, line=lineno,
|
|
76
|
+
severity=rule["severity"],
|
|
77
|
+
rule_id=rule["rule_id"],
|
|
78
|
+
message=rule["message"],
|
|
79
|
+
fix_hint=rule["fix_hint"],
|
|
80
|
+
source_line=line.strip(),
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
# ── RESOURCE-004:按方法级隔离追踪 DataSet 生命周期 ──
|
|
84
|
+
method_ranges = _collect_method_ranges(method_context, len(lines))
|
|
85
|
+
for method_name, m_start, m_end in method_ranges:
|
|
86
|
+
if method_name is None:
|
|
87
|
+
continue
|
|
88
|
+
_check_dataset_lifecycle(filepath, lines, m_start, m_end, issues)
|
|
89
|
+
|
|
90
|
+
return issues
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _check_dataset_lifecycle(
|
|
94
|
+
filepath: str, lines: List[str], m_start: int, m_end: int,
|
|
95
|
+
issues: List[LintIssue],
|
|
96
|
+
) -> None:
|
|
97
|
+
"""在一个方法范围内追踪 DataSet 声明/关闭/消费状态。"""
|
|
98
|
+
dataset_vars: Dict[str, int] = {} # name -> declaration line
|
|
99
|
+
closed_vars: Set[str] = set()
|
|
100
|
+
consumed_vars: Set[str] = set()
|
|
101
|
+
in_try_resource = False
|
|
102
|
+
try_paren_depth = 0
|
|
103
|
+
# 用于跨行消费检测:记录最近的 DataSet 声明变量名
|
|
104
|
+
pending_dataset_decl: Optional[str] = None
|
|
105
|
+
|
|
106
|
+
for i in range(m_start, m_end):
|
|
107
|
+
lineno = i + 1
|
|
108
|
+
code_line = code_for_structure(lines[i])
|
|
109
|
+
|
|
110
|
+
# ── 声明检测 ──
|
|
111
|
+
decl_match = DATASET_VAR_DECL_PATTERN.search(code_line)
|
|
112
|
+
if decl_match:
|
|
113
|
+
var_name = decl_match.group(1)
|
|
114
|
+
# 同方法内同名变量取最新声明行
|
|
115
|
+
dataset_vars[var_name] = lineno
|
|
116
|
+
pending_dataset_decl = var_name
|
|
117
|
+
|
|
118
|
+
# 同行 RHS 消费检测:DataSet result = ds1.union(ds2)
|
|
119
|
+
eq_idx = code_line.find("=", code_line.find(var_name))
|
|
120
|
+
if eq_idx >= 0:
|
|
121
|
+
rhs = code_line[eq_idx + 1:]
|
|
122
|
+
for tracked_var in list(dataset_vars):
|
|
123
|
+
if tracked_var != var_name and re.search(
|
|
124
|
+
rf"\b{re.escape(tracked_var)}\b", rhs
|
|
125
|
+
):
|
|
126
|
+
consumed_vars.add(tracked_var)
|
|
127
|
+
# 派生链传递:如果源被管理(在 try 块或已被标记为消费),派生变量同享管理
|
|
128
|
+
if tracked_var in closed_vars or tracked_var in consumed_vars:
|
|
129
|
+
consumed_vars.add(var_name)
|
|
130
|
+
else:
|
|
131
|
+
# ── 跨行消费检测:上一行 DataSet xxx = ds1,本行 .union(ds2) ──
|
|
132
|
+
if pending_dataset_decl and code_line.strip().startswith("."):
|
|
133
|
+
for tracked_var in list(dataset_vars):
|
|
134
|
+
if tracked_var != pending_dataset_decl and re.search(
|
|
135
|
+
rf"\b{re.escape(tracked_var)}\b", code_line
|
|
136
|
+
):
|
|
137
|
+
consumed_vars.add(tracked_var)
|
|
138
|
+
if tracked_var in closed_vars or tracked_var in consumed_vars:
|
|
139
|
+
consumed_vars.add(pending_dataset_decl)
|
|
140
|
+
else:
|
|
141
|
+
pending_dataset_decl = None
|
|
142
|
+
|
|
143
|
+
# ── try-with-resources 检测 ──
|
|
144
|
+
if re.search(r"\btry\s*\(", code_line):
|
|
145
|
+
in_try_resource = True
|
|
146
|
+
|
|
147
|
+
if in_try_resource:
|
|
148
|
+
try_paren_depth += code_line.count("(") - code_line.count(")")
|
|
149
|
+
for vn in DATASET_VAR_DECL_PATTERN.findall(code_line):
|
|
150
|
+
closed_vars.add(vn)
|
|
151
|
+
if try_paren_depth <= 0:
|
|
152
|
+
in_try_resource = False
|
|
153
|
+
try_paren_depth = 0
|
|
154
|
+
|
|
155
|
+
# ── .close() 检测 ──
|
|
156
|
+
for close_var in DATASET_CLOSE_PATTERN.findall(code_line):
|
|
157
|
+
closed_vars.add(close_var)
|
|
158
|
+
|
|
159
|
+
# ── return 消费检测 ──
|
|
160
|
+
return_match = DATASET_RETURN_PATTERN.search(code_line)
|
|
161
|
+
if return_match and return_match.group(1) in dataset_vars:
|
|
162
|
+
consumed_vars.add(return_match.group(1))
|
|
163
|
+
|
|
164
|
+
# ── 汇总本方法的 RESOURCE-004 ──
|
|
165
|
+
for var_name, decl_line in dataset_vars.items():
|
|
166
|
+
if var_name in closed_vars or var_name in consumed_vars:
|
|
167
|
+
continue
|
|
168
|
+
issues.append(LintIssue(
|
|
169
|
+
file=filepath,
|
|
170
|
+
line=decl_line,
|
|
171
|
+
severity=Severity.ERROR,
|
|
172
|
+
rule_id="RESOURCE-004",
|
|
173
|
+
message=f"检测到 DataSet 变量 `{var_name}` 但未发现 close() 调用,也未被 return 或后续 DataSet 计算消费",
|
|
174
|
+
fix_hint="在最靠近使用完成的位置调用 close();若该 DataSet 是方法返回值或已参与计算/合并则无需关闭",
|
|
175
|
+
source_line=lines[decl_line - 1].strip(),
|
|
176
|
+
))
|