@lcap/nasl-language-server-core 4.4.0-beta.9 → 4.4.0-rc.2

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.
Files changed (95) hide show
  1. package/out/checker.d.ts +2 -1
  2. package/out/checker.d.ts.map +1 -1
  3. package/out/checker.js +613 -92
  4. package/out/checker.js.map +1 -1
  5. package/out/reference-manager/collect-q-name.d.ts.map +1 -1
  6. package/out/reference-manager/collect-q-name.js +6 -3
  7. package/out/reference-manager/collect-q-name.js.map +1 -1
  8. package/out/reference-manager/get-q-name.d.ts.map +1 -1
  9. package/out/reference-manager/get-q-name.js +12 -4
  10. package/out/reference-manager/get-q-name.js.map +1 -1
  11. package/out/reference-manager/reference-manager.d.ts +94 -12
  12. package/out/reference-manager/reference-manager.d.ts.map +1 -1
  13. package/out/reference-manager/reference-manager.js +270 -77
  14. package/out/reference-manager/reference-manager.js.map +1 -1
  15. package/out/reference-manager/remove-q-name.d.ts.map +1 -1
  16. package/out/reference-manager/remove-q-name.js +7 -4
  17. package/out/reference-manager/remove-q-name.js.map +1 -1
  18. package/out/reference-manager/symbol-type.d.ts +1 -1
  19. package/out/reference-manager/symbol-type.d.ts.map +1 -1
  20. package/out/reference-manager/symbol-type.js +1 -0
  21. package/out/reference-manager/symbol-type.js.map +1 -1
  22. package/out/reference-manager/update-nasl-fragment.d.ts +2 -2
  23. package/out/reference-manager/update-nasl-fragment.d.ts.map +1 -1
  24. package/out/reference-manager/update-nasl-fragment.js +29 -24
  25. package/out/reference-manager/update-nasl-fragment.js.map +1 -1
  26. package/out/reference-manager/view-elem-logic.js.map +1 -1
  27. package/out/services/bindable-logic-service.js.map +1 -1
  28. package/out/symbol/traverse/concepts/index.d.ts +1 -1
  29. package/out/symbol/traverse/concepts/index.d.ts.map +1 -1
  30. package/out/symbol/traverse/concepts/logic/expression/member-expression.js.map +1 -1
  31. package/out/typer/collectGlobalDefs.d.ts +8 -0
  32. package/out/typer/collectGlobalDefs.d.ts.map +1 -1
  33. package/out/typer/collectGlobalDefs.js +74 -49
  34. package/out/typer/collectGlobalDefs.js.map +1 -1
  35. package/out/typer/dispatch-all.d.ts.map +1 -1
  36. package/out/typer/dispatch-all.js +3 -1
  37. package/out/typer/dispatch-all.js.map +1 -1
  38. package/out/typer/dispatch-call.d.ts.map +1 -1
  39. package/out/typer/dispatch-call.js +4 -0
  40. package/out/typer/dispatch-call.js.map +1 -1
  41. package/out/typer/dispatch-def.d.ts.map +1 -1
  42. package/out/typer/dispatch-def.js +45 -19
  43. package/out/typer/dispatch-def.js.map +1 -1
  44. package/out/typer/dispatch-expr.d.ts.map +1 -1
  45. package/out/typer/dispatch-expr.js +69 -60
  46. package/out/typer/dispatch-expr.js.map +1 -1
  47. package/out/typer/dispatch-process.d.ts +8 -1
  48. package/out/typer/dispatch-process.d.ts.map +1 -1
  49. package/out/typer/dispatch-process.js +34 -1
  50. package/out/typer/dispatch-process.js.map +1 -1
  51. package/out/typer/dispatch-stmt.js.map +1 -1
  52. package/out/typer/dispatch-view.d.ts.map +1 -1
  53. package/out/typer/dispatch-view.js +56 -20
  54. package/out/typer/dispatch-view.js.map +1 -1
  55. package/out/typer/fix-use-before-assign.js.map +1 -1
  56. package/out/typer/get-oql-files.d.ts.map +1 -1
  57. package/out/typer/get-oql-files.js +2 -8
  58. package/out/typer/get-oql-files.js.map +1 -1
  59. package/out/typer/incremental-update.d.ts +2 -5
  60. package/out/typer/incremental-update.d.ts.map +1 -1
  61. package/out/typer/incremental-update.js +95 -19
  62. package/out/typer/incremental-update.js.map +1 -1
  63. package/out/typer/oql-checker/ts-parser.d.ts.map +1 -1
  64. package/out/typer/oql-checker/ts-parser.js +99 -20
  65. package/out/typer/oql-checker/ts-parser.js.map +1 -1
  66. package/out/typer/overload-helper.d.ts.map +1 -1
  67. package/out/typer/overload-helper.js +185 -23
  68. package/out/typer/overload-helper.js.map +1 -1
  69. package/out/typer/solver.d.ts +1 -1
  70. package/out/typer/solver.d.ts.map +1 -1
  71. package/out/typer/solver.js +16 -4
  72. package/out/typer/solver.js.map +1 -1
  73. package/out/typer/subster.d.ts.map +1 -1
  74. package/out/typer/subster.js +32 -5
  75. package/out/typer/subster.js.map +1 -1
  76. package/out/typer/topo-sort.d.ts +1 -0
  77. package/out/typer/topo-sort.d.ts.map +1 -1
  78. package/out/typer/topo-sort.js +15 -5
  79. package/out/typer/topo-sort.js.map +1 -1
  80. package/out/typer/type-predicate.d.ts.map +1 -1
  81. package/out/typer/type-predicate.js +11 -3
  82. package/out/typer/type-predicate.js.map +1 -1
  83. package/out/typer/typer.d.ts +1 -1
  84. package/out/typer/typer.d.ts.map +1 -1
  85. package/out/typer/typer.js +14 -3
  86. package/out/typer/typer.js.map +1 -1
  87. package/out/typer/unifier.d.ts.map +1 -1
  88. package/out/typer/unifier.js +9 -7
  89. package/out/typer/unifier.js.map +1 -1
  90. package/out/utils/debug.js.map +1 -1
  91. package/out/utils/type-operator.d.ts +15 -0
  92. package/out/utils/type-operator.d.ts.map +1 -1
  93. package/out/utils/type-operator.js +65 -3
  94. package/out/utils/type-operator.js.map +1 -1
  95. package/package.json +5 -5
package/out/checker.js CHANGED
@@ -240,6 +240,12 @@ function resolveMetadataType(typeArg, app) {
240
240
  return metadataTypes?.find(metadataType => metadataType.name === typeArg.typeName)?.typeAnnotation;
241
241
  }
242
242
  }
