sysml-validate 0.14.5 → 0.15.1
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/main.js +146 -4
- package/out/main.js.map +3 -3
- package/package.json +1 -1
- package/resources/sysml.dimension-table.json +1 -1
package/out/main.js
CHANGED
|
@@ -30760,6 +30760,9 @@ function isExhibitStateDecl(item) {
|
|
|
30760
30760
|
var ExitAction = "ExitAction";
|
|
30761
30761
|
var ExposeMember = "ExposeMember";
|
|
30762
30762
|
var ExposePath = "ExposePath";
|
|
30763
|
+
function isExposePath(item) {
|
|
30764
|
+
return reflection2.isInstance(item, ExposePath);
|
|
30765
|
+
}
|
|
30763
30766
|
var ExpressionDecl = "ExpressionDecl";
|
|
30764
30767
|
var FeatureDecl = "FeatureDecl";
|
|
30765
30768
|
function isFeatureDecl(item) {
|
|
@@ -58052,14 +58055,14 @@ function evaluateFilterCondition(node, metadataTypes, resolveRefs) {
|
|
|
58052
58055
|
return node.items.length === 1 ? evaluateFilterCondition(node.items[0], metadataTypes, resolveRefs) : void 0;
|
|
58053
58056
|
}
|
|
58054
58057
|
if (isBinExpr(node)) {
|
|
58055
|
-
if (
|
|
58058
|
+
if (CONJUNCTION_OPERATORS.has(node.op)) {
|
|
58056
58059
|
const left = evaluateFilterCondition(node.left, metadataTypes, resolveRefs);
|
|
58057
58060
|
const right = evaluateFilterCondition(node.right, metadataTypes, resolveRefs);
|
|
58058
58061
|
if (left === false || right === false)
|
|
58059
58062
|
return false;
|
|
58060
58063
|
return left === void 0 || right === void 0 ? void 0 : true;
|
|
58061
58064
|
}
|
|
58062
|
-
if (
|
|
58065
|
+
if (DISJUNCTION_OPERATORS.has(node.op)) {
|
|
58063
58066
|
const left = evaluateFilterCondition(node.left, metadataTypes, resolveRefs);
|
|
58064
58067
|
const right = evaluateFilterCondition(node.right, metadataTypes, resolveRefs);
|
|
58065
58068
|
if (left === true || right === true)
|
|
@@ -58070,6 +58073,42 @@ function evaluateFilterCondition(node, metadataTypes, resolveRefs) {
|
|
|
58070
58073
|
}
|
|
58071
58074
|
return void 0;
|
|
58072
58075
|
}
|
|
58076
|
+
var CONJUNCTION_OPERATORS = /* @__PURE__ */ new Set(["and", "&"]);
|
|
58077
|
+
var DISJUNCTION_OPERATORS = /* @__PURE__ */ new Set(["or", "|"]);
|
|
58078
|
+
function undecidableFilterCondition(condition) {
|
|
58079
|
+
if (!condition)
|
|
58080
|
+
return void 0;
|
|
58081
|
+
if (isSelfClassifyExpr(condition)) {
|
|
58082
|
+
return condition.op === "@" || condition.op === "@@" ? void 0 : { node: condition, construct: `the '${condition.op}' classification operator` };
|
|
58083
|
+
}
|
|
58084
|
+
if (isNotExpr(condition) && condition.operand) {
|
|
58085
|
+
return undecidableFilterCondition(condition.operand);
|
|
58086
|
+
}
|
|
58087
|
+
if (condition.$type === "NotExpr")
|
|
58088
|
+
return void 0;
|
|
58089
|
+
if (isParenExpr(condition)) {
|
|
58090
|
+
return condition.items.length === 1 ? undecidableFilterCondition(condition.items[0]) : { node: condition, construct: "a parenthesized expression list" };
|
|
58091
|
+
}
|
|
58092
|
+
if (isBinExpr(condition)) {
|
|
58093
|
+
if (CONJUNCTION_OPERATORS.has(condition.op) || DISJUNCTION_OPERATORS.has(condition.op)) {
|
|
58094
|
+
return undecidableFilterCondition(condition.left) ?? undecidableFilterCondition(condition.right);
|
|
58095
|
+
}
|
|
58096
|
+
return { node: condition, construct: `the '${condition.op}' operator` };
|
|
58097
|
+
}
|
|
58098
|
+
return { node: condition, construct: describeFilterExpr(condition) };
|
|
58099
|
+
}
|
|
58100
|
+
function describeFilterExpr(node) {
|
|
58101
|
+
if (isPostfixOp(node)) {
|
|
58102
|
+
if ((node.dot || node.select) && node.field)
|
|
58103
|
+
return `the '.${node.field}' feature access`;
|
|
58104
|
+
if (node.arrow && node.invoke)
|
|
58105
|
+
return `the '->${node.invoke}' invocation`;
|
|
58106
|
+
return "this postfix expression";
|
|
58107
|
+
}
|
|
58108
|
+
if (isPathExpr(node))
|
|
58109
|
+
return "a plain feature reference";
|
|
58110
|
+
return "this expression form";
|
|
58111
|
+
}
|
|
58073
58112
|
function attachedMetadataTypes(node, resolveRefs) {
|
|
58074
58113
|
const siblings = node.$container?.members;
|
|
58075
58114
|
if (!Array.isArray(siblings))
|
|
@@ -58713,6 +58752,13 @@ var DIAGNOSTIC_MESSAGES = {
|
|
|
58713
58752
|
// implicit-redefinition resolution this validator does not yet model.
|
|
58714
58753
|
RES017_DUPLICATE_MEMBER: (name) => `'${name}' is already declared in this namespace. Members must be distinguishable by name \u2014 rename or remove the duplicate.`,
|
|
58715
58754
|
RES017_DUPLICATE_SHORT_NAME: (name) => `Short name '<${name}>' is already used in this namespace. Members must be distinguishable \u2014 rename the short name.`,
|
|
58755
|
+
// issue #102 — REQ-387. The filter evaluator (`import-visibility.ts`,
|
|
58756
|
+
// `evaluateFilterCondition`) decides metadata-presence tests and the Boolean
|
|
58757
|
+
// operators over them; every other form fails OPEN, keeping the member. Failing
|
|
58758
|
+
// open is the right default — under-importing would manufacture spurious
|
|
58759
|
+
// RES001s — but an unevaluated filter produces a scope or view that looks
|
|
58760
|
+
// authoritative and is not, so the fail-open is stated rather than assumed.
|
|
58761
|
+
RES018_UNEVALUATED_FILTER: (subject, construct) => `${subject} filter is not evaluated: ${construct} is outside the metadata-filter evaluator, which decides '@'/'@@' metadata tests combined with 'not', 'and'/'&', and 'or'/'|'. The filter fails open \u2014 it is treated as pass-through and removes nothing.`,
|
|
58716
58762
|
// REQ-007 — SysML v1 → v2 migration guardrail.
|
|
58717
58763
|
MIG001_V1_KEYWORD: (keyword, v2, note) => `'${keyword}' is a SysML v1 construct, not a SysML v2 keyword. ${note} Use '${v2}'.`,
|
|
58718
58764
|
MIG002_V1_STEREOTYPE_KNOWN: (stereotype, v2, note) => `SysML v2 has no stereotypes. ${note} Replace \xAB${stereotype}\xBB with '${v2}'.`,
|
|
@@ -59496,6 +59542,26 @@ function actionOnlyConstruct(node) {
|
|
|
59496
59542
|
const keyword = CONTROL_NODE_KEYWORDS[node.$type];
|
|
59497
59543
|
return keyword ? { construct: `A '${keyword}' control node`, clause: "7.17.3" } : void 0;
|
|
59498
59544
|
}
|
|
59545
|
+
function portionKindOf(node) {
|
|
59546
|
+
const mods = node.modifiers;
|
|
59547
|
+
if (!Array.isArray(mods))
|
|
59548
|
+
return void 0;
|
|
59549
|
+
return mods.find((m) => m === "timeslice" || m === "snapshot");
|
|
59550
|
+
}
|
|
59551
|
+
var NAMESPACE_ONLY_TYPES = /* @__PURE__ */ new Set(["Package", "Document", "NamespaceDecl"]);
|
|
59552
|
+
function owningTypeOf(node) {
|
|
59553
|
+
let owner = node.$container;
|
|
59554
|
+
while (owner && (owner.$type === "PrefixMetadataMember" || owner.$type === "ThenActionMember")) {
|
|
59555
|
+
owner = owner.$container;
|
|
59556
|
+
}
|
|
59557
|
+
if (!owner)
|
|
59558
|
+
return void 0;
|
|
59559
|
+
return NAMESPACE_ONLY_TYPES.has(owner.$type) ? void 0 : owner;
|
|
59560
|
+
}
|
|
59561
|
+
function declLabel(node, kind) {
|
|
59562
|
+
const name = node.name;
|
|
59563
|
+
return name ? `'${name}'` : `this '${kind}'`;
|
|
59564
|
+
}
|
|
59499
59565
|
var SysmlValidator = class _SysmlValidator {
|
|
59500
59566
|
indexManager;
|
|
59501
59567
|
langiumDocuments;
|
|
@@ -59719,6 +59785,7 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
59719
59785
|
this.checkLanguageDisjointness(node, accept);
|
|
59720
59786
|
this.checkKermlWellFormedness(node, accept);
|
|
59721
59787
|
this.checkFilterExpressions(node, accept);
|
|
59788
|
+
this.checkOccurrencePortions(node, accept);
|
|
59722
59789
|
this.checkCrossReferences(node, accept);
|
|
59723
59790
|
this.checkAutoImportSuggestions(node, accept);
|
|
59724
59791
|
this.checkUnits(node, accept);
|
|
@@ -60546,6 +60613,7 @@ ${baseIndent}}`;
|
|
|
60546
60613
|
checkCrossReferences(node, accept) {
|
|
60547
60614
|
const index = this.buildIndex();
|
|
60548
60615
|
const imports = [];
|
|
60616
|
+
const exposePaths = [];
|
|
60549
60617
|
const decls = [];
|
|
60550
60618
|
const verifyStmts = [];
|
|
60551
60619
|
const satisfyStmts = [];
|
|
@@ -60557,7 +60625,9 @@ ${baseIndent}}`;
|
|
|
60557
60625
|
const path10 = importedPath(child);
|
|
60558
60626
|
if (alias && path10 && importWildcard(child) === "none")
|
|
60559
60627
|
aliasMap.set(alias, path10);
|
|
60560
|
-
} else if (
|
|
60628
|
+
} else if (isExposePath(child))
|
|
60629
|
+
exposePaths.push(child);
|
|
60630
|
+
else if (isVerifyStmt(child))
|
|
60561
60631
|
verifyStmts.push(child);
|
|
60562
60632
|
else if (isSatisfyStmt(child))
|
|
60563
60633
|
satisfyStmts.push(child);
|
|
@@ -60573,6 +60643,7 @@ ${baseIndent}}`;
|
|
|
60573
60643
|
this.checkPrivateImports(imports, index, accept);
|
|
60574
60644
|
this.checkPrivateWildcardReferences(node, imports, index, accept);
|
|
60575
60645
|
this.checkImportCycles(imports, index, accept);
|
|
60646
|
+
this.checkUnevaluatedFilters(imports, exposePaths, accept);
|
|
60576
60647
|
if (sysmlDiagnosticSettings.unusedImports !== "off") {
|
|
60577
60648
|
this.checkUnusedImports(node, imports, index, sysmlDiagnosticSettings.unusedImports, accept);
|
|
60578
60649
|
}
|
|
@@ -60602,6 +60673,49 @@ ${baseIndent}}`;
|
|
|
60602
60673
|
this.checkTypeComposition(decl, index, accept);
|
|
60603
60674
|
}
|
|
60604
60675
|
}
|
|
60676
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
60677
|
+
// REQ-160 — occurrence portions: `timeslice` / `snapshot` well-formedness
|
|
60678
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
60679
|
+
// The declaration kinds that are NOT occurrences, so a portion of their life is
|
|
60680
|
+
// meaningless. Kept as a deny-list rather than an allow-list because nearly every
|
|
60681
|
+
// SysML usage kind IS an OccurrenceUsage (part, item, action, state, port,
|
|
60682
|
+
// connection, case, …) while the data-valued ones are few and closed: an
|
|
60683
|
+
// AttributeUsage is typed by a DataType and an enumeration is a special attribute,
|
|
60684
|
+
// neither of which has a life to carve up.
|
|
60685
|
+
static NON_OCCURRENCE_OWNER_TYPES = /* @__PURE__ */ new Set(["AttributeDecl", "EnumDecl", "EnumValueDecl", "MetadataDecl", "MultiplicityDecl"]);
|
|
60686
|
+
// REQ-160 — OMG SysML v2 §8.2.2.9 (Occurrences) with the Kernel Semantic Library
|
|
60687
|
+
// `Occurrences::TimeSlice` / `Occurrences::Snapshot`:
|
|
60688
|
+
//
|
|
60689
|
+
// SSM015 — a portion is a portion OF something. Its owning type must be an
|
|
60690
|
+
// occurrence; declared at package level (or inside an attribute /
|
|
60691
|
+
// enumeration body) there is no life for it to be a portion of.
|
|
60692
|
+
// SSM016 — `Snapshot :> TimeSlice` with a zero duration: a snapshot is the
|
|
60693
|
+
// occurrence at ONE instant. Every portion of a zero-duration
|
|
60694
|
+
// occurrence is itself zero-duration, so a snapshot may own further
|
|
60695
|
+
// snapshots but never a `timeslice`.
|
|
60696
|
+
//
|
|
60697
|
+
// Both are structural — they read modifiers and owners only — so they hold
|
|
60698
|
+
// without resolving a single cross-reference.
|
|
60699
|
+
checkOccurrencePortions(node, accept) {
|
|
60700
|
+
for (const child of ast_utils_exports.streamAllContents(node)) {
|
|
60701
|
+
const kind = portionKindOf(child);
|
|
60702
|
+
if (!kind)
|
|
60703
|
+
continue;
|
|
60704
|
+
const label = declLabel(child, kind);
|
|
60705
|
+
const owner = owningTypeOf(child);
|
|
60706
|
+
if (!owner) {
|
|
60707
|
+
accept(severity("SSM015", "error"), `A '${kind}' is a portion of an occurrence's life, so ${label} must be declared inside an occurrence \u2014 not directly in a package.`, { node: child, code: "SSM015" });
|
|
60708
|
+
continue;
|
|
60709
|
+
}
|
|
60710
|
+
if (_SysmlValidator.NON_OCCURRENCE_OWNER_TYPES.has(owner.$type)) {
|
|
60711
|
+
accept(severity("SSM015", "error"), `A '${kind}' is a portion of an occurrence's life, and '${owner.name ?? owner.$type}' is not an occurrence.`, { node: child, code: "SSM015" });
|
|
60712
|
+
continue;
|
|
60713
|
+
}
|
|
60714
|
+
if (kind === "timeslice" && portionKindOf(owner) === "snapshot") {
|
|
60715
|
+
accept(severity("SSM016", "error"), `A 'snapshot' is a zero-duration timeslice, so ${label} cannot be a timeslice of it \u2014 a portion of an instant is itself a snapshot.`, { node: child, code: "SSM016" });
|
|
60716
|
+
}
|
|
60717
|
+
}
|
|
60718
|
+
}
|
|
60605
60719
|
// REQ-328 — KSM008: a `filter` membership condition must be Boolean. Only a
|
|
60606
60720
|
// *definitely* non-Boolean expression is flagged — a string or numeric/quantity
|
|
60607
60721
|
// literal, or an arithmetic/range binary expression. A bare feature path is left
|
|
@@ -61148,6 +61262,34 @@ ${baseIndent}}`;
|
|
|
61148
61262
|
}
|
|
61149
61263
|
return false;
|
|
61150
61264
|
}
|
|
61265
|
+
/**
|
|
61266
|
+
* REQ-387 — RES018: an `import` or `expose` filter the metadata-filter
|
|
61267
|
+
* evaluator cannot decide (issue #102).
|
|
61268
|
+
*
|
|
61269
|
+
* The evaluator keeps a member it cannot decide, so an unevaluated filter
|
|
61270
|
+
* silently contributes the members it was written to exclude — a scope or a
|
|
61271
|
+
* view that reads as authoritative and is not. Failing open stays (narrowing
|
|
61272
|
+
* on a guess would manufacture unresolved-name errors); what changes is that
|
|
61273
|
+
* the model now says so at the filter that is not applied.
|
|
61274
|
+
*
|
|
61275
|
+
* Default severity is `info` rather than a warning: the model is legal and the
|
|
61276
|
+
* limitation is this evaluator's, not the author's. Teams that want an
|
|
61277
|
+
* unevaluated filter to break a build raise it per code through
|
|
61278
|
+
* `sysml.validation.severities` (REQ-240).
|
|
61279
|
+
*/
|
|
61280
|
+
checkUnevaluatedFilters(imports, exposePaths, accept) {
|
|
61281
|
+
const report = (condition, subject) => {
|
|
61282
|
+
const undecidable = undecidableFilterCondition(condition);
|
|
61283
|
+
if (!undecidable)
|
|
61284
|
+
return;
|
|
61285
|
+
accept(severity("RES018", "info"), DIAGNOSTIC_MESSAGES.RES018_UNEVALUATED_FILTER(subject, undecidable.construct), { node: undecidable.node, code: "RES018" });
|
|
61286
|
+
};
|
|
61287
|
+
for (const imp of imports)
|
|
61288
|
+
for (const filter3 of imp.filters)
|
|
61289
|
+
report(filter3.condition, "Import");
|
|
61290
|
+
for (const path10 of exposePaths)
|
|
61291
|
+
report(path10.condition, "Expose");
|
|
61292
|
+
}
|
|
61151
61293
|
// REQ-324 — RES009 unused import (configurable, OFF by default)
|
|
61152
61294
|
checkUnusedImports(root3, imports, index, level, accept) {
|
|
61153
61295
|
if (imports.length === 0)
|
|
@@ -68967,7 +69109,7 @@ async function runValidation(command) {
|
|
|
68967
69109
|
}
|
|
68968
69110
|
|
|
68969
69111
|
// src/main.ts
|
|
68970
|
-
var VERSION2 = true ? "0.
|
|
69112
|
+
var VERSION2 = true ? "0.15.1" : "dev";
|
|
68971
69113
|
async function main(argv) {
|
|
68972
69114
|
const command = parseArgs(argv);
|
|
68973
69115
|
if (command.kind === "help") {
|