sysml-diagram 0.16.0 → 0.17.0
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 +406 -23
- package/out/main.js.map +4 -4
- package/package.json +1 -1
- package/resources/sysml.dimension-table.json +1 -1
package/out/main.js
CHANGED
|
@@ -161447,18 +161447,39 @@ function parseConstructions(text) {
|
|
|
161447
161447
|
function sourceOf(node) {
|
|
161448
161448
|
return node.$cstNode?.root.fullText;
|
|
161449
161449
|
}
|
|
161450
|
-
function
|
|
161451
|
-
let
|
|
161452
|
-
|
|
161450
|
+
function cleanBlockBodyLines(raw, rawOffset) {
|
|
161451
|
+
let cut = raw.length - raw.trimStart().length;
|
|
161452
|
+
let body = raw.slice(cut).trimEnd();
|
|
161453
|
+
if (body.startsWith("/*")) {
|
|
161453
161454
|
body = body.slice(2);
|
|
161455
|
+
cut += 2;
|
|
161456
|
+
}
|
|
161454
161457
|
if (body.endsWith("*/"))
|
|
161455
161458
|
body = body.slice(0, -2);
|
|
161456
|
-
const lines =
|
|
161457
|
-
|
|
161459
|
+
const lines = [];
|
|
161460
|
+
let cursor = cut;
|
|
161461
|
+
for (const rawLine of body.split("\n")) {
|
|
161462
|
+
const gutter = /^\s*\*+ ?/u.exec(rawLine);
|
|
161463
|
+
const skip = gutter ? gutter[0].length : 0;
|
|
161464
|
+
lines.push({
|
|
161465
|
+
text: rawLine.slice(skip).replace(/\s+$/u, ""),
|
|
161466
|
+
offset: rawOffset + cursor + skip
|
|
161467
|
+
});
|
|
161468
|
+
cursor += rawLine.length + 1;
|
|
161469
|
+
}
|
|
161470
|
+
while (lines.length > 0 && lines[0].text.trim() === "")
|
|
161458
161471
|
lines.shift();
|
|
161459
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === "")
|
|
161472
|
+
while (lines.length > 0 && lines[lines.length - 1].text.trim() === "")
|
|
161460
161473
|
lines.pop();
|
|
161461
|
-
|
|
161474
|
+
if (lines.length > 0) {
|
|
161475
|
+
const lead = lines[0].text.length - lines[0].text.trimStart().length;
|
|
161476
|
+
if (lead > 0)
|
|
161477
|
+
lines[0] = { text: lines[0].text.slice(lead), offset: lines[0].offset + lead };
|
|
161478
|
+
}
|
|
161479
|
+
return lines;
|
|
161480
|
+
}
|
|
161481
|
+
function cleanBlockBody(inner) {
|
|
161482
|
+
return cleanBlockBodyLines(inner, 0).map((line2) => line2.text).join("\n");
|
|
161462
161483
|
}
|
|
161463
161484
|
function docCommentBody(node) {
|
|
161464
161485
|
const raw = node.doc;
|
|
@@ -161471,7 +161492,7 @@ function docCommentBody(node) {
|
|
|
161471
161492
|
const body = cleanBlockBody(raw.slice(start2, end + 2));
|
|
161472
161493
|
return body.length > 0 ? body : void 0;
|
|
161473
161494
|
}
|
|
161474
|
-
function
|
|
161495
|
+
function trailingBlockBodySpan(node) {
|
|
161475
161496
|
const cst = node.$cstNode;
|
|
161476
161497
|
const text = sourceOf(node);
|
|
161477
161498
|
if (!cst || text === void 0)
|
|
@@ -161485,8 +161506,22 @@ function trailingBlockBody(node) {
|
|
|
161485
161506
|
const close = rest.indexOf("*/", open + 2);
|
|
161486
161507
|
if (close < 0)
|
|
161487
161508
|
return void 0;
|
|
161488
|
-
const
|
|
161489
|
-
|
|
161509
|
+
const raw = rest.slice(open, close + 2);
|
|
161510
|
+
const lines = cleanBlockBodyLines(raw, cst.end + open);
|
|
161511
|
+
const body = lines.map((line2) => line2.text).join("\n");
|
|
161512
|
+
if (body.length === 0)
|
|
161513
|
+
return void 0;
|
|
161514
|
+
return { text: body, lines, open: cst.end + open, close: cst.end + close + 2 };
|
|
161515
|
+
}
|
|
161516
|
+
function trailingBlockBody(node) {
|
|
161517
|
+
return trailingBlockBodySpan(node)?.text;
|
|
161518
|
+
}
|
|
161519
|
+
function bodyOffsetAt(span, line2, character) {
|
|
161520
|
+
const entry = Number.isInteger(line2) ? span.lines[line2] : void 0;
|
|
161521
|
+
if (!entry)
|
|
161522
|
+
return span.open;
|
|
161523
|
+
const column = Number.isFinite(character) ? character : 0;
|
|
161524
|
+
return entry.offset + Math.max(0, Math.min(column, entry.text.length));
|
|
161490
161525
|
}
|
|
161491
161526
|
|
|
161492
161527
|
// ../language-server/out/src/services/escaped-name.js
|
|
@@ -163515,10 +163550,10 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
|
|
|
163515
163550
|
addPorts(usage, id2, false);
|
|
163516
163551
|
if (self2)
|
|
163517
163552
|
addPorts(self2.usage, self2.id, true);
|
|
163518
|
-
const
|
|
163553
|
+
const bareName2 = /* @__PURE__ */ new Map();
|
|
163519
163554
|
for (const [k, v] of localPort) {
|
|
163520
163555
|
const pn = k.split(".").pop();
|
|
163521
|
-
|
|
163556
|
+
bareName2.set(pn, bareName2.has(pn) && bareName2.get(pn) !== v ? null : v);
|
|
163522
163557
|
}
|
|
163523
163558
|
return (pathText) => {
|
|
163524
163559
|
if (!pathText)
|
|
@@ -163534,7 +163569,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
|
|
|
163534
163569
|
if (localPort.has(firstLast))
|
|
163535
163570
|
return localPort.get(firstLast);
|
|
163536
163571
|
}
|
|
163537
|
-
const bare =
|
|
163572
|
+
const bare = bareName2.get(segs[segs.length - 1]);
|
|
163538
163573
|
if (bare)
|
|
163539
163574
|
return bare;
|
|
163540
163575
|
return segs.length === 1 ? localNode.get(segs[0]) : void 0;
|
|
@@ -168926,6 +168961,18 @@ var DIAGNOSTIC_MESSAGES = {
|
|
|
168926
168961
|
// `first a then b;` and the `then` occurrence shorthand in a part), so the
|
|
168927
168962
|
// message names the construct it means rather than "action language".
|
|
168928
168963
|
SEM011_ACTION_ONLY_CONSTRUCT: (construct, clause, owner) => `${construct} may only be declared in the body of an action definition or usage (OMG SysML v2 Part 1 \xA7${clause}), not in ${owner}.`,
|
|
168964
|
+
// REQ-392 — issue #150. A `rep … language "sysml"` body is a legal representation
|
|
168965
|
+
// of the element it annotates (OMG KerML §7.4.3), so the two faults are "this is
|
|
168966
|
+
// not SysML" and "this is not THIS element". The parser's own wording is carried
|
|
168967
|
+
// through verbatim, because it names the token the reader has to go and fix.
|
|
168968
|
+
SYN076_EMBEDDED_REPRESENTATION_SYNTAX: (language, detail) => `This representation declares language "${language}", so its body must be valid ${language}: ${detail}`,
|
|
168969
|
+
SEM012_REPRESENTATION_WRONG_KIND: (expected, actual) => `A textual representation must represent the element it annotates. This body represents '${actual}', but it annotates '${expected}'.`,
|
|
168970
|
+
// REQ-392 — The body is resolved in the represented element's own context, so an
|
|
168971
|
+
// unresolvable name there is the same defect it would be in ordinary code. It is
|
|
168972
|
+
// reported under its own code so a team can tune representations separately.
|
|
168973
|
+
RES020_REPRESENTATION_UNRESOLVED: (name, language) => `'${name}' cannot be resolved from here, so this ${language} representation is not a valid representation of the element it annotates.`,
|
|
168974
|
+
RES020_REPRESENTATION_PATH_SEGMENT: (segment, owner, language) => `Feature path segment '${segment}' does not exist on '${owner}', so this ${language} representation is not a valid representation of the element it annotates.`,
|
|
168975
|
+
SEM012_REPRESENTATION_WRONG_NAME: (expected, actual) => `A textual representation must represent the element it annotates. This body represents '${actual}', but it annotates '${expected}'.`,
|
|
168929
168976
|
RES016_LIBRARY_SHADOWING: (name) => `'${name}' shadows a standard-library element of the same name; the library symbol is hidden in this scope.`,
|
|
168930
168977
|
// issue #183 — KerML namespace distinguishability (validateNamespaceDistinguishability).
|
|
168931
168978
|
// Owned siblings sharing a name/short name (or an alias clashing with an owned
|
|
@@ -171923,6 +171970,193 @@ function isNamespaceOnlyDecl(node) {
|
|
|
171923
171970
|
return node.isDef === true;
|
|
171924
171971
|
}
|
|
171925
171972
|
|
|
171973
|
+
// ../language-server/out/src/services/embedded-representation.js
|
|
171974
|
+
var EMBEDDED_LANGUAGES = /* @__PURE__ */ new Set(["sysml", "kerml"]);
|
|
171975
|
+
var EXPRESSION_VALUED_OWNERS = /* @__PURE__ */ new Set([
|
|
171976
|
+
"AssertConstraintStmt",
|
|
171977
|
+
"AssumeConstraintStmt",
|
|
171978
|
+
"RequireConstraintStmt",
|
|
171979
|
+
"ConstraintDecl",
|
|
171980
|
+
"InvShorthand",
|
|
171981
|
+
"CalcDecl",
|
|
171982
|
+
"ExpressionDecl"
|
|
171983
|
+
]);
|
|
171984
|
+
function isEmbeddedLanguage(raw) {
|
|
171985
|
+
if (typeof raw !== "string")
|
|
171986
|
+
return false;
|
|
171987
|
+
return EMBEDDED_LANGUAGES.has(raw.replace(/^"|"$/gu, "").trim().toLowerCase());
|
|
171988
|
+
}
|
|
171989
|
+
function bareName(node) {
|
|
171990
|
+
const name = node?.name;
|
|
171991
|
+
return typeof name === "string" && name.length > 0 ? name.replace(/^'|'$/gu, "") : void 0;
|
|
171992
|
+
}
|
|
171993
|
+
function kindOf(node) {
|
|
171994
|
+
const decl = /^([A-Z][a-z]+)Decl$/.exec(node.$type);
|
|
171995
|
+
if (decl) {
|
|
171996
|
+
const base = decl[1].toLowerCase();
|
|
171997
|
+
return node.isDef ? `${base} def` : base;
|
|
171998
|
+
}
|
|
171999
|
+
switch (node.$type) {
|
|
172000
|
+
case "Package":
|
|
172001
|
+
return "package";
|
|
172002
|
+
case "InvShorthand":
|
|
172003
|
+
return "inv";
|
|
172004
|
+
case "AssertConstraintStmt":
|
|
172005
|
+
return "assert constraint";
|
|
172006
|
+
case "AssumeConstraintStmt":
|
|
172007
|
+
return "assume constraint";
|
|
172008
|
+
case "RequireConstraintStmt":
|
|
172009
|
+
return "require constraint";
|
|
172010
|
+
default:
|
|
172011
|
+
return node.$type;
|
|
172012
|
+
}
|
|
172013
|
+
}
|
|
172014
|
+
function hasErrors(result) {
|
|
172015
|
+
return result.lexerErrors.length > 0 || result.parserErrors.length > 0;
|
|
172016
|
+
}
|
|
172017
|
+
function describeParserError(error) {
|
|
172018
|
+
const image = error.token?.image ?? "";
|
|
172019
|
+
const expecting = /Expecting token of type '([^']+)'/u.exec(error.message ?? "");
|
|
172020
|
+
if (image === "") {
|
|
172021
|
+
return expecting ? `the body ends while '${expecting[1]}' is still expected.` : "the body ends before it is complete.";
|
|
172022
|
+
}
|
|
172023
|
+
if (error.name === "NotAllInputParsedException") {
|
|
172024
|
+
return `'${image}' is left over after the representation is already complete.`;
|
|
172025
|
+
}
|
|
172026
|
+
if (expecting)
|
|
172027
|
+
return `expected '${expecting[1]}' but found '${image}'.`;
|
|
172028
|
+
return `unexpected '${image}'.`;
|
|
172029
|
+
}
|
|
172030
|
+
function describeLexerError(error) {
|
|
172031
|
+
const character = /unexpected character: ->(.*?)<-/u.exec(error.message ?? "");
|
|
172032
|
+
return character ? `unexpected character '${character[1]}'.` : error.message ?? "the body cannot be read.";
|
|
172033
|
+
}
|
|
172034
|
+
function failureOf(result, span) {
|
|
172035
|
+
const endLine = Math.max(0, span.lines.length - 1);
|
|
172036
|
+
const end = { line: endLine, character: span.lines[endLine]?.text.length ?? 0 };
|
|
172037
|
+
const lexical = result.lexerErrors[0];
|
|
172038
|
+
const syntactic = result.parserErrors[0];
|
|
172039
|
+
if (lexical && Number.isFinite(lexical.line)) {
|
|
172040
|
+
return {
|
|
172041
|
+
line: lexical.line - 1,
|
|
172042
|
+
character: Math.max(0, (Number.isFinite(lexical.column) ? lexical.column : 1) - 1),
|
|
172043
|
+
message: describeLexerError(lexical)
|
|
172044
|
+
};
|
|
172045
|
+
}
|
|
172046
|
+
if (syntactic) {
|
|
172047
|
+
const token = syntactic.token;
|
|
172048
|
+
const message = describeParserError(syntactic);
|
|
172049
|
+
if (Number.isFinite(token?.startLine) && Number.isFinite(token?.startColumn)) {
|
|
172050
|
+
return { line: token.startLine - 1, character: token.startColumn - 1, message };
|
|
172051
|
+
}
|
|
172052
|
+
return { ...end, message };
|
|
172053
|
+
}
|
|
172054
|
+
return { ...end, message: lexical ? describeLexerError(lexical) : "the body is not valid SysML." };
|
|
172055
|
+
}
|
|
172056
|
+
function conformance(parsed, owner) {
|
|
172057
|
+
if (!parsed || !owner || owner.$type === "Document")
|
|
172058
|
+
return { status: "ok" };
|
|
172059
|
+
const sameKind = parsed.$type === owner.$type && Boolean(parsed.isDef) === Boolean(owner.isDef);
|
|
172060
|
+
if (!sameKind) {
|
|
172061
|
+
return { status: "mismatch", mismatch: { reason: "kind", expected: kindOf(owner), actual: kindOf(parsed) } };
|
|
172062
|
+
}
|
|
172063
|
+
const expected = bareName(owner);
|
|
172064
|
+
const actual = bareName(parsed);
|
|
172065
|
+
if (expected && actual && expected !== actual) {
|
|
172066
|
+
return { status: "mismatch", mismatch: { reason: "name", expected, actual } };
|
|
172067
|
+
}
|
|
172068
|
+
return { status: "ok" };
|
|
172069
|
+
}
|
|
172070
|
+
function nodesOf(root4) {
|
|
172071
|
+
return [root4, ...ast_utils_exports.streamAllContents(root4)];
|
|
172072
|
+
}
|
|
172073
|
+
function declaredNames(root4) {
|
|
172074
|
+
const names = /* @__PURE__ */ new Set();
|
|
172075
|
+
for (const node of nodesOf(root4)) {
|
|
172076
|
+
const name = node.name;
|
|
172077
|
+
if (typeof name === "string" && name.length > 0)
|
|
172078
|
+
names.add(name.replace(/^'|'$/gu, ""));
|
|
172079
|
+
}
|
|
172080
|
+
return names;
|
|
172081
|
+
}
|
|
172082
|
+
function graftInto(fragment, owner) {
|
|
172083
|
+
const mutable = fragment;
|
|
172084
|
+
mutable.$container = owner;
|
|
172085
|
+
mutable.$containerProperty = "members";
|
|
172086
|
+
}
|
|
172087
|
+
function firstSegment(refText) {
|
|
172088
|
+
return (refText.split("::")[0] ?? refText).replace(/^'|'$/gu, "").trim();
|
|
172089
|
+
}
|
|
172090
|
+
function unresolvedReferences(fragment, owner) {
|
|
172091
|
+
graftInto(fragment, owner);
|
|
172092
|
+
const own = declaredNames(fragment);
|
|
172093
|
+
const out = [];
|
|
172094
|
+
for (const node of nodesOf(fragment)) {
|
|
172095
|
+
for (const [property3, value] of Object.entries(node)) {
|
|
172096
|
+
if (property3.startsWith("$"))
|
|
172097
|
+
continue;
|
|
172098
|
+
for (const candidate of Array.isArray(value) ? value : [value]) {
|
|
172099
|
+
if (!isReference(candidate))
|
|
172100
|
+
continue;
|
|
172101
|
+
const reference = candidate;
|
|
172102
|
+
const refText = reference.$refText;
|
|
172103
|
+
if (!refText || own.has(firstSegment(refText)))
|
|
172104
|
+
continue;
|
|
172105
|
+
let resolved;
|
|
172106
|
+
try {
|
|
172107
|
+
resolved = reference.ref;
|
|
172108
|
+
} catch {
|
|
172109
|
+
return [];
|
|
172110
|
+
}
|
|
172111
|
+
if (resolved)
|
|
172112
|
+
continue;
|
|
172113
|
+
const range = reference.$refNode?.range;
|
|
172114
|
+
if (!range)
|
|
172115
|
+
continue;
|
|
172116
|
+
out.push({
|
|
172117
|
+
refText,
|
|
172118
|
+
line: range.start.line,
|
|
172119
|
+
character: range.start.character,
|
|
172120
|
+
length: Math.max(1, range.end.character - range.start.character)
|
|
172121
|
+
});
|
|
172122
|
+
}
|
|
172123
|
+
}
|
|
172124
|
+
}
|
|
172125
|
+
return out;
|
|
172126
|
+
}
|
|
172127
|
+
function evaluateRepresentation(parser, rep) {
|
|
172128
|
+
if (!isEmbeddedLanguage(rep.language))
|
|
172129
|
+
return void 0;
|
|
172130
|
+
const span = trailingBlockBodySpan(rep);
|
|
172131
|
+
if (!span)
|
|
172132
|
+
return void 0;
|
|
172133
|
+
const owner = rep.$container;
|
|
172134
|
+
const context = owner && owner.$type !== "Document" ? owner : void 0;
|
|
172135
|
+
const declaration = parser.parse(span.text, { rule: "NamespaceElement" });
|
|
172136
|
+
if (!hasErrors(declaration)) {
|
|
172137
|
+
return {
|
|
172138
|
+
span,
|
|
172139
|
+
verdict: conformance(declaration.value, owner),
|
|
172140
|
+
unresolved: context ? unresolvedReferences(declaration.value, context) : [],
|
|
172141
|
+
fragment: declaration.value,
|
|
172142
|
+
tier: "declaration"
|
|
172143
|
+
};
|
|
172144
|
+
}
|
|
172145
|
+
if (owner && EXPRESSION_VALUED_OWNERS.has(owner.$type)) {
|
|
172146
|
+
const expression = parser.parse(span.text, { rule: "Expr" });
|
|
172147
|
+
if (!hasErrors(expression)) {
|
|
172148
|
+
return {
|
|
172149
|
+
span,
|
|
172150
|
+
verdict: { status: "ok" },
|
|
172151
|
+
unresolved: context ? unresolvedReferences(expression.value, context) : [],
|
|
172152
|
+
fragment: expression.value,
|
|
172153
|
+
tier: "expression"
|
|
172154
|
+
};
|
|
172155
|
+
}
|
|
172156
|
+
}
|
|
172157
|
+
return { span, verdict: { status: "unparsed", failure: failureOf(declaration, span) }, unresolved: [] };
|
|
172158
|
+
}
|
|
172159
|
+
|
|
171926
172160
|
// ../language-server/out/src/services/conformance.js
|
|
171927
172161
|
var SPECIALIZATION_KINDS3 = /* @__PURE__ */ new Set([
|
|
171928
172162
|
":>",
|
|
@@ -172358,7 +172592,14 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
172358
172592
|
langiumDocuments;
|
|
172359
172593
|
astNodeLocator;
|
|
172360
172594
|
featurePaths;
|
|
172595
|
+
// REQ-396 — One SysmlValidator instance is registered against BOTH the SysML and
|
|
172596
|
+
// the KerML services (sysml-module.ts), so the parser used to read an embedded
|
|
172597
|
+
// representation is looked up from the document being validated rather than
|
|
172598
|
+
// captured here. The two share one grammar today; this keeps that from becoming
|
|
172599
|
+
// load-bearing.
|
|
172600
|
+
serviceRegistry;
|
|
172361
172601
|
constructor(services) {
|
|
172602
|
+
this.serviceRegistry = services.shared.ServiceRegistry;
|
|
172362
172603
|
this.indexManager = services.shared.workspace.IndexManager;
|
|
172363
172604
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
172364
172605
|
this.astNodeLocator = services.workspace.AstNodeLocator;
|
|
@@ -172374,23 +172615,38 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
172374
172615
|
// Langium linker cannot diagnose a missing intermediate feature. Resolve the
|
|
172375
172616
|
// chain here and underline only the first invalid segment.
|
|
172376
172617
|
checkPathExpr(node, accept) {
|
|
172618
|
+
const fault = this.featurePathFault(node);
|
|
172619
|
+
if (!fault)
|
|
172620
|
+
return;
|
|
172621
|
+
accept(severity("RES001", "error"), DIAGNOSTIC_MESSAGES.RES001_FEATURE_PATH_SEGMENT(fault.segment, fault.owner), {
|
|
172622
|
+
node,
|
|
172623
|
+
range: fault.range,
|
|
172624
|
+
code: "RES001",
|
|
172625
|
+
data: { featurePathSegment: true }
|
|
172626
|
+
});
|
|
172627
|
+
}
|
|
172628
|
+
// REQ-317, REQ-396 — The one place that decides whether a written feature path
|
|
172629
|
+
// has a provably absent segment. Extracted so an embedded representation body is
|
|
172630
|
+
// judged by exactly this rule (issue #150 review): a body is grafted onto the
|
|
172631
|
+
// element it represents and is therefore unreachable from the document walk that
|
|
172632
|
+
// dispatches `checkPathExpr`, so without a shared decision the two would drift
|
|
172633
|
+
// and a body would quietly escape a diagnostic the surrounding model receives.
|
|
172634
|
+
featurePathFault(node) {
|
|
172377
172635
|
let declaration = node.$container;
|
|
172378
172636
|
while (declaration && !isPartDecl(declaration))
|
|
172379
172637
|
declaration = declaration.$container;
|
|
172380
172638
|
const modifiers2 = declaration?.modifiers ?? [];
|
|
172381
172639
|
if (!declaration || !modifiers2.includes("ref"))
|
|
172382
|
-
return;
|
|
172640
|
+
return void 0;
|
|
172383
172641
|
const resolution = this.featurePaths.resolve(node);
|
|
172384
172642
|
if (resolution.unresolvedIndex === void 0)
|
|
172385
|
-
return;
|
|
172643
|
+
return void 0;
|
|
172386
172644
|
const invalid = resolution.segments[resolution.unresolvedIndex];
|
|
172387
|
-
|
|
172388
|
-
|
|
172389
|
-
|
|
172390
|
-
range: invalid.cst.range
|
|
172391
|
-
|
|
172392
|
-
data: { featurePathSegment: true }
|
|
172393
|
-
});
|
|
172645
|
+
return {
|
|
172646
|
+
segment: invalid.text,
|
|
172647
|
+
owner: resolution.unresolvedIndex > 0 ? resolution.segments[resolution.unresolvedIndex - 1].text : "the current scope",
|
|
172648
|
+
range: invalid.cst.range
|
|
172649
|
+
};
|
|
172394
172650
|
}
|
|
172395
172651
|
// REQ-389 — Namespace qualification versus feature chaining in every written path
|
|
172396
172652
|
// issue #213 — RES019: `::` binds tighter than `.`. Each dot-separated link of
|
|
@@ -172621,6 +172877,7 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
172621
172877
|
this.checkConnectorForms(node, accept);
|
|
172622
172878
|
this.checkActionStartSuccessions(node, accept);
|
|
172623
172879
|
this.checkAnnotationForms(node, accept);
|
|
172880
|
+
this.checkEmbeddedRepresentations(node, accept);
|
|
172624
172881
|
this.checkLanguageDisjointness(node, accept);
|
|
172625
172882
|
this.checkKermlWellFormedness(node, accept);
|
|
172626
172883
|
this.checkFilterExpressions(node, accept);
|
|
@@ -172961,6 +173218,132 @@ ${baseIndent}}`;
|
|
|
172961
173218
|
}
|
|
172962
173219
|
}
|
|
172963
173220
|
}
|
|
173221
|
+
// REQ-396 — SYN076/SEM012 embedded textual representations (issue #150).
|
|
173222
|
+
// A `rep … language "sysml"` (or `"kerml"`, matched case-insensitively) is not
|
|
173223
|
+
// opaque content: OMG KerML §7.4.3 requires its body to be a legal representation
|
|
173224
|
+
// of the element it annotates. So the body is processed by the comment-body rules,
|
|
173225
|
+
// parsed, and compared against that element:
|
|
173226
|
+
// SYN076 — the body is not valid SysML, reported ON the offending character
|
|
173227
|
+
// inside the body rather than on the `rep` keyword.
|
|
173228
|
+
// SEM012 — the body parses but represents a different element (wrong kind, or
|
|
173229
|
+
// the right kind under a different name).
|
|
173230
|
+
// Every other language stays opaque and is never parsed — an `alf`, `ocl` or
|
|
173231
|
+
// `yaml` body is content, and the vendored OMG corpus holds only those.
|
|
173232
|
+
checkEmbeddedRepresentations(node, accept) {
|
|
173233
|
+
const doc = ast_utils_exports.getDocument(node);
|
|
173234
|
+
if (!doc?.textDocument)
|
|
173235
|
+
return;
|
|
173236
|
+
let parser;
|
|
173237
|
+
for (const child of ast_utils_exports.streamAllContents(node)) {
|
|
173238
|
+
if (child.$type !== "RepStmt")
|
|
173239
|
+
continue;
|
|
173240
|
+
if (!isEmbeddedLanguage(child.language))
|
|
173241
|
+
continue;
|
|
173242
|
+
parser ??= this.serviceRegistry.getServices(doc.uri).parser.LangiumParser;
|
|
173243
|
+
const evaluated = evaluateRepresentation(parser, child);
|
|
173244
|
+
if (!evaluated)
|
|
173245
|
+
continue;
|
|
173246
|
+
const { span, verdict } = evaluated;
|
|
173247
|
+
const language = String(child.language ?? "").replace(/^"|"$/gu, "");
|
|
173248
|
+
if (verdict.status === "unparsed") {
|
|
173249
|
+
const { line: line2, character, message } = verdict.failure;
|
|
173250
|
+
const anchor = bodyOffsetAt(span, line2, character);
|
|
173251
|
+
const after = bodyOffsetAt(span, line2, character + 1);
|
|
173252
|
+
const from = after > anchor ? anchor : bodyOffsetAt(span, line2, Math.max(0, character - 1));
|
|
173253
|
+
accept(severity("SYN076", "error"), DIAGNOSTIC_MESSAGES.SYN076_EMBEDDED_REPRESENTATION_SYNTAX(language, message), {
|
|
173254
|
+
node: child,
|
|
173255
|
+
range: {
|
|
173256
|
+
start: doc.textDocument.positionAt(from),
|
|
173257
|
+
end: doc.textDocument.positionAt(Math.max(after, anchor))
|
|
173258
|
+
},
|
|
173259
|
+
code: "SYN076"
|
|
173260
|
+
});
|
|
173261
|
+
continue;
|
|
173262
|
+
}
|
|
173263
|
+
for (const reference of evaluated.unresolved) {
|
|
173264
|
+
accept(severity("RES020", "warning"), DIAGNOSTIC_MESSAGES.RES020_REPRESENTATION_UNRESOLVED(reference.refText, language), {
|
|
173265
|
+
node: child,
|
|
173266
|
+
range: {
|
|
173267
|
+
start: doc.textDocument.positionAt(bodyOffsetAt(span, reference.line, reference.character)),
|
|
173268
|
+
end: doc.textDocument.positionAt(bodyOffsetAt(span, reference.line, reference.character + reference.length))
|
|
173269
|
+
},
|
|
173270
|
+
code: "RES020"
|
|
173271
|
+
});
|
|
173272
|
+
}
|
|
173273
|
+
if (evaluated.fragment) {
|
|
173274
|
+
this.checkEmbeddedDialect(child, evaluated.fragment, language, span, doc, accept);
|
|
173275
|
+
this.checkEmbeddedFeaturePaths(child, evaluated.fragment, language, span, doc, accept);
|
|
173276
|
+
}
|
|
173277
|
+
if (verdict.status === "ok")
|
|
173278
|
+
continue;
|
|
173279
|
+
const { reason, expected, actual } = verdict.mismatch;
|
|
173280
|
+
const owner = child.$container;
|
|
173281
|
+
accept(severity("SEM012", "warning"), reason === "kind" ? DIAGNOSTIC_MESSAGES.SEM012_REPRESENTATION_WRONG_KIND(expected, actual) : DIAGNOSTIC_MESSAGES.SEM012_REPRESENTATION_WRONG_NAME(expected, actual), {
|
|
173282
|
+
node: child,
|
|
173283
|
+
range: {
|
|
173284
|
+
start: doc.textDocument.positionAt(bodyOffsetAt(span, 0, 0)),
|
|
173285
|
+
end: doc.textDocument.positionAt(bodyOffsetAt(span, 0, span.lines[0]?.text.length ?? 0))
|
|
173286
|
+
},
|
|
173287
|
+
code: "SEM012",
|
|
173288
|
+
relatedInformation: owner ? [relatedInfo(owner, "The element this representation annotates.")].filter((info) => info !== void 0) : void 0
|
|
173289
|
+
});
|
|
173290
|
+
}
|
|
173291
|
+
}
|
|
173292
|
+
// REQ-396 — SYN100/SYN101 inside a representation body (issue #150 review).
|
|
173293
|
+
// The normative rule is that the body is legal in the language the representation
|
|
173294
|
+
// NAMES, which is not the language of the file it sits in: a `.sysml` file may
|
|
173295
|
+
// carry a `language "kerml"` representation, and its body may then use no
|
|
173296
|
+
// SysML-only construct. `checkLanguageDisjointness` answers a different question
|
|
173297
|
+
// — what may this FILE contain — from the host URI, so the same tables are read
|
|
173298
|
+
// here against the declared language instead.
|
|
173299
|
+
checkEmbeddedDialect(rep, fragment, language, span, doc, accept) {
|
|
173300
|
+
const dialect = language.trim().toLowerCase();
|
|
173301
|
+
const isKerml = dialect === "kerml";
|
|
173302
|
+
const forbidden = isKerml ? SYSML_ONLY_TYPES : KERML_ONLY_TYPES;
|
|
173303
|
+
const code = isKerml ? "SYN100" : "SYN101";
|
|
173304
|
+
for (const node of [fragment, ...ast_utils_exports.streamAllContents(fragment)]) {
|
|
173305
|
+
if (!forbidden.has(node.$type))
|
|
173306
|
+
continue;
|
|
173307
|
+
const range = node.$cstNode?.range;
|
|
173308
|
+
if (!range)
|
|
173309
|
+
continue;
|
|
173310
|
+
accept(severity(code, "error"), isKerml ? `'${declKeyword(node)}' is a SysML construct and is not available in a "${language}" representation.` : `'${declKeyword(node)}' is a KerML-only construct \u2014 use the SysML form in a "${language}" representation.`, {
|
|
173311
|
+
node: rep,
|
|
173312
|
+
range: this.bodyRange(span, doc, range),
|
|
173313
|
+
code
|
|
173314
|
+
});
|
|
173315
|
+
}
|
|
173316
|
+
}
|
|
173317
|
+
// REQ-396 — RES020 for a written feature path with a provably absent segment
|
|
173318
|
+
// (issue #150 review). `checkPathExpr` is registered as a node-level check, so
|
|
173319
|
+
// Langium dispatches it by walking the document AST and never reaches the grafted
|
|
173320
|
+
// fragment. The decision itself is shared with it through `featurePathFault`, so
|
|
173321
|
+
// a body is held to the same rule as the model around it rather than a parallel
|
|
173322
|
+
// one that could drift; only the code differs, keeping every fault found IN a
|
|
173323
|
+
// representation body under the one severity a team can tune.
|
|
173324
|
+
checkEmbeddedFeaturePaths(rep, fragment, language, span, doc, accept) {
|
|
173325
|
+
for (const node of [fragment, ...ast_utils_exports.streamAllContents(fragment)]) {
|
|
173326
|
+
if (!this.featurePaths.isPathExpr(node))
|
|
173327
|
+
continue;
|
|
173328
|
+
const fault = this.featurePathFault(node);
|
|
173329
|
+
if (!fault)
|
|
173330
|
+
continue;
|
|
173331
|
+
accept(severity("RES020", "warning"), DIAGNOSTIC_MESSAGES.RES020_REPRESENTATION_PATH_SEGMENT(fault.segment, fault.owner, language), {
|
|
173332
|
+
node: rep,
|
|
173333
|
+
range: this.bodyRange(span, doc, fault.range),
|
|
173334
|
+
code: "RES020"
|
|
173335
|
+
});
|
|
173336
|
+
}
|
|
173337
|
+
}
|
|
173338
|
+
// REQ-396 — A range inside a parsed body, expressed in document coordinates.
|
|
173339
|
+
// Fragment CST positions are line/character within the PROCESSED body, which is
|
|
173340
|
+
// exactly what the span's line table converts.
|
|
173341
|
+
bodyRange(span, doc, range) {
|
|
173342
|
+
return {
|
|
173343
|
+
start: doc.textDocument.positionAt(bodyOffsetAt(span, range.start.line, range.start.character)),
|
|
173344
|
+
end: doc.textDocument.positionAt(bodyOffsetAt(span, range.end.line, range.end.character))
|
|
173345
|
+
};
|
|
173346
|
+
}
|
|
172964
173347
|
// REQ-311 — SYN050/051 connector syntax, with a specific message in place of
|
|
172965
173348
|
// the grammar's generic parse error:
|
|
172966
173349
|
// SYN050 — `connect a with b;` (use `connect a to b;`).
|
|
@@ -197539,7 +197922,7 @@ async function runExport(command) {
|
|
|
197539
197922
|
}
|
|
197540
197923
|
|
|
197541
197924
|
// src/main.ts
|
|
197542
|
-
var VERSION2 = true ? "0.
|
|
197925
|
+
var VERSION2 = true ? "0.17.0" : "dev";
|
|
197543
197926
|
function display(file) {
|
|
197544
197927
|
const rel2 = path9.relative(process.cwd(), file);
|
|
197545
197928
|
return rel2 && !rel2.startsWith("..") ? rel2.split(path9.sep).join("/") : file;
|