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,434 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""编码偏好检查 (STYLE-*) — 来源: coding-preferences.md"""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from .base import (
|
|
8
|
+
LintIssue,
|
|
9
|
+
Severity,
|
|
10
|
+
analyze_java_context,
|
|
11
|
+
analyze_plugin_context,
|
|
12
|
+
classify_lines,
|
|
13
|
+
code_for_structure,
|
|
14
|
+
detect_plugin_type,
|
|
15
|
+
looks_like_loop_header,
|
|
16
|
+
strip_line_comment,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# 编码偏好规则列表
|
|
21
|
+
STYLE_RULES = [
|
|
22
|
+
{
|
|
23
|
+
"pattern": r"\bStringUtils\s*\.\s*(isBlank|isNotBlank|isEmpty|isNotEmpty|equals)\b",
|
|
24
|
+
"rule_id": "STYLE-001",
|
|
25
|
+
"severity": Severity.WARNING,
|
|
26
|
+
"message": "字符串判空应使用 CharSequenceUtils 而非 StringUtils",
|
|
27
|
+
"fix_hint": "使用 CharSequenceUtils.isBlank() / isNotBlank()",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"pattern": r"(?:!=\s*null\s*&&\s*!\w+\.isEmpty\(\))|(?:==\s*null\s*\|\|\s*\w+\.isEmpty\(\))",
|
|
31
|
+
"rule_id": "STYLE-002",
|
|
32
|
+
"severity": Severity.WARNING,
|
|
33
|
+
"message": "手写 null/isEmpty 判空应使用工具类封装",
|
|
34
|
+
"fix_hint": "字符串用 CharSequenceUtils.isBlank() / isNotBlank();集合用 CollectionUtils.isEmpty() / isNotEmpty()",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"pattern": r"\bOperationServiceHelper\s*\.\s*(save|submit|audit)\b",
|
|
38
|
+
"rule_id": "STYLE-003",
|
|
39
|
+
"severity": Severity.WARNING,
|
|
40
|
+
"message": "不应散落调用 OperationServiceHelper,缺少错误聚合",
|
|
41
|
+
"fix_hint": "使用 OpUtils.executeOperateOrThrow() 或 OperateChain",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"pattern": r"new\s+PushArgs\s*\(",
|
|
45
|
+
"rule_id": "STYLE-004",
|
|
46
|
+
"severity": Severity.WARNING,
|
|
47
|
+
"message": "不应手拼 PushArgs,有封装可用",
|
|
48
|
+
"fix_hint": "使用 BotpUtils 封装下推逻辑",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"pattern": r"new\s+DrawArgs\s*\(",
|
|
52
|
+
"rule_id": "STYLE-005",
|
|
53
|
+
"severity": Severity.WARNING,
|
|
54
|
+
"message": "不应手拼 DrawArgs,有封装可用",
|
|
55
|
+
"fix_hint": "使用 BotpUtils 封装选单逻辑",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"pattern": r"\.\s*get\(\s*\"[\w]+\.[\w.]+\"",
|
|
59
|
+
"rule_id": "STYLE-006",
|
|
60
|
+
"severity": Severity.WARNING,
|
|
61
|
+
"message": "不应直接深链式 .get(\"a.b.c\"),中间节点为空时会抛异常",
|
|
62
|
+
"fix_hint": "使用 DynamicObjectUtils.nullSafeGet(dynamicObject, \"field\")",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"pattern": r"\bAttachmentServiceHelper\s*\.",
|
|
66
|
+
"rule_id": "STYLE-007",
|
|
67
|
+
"severity": Severity.WARNING,
|
|
68
|
+
"message": "附件处理应优先使用 AttachmentUtils 封装",
|
|
69
|
+
"fix_hint": "使用 AttachmentUtils 和 uploader",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"pattern": r"\bQueryServiceHelper\s*\.\s*queryOne\s*\([^)]*\)\s*(?:!=|==)\s*null",
|
|
73
|
+
"rule_id": "STYLE-008",
|
|
74
|
+
"severity": Severity.WARNING,
|
|
75
|
+
"message": "判断数据是否存在时,优先使用 QueryServiceHelper.exists(...)",
|
|
76
|
+
"fix_hint": "将 queryOne(...) != null / == null 改为 QueryServiceHelper.exists(...)",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"pattern": r"\bprintStackTrace\s*\(",
|
|
80
|
+
"rule_id": "STYLE-009",
|
|
81
|
+
"severity": Severity.ERROR,
|
|
82
|
+
"message": "不要直接调用 printStackTrace(),应使用统一日志框架",
|
|
83
|
+
"fix_hint": "改为 logger.error(\"错误分析描述\", e) 或插件内的 log.error(...)",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"pattern": r"\bthrow\s+new\s+(?:(?:java\.lang\.)?RuntimeException|(?:java\.lang\.)?IllegalArgumentException|(?:java\.lang\.)?IllegalStateException)\b",
|
|
87
|
+
"rule_id": "STYLE-018",
|
|
88
|
+
"severity": Severity.ERROR,
|
|
89
|
+
"message": "业务异常应统一使用 KDBizException,不要直接抛 RuntimeException/IllegalArgumentException/IllegalStateException",
|
|
90
|
+
"fix_hint": "改为 throw new KDBizException(new ErrorCode(...));若为包装异常,保留原始 cause",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"pattern": r"\bnew\s+Thread\s*\(",
|
|
94
|
+
"rule_id": "STYLE-019",
|
|
95
|
+
"severity": Severity.WARNING,
|
|
96
|
+
"message": "禁止直接 new Thread(),绕开了平台线程池的统一监控与资源回收",
|
|
97
|
+
"fix_hint": "使用 kd.bos.threads.ThreadPools.newXxx() 或 ThreadPools.executeOnceXxx()",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"pattern": r"\bExecutors\s*\.\s*(newFixedThreadPool|newCachedThreadPool|newSingleThreadExecutor|newScheduledThreadPool)\b",
|
|
101
|
+
"rule_id": "STYLE-020",
|
|
102
|
+
"severity": Severity.WARNING,
|
|
103
|
+
"message": "禁止直接使用 JDK Executors 创建线程池,无法遵守平台线程治理约束",
|
|
104
|
+
"fix_hint": "使用 kd.bos.threads.ThreadPools.newXxx() 或 ThreadPools.executeOnceXxx()",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"pattern": r"\bSerializationUtils\s*\.\s*toJsonString\s*\([^)]*\b(args|e|event|evt|dataEntity|dataEntities|view)\b",
|
|
108
|
+
"rule_id": "STYLE-021",
|
|
109
|
+
"severity": Severity.WARNING,
|
|
110
|
+
"message": "不要对页面对象/事件对象/数据对象整包 JSON 序列化打印,成本高且可能打印大对象",
|
|
111
|
+
"fix_hint": "只按需提取关键字段打印,如 log.info(\"billNo={}\", data.getString(\"billno\"))",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"pattern": r"\bBusinessDataServiceHelper\s*\.\s*load\s*\([^,]+,\s*(?:EntityUtils\.getMainEntityType|BusinessDataServiceHelper\.newDynamicObject|EntityMetadataCache\.getDataEntityType)",
|
|
115
|
+
"rule_id": "STYLE-025",
|
|
116
|
+
"severity": Severity.WARNING,
|
|
117
|
+
"message": "加载实体时应指定必要字段,避免全量加载",
|
|
118
|
+
"fix_hint": "使用 BusinessDataServiceHelper.load(entityName, selectFields, filters) 指定查询字段",
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# 主键/id 判空检测(STYLE-026)
|
|
123
|
+
# 检测模式:xxxId/xxxPk 变量与 null/0/0L 比较,或 .get("id"/"xxx_id") 与 null 比较
|
|
124
|
+
PK_NULL_CHECK_PATTERNS = [
|
|
125
|
+
# xxxId == null / xxxId != null / xxxPk == null / xxxPk != null
|
|
126
|
+
re.compile(r'\b\w*(?:[Ii]d|[Pp]k)\s*(?:==|!=)\s*null\b'),
|
|
127
|
+
# xxxId == 0L / xxxId == 0 / xxxId <= 0L / xxxId <= 0 / xxxId < 1
|
|
128
|
+
re.compile(r'\b\w*(?:[Ii]d|[Pp]k)\s*(?:==|<=|<)\s*(?:0L?|1)\b'),
|
|
129
|
+
# .get("id") == null / .get("id") != null
|
|
130
|
+
re.compile(r'\.\s*get\s*\(\s*"\w*[Ii]d"\s*\)\s*(?:==|!=)\s*null'),
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
# BigDecimal 原生运算检测(STYLE-027)
|
|
134
|
+
# 检测加减乘除 + compareTo 比较
|
|
135
|
+
BIGDECIMAL_RAW_PATTERNS = [
|
|
136
|
+
# 链式 .add/.subtract/.multiply/.divide + BigDecimal/new 上下文
|
|
137
|
+
re.compile(r'\.\s*(?:add|subtract|multiply|divide)\s*\(\s*(?:new\s+BigDecimal|BigDecimal\.)'),
|
|
138
|
+
# .divide(..., RoundingMode) —— 有 RoundingMode 参数必定是 BigDecimal 除法
|
|
139
|
+
re.compile(r'\.\s*divide\s*\([^)]*RoundingMode'),
|
|
140
|
+
# .compareTo(BigDecimal → BigDecimalUtils.equals/largeThan
|
|
141
|
+
re.compile(r'\.\s*compareTo\s*\(\s*BigDecimal'),
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# QFilter 字符串运算符检测(P0:编译通过但运行时崩溃)
|
|
145
|
+
QFILTER_STRING_OP_PATTERN = re.compile(
|
|
146
|
+
r'new\s+QFilter\s*\([^,]+,\s*"[^"]*"\s*,'
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
SQL_CONCAT_PATTERN = re.compile(
|
|
150
|
+
r'("[^"]*\b(select|insert|update|delete|from|where)\b[^"]*"\s*\+)'
|
|
151
|
+
r'|(\+\s*"[^"]*\b(from|where|and|or|order\s+by|group\s+by)\b[^"]*")',
|
|
152
|
+
re.IGNORECASE,
|
|
153
|
+
)
|
|
154
|
+
SQL_DIALECT_PATTERN = re.compile(
|
|
155
|
+
r'"[^"]*\b(select|insert|update|delete)\b[^"]*\b(limit|rownum|nvl|isnull|ifnull|top\s+\d+)\b[^"]*"',
|
|
156
|
+
re.IGNORECASE,
|
|
157
|
+
)
|
|
158
|
+
CHINESE_CONCAT_PATTERN = re.compile(
|
|
159
|
+
r'("[^"]*[\u4e00-\u9fff][^"]*"\s*\+)|(\+\s*"[^"]*[\u4e00-\u9fff][^"]*")'
|
|
160
|
+
)
|
|
161
|
+
LOOP_UPDATE_VIEW_PATTERN = re.compile(r"\bupdateView\s*\(")
|
|
162
|
+
LOOP_DB_PATTERN = re.compile(
|
|
163
|
+
r"\b(BusinessDataServiceHelper|QueryServiceHelper|SaveServiceHelper)\s*\.\s*"
|
|
164
|
+
r"(load|loadSingle|loadSingleFromCache|loadFromCache|query|queryOne|queryDataSet|exists?|save|update)\b"
|
|
165
|
+
)
|
|
166
|
+
LOOP_DB_HELPER_PATTERN = re.compile(r"\b(BusinessDataServiceHelper|QueryServiceHelper|SaveServiceHelper)\b")
|
|
167
|
+
LOOP_DB_METHOD_PATTERN = re.compile(
|
|
168
|
+
r"\.\s*(load|loadSingle|loadSingleFromCache|loadFromCache|query|queryOne|queryDataSet|exists?|save|update)\b"
|
|
169
|
+
)
|
|
170
|
+
LOOP_REDIS_PATTERN = re.compile(r"\b(RedisTemplate|StringRedisTemplate|Jedis|Redisson|redisTemplate|redisClient)\b")
|
|
171
|
+
LOOP_ORM_CREATE_PATTERN = re.compile(r"\bORM\s*\.\s*create\s*\(")
|
|
172
|
+
LOOP_DISPATCH_PATTERN = re.compile(r"\bDispatchServiceHelper\s*\.\s*invoke\w*\s*\(")
|
|
173
|
+
QUERY_RESULT_DECL_PATTERN = re.compile(
|
|
174
|
+
r"\b(?:DynamicObjectCollection|var)\s+([A-Za-z_]\w*)\s*=\s*QueryServiceHelper\s*\.\s*query\b"
|
|
175
|
+
)
|
|
176
|
+
QUERY_RESULT_HELPER_DECL_PATTERN = re.compile(
|
|
177
|
+
r"\b(?:DynamicObjectCollection|var)\s+([A-Za-z_]\w*)\s*=\s*QueryServiceHelper\b"
|
|
178
|
+
)
|
|
179
|
+
QUERY_RESULT_METHOD_PATTERN = re.compile(r"\.\s*query\b")
|
|
180
|
+
QUERY_RESULT_GET_ALIAS_PATTERN = re.compile(
|
|
181
|
+
r"\b(?:DynamicObject|var)\s+([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\s*\.\s*get\s*\("
|
|
182
|
+
)
|
|
183
|
+
ENHANCED_FOR_PATTERN = re.compile(
|
|
184
|
+
r"\bfor\s*\(\s*(?:final\s+)?(?:DynamicObject|var)\s+([A-Za-z_]\w*)\s*:\s*([A-Za-z_]\w*)\s*\)"
|
|
185
|
+
)
|
|
186
|
+
SAVE_SERVICE_PATTERN = re.compile(r"\bSaveServiceHelper\s*\.\s*(save|update)\s*\(")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def check(filepath: str, lines: List[str]) -> List[LintIssue]:
|
|
190
|
+
"""执行编码偏好检查,返回问题列表。"""
|
|
191
|
+
issues: List[LintIssue] = []
|
|
192
|
+
_, loop_context = analyze_java_context(lines)
|
|
193
|
+
plugin_context = analyze_plugin_context(lines)
|
|
194
|
+
skip_lines = classify_lines(lines)
|
|
195
|
+
query_result_vars: dict[str, int] = {}
|
|
196
|
+
query_result_entity_vars: dict[str, int] = {}
|
|
197
|
+
active_query_loop_vars: dict[str, int] = {}
|
|
198
|
+
single_line_query_loop_vars: dict[str, int] = {}
|
|
199
|
+
brace_depth = 0
|
|
200
|
+
|
|
201
|
+
for i, line in enumerate(lines):
|
|
202
|
+
lineno = i + 1
|
|
203
|
+
code_line = code_for_structure(line)
|
|
204
|
+
raw_code_line = strip_line_comment(line)
|
|
205
|
+
prev_code_line = code_for_structure(lines[i - 1]) if i > 0 else ""
|
|
206
|
+
current_depth = brace_depth
|
|
207
|
+
active_query_loop_vars = {
|
|
208
|
+
var_name: depth
|
|
209
|
+
for var_name, depth in active_query_loop_vars.items()
|
|
210
|
+
if current_depth >= depth
|
|
211
|
+
}
|
|
212
|
+
single_line_query_loop_vars = {
|
|
213
|
+
var_name: target_line
|
|
214
|
+
for var_name, target_line in single_line_query_loop_vars.items()
|
|
215
|
+
if lineno <= target_line
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
decl_match = QUERY_RESULT_DECL_PATTERN.search(code_line)
|
|
219
|
+
if decl_match:
|
|
220
|
+
query_result_vars.setdefault(decl_match.group(1), lineno)
|
|
221
|
+
elif QUERY_RESULT_METHOD_PATTERN.search(code_line):
|
|
222
|
+
helper_decl_match = QUERY_RESULT_HELPER_DECL_PATTERN.search(prev_code_line)
|
|
223
|
+
if helper_decl_match:
|
|
224
|
+
query_result_vars.setdefault(helper_decl_match.group(1), lineno - 1)
|
|
225
|
+
|
|
226
|
+
alias_match = QUERY_RESULT_GET_ALIAS_PATTERN.search(code_line)
|
|
227
|
+
if alias_match and alias_match.group(2) in query_result_vars:
|
|
228
|
+
query_result_entity_vars.setdefault(alias_match.group(1), query_result_vars[alias_match.group(2)])
|
|
229
|
+
|
|
230
|
+
for_match = ENHANCED_FOR_PATTERN.search(code_line)
|
|
231
|
+
if for_match and for_match.group(2) in query_result_vars:
|
|
232
|
+
loop_var = for_match.group(1)
|
|
233
|
+
if re.search(rf"\b{re.escape(loop_var)}\s*\.\s*set\s*\(", code_line):
|
|
234
|
+
query_result_entity_vars.setdefault(loop_var, query_result_vars[for_match.group(2)])
|
|
235
|
+
elif "{" in code_line:
|
|
236
|
+
active_query_loop_vars[loop_var] = current_depth + 1
|
|
237
|
+
else:
|
|
238
|
+
single_line_query_loop_vars[loop_var] = lineno + 1
|
|
239
|
+
|
|
240
|
+
if skip_lines[i]:
|
|
241
|
+
brace_depth += code_line.count("{") - code_line.count("}")
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
loop_line = loop_context[i] or looks_like_loop_header(line)
|
|
245
|
+
for rule in STYLE_RULES:
|
|
246
|
+
exclude = rule.get("exclude_pattern")
|
|
247
|
+
match_source = raw_code_line if rule["rule_id"] == "STYLE-006" else code_line
|
|
248
|
+
if exclude and re.search(exclude, match_source):
|
|
249
|
+
continue
|
|
250
|
+
if re.search(rule["pattern"], match_source):
|
|
251
|
+
issues.append(LintIssue(
|
|
252
|
+
file=filepath, line=lineno,
|
|
253
|
+
severity=rule["severity"],
|
|
254
|
+
rule_id=rule["rule_id"],
|
|
255
|
+
message=rule["message"],
|
|
256
|
+
fix_hint=rule["fix_hint"],
|
|
257
|
+
source_line=line.strip(),
|
|
258
|
+
))
|
|
259
|
+
|
|
260
|
+
if plugin_context[i] == "op" and re.search(r"\ballFields\s*\(", code_line):
|
|
261
|
+
issues.append(LintIssue(
|
|
262
|
+
file=filepath, line=lineno,
|
|
263
|
+
severity=Severity.WARNING,
|
|
264
|
+
rule_id="STYLE-010",
|
|
265
|
+
message="操作插件里除非字段很多,否则不要直接使用 allFields()",
|
|
266
|
+
fix_hint="按实际业务显式准备字段,优先 e.getFieldKeys().add(...) 或 entryFields(...)",
|
|
267
|
+
source_line=line.strip(),
|
|
268
|
+
))
|
|
269
|
+
|
|
270
|
+
if SQL_CONCAT_PATTERN.search(line):
|
|
271
|
+
# 降低误报:移除所有字符串字面量后,检查 + 附近是否有变量/标识符
|
|
272
|
+
# 纯字面量拼接(如 "Please select" + " an option")不构成 SQL 注入风险
|
|
273
|
+
line_without_strings = re.sub(r'"[^"]*"', '', line)
|
|
274
|
+
has_var_concat = bool(re.search(r'\w\s*\+|\+\s*\w', line_without_strings))
|
|
275
|
+
if has_var_concat:
|
|
276
|
+
issues.append(LintIssue(
|
|
277
|
+
file=filepath, line=lineno,
|
|
278
|
+
severity=Severity.ERROR,
|
|
279
|
+
rule_id="STYLE-011",
|
|
280
|
+
message="SQL/KSQL 传参不要通过字符串拼接构造",
|
|
281
|
+
fix_hint="改为参数化查询或平台查询构造,避免手拼 SQL 条件",
|
|
282
|
+
source_line=line.strip(),
|
|
283
|
+
))
|
|
284
|
+
|
|
285
|
+
if SQL_DIALECT_PATTERN.search(line):
|
|
286
|
+
issues.append(LintIssue(
|
|
287
|
+
file=filepath, line=lineno,
|
|
288
|
+
severity=Severity.ERROR,
|
|
289
|
+
rule_id="STYLE-012",
|
|
290
|
+
message="检测到可能的数据库方言 SQL 关键字,跨库兼容性差",
|
|
291
|
+
fix_hint="改为 KSQL 或平台查询接口,避免 limit/rownum/nvl/isnull/top 等方言写法",
|
|
292
|
+
source_line=line.strip(),
|
|
293
|
+
))
|
|
294
|
+
|
|
295
|
+
# STYLE-024: QFilter 使用字符串运算符代替 QCP 枚举(编译通过但运行时崩溃)
|
|
296
|
+
if QFILTER_STRING_OP_PATTERN.search(raw_code_line):
|
|
297
|
+
issues.append(LintIssue(
|
|
298
|
+
file=filepath, line=lineno,
|
|
299
|
+
severity=Severity.ERROR,
|
|
300
|
+
rule_id="STYLE-024",
|
|
301
|
+
message="QFilter 第二个参数必须使用 QCP 枚举,不能用字符串(编译通过但运行时崩溃)",
|
|
302
|
+
fix_hint='\u5c06 new QFilter(field, "=", value) \u6539\u4e3a new QFilter(field, QCP.equals, value)\uff1b\u5e38\u7528\u679a\u4e3e: QCP.equals / not_equals / large_equals / less_equals / like / in',
|
|
303
|
+
source_line=line.strip(),
|
|
304
|
+
))
|
|
305
|
+
|
|
306
|
+
# STYLE-027: BigDecimal 原生运算,应优先使用 BigDecimalUtils
|
|
307
|
+
if 'BigDecimalUtils' not in code_line:
|
|
308
|
+
for bd_pat in BIGDECIMAL_RAW_PATTERNS:
|
|
309
|
+
if bd_pat.search(code_line):
|
|
310
|
+
issues.append(LintIssue(
|
|
311
|
+
file=filepath, line=lineno,
|
|
312
|
+
severity=Severity.WARNING,
|
|
313
|
+
rule_id="STYLE-027",
|
|
314
|
+
message="BigDecimal 原生运算应优先使用 BigDecimalUtils 工具方法",
|
|
315
|
+
fix_hint="使用 BigDecimalUtils.valueOf() / add() / subtract() / multiply() / divide() / largeThanZero() / nullToZero() 等",
|
|
316
|
+
source_line=line.strip(),
|
|
317
|
+
))
|
|
318
|
+
break
|
|
319
|
+
|
|
320
|
+
# STYLE-026: 主键/id 用 == null / != null / == 0L 等方式判空
|
|
321
|
+
if not any(kw in code_line for kw in ('isEmptyPk', 'isNotEmptyPk')):
|
|
322
|
+
# 前两个 pattern 检测变量名,用 code_line;第三个 pattern 检测 .get("id"),用 raw_code_line(保留字符串字面量)
|
|
323
|
+
pk_sources = [code_line, code_line, raw_code_line]
|
|
324
|
+
for pk_pat, src in zip(PK_NULL_CHECK_PATTERNS, pk_sources):
|
|
325
|
+
if pk_pat.search(src):
|
|
326
|
+
issues.append(LintIssue(
|
|
327
|
+
file=filepath, line=lineno,
|
|
328
|
+
severity=Severity.WARNING,
|
|
329
|
+
rule_id="STYLE-026",
|
|
330
|
+
message="主键/id 判空不应直接用 == null / != null / == 0L,苍穹主键默认值为 0L",
|
|
331
|
+
fix_hint="使用 EntityUtils.isEmptyPk(pk) 或 EntityUtils.isNotEmptyPk(pk),兼容 null 和 0L",
|
|
332
|
+
source_line=line.strip(),
|
|
333
|
+
))
|
|
334
|
+
break
|
|
335
|
+
|
|
336
|
+
if "ResManager.loadKDString" not in line and "String.format" not in line and CHINESE_CONCAT_PATTERN.search(line):
|
|
337
|
+
issues.append(LintIssue(
|
|
338
|
+
file=filepath, line=lineno,
|
|
339
|
+
severity=Severity.INFO,
|
|
340
|
+
rule_id="STYLE-013",
|
|
341
|
+
message="中文提示语存在拼接,后续多语言翻译时语序容易失真",
|
|
342
|
+
fix_hint="使用完整句模板 + String.format(ResManager.loadKDString(...), ...)",
|
|
343
|
+
source_line=line.strip(),
|
|
344
|
+
))
|
|
345
|
+
|
|
346
|
+
if loop_line and LOOP_UPDATE_VIEW_PATTERN.search(code_line):
|
|
347
|
+
issues.append(LintIssue(
|
|
348
|
+
file=filepath, line=lineno,
|
|
349
|
+
severity=Severity.ERROR,
|
|
350
|
+
rule_id="STYLE-014",
|
|
351
|
+
message="禁止在循环中调用 updateView(),界面刷新成本高",
|
|
352
|
+
fix_hint="循环结束后统一执行局部刷新",
|
|
353
|
+
source_line=line.strip(),
|
|
354
|
+
))
|
|
355
|
+
|
|
356
|
+
loop_db_hit = LOOP_DB_PATTERN.search(code_line) or (
|
|
357
|
+
LOOP_DB_METHOD_PATTERN.search(code_line) and LOOP_DB_HELPER_PATTERN.search(prev_code_line)
|
|
358
|
+
)
|
|
359
|
+
if loop_line and loop_db_hit:
|
|
360
|
+
issues.append(LintIssue(
|
|
361
|
+
file=filepath, line=lineno,
|
|
362
|
+
severity=Severity.ERROR,
|
|
363
|
+
rule_id="STYLE-015",
|
|
364
|
+
message="在循环中访问数据库(包括 BusinessDataServiceHelper.loadSingle(...) 等),存在明显的 N+1 查询风险",
|
|
365
|
+
fix_hint="先看 skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java,按“分组 key -> 批量查询 -> 本地映射”改写,避免循环里逐条查库",
|
|
366
|
+
source_line=line.strip(),
|
|
367
|
+
))
|
|
368
|
+
|
|
369
|
+
if loop_line and LOOP_REDIS_PATTERN.search(code_line):
|
|
370
|
+
issues.append(LintIssue(
|
|
371
|
+
file=filepath, line=lineno,
|
|
372
|
+
severity=Severity.ERROR,
|
|
373
|
+
rule_id="STYLE-016",
|
|
374
|
+
message="禁止在循环中频繁访问 Redis,容易造成缓存压力",
|
|
375
|
+
fix_hint="减少访问次数,优先批量读取或增加本地缓存",
|
|
376
|
+
source_line=line.strip(),
|
|
377
|
+
))
|
|
378
|
+
|
|
379
|
+
if loop_line and LOOP_ORM_CREATE_PATTERN.search(code_line):
|
|
380
|
+
issues.append(LintIssue(
|
|
381
|
+
file=filepath, line=lineno,
|
|
382
|
+
severity=Severity.ERROR,
|
|
383
|
+
rule_id="STYLE-022",
|
|
384
|
+
message="禁止在循环中频繁调用 ORM.create(),容易造成性能问题",
|
|
385
|
+
fix_hint="先批量组织数据,按批次处理",
|
|
386
|
+
source_line=line.strip(),
|
|
387
|
+
))
|
|
388
|
+
|
|
389
|
+
if loop_line and LOOP_DISPATCH_PATTERN.search(code_line):
|
|
390
|
+
issues.append(LintIssue(
|
|
391
|
+
file=filepath, line=lineno,
|
|
392
|
+
severity=Severity.ERROR,
|
|
393
|
+
rule_id="STYLE-023",
|
|
394
|
+
message="禁止在循环中调用 DispatchServiceHelper.invoke*(),远程调用放进循环容易放大时延和失败面",
|
|
395
|
+
fix_hint="合并调用、批量调用或先聚合参数",
|
|
396
|
+
source_line=line.strip(),
|
|
397
|
+
))
|
|
398
|
+
|
|
399
|
+
query_update_hit = False
|
|
400
|
+
query_update_var = ""
|
|
401
|
+
if SAVE_SERVICE_PATTERN.search(code_line):
|
|
402
|
+
for var_name in list(query_result_vars) + list(query_result_entity_vars) + list(active_query_loop_vars) + list(single_line_query_loop_vars):
|
|
403
|
+
if re.search(rf"\b{re.escape(var_name)}\b", code_line):
|
|
404
|
+
query_update_hit = True
|
|
405
|
+
query_update_var = var_name
|
|
406
|
+
break
|
|
407
|
+
|
|
408
|
+
if not query_update_hit:
|
|
409
|
+
for var_name in list(query_result_entity_vars) + list(active_query_loop_vars) + list(single_line_query_loop_vars):
|
|
410
|
+
if re.search(rf"\b{re.escape(var_name)}\s*\.\s*set\s*\(", code_line):
|
|
411
|
+
query_update_hit = True
|
|
412
|
+
query_update_var = var_name
|
|
413
|
+
break
|
|
414
|
+
|
|
415
|
+
if not query_update_hit:
|
|
416
|
+
for var_name in query_result_vars:
|
|
417
|
+
if re.search(rf"\b{re.escape(var_name)}\s*\.\s*get\s*\([^)]*\)\s*\.\s*set\s*\(", code_line):
|
|
418
|
+
query_update_hit = True
|
|
419
|
+
query_update_var = var_name
|
|
420
|
+
break
|
|
421
|
+
|
|
422
|
+
if query_update_hit:
|
|
423
|
+
issues.append(LintIssue(
|
|
424
|
+
file=filepath, line=lineno,
|
|
425
|
+
severity=Severity.WARNING,
|
|
426
|
+
rule_id="STYLE-017",
|
|
427
|
+
message="QueryServiceHelper.query(...) 返回的是扁平查询结果,不应直接当成可更新实体使用",
|
|
428
|
+
fix_hint="先看 skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java 的“query -> id -> load -> update”桥接样例,先查 id,再 load 实体包后更新保存",
|
|
429
|
+
source_line=line.strip(),
|
|
430
|
+
))
|
|
431
|
+
|
|
432
|
+
brace_depth += code_line.count("{") - code_line.count("}")
|
|
433
|
+
|
|
434
|
+
return issues
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""验证来源注释检查 (VERIFY-*) — 来源: coding-preferences.md C 层治理项(仅严格模式生效)"""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from .base import LintIssue, Severity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
OVERRIDE_PATTERN = re.compile(r"@Override")
|
|
11
|
+
VERIFY_PATTERN = re.compile(r"//\s*验证来源:")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check(filepath: str, lines: List[str]) -> List[LintIssue]:
|
|
15
|
+
"""执行 @Override 验证来源注释检查,返回 C 层治理建议。"""
|
|
16
|
+
issues: List[LintIssue] = []
|
|
17
|
+
|
|
18
|
+
for i, line in enumerate(lines):
|
|
19
|
+
if OVERRIDE_PATTERN.search(line):
|
|
20
|
+
# 检查上方 15 行内是否有验证来源注释(兼容被 JavaDoc 隔开的场景)
|
|
21
|
+
has_verify = False
|
|
22
|
+
for j in range(max(0, i - 15), i):
|
|
23
|
+
if VERIFY_PATTERN.search(lines[j]):
|
|
24
|
+
has_verify = True
|
|
25
|
+
break
|
|
26
|
+
if not has_verify:
|
|
27
|
+
issues.append(LintIssue(
|
|
28
|
+
file=filepath, line=i + 1,
|
|
29
|
+
severity=Severity.INFO,
|
|
30
|
+
rule_id="VERIFY-001",
|
|
31
|
+
message="@Override 方法缺少验证来源注释",
|
|
32
|
+
fix_hint="在 @Override 上方添加: // 验证来源: scripts/cosmic-api-knowledge.py detail [ClassName]",
|
|
33
|
+
source_line=line.strip(),
|
|
34
|
+
))
|
|
35
|
+
|
|
36
|
+
return issues
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: NOASSERTION
|
|
3
|
+
"""Shared runtime/route HTTP client helpers for ok-cosmic scripts."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import ssl
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.parse
|
|
11
|
+
import urllib.request
|
|
12
|
+
from typing import Any, Callable, Dict, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DEFAULT_ROUTE_TIMEOUT_SECONDS = 10.0
|
|
16
|
+
ROUTE_PAYLOAD_KEYS = ("data", "result", "respData", "response")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def append_query_param(url: str, key: str, value: str) -> str:
|
|
20
|
+
"""Append a query parameter unless the key already exists."""
|
|
21
|
+
if not url or not key or not value:
|
|
22
|
+
return url
|
|
23
|
+
parsed = urllib.parse.urlsplit(url)
|
|
24
|
+
query_pairs = urllib.parse.parse_qsl(parsed.query, keep_blank_values=True)
|
|
25
|
+
if any(k == key for k, _ in query_pairs):
|
|
26
|
+
return url
|
|
27
|
+
query_pairs.append((key, value))
|
|
28
|
+
return urllib.parse.urlunsplit(
|
|
29
|
+
(
|
|
30
|
+
parsed.scheme,
|
|
31
|
+
parsed.netloc,
|
|
32
|
+
parsed.path,
|
|
33
|
+
urllib.parse.urlencode(query_pairs),
|
|
34
|
+
parsed.fragment,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_bool(value: Any, default: bool = False) -> bool:
|
|
40
|
+
"""Parse bool-like JSON/env values."""
|
|
41
|
+
if value is None:
|
|
42
|
+
return default
|
|
43
|
+
if isinstance(value, bool):
|
|
44
|
+
return value
|
|
45
|
+
if isinstance(value, (int, float)):
|
|
46
|
+
return value != 0
|
|
47
|
+
if isinstance(value, str):
|
|
48
|
+
normalized = value.strip().lower()
|
|
49
|
+
if normalized in {"1", "true", "yes", "y", "on"}:
|
|
50
|
+
return True
|
|
51
|
+
if normalized in {"0", "false", "no", "n", "off"}:
|
|
52
|
+
return False
|
|
53
|
+
return bool(value)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _first_non_empty(*values: Any) -> str:
|
|
57
|
+
for value in values:
|
|
58
|
+
text = str(value or "").strip()
|
|
59
|
+
if text:
|
|
60
|
+
return text
|
|
61
|
+
return ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class RouteClient:
|
|
65
|
+
"""Small JSON POST client for Cosmic unified runtime/route APIs."""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
route_config: Optional[Dict[str, Any]] = None,
|
|
70
|
+
*,
|
|
71
|
+
api_url: Optional[str] = None,
|
|
72
|
+
debug: bool = False,
|
|
73
|
+
default_timeout: float = DEFAULT_ROUTE_TIMEOUT_SECONDS,
|
|
74
|
+
missing_message: Optional[str] = None,
|
|
75
|
+
):
|
|
76
|
+
if not isinstance(route_config, dict):
|
|
77
|
+
route_config = {}
|
|
78
|
+
|
|
79
|
+
self.debug = debug
|
|
80
|
+
self.api_url = _first_non_empty(
|
|
81
|
+
api_url,
|
|
82
|
+
route_config.get("apiUrl"),
|
|
83
|
+
os.getenv("COSMIC_ROUTE_API"),
|
|
84
|
+
os.getenv("COSMIC_RUNTIME_ROUTE_API"),
|
|
85
|
+
)
|
|
86
|
+
open_api_sign = _first_non_empty(
|
|
87
|
+
route_config.get("openApiSign"),
|
|
88
|
+
route_config.get("openapiSign"),
|
|
89
|
+
os.getenv("COSMIC_ROUTE_OPEN_API_SIGN"),
|
|
90
|
+
os.getenv("COSMIC_OPEN_API_SIGN"),
|
|
91
|
+
)
|
|
92
|
+
if open_api_sign:
|
|
93
|
+
self.api_url = append_query_param(self.api_url, "openApiSign", open_api_sign)
|
|
94
|
+
|
|
95
|
+
self.api_token = _first_non_empty(
|
|
96
|
+
route_config.get("apiToken"),
|
|
97
|
+
route_config.get("token"),
|
|
98
|
+
os.getenv("COSMIC_ROUTE_TOKEN"),
|
|
99
|
+
)
|
|
100
|
+
self.timeout = float(
|
|
101
|
+
route_config.get("timeoutSeconds")
|
|
102
|
+
or os.getenv("COSMIC_ROUTE_TIMEOUT")
|
|
103
|
+
or default_timeout
|
|
104
|
+
)
|
|
105
|
+
self.skip_ssl_verify = parse_bool(route_config.get("skipSslVerify"), default=True)
|
|
106
|
+
self.missing_message = missing_message or (
|
|
107
|
+
"未配置统一路由 API。请在 ok-cosmic.json 的 route.apiUrl 中配置统一路由,"
|
|
108
|
+
"或设置 COSMIC_ROUTE_API / COSMIC_RUNTIME_ROUTE_API 环境变量。"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _log_debug(self, msg: str) -> None:
|
|
112
|
+
if self.debug:
|
|
113
|
+
print(f" (DEBUG) {msg}", file=sys.stderr)
|
|
114
|
+
|
|
115
|
+
def post(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
116
|
+
"""POST JSON payload and return the decoded JSON object."""
|
|
117
|
+
if not self.api_url:
|
|
118
|
+
raise RuntimeError(self.missing_message)
|
|
119
|
+
|
|
120
|
+
headers = {"Content-Type": "application/json"}
|
|
121
|
+
if self.api_token:
|
|
122
|
+
headers["Authorization"] = f"Bearer {self.api_token}"
|
|
123
|
+
|
|
124
|
+
req_body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
125
|
+
self._log_debug(f"POST {self.api_url}")
|
|
126
|
+
self._log_debug(f"payload={json.dumps(payload, ensure_ascii=False)}")
|
|
127
|
+
req = urllib.request.Request(
|
|
128
|
+
self.api_url,
|
|
129
|
+
data=req_body,
|
|
130
|
+
headers=headers,
|
|
131
|
+
method="POST",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
ssl_context = ssl.create_default_context()
|
|
136
|
+
if self.skip_ssl_verify:
|
|
137
|
+
ssl_context.check_hostname = False
|
|
138
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
139
|
+
with urllib.request.urlopen(req, timeout=self.timeout, context=ssl_context) as resp:
|
|
140
|
+
raw = json.loads(resp.read().decode("utf-8", errors="replace"))
|
|
141
|
+
if not isinstance(raw, dict):
|
|
142
|
+
raise RuntimeError("远程接口返回的根对象不是 JSON Object。")
|
|
143
|
+
return raw
|
|
144
|
+
except urllib.error.HTTPError as e:
|
|
145
|
+
body = ""
|
|
146
|
+
try:
|
|
147
|
+
body = e.read().decode("utf-8", errors="replace").strip()
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
detail = f": {body}" if body else f": {e.reason}"
|
|
151
|
+
raise RuntimeError(f"HTTP {e.code}{detail}") from e
|
|
152
|
+
except urllib.error.URLError as e:
|
|
153
|
+
raise RuntimeError(f"Network error: {e.reason}") from e
|
|
154
|
+
except json.JSONDecodeError as e:
|
|
155
|
+
raise RuntimeError(f"响应 JSON 解析失败: {e}") from e
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def unwrap_route_payload(data: Any, payload_matcher: Callable[[Any], bool]) -> Any:
|
|
159
|
+
"""Unwrap common route wrapper keys and return the domain payload."""
|
|
160
|
+
if not isinstance(data, dict):
|
|
161
|
+
return data
|
|
162
|
+
if payload_matcher(data):
|
|
163
|
+
return data
|
|
164
|
+
for key in ROUTE_PAYLOAD_KEYS:
|
|
165
|
+
value = data.get(key)
|
|
166
|
+
if payload_matcher(value):
|
|
167
|
+
return value
|
|
168
|
+
return data
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def unwrap_route_raw(raw: Dict[str, Any], payload_matcher: Callable[[Any], bool]) -> Dict[str, Any]:
|
|
172
|
+
"""Unwrap one level of route envelope while preserving the original root shape."""
|
|
173
|
+
data = raw.get("data")
|
|
174
|
+
if not isinstance(data, dict):
|
|
175
|
+
return raw
|
|
176
|
+
if "status" in data and "data" in data:
|
|
177
|
+
return data
|
|
178
|
+
if payload_matcher(data):
|
|
179
|
+
return raw
|
|
180
|
+
for key in ROUTE_PAYLOAD_KEYS:
|
|
181
|
+
value = data.get(key)
|
|
182
|
+
if payload_matcher(value):
|
|
183
|
+
cloned = dict(raw)
|
|
184
|
+
cloned["data"] = value
|
|
185
|
+
return cloned
|
|
186
|
+
return raw
|