@lcap/nasl-language-server-core 4.4.0-beta.32 → 4.4.0-beta.34

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 (41) hide show
  1. package/out/checker.d.ts.map +1 -1
  2. package/out/checker.js +27 -13
  3. package/out/checker.js.map +1 -1
  4. package/out/typer/collectGlobalDefs.d.ts.map +1 -1
  5. package/out/typer/collectGlobalDefs.js +55 -7
  6. package/out/typer/collectGlobalDefs.js.map +1 -1
  7. package/out/typer/component-def-manager/component-def-manager.d.ts +65 -7
  8. package/out/typer/component-def-manager/component-def-manager.d.ts.map +1 -1
  9. package/out/typer/component-def-manager/component-def-manager.js +400 -8
  10. package/out/typer/component-def-manager/component-def-manager.js.map +1 -1
  11. package/out/typer/dispatch-call.d.ts.map +1 -1
  12. package/out/typer/dispatch-call.js +7 -1
  13. package/out/typer/dispatch-call.js.map +1 -1
  14. package/out/typer/dispatch-def.d.ts.map +1 -1
  15. package/out/typer/dispatch-def.js +1 -0
  16. package/out/typer/dispatch-def.js.map +1 -1
  17. package/out/typer/dispatch-view.d.ts.map +1 -1
  18. package/out/typer/dispatch-view.js +326 -142
  19. package/out/typer/dispatch-view.js.map +1 -1
  20. package/out/typer/overload-helper.d.ts +1 -1
  21. package/out/typer/overload-helper.d.ts.map +1 -1
  22. package/out/typer/overload-helper.js +13 -2
  23. package/out/typer/overload-helper.js.map +1 -1
  24. package/out/typer/subster.d.ts.map +1 -1
  25. package/out/typer/subster.js +80 -29
  26. package/out/typer/subster.js.map +1 -1
  27. package/out/typer/type-predicate.d.ts +14 -1
  28. package/out/typer/type-predicate.d.ts.map +1 -1
  29. package/out/typer/type-predicate.js +303 -3
  30. package/out/typer/type-predicate.js.map +1 -1
  31. package/out/typer/typer.d.ts +1 -0
  32. package/out/typer/typer.d.ts.map +1 -1
  33. package/out/typer/typer.js.map +1 -1
  34. package/out/typer/unifier.d.ts +8 -0
  35. package/out/typer/unifier.d.ts.map +1 -1
  36. package/out/typer/unifier.js +32 -0
  37. package/out/typer/unifier.js.map +1 -1
  38. package/out/utils/parseTsClassType.d.ts.map +1 -1
  39. package/out/utils/parseTsClassType.js +2 -2
  40. package/out/utils/parseTsClassType.js.map +1 -1
  41. package/package.json +6 -6
@@ -4,6 +4,7 @@ exports.isTagIgnored = exports.isAttrIgnored = exports.singletonValue = exports.
4
4
  const nasl_concepts_1 = require("@lcap/nasl-concepts");
5
5
  const type_manager_1 = require("./type-manager");
6
6
  const type_predicate_1 = require("./type-predicate");
7
+ const component_def_manager_1 = require("./component-def-manager/component-def-manager");
7
8
  const assertion_1 = require("../utils/assertion");
8
9
  const def_key_helpers_1 = require("../reference-manager/def-key-helpers");
9
10
  const reference_manager_1 = require("../reference-manager/reference-manager");
@@ -189,8 +190,21 @@ function* tpBusinessComponent(env, nd) {
189
190
  });