243
+ function isConcreteTy(ty) {
244
+ if (ty.typeKind === 'generic') {
245
+ return !!ty.typeArguments?.every(x => isConcreteTy(x));
246
+ }
247
+ return !(0, type_predicate_1.isAnyTy)(ty) && !(0, type_predicate_1.isTyAnnTyParam)(ty);
248
+ }
243
249
  /**
244
250
  * 创建错误诊断器
245
251
  * @returns
@@ -262,6 +268,7 @@ function createErrorDiagnoser(context) {
262
268
  let inAuthLogic = false;
263
269
  let dbType = '';
264
270
  let curVarCounter = 0;
271
+ const listComponentStack = [];
265
272
  const nodeNameCache = new Map();
266
273
  const nodeNameGetterMap = {
267
274
  View: 'getViewExistingNames',
@@ -597,6 +604,9 @@ function createErrorDiagnoser(context) {
597
604
  else if (nasl_concepts_1.asserts.isValidationLogic(node)) {
598
605
  message = '验证函数:参数不能为空!';
599
606
  }
607
+ else if (nasl_concepts_1.asserts.isNewComposite(node)) {
608
+ message = '新建实体/数据结构:参数不能为空!';
609
+ }
600
610
  if (message) {
601
611
  error(node, message);
602
612
  }
@@ -648,6 +658,123 @@ function createErrorDiagnoser(context) {
648
658
  const ref = type?.typeKind === 'anonymousStructure' ? type : env.resolveRef(type);
649
659
  return findPropertyByName(ref, name);
650
660
  }
661
+ // 判断节点是否在指定祖先之内,用于给列表上下文回填 slot 变量
662
+ function isDescendantOf(node, ancestor) {
663
+ let cur = node?.parentNode;
664
+ while (cur) {
665
+ if (cur === ancestor)
666
+ return true;
667
+ cur = cur.parentNode;
668
+ }
669
+ return false;
670
+ }
671
+ function getMemberPropertyName(member) {
672
+ const { property } = member || {};
673
+ if (!property)
674
+ return '';
675
+ if (nasl_concepts_1.asserts.isIdentifier(property))
676
+ return property.name;
677
+ return property?.value ?? property?.name ?? '';
678
+ }
679
+ function getMemberPath(member) {
680
+ const path = [];
681
+ let cur = member;
682
+ while (nasl_concepts_1.asserts.isMemberExpression(cur)) {
683
+ const propName = getMemberPropertyName(cur);
684
+ if (propName) {
685
+ path.unshift(propName);
686
+ }
687
+ const obj = cur.object;
688
+ if (nasl_concepts_1.asserts.isIdentifier(obj)) {
689
+ path.unshift(obj.name);
690
+ break;
691
+ }
692
+ cur = obj;
693
+ }
694
+ return path;
695
+ }
696
+ function isCurrentItemMember(member, slotVarName) {
697
+ if (!slotVarName)
698
+ return false;
699
+ const completeName = member?.completeName;
700
+ if (typeof completeName === 'string' && completeName.startsWith(`${slotVarName}.item`)) {
701
+ return true;
702
+ }
703
+ const path = getMemberPath(member);
704
+ return path[0] === slotVarName && path[1] === 'item';
705
+ }
706
+ // 找到命中的 currentX.item.* 成员表达式,优先返回首个匹配
707
+ function findCurrentItemMember(node, slotVarName) {
708
+ if (!slotVarName || !node)
709
+ return;
710
+ if (nasl_concepts_1.asserts.isMemberExpression(node) && isCurrentItemMember(node, slotVarName)) {
711
+ return node;
712
+ }
713
+ let matched;
714
+ node.traverseStrictChildren?.((child) => {
715
+ if (matched)
716
+ return;
717
+ if (nasl_concepts_1.asserts.isMemberExpression(child) && isCurrentItemMember(child, slotVarName)) {
718
+ matched = child;
719
+ }
720
+ });
721
+ return matched;
722
+ }
723
+ // 自内向外查找最近的未开启 formMode 的列表上下文,并携带命中的 MemberExpression
724
+ function getListContextByNode(node) {
725
+ if (!node)
726
+ return;
727
+ for (let i = listComponentStack.length - 1; i >= 0; i--) {
728
+ const ctx = listComponentStack[i];
729
+ if (ctx.formModeEnabled)
730
+ continue;
731
+ const member = findCurrentItemMember(node, ctx.slotVarName);
732
+ if (member) {
733
+ return { ...ctx, member };
734
+ }
735
+ }
736
+ }
737
+ // 解析 currentX.item.* 访问的上下文与字段路径
738
+ function getCurrentItemAccessInfo(target) {
739
+ const ctxWithMember = getListContextByNode(target);
740
+ if (!ctxWithMember)
741
+ return;
742
+ const member = ctxWithMember.member;
743
+ if (!member)
744
+ return;
745
+ const path = getMemberPath(member);
746
+ const itemIndex = path.indexOf('item');
747
+ if (itemIndex < 0)
748
+ return;
749
+ const afterItemPath = path.slice(itemIndex + 1).join('.');
750
+ const currentName = ctxWithMember.slotVarName || path[0] || 'current';
751
+ return {
752
+ ctx: ctxWithMember,
753
+ afterItemPath,
754
+ currentName,
755
+ };
756
+ }
757
+ // 统一报错出口:仅在 item 之后存在字段时提示切换表单模式
758
+ function reportCurrentItemModification(target, errorNode) {
759
+ const info = getCurrentItemAccessInfo(target);
760
+ if (!info)
761
+ return;
762
+ const { ctx, afterItemPath, currentName } = info;
763
+ if (!afterItemPath)
764
+ return;
765
+ error(errorNode, `组件列表 ${ctx.node.name || ctx.node.tag} 内部存在对数据源数据 ${currentName}.item.${afterItemPath} 的修改,请切换成表单模式,并且需要将“数据源”属性切换为使用“表单数据”属性`);
766
+ }
767
+ function isFormModeEnabled(attr) {
768
+ if (!attr)
769
+ return false;
770
+ if (attr.type === 'static') {
771
+ return attr.value === true || attr.value === 'true';
772
+ }
773
+ if (nasl_concepts_1.asserts.isBooleanLiteral(attr.expression)) {
774
+ return attr.expression.value === 'true';
775
+ }
776
+ return false;
777
+ }
651
778
  /**
652
779
  * 是否在赋值左侧
653
780
  * @param node
@@ -1087,6 +1214,8 @@ function createErrorDiagnoser(context) {
1087
1214
  yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.processV2s, function* (node) {
1088
1215
  yield* checkNode(node);
1089
1216
  });
1217
+ // 检测所有流程的循环引用(在 App 级别,因为流程的变更会影响兄弟级别的流程)
1218
+ checkAllProcessCircularReferences(node);
1090
1219
  }
1091
1220
  if (node.roles?.length) {
1092
1221
  yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.roles, function* (node) {
@@ -1136,6 +1265,19 @@ function createErrorDiagnoser(context) {
1136
1265
  });
1137
1266
  }
1138
1267
  }
1268
+ // 检查 JDK 版本兼容性
1269
+ function jdkDisabled(item) {
1270
+ const isBackendComp = !!item?.externalDependencyMap?.maven;
1271
+ if (isBackendComp) {
1272
+ const jdk = app?.jdkVersion || 'JDK8';
1273
+ const dependencyJdk = item?.externalDependencyMap?.jdk?.version || 'JDK8';
1274
+ return jdk.toLowerCase() !== dependencyJdk.toLowerCase();
1275
+ }
1276
+ return false;
1277
+ }
1278
+ if (jdkDisabled(node)) {
1279
+ error(node, `与当前应用的JDK版本不匹配`);
1280
+ }
1139
1281
  inModule = false;
1140
1282
  }
1141
1283
  /**
@@ -1342,6 +1484,11 @@ function createErrorDiagnoser(context) {
1342
1484
  yield* checkNode(node);
1343
1485
  });
1344
1486
  }
1487
+ if (node.indexes?.length) {
1488
+ yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.indexes, function* (node) {
1489
+ yield* checkNode(node);
1490
+ });
1491
+ }
1345
1492
  // 在多模式切换为单模式的时候,数据库表名是否符合规范
1346
1493
  // /^[a-zA-Z0-9_]+$/ 由字母、数字、下划线组成
1347
1494
  const dataSource = node.dataSource;
@@ -1669,11 +1816,31 @@ function createErrorDiagnoser(context) {
1669
1816
  }
1670
1817
  }
1671
1818
  }
1819
+ /**
1820
+ * 检查 实体索引
1821
+ * @param node
1822
+ */
1823
+ function* checkEntityIndex(node) {
1824
+ if (node.propertyNames?.length) {
1825
+ const entity = node.parentNode;
1826
+ node.propertyNames.forEach((propertyName) => {
1827
+ const prop = findPropertyByName(entity, propertyName);
1828
+ if (!prop) {
1829
+ const scope = node?.constructor?.nodeTitle;
1830
+ error(node, `${scope}:索引${node.name}找不到 实体上的 ${propertyName}属性。`);
1831
+ return errorType;
1832
+ }
1833
+ });
1834
+ }
1835
+ }
1672
1836
  /**
1673
1837
  * 检查 枚举
1674
1838
  * @param node
1675
1839
  */
