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