190
191
  nd.elements.forEach((e) => {
191
192
  (0, traverse_util_1.traverseViewElements)(e, (ve) => {
192
- const ty = env.unifier.nextFreshTyAnn('free');
193
- env.setType(ve, ty);
193
+ const compResult = env.refMgr?.compDefMgr?.queryByViewElement(ve);
194
+ if (compResult) {
195
+ const componentDef = compResult.instantiate(env);
196
+ const instanceProps = componentDef.getInstanceProps();
197
+ const veTy = nasl_concepts_1.TypeAnnotation.createTypeAnonymousStructure(instanceProps.map((x) => {
198
+ x.id = 'placeholder';
199
+ return nasl_concepts_1.StructureProperty.createProtoTypeOnly(x);
200
+ }), { id: 'placeholder' });
201
+ env.setType(ve, veTy);
202
+ ve.__preInstantiatedHelper = componentDef;
203
+ }
204
+ else {
205
+ const ty = env.unifier.nextFreshTyAnn('free');
206
+ env.setType(ve, ty);
207
+ }
194
208
  const qName = (0, def_key_helpers_1.getGlobalDefKey)(ve);
195
209
  (0, assertion_1.assertTrusy)(qName);
196
210
  env.allocatedVEQNames.push(qName);
@@ -329,8 +343,26 @@ function* tpView(env, nd, skipSubView = false) {
329
343
  });
330
344
  nd.elements.forEach((e) => {
331
345
  (0, traverse_util_1.traverseViewElements)(e, (ve) => {
332
- const ty = env.unifier.nextFreshTyAnn('free');
333
- env.setType(ve, ty);
346
+ // logics 之前提前实例化组件定义,将 VE 的类型设为带有已重命名类型参数的
347
+ // 实际结构类型(如 { data: List<T'>, page: Integer, ... })。
348
+ // 这样 logics 中 elements.xxx.property.data 能获得完整的结构信息,
349
+ // 并通过 T'(fresh type var)在约束系统中与 ListTransform 等函数的类型参数关联。
350
+ // tpViewElementFirstPass 会复用此 helper,处理 bindAttrs 以解出 T' 的具体类型。
351
+ const compResult = env.refMgr?.compDefMgr?.queryByViewElement(ve);
352
+ if (compResult) {
353
+ const componentDef = compResult.instantiate(env);
354
+ const instanceProps = componentDef.getInstanceProps();
355
+ const veTy = nasl_concepts_1.TypeAnnotation.createTypeAnonymousStructure(instanceProps.map((x) => {
356
+ x.id = 'placeholder';
357
+ return nasl_concepts_1.StructureProperty.createProtoTypeOnly(x);
358
+ }), { id: 'placeholder' });
359
+ env.setType(ve, veTy);
360
+ ve.__preInstantiatedHelper = componentDef;
361
+ }
362
+ else {
363
+ const ty = env.unifier.nextFreshTyAnn('free');
364
+ env.setType(ve, ty);
365
+ }
334
366
  const qName = (0, def_key_helpers_1.getGlobalDefKey)(ve);
335
367
  (0, assertion_1.assertTrusy)(qName);
336
368
  env.allocatedVEQNames.push(qName);
@@ -401,12 +433,21 @@ const excludedViewElementPropNames = new Set([
401
433
  'formDesignerBuildInDefaults',
402
434
  'formDesignerCalcDefaultsTargetField',
403
435
  ]);
404
- function findAncestorWhichHasTypeParamT(env, nd) {
436
+ /**
437
+ * 在祖先组件链中,找到拥有自己数据源的 dataItem 类型参数(基于 TypeParamMeta.role,而非名字)。
438
+ */
439
+ function findAncestorDataItemTypeParam(env, nd) {
405
440
  let cur = nd.parentNode;
406
441
  while (cur) {
407
- const t = env.getDataSourceTyParam(cur)?.find((x) => x.rawName === 'T');
408
- if (t) {
409
- return t;
442
+ const wrappers = env.getDataSourceTyParam(cur);
443
+ if (wrappers) {
444
+ const match = wrappers.find((w) => {
445
+ const ancestorComponentDef = env.refMgr?.compDefMgr.queryByViewElement(cur)?.instantiate(env);
446
+ const meta = ancestorComponentDef?.getTypeParamMeta(w.rawName);
447
+ return meta?.role === 'dataItem' && meta.hasOwnDataSource;
448
+ });
449
+ if (match)
450
+ return match;
410
451
  }
411
452
  if ((0, asserts_1.isViewElement)(cur.parentNode)) {
412
453
  cur = cur.parentNode;
@@ -442,7 +483,8 @@ function* tpViewElementFirstPass(env, nd) {
442
483
  if ((0, file_node_cache_1.checkNodeIsFileModuleInIdeWithCache)(nd)) {
443
484
  env.fileNodes.push(nd);
444
485
  }
445
- const componentDef = env.refMgr.compDefMgr.queryByViewElement(nd)?.instantiate(env);
486
+ const componentDef = nd.__preInstantiatedHelper
487
+ ?? env.refMgr.compDefMgr.queryByViewElement(nd)?.instantiate(env);
446
488
  yield;
447
489
  if (!componentDef) {
448
490
  env.addDiagnosticsFor(`${nd.tag} 组件的定义缺失`, nd, 'warning');
@@ -451,9 +493,55 @@ function* tpViewElementFirstPass(env, nd) {
451
493
  env.addError(`[DEBUG] ${nd.tag} 组件的定义缺失`);
452
494
  }
453
495
  }
454
- env.setDataSourceTyParam(nd, componentDef?.getRenamedTypeParamWrappers() ?? []);
496
+ const typeParamWrappers = componentDef?.getRenamedTypeParamWrappers() ?? [];
497
+ env.setDataSourceTyParam(nd, typeParamWrappers);
498
+ // 从父组件的 slot 返回类型统一泛型参数(按位置,不按名字)
499
+ // componentType 可能是 union,resolveComponentTypeForTag 会找到匹配的分支
500
+ const resolvedParentType = env.componentType?.typeKind === 'union'
501
+ ? resolveComponentTypeForTag(env.componentType, nd.tag)
502
+ : (env.componentType?.typeName === component_def_manager_1.ComponentTagUtils.normalizeTag(nd.tag)
503
+ ? env.componentType
504
+ : undefined);
505
+ if (resolvedParentType) {
506
+ typeParamWrappers.forEach((typeParam, index) => {
507
+ const pa = resolvedParentType.typeArguments?.[index];
508
+ if (typeParam?.renamed && pa) {
509
+ env.unifier.unify(typeParam.renamed, pa);
510
+ }
511
+ });
512
+ }
513
+ // 对于没有自己数据源但角色是 dataItem 的类型参数,从祖先组件继承
514
+ if (componentDef) {
515
+ typeParamWrappers.forEach((w) => {
516
+ const meta = componentDef.getTypeParamMeta(w.rawName);
517
+ if (meta?.role === 'dataItem' && !meta.hasOwnDataSource) {
518
+ const ancestorParam = findAncestorDataItemTypeParam(env, nd);
519
+ if (ancestorParam) {
520
+ env.unifier.unify(w.renamed, ancestorParam.renamed);
521
+ }
522
+ }
523
+ });
524
+ }
455
525
  const initProp = componentDef?.getInitPropsTypeAnnotation();
456
526
  const instanceProps = componentDef?.getInstanceProps();
527
+ // 判断某个类型变元是否在初始化属性里以
528
+ // typeParam | String | Boolean | Integer
529
+ // 这类 union 形式出现,用于在完全无约束的情况下兜底为 String。
530
+ const isStringishUnionParam = (wrapper) => {
531
+ const props = initProp?.properties ?? [];
532
+ return props.some((p) => {
533
+ const ty = p.typeAnnotation;
534
+ if (!ty || ty.typeKind !== 'union' || !ty.typeArguments?.length)
535
+ return false;
536
+ // 通过类型形状而不是引用相等来识别同一个类型变元
537
+ const hasThisParam = ty.typeArguments.some((arg) => arg?.typeKind === 'typeParam' && arg.typeName === wrapper.renamed.typeName);
538
+ if (!hasThisParam)
539
+ return false;
540
+ const hasStringLike = ty.typeArguments.some((arg) => arg?.typeNamespace === 'nasl.core' &&
541
+ (arg.typeName === 'String' || arg.typeName === 'Boolean' || arg.typeName === 'Integer'));
542
+ return hasThisParam && hasStringLike;
543
+ });
544
+ };
457
545
  /**
458
546
  * 线性复杂度根据name寻找给定的property
459
547
  * @param name
@@ -477,49 +565,56 @@ function* tpViewElementFirstPass(env, nd) {
477
565
  }
478
566
  return prop?.typeAnnotation ?? type_manager_1.naslAnyTy;
479
567
  };
480
- const typeParamWrappers = componentDef?.getRenamedTypeParamWrappers() ?? [];
568
+ // slot 返回类型中提取子组件类型(可能是 Array/List 包裹的 union)
569
+ function extractComponentTypeFromSlotReturn(slotProp) {
570
+ let ty = slotProp?.typeAnnotation?.returnType?.[0];
571
+ if (ty?.typeName === 'List' || ty?.typeName === 'Array') {
572
+ ty = ty?.typeArguments?.[0];
573
+ }
574
+ return ty;
575
+ }
576
+ // 从一个可能是 union 的类型中,找到与 childTag 匹配的那个分支
577
+ function resolveComponentTypeForTag(ty, childTag) {
578
+ if (!ty)
579
+ return undefined;
580
+ const normalizedChildTag = component_def_manager_1.ComponentTagUtils.normalizeTag(childTag);
581
+ // 直接匹配
582
+ if (ty.typeName === normalizedChildTag)
583
+ return ty;
584
+ // union: 遍历 typeArguments 找匹配的分支
585
+ if (ty.typeKind === 'union' && ty.typeArguments?.length) {
586
+ return ty.typeArguments.find((alt) => alt && component_def_manager_1.ComponentTagUtils.normalizeTag(alt.typeName ?? '') === normalizedChildTag);
587
+ }
588
+ return undefined;
589
+ }
481
590
  function makeCurrentManager(nd) {
482
- // currentManager机制分为两部分:
483
- // 1. 维护类型变元的约束
484
- // 2. current变量的创建和销毁
485
591
  if (!componentDef) {
486
592
  return;
487
593
  }
488
- const T1 = typeParamWrappers.find((x) => x.rawName === 'T1');
489
- const T = typeParamWrappers.find((x) => x.rawName === 'T');
490
- // 以下两个情形,从祖先组件中获取 T
491
- if (T1 && T) {
492
- // 如果当前组件定义了 T1 和 T,那么就必定从祖先组件中获取T
493
- const ancestorTableTypeParam = findAncestorWhichHasTypeParamT(env, nd)?.renamed ?? type_manager_1.naslAnyTy;
494
- env.unifier.unify(T.renamed, ancestorTableTypeParam);
495
- }
496
- // 如果当前组件没有定义 dataSource 且有 T,就需要从祖先组件中获取 T
497
- // u-transfer 使用 source 而非 dataSource 作为数据源
498
- const hasDataSourceInInitProp = !!componentDef
499
- .getInitPropsTypeAnnotation()
500
- ?.properties?.some((x) => x.name === 'dataSource' || (nd.tag === 'u-transfer' && x.name === 'source'));
501
- const shouldStealAncestorT = !hasDataSourceInInitProp;
502
- if (shouldStealAncestorT && T) {
503
- const ancestorTableTypeParam = findAncestorWhichHasTypeParamT(env, nd)?.renamed ?? type_manager_1.naslAnyTy;
504
- env.unifier.unify(T.renamed, ancestorTableTypeParam);
505
- }
594
+ // 预先解析 default slot 的返回类型,供非 template 直接子组件使用
595
+ const defaultSlotProp = componentDef.getSlotPropBySlotTarget('default');
596
+ const defaultSlotComponentType = extractComponentTypeFromSlotReturn(defaultSlotProp);
506
597
  return {
507
598
  create: (options) => {
508
- if (options.templateElement?.tag !== 'template') {
509
- return undefined;
510
- }
511
- const slotTarget = options.templateElement?.slotTarget ?? 'default';
512
- const slotScopeType = componentDef?.getSlotScopeTypeBySlotTarget(slotTarget);
513
- // 如果curTy非空,那么才需要引入current变量。否则完全不需要引入。
514
- let curTy = slotScopeType?.[0]?.type;
515
- if (curTy) {
516
- // 引入变量 current_i: CurTy
517
- const curName = env.unifier.nextFreshCurrent();
518
- const curVar = (0, collectGlobalDefs_1.createVarAndSetParentNode)(curName, curTy, nd);
519
- env.setType(curVar, curTy);
520
- env.addLexicalSym(curVar);
599
+ const child = options.templateElement;
600
+ if (child?.tag === 'template') {
601
+ // template slot: 创建 current 变量 + 提供 slot 级别的 componentType
602
+ const slotTarget = child.slotTarget ?? 'default';
603
+ const slotProp = componentDef?.getSlotPropBySlotTarget(slotTarget);
604
+ const slotScopeType = slotProp ? componentDef?.getSlotScopeType(slotProp) : null;
605
+ const slotComponentType = extractComponentTypeFromSlotReturn(slotProp);
606
+ let curTy = slotScopeType?.[0]?.type;
607
+ let curName;
608
+ let curVar;
609
+ if (curTy) {
610
+ curName = env.unifier.nextFreshCurrent();
611
+ curVar = (0, collectGlobalDefs_1.createVarAndSetParentNode)(curName, curTy, nd);
612
+ env.setType(curVar, curTy);
613
+ env.addLexicalSym(curVar);
614
+ }
521
615
  return {
522
616
  createdVar: curVar,
617
+ componentType: slotComponentType,
523
618
  revoke: () => {
524
619
  if (curName) {
525
620
  env.unifier.decreaseCurrentCounter();
@@ -528,6 +623,17 @@ function* tpViewElementFirstPass(env, nd) {
528
623
  },
529
624
  };
530
625
  }
626
+ // 非 template 直接子组件:使用 default slot 的返回类型
627
+ // 从 union 中按 tag 找到对应的分支,从而实现按位置统一泛型
628
+ const matched = resolveComponentTypeForTag(defaultSlotComponentType, child?.tag ?? '');
629
+ if (matched) {
630
+ return {
631
+ createdVar: undefined,
632
+ componentType: matched,
633
+ revoke: () => { },
634
+ };
635
+ }
636
+ return undefined;
531
637
  },
532
638
  };
533
639
  }
@@ -559,8 +665,34 @@ function* tpViewElementFirstPass(env, nd) {
559
665
  }
560
666
  });
561
667
  }
562
- env.unifier.unify(env.getRawType(nd), veTy);
563
- env.typeBindings.set(nd, veTy);
668
+ // 如果 VE 已经在 tpView 中被预实例化(类型已经是 anonymousStructure),
669
+ // 跳过 unify 以避免将同一结构的属性类型与自身统一导致自引用约束。
670
+ // 否则(VE 类型是 freshTyVar),通过 unify 连接 freshTyVar 与实际类型。
671
+ const existingTy = env.getRawType(nd);
672
+ if (!existingTy || existingTy.typeKind !== 'anonymousStructure') {
673
+ env.unifier.unify(existingTy, veTy);
674
+ }
675
+ env.setType(nd, veTy);
676
+ // 在完全求解后,对仍未解出的类型变元做默认兜底:
677
+ // 1. dataItem 角色(无论是否有自己的数据源):当数据源未设置且无其他约束能推出具体类型时,兜底为 String
678
+ // 2. 形状类似 T | String | Boolean | Integer 的 union 参数:兜底为 String
679
+ if (typeParamWrappers.length) {
680
+ env.addPostCheckTask(nd, (env, pSubst) => {
681
+ typeParamWrappers.forEach((w) => {
682
+ const solved = pSubst(w.renamed);
683
+ if (!(0, type_predicate_1.isUnResolvedTy)(solved))
684
+ return;
685
+ const meta = componentDef?.getTypeParamMeta(w.rawName);
686
+ if (meta?.role === 'dataItem') {
687
+ env.unifier.unify(w.renamed, meta.defaultWhenUnresolved ?? type_manager_1.naslStringTy);
688
+ return;
689
+ }
690
+ if (isStringishUnionParam(w)) {
691
+ env.unifier.unify(w.renamed, type_manager_1.naslStringTy);
692
+ }
693
+ });
694
+ });
695
+ }
564
696
  const ceTy = nasl_concepts_1.TypeAnnotation.createProtoTypeOnly({
565
697
  typeName: 'IElements',
566
698
  typeKind: 'anonymousStructure',
@@ -577,7 +709,7 @@ function* tpViewElementFirstPass(env, nd) {
577
709
  const elements = (0, collectGlobalDefs_1.createVarAndSetParentNode)('elements', ceTy, nd);
578
710
  // env.setType(elements, ceTy);
579
711
  // 省点内存
580
- env.typeBindings.set(elements, ceTy);
712
+ env.setType(elements, ceTy);
581
713
  env.addLexicalSym(elements);
582
714
  const dropList = [];
583
715
  dropList.push(elements);
@@ -633,8 +765,11 @@ function* tpViewElementFirstPass(env, nd) {
633
765
  dropList.push(popper.createdVar);
634
766
  (0, helper_1.createOnPush)(env.allocatedVirtualNodes, env.getCurFileNode(), popper.createdVar);
635
767
  }
768
+ // 存在组件类型
769
+ env.componentType = popper?.componentType;
636
770
  yield* tpViewElementFirstPass(env, e);
637
771
  popper?.revoke();
772
+ env.componentType = undefined;
638
773
  });
639
774
  /**
640
775
  * 特性:部分元素的 V 类型可以由子元素的 V 推导得到。同时又尊重此元素本身引入的 V 约束
@@ -675,22 +810,32 @@ function* tpViewElementFirstPass(env, nd) {
675
810
  * 当前ViewElement的children中第一个非template的 BindAttribute[name='value'](若无,则为其他类型为V的字段)的类型
676
811
  */
677
812
  /**
678
- * 如果当前元素没有T1,且当前元素的定义中有类型变元 V,而子级组件也有类似的变元 V',那么需要unify(V', V)。
679
- * 以此将子元素的 V' 约束贡献给当前元素的 V
813
+ * 如果当前组件有 valueResult 类型变元,且没有自己的额外数据源(role = 'dataItem' 且 hasOwnDataSource 的第二个),
814
+ * 则子组件同角色的 valueResult 应当贡献到当前组件的 valueResult 上。
815
+ * 这里通过 role 匹配,而不是看名字是否是 'V' / 'T1'。
680
816
  */
681
- const vThisLevel = typeParamWrappers.find((x) => x.rawName === 'V')?.renamed;
682
- const shouldVThisLevelBeInferred = !typeParamWrappers.find((x) => x.rawName === 'T1') &&
683
- // 若当前 V 无任何约束
684
- !!vThisLevel &&
685
- env.unifier.containsEmptyBounds(vThisLevel);
686
- if (shouldVThisLevelBeInferred) {
817
+ const valueResultWrapper = typeParamWrappers.find((w) => {
818
+ const m = componentDef?.getTypeParamMeta(w.rawName);
819
+ return m?.role === 'valueResult';
820
+ });
821
+ const hasMultipleDataItems = typeParamWrappers.filter((w) => {
822
+ const m = componentDef?.getTypeParamMeta(w.rawName);
823
+ return m?.role === 'dataItem' && m.hasOwnDataSource;
824
+ }).length > 1;
825
+ const shouldValueBeInferred = !hasMultipleDataItems &&
826
+ !!valueResultWrapper &&
827
+ env.unifier.containsEmptyBounds(valueResultWrapper.renamed);
828
+ if (shouldValueBeInferred) {
687
829
  yield* (0, nasl_utils_1.wrapForEachToGenerator)(nd.children, function* (e) {
688
- // HACK: 简单的关联组件判断,后续可以考虑更复杂的判断
689
- if (e.tag.startsWith(nd.tag) ||
690
- (e.tag === 'el-option' && ['el-select', 'el-form-select'].includes(nd.tag))) {
691
- const vChild = env.getDataSourceTyParam(e)?.find((x) => x.rawName === 'V')?.renamed;
830
+ if (e.tag.startsWith(nd.tag)) {
831
+ const childWrappers = env.getDataSourceTyParam(e);
832
+ const vChild = childWrappers?.find((cw) => {
833
+ const childDef = env.refMgr?.compDefMgr.queryByViewElement(e)?.instantiate(env);
834
+ const cm = childDef?.getTypeParamMeta(cw.rawName);
835
+ return cm?.role === 'valueResult';
836
+ })?.renamed;
692
837
  if (vChild) {
693
- env.unifier.unify(vChild, vThisLevel);
838
+ env.unifier.unify(vChild, valueResultWrapper.renamed);
694
839
  }
695
840
  }
696
841
  });
@@ -703,8 +848,10 @@ function* tpViewElementFirstPass(env, nd) {
703
848
  dropList.forEach((x) => {
704
849
  env.removeLexicalVarByName(x.name);
705
850
  });
706
- // el-table-column-dynamic,u-table-view-column-dynamic,T/T1 特殊处理
707
- const isColumnDynamic = ['el-table-column-dynamic', 'u-table-view-column-dynamic'].includes(nd?.tag ?? '');
851
+ /**
852
+ * 通用的类型变元 fallback 过程:
853
+ * 完全基于 TypeParamMeta.role 驱动,不依赖任何 rawName 硬编码。
854
+ */
708
855
  const isBindAttrEmptyExpression = (name) => {
709
856
  const attr = nd.bindAttrs?.find((attr) => attr.name === name);
710
857
  if (!attr) {
@@ -714,98 +861,98 @@ function* tpViewElementFirstPass(env, nd) {
714
861
  return attr.type === 'dynamic' && !attr.expression;
715
862
  }
716
863
  };
717
- // 组件语义化的类型变元 fallback 规则
718
- typeParamWrappers.forEach((x) => {
719
- if (x.rawName === 'T') {
720
- // el-list-components 开启表单模式 formMode,会清除 dataSource,T 类型变元会根据表单值 model 推导,因此不需要 fallback 规则
721
- if (nd?.tag === 'el-list-components') {
722
- const formModeAttr = nd.bindAttrs?.find((attr) => attr.name === 'formMode');
723
- if (formModeAttr?.type === 'static' && formModeAttr?.value === true) {
724
- return;
725
- }
726
- }
727
- // ColumnDynamic 场景下,T 类型变元从 table 中推导,不需要 fallback 规则
728
- if (isColumnDynamic) {
729
- return;
864
+ const inferTypeFromPropMeta = (cfg, nd) => {
865
+ const attr = nd.bindAttrs?.find((a) => a.name === cfg.propName);
866
+ if (!attr) {
867
+ if (cfg.mode === 'booleanFlag')
868
+ return nasl_concepts_1.TypeAnnotation.createLiteral(false);
869
+ return undefined;
870
+ }
871
+ if (cfg.mode === 'booleanFlag') {
872
+ if (['static', 'string'].includes(attr.type ?? '')) {
873
+ return nasl_concepts_1.TypeAnnotation.createLiteral(!!attr.value);
730
874
  }
731
- // 如果数据源未定义或为空,则不可能有任何合适的解,回落到 String 类型
732
- const isEmpty = isBindAttrEmptyExpression('dataSource');
733
- if (isEmpty) {
734
- env.unifier.unify(x.renamed, type_manager_1.naslStringTy);
875
+ return type_manager_1.naslBooleanTy;
876
+ }
877
+ if (cfg.mode === 'converterLiteral') {
878
+ if (['static', 'string'].includes(attr.type ?? '')) {
879
+ if ([true, 'true'].includes(attr.value)) {
880
+ return nasl_concepts_1.TypeAnnotation.createLiteral(true);
881
+ }
882
+ if ([false, 'false'].includes(attr.value)) {
883
+ return nasl_concepts_1.TypeAnnotation.createLiteral(false);
884
+ }
885
+ return nasl_concepts_1.TypeAnnotation.createLiteral(String(attr.value));
735
886
  }
887
+ return nasl_concepts_1.TypeAnnotation.createLiteral('');
736
888
  }
737
- else if (x.rawName === 'T1') {
738
- // ColumnDynamic 场景下,T1 类型变元需要 fallback 规则
739
- // 如果数据源未定义或为空,则不可能有任何合适的解,回落到 String 类型
740
- if (isColumnDynamic && isBindAttrEmptyExpression('dataSource')) {
741
- env.unifier.unify(x.renamed, type_manager_1.naslStringTy);
889
+ if (cfg.mode === 'stringLiteral') {
890
+ if (['static', 'string'].includes(attr.type ?? '')) {
891
+ return nasl_concepts_1.TypeAnnotation.createLiteral(String(attr.value ?? ''));
742
892
  }
893
+ return type_manager_1.naslStringTy;
743
894
  }
744
- if (env.unifier.containsEmptyBounds(x.renamed)) {
745
- if (x.rawName === 'T' || x.rawName === 'V') {
746
- // 兼容旧规则: 如果组件的T V类型变元没有约束,那么它就是 String
747
- env.unifier.unify(x.renamed, type_manager_1.naslStringTy);
895
+ return undefined;
896
+ };
897
+ typeParamWrappers.forEach((wrapper) => {
898
+ const meta = componentDef?.getTypeParamMeta(wrapper.rawName);
899
+ if (!meta) {
900
+ // 没有 meta 的类型参数(如 u-transfer 的 V,仅出现在事件处理器参数结构体内)
901
+ // 也需要注册到约束系统中,否则 solver 无法将其兜底为 any,
902
+ // 导致包含该类型参数的函数类型在 postCheck 中被判为 unresolved 而误报。
903
+ if (wrapper.renamed?.typeName && env.unifier.containsEmptyBounds(wrapper.renamed)) {
904
+ env.unifier.unify(wrapper.renamed, type_manager_1.naslAnyTy);
748
905
  }
749
- else if (x.rawName === 'C') {
750
- // 规则引自 ViewElement的toEmbeddedTSRefTree
751
- const getConverterDefaultValue = (name) => {
752
- const attr = nd.bindAttrs?.find((attr) => attr.name === name);
753
- if (attr) {
754
- if (['static', 'string'].includes(attr?.type ?? "")) {
755
- if ([true, 'true'].includes(attr.value)) {
756
- return nasl_concepts_1.TypeAnnotation.createLiteral(true);
757
- }
758
- else if ([false, 'false'].includes(attr.value)) {
759
- return nasl_concepts_1.TypeAnnotation.createLiteral(false);
760
- }
761
- else {
762
- return nasl_concepts_1.TypeAnnotation.createLiteral(String(attr.value));
763
- }
764
- }
765
- }
766
- return nasl_concepts_1.TypeAnnotation.createLiteral("");
767
- };
768
- // 兼容旧规则: 如果组件的C类型变元没有约束,那么它就是...
769
- env.unifier.unify(x.renamed, getConverterDefaultValue('converter'));
906
+ return;
907
+ }
908
+ // dataItem 且有自己的数据源:仅在数据源为空且无具体类型约束时回落到默认类型。
909
+ // 预实例化后逻辑可能已为 T 建立了纯类型参数约束(如 T T_lt),
910
+ // 这些不携带具体信息,不应阻止兜底;但 model 等属性提供的具体类型约束应阻止兜底。
911
+ if (meta.role === 'dataItem' && meta.hasOwnDataSource && meta.dataSourcePropName) {
912
+ if (isBindAttrEmptyExpression(meta.dataSourcePropName) && meta.defaultWhenUnresolved
913
+ && env.unifier.hasNoConcreteBounds(wrapper.renamed)) {
914
+ env.unifier.unify(wrapper.renamed, meta.defaultWhenUnresolved);
770
915
  }
771
- else if (x.rawName === 'M') {
772
- // 规则引自 ViewElement的toEmbeddedTSRefTree
773
- const getPropInitialValue = (name) => {
774
- const attr = nd.bindAttrs?.find((attr) => attr.name === name);
775
- if (attr && ['static', 'string'].includes(attr?.type ?? "")) {
776
- return nasl_concepts_1.TypeAnnotation.createLiteral(!!attr.value);
777
- }
778
- return type_manager_1.naslBooleanTy;
779
- };
780
- // 兼容旧规则: 如果组件的M类型变元没有约束,那么它就是...
781
- env.unifier.unify(x.renamed, getPropInitialValue('multiple'));
916
+ return;
917
+ }
918
+ // dataItem 但没有自己的数据源(从祖先继承):祖先的类型参数 bound 是有意义的约束,
919
+ // 必须用 containsEmptyBounds(而非 hasNoConcreteBounds)来防止覆盖祖先传播的类型。
920
+ if (meta.role === 'dataItem' && !meta.hasOwnDataSource) {
921
+ if (meta.defaultWhenUnresolved && env.unifier.containsEmptyBounds(wrapper.renamed)) {
922
+ env.unifier.unify(wrapper.renamed, meta.defaultWhenUnresolved);
782
923
  }
783
- else if (x.rawName === 'P') {
784
- // 规则引自 ViewElement的toEmbeddedTSRefTree
785
- const getPropInitialValue = (name) => {
786
- const attr = nd.bindAttrs?.find((attr) => attr.name === name);
787
- if (attr && ['static', 'string'].includes(attr?.type ?? "")) {
788
- return nasl_concepts_1.TypeAnnotation.createLiteral(!!attr.value);
789
- }
790
- return type_manager_1.naslBooleanTy;
791
- };
792
- // 兼容旧规则: 如果组件的P类型变元没有约束,那么它就是...
793
- env.unifier.unify(x.renamed, getPropInitialValue('pageable'));
924
+ return;
925
+ }
926
+ // booleanFlag / converterLiteral 等:优先根据属性值推导类型
927
+ if (meta.defaultFromProp && env.unifier.hasNoConcreteBounds(wrapper.renamed)) {
928
+ const ty = inferTypeFromPropMeta(meta.defaultFromProp, nd);
929
+ if (ty) {
930
+ env.unifier.unify(wrapper.renamed, ty);
794
931
  }
795
932
  }
933
+ // 非 dataItem 的参数:若仍无具体类型约束,使用默认类型
934
+ if (meta.defaultWhenUnresolved && env.unifier.hasNoConcreteBounds(wrapper.renamed)) {
935
+ env.unifier.unify(wrapper.renamed, meta.defaultWhenUnresolved);
936
+ }
796
937
  });
797
938
  env.addPostCheckTask(nd, (_, pSubst) => {
798
- nd.__ViewElementTV = typeParamWrappers.map((w) => ({
799
- rawName: w.rawName,
800
- renamed: pSubst(w.renamed),
801
- }));
939
+ const meta = (rawName) => componentDef?.getTypeParamMeta(rawName);
940
+ nd.__ViewElementTV = typeParamWrappers.map((w) => {
941
+ const m = meta(w.rawName);
942
+ return {
943
+ rawName: w.rawName,
944
+ renamed: pSubst(w.renamed),
945
+ role: m?.role,
946
+ hasOwnDataSource: m?.hasOwnDataSource,
947
+ };
948
+ });
802
949
  });
803
950
  // 在 postCheck 中设置事件类型,使用求解完成后的类型信息
804
951
  if (componentDef) {
805
952
  env.addPostCheckTask(nd, (env, pSubst) => {
806
953
  const initProp = componentDef.getInitPropsTypeAnnotation();
807
954
  if (initProp?.properties) {
808
- initProp.properties.forEach(prop => {
955
+ initProp.properties.forEach((prop) => {
809
956
  if (prop.name.startsWith('on') && prop.typeAnnotation?.typeKind === 'function') {
810
957
  // eventName 形如 "onBlurValid"
811
958
  const eventName = prop.name;
@@ -926,7 +1073,16 @@ function* tpViewElementFirstPass(env, nd) {
926
1073
  }
927
1074
  }
928
1075
  else if (tgtTy.typeKind !== 'function') {
929
- env.unifier.unify(type_manager_1.naslStringTy, tgtTy);
1076
+ // 类型参数带 extends(如 DT extends 'date' | 'datetime')时,应用字面量约束而非 String,
1077
+ // 否则 unify(naslStringTy, DT) 会把 DT 的界弄成宽 String,丢失字面量判别。
1078
+ let subTy = type_manager_1.naslStringTy;
1079
+ if ((0, type_predicate_1.isTyAnnTyParam)(tgtTy) && tgtTy.constraints) {
1080
+ const litTy = (0, bind_expression_hack_1.inferStaticValueTypeAsLiteral)(prop.value);
1081
+ subTy = (0, type_predicate_1.isAnyTy)(litTy)
1082
+ ? nasl_concepts_1.TypeAnnotation.createLiteral(String(prop.value ?? ''))
1083
+ : litTy;
1084
+ }
1085
+ env.unifier.unify(subTy, tgtTy);
930
1086
  }
931
1087
  }
932
1088
  function collectTypeConstraints(prop) {
@@ -1024,20 +1180,48 @@ function* tpViewElementFirstPass(env, nd) {
1024
1180
  }
1025
1181
  const args = (0, nasl_type_manipulation_1.extractArgPairsFromFnTy)(propTy);
1026
1182
  l.virtualParams = [];
1183
+ // 兜底替换:将所有组件类型参数映射为 any,用于 solver 无法完整求解时的回退。
1184
+ // 例如 u-transfer 的 onChange 事件参数类型为 { source: List<T>, transferValues: List<V> },
1185
+ // 若 V 无约束且从未进入 cs,solver 会将 ty 整体坍缩为 any。
1186
+ // 此时利用 solver deref 已原地修改的 arg.type(T 已替换)+ fallbackSubst(V→any)
1187
+ // 可以恢复出 { source: List<{...}>, transferValues: List<any> } 这个更精确的类型。
1188
+ const typeParamFallbackSubst = new Map();
1189
+ typeParamWrappers.forEach((w) => {
1190
+ if (w.renamed?.typeName) {
1191
+ typeParamFallbackSubst.set(w.renamed.typeName, type_manager_1.naslAnyTy);
1192
+ }
1193
+ });
1027
1194
  args?.forEach((arg) => {
1028
1195
  // pointToSymbol机制要求类型必须是类型变元才会被替换
1029
1196
  const ty = env.unifier.nextFreshTyAnn('bound');
1030
1197
  env.unifier.unify(ty, arg.type);
1198
+ // 直接使用 arg.type(如匿名结构 { value: V })作为虚参的类型标注,
1199
+ // 而非使用 fresh type var ty。这样事件体内对 event.value 的成员访问
1200
+ // 能直接在结构上找到 V,从而让 event.value = true 把 Boolean 约束回 V。
1201
+ const paramTyAnn = arg.type ?? ty;
1031
1202
  const newParam = nasl_concepts_1.Param.createProtoTypeOnly({
1032
1203
  name: arg.name,
1033
- typeAnnotation: ty
1204
+ typeAnnotation: paramTyAnn
1034
1205
  });
1035
1206
  // 必须设置好,不然 IDE 展示有问题
1036
1207
  newParam.parentNode = l;
1037
1208
  newParam.parentKey = 'virtualParams';
1038
1209
  l.virtualParams.push(newParam);
1039
- env.queryManager.asyncQuery(l).typeLevelEvaluationOf(ty).then(x => {
1040
- if ((0, type_predicate_1.isUnResolvedTy)(x)) {
1210
+ env.queryManager.asyncQuery(l).typeLevelEvaluationOf(ty).then((x) => {
1211
+ if ((0, type_predicate_1.isUnResolvedTy)(x) || (0, type_predicate_1.isAnyTy)(x)) {
1212
+ // ty 被 solver 坍缩为 any(常见原因:UB 中的匿名结构含有未进入 cs 的类型参数)。
1213
+ // arg.type 可能已被 solver 的 deref 原地修改(已求解的类型参数已替换)。
1214
+ // 通过 substTyAnn + typeParamFallbackSubst 将剩余类型参数替换为 any,
1215
+ // 保留已求解部分,得到更精确的兜底类型。
1216
+ const inPlaceType = newParam.typeAnnotation;
1217
+ if (inPlaceType && !(0, type_predicate_1.isAnyTy)(inPlaceType) && inPlaceType !== ty) {
1218
+ const resolved = (0, subster_1.substTyAnn)(env, typeParamFallbackSubst)(inPlaceType);
1219
+ if (!(0, type_predicate_1.isAnyTy)(resolved) && !(0, type_predicate_1.isUnResolvedTy)(resolved)) {
1220
+ env.setType(newParam, resolved);
1221
+ newParam.typeAnnotation = resolved;
1222
+ return;
1223
+ }
1224
+ }
1041
1225
  env.setType(newParam, undefined);
1042
1226
  newParam.typeAnnotation = undefined;
1043
1227
  }
@@ -1197,7 +1381,7 @@ const skipCheckDataBase = {
1197
1381
  };
1198
1382
  function isAttrIgnored(options) {
1199
1383
  if (options.attrName === 'value') {
1200
- if (options.tag === 'el-option') {
1384
+ if (['u-radios', 'van-option'].includes(options.tag)) {
1201
1385
  // HACK: 仅允许它贡献到父组件上
1202
1386
  return false;
1203
1387
  }