1676
1840
  function* checkEnum(node) {
1841
+ if (!node.valueType) {
1842
+ error(node, `枚举 ${node.name} 缺少值类型定义`);
1843
+ }
1677
1844
  if (node.enumItems?.length) {
1678
1845
  yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.enumItems, function* (node) {
1679
1846
  yield* checkNode(node);
@@ -1809,6 +1976,15 @@ function createErrorDiagnoser(context) {
1809
1976
  yield* checkNode(node);
1810
1977
  });
1811
1978
  }
1979
+ if (ensureNodeKeyExists(node, 'originLogicName')) {
1980
+ if (nasl_concepts_1.asserts.isApp(node.parentNode)) {
1981
+ const originLogicName = node.originLogicName;
1982
+ const ref = app.logics.find((item) => item.name === originLogicName);
1983
+ if (!ref) {
1984
+ error(node, `接口 ${node.title || node.name} 引用了不存在的逻辑 ${originLogicName}`);
1985
+ }
1986
+ }
1987
+ }
1812
1988
  yield* checkNode(node.validation);
1813
1989
  yield* checkTimeout(node);
1814
1990
  env.exitScope();
@@ -2075,57 +2251,84 @@ function createErrorDiagnoser(context) {
2075
2251
  }
2076
2252
  const shouldProvideCurrentCtx = !!(node.slotScope);
2077
2253
  let scopeEntered = false;
2254
+ const isListComponents = node.tag === 'el-list-components';
2255
+ const formModeAttr = isListComponents ? node.bindAttrs?.find(attr => attr.name === 'formMode') : undefined;
2256
+ const formModeEnabled = isFormModeEnabled(formModeAttr);
2257
+ let currentSlotVarName;
2078
2258
  if (shouldProvideCurrentCtx) {
2079
2259
  const name = curVarCounter > 0 ? node.slotScope + curVarCounter : node.slotScope;
2260
+ currentSlotVarName = name;
2080
2261
  const variable = new nasl_concepts_1.Variable({ name });
2081
2262
  curVarCounter++;
2082
2263
  // @ts-expect-error ViewElement 引入的 current 目前没有加入 refMgr,VE 也没当做 ScopeNode
2083
2264
  env.enterScope(node, [variable]);
2084
2265
  scopeEntered = true;
2085
2266
  }
2086
- if (node.bindAttrs?.length) {
2087
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindAttrs, function* (node) {
2088
- yield* checkNode(node);
2089
- });
2090
- }
2091
- if (node.bindDirectives?.length) {
2092
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindDirectives, function* (node) {
2093
- yield* checkNode(node);
2094
- });
2095
- }
2096
- if (node.bindStyles?.length) {
2097
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindStyles, function* (node) {
2098
- yield* checkNode(node);
2099
- });
2100
- }
2101
- if (node.bindEvents?.length) {
2102
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindEvents, function* (node) {
2103
- yield* checkNode(node);
2104
- });
2105
- }
2106
- if (node.children?.length) {
2107
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.children, function* (node) {
2108
- yield* checkNode(node);
2109
- });
2110
- }
2111
- // 已知:选择器会漏报,因为 children 里是两个空 template。暂时先这样吧。
2112
- let dsFilled = true;
2113
- let errNode = node;
2114
- const ds = node.bindAttrs.find(nd => nd.name === 'dataSource');
2115
- if (ds) {
2116
- if (ds.type === 'dynamic' && !ds.expression) {
2117
- dsFilled = false;
2118
- errNode = ds;
2267
+ let listCtxPushed = false;
2268
+ try {
2269
+ if (currentSlotVarName && listComponentStack.length) {
2270
+ const top = listComponentStack[listComponentStack.length - 1];
2271
+ if (!top.slotVarName && isDescendantOf(node, top.node)) {
2272
+ top.slotVarName = currentSlotVarName;
2273
+ }
2274
+ }
2275
+ if (isListComponents && !formModeEnabled) {
2276
+ listComponentStack.push({
2277
+ node,
2278
+ slotVarName: currentSlotVarName,
2279
+ formModeEnabled,
2280
+ });
2281
+ listCtxPushed = true;
2282
+ }
2283
+ if (node.bindAttrs?.length) {
2284
+ yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindAttrs, function* (node) {
2285
+ yield* checkNode(node);
2286
+ });
2287
+ }
2288
+ if (node.bindDirectives?.length) {
2289
+ yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindDirectives, function* (node) {
2290
+ yield* checkNode(node);
2291
+ });
2292
+ }
2293
+ if (node.bindStyles?.length) {
2294
+ yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindStyles, function* (node) {
2295
+ yield* checkNode(node);
2296
+ });
2297
+ }
2298
+ if (node.bindEvents?.length) {
2299
+ yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindEvents, function* (node) {
2300
+ yield* checkNode(node);
2301
+ });
2302
+ }
2303
+ if (node.children?.length) {
2304
+ yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.children, function* (node) {
2305
+ yield* checkNode(node);
2306
+ });
2307
+ }
2308
+ // 已知:选择器会漏报,因为 children 里是两个空 template。暂时先这样吧。
2309
+ let dsFilled = true;
2310
+ let errNode = node;
2311
+ const ds = node.bindAttrs.find(nd => nd.name === 'dataSource');
2312
+ if (ds) {
2313
+ if (ds.type === 'dynamic' && !ds.expression) {
2314
+ dsFilled = false;
2315
+ errNode = ds;
2316
+ }
2317
+ }
2318
+ const hasSubComponents = node.children.filter(c => c.tag !== 'template').length > 0 ||
2319
+ node.children.filter(c => c.tag === 'template').some(cc => cc.children.length > 0);
2320
+ if (!dsFilled && !hasSubComponents) {
2321
+ error(errNode, '数据源不能为空');
2119
2322
  }
