@lcap/nasl-language-server-core 4.4.0-beta.3 → 4.4.0-beta.30
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/out/checker.d.ts +4 -1
- package/out/checker.d.ts.map +1 -1
- package/out/checker.js +996 -96
- package/out/checker.js.map +1 -1
- package/out/index.d.ts +1 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js +5 -3
- package/out/index.js.map +1 -1
- package/out/reference-manager/build-q-name-def.d.ts.map +1 -1
- package/out/reference-manager/build-q-name-def.js +11 -0
- package/out/reference-manager/build-q-name-def.js.map +1 -1
- package/out/reference-manager/builtin-q-name.js +2 -2
- package/out/reference-manager/builtin-q-name.js.map +1 -1
- package/out/reference-manager/collect-q-name.d.ts +1 -1
- package/out/reference-manager/collect-q-name.d.ts.map +1 -1
- package/out/reference-manager/collect-q-name.js +64 -27
- package/out/reference-manager/collect-q-name.js.map +1 -1
- package/out/reference-manager/def-key-helpers.d.ts +38 -0
- package/out/reference-manager/def-key-helpers.d.ts.map +1 -0
- package/out/reference-manager/{get-q-name.js → def-key-helpers.js} +195 -23
- package/out/reference-manager/def-key-helpers.js.map +1 -0
- package/out/reference-manager/helper.js +2 -2
- package/out/reference-manager/helper.js.map +1 -1
- package/out/reference-manager/reference-manager.d.ts +146 -47
- package/out/reference-manager/reference-manager.d.ts.map +1 -1
- package/out/reference-manager/reference-manager.js +632 -204
- package/out/reference-manager/reference-manager.js.map +1 -1
- package/out/reference-manager/remove-q-name.d.ts +1 -1
- package/out/reference-manager/remove-q-name.d.ts.map +1 -1
- package/out/reference-manager/remove-q-name.js +14 -14
- package/out/reference-manager/remove-q-name.js.map +1 -1
- package/out/reference-manager/rename-q-name.d.ts +26 -26
- package/out/reference-manager/rename-q-name.d.ts.map +1 -1
- package/out/reference-manager/rename-q-name.js +48 -167
- package/out/reference-manager/rename-q-name.js.map +1 -1
- package/out/reference-manager/symbol-type.d.ts +8 -3
- package/out/reference-manager/symbol-type.d.ts.map +1 -1
- package/out/reference-manager/symbol-type.js +9 -2
- package/out/reference-manager/symbol-type.js.map +1 -1
- package/out/reference-manager/update-nasl-fragment.d.ts +2 -2
- package/out/reference-manager/update-nasl-fragment.d.ts.map +1 -1
- package/out/reference-manager/update-nasl-fragment.js +33 -28
- package/out/reference-manager/update-nasl-fragment.js.map +1 -1
- package/out/services/bindable-logic-service.js.map +1 -1
- package/out/symbol/traverse/concepts/index.d.ts +1 -1
- package/out/symbol/traverse/concepts/index.d.ts.map +1 -1
- package/out/symbol/traverse/concepts/logic/expression/member-expression.js.map +1 -1
- package/out/typer/collectGlobalDefs.d.ts +8 -0
- package/out/typer/collectGlobalDefs.d.ts.map +1 -1
- package/out/typer/collectGlobalDefs.js +102 -54
- package/out/typer/collectGlobalDefs.js.map +1 -1
- package/out/typer/component-def-manager/component-def-manager.d.ts +16 -0
- package/out/typer/component-def-manager/component-def-manager.d.ts.map +1 -1
- package/out/typer/component-def-manager/component-def-manager.js +63 -2
- package/out/typer/component-def-manager/component-def-manager.js.map +1 -1
- package/out/typer/component-def-manager/utils.d.ts.map +1 -1
- package/out/typer/component-def-manager/utils.js +26 -0
- package/out/typer/component-def-manager/utils.js.map +1 -1
- package/out/typer/dispatch-all.d.ts +3 -1
- package/out/typer/dispatch-all.d.ts.map +1 -1
- package/out/typer/dispatch-all.js +51 -6
- package/out/typer/dispatch-all.js.map +1 -1
- package/out/typer/dispatch-call.d.ts +27 -1
- package/out/typer/dispatch-call.d.ts.map +1 -1
- package/out/typer/dispatch-call.js +151 -107
- package/out/typer/dispatch-call.js.map +1 -1
- package/out/typer/dispatch-def.d.ts.map +1 -1
- package/out/typer/dispatch-def.js +51 -23
- package/out/typer/dispatch-def.js.map +1 -1
- package/out/typer/dispatch-expr.d.ts +2 -1
- package/out/typer/dispatch-expr.d.ts.map +1 -1
- package/out/typer/dispatch-expr.js +240 -199
- package/out/typer/dispatch-expr.js.map +1 -1
- package/out/typer/dispatch-process.d.ts +8 -1
- package/out/typer/dispatch-process.d.ts.map +1 -1
- package/out/typer/dispatch-process.js +37 -4
- package/out/typer/dispatch-process.js.map +1 -1
- package/out/typer/dispatch-stmt.d.ts.map +1 -1
- package/out/typer/dispatch-stmt.js +39 -25
- package/out/typer/dispatch-stmt.js.map +1 -1
- package/out/typer/dispatch-view.d.ts +1 -1
- package/out/typer/dispatch-view.d.ts.map +1 -1
- package/out/typer/dispatch-view.js +87 -48
- package/out/typer/dispatch-view.js.map +1 -1
- package/out/typer/fix-use-before-assign.js.map +1 -1
- package/out/typer/get-oql-files.d.ts.map +1 -1
- package/out/typer/get-oql-files.js +3 -9
- package/out/typer/get-oql-files.js.map +1 -1
- package/out/typer/incremental-update.d.ts +14 -7
- package/out/typer/incremental-update.d.ts.map +1 -1
- package/out/typer/incremental-update.js +396 -43
- package/out/typer/incremental-update.js.map +1 -1
- package/out/typer/oql-checker/chain-call-transformer.d.ts.map +1 -1
- package/out/typer/oql-checker/chain-call-transformer.js +17 -4
- package/out/typer/oql-checker/chain-call-transformer.js.map +1 -1
- package/out/typer/oql-checker/ts-parser.d.ts.map +1 -1
- package/out/typer/oql-checker/ts-parser.js +252 -37
- package/out/typer/oql-checker/ts-parser.js.map +1 -1
- package/out/typer/overload-helper.d.ts.map +1 -1
- package/out/typer/overload-helper.js +190 -23
- package/out/typer/overload-helper.js.map +1 -1
- package/out/typer/solver.d.ts +8 -9
- package/out/typer/solver.d.ts.map +1 -1
- package/out/typer/solver.js +54 -31
- package/out/typer/solver.js.map +1 -1
- package/out/typer/subster.d.ts +1 -1
- package/out/typer/subster.d.ts.map +1 -1
- package/out/typer/subster.js +251 -57
- package/out/typer/subster.js.map +1 -1
- package/out/typer/topo-sort.d.ts +38 -0
- package/out/typer/topo-sort.d.ts.map +1 -1
- package/out/typer/topo-sort.js +272 -3
- package/out/typer/topo-sort.js.map +1 -1
- package/out/typer/type-manager.d.ts +1 -0
- package/out/typer/type-manager.d.ts.map +1 -1
- package/out/typer/type-manager.js +14 -11
- package/out/typer/type-manager.js.map +1 -1
- package/out/typer/type-predicate.d.ts +1 -0
- package/out/typer/type-predicate.d.ts.map +1 -1
- package/out/typer/type-predicate.js +83 -14
- package/out/typer/type-predicate.js.map +1 -1
- package/out/typer/typer.d.ts +51 -6
- package/out/typer/typer.d.ts.map +1 -1
- package/out/typer/typer.js +248 -46
- package/out/typer/typer.js.map +1 -1
- package/out/typer/unifier.d.ts +12 -1
- package/out/typer/unifier.d.ts.map +1 -1
- package/out/typer/unifier.js +110 -37
- package/out/typer/unifier.js.map +1 -1
- package/out/utils/debug.js.map +1 -1
- package/out/utils/type-operator.d.ts +15 -0
- package/out/utils/type-operator.d.ts.map +1 -1
- package/out/utils/type-operator.js +65 -3
- package/out/utils/type-operator.js.map +1 -1
- package/package.json +6 -6
- package/out/reference-manager/get-q-name.d.ts +0 -9
- package/out/reference-manager/get-q-name.d.ts.map +0 -1
- package/out/reference-manager/get-q-name.js.map +0 -1
- package/out/reference-manager/view-elem-logic.d.ts +0 -44
- package/out/reference-manager/view-elem-logic.d.ts.map +0 -1
- package/out/reference-manager/view-elem-logic.js +0 -164
- package/out/reference-manager/view-elem-logic.js.map +0 -1
package/out/checker.js
CHANGED
|
@@ -10,10 +10,13 @@ const sem_diag_1 = require("./typer/sem-diag");
|
|
|
10
10
|
const type_predicate_1 = require("./typer/type-predicate");
|
|
11
11
|
const type_manager_1 = require("./typer/type-manager");
|
|
12
12
|
const service_2 = require("@lcap/nasl-concepts/service");
|
|
13
|
-
const asserts_1 = require("@lcap/nasl-concepts/asserts");
|
|
14
13
|
const lodash_1 = require("lodash");
|
|
14
|
+
const asserts_1 = require("@lcap/nasl-concepts/asserts");
|
|
15
15
|
const helper_1 = require("./typer/helper");
|
|
16
16
|
const semantic_rules_1 = require("./semantic-rules");
|
|
17
|
+
const view_element_field_attrs_1 = require("@lcap/nasl-concepts/view-element-field-attrs");
|
|
18
|
+
const regeExp = new nasl_utils_1.ExpressionLexer();
|
|
19
|
+
regeExp.profile = nasl_utils_1.profiles.js;
|
|
17
20
|
var Severity;
|
|
18
21
|
(function (Severity) {
|
|
19
22
|
Severity["WARN"] = "warning";
|
|
@@ -71,6 +74,7 @@ function getExpressionNodeName(concept) {
|
|
|
71
74
|
CallMicroserviceInterface: '微服务接口',
|
|
72
75
|
CallGatewayInterface: '网关接口',
|
|
73
76
|
CallEvent: '事件',
|
|
77
|
+
Destination: '页面',
|
|
74
78
|
};
|
|
75
79
|
return expressionNodeName[concept];
|
|
76
80
|
}
|
|
@@ -238,6 +242,12 @@ function resolveMetadataType(typeArg, app) {
|
|
|
238
242
|
return metadataTypes?.find(metadataType => metadataType.name === typeArg.typeName)?.typeAnnotation;
|
|
239
243
|
}
|
|
240
244
|
}
|
|
245
|
+
function isConcreteTy(ty) {
|
|
246
|
+
if (ty.typeKind === 'generic') {
|
|
247
|
+
return !!ty.typeArguments?.every(x => isConcreteTy(x));
|
|
248
|
+
}
|
|
249
|
+
return !(0, type_predicate_1.isAnyTy)(ty) && !(0, type_predicate_1.isTyAnnTyParam)(ty);
|
|
250
|
+
}
|
|
241
251
|
/**
|
|
242
252
|
* 创建错误诊断器
|
|
243
253
|
* @returns
|
|
@@ -246,6 +256,8 @@ function createErrorDiagnoser(context) {
|
|
|
246
256
|
let app = context.app;
|
|
247
257
|
let env = context.env;
|
|
248
258
|
let isDebug = context.debug;
|
|
259
|
+
// 严格模式
|
|
260
|
+
let isAIStrictMode = context.isAIStrictMode;
|
|
249
261
|
// 引用管理器
|
|
250
262
|
let referenceManager = env.refMgr;
|
|
251
263
|
// 检查共享数据是否有更新
|
|
@@ -258,8 +270,11 @@ function createErrorDiagnoser(context) {
|
|
|
258
270
|
let inModule = false;
|
|
259
271
|
let inPlayground = 0;
|
|
260
272
|
let inAuthLogic = false;
|
|
273
|
+
let inFrontendType = false;
|
|
274
|
+
let currentFrontendKind = '';
|
|
261
275
|
let dbType = '';
|
|
262
276
|
let curVarCounter = 0;
|
|
277
|
+
const listComponentStack = [];
|
|
263
278
|
const nodeNameCache = new Map();
|
|
264
279
|
const nodeNameGetterMap = {
|
|
265
280
|
View: 'getViewExistingNames',
|
|
@@ -304,6 +319,16 @@ function createErrorDiagnoser(context) {
|
|
|
304
319
|
diagnosticMap.set(curFileNode, diagnostics);
|
|
305
320
|
}
|
|
306
321
|
if (diagnostics) {
|
|
322
|
+
const parentKey = node?.parentKey;
|
|
323
|
+
const parentConcept = node.parentNode?.concept;
|
|
324
|
+
if (message.startsWith('类型不匹配:') &&
|
|
325
|
+
((parentKey === 'test' && parentConcept === 'IfStatement') || (parentKey === 'condition' && parentConcept === 'IfExpression'))) {
|
|
326
|
+
const map = {
|
|
327
|
+
'IfExpression': 'If表达式',
|
|
328
|
+
'IfStatement': 'If语句',
|
|
329
|
+
};
|
|
330
|
+
message = `${map[parentConcept]}条件槽位${message}`;
|
|
331
|
+
}
|
|
307
332
|
const diagnostic = {
|
|
308
333
|
message,
|
|
309
334
|
severity: inPlayground > 0 ? Severity.WARN : (severity ?? Severity.ERROR),
|
|
@@ -321,7 +346,30 @@ function createErrorDiagnoser(context) {
|
|
|
321
346
|
}
|
|
322
347
|
const finalNode = memberExpression ?? node;
|
|
323
348
|
(0, helper_1.createOnPush)(diagnostics, finalNode, diagnostic);
|
|
324
|
-
|
|
349
|
+
const aggregationFields = {
|
|
350
|
+
assignmentLine: 'assignmentLines',
|
|
351
|
+
};
|
|
352
|
+
const nodeDiagnostics = diagnostics.get(finalNode);
|
|
353
|
+
if (nodeDiagnostics && nodeDiagnostics.length > 0) {
|
|
354
|
+
// 构建最终的 tsErrorDetail,合并所有上下文信息
|
|
355
|
+
const aggregatedContext = {};
|
|
356
|
+
// 对每个需要聚合的字段进行收集
|
|
357
|
+
for (const [singleField, arrayField] of Object.entries(aggregationFields)) {
|
|
358
|
+
const values = nodeDiagnostics
|
|
359
|
+
.map(d => d[singleField])
|
|
360
|
+
.filter((val) => val !== undefined);
|
|
361
|
+
if (values.length > 0) {
|
|
362
|
+
aggregatedContext[arrayField] = values;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
finalNode.tsErrorDetail = {
|
|
366
|
+
...diagnostic,
|
|
367
|
+
...aggregatedContext,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
finalNode.tsErrorDetail = diagnostic;
|
|
372
|
+
}
|
|
325
373
|
}
|
|
326
374
|
else { }
|
|
327
375
|
}
|
|
@@ -343,7 +391,7 @@ function createErrorDiagnoser(context) {
|
|
|
343
391
|
* @returns
|
|
344
392
|
*/
|
|
345
393
|
function getError(node) {
|
|
346
|
-
const curFileNode = getCurFileNode();
|
|
394
|
+
const curFileNode = getCurFileNode() ?? node;
|
|
347
395
|
const diagnostics = diagnosticMap.get(curFileNode);
|
|
348
396
|
if (diagnostics) {
|
|
349
397
|
return diagnostics.get(node);
|
|
@@ -572,6 +620,9 @@ function createErrorDiagnoser(context) {
|
|
|
572
620
|
else if (nasl_concepts_1.asserts.isValidationLogic(node)) {
|
|
573
621
|
message = '验证函数:参数不能为空!';
|
|
574
622
|
}
|
|
623
|
+
else if (nasl_concepts_1.asserts.isNewComposite(node)) {
|
|
624
|
+
message = '新建实体/数据结构:参数不能为空!';
|
|
625
|
+
}
|
|
575
626
|
if (message) {
|
|
576
627
|
error(node, message);
|
|
577
628
|
}
|
|
@@ -623,6 +674,148 @@ function createErrorDiagnoser(context) {
|
|
|
623
674
|
const ref = type?.typeKind === 'anonymousStructure' ? type : env.resolveRef(type);
|
|
624
675
|
return findPropertyByName(ref, name);
|
|
625
676
|
}
|
|
677
|
+
// 判断节点是否在指定祖先之内,用于给列表上下文回填 slot 变量
|
|
678
|
+
function isDescendantOf(node, ancestor) {
|
|
679
|
+
let cur = node?.parentNode;
|
|
680
|
+
while (cur) {
|
|
681
|
+
if (cur === ancestor)
|
|
682
|
+
return true;
|
|
683
|
+
cur = cur.parentNode;
|
|
684
|
+
}
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
function getMemberPropertyName(member) {
|
|
688
|
+
const { property } = member || {};
|
|
689
|
+
if (!property)
|
|
690
|
+
return '';
|
|
691
|
+
if (nasl_concepts_1.asserts.isIdentifier(property))
|
|
692
|
+
return property.name;
|
|
693
|
+
return property?.value ?? property?.name ?? '';
|
|
694
|
+
}
|
|
695
|
+
function getMemberPath(member) {
|
|
696
|
+
const path = [];
|
|
697
|
+
let cur = member;
|
|
698
|
+
while (nasl_concepts_1.asserts.isMemberExpression(cur)) {
|
|
699
|
+
const propName = getMemberPropertyName(cur);
|
|
700
|
+
if (propName) {
|
|
701
|
+
path.unshift(propName);
|
|
702
|
+
}
|
|
703
|
+
const obj = cur.object;
|
|
704
|
+
if (nasl_concepts_1.asserts.isIdentifier(obj)) {
|
|
705
|
+
path.unshift(obj.name);
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
cur = obj;
|
|
709
|
+
}
|
|
710
|
+
return path;
|
|
711
|
+
}
|
|
712
|
+
function isCurrentItemMember(member, slotVarName) {
|
|
713
|
+
if (!slotVarName)
|
|
714
|
+
return false;
|
|
715
|
+
const completeName = member?.completeName;
|
|
716
|
+
if (typeof completeName === 'string' && completeName.startsWith(`${slotVarName}.item`)) {
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
const path = getMemberPath(member);
|
|
720
|
+
return path[0] === slotVarName && path[1] === 'item';
|
|
721
|
+
}
|
|
722
|
+
// 找到命中的 currentX.item.* 成员表达式,优先返回首个匹配
|
|
723
|
+
function findCurrentItemMember(node, slotVarName) {
|
|
724
|
+
if (!slotVarName || !node)
|
|
725
|
+
return;
|
|
726
|
+
if (nasl_concepts_1.asserts.isMemberExpression(node) && isCurrentItemMember(node, slotVarName)) {
|
|
727
|
+
return node;
|
|
728
|
+
}
|
|
729
|
+
let matched;
|
|
730
|
+
node.traverseStrictChildren?.((child) => {
|
|
731
|
+
if (matched)
|
|
732
|
+
return;
|
|
733
|
+
if (nasl_concepts_1.asserts.isMemberExpression(child) && isCurrentItemMember(child, slotVarName)) {
|
|
734
|
+
matched = child;
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
return matched;
|
|
738
|
+
}
|
|
739
|
+
// 自内向外查找最近的未开启 formMode 的列表上下文,并携带命中的 MemberExpression
|
|
740
|
+
function getListContextByNode(node) {
|
|
741
|
+
if (!node)
|
|
742
|
+
return;
|
|
743
|
+
for (let i = listComponentStack.length - 1; i >= 0; i--) {
|
|
744
|
+
const ctx = listComponentStack[i];
|
|
745
|
+
if (ctx.formModeEnabled)
|
|
746
|
+
continue;
|
|
747
|
+
const member = findCurrentItemMember(node, ctx.slotVarName);
|
|
748
|
+
if (member) {
|
|
749
|
+
return { ...ctx, member };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// 解析 currentX.item.* 访问的上下文与字段路径
|
|
754
|
+
function getCurrentItemAccessInfo(target) {
|
|
755
|
+
const ctxWithMember = getListContextByNode(target);
|
|
756
|
+
if (!ctxWithMember)
|
|
757
|
+
return;
|
|
758
|
+
const member = ctxWithMember.member;
|
|
759
|
+
if (!member)
|
|
760
|
+
return;
|
|
761
|
+
const path = getMemberPath(member);
|
|
762
|
+
const itemIndex = path.indexOf('item');
|
|
763
|
+
if (itemIndex < 0)
|
|
764
|
+
return;
|
|
765
|
+
const afterItemPath = path.slice(itemIndex + 1).join('.');
|
|
766
|
+
const currentName = ctxWithMember.slotVarName || path[0] || 'current';
|
|
767
|
+
return {
|
|
768
|
+
ctx: ctxWithMember,
|
|
769
|
+
afterItemPath,
|
|
770
|
+
currentName,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
// 统一报错出口:仅在 item 之后存在字段时提示切换表单模式
|
|
774
|
+
function reportCurrentItemModification(target, errorNode) {
|
|
775
|
+
const info = getCurrentItemAccessInfo(target);
|
|
776
|
+
if (!info)
|
|
777
|
+
return;
|
|
778
|
+
const { ctx, afterItemPath, currentName } = info;
|
|
779
|
+
if (!afterItemPath)
|
|
780
|
+
return;
|
|
781
|
+
error(errorNode, `组件列表 ${ctx.node.name || ctx.node.tag} 内部存在对数据源数据 ${currentName}.item.${afterItemPath} 的修改,请切换成表单模式,并且需要将“数据源”属性切换为使用“表单数据”属性`);
|
|
782
|
+
}
|
|
783
|
+
function isFormModeEnabled(attr) {
|
|
784
|
+
if (!attr)
|
|
785
|
+
return false;
|
|
786
|
+
if (attr.type === 'static') {
|
|
787
|
+
return attr.value === true || attr.value === 'true';
|
|
788
|
+
}
|
|
789
|
+
if (nasl_concepts_1.asserts.isBooleanLiteral(attr.expression)) {
|
|
790
|
+
return attr.expression.value === 'true';
|
|
791
|
+
}
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 获取 ViewElement 的数据源类型(T 或 T1)
|
|
796
|
+
* 参考 util.js 中的 getDataTypeOfViewElement 实现
|
|
797
|
+
*/
|
|
798
|
+
function getDataSourceTypeAnnotation(viewElement) {
|
|
799
|
+
// 向上查找定义了数据源类型的祖先组件
|
|
800
|
+
let currentNode = viewElement;
|
|
801
|
+
let depth = 0;
|
|
802
|
+
const maxSearchDepth = 5;
|
|
803
|
+
while (currentNode?.concept === 'ViewElement' && depth <= maxSearchDepth) {
|
|
804
|
+
// 使用 __ViewElementTV 获取类型,与 IDE 前端 getDataTypeOfViewElement 保持一致
|
|
805
|
+
if (currentNode.__TypeAnnotation && currentNode.__ViewElementTV?.length) {
|
|
806
|
+
// 先查找 T1(动态列),若无再查找 T(普通数据源组件)
|
|
807
|
+
const t1 = currentNode.__ViewElementTV.find((x) => x.rawName === 'T1');
|
|
808
|
+
if (t1?.renamed)
|
|
809
|
+
return t1.renamed;
|
|
810
|
+
const t = currentNode.__ViewElementTV.find((x) => x.rawName === 'T');
|
|
811
|
+
if (t?.renamed)
|
|
812
|
+
return t.renamed;
|
|
813
|
+
}
|
|
814
|
+
currentNode = currentNode.parentNode;
|
|
815
|
+
depth++;
|
|
816
|
+
}
|
|
817
|
+
return undefined;
|
|
818
|
+
}
|
|
626
819
|
/**
|
|
627
820
|
* 是否在赋值左侧
|
|
628
821
|
* @param node
|
|
@@ -705,7 +898,8 @@ function createErrorDiagnoser(context) {
|
|
|
705
898
|
if (param?.spread) {
|
|
706
899
|
continue;
|
|
707
900
|
}
|
|
708
|
-
|
|
901
|
+
// 严格模式非必填也需要有Argument占位
|
|
902
|
+
if (isAIStrictMode || !isNonRequiredParam(param)) {
|
|
709
903
|
minArgsCount++; // 如果参数有默认值,则跳过
|
|
710
904
|
}
|
|
711
905
|
}
|
|
@@ -823,7 +1017,11 @@ function createErrorDiagnoser(context) {
|
|
|
823
1017
|
error(node, `预期 ${paramsLen} 个参数,但传入了 ${argsLen} 个。`);
|
|
824
1018
|
}
|
|
825
1019
|
else if (argsLen < minArgsCount) {
|
|
826
|
-
|
|
1020
|
+
let msg = `传入参数的个数 ${argsLen} 与 ${getExpressionNodeName(node.concept)} 定义的参数个数 ${paramsLen} 不符。`;
|
|
1021
|
+
if (isAIStrictMode) {
|
|
1022
|
+
msg += `可选参数和默认值参数必须用 undefined 占位`;
|
|
1023
|
+
}
|
|
1024
|
+
error(node, msg);
|
|
827
1025
|
}
|
|
828
1026
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(args, function* (arg, index) {
|
|
829
1027
|
const param = params?.[index];
|
|
@@ -1062,6 +1260,8 @@ function createErrorDiagnoser(context) {
|
|
|
1062
1260
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.processV2s, function* (node) {
|
|
1063
1261
|
yield* checkNode(node);
|
|
1064
1262
|
});
|
|
1263
|
+
// 检测所有流程的循环引用(在 App 级别,因为流程的变更会影响兄弟级别的流程)
|
|
1264
|
+
checkAllProcessCircularReferences(node);
|
|
1065
1265
|
}
|
|
1066
1266
|
if (node.roles?.length) {
|
|
1067
1267
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.roles, function* (node) {
|
|
@@ -1098,6 +1298,11 @@ function createErrorDiagnoser(context) {
|
|
|
1098
1298
|
});
|
|
1099
1299
|
}
|
|
1100
1300
|
}
|
|
1301
|
+
if (node.interfaces?.length) {
|
|
1302
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.interfaces, function* (node) {
|
|
1303
|
+
yield* checkNode(node);
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1101
1306
|
yield* checkNode(node.configuration);
|
|
1102
1307
|
if (node.type === 'sharedApp') {
|
|
1103
1308
|
const status = getStatusOfSharedApp(node);
|
|
@@ -1111,6 +1316,19 @@ function createErrorDiagnoser(context) {
|
|
|
1111
1316
|
});
|
|
1112
1317
|
}
|
|
1113
1318
|
}
|
|
1319
|
+
// 检查 JDK 版本兼容性
|
|
1320
|
+
function jdkDisabled(item) {
|
|
1321
|
+
const isBackendComp = !!item?.externalDependencyMap?.maven;
|
|
1322
|
+
if (isBackendComp) {
|
|
1323
|
+
const jdk = app?.jdkVersion || 'JDK8';
|
|
1324
|
+
const dependencyJdk = item?.externalDependencyMap?.jdk?.version || 'JDK8';
|
|
1325
|
+
return jdk.toLowerCase() !== dependencyJdk.toLowerCase();
|
|
1326
|
+
}
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
if (jdkDisabled(node)) {
|
|
1330
|
+
error(node, `与当前应用的JDK版本不匹配`);
|
|
1331
|
+
}
|
|
1114
1332
|
inModule = false;
|
|
1115
1333
|
}
|
|
1116
1334
|
/**
|
|
@@ -1287,7 +1505,7 @@ function createErrorDiagnoser(context) {
|
|
|
1287
1505
|
yield* checkNode(node);
|
|
1288
1506
|
});
|
|
1289
1507
|
}
|
|
1290
|
-
const dataSourceGroup = app.configuration?.getGroup('dataSource');
|
|
1508
|
+
const dataSourceGroup = app.configuration?.getGroup?.('dataSource');
|
|
1291
1509
|
if (dataSourceGroup) {
|
|
1292
1510
|
const property = dataSourceGroup.getProperty(node.name);
|
|
1293
1511
|
if (property.values?.length) {
|
|
@@ -1317,6 +1535,11 @@ function createErrorDiagnoser(context) {
|
|
|
1317
1535
|
yield* checkNode(node);
|
|
1318
1536
|
});
|
|
1319
1537
|
}
|
|
1538
|
+
if (node.indexes?.length) {
|
|
1539
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.indexes, function* (node) {
|
|
1540
|
+
yield* checkNode(node);
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1320
1543
|
// 在多模式切换为单模式的时候,数据库表名是否符合规范
|
|
1321
1544
|
// /^[a-zA-Z0-9_]+$/ 由字母、数字、下划线组成
|
|
1322
1545
|
const dataSource = node.dataSource;
|
|
@@ -1554,7 +1777,7 @@ function createErrorDiagnoser(context) {
|
|
|
1554
1777
|
const decimalPlaces = value?.split('.')[1]?.length || 0;
|
|
1555
1778
|
if (decimalPlaces > +scale) {
|
|
1556
1779
|
error(node, `实体字段${node.name}默认值的小数位数不能大于设置的小数位数${scale},否则将会按照小数位数自动截断`, {
|
|
1557
|
-
severity: Severity.WARN,
|
|
1780
|
+
severity: isAIStrictMode ? Severity.ERROR : Severity.WARN,
|
|
1558
1781
|
});
|
|
1559
1782
|
}
|
|
1560
1783
|
}
|
|
@@ -1644,11 +1867,31 @@ function createErrorDiagnoser(context) {
|
|
|
1644
1867
|
}
|
|
1645
1868
|
}
|
|
1646
1869
|
}
|
|
1870
|
+
/**
|
|
1871
|
+
* 检查 实体索引
|
|
1872
|
+
* @param node
|
|
1873
|
+
*/
|
|
1874
|
+
function* checkEntityIndex(node) {
|
|
1875
|
+
if (node.propertyNames?.length) {
|
|
1876
|
+
const entity = node.parentNode;
|
|
1877
|
+
node.propertyNames.forEach((propertyName) => {
|
|
1878
|
+
const prop = findPropertyByName(entity, propertyName);
|
|
1879
|
+
if (!prop) {
|
|
1880
|
+
const scope = node?.constructor?.nodeTitle;
|
|
1881
|
+
error(node, `${scope}:索引${node.name}找不到 实体上的 ${propertyName}属性。`);
|
|
1882
|
+
return errorType;
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1647
1887
|
/**
|
|
1648
1888
|
* 检查 枚举
|
|
1649
1889
|
* @param node
|
|
1650
1890
|
*/
|
|
1651
1891
|
function* checkEnum(node) {
|
|
1892
|
+
if (!node.valueType) {
|
|
1893
|
+
error(node, `枚举 ${node.name} 缺少值类型定义`);
|
|
1894
|
+
}
|
|
1652
1895
|
if (node.enumItems?.length) {
|
|
1653
1896
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.enumItems, function* (node) {
|
|
1654
1897
|
yield* checkNode(node);
|
|
@@ -1784,6 +2027,17 @@ function createErrorDiagnoser(context) {
|
|
|
1784
2027
|
yield* checkNode(node);
|
|
1785
2028
|
});
|
|
1786
2029
|
}
|
|
2030
|
+
// 仅对内部接口(app.interfaces,由 Logic 导出)校验 originLogicName;外部接口(interfaceDependencies 等)无此字段
|
|
2031
|
+
const isInternalInterface = nasl_concepts_1.asserts.isApp(node.parentNode);
|
|
2032
|
+
if (isInternalInterface) {
|
|
2033
|
+
if (ensureNodeKeyExists(node, 'originLogicName')) {
|
|
2034
|
+
const originLogicName = node.originLogicName;
|
|
2035
|
+
const ref = app.logics.find((item) => item.name === originLogicName);
|
|
2036
|
+
if (!ref) {
|
|
2037
|
+
error(node, `接口 ${node.title || node.name} 引用了不存在的逻辑 ${originLogicName}`);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1787
2041
|
yield* checkNode(node.validation);
|
|
1788
2042
|
yield* checkTimeout(node);
|
|
1789
2043
|
env.exitScope();
|
|
@@ -1912,9 +2166,39 @@ function createErrorDiagnoser(context) {
|
|
|
1912
2166
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.views, function* (node) {
|
|
1913
2167
|
yield* checkNode(node);
|
|
1914
2168
|
});
|
|
2169
|
+
checkViewIndexPageSetting(node);
|
|
1915
2170
|
}
|
|
1916
2171
|
env.exitScope();
|
|
1917
2172
|
}
|
|
2173
|
+
/**
|
|
2174
|
+
* 检查页面的首页/默认跳转页设置
|
|
2175
|
+
* 根据父级类型(Frontend 或 View)自动判断检查场景
|
|
2176
|
+
* @param node 当前页面节点
|
|
2177
|
+
*/
|
|
2178
|
+
function checkViewIndexPageSetting(node) {
|
|
2179
|
+
const isNodeFrontend = (0, asserts_1.isFrontend)(node);
|
|
2180
|
+
const children = isNodeFrontend ? node.views : node.children;
|
|
2181
|
+
// 防御性检查:确保 children 存在且是数组
|
|
2182
|
+
if (!children || !Array.isArray(children)) {
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
// 检查默认跳转页数量,只允许有一个默认跳转页
|
|
2186
|
+
const defaultViews = children.filter((view) => view.isIndex);
|
|
2187
|
+
if (defaultViews.length > 1) {
|
|
2188
|
+
// 收集所有默认跳转页的名称
|
|
2189
|
+
const defaultViewNames = defaultViews.map((view) => view.name).join('、');
|
|
2190
|
+
const typeMap = {
|
|
2191
|
+
Frontend: '首页',
|
|
2192
|
+
View: '默认跳转页',
|
|
2193
|
+
};
|
|
2194
|
+
const pageType = typeMap[node.concept] || '默认跳转页';
|
|
2195
|
+
const errorMessage = `页面 ${node.name} 只能有一个 ${pageType},目前存在 ${defaultViewNames}`;
|
|
2196
|
+
error(node, errorMessage, {
|
|
2197
|
+
fileNode: node,
|
|
2198
|
+
isIndex: true,
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
1918
2202
|
/**
|
|
1919
2203
|
* 检查 页面
|
|
1920
2204
|
* @param node
|
|
@@ -1962,6 +2246,7 @@ function createErrorDiagnoser(context) {
|
|
|
1962
2246
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.children, function* (node) {
|
|
1963
2247
|
yield* checkNode(node);
|
|
1964
2248
|
});
|
|
2249
|
+
checkViewIndexPageSetting(node);
|
|
1965
2250
|
}
|
|
1966
2251
|
env.exitScope();
|
|
1967
2252
|
}
|
|
@@ -2019,57 +2304,84 @@ function createErrorDiagnoser(context) {
|
|
|
2019
2304
|
}
|
|
2020
2305
|
const shouldProvideCurrentCtx = !!(node.slotScope);
|
|
2021
2306
|
let scopeEntered = false;
|
|
2307
|
+
const isListComponents = node.tag === 'el-list-components';
|
|
2308
|
+
const formModeAttr = isListComponents ? node.bindAttrs?.find(attr => attr.name === 'formMode') : undefined;
|
|
2309
|
+
const formModeEnabled = isFormModeEnabled(formModeAttr);
|
|
2310
|
+
let currentSlotVarName;
|
|
2022
2311
|
if (shouldProvideCurrentCtx) {
|
|
2023
2312
|
const name = curVarCounter > 0 ? node.slotScope + curVarCounter : node.slotScope;
|
|
2313
|
+
currentSlotVarName = name;
|
|
2024
2314
|
const variable = new nasl_concepts_1.Variable({ name });
|
|
2025
2315
|
curVarCounter++;
|
|
2026
2316
|
// @ts-expect-error ViewElement 引入的 current 目前没有加入 refMgr,VE 也没当做 ScopeNode
|
|
2027
2317
|
env.enterScope(node, [variable]);
|
|
2028
2318
|
scopeEntered = true;
|
|
2029
2319
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
yield*
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
yield*
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2320
|
+
let listCtxPushed = false;
|
|
2321
|
+
try {
|
|
2322
|
+
if (currentSlotVarName && listComponentStack.length) {
|
|
2323
|
+
const top = listComponentStack[listComponentStack.length - 1];
|
|
2324
|
+
if (!top.slotVarName && isDescendantOf(node, top.node)) {
|
|
2325
|
+
top.slotVarName = currentSlotVarName;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
if (isListComponents && !formModeEnabled) {
|
|
2329
|
+
listComponentStack.push({
|
|
2330
|
+
node,
|
|
2331
|
+
slotVarName: currentSlotVarName,
|
|
2332
|
+
formModeEnabled,
|
|
2333
|
+
});
|
|
2334
|
+
listCtxPushed = true;
|
|
2335
|
+
}
|
|
2336
|
+
if (node.bindAttrs?.length) {
|
|
2337
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindAttrs, function* (node) {
|
|
2338
|
+
yield* checkNode(node);
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
if (node.bindDirectives?.length) {
|
|
2342
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindDirectives, function* (node) {
|
|
2343
|
+
yield* checkNode(node);
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
if (node.bindStyles?.length) {
|
|
2347
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindStyles, function* (node) {
|
|
2348
|
+
yield* checkNode(node);
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
if (node.bindEvents?.length) {
|
|
2352
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindEvents, function* (node) {
|
|
2353
|
+
yield* checkNode(node);
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
if (node.children?.length) {
|
|
2357
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.children, function* (node) {
|
|
2358
|
+
yield* checkNode(node);
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
// 已知:选择器会漏报,因为 children 里是两个空 template。暂时先这样吧。
|
|
2362
|
+
let dsFilled = true;
|
|
2363
|
+
let errNode = node;
|
|
2364
|
+
const ds = node.bindAttrs.find(nd => nd.name === 'dataSource');
|
|
2365
|
+
if (ds) {
|
|
2366
|
+
if (ds.type === 'dynamic' && !ds.expression) {
|
|
2367
|
+
dsFilled = false;
|
|
2368
|
+
errNode = ds;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
const hasSubComponents = node.children.filter(c => c.tag !== 'template').length > 0 ||
|
|
2372
|
+
node.children.filter(c => c.tag === 'template').some(cc => cc.children.length > 0);
|
|
2373
|
+
if (!dsFilled && !hasSubComponents) {
|
|
2374
|
+
error(errNode, '数据源不能为空');
|
|
2063
2375
|
}
|
|
2064
2376
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2377
|
+
finally {
|
|
2378
|
+
if (listCtxPushed) {
|
|
2379
|
+
listComponentStack.pop();
|
|
2380
|
+
}
|
|
2381
|
+
if (scopeEntered) {
|
|
2382
|
+
env.exitScope();
|
|
2383
|
+
curVarCounter--;
|
|
2384
|
+
}
|
|
2073
2385
|
}
|
|
2074
2386
|
}
|
|
2075
2387
|
/**
|
|
@@ -2090,6 +2402,31 @@ function createErrorDiagnoser(context) {
|
|
|
2090
2402
|
}
|
|
2091
2403
|
yield* checkPlayground(node.playground);
|
|
2092
2404
|
yield* checkBindExpressions(node);
|
|
2405
|
+
// 检查数据源上的字段属性是否在类型上存在
|
|
2406
|
+
// 判断条件:
|
|
2407
|
+
// 1. 属性的 setter 是 PropertySelectSetter(从组件定义的 ViewComponentDeclaration 中获取)
|
|
2408
|
+
// 2. 或者属性在 FIELD_ATTRS_BY_COMPONENT 配置中(兼容老版本组件配置)
|
|
2409
|
+
if (node.type === 'string' && typeof node.value === 'string' && node.value) {
|
|
2410
|
+
const viewElement = node.parentNode;
|
|
2411
|
+
if (viewElement?.concept === 'ViewElement') {
|
|
2412
|
+
// 优先检查组件定义中的 PropertySelectSetter
|
|
2413
|
+
const isPropertySelect = env.refMgr.compDefMgr.isPropertySelectSetter(viewElement.tag, node.name);
|
|
2414
|
+
// 同时兼容 FIELD_ATTRS_BY_COMPONENT 配置
|
|
2415
|
+
const needsFieldCheck = isPropertySelect || (0, view_element_field_attrs_1.shouldCheckFieldExists)(viewElement.tag, node.name);
|
|
2416
|
+
if (needsFieldCheck) {
|
|
2417
|
+
const dataSourceType = getDataSourceTypeAnnotation(viewElement);
|
|
2418
|
+
if (dataSourceType) {
|
|
2419
|
+
const { exists, missingField } = checkFieldExistsInType(env, dataSourceType, node.value);
|
|
2420
|
+
if (!exists && missingField) {
|
|
2421
|
+
// 优先从组件定义中获取属性标题,兜底使用硬编码配置
|
|
2422
|
+
const attrTitle = env.refMgr.compDefMgr.getPropTitle(viewElement.tag, node.name)
|
|
2423
|
+
|| (0, view_element_field_attrs_1.getFieldAttrTitle)(node.name);
|
|
2424
|
+
error(node, `${attrTitle} "${missingField}" 在数据类型上不存在`);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2093
2430
|
// 检查不可复制属性开启双向绑定
|
|
2094
2431
|
if ((node.model || node.sync) && node.expression) {
|
|
2095
2432
|
const bindExpression = node.expression;
|
|
@@ -2116,6 +2453,9 @@ function createErrorDiagnoser(context) {
|
|
|
2116
2453
|
error(bindExpression, '页面组件属性表达式:该表达式不支持获取组件输入内容,请改为可赋值的变量或属性。');
|
|
2117
2454
|
}
|
|
2118
2455
|
}
|
|
2456
|
+
if (node.model || node.sync) {
|
|
2457
|
+
reportCurrentItemModification(node.expression, node);
|
|
2458
|
+
}
|
|
2119
2459
|
// 检查组件数据源依赖监听变量重复
|
|
2120
2460
|
if (node?.name === 'dataSourceWatch' && nasl_concepts_1.asserts.isNewList(node?.expression)) {
|
|
2121
2461
|
const items = node?.expression?.items || [];
|
|
@@ -2177,8 +2517,15 @@ function createErrorDiagnoser(context) {
|
|
|
2177
2517
|
...(node.fromLogics || []),
|
|
2178
2518
|
...(node.roles || []),
|
|
2179
2519
|
].length) {
|
|
2520
|
+
const processElementV2 = node.getAncestor('ProcessElementV2');
|
|
2521
|
+
const title = processElementV2 && processElementV2.type === 'CallSubProcess' ? '子流程发起人' : '任务完成人';
|
|
2180
2522
|
// 任务完成人为空会有一个提示信息
|
|
2181
|
-
error(node,
|
|
2523
|
+
error(node, `${title}为空`);
|
|
2524
|
+
}
|
|
2525
|
+
if (Array.isArray(node.fromLogics)) {
|
|
2526
|
+
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.fromLogics, function* (node) {
|
|
2527
|
+
yield* checkNode(node);
|
|
2528
|
+
});
|
|
2182
2529
|
}
|
|
2183
2530
|
}
|
|
2184
2531
|
/**
|
|
@@ -2340,6 +2687,148 @@ function createErrorDiagnoser(context) {
|
|
|
2340
2687
|
}
|
|
2341
2688
|
env.exitScope();
|
|
2342
2689
|
}
|
|
2690
|
+
/**
|
|
2691
|
+
* 检测所有流程的循环引用(在 App 级别)
|
|
2692
|
+
* 使用 DFS 算法检测 ProcessV2 之间的循环引用关系
|
|
2693
|
+
* 因为流程的变更会影响兄弟级别的流程,所以需要在 App 级别进行检测
|
|
2694
|
+
*/
|
|
2695
|
+
function checkAllProcessCircularReferences(appNode) {
|
|
2696
|
+
if (!appNode?.processV2s || appNode.processV2s.length === 0) {
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
const visiting = new Set();
|
|
2700
|
+
const processMap = new Map();
|
|
2701
|
+
const depthReported = new Set();
|
|
2702
|
+
// 构建流程映射表
|
|
2703
|
+
for (const p of appNode.processV2s) {
|
|
2704
|
+
if (p.uniqueKey) {
|
|
2705
|
+
processMap.set(p.uniqueKey, p);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
// 查找 CallSubProcess 元素
|
|
2709
|
+
const findCallSubProcessElement = (fromKey, toKey) => {
|
|
2710
|
+
const fromProcess = processMap.get(fromKey);
|
|
2711
|
+
if (!fromProcess)
|
|
2712
|
+
return undefined;
|
|
2713
|
+
for (const procDef of fromProcess.processDefinitions || []) {
|
|
2714
|
+
for (const ele of procDef.elements || []) {
|
|
2715
|
+
if (ele.type === 'CallSubProcess' && ele.subProcessKey === toKey) {
|
|
2716
|
+
return ele;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return undefined;
|
|
2721
|
+
};
|
|
2722
|
+
// 将路径转换为流程名称字符串
|
|
2723
|
+
const pathToNames = (path) => {
|
|
2724
|
+
return path.map(key => processMap.get(key)?.name || key).join(' -> ');
|
|
2725
|
+
};
|
|
2726
|
+
// 记录循环并返回完整路径
|
|
2727
|
+
const recordCycle = (path, currentKey, cycles) => {
|
|
2728
|
+
const fullPath = [...path, currentKey];
|
|
2729
|
+
const cycleStart = path.indexOf(currentKey);
|
|
2730
|
+
if (cycleStart >= 0) {
|
|
2731
|
+
cycles.push(fullPath.slice(cycleStart));
|
|
2732
|
+
}
|
|
2733
|
+
return fullPath;
|
|
2734
|
+
};
|
|
2735
|
+
// DFS 遍历检测循环和深度
|
|
2736
|
+
function dfs(currentProcessKey, path, cycles) {
|
|
2737
|
+
const fullPath = [...path, currentProcessKey];
|
|
2738
|
+
// 检测循环:当前节点已在访问路径中
|
|
2739
|
+
if (visiting.has(currentProcessKey)) {
|
|
2740
|
+
return { path: recordCycle(path, currentProcessKey, cycles) };
|
|
2741
|
+
}
|
|
2742
|
+
visiting.add(currentProcessKey);
|
|
2743
|
+
const currentProcess = processMap.get(currentProcessKey);
|
|
2744
|
+
let maxPath = fullPath; // 记录最长路径
|
|
2745
|
+
if (currentProcess) {
|
|
2746
|
+
for (const procDef of currentProcess.processDefinitions || []) {
|
|
2747
|
+
for (const element of procDef.elements || []) {
|
|
2748
|
+
if (element.type === 'CallSubProcess' && element.subProcessKey) {
|
|
2749
|
+
const subProcessKey = element.subProcessKey;
|
|
2750
|
+
// 检测循环:子流程已在访问路径中
|
|
2751
|
+
if (visiting.has(subProcessKey)) {
|
|
2752
|
+
const cycleStart = fullPath.indexOf(subProcessKey);
|
|
2753
|
+
if (cycleStart >= 0) {
|
|
2754
|
+
cycles.push(fullPath.slice(cycleStart).concat(subProcessKey));
|
|
2755
|
+
}
|
|
2756
|
+
visiting.delete(currentProcessKey);
|
|
2757
|
+
// 发现循环,立即返回
|
|
2758
|
+
return { path: [...fullPath, subProcessKey] };
|
|
2759
|
+
}
|
|
2760
|
+
// 递归检查子流程
|
|
2761
|
+
const result = dfs(subProcessKey, fullPath, cycles);
|
|
2762
|
+
if (result) {
|
|
2763
|
+
// 如果发现循环,立即返回
|
|
2764
|
+
if (cycles.length > 0) {
|
|
2765
|
+
visiting.delete(currentProcessKey);
|
|
2766
|
+
return result;
|
|
2767
|
+
}
|
|
2768
|
+
// 否则记录最长路径,继续检查其他子流程
|
|
2769
|
+
if (result.path.length > maxPath.length) {
|
|
2770
|
+
maxPath = result.path;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
visiting.delete(currentProcessKey);
|
|
2778
|
+
return { path: maxPath };
|
|
2779
|
+
}
|
|
2780
|
+
// 报告循环引用错误
|
|
2781
|
+
const reportCycleError = (process, path) => {
|
|
2782
|
+
const nextKey = path[1];
|
|
2783
|
+
if (!nextKey)
|
|
2784
|
+
return;
|
|
2785
|
+
const callSubProcess = findCallSubProcessElement(process.uniqueKey, nextKey);
|
|
2786
|
+
if (callSubProcess) {
|
|
2787
|
+
error(callSubProcess, `流程存在循环引用: ${pathToNames(path)}`, {
|
|
2788
|
+
fileNode: process,
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
// 报告深度超限错误
|
|
2793
|
+
const reportDepthError = (path) => {
|
|
2794
|
+
const MIN_LEN = 4;
|
|
2795
|
+
for (let i = 0; i <= path.length - MIN_LEN; i++) {
|
|
2796
|
+
const subChain = path.slice(i);
|
|
2797
|
+
if (subChain.length < MIN_LEN)
|
|
2798
|
+
break;
|
|
2799
|
+
const reportKey = subChain.join('>');
|
|
2800
|
+
if (depthReported.has(reportKey))
|
|
2801
|
+
continue;
|
|
2802
|
+
depthReported.add(reportKey);
|
|
2803
|
+
const topFrom = subChain[0];
|
|
2804
|
+
const topTo = subChain[1];
|
|
2805
|
+
const topElement = findCallSubProcessElement(topFrom, topTo);
|
|
2806
|
+
const topProcess = processMap.get(topFrom);
|
|
2807
|
+
if (topElement && topProcess) {
|
|
2808
|
+
error(topElement, `流程引用层级不可超过3层,当前为: ${pathToNames(subChain)}`, {
|
|
2809
|
+
fileNode: topProcess,
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
// 对每个流程独立检测
|
|
2815
|
+
for (const process of appNode.processV2s) {
|
|
2816
|
+
if (!process.uniqueKey)
|
|
2817
|
+
continue;
|
|
2818
|
+
visiting.clear();
|
|
2819
|
+
depthReported.clear();
|
|
2820
|
+
const cycles = [];
|
|
2821
|
+
const result = dfs(process.uniqueKey, [], cycles);
|
|
2822
|
+
if (cycles.length > 0 && result) {
|
|
2823
|
+
// 有循环:使用完整路径报错
|
|
2824
|
+
reportCycleError(process, result.path);
|
|
2825
|
+
}
|
|
2826
|
+
else if (result && result.path.length > 3) {
|
|
2827
|
+
// 无循环但深度超限:报告所有超长链路
|
|
2828
|
+
reportDepthError(result.path);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2343
2832
|
/**
|
|
2344
2833
|
* 检查 绑定实体或数据结构
|
|
2345
2834
|
* @param node
|
|
@@ -2430,6 +2919,29 @@ function createErrorDiagnoser(context) {
|
|
|
2430
2919
|
else if (!service_1.ProcessesV2.isFromStartNode(node)) {
|
|
2431
2920
|
error(node, '节点未关联到流程图');
|
|
2432
2921
|
}
|
|
2922
|
+
// 子流程
|
|
2923
|
+
if (node.type === 'CallSubProcess') {
|
|
2924
|
+
// 数据映射规则子逻辑
|
|
2925
|
+
if (node.dataMappingRule) {
|
|
2926
|
+
if (node.dataMappingRule.mainToSub) {
|
|
2927
|
+
yield* checkNode(node.dataMappingRule.mainToSub);
|
|
2928
|
+
}
|
|
2929
|
+
if (node.dataMappingRule.subToMain) {
|
|
2930
|
+
yield* checkNode(node.dataMappingRule.subToMain);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
if (!node.subProcessKey) {
|
|
2934
|
+
error(node, '关联子流程为空');
|
|
2935
|
+
}
|
|
2936
|
+
else {
|
|
2937
|
+
// 检查引用的子流程是否存在(类似调用逻辑的检查)
|
|
2938
|
+
// subProcessKey 就是 ProcessV2 的 uniqueKey,直接作为 qName 使用
|
|
2939
|
+
const subProcessDef = env.refMgr.gQNameDefs.get(node.subProcessKey);
|
|
2940
|
+
if (!subProcessDef || subProcessDef.concept !== 'ProcessV2') {
|
|
2941
|
+
error(node, `找不到子流程 ${node.subProcessKey}`);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2433
2945
|
env.exitScope();
|
|
2434
2946
|
}
|
|
2435
2947
|
/**
|
|
@@ -2588,8 +3100,8 @@ function createErrorDiagnoser(context) {
|
|
|
2588
3100
|
}
|
|
2589
3101
|
}
|
|
2590
3102
|
if (!isSame) {
|
|
2591
|
-
const
|
|
2592
|
-
const key =
|
|
3103
|
+
const matched = exportInterface.concept === 'McpInterface';
|
|
3104
|
+
const key = matched ? 'exportMcpInterface' : 'exportInterface';
|
|
2593
3105
|
error(node, `逻辑已变更,请确认请求类型无误后重新导出${'${event}'}`, {
|
|
2594
3106
|
severity: Severity.ERROR,
|
|
2595
3107
|
events: [
|
|
@@ -2849,7 +3361,7 @@ function createErrorDiagnoser(context) {
|
|
|
2849
3361
|
return;
|
|
2850
3362
|
}
|
|
2851
3363
|
yield* checkVoidCallUsedAsStatement(node);
|
|
2852
|
-
const ref = env.resolveRef(node); // 前端
|
|
3364
|
+
const ref = env.resolveRef(node) ?? env.quickGetLibDef(node, node.calleeName, undefined); // 前端
|
|
2853
3365
|
// TODO: sql 函数的 namespace 带有数据库名 nasl.sqlFunction.mysql nasl.sqlFunction.oracle ...
|
|
2854
3366
|
// 暂时不在 gQNameDefs 里,用 isBuiltInCall 简单判断了
|
|
2855
3367
|
if (!ref && !isBuiltInCall(node)) {
|
|
@@ -2941,15 +3453,40 @@ function createErrorDiagnoser(context) {
|
|
|
2941
3453
|
}
|
|
2942
3454
|
return type;
|
|
2943
3455
|
function checkListSort(nd) {
|
|
3456
|
+
const parTy = env.getType(nd.arguments?.[0]);
|
|
3457
|
+
let paramType = '未知类型';
|
|
3458
|
+
if (parTy?.typeName === 'List') {
|
|
3459
|
+
paramType = (0, type_manager_1.showUserLevelType)(parTy.typeArguments?.[0] || type_manager_1.naslAnyTy);
|
|
3460
|
+
}
|
|
3461
|
+
const expectTy = `{ by: (item: ${paramType}) => 未知类型, asc: 布尔值 }`;
|
|
2944
3462
|
for (let i = 1; i < nd.arguments.length; i++) {
|
|
2945
3463
|
const expr = nd.arguments[i].expression;
|
|
2946
3464
|
let resTy;
|
|
3465
|
+
const reportArgError = (targetExpr) => {
|
|
3466
|
+
const actualTy = targetExpr ? env.getType(targetExpr) || type_manager_1.naslAnyTy : type_manager_1.naslAnyTy;
|
|
3467
|
+
error(nd.arguments[i], (0, sem_diag_1.mkIncompatibleTyErr)(env, actualTy, expectTy));
|
|
3468
|
+
};
|
|
2947
3469
|
if (expr?.concept === 'AnonymousFunction') {
|
|
2948
3470
|
resTy = env.getType(expr.body);
|
|
3471
|
+
if (!nasl_concepts_1.asserts.isNewComposite(expr.body)) {
|
|
3472
|
+
reportArgError(expr);
|
|
3473
|
+
}
|
|
2949
3474
|
}
|
|
2950
3475
|
else if (expr?.concept === 'SubLogic') {
|
|
2951
3476
|
resTy = env.getType(expr.returns?.[0]);
|
|
2952
3477
|
}
|
|
3478
|
+
else if (expr?.concept === 'Identifier') {
|
|
3479
|
+
const ty = env.getType(expr);
|
|
3480
|
+
if ((0, type_predicate_1.isFuncTy)(ty)) {
|
|
3481
|
+
resTy = ty;
|
|
3482
|
+
}
|
|
3483
|
+
else {
|
|
3484
|
+
reportArgError(expr);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
else {
|
|
3488
|
+
reportArgError(expr);
|
|
3489
|
+
}
|
|
2953
3490
|
const byTy = resTy?.properties?.find((prop) => prop.name === 'by')?.typeAnnotation;
|
|
2954
3491
|
if (byTy?.typeKind === 'union') {
|
|
2955
3492
|
// 这里使用 node.arguments[i] 则无红框,猜测是 ListSort 的特殊交互导致的问题
|
|
@@ -2985,6 +3522,29 @@ function createErrorDiagnoser(context) {
|
|
|
2985
3522
|
error(node, '下载组件必须为page和size指定入参,且不能重复');
|
|
2986
3523
|
}
|
|
2987
3524
|
}
|
|
3525
|
+
const map = {
|
|
3526
|
+
frontend: {
|
|
3527
|
+
scope: '前端',
|
|
3528
|
+
forbiddenScope: '服务端',
|
|
3529
|
+
forbiddenCalleeNamesMap: {
|
|
3530
|
+
'nasl.auth': ['getCurrentUser', 'generateUserId', 'validatePassword', 'encryptPassword'],
|
|
3531
|
+
},
|
|
3532
|
+
},
|
|
3533
|
+
backend: {
|
|
3534
|
+
scope: '服务端',
|
|
3535
|
+
forbiddenScope: '前端',
|
|
3536
|
+
forbiddenCalleeNamespaces: ['nasl.browser', 'nasl.io'],
|
|
3537
|
+
forbiddenCalleeNamesMap: {
|
|
3538
|
+
'nasl.auth': ['decryptByAES', 'encryptByAES', 'logout', 'hasAuth'],
|
|
3539
|
+
'nasl.configuration': ['getCurrentIp', 'getUserLanguage', 'getI18nList', 'setI18nLocale'],
|
|
3540
|
+
},
|
|
3541
|
+
},
|
|
3542
|
+
};
|
|
3543
|
+
const config = inFrontendType ? map.frontend : map.backend;
|
|
3544
|
+
const { scope, forbiddenScope, forbiddenCalleeNamespaces, forbiddenCalleeNamesMap } = config;
|
|
3545
|
+
if (forbiddenCalleeNamespaces?.includes(node.calleeNamespace) || forbiddenCalleeNamesMap?.[node.calleeNamespace]?.includes(node.calleeName)) {
|
|
3546
|
+
error(node, `不能在${scope}作用域下使用${forbiddenScope}系统逻辑`);
|
|
3547
|
+
}
|
|
2988
3548
|
return env.getType(node);
|
|
2989
3549
|
}
|
|
2990
3550
|
/**
|
|
@@ -3031,6 +3591,7 @@ function createErrorDiagnoser(context) {
|
|
|
3031
3591
|
*/
|
|
3032
3592
|
function* checkIfStatement(node) {
|
|
3033
3593
|
if (ensureNodeKeyExists(node, 'test')) {
|
|
3594
|
+
yield* rejectNullLiteral(node.test);
|
|
3034
3595
|
yield* checkNode(node.test);
|
|
3035
3596
|
}
|
|
3036
3597
|
// then
|
|
@@ -3046,12 +3607,32 @@ function createErrorDiagnoser(context) {
|
|
|
3046
3607
|
});
|
|
3047
3608
|
}
|
|
3048
3609
|
}
|
|
3610
|
+
/**
|
|
3611
|
+
* 检查 条件表达式
|
|
3612
|
+
* @param node
|
|
3613
|
+
*/
|
|
3614
|
+
function* checkIfExpression(node) {
|
|
3615
|
+
if (ensureNodeKeyExists(node, 'condition')) {
|
|
3616
|
+
yield* rejectNullLiteral(node.condition);
|
|
3617
|
+
yield* checkNode(node.condition);
|
|
3618
|
+
}
|
|
3619
|
+
// then
|
|
3620
|
+
if (ensureNodeKeyExists(node, 'consequent')) {
|
|
3621
|
+
yield* checkNode(node.consequent);
|
|
3622
|
+
}
|
|
3623
|
+
// else
|
|
3624
|
+
if (ensureNodeKeyExists(node, 'alternate')) {
|
|
3625
|
+
yield* checkNode(node.alternate);
|
|
3626
|
+
}
|
|
3627
|
+
return env.getType(node);
|
|
3628
|
+
}
|
|
3049
3629
|
/**
|
|
3050
3630
|
* 检查 匹配
|
|
3051
3631
|
* @param node
|
|
3052
3632
|
*/
|
|
3053
3633
|
function* checkMatch(node) {
|
|
3054
3634
|
if (ensureNodeKeyExists(node, 'expression')) {
|
|
3635
|
+
yield* rejectNullLiteral(node.expression);
|
|
3055
3636
|
yield* checkNode(node.expression);
|
|
3056
3637
|
}
|
|
3057
3638
|
if (node.cases?.length) {
|
|
@@ -3306,6 +3887,7 @@ function createErrorDiagnoser(context) {
|
|
|
3306
3887
|
function* checkSwitchCase(node) {
|
|
3307
3888
|
if (!(0, nasl_concepts_1.isLastNode)(node)) {
|
|
3308
3889
|
if (ensureNodeKeyExists(node, 'test')) {
|
|
3890
|
+
yield* rejectNullLiteral(node.test);
|
|
3309
3891
|
yield* checkNode(node.test);
|
|
3310
3892
|
}
|
|
3311
3893
|
}
|
|
@@ -3321,12 +3903,18 @@ function createErrorDiagnoser(context) {
|
|
|
3321
3903
|
*/
|
|
3322
3904
|
function* checkForEachStatement(node) {
|
|
3323
3905
|
env.enterScope(node);
|
|
3906
|
+
if (ensureNodeKeyExists(node, 'item')) {
|
|
3907
|
+
yield* checkNode(node.item);
|
|
3908
|
+
}
|
|
3324
3909
|
if (ensureNodeKeyExists(node, 'each')) {
|
|
3325
3910
|
yield* checkNode(node.each);
|
|
3326
3911
|
}
|
|
3327
3912
|
if (ensureNodeKeyExists(node, 'start')) {
|
|
3328
3913
|
yield* checkNode(node.start);
|
|
3329
3914
|
}
|
|
3915
|
+
if (node.end) {
|
|
3916
|
+
yield* checkNode(node.end);
|
|
3917
|
+
}
|
|
3330
3918
|
if (node.body?.length) {
|
|
3331
3919
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.body, function* (node) {
|
|
3332
3920
|
yield* checkNode(node);
|
|
@@ -3350,6 +3938,7 @@ function createErrorDiagnoser(context) {
|
|
|
3350
3938
|
*/
|
|
3351
3939
|
function* checkWhileStatement(node) {
|
|
3352
3940
|
if (ensureNodeKeyExists(node, 'test')) {
|
|
3941
|
+
yield* rejectNullLiteral(node.test);
|
|
3353
3942
|
yield* checkNode(node.test);
|
|
3354
3943
|
}
|
|
3355
3944
|
if (node.body?.length) {
|
|
@@ -3379,11 +3968,23 @@ function createErrorDiagnoser(context) {
|
|
|
3379
3968
|
}
|
|
3380
3969
|
else {
|
|
3381
3970
|
const { name } = node;
|
|
3971
|
+
// 检查是否在 NewComposite 的 properties(左边)中
|
|
3972
|
+
// 如果在 properties 中,可以跳过未定义检查(因为 properties 是属性名,不是变量)
|
|
3973
|
+
// 但如果是在 rights(右边)中,即使有类型也应该检查变量是否定义
|
|
3974
|
+
const newComposite = node.getAncestor('NewComposite');
|
|
3975
|
+
if (newComposite) {
|
|
3976
|
+
const { properties = [] } = newComposite;
|
|
3977
|
+
// 只对 properties 中的 node 允许跳过未定义检查
|
|
3978
|
+
// 无论是否有类型都应该返回,因为 properties 是属性名,不是变量,不应该报"未定义"错误
|
|
3979
|
+
if (properties.some((item) => item === node)) {
|
|
3980
|
+
const type = env.getType(node);
|
|
3981
|
+
return type || errorType;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3382
3984
|
if (
|
|
3383
3985
|
// name 是 A.B.C
|
|
3384
3986
|
name?.split('.')?.length > 1
|
|
3385
|
-
|| node.getAncestor('CallQueryComponent')
|
|
3386
|
-
|| node.getAncestor('NewComposite')) {
|
|
3987
|
+
|| node.getAncestor('CallQueryComponent')) {
|
|
3387
3988
|
const type = env.getType(node);
|
|
3388
3989
|
if (type) {
|
|
3389
3990
|
return type;
|
|
@@ -3403,6 +4004,36 @@ function createErrorDiagnoser(context) {
|
|
|
3403
4004
|
scopedError(node, `${node.toNaturalTS()} 枚举类不能直接使用,请取它的枚举项`);
|
|
3404
4005
|
return errorType;
|
|
3405
4006
|
}
|
|
4007
|
+
const map = {
|
|
4008
|
+
frontend: {
|
|
4009
|
+
scope: '前端',
|
|
4010
|
+
forbiddenScope: '服务端',
|
|
4011
|
+
prefix: 'app.frontendTypes',
|
|
4012
|
+
forbiddenPrefix: 'app.backend',
|
|
4013
|
+
},
|
|
4014
|
+
backend: {
|
|
4015
|
+
scope: '服务端',
|
|
4016
|
+
forbiddenScope: '前端',
|
|
4017
|
+
prefix: 'app.backend',
|
|
4018
|
+
forbiddenPrefix: 'app.frontendTypes',
|
|
4019
|
+
forbiddenCalleeNamesMap: {
|
|
4020
|
+
'nasl.auth': ['userInfo']
|
|
4021
|
+
}
|
|
4022
|
+
},
|
|
4023
|
+
};
|
|
4024
|
+
const config = inFrontendType ? map.frontend : map.backend;
|
|
4025
|
+
const { scope, forbiddenScope, prefix, forbiddenPrefix, forbiddenCalleeNamesMap } = config;
|
|
4026
|
+
// 互斥的端类型 (pc <-> h5)
|
|
4027
|
+
const forbiddenKind = currentFrontendKind === 'pc' ? 'h5' : 'pc';
|
|
4028
|
+
if (node.namespace?.startsWith(forbiddenPrefix)) {
|
|
4029
|
+
error(node, `不能在${scope}作用域下使用${forbiddenScope}全局变量`);
|
|
4030
|
+
}
|
|
4031
|
+
else if (inFrontendType && node.namespace?.startsWith(`${prefix}.${forbiddenKind}`)) {
|
|
4032
|
+
error(node, `不能在${currentFrontendKind}作用域下使用${forbiddenKind}作用域下的前端变量`);
|
|
4033
|
+
}
|
|
4034
|
+
if (forbiddenCalleeNamesMap?.[node.namespace]?.includes(node.name)) {
|
|
4035
|
+
error(node, '不能在服务端作用域下使用前端公共变量');
|
|
4036
|
+
}
|
|
3406
4037
|
// 检查使用范围
|
|
3407
4038
|
yield* checkScopeOfUse(node, ref);
|
|
3408
4039
|
return env.getType(node);
|
|
@@ -3469,6 +4100,11 @@ function createErrorDiagnoser(context) {
|
|
|
3469
4100
|
if (typeof node.value !== 'string') {
|
|
3470
4101
|
error(node, '字符串');
|
|
3471
4102
|
}
|
|
4103
|
+
const MAX_LENGTH = 2 ** 16;
|
|
4104
|
+
if (node.value.length > MAX_LENGTH) {
|
|
4105
|
+
// 插值内部的字符串过长时要报错在插值节点上有红框
|
|
4106
|
+
error(node.parentNode?.concept === 'StringInterpolation' ? node.parentNode : node, `存在过长文本,当前长度:${node.value.length},最大长度:${MAX_LENGTH}。建议使用变量拆分文本后再拼接。`);
|
|
4107
|
+
}
|
|
3472
4108
|
return env.getType(node);
|
|
3473
4109
|
}
|
|
3474
4110
|
/**
|
|
@@ -3531,15 +4167,6 @@ function createErrorDiagnoser(context) {
|
|
|
3531
4167
|
}
|
|
3532
4168
|
}
|
|
3533
4169
|
const ty = env.getType(node);
|
|
3534
|
-
function isConcreteTy(ty) {
|
|
3535
|
-
if (ty.typeKind === 'generic') {
|
|
3536
|
-
return !!ty.typeArguments?.every(x => isConcreteTy(x));
|
|
3537
|
-
}
|
|
3538
|
-
else if (ty.typeKind === 'primitive') {
|
|
3539
|
-
return ty !== type_manager_1.naslAnyTy;
|
|
3540
|
-
}
|
|
3541
|
-
return true;
|
|
3542
|
-
}
|
|
3543
4170
|
if (!ty || !isConcreteTy(ty)) {
|
|
3544
4171
|
const p = node.parentNode;
|
|
3545
4172
|
if (p && p.name === 'dataSourceWatch' && (0, asserts_1.isBindAttribute)(p)) {
|
|
@@ -3581,7 +4208,13 @@ function createErrorDiagnoser(context) {
|
|
|
3581
4208
|
});
|
|
3582
4209
|
}
|
|
3583
4210
|
}
|
|
3584
|
-
|
|
4211
|
+
const type = env.getType(node);
|
|
4212
|
+
if (!node.keys?.length && !node.values?.length) {
|
|
4213
|
+
if (!type || !isConcreteTy(type)) {
|
|
4214
|
+
error(node, `NewMap 无法推导类型,请添加元素`);
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
return type;
|
|
3585
4218
|
}
|
|
3586
4219
|
/**
|
|
3587
4220
|
* 检查 实体/(匿名)数据结构构造器
|
|
@@ -3589,6 +4222,7 @@ function createErrorDiagnoser(context) {
|
|
|
3589
4222
|
*/
|
|
3590
4223
|
function* checkNewComposite(node) {
|
|
3591
4224
|
yield* semantic_rules_1.ExtraSemanticRules.rejectFileTypes(node, env, error);
|
|
4225
|
+
ensureNodeKeyExists(node, 'typeAnnotation');
|
|
3592
4226
|
const type = env.getType(node);
|
|
3593
4227
|
if (node.properties?.length) {
|
|
3594
4228
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.properties, function* (property, index) {
|
|
@@ -3624,7 +4258,59 @@ function createErrorDiagnoser(context) {
|
|
|
3624
4258
|
}
|
|
3625
4259
|
return type;
|
|
3626
4260
|
}
|
|
4261
|
+
/**
|
|
4262
|
+
* 获取正则表达式错误的中文消息
|
|
4263
|
+
*/
|
|
4264
|
+
function getRegexErrorMessage(errorId) {
|
|
4265
|
+
const errors = nasl_utils_1.referenceContent?.errors;
|
|
4266
|
+
return errors?.[errorId] || `正则表达式错误: ${errorId}`;
|
|
4267
|
+
}
|
|
4268
|
+
/**
|
|
4269
|
+
* 检查正则表达式标志符
|
|
4270
|
+
*/
|
|
4271
|
+
function checkRegexFlags(flags, targetNode) {
|
|
4272
|
+
const VALID_FLAGS = new Set(['g', 'i', 'm', 's', 'u', 'y']);
|
|
4273
|
+
const flagCounts = new Map();
|
|
4274
|
+
for (const flag of flags) {
|
|
4275
|
+
if (!VALID_FLAGS.has(flag)) {
|
|
4276
|
+
error(targetNode, `正则表达式标志符 "${flag}" 无效`);
|
|
4277
|
+
continue;
|
|
4278
|
+
}
|
|
4279
|
+
const count = (flagCounts.get(flag) || 0) + 1;
|
|
4280
|
+
flagCounts.set(flag, count);
|
|
4281
|
+
if (count > 1) {
|
|
4282
|
+
error(targetNode, `正则表达式标志符 "${flag}" 重复`);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
/**
|
|
4287
|
+
* 检查正则表达式语法和标志符
|
|
4288
|
+
*/
|
|
3627
4289
|
function checkNewRegex(node) {
|
|
4290
|
+
// 检查正则表达式模式语法
|
|
4291
|
+
if (nasl_concepts_1.asserts.isStringLiteral(node.pattern)) {
|
|
4292
|
+
const targetNode = node.kind === 'static' ? node : node.pattern;
|
|
4293
|
+
try {
|
|
4294
|
+
regeExp.parse(`/${node.pattern.value}/`);
|
|
4295
|
+
const parseErrors = regeExp.errors;
|
|
4296
|
+
if (Array.isArray(parseErrors) && parseErrors.length > 0) {
|
|
4297
|
+
parseErrors.forEach((errToken) => {
|
|
4298
|
+
const errorId = errToken?.error?.id;
|
|
4299
|
+
if (errorId) {
|
|
4300
|
+
error(targetNode, getRegexErrorMessage(errorId));
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
catch (e) {
|
|
4306
|
+
error(targetNode, '正则表达式解析失败');
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
// 检查正则表达式标志符
|
|
4310
|
+
if (nasl_concepts_1.asserts.isStringLiteral(node.flags)) {
|
|
4311
|
+
const targetNode = node.kind === 'static' ? node : node.flags;
|
|
4312
|
+
checkRegexFlags(node.flags.value, targetNode);
|
|
4313
|
+
}
|
|
3628
4314
|
return env.getType(node);
|
|
3629
4315
|
}
|
|
3630
4316
|
/**
|
|
@@ -3638,36 +4324,8 @@ function createErrorDiagnoser(context) {
|
|
|
3638
4324
|
if (ensureNodeKeyExists(node, 'right')) {
|
|
3639
4325
|
yield* checkNode(node.right);
|
|
3640
4326
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
const rightType = env.getType(node.right);
|
|
3644
|
-
// 检查左右两侧同为匿名结构体时,对每个字段,都增加 Decimal 和 Union<Decimal, Long> 的检查
|
|
3645
|
-
if (leftType && rightType && (0, type_predicate_1.isAnonymousTy)(leftType) && (0, type_predicate_1.isAnonymousTy)(rightType)) {
|
|
3646
|
-
const leftProps = leftType.properties ?? [];
|
|
3647
|
-
const rightProps = rightType.properties ?? [];
|
|
3648
|
-
for (const leftProp of leftProps) {
|
|
3649
|
-
const rightProp = rightProps.find(p => p.name === leftProp.name);
|
|
3650
|
-
if (rightProp) {
|
|
3651
|
-
if (leftProp.typeAnnotation && rightProp.typeAnnotation) {
|
|
3652
|
-
if ((0, type_predicate_1.isDecimalTy)(leftProp.typeAnnotation) && (0, type_predicate_1.isDecimalUnionLongTy)(rightProp.typeAnnotation)) {
|
|
3653
|
-
error(node, `赋值:字段 ${leftProp.name} 类型不匹配,不能将 ${(0, type_manager_1.showUserLevelType)(rightProp.typeAnnotation)} 赋值给 ${(0, type_manager_1.showUserLevelType)(leftProp.typeAnnotation)} 类型。`);
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
if (leftType && rightType) {
|
|
3660
|
-
// 检查左侧是否为 Decimal
|
|
3661
|
-
const isLeftDecimal = leftType.typeKind === 'primitive' && leftType.typeName === 'Decimal';
|
|
3662
|
-
// 检查右侧是否为 Union<Decimal, Long> 或 Union<Long, Decimal>
|
|
3663
|
-
if (isLeftDecimal && rightType.typeKind === 'union' && rightType.typeArguments) {
|
|
3664
|
-
const typeArgs = rightType.typeArguments;
|
|
3665
|
-
const hasDecimal = typeArgs.some((arg) => arg.typeKind === 'primitive' && arg.typeName === 'Decimal');
|
|
3666
|
-
const hasLong = typeArgs.some((arg) => arg.typeKind === 'primitive' && arg.typeName === 'Long');
|
|
3667
|
-
if (hasDecimal && hasLong && typeArgs.length === 2) {
|
|
3668
|
-
error(node, `赋值:类型不匹配,不能将联合类型 ${(0, type_manager_1.showUserLevelType)(rightType)} 赋值给 ${(0, type_manager_1.showUserLevelType)(leftType)} 类型。`);
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
4327
|
+
if (nasl_concepts_1.asserts.isMemberExpression(node.left)) {
|
|
4328
|
+
reportCurrentItemModification(node.left, node);
|
|
3671
4329
|
}
|
|
3672
4330
|
}
|
|
3673
4331
|
/**
|
|
@@ -3731,6 +4389,9 @@ function createErrorDiagnoser(context) {
|
|
|
3731
4389
|
error(args[i], '当前参数无效,不可用。');
|
|
3732
4390
|
}
|
|
3733
4391
|
}
|
|
4392
|
+
if (isAIStrictMode) {
|
|
4393
|
+
yield* checkCallExpressionParameters(node, ref);
|
|
4394
|
+
}
|
|
3734
4395
|
}
|
|
3735
4396
|
else {
|
|
3736
4397
|
error(node, `找不到页面${node.viewName}`);
|
|
@@ -3777,6 +4438,34 @@ function createErrorDiagnoser(context) {
|
|
|
3777
4438
|
severity: Severity.WARN,
|
|
3778
4439
|
});
|
|
3779
4440
|
}
|
|
4441
|
+
/**
|
|
4442
|
+
* 检查数据查询是否在有效的上下文中使用
|
|
4443
|
+
* 数据查询不能直接放在语句中,只能作为表达式使用
|
|
4444
|
+
*/
|
|
4445
|
+
function* checkQueryContextValidity(node) {
|
|
4446
|
+
const parent = node.parentNode;
|
|
4447
|
+
const parentConcept = parent.concept;
|
|
4448
|
+
// 允许的父节点类型直接返回
|
|
4449
|
+
const invalidParentConcepts = ['Logic', 'IfStatement', 'ForEachStatement', 'SwitchCase', 'MatchCase', 'WhileStatement'];
|
|
4450
|
+
if (!invalidParentConcepts.includes(parentConcept)) {
|
|
4451
|
+
return;
|
|
4452
|
+
}
|
|
4453
|
+
// MatchCase 在表达式场景下是例外:Match 表达式中的 MatchCase 允许数据查询
|
|
4454
|
+
if (parentConcept === 'MatchCase') {
|
|
4455
|
+
const matchNode = parent.parentNode;
|
|
4456
|
+
if (matchNode?.concept === 'Match' && matchNode.isExpression) {
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
// 获取当前节点在父节点中的属性名
|
|
4461
|
+
const parentKey = node.parentKey;
|
|
4462
|
+
// 只在 parentKey 为 body、consequent、alternate 时才报错
|
|
4463
|
+
const errorParentKeys = ['body', 'consequent', 'alternate'];
|
|
4464
|
+
if (!errorParentKeys.includes(parentKey)) {
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
error(node, '数据查询不能直接放在语句中,只能作为表达式使用。');
|
|
4468
|
+
}
|
|
3780
4469
|
/**
|
|
3781
4470
|
* 检查 数据查询
|
|
3782
4471
|
* @param node
|
|
@@ -3788,6 +4477,8 @@ function createErrorDiagnoser(context) {
|
|
|
3788
4477
|
else {
|
|
3789
4478
|
error(node, '预期 1 个参数,但传入了 0 个。');
|
|
3790
4479
|
}
|
|
4480
|
+
// 检查数据查询的使用上下文
|
|
4481
|
+
yield* checkQueryContextValidity(node);
|
|
3791
4482
|
// where 子句
|
|
3792
4483
|
yield* checkNode(node.where);
|
|
3793
4484
|
// wherePlayground
|
|
@@ -3859,7 +4550,13 @@ function createErrorDiagnoser(context) {
|
|
|
3859
4550
|
return;
|
|
3860
4551
|
}
|
|
3861
4552
|
const { entityAsName } = node;
|
|
3862
|
-
const
|
|
4553
|
+
const callQueryComponent = node.getAncestor('CallQueryComponent');
|
|
4554
|
+
if (!callQueryComponent.inFromJoinPart(entityAsName)) {
|
|
4555
|
+
// 实体不存在from中
|
|
4556
|
+
error(node, '数据查询:所查询的实体不存在数据源中。');
|
|
4557
|
+
return;
|
|
4558
|
+
}
|
|
4559
|
+
const entityNamespace = node.entityNamespace ?? callQueryComponent?.from?.entityNamespace;
|
|
3863
4560
|
const entityQName = `${entityNamespace}.${entityAsName}`;
|
|
3864
4561
|
const ref = referenceManager.gQNameDefs.get(entityQName);
|
|
3865
4562
|
if (!ref) {
|
|
@@ -3902,6 +4599,9 @@ function createErrorDiagnoser(context) {
|
|
|
3902
4599
|
yield* checkNode(node);
|
|
3903
4600
|
});
|
|
3904
4601
|
}
|
|
4602
|
+
else {
|
|
4603
|
+
error(node, 'Join 子句的关联关系不能为空');
|
|
4604
|
+
}
|
|
3905
4605
|
if (node.joinParts?.length) {
|
|
3906
4606
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.joinParts, function* (node) {
|
|
3907
4607
|
yield* checkNode(node);
|
|
@@ -3936,6 +4636,9 @@ function createErrorDiagnoser(context) {
|
|
|
3936
4636
|
else if (!node.asName) {
|
|
3937
4637
|
error(node, '数据查询:聚合函数别名不能为空。');
|
|
3938
4638
|
}
|
|
4639
|
+
if (node.aggregateParam) {
|
|
4640
|
+
yield* checkNode(node.aggregateParam);
|
|
4641
|
+
}
|
|
3939
4642
|
}
|
|
3940
4643
|
/**
|
|
3941
4644
|
* 检查 SQL 查询
|
|
@@ -4003,10 +4706,17 @@ function createErrorDiagnoser(context) {
|
|
|
4003
4706
|
*/
|
|
4004
4707
|
function* checkBinaryExpression(node) {
|
|
4005
4708
|
yield* semantic_rules_1.ExtraSemanticRules.rejectFileTypes(node, env, error);
|
|
4709
|
+
const isCandidate = ['+', '-', '*', '/', '%', '>', '<', '>=', '<='].includes(node.operator);
|
|
4006
4710
|
if (ensureNodeKeyExists(node, 'left')) {
|
|
4711
|
+
if (isCandidate) {
|
|
4712
|
+
yield* rejectNullLiteral(node.left);
|
|
4713
|
+
}
|
|
4007
4714
|
yield* checkNode(node.left);
|
|
4008
4715
|
}
|
|
4009
4716
|
if (ensureNodeKeyExists(node, 'right')) {
|
|
4717
|
+
if (isCandidate) {
|
|
4718
|
+
yield* rejectNullLiteral(node.right);
|
|
4719
|
+
}
|
|
4010
4720
|
yield* checkNode(node.right);
|
|
4011
4721
|
}
|
|
4012
4722
|
return env.getType(node);
|
|
@@ -4019,6 +4729,7 @@ function createErrorDiagnoser(context) {
|
|
|
4019
4729
|
error(node, '表达式不能为空!');
|
|
4020
4730
|
}
|
|
4021
4731
|
yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.expressions, function* (node) {
|
|
4732
|
+
yield* rejectNullLiteral(node);
|
|
4022
4733
|
yield* checkNode(node);
|
|
4023
4734
|
});
|
|
4024
4735
|
return env.getType(node);
|
|
@@ -4029,6 +4740,13 @@ function createErrorDiagnoser(context) {
|
|
|
4029
4740
|
*/
|
|
4030
4741
|
function* checkUnaryExpression(node) {
|
|
4031
4742
|
if (ensureNodeKeyExists(node, 'argument')) {
|
|
4743
|
+
const isCandidate = ['!'].includes(node.operator);
|
|
4744
|
+
if (isCandidate) {
|
|
4745
|
+
yield* rejectNullLiteral(node.argument);
|
|
4746
|
+
}
|
|
4747
|
+
if (node.operator === 'isNull' && node.getAncestor('CallQueryComponent') && !nasl_concepts_1.asserts.isQueryFieldExpression(node.argument)) {
|
|
4748
|
+
error(node, '数据查询:数据查询中的 isNull 只面向实体属性使用。针对变量和表达式为空值的情况,已自动进行剪枝处理,请移除条件中的 isNull 即可。');
|
|
4749
|
+
}
|
|
4032
4750
|
yield* checkNode(node.argument);
|
|
4033
4751
|
}
|
|
4034
4752
|
return env.getType(node);
|
|
@@ -4051,6 +4769,26 @@ function createErrorDiagnoser(context) {
|
|
|
4051
4769
|
if (typeAnnotation?.typeKind === 'generic') {
|
|
4052
4770
|
yield* checkGenericTypeHasExpectedTyArgs(typeAnnotation);
|
|
4053
4771
|
}
|
|
4772
|
+
// 在特定流程逻辑中,data 形参必须传入且内部 new 对象的 data 字段需要显式初始化且不可为 null
|
|
4773
|
+
if (node.keyword === 'data' && node.getAncestor('CallLogic')?.isSpecificProcessLogic && ensureNodeKeyExists(node, 'expression')) {
|
|
4774
|
+
if (nasl_concepts_1.asserts.isNewComposite(node.expression)) {
|
|
4775
|
+
const dataIndex = (node.expression.properties || [])?.findIndex((propery) => {
|
|
4776
|
+
return nasl_concepts_1.asserts.isIdentifier(propery) && propery.name === 'data';
|
|
4777
|
+
});
|
|
4778
|
+
if (dataIndex === -1) {
|
|
4779
|
+
error(node, 'data未初始化!');
|
|
4780
|
+
}
|
|
4781
|
+
else {
|
|
4782
|
+
const rightSelectMember = (0, nasl_concepts_1.getRightFromNewCompositeByLeftIndex)({
|
|
4783
|
+
newComposite: node.expression,
|
|
4784
|
+
propertyIndex: dataIndex,
|
|
4785
|
+
});
|
|
4786
|
+
if (nasl_concepts_1.asserts.isNullLiteral(rightSelectMember?.expression)) {
|
|
4787
|
+
error(rightSelectMember?.expression, 'data不能为null');
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4054
4792
|
return typeAnnotation;
|
|
4055
4793
|
}
|
|
4056
4794
|
/**
|
|
@@ -4208,6 +4946,18 @@ function createErrorDiagnoser(context) {
|
|
|
4208
4946
|
error(node, `无返回值的${node?.constructor?.nodeTitle}${node.calleeName} 不能在${parentNode?.constructor?.nodeTitle}中作为表达式使用。`);
|
|
4209
4947
|
}
|
|
4210
4948
|
}
|
|
4949
|
+
/**
|
|
4950
|
+
* 检查 不允许使用 null 字面量
|
|
4951
|
+
* @param node
|
|
4952
|
+
*/
|
|
4953
|
+
function* rejectNullLiteral(node) {
|
|
4954
|
+
if (!node) {
|
|
4955
|
+
return; // 如果 node 为 null 或 undefined,直接返回
|
|
4956
|
+
}
|
|
4957
|
+
if (node.concept === 'NullLiteral') {
|
|
4958
|
+
error(node, `此处不允许使用 null`);
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4211
4961
|
function* dispatchNode(node) {
|
|
4212
4962
|
try {
|
|
4213
4963
|
// 调用表达式
|
|
@@ -4260,6 +5010,8 @@ function createErrorDiagnoser(context) {
|
|
|
4260
5010
|
return yield* checkEntity(node);
|
|
4261
5011
|
case 'EntityProperty':
|
|
4262
5012
|
return yield* checkEntityProperty(node);
|
|
5013
|
+
case 'EntityIndex':
|
|
5014
|
+
return yield* checkEntityIndex(node);
|
|
4263
5015
|
case 'Enum':
|
|
4264
5016
|
return yield* checkEnum(node);
|
|
4265
5017
|
case 'EnumItem':
|
|
@@ -4354,6 +5106,8 @@ function createErrorDiagnoser(context) {
|
|
|
4354
5106
|
return yield* checkBlock(node);
|
|
4355
5107
|
case 'IfStatement':
|
|
4356
5108
|
return yield* checkIfStatement(node);
|
|
5109
|
+
case 'IfExpression':
|
|
5110
|
+
return yield* checkIfExpression(node);
|
|
4357
5111
|
case 'Match':
|
|
4358
5112
|
return yield* checkMatch(node);
|
|
4359
5113
|
case 'MatchCase':
|
|
@@ -4471,6 +5225,13 @@ function createErrorDiagnoser(context) {
|
|
|
4471
5225
|
// TODO: checkNodeIsFileModuleInIdeWithCache 也慢,后续可以优化
|
|
4472
5226
|
const isFileModule = fileNodes.length ? (0, file_node_cache_1.checkNodeIsFileModuleInIdeWithCache)(node) : true;
|
|
4473
5227
|
if (isFileModule) {
|
|
5228
|
+
// 增量更新时按文件逐个 checkNode,需重置闭包状态;若本文件在 Module 内,inModule 需为 true(否则不会经过 checkModule,inModule 会失效)
|
|
5229
|
+
inModule = !!node.getAncestor?.('Module');
|
|
5230
|
+
inPlayground = 0;
|
|
5231
|
+
inAuthLogic = false;
|
|
5232
|
+
const frontendType = node.getAncestor?.('FrontendType');
|
|
5233
|
+
inFrontendType = !!frontendType;
|
|
5234
|
+
currentFrontendKind = frontendType?.kind || '';
|
|
4474
5235
|
fileNodes.push(node);
|
|
4475
5236
|
let diagnostics = diagnosticMap.get(node);
|
|
4476
5237
|
if (!diagnostics) {
|
|
@@ -4593,6 +5354,84 @@ function createErrorDiagnoser(context) {
|
|
|
4593
5354
|
});
|
|
4594
5355
|
return flattenedDiags;
|
|
4595
5356
|
}
|
|
5357
|
+
/**
|
|
5358
|
+
* 重新验证祖先节点
|
|
5359
|
+
* 用于增量更新时,当子节点变更需要重新验证祖先节点的场景
|
|
5360
|
+
* @param ancestorNode 需要重新验证的祖先节点
|
|
5361
|
+
* @param validationFn 验证函数
|
|
5362
|
+
* @param errorFilter 错误过滤函数(可选),用于保留特定的错误(返回 true 表示保留)
|
|
5363
|
+
* @param siblingNodeTypes 需要过滤错误的兄弟节点类型(可选)
|
|
5364
|
+
* @param getSiblingNodes 获取兄弟节点的函数(可选),用于从祖先节点中获取需要过滤错误的兄弟节点
|
|
5365
|
+
* @returns 新的诊断信息 Map,如果没有则返回 null
|
|
5366
|
+
*/
|
|
5367
|
+
function revalidateAncestorNode(ancestorNode, validationFn, errorFilter, siblingNodeTypes, getSiblingNodes) {
|
|
5368
|
+
// 收集所有需要处理的文件节点(祖先节点 + 兄弟节点)
|
|
5369
|
+
const fileNodesToProcess = [ancestorNode];
|
|
5370
|
+
// 获取需要过滤错误的兄弟节点(通过配置的 getSiblingNodes 函数)
|
|
5371
|
+
if (errorFilter && getSiblingNodes) {
|
|
5372
|
+
const siblings = getSiblingNodes(ancestorNode);
|
|
5373
|
+
if (siblingNodeTypes && siblingNodeTypes.length > 0) {
|
|
5374
|
+
fileNodesToProcess.push(...siblings.filter(s => siblingNodeTypes.includes(s.concept)));
|
|
5375
|
+
}
|
|
5376
|
+
else {
|
|
5377
|
+
fileNodesToProcess.push(...siblings);
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
// 收集所有文件节点需要保留的错误
|
|
5381
|
+
const errorsToRestore = [];
|
|
5382
|
+
for (const fileNode of fileNodesToProcess) {
|
|
5383
|
+
const diagnostics = getDiagnostics(fileNode);
|
|
5384
|
+
if (diagnostics) {
|
|
5385
|
+
diagnostics.forEach((errs, node) => {
|
|
5386
|
+
// 如果有 errorFilter,只保留满足条件的错误;否则保留所有错误
|
|
5387
|
+
const filtered = errorFilter ? errs.filter(errorFilter) : errs;
|
|
5388
|
+
filtered.forEach(err => {
|
|
5389
|
+
errorsToRestore.push({ fileNode, node, err });
|
|
5390
|
+
});
|
|
5391
|
+
});
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
// 清除所有文件节点的诊断信息
|
|
5395
|
+
for (const fileNode of fileNodesToProcess) {
|
|
5396
|
+
clearDiagnostics(fileNode);
|
|
5397
|
+
let diagnostics = diagnosticMap.get(fileNode);
|
|
5398
|
+
if (!diagnostics && fileNode) {
|
|
5399
|
+
diagnostics = new Map();
|
|
5400
|
+
diagnosticMap.set(fileNode, diagnostics);
|
|
5401
|
+
}
|
|
5402
|
+
diagnostics.set(fileNode, []);
|
|
5403
|
+
}
|
|
5404
|
+
// 恢复所有需要保留的错误
|
|
5405
|
+
errorsToRestore.forEach(({ fileNode, node, err }) => {
|
|
5406
|
+
const { message, severity, ...context } = err;
|
|
5407
|
+
if (message) {
|
|
5408
|
+
error(node, message, {
|
|
5409
|
+
severity: severity,
|
|
5410
|
+
fileNode,
|
|
5411
|
+
...context,
|
|
5412
|
+
});
|
|
5413
|
+
}
|
|
5414
|
+
});
|
|
5415
|
+
// 设置文件节点上下文并执行验证
|
|
5416
|
+
const cleanup = handleFileNode(ancestorNode);
|
|
5417
|
+
try {
|
|
5418
|
+
validationFn(ancestorNode, env);
|
|
5419
|
+
}
|
|
5420
|
+
finally {
|
|
5421
|
+
cleanup?.();
|
|
5422
|
+
}
|
|
5423
|
+
// 收集并返回所有文件节点的诊断信息
|
|
5424
|
+
const allDiagnostics = new Map();
|
|
5425
|
+
for (const fileNode of fileNodesToProcess) {
|
|
5426
|
+
const diagnostics = getDiagnostics(fileNode);
|
|
5427
|
+
if (diagnostics) {
|
|
5428
|
+
diagnostics.forEach((errs, node) => {
|
|
5429
|
+
allDiagnostics.set(node, errs);
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
return allDiagnostics.size > 0 ? allDiagnostics : null;
|
|
5434
|
+
}
|
|
4596
5435
|
return {
|
|
4597
5436
|
setMetadataTypeEnable,
|
|
4598
5437
|
setLatestVersionsOfSharedApp,
|
|
@@ -4601,11 +5440,72 @@ function createErrorDiagnoser(context) {
|
|
|
4601
5440
|
clearDiagnostics,
|
|
4602
5441
|
getDiagnostics,
|
|
4603
5442
|
getAllDiagnostics,
|
|
5443
|
+
checkAllProcessCircularReferences,
|
|
4604
5444
|
getDebugDiagnostics,
|
|
4605
5445
|
error,
|
|
5446
|
+
checkViewIndexPageSetting,
|
|
5447
|
+
revalidateAncestorNode,
|
|
4606
5448
|
};
|
|
4607
5449
|
}
|
|
4608
5450
|
exports.createErrorDiagnoser = createErrorDiagnoser;
|
|
5451
|
+
/**
|
|
5452
|
+
* 检查字段路径是否在类型上存在
|
|
5453
|
+
* 类似于 s-component-attr-form.vue 中的 findPropertyNode 功能
|
|
5454
|
+
* @param env 语义环境
|
|
5455
|
+
* @param dataType 数据源类型
|
|
5456
|
+
* @param fieldPath 字段路径,如 "name" 或 "user.name"
|
|
5457
|
+
* @returns { exists: boolean, missingField?: string } 如果字段不存在,返回第一个不存在的字段名
|
|
5458
|
+
*/
|
|
5459
|
+
function checkFieldExistsInType(env, dataType, fieldPath) {
|
|
5460
|
+
if (!dataType || !fieldPath) {
|
|
5461
|
+
return { exists: true }; // 没有类型信息或字段路径时,跳过检查
|
|
5462
|
+
}
|
|
5463
|
+
const fieldParts = fieldPath.split('.');
|
|
5464
|
+
let currentType = dataType;
|
|
5465
|
+
for (const fieldName of fieldParts) {
|
|
5466
|
+
if (!currentType) {
|
|
5467
|
+
return { exists: false, missingField: fieldName };
|
|
5468
|
+
}
|
|
5469
|
+
// 获取类型的 properties
|
|
5470
|
+
let properties;
|
|
5471
|
+
if (currentType.typeKind === 'anonymousStructure') {
|
|
5472
|
+
properties = currentType.properties;
|
|
5473
|
+
}
|
|
5474
|
+
else if (currentType.typeKind === 'reference') {
|
|
5475
|
+
// 对于引用类型,需要解析引用
|
|
5476
|
+
const resolved = env.resolveRef(currentType);
|
|
5477
|
+
// @ts-expect-error
|
|
5478
|
+
properties = resolved?.properties;
|
|
5479
|
+
}
|
|
5480
|
+
else if (currentType.typeKind === 'generic' && currentType.typeName === 'List') {
|
|
5481
|
+
// 对于 List<T>,检查 T 的属性
|
|
5482
|
+
currentType = currentType.typeArguments?.[0];
|
|
5483
|
+
if (currentType?.typeKind === 'anonymousStructure') {
|
|
5484
|
+
properties = currentType.properties;
|
|
5485
|
+
}
|
|
5486
|
+
else if (currentType?.typeKind === 'reference') {
|
|
5487
|
+
const resolved = env.resolveRef(currentType);
|
|
5488
|
+
// @ts-expect-error
|
|
5489
|
+
properties = resolved?.properties;
|
|
5490
|
+
}
|
|
5491
|
+
}
|
|
5492
|
+
if (!properties) {
|
|
5493
|
+
// 类型没有 properties,可能是基本类型或未解析的类型
|
|
5494
|
+
// 对于未解析的类型,跳过检查
|
|
5495
|
+
if ((0, type_predicate_1.isTyAnnTyParam)(currentType) || (0, type_predicate_1.isAnyTy)(currentType)) {
|
|
5496
|
+
return { exists: true };
|
|
5497
|
+
}
|
|
5498
|
+
return { exists: false, missingField: fieldName };
|
|
5499
|
+
}
|
|
5500
|
+
const prop = properties.find((p) => p.name === fieldName);
|
|
5501
|
+
if (!prop) {
|
|
5502
|
+
return { exists: false, missingField: fieldName };
|
|
5503
|
+
}
|
|
5504
|
+
// 继续检查下一级
|
|
5505
|
+
currentType = prop.typeAnnotation;
|
|
5506
|
+
}
|
|
5507
|
+
return { exists: true };
|
|
5508
|
+
}
|
|
4609
5509
|
function deduplicateDiagnostics(diagnostics) {
|
|
4610
5510
|
return (0, lodash_1.uniqWith)(diagnostics, (a, b) => a.node === b.node && a.message === b.message);
|
|
4611
5511
|
}
|
|
@@ -4655,7 +5555,7 @@ exports.transformDiagnosticsToRecords = transformDiagnosticsToRecords;
|
|
|
4655
5555
|
// @ts-expect-error
|
|
4656
5556
|
const toRaw = (nd) => nd.__v_raw ?? nd;
|
|
4657
5557
|
// 是否来自系统内置
|
|
4658
|
-
const isBuiltInCall = (nd) => !!nd.calleeNamespace?.startsWith('nasl.');
|
|
5558
|
+
const isBuiltInCall = (nd) => !!nd.calleeNamespace?.startsWith('nasl.') && !['nasl.auth'].includes(nd.calleeNamespace);
|
|
4659
5559
|
// 获取依赖库名
|
|
4660
5560
|
const getExtensionLibName = (nd) => nd.calleeNamespace?.startsWith('extensions.') ? nd.calleeNamespace.split('.')[1] : undefined;
|
|
4661
5561
|
//# sourceMappingURL=checker.js.map
|