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 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
- const owner = resolution.unresolvedIndex > 0 ? resolution.segments[resolution.unresolvedIndex - 1].text : "the current scope";
60064
- accept(severity("RES001", "error"), DIAGNOSTIC_MESSAGES.RES001_FEATURE_PATH_SEGMENT(invalid.text, owner), {
60065
- node,
60066
- range: invalid.cst.range,
60067
- code: "RES001",
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.16.0" : "dev";
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") {