2120
2323
  }
2121
- const hasSubComponents = node.children.filter(c => c.tag !== 'template').length > 0 ||
2122
- node.children.filter(c => c.tag === 'template').some(cc => cc.children.length > 0);
2123
- if (!dsFilled && !hasSubComponents) {
2124
- error(errNode, '数据源不能为空');
2125
- }
2126
- if (scopeEntered) {
2127
- env.exitScope();
2128
- curVarCounter--;
2324
+ finally {
2325
+ if (listCtxPushed) {
2326
+ listComponentStack.pop();
2327
+ }
2328
+ if (scopeEntered) {
2329
+ env.exitScope();
2330
+ curVarCounter--;
2331
+ }
2129
2332
  }
2130
2333
  }
2131
2334
  /**
@@ -2172,6 +2375,9 @@ function createErrorDiagnoser(context) {
2172
2375
  error(bindExpression, '页面组件属性表达式:该表达式不支持获取组件输入内容,请改为可赋值的变量或属性。');
2173
2376
  }
2174
2377
  }
2378
+ if (node.model || node.sync) {
2379
+ reportCurrentItemModification(node.expression, node);
2380
+ }
2175
2381
  // 检查组件数据源依赖监听变量重复
2176
2382
  if (node?.name === 'dataSourceWatch' && nasl_concepts_1.asserts.isNewList(node?.expression)) {
2177
2383
  const items = node?.expression?.items || [];
@@ -2233,8 +2439,10 @@ function createErrorDiagnoser(context) {
2233
2439
  ...(node.fromLogics || []),
2234
2440
  ...(node.roles || []),
2235
2441
  ].length) {
2442
+ const processElementV2 = node.getAncestor('ProcessElementV2');
2443
+ const title = processElementV2 && processElementV2.type === 'CallSubProcess' ? '子流程发起人' : '任务完成人';
2236
2444
  // 任务完成人为空会有一个提示信息
2237
- error(node, '任务完成人为空');
2445
+ error(node, `${title}为空`);
2238
2446
  }
2239
2447
  }
2240
2448
  /**
@@ -2396,6 +2604,148 @@ function createErrorDiagnoser(context) {
2396
2604
  }
2397
2605
  env.exitScope();
2398
2606
  }
2607
+ /**
2608
+ * 检测所有流程的循环引用(在 App 级别)
2609
+ * 使用 DFS 算法检测 ProcessV2 之间的循环引用关系
2610
+ * 因为流程的变更会影响兄弟级别的流程,所以需要在 App 级别进行检测
2611
+ */
2612
+ function checkAllProcessCircularReferences(appNode) {
2613
+ if (!appNode?.processV2s || appNode.processV2s.length === 0) {
2614
+ return;
2615
+ }
2616
+ const visiting = new Set();
2617
+ const processMap = new Map();
2618
+ const depthReported = new Set();
2619
+ // 构建流程映射表
2620
+ for (const p of appNode.processV2s) {
2621
+ if (p.uniqueKey) {
2622
+ processMap.set(p.uniqueKey, p);
2623
+ }
2624
+ }
2625
+ // 查找 CallSubProcess 元素
2626
+ const findCallSubProcessElement = (fromKey, toKey) => {
2627
+ const fromProcess = processMap.get(fromKey);
2628
+ if (!fromProcess)
2629
+ return undefined;
2630
+ for (const procDef of fromProcess.processDefinitions || []) {
2631
+ for (const ele of procDef.elements || []) {
2632
+ if (ele.type === 'CallSubProcess' && ele.subProcessKey === toKey) {
2633
+ return ele;
2634
+ }
2635
+ }
2636
+ }
2637
+ return undefined;
2638
+ };
2639
+ // 将路径转换为流程名称字符串
2640
+ const pathToNames = (path) => {
2641
+ return path.map(key => processMap.get(key)?.name || key).join(' -> ');
2642
+ };
2643
+ // 记录循环并返回完整路径
2644
+ const recordCycle = (path, currentKey, cycles) => {
2645
+ const fullPath = [...path, currentKey];
2646
+ const cycleStart = path.indexOf(currentKey);
2647
+ if (cycleStart >= 0) {
2648
+ cycles.push(fullPath.slice(cycleStart));
2649
+ }
2650
+ return fullPath;
2651
+ };
2652
+ // DFS 遍历检测循环和深度
2653
+ function dfs(currentProcessKey, path, cycles) {
2654
+ const fullPath = [...path, currentProcessKey];
2655
+ // 检测循环:当前节点已在访问路径中
2656
+ if (visiting.has(currentProcessKey)) {
2657
+ return { path: recordCycle(path, currentProcessKey, cycles) };
2658
+ }
2659
+ visiting.add(currentProcessKey);
2660
+ const currentProcess = processMap.get(currentProcessKey);
2661
+ let maxPath = fullPath; // 记录最长路径
2662
+ if (currentProcess) {
2663
+ for (const procDef of currentProcess.processDefinitions || []) {
2664
+ for (const element of procDef.elements || []) {
2665
+ if (element.type === 'CallSubProcess' && element.subProcessKey) {
2666
+ const subProcessKey = element.subProcessKey;
2667
+ // 检测循环:子流程已在访问路径中
2668
+ if (visiting.has(subProcessKey)) {
2669
+ const cycleStart = fullPath.indexOf(subProcessKey);
2670
+ if (cycleStart >= 0) {
2671
+ cycles.push(fullPath.slice(cycleStart).concat(subProcessKey));
2672
+ }
2673
+ visiting.delete(currentProcessKey);
2674
+ // 发现循环,立即返回
2675
+ return { path: [...fullPath, subProcessKey] };
2676
+ }
2677
+ // 递归检查子流程
2678
+ const result = dfs(subProcessKey, fullPath, cycles);
2679
+ if (result) {
2680
+ // 如果发现循环,立即返回
2681
+ if (cycles.length > 0) {
2682
+ visiting.delete(currentProcessKey);
2683
+ return result;
2684
+ }
2685
+ // 否则记录最长路径,继续检查其他子流程
2686
+ if (result.path.length > maxPath.length) {
2687
+ maxPath = result.path;
2688
+ }
2689
+ }
2690
+ }
2691
+ }
2692
+ }
2693
+ }
2694
+ visiting.delete(currentProcessKey);
2695
+ return { path: maxPath };
2696
+ }
2697
+ // 报告循环引用错误
2698
+ const reportCycleError = (process, path) => {
2699
+ const nextKey = path[1];
2700
+ if (!nextKey)
2701
+ return;
2702
+ const callSubProcess = findCallSubProcessElement(process.uniqueKey, nextKey);
2703
+ if (callSubProcess) {
2704
+ error(callSubProcess, `流程存在循环引用: ${pathToNames(path)}`, {
2705
+ fileNode: process,
2706
+ });
2707
+ }
2708
+ };
2709
+ // 报告深度超限错误
2710
+ const reportDepthError = (path) => {
2711
+ const MIN_LEN = 4;
2712
+ for (let i = 0; i <= path.length - MIN_LEN; i++) {
2713
+ const subChain = path.slice(i);
2714
+ if (subChain.length < MIN_LEN)
2715
+ break;
2716
+ const reportKey = subChain.join('>');
2717
+ if (depthReported.has(reportKey))
2718
+ continue;
2719
+ depthReported.add(reportKey);
2720
+ const topFrom = subChain[0];
2721
+ const topTo = subChain[1];
2722
+ const topElement = findCallSubProcessElement(topFrom, topTo);
2723
+ const topProcess = processMap.get(topFrom);
2724
+ if (topElement && topProcess) {
2725
+ error(topElement, `流程引用层级不可超过3层,当前为: ${pathToNames(subChain)}`, {
2726
+ fileNode: topProcess,
2727
+ });
2728
+ }
2729
+ }
2730
+ };
2731
+ // 对每个流程独立检测
2732
+ for (const process of appNode.processV2s) {
2733
+ if (!process.uniqueKey)
2734
+ continue;
2735
+ visiting.clear();
2736
+ depthReported.clear();
2737
+ const cycles = [];
2738
+ const result = dfs(process.uniqueKey, [], cycles);
2739
+ if (cycles.length > 0 && result) {
2740
+ // 有循环:使用完整路径报错
2741
+ reportCycleError(process, result.path);
2742
+ }
2743
+ else if (result && result.path.length > 3) {
2744
+ // 无循环但深度超限:报告所有超长链路
2745
+ reportDepthError(result.path);
2746
+ }
2747
+ }
2748
+ }
2399
2749
  /**
2400
2750
  * 检查 绑定实体或数据结构
2401
2751
  * @param node
@@ -2486,6 +2836,29 @@ function createErrorDiagnoser(context) {
2486
2836
  else if (!service_1.ProcessesV2.isFromStartNode(node)) {
2487
2837
  error(node, '节点未关联到流程图');
2488
2838
  }
2839
+ // 子流程
2840
+ if (node.type === 'CallSubProcess') {
2841
+ // 数据映射规则子逻辑
2842
+ if (node.dataMappingRule) {
2843
+ if (node.dataMappingRule.mainToSub) {
2844
+ yield* checkNode(node.dataMappingRule.mainToSub);
2845
+ }
2846
+ if (node.dataMappingRule.subToMain) {
2847
+ yield* checkNode(node.dataMappingRule.subToMain);
2848
+ }
2849
+ }
2850
+ if (!node.subProcessKey) {
2851
+ error(node, '关联子流程为空');
2852
+ }
2853
+ else {
2854
+ // 检查引用的子流程是否存在(类似调用逻辑的检查)
2855
+ // subProcessKey 就是 ProcessV2 的 uniqueKey,直接作为 qName 使用
2856
+ const subProcessDef = env.refMgr.gQNameDefs.get(node.subProcessKey);
2857
+ if (!subProcessDef || subProcessDef.concept !== 'ProcessV2') {
2858
+ error(node, `找不到子流程 ${node.subProcessKey}`);
2859
+ }
2860
+ }
2861
+ }
2489
2862
  env.exitScope();
2490
2863
  }
2491
2864
  /**
@@ -3470,11 +3843,23 @@ function createErrorDiagnoser(context) {
3470
3843
  }
3471
3844
  else {
3472
3845
  const { name } = node;
3846
+ // 检查是否在 NewComposite 的 properties(左边)中
3847
+ // 如果在 properties 中,可以跳过未定义检查(因为 properties 是属性名,不是变量)
3848
+ // 但如果是在 rights(右边)中,即使有类型也应该检查变量是否定义
3849
+ const newComposite = node.getAncestor('NewComposite');
3850
+ if (newComposite) {
3851
+ const { properties = [] } = newComposite;
3852
+ // 只对 properties 中的 node 允许跳过未定义检查
3853
+ // 无论是否有类型都应该返回,因为 properties 是属性名,不是变量,不应该报"未定义"错误
3854
+ if (properties.some((item) => item === node)) {
3855
+ const type = env.getType(node);
3856
+ return type || errorType;
3857
+ }
3858
+ }
3473
3859
  if (
3474
3860
  // name 是 A.B.C
3475
3861
  name?.split('.')?.length > 1
3476
- || node.getAncestor('CallQueryComponent')
3477
- || node.getAncestor('NewComposite')) {
3862
+ || node.getAncestor('CallQueryComponent')) {
3478
3863
  const type = env.getType(node);
3479
3864
  if (type) {
3480
3865
  return type;
@@ -3627,15 +4012,6 @@ function createErrorDiagnoser(context) {
3627
4012
  }
3628
4013
  }
3629
4014
  const ty = env.getType(node);
3630
- function isConcreteTy(ty) {
3631
- if (ty.typeKind === 'generic') {
3632
- return !!ty.typeArguments?.every(x => isConcreteTy(x));
3633
- }
3634
- else if (ty.typeKind === 'primitive') {
3635
- return ty !== type_manager_1.naslAnyTy;
3636
- }
3637
- return true;
3638
- }
3639
4015
  if (!ty || !isConcreteTy(ty)) {
3640
4016
  const p = node.parentNode;
3641
4017
  if (p && p.name === 'dataSourceWatch' && (0, asserts_1.isBindAttribute)(p)) {
@@ -3677,7 +4053,13 @@ function createErrorDiagnoser(context) {
3677
4053
  });
3678
4054
  }
3679
4055
  }
3680
- return env.getType(node);
4056
+ const type = env.getType(node);
4057
+ if (!node.keys?.length && !node.values?.length) {
4058
+ if (!type || !isConcreteTy(type)) {
4059
+ error(node, `NewMap 无法推导类型,请添加元素`);
4060
+ }
4061
+ }
4062
+ return type;
3681
4063
  }
3682
4064
  /**
3683
4065
  * 检查 实体/(匿名)数据结构构造器
@@ -3685,6 +4067,7 @@ function createErrorDiagnoser(context) {
3685
4067
  */
