sysml-validate 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 +439 -56
- 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
|
@@ -58649,6 +58649,18 @@ var DIAGNOSTIC_MESSAGES = {
|
|
|
58649
58649
|
// `first a then b;` and the `then` occurrence shorthand in a part), so the
|
|
58650
58650
|
// message names the construct it means rather than "action language".
|
|
58651
58651
|
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}.`,
|
|
58652
|
+
// REQ-392 — issue #150. A `rep … language "sysml"` body is a legal representation
|
|
58653
|
+
// of the element it annotates (OMG KerML §7.4.3), so the two faults are "this is
|
|
58654
|
+
// not SysML" and "this is not THIS element". The parser's own wording is carried
|
|
58655
|
+
// through verbatim, because it names the token the reader has to go and fix.
|
|
58656
|
+
SYN076_EMBEDDED_REPRESENTATION_SYNTAX: (language, detail) => `This representation declares language "${language}", so its body must be valid ${language}: ${detail}`,
|
|
58657
|
+
SEM012_REPRESENTATION_WRONG_KIND: (expected, actual) => `A textual representation must represent the element it annotates. This body represents '${actual}', but it annotates '${expected}'.`,
|
|
58658
|
+
// REQ-392 — The body is resolved in the represented element's own context, so an
|
|
58659
|
+
// unresolvable name there is the same defect it would be in ordinary code. It is
|
|
58660
|
+
// reported under its own code so a team can tune representations separately.
|
|
58661
|
+
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.`,
|
|
58662
|
+
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.`,
|
|
58663
|
+
SEM012_REPRESENTATION_WRONG_NAME: (expected, actual) => `A textual representation must represent the element it annotates. This body represents '${actual}', but it annotates '${expected}'.`,
|
|
58652
58664
|
RES016_LIBRARY_SHADOWING: (name) => `'${name}' shadows a standard-library element of the same name; the library symbol is hidden in this scope.`,
|
|
58653
58665
|
// issue #183 — KerML namespace distinguishability (validateNamespaceDistinguishability).
|
|
58654
58666
|
// Owned siblings sharing a name/short name (or an alias clashing with an owned
|
|
@@ -59596,6 +59608,274 @@ function isNamespaceOnlyDecl(node) {
|
|
|
59596
59608
|
return node.isDef === true;
|
|
59597
59609
|
}
|
|
59598
59610
|
|
|
59611
|
+
// ../language-server/out/src/services/comment-body.js
|
|
59612
|
+
function sourceOf(node) {
|
|
59613
|
+
return node.$cstNode?.root.fullText;
|
|
59614
|
+
}
|
|
59615
|
+
function cleanBlockBodyLines(raw, rawOffset) {
|
|
59616
|
+
let cut = raw.length - raw.trimStart().length;
|
|
59617
|
+
let body = raw.slice(cut).trimEnd();
|
|
59618
|
+
if (body.startsWith("/*")) {
|
|
59619
|
+
body = body.slice(2);
|
|
59620
|
+
cut += 2;
|
|
59621
|
+
}
|
|
59622
|
+
if (body.endsWith("*/"))
|
|
59623
|
+
body = body.slice(0, -2);
|
|
59624
|
+
const lines = [];
|
|
59625
|
+
let cursor = cut;
|
|
59626
|
+
for (const rawLine of body.split("\n")) {
|
|
59627
|
+
const gutter = /^\s*\*+ ?/u.exec(rawLine);
|
|
59628
|
+
const skip = gutter ? gutter[0].length : 0;
|
|
59629
|
+
lines.push({
|
|
59630
|
+
text: rawLine.slice(skip).replace(/\s+$/u, ""),
|
|
59631
|
+
offset: rawOffset + cursor + skip
|
|
59632
|
+
});
|
|
59633
|
+
cursor += rawLine.length + 1;
|
|
59634
|
+
}
|
|
59635
|
+
while (lines.length > 0 && lines[0].text.trim() === "")
|
|
59636
|
+
lines.shift();
|
|
59637
|
+
while (lines.length > 0 && lines[lines.length - 1].text.trim() === "")
|
|
59638
|
+
lines.pop();
|
|
59639
|
+
if (lines.length > 0) {
|
|
59640
|
+
const lead = lines[0].text.length - lines[0].text.trimStart().length;
|
|
59641
|
+
if (lead > 0)
|
|
59642
|
+
lines[0] = { text: lines[0].text.slice(lead), offset: lines[0].offset + lead };
|
|
59643
|
+
}
|
|
59644
|
+
return lines;
|
|
59645
|
+
}
|
|
59646
|
+
function cleanBlockBody(inner) {
|
|
59647
|
+
return cleanBlockBodyLines(inner, 0).map((line) => line.text).join("\n");
|
|
59648
|
+
}
|
|
59649
|
+
function docCommentBody(node) {
|
|
59650
|
+
const raw = node.doc;
|
|
59651
|
+
if (typeof raw !== "string")
|
|
59652
|
+
return void 0;
|
|
59653
|
+
const start = raw.indexOf("/*");
|
|
59654
|
+
const end = raw.lastIndexOf("*/");
|
|
59655
|
+
if (start < 0 || end < start)
|
|
59656
|
+
return void 0;
|
|
59657
|
+
const body = cleanBlockBody(raw.slice(start, end + 2));
|
|
59658
|
+
return body.length > 0 ? body : void 0;
|
|
59659
|
+
}
|
|
59660
|
+
function trailingBlockBodySpan(node) {
|
|
59661
|
+
const cst = node.$cstNode;
|
|
59662
|
+
const text = sourceOf(node);
|
|
59663
|
+
if (!cst || text === void 0)
|
|
59664
|
+
return void 0;
|
|
59665
|
+
const rest = text.slice(cst.end);
|
|
59666
|
+
const open = rest.indexOf("/*");
|
|
59667
|
+
if (open < 0)
|
|
59668
|
+
return void 0;
|
|
59669
|
+
if (rest.slice(0, open).trim() !== "")
|
|
59670
|
+
return void 0;
|
|
59671
|
+
const close = rest.indexOf("*/", open + 2);
|
|
59672
|
+
if (close < 0)
|
|
59673
|
+
return void 0;
|
|
59674
|
+
const raw = rest.slice(open, close + 2);
|
|
59675
|
+
const lines = cleanBlockBodyLines(raw, cst.end + open);
|
|
59676
|
+
const body = lines.map((line) => line.text).join("\n");
|
|
59677
|
+
if (body.length === 0)
|
|
59678
|
+
return void 0;
|
|
59679
|
+
return { text: body, lines, open: cst.end + open, close: cst.end + close + 2 };
|
|
59680
|
+
}
|
|
59681
|
+
function trailingBlockBody(node) {
|
|
59682
|
+
return trailingBlockBodySpan(node)?.text;
|
|
59683
|
+
}
|
|
59684
|
+
function bodyOffsetAt(span, line, character) {
|
|
59685
|
+
const entry = Number.isInteger(line) ? span.lines[line] : void 0;
|
|
59686
|
+
if (!entry)
|
|
59687
|
+
return span.open;
|
|
59688
|
+
const column = Number.isFinite(character) ? character : 0;
|
|
59689
|
+
return entry.offset + Math.max(0, Math.min(column, entry.text.length));
|
|
59690
|
+
}
|
|
59691
|
+
|
|
59692
|
+
// ../language-server/out/src/services/embedded-representation.js
|
|
59693
|
+
var EMBEDDED_LANGUAGES = /* @__PURE__ */ new Set(["sysml", "kerml"]);
|
|
59694
|
+
var EXPRESSION_VALUED_OWNERS = /* @__PURE__ */ new Set([
|
|
59695
|
+
"AssertConstraintStmt",
|
|
59696
|
+
"AssumeConstraintStmt",
|
|
59697
|
+
"RequireConstraintStmt",
|
|
59698
|
+
"ConstraintDecl",
|
|
59699
|
+
"InvShorthand",
|
|
59700
|
+
"CalcDecl",
|
|
59701
|
+
"ExpressionDecl"
|
|
59702
|
+
]);
|
|
59703
|
+
function isEmbeddedLanguage(raw) {
|
|
59704
|
+
if (typeof raw !== "string")
|
|
59705
|
+
return false;
|
|
59706
|
+
return EMBEDDED_LANGUAGES.has(raw.replace(/^"|"$/gu, "").trim().toLowerCase());
|
|
59707
|
+
}
|
|
59708
|
+
function bareName(node) {
|
|
59709
|
+
const name = node?.name;
|
|
59710
|
+
return typeof name === "string" && name.length > 0 ? name.replace(/^'|'$/gu, "") : void 0;
|
|
59711
|
+
}
|
|
59712
|
+
function kindOf(node) {
|
|
59713
|
+
const decl = /^([A-Z][a-z]+)Decl$/.exec(node.$type);
|
|
59714
|
+
if (decl) {
|
|
59715
|
+
const base = decl[1].toLowerCase();
|
|
59716
|
+
return node.isDef ? `${base} def` : base;
|
|
59717
|
+
}
|
|
59718
|
+
switch (node.$type) {
|
|
59719
|
+
case "Package":
|
|
59720
|
+
return "package";
|
|
59721
|
+
case "InvShorthand":
|
|
59722
|
+
return "inv";
|
|
59723
|
+
case "AssertConstraintStmt":
|
|
59724
|
+
return "assert constraint";
|
|
59725
|
+
case "AssumeConstraintStmt":
|
|
59726
|
+
return "assume constraint";
|
|
59727
|
+
case "RequireConstraintStmt":
|
|
59728
|
+
return "require constraint";
|
|
59729
|
+
default:
|
|
59730
|
+
return node.$type;
|
|
59731
|
+
}
|
|
59732
|
+
}
|
|
59733
|
+
function hasErrors(result) {
|
|
59734
|
+
return result.lexerErrors.length > 0 || result.parserErrors.length > 0;
|
|
59735
|
+
}
|
|
59736
|
+
function describeParserError(error) {
|
|
59737
|
+
const image = error.token?.image ?? "";
|
|
59738
|
+
const expecting = /Expecting token of type '([^']+)'/u.exec(error.message ?? "");
|
|
59739
|
+
if (image === "") {
|
|
59740
|
+
return expecting ? `the body ends while '${expecting[1]}' is still expected.` : "the body ends before it is complete.";
|
|
59741
|
+
}
|
|
59742
|
+
if (error.name === "NotAllInputParsedException") {
|
|
59743
|
+
return `'${image}' is left over after the representation is already complete.`;
|
|
59744
|
+
}
|
|
59745
|
+
if (expecting)
|
|
59746
|
+
return `expected '${expecting[1]}' but found '${image}'.`;
|
|
59747
|
+
return `unexpected '${image}'.`;
|
|
59748
|
+
}
|
|
59749
|
+
function describeLexerError(error) {
|
|
59750
|
+
const character = /unexpected character: ->(.*?)<-/u.exec(error.message ?? "");
|
|
59751
|
+
return character ? `unexpected character '${character[1]}'.` : error.message ?? "the body cannot be read.";
|
|
59752
|
+
}
|
|
59753
|
+
function failureOf(result, span) {
|
|
59754
|
+
const endLine = Math.max(0, span.lines.length - 1);
|
|
59755
|
+
const end = { line: endLine, character: span.lines[endLine]?.text.length ?? 0 };
|
|
59756
|
+
const lexical = result.lexerErrors[0];
|
|
59757
|
+
const syntactic = result.parserErrors[0];
|
|
59758
|
+
if (lexical && Number.isFinite(lexical.line)) {
|
|
59759
|
+
return {
|
|
59760
|
+
line: lexical.line - 1,
|
|
59761
|
+
character: Math.max(0, (Number.isFinite(lexical.column) ? lexical.column : 1) - 1),
|
|
59762
|
+
message: describeLexerError(lexical)
|
|
59763
|
+
};
|
|
59764
|
+
}
|
|
59765
|
+
if (syntactic) {
|
|
59766
|
+
const token = syntactic.token;
|
|
59767
|
+
const message = describeParserError(syntactic);
|
|
59768
|
+
if (Number.isFinite(token?.startLine) && Number.isFinite(token?.startColumn)) {
|
|
59769
|
+
return { line: token.startLine - 1, character: token.startColumn - 1, message };
|
|
59770
|
+
}
|
|
59771
|
+
return { ...end, message };
|
|
59772
|
+
}
|
|
59773
|
+
return { ...end, message: lexical ? describeLexerError(lexical) : "the body is not valid SysML." };
|
|
59774
|
+
}
|
|
59775
|
+
function conformance(parsed, owner) {
|
|
59776
|
+
if (!parsed || !owner || owner.$type === "Document")
|
|
59777
|
+
return { status: "ok" };
|
|
59778
|
+
const sameKind = parsed.$type === owner.$type && Boolean(parsed.isDef) === Boolean(owner.isDef);
|
|
59779
|
+
if (!sameKind) {
|
|
59780
|
+
return { status: "mismatch", mismatch: { reason: "kind", expected: kindOf(owner), actual: kindOf(parsed) } };
|
|
59781
|
+
}
|
|
59782
|
+
const expected = bareName(owner);
|
|
59783
|
+
const actual = bareName(parsed);
|
|
59784
|
+
if (expected && actual && expected !== actual) {
|
|
59785
|
+
return { status: "mismatch", mismatch: { reason: "name", expected, actual } };
|
|
59786
|
+
}
|
|
59787
|
+
return { status: "ok" };
|
|
59788
|
+
}
|
|
59789
|
+
function nodesOf(root3) {
|
|
59790
|
+
return [root3, ...ast_utils_exports.streamAllContents(root3)];
|
|
59791
|
+
}
|
|
59792
|
+
function declaredNames(root3) {
|
|
59793
|
+
const names = /* @__PURE__ */ new Set();
|
|
59794
|
+
for (const node of nodesOf(root3)) {
|
|
59795
|
+
const name = node.name;
|
|
59796
|
+
if (typeof name === "string" && name.length > 0)
|
|
59797
|
+
names.add(name.replace(/^'|'$/gu, ""));
|
|
59798
|
+
}
|
|
59799
|
+
return names;
|
|
59800
|
+
}
|
|
59801
|
+
function graftInto(fragment, owner) {
|
|
59802
|
+
const mutable = fragment;
|
|
59803
|
+
mutable.$container = owner;
|
|
59804
|
+
mutable.$containerProperty = "members";
|
|
59805
|
+
}
|
|
59806
|
+
function firstSegment(refText) {
|
|
59807
|
+
return (refText.split("::")[0] ?? refText).replace(/^'|'$/gu, "").trim();
|
|
59808
|
+
}
|
|
59809
|
+
function unresolvedReferences(fragment, owner) {
|
|
59810
|
+
graftInto(fragment, owner);
|
|
59811
|
+
const own = declaredNames(fragment);
|
|
59812
|
+
const out = [];
|
|
59813
|
+
for (const node of nodesOf(fragment)) {
|
|
59814
|
+
for (const [property3, value] of Object.entries(node)) {
|
|
59815
|
+
if (property3.startsWith("$"))
|
|
59816
|
+
continue;
|
|
59817
|
+
for (const candidate of Array.isArray(value) ? value : [value]) {
|
|
59818
|
+
if (!isReference(candidate))
|
|
59819
|
+
continue;
|
|
59820
|
+
const reference = candidate;
|
|
59821
|
+
const refText = reference.$refText;
|
|
59822
|
+
if (!refText || own.has(firstSegment(refText)))
|
|
59823
|
+
continue;
|
|
59824
|
+
let resolved;
|
|
59825
|
+
try {
|
|
59826
|
+
resolved = reference.ref;
|
|
59827
|
+
} catch {
|
|
59828
|
+
return [];
|
|
59829
|
+
}
|
|
59830
|
+
if (resolved)
|
|
59831
|
+
continue;
|
|
59832
|
+
const range = reference.$refNode?.range;
|
|
59833
|
+
if (!range)
|
|
59834
|
+
continue;
|
|
59835
|
+
out.push({
|
|
59836
|
+
refText,
|
|
59837
|
+
line: range.start.line,
|
|
59838
|
+
character: range.start.character,
|
|
59839
|
+
length: Math.max(1, range.end.character - range.start.character)
|
|
59840
|
+
});
|
|
59841
|
+
}
|
|
59842
|
+
}
|
|
59843
|
+
}
|
|
59844
|
+
return out;
|
|
59845
|
+
}
|
|
59846
|
+
function evaluateRepresentation(parser, rep) {
|
|
59847
|
+
if (!isEmbeddedLanguage(rep.language))
|
|
59848
|
+
return void 0;
|
|
59849
|
+
const span = trailingBlockBodySpan(rep);
|
|
59850
|
+
if (!span)
|
|
59851
|
+
return void 0;
|
|
59852
|
+
const owner = rep.$container;
|
|
59853
|
+
const context = owner && owner.$type !== "Document" ? owner : void 0;
|
|
59854
|
+
const declaration = parser.parse(span.text, { rule: "NamespaceElement" });
|
|
59855
|
+
if (!hasErrors(declaration)) {
|
|
59856
|
+
return {
|
|
59857
|
+
span,
|
|
59858
|
+
verdict: conformance(declaration.value, owner),
|
|
59859
|
+
unresolved: context ? unresolvedReferences(declaration.value, context) : [],
|
|
59860
|
+
fragment: declaration.value,
|
|
59861
|
+
tier: "declaration"
|
|
59862
|
+
};
|
|
59863
|
+
}
|
|
59864
|
+
if (owner && EXPRESSION_VALUED_OWNERS.has(owner.$type)) {
|
|
59865
|
+
const expression = parser.parse(span.text, { rule: "Expr" });
|
|
59866
|
+
if (!hasErrors(expression)) {
|
|
59867
|
+
return {
|
|
59868
|
+
span,
|
|
59869
|
+
verdict: { status: "ok" },
|
|
59870
|
+
unresolved: context ? unresolvedReferences(expression.value, context) : [],
|
|
59871
|
+
fragment: expression.value,
|
|
59872
|
+
tier: "expression"
|
|
59873
|
+
};
|
|
59874
|
+
}
|
|
59875
|
+
}
|
|
59876
|
+
return { span, verdict: { status: "unparsed", failure: failureOf(declaration, span) }, unresolved: [] };
|
|
59877
|
+
}
|
|
59878
|
+
|
|
59599
59879
|
// ../language-server/out/src/services/conformance.js
|
|
59600
59880
|
var SPECIALIZATION_KINDS2 = /* @__PURE__ */ new Set([
|
|
59601
59881
|
":>",
|
|
@@ -60034,7 +60314,14 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
60034
60314
|
langiumDocuments;
|
|
60035
60315
|
astNodeLocator;
|
|
60036
60316
|
featurePaths;
|
|
60317
|
+
// REQ-396 — One SysmlValidator instance is registered against BOTH the SysML and
|
|
60318
|
+
// the KerML services (sysml-module.ts), so the parser used to read an embedded
|
|
60319
|
+
// representation is looked up from the document being validated rather than
|
|
60320
|
+
// captured here. The two share one grammar today; this keeps that from becoming
|
|
60321
|
+
// load-bearing.
|
|
60322
|
+
serviceRegistry;
|
|
60037
60323
|
constructor(services) {
|
|
60324
|
+
this.serviceRegistry = services.shared.ServiceRegistry;
|
|
60038
60325
|
this.indexManager = services.shared.workspace.IndexManager;
|
|
60039
60326
|
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
60040
60327
|
this.astNodeLocator = services.workspace.AstNodeLocator;
|
|
@@ -60050,23 +60337,38 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
60050
60337
|
// Langium linker cannot diagnose a missing intermediate feature. Resolve the
|
|
60051
60338
|
// chain here and underline only the first invalid segment.
|
|
60052
60339
|
checkPathExpr(node, accept) {
|
|
60340
|
+
const fault = this.featurePathFault(node);
|
|
60341
|
+
if (!fault)
|
|
60342
|
+
return;
|
|
60343
|
+
accept(severity("RES001", "error"), DIAGNOSTIC_MESSAGES.RES001_FEATURE_PATH_SEGMENT(fault.segment, fault.owner), {
|
|
60344
|
+
node,
|
|
60345
|
+
range: fault.range,
|
|
60346
|
+
code: "RES001",
|
|
60347
|
+
data: { featurePathSegment: true }
|
|
60348
|
+
});
|
|
60349
|
+
}
|
|
60350
|
+
// REQ-317, REQ-396 — The one place that decides whether a written feature path
|
|
60351
|
+
// has a provably absent segment. Extracted so an embedded representation body is
|
|
60352
|
+
// judged by exactly this rule (issue #150 review): a body is grafted onto the
|
|
60353
|
+
// element it represents and is therefore unreachable from the document walk that
|
|
60354
|
+
// dispatches `checkPathExpr`, so without a shared decision the two would drift
|
|
60355
|
+
// and a body would quietly escape a diagnostic the surrounding model receives.
|
|
60356
|
+
featurePathFault(node) {
|
|
60053
60357
|
let declaration = node.$container;
|
|
60054
60358
|
while (declaration && !isPartDecl(declaration))
|
|
60055
60359
|
declaration = declaration.$container;
|
|
60056
60360
|
const modifiers2 = declaration?.modifiers ?? [];
|
|
60057
60361
|
if (!declaration || !modifiers2.includes("ref"))
|
|
60058
|
-
return;
|
|
60362
|
+
return void 0;
|
|
60059
60363
|
const resolution = this.featurePaths.resolve(node);
|
|
60060
60364
|
if (resolution.unresolvedIndex === void 0)
|
|
60061
|
-
return;
|
|
60365
|
+
return void 0;
|
|
60062
60366
|
const invalid = resolution.segments[resolution.unresolvedIndex];
|
|
60063
|
-
|
|
60064
|
-
|
|
60065
|
-
|
|
60066
|
-
range: invalid.cst.range
|
|
60067
|
-
|
|
60068
|
-
data: { featurePathSegment: true }
|
|
60069
|
-
});
|
|
60367
|
+
return {
|
|
60368
|
+
segment: invalid.text,
|
|
60369
|
+
owner: resolution.unresolvedIndex > 0 ? resolution.segments[resolution.unresolvedIndex - 1].text : "the current scope",
|
|
60370
|
+
range: invalid.cst.range
|
|
60371
|
+
};
|
|
60070
60372
|
}
|
|
60071
60373
|
// REQ-389 — Namespace qualification versus feature chaining in every written path
|
|
60072
60374
|
// issue #213 — RES019: `::` binds tighter than `.`. Each dot-separated link of
|
|
@@ -60297,6 +60599,7 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
60297
60599
|
this.checkConnectorForms(node, accept);
|
|
60298
60600
|
this.checkActionStartSuccessions(node, accept);
|
|
60299
60601
|
this.checkAnnotationForms(node, accept);
|
|
60602
|
+
this.checkEmbeddedRepresentations(node, accept);
|
|
60300
60603
|
this.checkLanguageDisjointness(node, accept);
|
|
60301
60604
|
this.checkKermlWellFormedness(node, accept);
|
|
60302
60605
|
this.checkFilterExpressions(node, accept);
|
|
@@ -60637,6 +60940,132 @@ ${baseIndent}}`;
|
|
|
60637
60940
|
}
|
|
60638
60941
|
}
|
|
60639
60942
|
}
|
|
60943
|
+
// REQ-396 — SYN076/SEM012 embedded textual representations (issue #150).
|
|
60944
|
+
// A `rep … language "sysml"` (or `"kerml"`, matched case-insensitively) is not
|
|
60945
|
+
// opaque content: OMG KerML §7.4.3 requires its body to be a legal representation
|
|
60946
|
+
// of the element it annotates. So the body is processed by the comment-body rules,
|
|
60947
|
+
// parsed, and compared against that element:
|
|
60948
|
+
// SYN076 — the body is not valid SysML, reported ON the offending character
|
|
60949
|
+
// inside the body rather than on the `rep` keyword.
|
|
60950
|
+
// SEM012 — the body parses but represents a different element (wrong kind, or
|
|
60951
|
+
// the right kind under a different name).
|
|
60952
|
+
// Every other language stays opaque and is never parsed — an `alf`, `ocl` or
|
|
60953
|
+
// `yaml` body is content, and the vendored OMG corpus holds only those.
|
|
60954
|
+
checkEmbeddedRepresentations(node, accept) {
|
|
60955
|
+
const doc = ast_utils_exports.getDocument(node);
|
|
60956
|
+
if (!doc?.textDocument)
|
|
60957
|
+
return;
|
|
60958
|
+
let parser;
|
|
60959
|
+
for (const child of ast_utils_exports.streamAllContents(node)) {
|
|
60960
|
+
if (child.$type !== "RepStmt")
|
|
60961
|
+
continue;
|
|
60962
|
+
if (!isEmbeddedLanguage(child.language))
|
|
60963
|
+
continue;
|
|
60964
|
+
parser ??= this.serviceRegistry.getServices(doc.uri).parser.LangiumParser;
|
|
60965
|
+
const evaluated = evaluateRepresentation(parser, child);
|
|
60966
|
+
if (!evaluated)
|
|
60967
|
+
continue;
|
|
60968
|
+
const { span, verdict } = evaluated;
|
|
60969
|
+
const language = String(child.language ?? "").replace(/^"|"$/gu, "");
|
|
60970
|
+
if (verdict.status === "unparsed") {
|
|
60971
|
+
const { line, character, message } = verdict.failure;
|
|
60972
|
+
const anchor = bodyOffsetAt(span, line, character);
|
|
60973
|
+
const after = bodyOffsetAt(span, line, character + 1);
|
|
60974
|
+
const from = after > anchor ? anchor : bodyOffsetAt(span, line, Math.max(0, character - 1));
|
|
60975
|
+
accept(severity("SYN076", "error"), DIAGNOSTIC_MESSAGES.SYN076_EMBEDDED_REPRESENTATION_SYNTAX(language, message), {
|
|
60976
|
+
node: child,
|
|
60977
|
+
range: {
|
|
60978
|
+
start: doc.textDocument.positionAt(from),
|
|
60979
|
+
end: doc.textDocument.positionAt(Math.max(after, anchor))
|
|
60980
|
+
},
|
|
60981
|
+
code: "SYN076"
|
|
60982
|
+
});
|
|
60983
|
+
continue;
|
|
60984
|
+
}
|
|
60985
|
+
for (const reference of evaluated.unresolved) {
|
|
60986
|
+
accept(severity("RES020", "warning"), DIAGNOSTIC_MESSAGES.RES020_REPRESENTATION_UNRESOLVED(reference.refText, language), {
|
|
60987
|
+
node: child,
|
|
60988
|
+
range: {
|
|
60989
|
+
start: doc.textDocument.positionAt(bodyOffsetAt(span, reference.line, reference.character)),
|
|
60990
|
+
end: doc.textDocument.positionAt(bodyOffsetAt(span, reference.line, reference.character + reference.length))
|
|
60991
|
+
},
|
|
60992
|
+
code: "RES020"
|
|
60993
|
+
});
|
|
60994
|
+
}
|
|
60995
|
+
if (evaluated.fragment) {
|
|
60996
|
+
this.checkEmbeddedDialect(child, evaluated.fragment, language, span, doc, accept);
|
|
60997
|
+
this.checkEmbeddedFeaturePaths(child, evaluated.fragment, language, span, doc, accept);
|
|
60998
|
+
}
|
|
60999
|
+
if (verdict.status === "ok")
|
|
61000
|
+
continue;
|
|
61001
|
+
const { reason, expected, actual } = verdict.mismatch;
|
|
61002
|
+
const owner = child.$container;
|
|
61003
|
+
accept(severity("SEM012", "warning"), reason === "kind" ? DIAGNOSTIC_MESSAGES.SEM012_REPRESENTATION_WRONG_KIND(expected, actual) : DIAGNOSTIC_MESSAGES.SEM012_REPRESENTATION_WRONG_NAME(expected, actual), {
|
|
61004
|
+
node: child,
|
|
61005
|
+
range: {
|
|
61006
|
+
start: doc.textDocument.positionAt(bodyOffsetAt(span, 0, 0)),
|
|
61007
|
+
end: doc.textDocument.positionAt(bodyOffsetAt(span, 0, span.lines[0]?.text.length ?? 0))
|
|
61008
|
+
},
|
|
61009
|
+
code: "SEM012",
|
|
61010
|
+
relatedInformation: owner ? [relatedInfo(owner, "The element this representation annotates.")].filter((info) => info !== void 0) : void 0
|
|
61011
|
+
});
|
|
61012
|
+
}
|
|
61013
|
+
}
|
|
61014
|
+
// REQ-396 — SYN100/SYN101 inside a representation body (issue #150 review).
|
|
61015
|
+
// The normative rule is that the body is legal in the language the representation
|
|
61016
|
+
// NAMES, which is not the language of the file it sits in: a `.sysml` file may
|
|
61017
|
+
// carry a `language "kerml"` representation, and its body may then use no
|
|
61018
|
+
// SysML-only construct. `checkLanguageDisjointness` answers a different question
|
|
61019
|
+
// — what may this FILE contain — from the host URI, so the same tables are read
|
|
61020
|
+
// here against the declared language instead.
|
|
61021
|
+
checkEmbeddedDialect(rep, fragment, language, span, doc, accept) {
|
|
61022
|
+
const dialect = language.trim().toLowerCase();
|
|
61023
|
+
const isKerml = dialect === "kerml";
|
|
61024
|
+
const forbidden = isKerml ? SYSML_ONLY_TYPES : KERML_ONLY_TYPES;
|
|
61025
|
+
const code = isKerml ? "SYN100" : "SYN101";
|
|
61026
|
+
for (const node of [fragment, ...ast_utils_exports.streamAllContents(fragment)]) {
|
|
61027
|
+
if (!forbidden.has(node.$type))
|
|
61028
|
+
continue;
|
|
61029
|
+
const range = node.$cstNode?.range;
|
|
61030
|
+
if (!range)
|
|
61031
|
+
continue;
|
|
61032
|
+
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.`, {
|
|
61033
|
+
node: rep,
|
|
61034
|
+
range: this.bodyRange(span, doc, range),
|
|
61035
|
+
code
|
|
61036
|
+
});
|
|
61037
|
+
}
|
|
61038
|
+
}
|
|
61039
|
+
// REQ-396 — RES020 for a written feature path with a provably absent segment
|
|
61040
|
+
// (issue #150 review). `checkPathExpr` is registered as a node-level check, so
|
|
61041
|
+
// Langium dispatches it by walking the document AST and never reaches the grafted
|
|
61042
|
+
// fragment. The decision itself is shared with it through `featurePathFault`, so
|
|
61043
|
+
// a body is held to the same rule as the model around it rather than a parallel
|
|
61044
|
+
// one that could drift; only the code differs, keeping every fault found IN a
|
|
61045
|
+
// representation body under the one severity a team can tune.
|
|
61046
|
+
checkEmbeddedFeaturePaths(rep, fragment, language, span, doc, accept) {
|
|
61047
|
+
for (const node of [fragment, ...ast_utils_exports.streamAllContents(fragment)]) {
|
|
61048
|
+
if (!this.featurePaths.isPathExpr(node))
|
|
61049
|
+
continue;
|
|
61050
|
+
const fault = this.featurePathFault(node);
|
|
61051
|
+
if (!fault)
|
|
61052
|
+
continue;
|
|
61053
|
+
accept(severity("RES020", "warning"), DIAGNOSTIC_MESSAGES.RES020_REPRESENTATION_PATH_SEGMENT(fault.segment, fault.owner, language), {
|
|
61054
|
+
node: rep,
|
|
61055
|
+
range: this.bodyRange(span, doc, fault.range),
|
|
61056
|
+
code: "RES020"
|
|
61057
|
+
});
|
|
61058
|
+
}
|
|
61059
|
+
}
|
|
61060
|
+
// REQ-396 — A range inside a parsed body, expressed in document coordinates.
|
|
61061
|
+
// Fragment CST positions are line/character within the PROCESSED body, which is
|
|
61062
|
+
// exactly what the span's line table converts.
|
|
61063
|
+
bodyRange(span, doc, range) {
|
|
61064
|
+
return {
|
|
61065
|
+
start: doc.textDocument.positionAt(bodyOffsetAt(span, range.start.line, range.start.character)),
|
|
61066
|
+
end: doc.textDocument.positionAt(bodyOffsetAt(span, range.end.line, range.end.character))
|
|
61067
|
+
};
|
|
61068
|
+
}
|
|
60640
61069
|
// REQ-311 — SYN050/051 connector syntax, with a specific message in place of
|
|
60641
61070
|
// the grammar's generic parse error:
|
|
60642
61071
|
// SYN050 — `connect a with b;` (use `connect a to b;`).
|
|
@@ -63511,52 +63940,6 @@ function outlineGroupForType(astType) {
|
|
|
63511
63940
|
return CATEGORY_TO_OUTLINE_GROUP[categoryForType(astType)] ?? "structure";
|
|
63512
63941
|
}
|
|
63513
63942
|
|
|
63514
|
-
// ../language-server/out/src/services/comment-body.js
|
|
63515
|
-
function sourceOf(node) {
|
|
63516
|
-
return node.$cstNode?.root.fullText;
|
|
63517
|
-
}
|
|
63518
|
-
function cleanBlockBody(inner) {
|
|
63519
|
-
let body = inner.trim();
|
|
63520
|
-
if (body.startsWith("/*"))
|
|
63521
|
-
body = body.slice(2);
|
|
63522
|
-
if (body.endsWith("*/"))
|
|
63523
|
-
body = body.slice(0, -2);
|
|
63524
|
-
const lines = body.split("\n").map((line) => line.replace(/^\s*\*+ ?/u, "").replace(/\s+$/u, ""));
|
|
63525
|
-
while (lines.length > 0 && lines[0].trim() === "")
|
|
63526
|
-
lines.shift();
|
|
63527
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === "")
|
|
63528
|
-
lines.pop();
|
|
63529
|
-
return lines.join("\n").trim();
|
|
63530
|
-
}
|
|
63531
|
-
function docCommentBody(node) {
|
|
63532
|
-
const raw = node.doc;
|
|
63533
|
-
if (typeof raw !== "string")
|
|
63534
|
-
return void 0;
|
|
63535
|
-
const start = raw.indexOf("/*");
|
|
63536
|
-
const end = raw.lastIndexOf("*/");
|
|
63537
|
-
if (start < 0 || end < start)
|
|
63538
|
-
return void 0;
|
|
63539
|
-
const body = cleanBlockBody(raw.slice(start, end + 2));
|
|
63540
|
-
return body.length > 0 ? body : void 0;
|
|
63541
|
-
}
|
|
63542
|
-
function trailingBlockBody(node) {
|
|
63543
|
-
const cst = node.$cstNode;
|
|
63544
|
-
const text = sourceOf(node);
|
|
63545
|
-
if (!cst || text === void 0)
|
|
63546
|
-
return void 0;
|
|
63547
|
-
const rest = text.slice(cst.end);
|
|
63548
|
-
const open = rest.indexOf("/*");
|
|
63549
|
-
if (open < 0)
|
|
63550
|
-
return void 0;
|
|
63551
|
-
if (rest.slice(0, open).trim() !== "")
|
|
63552
|
-
return void 0;
|
|
63553
|
-
const close = rest.indexOf("*/", open + 2);
|
|
63554
|
-
if (close < 0)
|
|
63555
|
-
return void 0;
|
|
63556
|
-
const body = cleanBlockBody(rest.slice(open, close + 2));
|
|
63557
|
-
return body.length > 0 ? body : void 0;
|
|
63558
|
-
}
|
|
63559
|
-
|
|
63560
63943
|
// ../language-server/out/src/services/document-symbol-provider.js
|
|
63561
63944
|
var SPECIALIZATION_KINDS4 = /* @__PURE__ */ new Set([":>", "specializes"]);
|
|
63562
63945
|
var REDEFINITION_KINDS = /* @__PURE__ */ new Set([":>>", "redefines"]);
|
|
@@ -71093,7 +71476,7 @@ async function runValidation(command) {
|
|
|
71093
71476
|
}
|
|
71094
71477
|
|
|
71095
71478
|
// src/main.ts
|
|
71096
|
-
var VERSION2 = true ? "0.
|
|
71479
|
+
var VERSION2 = true ? "0.17.0" : "dev";
|
|
71097
71480
|
async function main(argv) {
|
|
71098
71481
|
const command = parseArgs(argv);
|
|
71099
71482
|
if (command.kind === "help") {
|