@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.
Files changed (142) hide show
  1. package/out/checker.d.ts +4 -1
  2. package/out/checker.d.ts.map +1 -1
  3. package/out/checker.js +996 -96
  4. package/out/checker.js.map +1 -1
  5. package/out/index.d.ts +1 -1
  6. package/out/index.d.ts.map +1 -1
  7. package/out/index.js +5 -3
  8. package/out/index.js.map +1 -1
  9. package/out/reference-manager/build-q-name-def.d.ts.map +1 -1
  10. package/out/reference-manager/build-q-name-def.js +11 -0
  11. package/out/reference-manager/build-q-name-def.js.map +1 -1
  12. package/out/reference-manager/builtin-q-name.js +2 -2
  13. package/out/reference-manager/builtin-q-name.js.map +1 -1
  14. package/out/reference-manager/collect-q-name.d.ts +1 -1
  15. package/out/reference-manager/collect-q-name.d.ts.map +1 -1
  16. package/out/reference-manager/collect-q-name.js +64 -27
  17. package/out/reference-manager/collect-q-name.js.map +1 -1
  18. package/out/reference-manager/def-key-helpers.d.ts +38 -0
  19. package/out/reference-manager/def-key-helpers.d.ts.map +1 -0
  20. package/out/reference-manager/{get-q-name.js → def-key-helpers.js} +195 -23
  21. package/out/reference-manager/def-key-helpers.js.map +1 -0
  22. package/out/reference-manager/helper.js +2 -2
  23. package/out/reference-manager/helper.js.map +1 -1
  24. package/out/reference-manager/reference-manager.d.ts +146 -47
  25. package/out/reference-manager/reference-manager.d.ts.map +1 -1
  26. package/out/reference-manager/reference-manager.js +632 -204
  27. package/out/reference-manager/reference-manager.js.map +1 -1
  28. package/out/reference-manager/remove-q-name.d.ts +1 -1
  29. package/out/reference-manager/remove-q-name.d.ts.map +1 -1
  30. package/out/reference-manager/remove-q-name.js +14 -14
  31. package/out/reference-manager/remove-q-name.js.map +1 -1
  32. package/out/reference-manager/rename-q-name.d.ts +26 -26
  33. package/out/reference-manager/rename-q-name.d.ts.map +1 -1
  34. package/out/reference-manager/rename-q-name.js +48 -167
  35. package/out/reference-manager/rename-q-name.js.map +1 -1
  36. package/out/reference-manager/symbol-type.d.ts +8 -3
  37. package/out/reference-manager/symbol-type.d.ts.map +1 -1
  38. package/out/reference-manager/symbol-type.js +9 -2
  39. package/out/reference-manager/symbol-type.js.map +1 -1
  40. package/out/reference-manager/update-nasl-fragment.d.ts +2 -2
  41. package/out/reference-manager/update-nasl-fragment.d.ts.map +1 -1
  42. package/out/reference-manager/update-nasl-fragment.js +33 -28
  43. package/out/reference-manager/update-nasl-fragment.js.map +1 -1
  44. package/out/services/bindable-logic-service.js.map +1 -1
  45. package/out/symbol/traverse/concepts/index.d.ts +1 -1
  46. package/out/symbol/traverse/concepts/index.d.ts.map +1 -1
  47. package/out/symbol/traverse/concepts/logic/expression/member-expression.js.map +1 -1
  48. package/out/typer/collectGlobalDefs.d.ts +8 -0
  49. package/out/typer/collectGlobalDefs.d.ts.map +1 -1
  50. package/out/typer/collectGlobalDefs.js +102 -54
  51. package/out/typer/collectGlobalDefs.js.map +1 -1
  52. package/out/typer/component-def-manager/component-def-manager.d.ts +16 -0
  53. package/out/typer/component-def-manager/component-def-manager.d.ts.map +1 -1
  54. package/out/typer/component-def-manager/component-def-manager.js +63 -2
  55. package/out/typer/component-def-manager/component-def-manager.js.map +1 -1
  56. package/out/typer/component-def-manager/utils.d.ts.map +1 -1
  57. package/out/typer/component-def-manager/utils.js +26 -0
  58. package/out/typer/component-def-manager/utils.js.map +1 -1
  59. package/out/typer/dispatch-all.d.ts +3 -1
  60. package/out/typer/dispatch-all.d.ts.map +1 -1
  61. package/out/typer/dispatch-all.js +51 -6
  62. package/out/typer/dispatch-all.js.map +1 -1
  63. package/out/typer/dispatch-call.d.ts +27 -1
  64. package/out/typer/dispatch-call.d.ts.map +1 -1
  65. package/out/typer/dispatch-call.js +151 -107
  66. package/out/typer/dispatch-call.js.map +1 -1
  67. package/out/typer/dispatch-def.d.ts.map +1 -1
  68. package/out/typer/dispatch-def.js +51 -23
  69. package/out/typer/dispatch-def.js.map +1 -1
  70. package/out/typer/dispatch-expr.d.ts +2 -1
  71. package/out/typer/dispatch-expr.d.ts.map +1 -1
  72. package/out/typer/dispatch-expr.js +240 -199
  73. package/out/typer/dispatch-expr.js.map +1 -1
  74. package/out/typer/dispatch-process.d.ts +8 -1
  75. package/out/typer/dispatch-process.d.ts.map +1 -1
  76. package/out/typer/dispatch-process.js +37 -4
  77. package/out/typer/dispatch-process.js.map +1 -1
  78. package/out/typer/dispatch-stmt.d.ts.map +1 -1
  79. package/out/typer/dispatch-stmt.js +39 -25
  80. package/out/typer/dispatch-stmt.js.map +1 -1
  81. package/out/typer/dispatch-view.d.ts +1 -1
  82. package/out/typer/dispatch-view.d.ts.map +1 -1
  83. package/out/typer/dispatch-view.js +87 -48
  84. package/out/typer/dispatch-view.js.map +1 -1
  85. package/out/typer/fix-use-before-assign.js.map +1 -1
  86. package/out/typer/get-oql-files.d.ts.map +1 -1
  87. package/out/typer/get-oql-files.js +3 -9
  88. package/out/typer/get-oql-files.js.map +1 -1
  89. package/out/typer/incremental-update.d.ts +14 -7
  90. package/out/typer/incremental-update.d.ts.map +1 -1
  91. package/out/typer/incremental-update.js +396 -43
  92. package/out/typer/incremental-update.js.map +1 -1
  93. package/out/typer/oql-checker/chain-call-transformer.d.ts.map +1 -1
  94. package/out/typer/oql-checker/chain-call-transformer.js +17 -4
  95. package/out/typer/oql-checker/chain-call-transformer.js.map +1 -1
  96. package/out/typer/oql-checker/ts-parser.d.ts.map +1 -1
  97. package/out/typer/oql-checker/ts-parser.js +252 -37
  98. package/out/typer/oql-checker/ts-parser.js.map +1 -1
  99. package/out/typer/overload-helper.d.ts.map +1 -1
  100. package/out/typer/overload-helper.js +190 -23
  101. package/out/typer/overload-helper.js.map +1 -1
  102. package/out/typer/solver.d.ts +8 -9
  103. package/out/typer/solver.d.ts.map +1 -1
  104. package/out/typer/solver.js +54 -31
  105. package/out/typer/solver.js.map +1 -1
  106. package/out/typer/subster.d.ts +1 -1
  107. package/out/typer/subster.d.ts.map +1 -1
  108. package/out/typer/subster.js +251 -57
  109. package/out/typer/subster.js.map +1 -1
  110. package/out/typer/topo-sort.d.ts +38 -0
  111. package/out/typer/topo-sort.d.ts.map +1 -1
  112. package/out/typer/topo-sort.js +272 -3
  113. package/out/typer/topo-sort.js.map +1 -1
  114. package/out/typer/type-manager.d.ts +1 -0
  115. package/out/typer/type-manager.d.ts.map +1 -1
  116. package/out/typer/type-manager.js +14 -11
  117. package/out/typer/type-manager.js.map +1 -1
  118. package/out/typer/type-predicate.d.ts +1 -0
  119. package/out/typer/type-predicate.d.ts.map +1 -1
  120. package/out/typer/type-predicate.js +83 -14
  121. package/out/typer/type-predicate.js.map +1 -1
  122. package/out/typer/typer.d.ts +51 -6
  123. package/out/typer/typer.d.ts.map +1 -1
  124. package/out/typer/typer.js +248 -46
  125. package/out/typer/typer.js.map +1 -1
  126. package/out/typer/unifier.d.ts +12 -1
  127. package/out/typer/unifier.d.ts.map +1 -1
  128. package/out/typer/unifier.js +110 -37
  129. package/out/typer/unifier.js.map +1 -1
  130. package/out/utils/debug.js.map +1 -1
  131. package/out/utils/type-operator.d.ts +15 -0
  132. package/out/utils/type-operator.d.ts.map +1 -1
  133. package/out/utils/type-operator.js +65 -3
  134. package/out/utils/type-operator.js.map +1 -1
  135. package/package.json +6 -6
  136. package/out/reference-manager/get-q-name.d.ts +0 -9
  137. package/out/reference-manager/get-q-name.d.ts.map +0 -1
  138. package/out/reference-manager/get-q-name.js.map +0 -1
  139. package/out/reference-manager/view-elem-logic.d.ts +0 -44
  140. package/out/reference-manager/view-elem-logic.d.ts.map +0 -1
  141. package/out/reference-manager/view-elem-logic.js +0 -164
  142. 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