3686
4068
  function* checkNewComposite(node) {
3687
4069
  yield* semantic_rules_1.ExtraSemanticRules.rejectFileTypes(node, env, error);
4070
+ ensureNodeKeyExists(node, 'typeAnnotation');
3688
4071
  const type = env.getType(node);
3689
4072
  if (node.properties?.length) {
3690
4073
  yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.properties, function* (property, index) {
@@ -3781,20 +4164,46 @@ function createErrorDiagnoser(context) {
3781
4164
  * @param leftType 左侧类型
3782
4165
  * @param rightType 右侧类型
3783
4166
  * @param context 上下文信息,用于生成错误消息
4167
+ * @param allowImplicitConversion 是否允许隐式类型转换(仅对直接的基础类型赋值允许,复杂类型内部不允许)
3784
4168
  * @returns 所有类型不兼容的错误信息数组
3785
4169
  */
3786
- function checkAssignmentTypeIncompatibility(leftType, rightType, context = {}) {
4170
+ function checkAssignmentTypeIncompatibility(leftType, rightType, context = {}, allowImplicitConversion = true) {
3787
4171
  if (!leftType || !rightType)
3788
4172
  return [];
3789
4173
  const errors = [];
3790
- // 1. 检查普通类型:Decimal/Long vs Union<Decimal, Long>
3791
- const isLeftDecimalOrLong = (leftType.typeKind === 'primitive' && leftType.typeName === 'Decimal') ||
3792
- (leftType.typeKind === 'primitive' && leftType.typeName === 'Long');
3793
- if (isLeftDecimalOrLong && (0, type_predicate_1.isDecimalUnionLongTy)(rightType)) {
3794
- const prefix = context.prefix || '赋值';
3795
- const fieldInfo = context.fieldName ? `字段 ${context.fieldName} ` : '';
3796
- errors.push(`${prefix}:${fieldInfo}类型不匹配,不能将 ${(0, type_manager_1.showUserLevelType)(rightType)} 赋值给 ${(0, type_manager_1.showUserLevelType)(leftType)} 类型。`);
3797
- return errors; // 找到基本类型错误后直接返回,不再检查嵌套
4174
+ // 先检查 Decimal/Long vs Union<Decimal, Long> 的情况(需要先于其他检查)
4175
+ // 这种情况应该报错,即使允许隐式转换
4176
+ if (allowImplicitConversion) {
4177
+ const isLeftPrimitive = leftType.typeKind === 'primitive';
4178
+ const isLeftDecimalOrLong = isLeftPrimitive && (leftType.typeName === 'Decimal' || leftType.typeName === 'Long');
4179
+ if (isLeftDecimalOrLong && (0, type_predicate_1.isDecimalUnionLongTy)(rightType)) {
4180
+ const prefix = context.prefix || '赋值';
4181
+ const fieldInfo = context.fieldName ? `字段 ${context.fieldName} ` : '';
4182
+ errors.push(`${prefix}:${fieldInfo}类型不匹配,不能将 ${(0, type_manager_1.showUserLevelType)(rightType)} 赋值给 ${(0, type_manager_1.showUserLevelType)(leftType)} 类型。`);
4183
+ return errors; // 找到基本类型错误后直接返回,不再检查嵌套
4184
+ }
4185
+ }
4186
+ // 检查基础类型是否严格相等(不允许隐式转换)
4187
+ const isLeftPrimitive = leftType.typeKind === 'primitive';
4188
+ const isRightPrimitive = rightType.typeKind === 'primitive';
4189
+ if (isLeftPrimitive && isRightPrimitive) {
4190
+ // 如果不允许隐式转换,则要求类型完全相等
4191
+ if (!allowImplicitConversion) {
4192
+ if (leftType.typeName !== rightType.typeName || leftType.typeNamespace !== rightType.typeNamespace) {
4193
+ const prefix = context.prefix || '赋值';
4194
+ const fieldInfo = context.fieldName ? `字段 ${context.fieldName} ` : '';
4195
+ errors.push(`${prefix}:${fieldInfo}类型不匹配,不能将 ${(0, type_manager_1.showUserLevelType)(rightType)} 赋值给 ${(0, type_manager_1.showUserLevelType)(leftType)} 类型。`);
4196
+ return errors;
4197
+ }
4198
+ // 类型完全相等,没有错误,继续检查其他情况
4199
+ }
4200
+ else {
4201
+ // 允许隐式转换的情况下,检查是否通过 isSubTy 检查(允许隐式转换)
4202
+ if ((0, type_predicate_1.isSubTy)(env, rightType, leftType)) {
4203
+ // 类型兼容(允许隐式转换),没有错误,继续检查其他情况
4204
+ }
4205
+ // 如果 isSubTy 返回 false,继续检查其他不兼容的情况
4206
+ }
3798
4207
  }
3799
4208
  // 2. 检查 List 类型(包括嵌套):List<T> vs List<Union<...>>
3800
4209
  if (leftType.typeKind === 'generic' &&
@@ -3803,8 +4212,9 @@ function createErrorDiagnoser(context) {
3803
4212
  rightType.typeName === 'List' &&
3804
4213
  leftType.typeArguments?.[0] &&
3805
4214
  rightType.typeArguments?.[0]) {
3806
- // 递归检查 List 的元素类型
3807
- return checkAssignmentTypeIncompatibility(leftType.typeArguments[0], rightType.typeArguments[0], { ...context, prefix: context.prefix || '赋值' });
4215
+ // 递归检查 List 的元素类型,嵌套类型内部不允许隐式转换
4216
+ return checkAssignmentTypeIncompatibility(leftType.typeArguments[0], rightType.typeArguments[0], { ...context, prefix: context.prefix || '赋值' }, false // 嵌套类型内部不允许隐式转换
4217
+ );
3808
4218
  }
3809
4219
  // 3. 检查 Map 类型(包括嵌套):Map<K, V> vs Map<K, Union<...>>
3810
4220
  if (leftType.typeKind === 'generic' &&
@@ -3813,11 +4223,13 @@ function createErrorDiagnoser(context) {
3813
4223
  rightType.typeName === 'Map' &&
3814
4224
  leftType.typeArguments?.length === 2 &&
3815
4225
  rightType.typeArguments?.length === 2) {
3816
- // 递归检查 Map 的值类型(第二个类型参数)
3817
- const valueTypeErrors = checkAssignmentTypeIncompatibility(leftType.typeArguments[1], rightType.typeArguments[1], { ...context, prefix: context.prefix || '赋值' });
4226
+ // 递归检查 Map 的值类型(第二个类型参数),嵌套类型内部不允许隐式转换
4227
+ const valueTypeErrors = checkAssignmentTypeIncompatibility(leftType.typeArguments[1], rightType.typeArguments[1], { ...context, prefix: context.prefix || '赋值' }, false // 嵌套类型内部不允许隐式转换
4228
+ );
3818
4229
  errors.push(...valueTypeErrors);
3819
- // 也检查键类型(第一个类型参数)
3820
- const keyTypeErrors = checkAssignmentTypeIncompatibility(leftType.typeArguments[0], rightType.typeArguments[0], { ...context, prefix: context.prefix || '赋值' });
4230
+ // 也检查键类型(第一个类型参数),嵌套类型内部不允许隐式转换
4231
+ const keyTypeErrors = checkAssignmentTypeIncompatibility(leftType.typeArguments[0], rightType.typeArguments[0], { ...context, prefix: context.prefix || '赋值' }, false // 嵌套类型内部不允许隐式转换
4232
+ );
3821
4233
  errors.push(...keyTypeErrors);
3822
4234
  return errors;
3823
4235
  }
@@ -3828,7 +4240,9 @@ function createErrorDiagnoser(context) {
3828
4240
  for (const leftProp of leftProps) {
3829
4241
  const rightProp = rightProps.find(p => p.name === leftProp.name);
3830
4242
  if (rightProp && leftProp.typeAnnotation && rightProp.typeAnnotation) {
3831
- const fieldErrors = checkAssignmentTypeIncompatibility(leftProp.typeAnnotation, rightProp.typeAnnotation, { prefix: context.prefix || '赋值', fieldName: leftProp.name });
4243
+ // 匿名结构体的字段类型检查不允许隐式转换
4244
+ const fieldErrors = checkAssignmentTypeIncompatibility(leftProp.typeAnnotation, rightProp.typeAnnotation, { prefix: context.prefix || '赋值', fieldName: leftProp.name }, false // 复杂类型内部不允许隐式转换
4245
+ );
3832
4246
  errors.push(...fieldErrors); // 收集所有字段的错误,不提前返回
3833
4247
  }
3834
4248
  }
@@ -3854,6 +4268,9 @@ function createErrorDiagnoser(context) {
3854
4268
  errorMessages.forEach(errorMessage => {
3855
4269
  error(node, errorMessage);
3856
4270
  });
4271
+ if (nasl_concepts_1.asserts.isMemberExpression(node.left)) {
4272
+ reportCurrentItemModification(node.left, node);
4273
+ }
3857
4274
  }
3858
4275
  /**
3859
4276
  * 检查 批量赋值语句
@@ -3962,6 +4379,34 @@ function createErrorDiagnoser(context) {
3962
4379
  severity: Severity.WARN,
3963
4380
  });
3964
4381
  }
4382
+ /**
4383
+ * 检查数据查询是否在有效的上下文中使用
4384
+ * 数据查询不能直接放在语句中,只能作为表达式使用
4385
+ */
4386
+ function* checkQueryContextValidity(node) {
4387
+ const parent = node.parentNode;
4388
+ const parentConcept = parent.concept;
4389
+ // 允许的父节点类型直接返回
4390
+ const invalidParentConcepts = ['Logic', 'IfStatement', 'ForEachStatement', 'SwitchCase', 'MatchCase', 'WhileStatement'];
4391
+ if (!invalidParentConcepts.includes(parentConcept)) {
4392
+ return;
4393
+ }
4394
+ // MatchCase 在表达式场景下是例外:Match 表达式中的 MatchCase 允许数据查询
4395
+ if (parentConcept === 'MatchCase') {
4396
+ const matchNode = parent.parentNode;
4397
+ if (matchNode?.concept === 'Match' && matchNode.isExpression) {
4398
+ return;
4399
+ }
4400
+ }
4401
+ // 获取当前节点在父节点中的属性名
4402
+ const parentKey = node.parentKey;
4403
+ // 只在 parentKey 为 body、consequent、alternate 时才报错
4404
+ const errorParentKeys = ['body', 'consequent', 'alternate'];
4405
+ if (!errorParentKeys.includes(parentKey)) {
4406
+ return;
4407
+ }
4408
+ error(node, '数据查询不能直接放在语句中,只能作为表达式使用。');
4409
+ }
3965
4410
  /**
3966
4411
  * 检查 数据查询
3967
4412
  * @param node
@@ -3973,6 +4418,8 @@ function createErrorDiagnoser(context) {
3973
4418
  else {
3974
4419
  error(node, '预期 1 个参数,但传入了 0 个。');
3975
4420
  }
4421
+ // 检查数据查询的使用上下文
4422
+ yield* checkQueryContextValidity(node);
3976
4423
  // where 子句
3977
4424
  yield* checkNode(node.where);
3978
4425
  // wherePlayground
@@ -4044,7 +4491,13 @@ function createErrorDiagnoser(context) {
4044
4491
  return;
4045
4492
  }
4046
4493
  const { entityAsName } = node;
4047
- const entityNamespace = node.entityNamespace ?? node.getAncestor('CallQueryComponent')?.from?.entityNamespace;
4494
+ const callQueryComponent = node.getAncestor('CallQueryComponent');
4495
+ if (!callQueryComponent.inFromJoinPart(entityAsName)) {
4496
+ // 实体不存在from中
4497
+ error(node, '数据查询:所查询的实体不存在数据源中。');
4498
+ return;
4499
+ }
4500
+ const entityNamespace = node.entityNamespace ?? callQueryComponent?.from?.entityNamespace;
4048
4501
  const entityQName = `${entityNamespace}.${entityAsName}`;
4049
4502
  const ref = referenceManager.gQNameDefs.get(entityQName);
4050
4503
  if (!ref) {
@@ -4226,6 +4679,9 @@ function createErrorDiagnoser(context) {
4226
4679
  if (isCandidate) {
4227
4680
  yield* rejectNullLiteral(node.argument);
4228
4681
  }
4682
+ if (node.operator === 'isNull' && node.getAncestor('CallQueryComponent') && !nasl_concepts_1.asserts.isQueryFieldExpression(node.argument)) {
4683
+ error(node, '数据查询:数据查询中的 isNull 只面向实体属性使用。针对变量和表达式为空值的情况,已自动进行剪枝处理,请移除条件中的 isNull 即可。');
4684
+ }
4229
4685
  yield* checkNode(node.argument);
4230
4686
  }
4231
4687
  return env.getType(node);
@@ -4248,6 +4704,26 @@ function createErrorDiagnoser(context) {
4248
4704
  if (typeAnnotation?.typeKind === 'generic') {
4249
4705
  yield* checkGenericTypeHasExpectedTyArgs(typeAnnotation);
4250
4706
  }
4707
+ // 在特定流程逻辑中,data 形参必须传入且内部 new 对象的 data 字段需要显式初始化且不可为 null
4708
+ if (node.keyword === 'data' && node.getAncestor('CallLogic')?.isSpecificProcessLogic && ensureNodeKeyExists(node, 'expression')) {
4709
+ if (nasl_concepts_1.asserts.isNewComposite(node.expression)) {
4710
+ const dataIndex = (node.expression.properties || [])?.findIndex((propery) => {
4711
+ return nasl_concepts_1.asserts.isIdentifier(propery) && propery.name === 'data';
4712
+ });
4713
+ if (dataIndex === -1) {
4714
+ error(node, 'data未初始化!');
4715
+ }
4716
+ else {
4717
+ const rightSelectMember = (0, nasl_concepts_1.getRightFromNewCompositeByLeftIndex)({
4718
+ newComposite: node.expression,
4719
+ propertyIndex: dataIndex,
4720
+ });
4721
+ if (nasl_concepts_1.asserts.isNullLiteral(rightSelectMember?.expression)) {
4722
+ error(rightSelectMember?.expression, 'data不能为null');
4723
+ }
4724
+ }
4725
+ }
4726
+ }
4251
4727
  return typeAnnotation;
4252
4728
  }
4253
4729
  /**
@@ -4410,6 +4886,9 @@ function createErrorDiagnoser(context) {
4410
4886
  * @param node
4411
4887
  */
4412
4888
  function* rejectNullLiteral(node) {
4889
+ if (!node) {
4890
+ return; // 如果 node 为 null 或 undefined,直接返回
4891
+ }
4413
4892
  if (node.concept === 'NullLiteral') {
4414
4893
  error(node, `此处不允许使用 null`);
4415
4894
  }
@@ -4466,6 +4945,8 @@ function createErrorDiagnoser(context) {
4466
4945
  return yield* checkEntity(node);
4467
4946
  case 'EntityProperty':
4468
4947
  return yield* checkEntityProperty(node);
4948
+ case 'EntityIndex':
4949
+ return yield* checkEntityIndex(node);
4469
4950
  case 'Enum':
4470
4951
  return yield* checkEnum(node);
4471
4952
  case 'EnumItem':
@@ -4806,29 +5287,59 @@ function createErrorDiagnoser(context) {
4806
5287
  * 用于增量更新时,当子节点变更需要重新验证祖先节点的场景
4807
5288
  * @param ancestorNode 需要重新验证的祖先节点
4808
5289
  * @param validationFn 验证函数
4809
- * @param errorFilter 错误过滤函数(可选),用于保留特定的错误
5290
+ * @param errorFilter 错误过滤函数(可选),用于保留特定的错误(返回 true 表示保留)
5291
+ * @param siblingNodeTypes 需要过滤错误的兄弟节点类型(可选)
5292
+ * @param getSiblingNodes 获取兄弟节点的函数(可选),用于从祖先节点中获取需要过滤错误的兄弟节点
4810
5293
  * @returns 新的诊断信息 Map,如果没有则返回 null
4811
5294
  */
4812
- function revalidateAncestorNode(ancestorNode, validationFn, errorFilter) {
4813
- // 保存并过滤相关的错误诊断
4814
- const errs = getError(ancestorNode) || [];
4815
- const filteredErrs = errorFilter
4816
- ? errs.filter(errorFilter)
4817
- : [];
4818
- // 清除祖先节点的诊断信息
4819
- clearDiagnostics(ancestorNode);
4820
- // 恢复过滤后的错误诊断
4821
- if (filteredErrs.length > 0) {
4822
- filteredErrs.forEach((err) => {
4823
- const { message, severity, ...context } = err;
4824
- if (message) {
4825
- error(ancestorNode, message, {
4826
- severity: severity,
4827
- ...context,
5295
+ function revalidateAncestorNode(ancestorNode, validationFn, errorFilter, siblingNodeTypes, getSiblingNodes) {
5296
+ // 收集所有需要处理的文件节点(祖先节点 + 兄弟节点)
5297
+ const fileNodesToProcess = [ancestorNode];
5298
+ // 获取需要过滤错误的兄弟节点(通过配置的 getSiblingNodes 函数)
5299
+ if (errorFilter && getSiblingNodes) {
5300
+ const siblings = getSiblingNodes(ancestorNode);
5301
+ if (siblingNodeTypes && siblingNodeTypes.length > 0) {
5302
+ fileNodesToProcess.push(...siblings.filter(s => siblingNodeTypes.includes(s.concept)));
5303
+ }
5304
+ else {
5305
+ fileNodesToProcess.push(...siblings);
5306
+ }
5307
+ }
5308
+ // 收集所有文件节点需要保留的错误
5309
+ const errorsToRestore = [];
5310
+ for (const fileNode of fileNodesToProcess) {
5311
+ const diagnostics = getDiagnostics(fileNode);
5312
+ if (diagnostics) {
5313
+ diagnostics.forEach((errs, node) => {
5314
+ // 如果有 errorFilter,只保留满足条件的错误;否则保留所有错误
5315
+ const filtered = errorFilter ? errs.filter(errorFilter) : errs;
5316
+ filtered.forEach(err => {
5317
+ errorsToRestore.push({ fileNode, node, err });
4828
5318
  });
4829
- }
4830
- });
5319
+ });
5320
+ }
5321
+ }
5322
+ // 清除所有文件节点的诊断信息
5323
+ for (const fileNode of fileNodesToProcess) {
5324
+ clearDiagnostics(fileNode);
5325
+ let diagnostics = diagnosticMap.get(fileNode);
5326
+ if (!diagnostics && fileNode) {
5327
+ diagnostics = new Map();
5328
+ diagnosticMap.set(fileNode, diagnostics);
5329
+ }
5330
+ diagnostics.set(fileNode, []);
4831
5331
  }
5332
+ // 恢复所有需要保留的错误
5333
+ errorsToRestore.forEach(({ fileNode, node, err }) => {
5334
+ const { message, severity, ...context } = err;
5335
+ if (message) {
5336
+ error(node, message, {
5337
+ severity: severity,
5338
+ fileNode,
5339
+ ...context,
5340
+ });
5341
+ }
5342
+ });
4832
5343
  // 设置文件节点上下文并执行验证
4833
5344
  const cleanup = handleFileNode(ancestorNode);
4834
5345
  try {
@@ -4837,8 +5348,17 @@ function createErrorDiagnoser(context) {
4837
5348
  finally {
4838
5349
  cleanup?.();
4839
5350
  }
4840
- // 返回新的诊断信息
4841
- return getDiagnostics(ancestorNode) || null;
5351
+ // 收集并返回所有文件节点的诊断信息
5352
+ const allDiagnostics = new Map();
5353
+ for (const fileNode of fileNodesToProcess) {
5354
+ const diagnostics = getDiagnostics(fileNode);
5355
+ if (diagnostics) {
5356
+ diagnostics.forEach((errs, node) => {
5357
+ allDiagnostics.set(node, errs);
5358
+ });
5359
+ }
5360
+ }
5361
+ return allDiagnostics.size > 0 ? allDiagnostics : null;
4842
5362
  }
4843
5363
  return {
4844
5364
  setMetadataTypeEnable,
@@ -4848,6 +5368,7 @@ function createErrorDiagnoser(context) {
4848
5368
  clearDiagnostics,
4849
5369
  getDiagnostics,
4850
5370
  getAllDiagnostics,
5371
+ checkAllProcessCircularReferences,
4851
5372
  getDebugDiagnostics,
4852
5373
  error,
4853
5374
  checkViewIndexPageSetting,