- finalNode.tsErrorDetail = diagnostic;
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
- if (!isNonRequiredParam(param)) {
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
- error(node, `预期 ${minArgsCount} 个参数,但传入了 ${argsLen} 个。`);
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
- if (node.bindAttrs?.length) {
2031
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindAttrs, function* (node) {
2032
- yield* checkNode(node);
2033
- });
2034
- }
2035
- if (node.bindDirectives?.length) {
2036
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindDirectives, function* (node) {
2037
- yield* checkNode(node);
2038
- });
2039
- }
2040
- if (node.bindStyles?.length) {
2041
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindStyles, function* (node) {
2042
- yield* checkNode(node);
2043
- });
2044
- }
2045
- if (node.bindEvents?.length) {
2046
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.bindEvents, function* (node) {
2047
- yield* checkNode(node);
2048
- });
2049
- }
2050
- if (node.children?.length) {
2051
- yield* (0, nasl_utils_1.wrapForEachToGenerator)(node.children, function* (node) {
2052
- yield* checkNode(node);
2053
- });
2054
- }
2055
- // 已知:选择器会漏报,因为 children 里是两个空 template。暂时先这样吧。
2056
- let dsFilled = true;
2057
- let errNode = node;
2058
- const ds = node.bindAttrs.find(nd => nd.name === 'dataSource');
2059
- if (ds) {
2060
- if (ds.type === 'dynamic' && !ds.expression) {
2061
- dsFilled = false;
2062
- errNode = ds;
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
- const hasSubComponents = node.children.filter(c => c.tag !== 'template').length > 0 ||
2066
- node.children.filter(c => c.tag === 'template').some(cc => cc.children.length > 0);
2067
- if (!dsFilled && !hasSubComponents) {
2068
- error(errNode, '数据源不能为空');
2069
- }
2070
- if (scopeEntered) {
2071
- env.exitScope();
2072
- curVarCounter--;
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 macthed = exportInterface.concept === 'McpInterface';
2592
- const key = macthed ? 'exportMcpInterface' : 'exportInterface';
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
- return env.getType(node);
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
- // 检查左侧为 Decimal,右侧为 Union<Decimal, Integer> 的情况
3642
- const leftType = env.getType(node.left);
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 entityNamespace = node.entityNamespace ?? node.getAncestor('CallQueryComponent')?.from?.entityNamespace;
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