sysml-validate 0.15.8 → 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
@@ -4108,21 +4108,21 @@ var require_main2 = __commonJS({
4108
4108
  }
4109
4109
  TextDocumentItem3.is = is;
4110
4110
  })(TextDocumentItem2 || (exports3.TextDocumentItem = TextDocumentItem2 = {}));
4111
- var MarkupKind3;
4112
- (function(MarkupKind4) {
4113
- MarkupKind4.PlainText = "plaintext";
4114
- MarkupKind4.Markdown = "markdown";
4111
+ var MarkupKind4;
4112
+ (function(MarkupKind5) {
4113
+ MarkupKind5.PlainText = "plaintext";
4114
+ MarkupKind5.Markdown = "markdown";
4115
4115
  function is(value) {
4116
4116
  var candidate = value;
4117
- return candidate === MarkupKind4.PlainText || candidate === MarkupKind4.Markdown;
4117
+ return candidate === MarkupKind5.PlainText || candidate === MarkupKind5.Markdown;
4118
4118
  }
4119
- MarkupKind4.is = is;
4120
- })(MarkupKind3 || (exports3.MarkupKind = MarkupKind3 = {}));
4119
+ MarkupKind5.is = is;
4120
+ })(MarkupKind4 || (exports3.MarkupKind = MarkupKind4 = {}));
4121
4121
  var MarkupContent2;
4122
4122
  (function(MarkupContent3) {
4123
4123
  function is(value) {
4124
4124
  var candidate = value;
4125
- return Is2.objectLiteral(value) && MarkupKind3.is(candidate.kind) && Is2.string(candidate.value);
4125
+ return Is2.objectLiteral(value) && MarkupKind4.is(candidate.kind) && Is2.string(candidate.value);
4126
4126
  }
4127
4127
  MarkupContent3.is = is;
4128
4128
  })(MarkupContent2 || (exports3.MarkupContent = MarkupContent2 = {}));
@@ -24188,14 +24188,14 @@ var TextDocumentItem;
24188
24188
  TextDocumentItem2.is = is;
24189
24189
  })(TextDocumentItem || (TextDocumentItem = {}));
24190
24190
  var MarkupKind;
24191
- (function(MarkupKind3) {
24192
- MarkupKind3.PlainText = "plaintext";
24193
- MarkupKind3.Markdown = "markdown";
24191
+ (function(MarkupKind4) {
24192
+ MarkupKind4.PlainText = "plaintext";
24193
+ MarkupKind4.Markdown = "markdown";
24194
24194
  function is(value) {
24195
24195
  const candidate = value;
24196
- return candidate === MarkupKind3.PlainText || candidate === MarkupKind3.Markdown;
24196
+ return candidate === MarkupKind4.PlainText || candidate === MarkupKind4.Markdown;
24197
24197
  }
24198
- MarkupKind3.is = is;
24198
+ MarkupKind4.is = is;
24199
24199
  })(MarkupKind || (MarkupKind = {}));
24200
24200
  var MarkupContent;
24201
24201
  (function(MarkupContent2) {
@@ -57803,7 +57803,14 @@ var sysmlOutlineSettings = {
57803
57803
  hiddenCategories: []
57804
57804
  };
57805
57805
  var sysmlInlayHintSettings = {
57806
- dimension: false
57806
+ enabled: true,
57807
+ multiplicity: true,
57808
+ dimension: false,
57809
+ visibility: false,
57810
+ modifiers: false,
57811
+ specialization: false,
57812
+ redefinition: false,
57813
+ effectiveNames: false
57807
57814
  };
57808
57815
  function asDiagnosticLevel(value, fallback = "off") {
57809
57816
  return value === "off" || value === "warning" || value === "error" ? value : fallback;
@@ -58642,6 +58649,18 @@ var DIAGNOSTIC_MESSAGES = {
58642
58649
  // `first a then b;` and the `then` occurrence shorthand in a part), so the
58643
58650
  // message names the construct it means rather than "action language".
58644
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}'.`,
58645
58664
  RES016_LIBRARY_SHADOWING: (name) => `'${name}' shadows a standard-library element of the same name; the library symbol is hidden in this scope.`,
58646
58665
  // issue #183 — KerML namespace distinguishability (validateNamespaceDistinguishability).
58647
58666
  // Owned siblings sharing a name/short name (or an alias clashing with an owned
@@ -58685,7 +58704,16 @@ var DIAGNOSTIC_MESSAGES = {
58685
58704
  SSM025_ACTION_PARAMETER_COUNT: (usage, declared, typeName, available) => `'${usage}' declares ${declared} directed parameter${declared === 1 ? "" : "s"} but its definition '${typeName}' has ${available}. An action usage's parameters correspond positionally to its definition's, so there is no ${available === 0 ? "parameter" : `${available + 1}th parameter`} to correspond to.`,
58686
58705
  SSM025_ACTION_PARAMETER_DIRECTION: (position, mine, typeName, theirs) => `Parameter ${position} is '${mine}' but the corresponding parameter of '${typeName}' is '${theirs}'. Positional correspondence redefines that parameter, and a redefinition cannot reverse a parameter's direction.`,
58687
58706
  SSM026_REDEFINITION_DIRECTION: (feature, mine, theirs) => `Direction '${mine}' reverses the redefined feature '${feature}', which is '${theirs}'. A redefinition refines a feature \u2014 it cannot turn an input into an output.`,
58688
- SSM027_INTERFACE_END_TYPE: (position, endType, definition, expected) => `End ${position} is a '${endType}', but '${definition}' declares that end as '${expected}'. An interface usage connects through the ends its definition declares.`
58707
+ SSM027_INTERFACE_END_TYPE: (position, endType, definition, expected) => `End ${position} is a '${endType}', but '${definition}' declares that end as '${expected}'. An interface usage connects through the ends its definition declares.`,
58708
+ // REQ-392 — a `sysml-format` comment the formatter cannot act on. Advisory:
58709
+ // the directive is inert, and saying so beats leaving the author to wonder
58710
+ // why their layout was reformatted anyway.
58711
+ STYL008_FORMAT_DIRECTIVE_UNATTACHED: "This `sysml-format ignore` directive has no element after it to attach to, so nothing is preserved. Move it directly above the element whose layout you want to keep.",
58712
+ // REQ-345, REQ-393 — the formatter-settings refusal is shown to the user
58713
+ // through `window/showWarningMessage`, so its text lives here like every
58714
+ // other reviewable string rather than inline at the call site.
58715
+ FORMAT_SETTINGS_REFUSED: (detail) => `SysML formatter settings ignored (${detail}). The previous value is still in use.`,
58716
+ STYL008_FORMAT_DIRECTIVE_UNKNOWN: (argument) => `Unknown formatter directive '${argument || "(none)"}'. The only directive is 'sysml-format ignore', which preserves the next element's layout.`
58689
58717
  };
58690
58718
  var KEYWORD_TOOLTIPS = {
58691
58719
  // Package / namespace
@@ -58882,6 +58910,107 @@ var OPERATOR_TOOLTIPS = {
58882
58910
  "@@": { desc: "**Metaclass application** \u2014 applies a metaclass annotation.", precedence: 0, example: "@@StandardProfile", cite: "OMG SysML v2.0 Part 1 \xA78.2.2.27 (Metadata)" }
58883
58911
  };
58884
58912
 
58913
+ // ../language-server/out/src/services/format-directives.js
58914
+ var DIRECTIVE_WORD = "sysml-format";
58915
+ var IGNORE_ARGUMENT = "ignore";
58916
+ var EMPTY_SCAN = { directives: [], protectedRanges: [] };
58917
+ function commentBody(text) {
58918
+ if (text.startsWith("//*") && text.endsWith("*/") && text.length >= 5)
58919
+ return text.slice(3, -2);
58920
+ if (text.startsWith("//"))
58921
+ return text.slice(2);
58922
+ if (text.startsWith("/*") && text.endsWith("*/") && text.length >= 4)
58923
+ return text.slice(2, -2);
58924
+ return void 0;
58925
+ }
58926
+ function classify(text) {
58927
+ const body = commentBody(text);
58928
+ if (body === void 0)
58929
+ return void 0;
58930
+ const trimmed = body.trim();
58931
+ if (trimmed !== DIRECTIVE_WORD && !trimmed.startsWith(`${DIRECTIVE_WORD} `) && !trimmed.startsWith(`${DIRECTIVE_WORD} `)) {
58932
+ return void 0;
58933
+ }
58934
+ const argument = trimmed.slice(DIRECTIVE_WORD.length).trim();
58935
+ return { kind: argument === IGNORE_ARGUMENT ? "ignore" : "malformed", argument };
58936
+ }
58937
+ function looksLikeFormatDirective(text) {
58938
+ return text.includes(DIRECTIVE_WORD);
58939
+ }
58940
+ function nodesByStartOffset(root3) {
58941
+ const byOffset = /* @__PURE__ */ new Map();
58942
+ for (const node of ast_utils_exports.streamAllContents(root3)) {
58943
+ const cst = node.$cstNode;
58944
+ if (!cst)
58945
+ continue;
58946
+ const existing = byOffset.get(cst.offset);
58947
+ if (!existing || (existing.$cstNode?.end ?? 0) < cst.end)
58948
+ byOffset.set(cst.offset, node);
58949
+ }
58950
+ return byOffset;
58951
+ }
58952
+ function mergeSlices(slices) {
58953
+ const sorted = [...slices].sort((a2, b) => a2.offset - b.offset || a2.end - b.end);
58954
+ const merged = [];
58955
+ for (const slice of sorted) {
58956
+ const last2 = merged.at(-1);
58957
+ if (last2 && slice.offset <= last2.end) {
58958
+ last2.end = Math.max(last2.end, slice.end);
58959
+ } else {
58960
+ merged.push({ ...slice });
58961
+ }
58962
+ }
58963
+ return merged;
58964
+ }
58965
+ function scanFormatDirectives(document) {
58966
+ const root3 = document.parseResult?.value;
58967
+ const rootCst = root3?.$cstNode;
58968
+ if (!root3 || !rootCst)
58969
+ return EMPTY_SCAN;
58970
+ const text = document.textDocument.getText();
58971
+ if (!looksLikeFormatDirective(text))
58972
+ return EMPTY_SCAN;
58973
+ const leaves = cst_utils_exports.flattenCst(rootCst).toArray();
58974
+ const directives = [];
58975
+ const protectedRanges = [];
58976
+ let byOffset;
58977
+ for (let index = 0; index < leaves.length; index++) {
58978
+ const leaf = leaves[index];
58979
+ if (!leaf.hidden)
58980
+ continue;
58981
+ const classified = classify(leaf.text);
58982
+ if (!classified)
58983
+ continue;
58984
+ const comment = { offset: leaf.offset, end: leaf.end };
58985
+ if (classified.kind === "malformed") {
58986
+ directives.push({ kind: "malformed", comment, argument: classified.argument });
58987
+ continue;
58988
+ }
58989
+ let next;
58990
+ for (let scan = index + 1; scan < leaves.length; scan++) {
58991
+ if (!leaves[scan].hidden) {
58992
+ next = leaves[scan];
58993
+ break;
58994
+ }
58995
+ }
58996
+ byOffset ??= nodesByStartOffset(root3);
58997
+ const target = next ? byOffset.get(next.offset)?.$cstNode : void 0;
58998
+ if (!target) {
58999
+ directives.push({ kind: "dangling", comment, argument: classified.argument });
59000
+ continue;
59001
+ }
59002
+ const slice = { offset: target.offset, end: target.end };
59003
+ directives.push({ kind: "ignore", comment, argument: classified.argument, target: slice });
59004
+ protectedRanges.push(slice);
59005
+ }
59006
+ if (directives.length === 0)
59007
+ return EMPTY_SCAN;
59008
+ return { directives, protectedRanges: mergeSlices(protectedRanges) };
59009
+ }
59010
+ function intersectsProtected(ranges, offset, end) {
59011
+ return ranges.some((slice) => offset < slice.end && end > slice.offset);
59012
+ }
59013
+
58885
59014
  // ../language-server/out/src/services/v1-guardrail.js
58886
59015
  var V1_KEYWORD_MAP = {
58887
59016
  block: { v2: "part def", note: "SysML v2 replaces the v1 'Block' with 'part def'." },
@@ -59479,6 +59608,274 @@ function isNamespaceOnlyDecl(node) {
59479
59608
  return node.isDef === true;
59480
59609
  }
59481
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
+
59482
59879
  // ../language-server/out/src/services/conformance.js
59483
59880
  var SPECIALIZATION_KINDS2 = /* @__PURE__ */ new Set([
59484
59881
  ":>",
@@ -59917,7 +60314,14 @@ var SysmlValidator = class _SysmlValidator {
59917
60314
  langiumDocuments;
59918
60315
  astNodeLocator;
59919
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;
59920
60323
  constructor(services) {
60324
+ this.serviceRegistry = services.shared.ServiceRegistry;
59921
60325
  this.indexManager = services.shared.workspace.IndexManager;
59922
60326
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
59923
60327
  this.astNodeLocator = services.workspace.AstNodeLocator;
@@ -59933,23 +60337,38 @@ var SysmlValidator = class _SysmlValidator {
59933
60337
  // Langium linker cannot diagnose a missing intermediate feature. Resolve the
59934
60338
  // chain here and underline only the first invalid segment.
59935
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) {
59936
60357
  let declaration = node.$container;
59937
60358
  while (declaration && !isPartDecl(declaration))
59938
60359
  declaration = declaration.$container;
59939
60360
  const modifiers2 = declaration?.modifiers ?? [];
59940
60361
  if (!declaration || !modifiers2.includes("ref"))
59941
- return;
60362
+ return void 0;
59942
60363
  const resolution = this.featurePaths.resolve(node);
59943
60364
  if (resolution.unresolvedIndex === void 0)
59944
- return;
60365
+ return void 0;
59945
60366
  const invalid = resolution.segments[resolution.unresolvedIndex];
59946
- const owner = resolution.unresolvedIndex > 0 ? resolution.segments[resolution.unresolvedIndex - 1].text : "the current scope";
59947
- accept(severity("RES001", "error"), DIAGNOSTIC_MESSAGES.RES001_FEATURE_PATH_SEGMENT(invalid.text, owner), {
59948
- node,
59949
- range: invalid.cst.range,
59950
- code: "RES001",
59951
- data: { featurePathSegment: true }
59952
- });
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
+ };
59953
60372
  }
59954
60373
  // REQ-389 — Namespace qualification versus feature chaining in every written path
59955
60374
  // issue #213 — RES019: `::` binds tighter than `.`. Each dot-separated link of
@@ -60180,6 +60599,7 @@ var SysmlValidator = class _SysmlValidator {
60180
60599
  this.checkConnectorForms(node, accept);
60181
60600
  this.checkActionStartSuccessions(node, accept);
60182
60601
  this.checkAnnotationForms(node, accept);
60602
+ this.checkEmbeddedRepresentations(node, accept);
60183
60603
  this.checkLanguageDisjointness(node, accept);
60184
60604
  this.checkKermlWellFormedness(node, accept);
60185
60605
  this.checkFilterExpressions(node, accept);
@@ -60268,6 +60688,23 @@ var SysmlValidator = class _SysmlValidator {
60268
60688
  }
60269
60689
  this.checkMixedIndentation(node, doc, accept);
60270
60690
  this.checkAlphabetizedImportGroups(node, doc, accept);
60691
+ this.checkFormatDirectives(node, doc, accept);
60692
+ }
60693
+ // REQ-392 — STYL008 a `sysml-format` directive the formatter cannot act on.
60694
+ // A well-formed `sysml-format ignore` that found its element is silent; only
60695
+ // an unknown argument and one with nothing left to attach to are reported,
60696
+ // each on the comment itself so the fix is where the cursor lands.
60697
+ checkFormatDirectives(root3, doc, accept) {
60698
+ for (const directive of scanFormatDirectives(doc).directives) {
60699
+ if (directive.kind === "ignore")
60700
+ continue;
60701
+ const range = {
60702
+ start: doc.textDocument.positionAt(directive.comment.offset),
60703
+ end: doc.textDocument.positionAt(directive.comment.end)
60704
+ };
60705
+ const message = directive.kind === "dangling" ? DIAGNOSTIC_MESSAGES.STYL008_FORMAT_DIRECTIVE_UNATTACHED : DIAGNOSTIC_MESSAGES.STYL008_FORMAT_DIRECTIVE_UNKNOWN(directive.argument);
60706
+ accept(severity("STYL008", "info"), message, { node: root3, range, code: "STYL008" });
60707
+ }
60271
60708
  }
60272
60709
  // REQ-332 — STYL004 mixed tab + space indentation.
60273
60710
  checkMixedIndentation(root3, doc, accept) {
@@ -60503,6 +60940,132 @@ ${baseIndent}}`;
60503
60940
  }
60504
60941
  }
60505
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
+ }
60506
61069
  // REQ-311 — SYN050/051 connector syntax, with a specific message in place of
60507
61070
  // the grammar's generic parse error:
60508
61071
  // SYN050 — `connect a with b;` (use `connect a to b;`).
@@ -62910,6 +63473,7 @@ var STYLE_ADVISORY_CODES = /* @__PURE__ */ new Set([
62910
63473
  "STYL005",
62911
63474
  "STYL006",
62912
63475
  "STYL007",
63476
+ "STYL008",
62913
63477
  "LEX009",
62914
63478
  // trailing whitespace
62915
63479
  "SYN024",
@@ -63376,52 +63940,6 @@ function outlineGroupForType(astType) {
63376
63940
  return CATEGORY_TO_OUTLINE_GROUP[categoryForType(astType)] ?? "structure";
63377
63941
  }
63378
63942
 
63379
- // ../language-server/out/src/services/comment-body.js
63380
- function sourceOf(node) {
63381
- return node.$cstNode?.root.fullText;
63382
- }
63383
- function cleanBlockBody(inner) {
63384
- let body = inner.trim();
63385
- if (body.startsWith("/*"))
63386
- body = body.slice(2);
63387
- if (body.endsWith("*/"))
63388
- body = body.slice(0, -2);
63389
- const lines = body.split("\n").map((line) => line.replace(/^\s*\*+ ?/u, "").replace(/\s+$/u, ""));
63390
- while (lines.length > 0 && lines[0].trim() === "")
63391
- lines.shift();
63392
- while (lines.length > 0 && lines[lines.length - 1].trim() === "")
63393
- lines.pop();
63394
- return lines.join("\n").trim();
63395
- }
63396
- function docCommentBody(node) {
63397
- const raw = node.doc;
63398
- if (typeof raw !== "string")
63399
- return void 0;
63400
- const start = raw.indexOf("/*");
63401
- const end = raw.lastIndexOf("*/");
63402
- if (start < 0 || end < start)
63403
- return void 0;
63404
- const body = cleanBlockBody(raw.slice(start, end + 2));
63405
- return body.length > 0 ? body : void 0;
63406
- }
63407
- function trailingBlockBody(node) {
63408
- const cst = node.$cstNode;
63409
- const text = sourceOf(node);
63410
- if (!cst || text === void 0)
63411
- return void 0;
63412
- const rest = text.slice(cst.end);
63413
- const open = rest.indexOf("/*");
63414
- if (open < 0)
63415
- return void 0;
63416
- if (rest.slice(0, open).trim() !== "")
63417
- return void 0;
63418
- const close = rest.indexOf("*/", open + 2);
63419
- if (close < 0)
63420
- return void 0;
63421
- const body = cleanBlockBody(rest.slice(open, close + 2));
63422
- return body.length > 0 ? body : void 0;
63423
- }
63424
-
63425
63943
  // ../language-server/out/src/services/document-symbol-provider.js
63426
63944
  var SPECIALIZATION_KINDS4 = /* @__PURE__ */ new Set([":>", "specializes"]);
63427
63945
  var REDEFINITION_KINDS = /* @__PURE__ */ new Set([":>>", "redefines"]);
@@ -63988,6 +64506,18 @@ function resolveEffectiveSubject(node) {
63988
64506
 
63989
64507
  // ../language-server/out/src/services/hover-provider.js
63990
64508
  var MAX_HOVER_LINES = 30;
64509
+ var MAX_DOC_DEPTH = 16;
64510
+ var MAX_DOC_NODES = 64;
64511
+ var MAX_DOC_SECTION_LINES = 14;
64512
+ var DOC_SPECIALIZATION_KINDS = /* @__PURE__ */ new Set([
64513
+ ":>",
64514
+ ":>>",
64515
+ "::>",
64516
+ "specializes",
64517
+ "subsets",
64518
+ "redefines",
64519
+ "references"
64520
+ ]);
63991
64521
  function leafAtOffset(node, offset) {
63992
64522
  if (isLeafCstNode(node)) {
63993
64523
  return node.offset <= offset && offset < node.offset + node.length ? node : void 0;
@@ -64142,22 +64672,26 @@ function multiplicityPlainEnglish(node) {
64142
64672
  return lo === 1 ? "Exactly 1 value" : `Exactly ${lo} values`;
64143
64673
  return `Between ${lo} and ${hi ?? "*"} values`;
64144
64674
  }
64145
- function docComment(node) {
64675
+ function localDocs(node) {
64676
+ const bodies = [];
64146
64677
  const members = node.members;
64147
64678
  if (Array.isArray(members)) {
64148
64679
  for (const m of members) {
64149
64680
  const body = docBody(m);
64150
64681
  if (body)
64151
- return body;
64682
+ bodies.push(body);
64152
64683
  }
64153
64684
  }
64685
+ if (bodies.length > 0)
64686
+ return bodies;
64154
64687
  const siblings = node.$container?.members;
64155
64688
  if (Array.isArray(siblings)) {
64156
64689
  const index = siblings.indexOf(node);
64157
- if (index > 0)
64158
- return docBody(siblings[index - 1]);
64690
+ const preceding = index > 0 ? docBody(siblings[index - 1]) : void 0;
64691
+ if (preceding)
64692
+ bodies.push(preceding);
64159
64693
  }
64160
- return void 0;
64694
+ return bodies;
64161
64695
  }
64162
64696
  function docBody(node) {
64163
64697
  const member = node;
@@ -64303,13 +64837,67 @@ function buildCommentHover(node) {
64303
64837
  lines.push("", sourceFooter(nodeSource(node)));
64304
64838
  return lines.join("\n");
64305
64839
  }
64840
+ function uniqueLocalTarget(source, targetText) {
64841
+ const root3 = ast_utils_exports.getDocument(source).parseResult?.value;
64842
+ if (!root3)
64843
+ return void 0;
64844
+ let found;
64845
+ for (const candidate of ast_utils_exports.streamAllContents(root3)) {
64846
+ const named = candidate;
64847
+ if (named.name !== targetText && named.shortName?.name !== targetText)
64848
+ continue;
64849
+ if (found)
64850
+ return void 0;
64851
+ found = candidate;
64852
+ }
64853
+ return found;
64854
+ }
64855
+ function declarationLink(node) {
64856
+ const uri = ast_utils_exports.getDocument(node).uri.toString();
64857
+ const line = (node.$cstNode?.range.start.line ?? 0) + 1;
64858
+ return `[${shortUri(uri)}:${line}](${uri}#L${line})`;
64859
+ }
64860
+ function limitDocSection(value) {
64861
+ const lines = value.split(/\r?\n/u);
64862
+ if (lines.length <= MAX_DOC_SECTION_LINES)
64863
+ return value;
64864
+ const kept = lines.slice(0, MAX_DOC_SECTION_LINES);
64865
+ if (kept.filter((line) => line.startsWith("```")).length % 2 !== 0)
64866
+ kept.push("```");
64867
+ kept.push("\u2026");
64868
+ return kept.join("\n");
64869
+ }
64870
+ function renderDocs(bodies, provenance) {
64871
+ const body = limitDocSection(bodies.join("\n\n"));
64872
+ return provenance ? `${provenance}
64873
+
64874
+ ${body}` : body;
64875
+ }
64876
+ function supertypeCandidates(node) {
64877
+ const named = node;
64878
+ const out = [];
64879
+ const typing = named.typing;
64880
+ if (typing?.type?.$refText)
64881
+ out.push({ refText: typing.type.$refText, ref: typing.type.ref });
64882
+ for (const more of typing?.moreTypes ?? []) {
64883
+ if (more.$refText)
64884
+ out.push({ refText: more.$refText, ref: more.ref });
64885
+ }
64886
+ for (const rel of allRelationships(named)) {
64887
+ if (!rel.kind || !DOC_SPECIALIZATION_KINDS.has(rel.kind))
64888
+ continue;
64889
+ for (const target of rel.targets ?? [])
64890
+ out.push({ refText: target });
64891
+ }
64892
+ return out;
64893
+ }
64306
64894
  function buildContextualHover(node) {
64307
64895
  const contextual = nearestAncestor(node, /* @__PURE__ */ new Set(["IfExpr", "MetadataAnnotation", "RepStmt", "CommentStmt"]));
64308
64896
  if (!contextual)
64309
64897
  return void 0;
64310
64898
  return buildConditionalHover(contextual) ?? buildMetadataAnnotationHover(contextual) ?? buildRepresentationHover(contextual) ?? buildCommentHover(contextual);
64311
64899
  }
64312
- function buildHoverMarkdown(node, relationshipDetails) {
64900
+ function buildHoverMarkdown(node, relationshipDetails, documentation) {
64313
64901
  const named = node;
64314
64902
  const dir = directionLabel(node);
64315
64903
  const kind = kindLabel2(node);
@@ -64318,7 +64906,7 @@ function buildHoverMarkdown(node, relationshipDetails) {
64318
64906
  const mult = multiplicityLabel(node);
64319
64907
  const multHint = multiplicityPlainEnglish(node);
64320
64908
  const rels = relationshipLabel(allRelationships(named));
64321
- const doc = docComment(node);
64909
+ const doc = documentation;
64322
64910
  const members = memberList(node);
64323
64911
  const headerLines = [`${dir}${kind} ${name}${typing}${mult}${rels}`];
64324
64912
  if (members.length > 0) {
@@ -64671,11 +65259,14 @@ var SysmlHoverProvider = class {
64671
65259
  astNodeLocator;
64672
65260
  documents;
64673
65261
  indexManager;
65262
+ /** REQ-394 — the linker's lazy standard-library load, reused rather than duplicated. */
65263
+ linker;
64674
65264
  constructor(services) {
64675
65265
  this.references = services.references.References;
64676
65266
  this.astNodeLocator = services.workspace.AstNodeLocator;
64677
65267
  this.documents = services.shared.workspace.LangiumDocuments;
64678
65268
  this.indexManager = services.shared.workspace.IndexManager;
65269
+ this.linker = services.references.Linker;
64679
65270
  }
64680
65271
  // REQ-246, REQ-334, REQ-335, REQ-336, REQ-342, REQ-343 — Route hover requests to markdown tooltip builders
64681
65272
  async getHoverContent(document, params) {
@@ -64749,7 +65340,50 @@ var SysmlHoverProvider = class {
64749
65340
  };
64750
65341
  }
64751
65342
  async buildElementHover(node) {
64752
- return buildHoverMarkdown(node, await this.relationshipDetailSection(node));
65343
+ return buildHoverMarkdown(node, await this.relationshipDetailSection(node), await this.documentationSection(node));
65344
+ }
65345
+ /**
65346
+ * REQ-394 — The documentation an element reads with.
65347
+ *
65348
+ * Its own `doc` members win, all of them, in declaration order. With none of
65349
+ * its own, the walk goes up the typing and specialization graph breadth
65350
+ * first, so the CLOSEST documented type wins over a more general ancestor,
65351
+ * and stops at the first documented one — naming it, and linking to it, so
65352
+ * the reader can see where the prose came from.
65353
+ *
65354
+ * The walk resolves through the same machinery as go-to-definition
65355
+ * (`resolveRelationshipTarget` — local nodes, then the workspace index, then
65356
+ * one lazily parsed library file), so cross-file and standard-library
65357
+ * documentation arrive without a second name resolver. A `visited` set plus
65358
+ * the depth and node budgets make a cyclic or malformed graph terminate.
65359
+ */
65360
+ async documentationSection(node) {
65361
+ const own = localDocs(node);
65362
+ if (own.length > 0)
65363
+ return renderDocs(own);
65364
+ const visited = /* @__PURE__ */ new Set([node]);
65365
+ let frontier = [node];
65366
+ for (let depth = 0; depth < MAX_DOC_DEPTH && frontier.length > 0; depth++) {
65367
+ const next = [];
65368
+ for (const current2 of frontier) {
65369
+ for (const candidate of supertypeCandidates(current2)) {
65370
+ if (visited.size >= MAX_DOC_NODES)
65371
+ return void 0;
65372
+ const target = candidate.ref ?? this.resolveDocumentationTarget(current2, candidate.refText);
65373
+ if (!target || visited.has(target))
65374
+ continue;
65375
+ visited.add(target);
65376
+ const inherited = localDocs(target);
65377
+ if (inherited.length > 0) {
65378
+ const label = identificationLabel(target) ?? candidate.refText;
65379
+ return renderDocs(inherited, `*Documentation inherited from ${kindLabel2(target)} \`${label}\`* \u2014 ${declarationLink(target)}`);
65380
+ }
65381
+ next.push(target);
65382
+ }
65383
+ }
65384
+ frontier = next;
65385
+ }
65386
+ return void 0;
64753
65387
  }
64754
65388
  async buildOperatorHover(token) {
64755
65389
  const qualifiedName = OPERATOR_FUNCTIONS[token];
@@ -64799,6 +65433,46 @@ var SysmlHoverProvider = class {
64799
65433
  return lines.length > 0 ? `*Relationships:*
64800
65434
  ${lines.join("\n")}` : void 0;
64801
65435
  }
65436
+ /**
65437
+ * REQ-394 — Resolve a supertype for the documentation walk without pulling a
65438
+ * document into the workspace.
65439
+ *
65440
+ * The card itself may lazily parse ONE indexed library target (REQ-251), and
65441
+ * that is the budget. Looking for an inherited `doc` must not add to it, so
65442
+ * this reuses the LINKER's own lazy load — the same path a cross-reference
65443
+ * takes, which parses a standard-library file off to the side and caches it
65444
+ * — instead of `getOrCreateDocument`, which would make every hover grow
65445
+ * `LangiumDocuments`.
65446
+ *
65447
+ * The match has to be UNAMBIGUOUS. This is not a scope-aware resolver — it
65448
+ * cannot apply imports or visibility — so it only accepts a name written
65449
+ * exactly as the index holds it, or a final segment that names exactly one
65450
+ * element in the whole index. Two `Vehicle`s in different packages, or an
65451
+ * unresolved reference that happens to share a final segment with something
65452
+ * unrelated, therefore yield NO documentation instead of another element's
65453
+ * prose picked by index order. Wrong documentation reads as authoritative;
65454
+ * none reads as none.
65455
+ */
65456
+ resolveDocumentationTarget(source, targetText) {
65457
+ const local = uniqueLocalTarget(source, targetText);
65458
+ if (local)
65459
+ return local;
65460
+ const key = relationshipTargetKey(targetText);
65461
+ const exact = /* @__PURE__ */ new Map();
65462
+ const byKey = /* @__PURE__ */ new Map();
65463
+ for (const description of this.indexManager.allElements()) {
65464
+ const id = `${description.documentUri.toString()}#${description.path}`;
65465
+ if (description.name === targetText)
65466
+ exact.set(id, description);
65467
+ else if (description.name === key)
65468
+ byKey.set(id, description);
65469
+ }
65470
+ const pool = exact.size > 0 ? exact : byKey;
65471
+ if (pool.size !== 1)
65472
+ return void 0;
65473
+ const chosen = pool.values().next().value;
65474
+ return this.linker.resolveIndexedNode?.(chosen) ?? chosen.node;
65475
+ }
64802
65476
  async resolveRelationshipTarget(source, targetText) {
64803
65477
  const local = this.findLocalRelationshipTarget(source, targetText);
64804
65478
  if (local)
@@ -66671,6 +67345,19 @@ var SysmlLinker = class extends DefaultLinker {
66671
67345
  const root3 = this.parsedLibraryDocument(nodeDescription.documentUri)?.parseResult?.value;
66672
67346
  return root3 ? this.astNodeLocator.getAstNode(root3, nodeDescription.path) : void 0;
66673
67347
  }
67348
+ /**
67349
+ * REQ-394 — Resolve an indexed description to a node WITHOUT adding a
67350
+ * document to the workspace.
67351
+ *
67352
+ * This is the same path a cross-reference takes (`loadAstNode`): an already
67353
+ * live node, otherwise the standard-library file parsed once off to the side
67354
+ * and cached here. Hover's documentation walk uses it so looking for an
67355
+ * inherited `doc` never pulls a library file into `LangiumDocuments`, and so
67356
+ * there is one lazy-library-load path rather than two.
67357
+ */
67358
+ resolveIndexedNode(nodeDescription) {
67359
+ return this.loadAstNode(nodeDescription);
67360
+ }
66674
67361
  /** Parse (once) the standard-library file a precomputed description points at.
66675
67362
  * Prefers an already-live workspace document when one exists (runtime-loaded
66676
67363
  * library, or the user opened the file) so there is never a second copy. */
@@ -67302,14 +67989,526 @@ function getNodeAtRange(document, params) {
67302
67989
  return leafAtOffset2(root3, offset)?.astNode;
67303
67990
  }
67304
67991
 
67992
+ // ../language-server/out/src/services/formatter-settings.js
67993
+ var FORMATTER_DEFAULTS = Object.freeze({
67994
+ indentStyle: "editor",
67995
+ indentSize: 0,
67996
+ blankLines: 1,
67997
+ lineWidth: 0,
67998
+ emptyBody: "preserve",
67999
+ quotedNames: "preserve",
68000
+ documentationIndent: "preserve",
68001
+ keywords: "preserve"
68002
+ });
68003
+ var sysmlFormatterSettings = { ...FORMATTER_DEFAULTS };
68004
+ var OPTION_PREFIX = "sysml.format.";
68005
+ var INDENT_STYLES = ["editor", "spaces", "tabs"];
68006
+ var EMPTY_BODY_STYLES = ["preserve", "semicolon", "braces"];
68007
+ var QUOTED_NAME_STYLES = ["preserve", "unquoteSafe"];
68008
+ var DOC_INDENT_STYLES = ["preserve", "align"];
68009
+ var KEYWORD_STYLES = ["preserve", "symbolic"];
68010
+ var MAX_INDENT_SIZE = 16;
68011
+ var MIN_LINE_WIDTH = 40;
68012
+ var MAX_LINE_WIDTH = 400;
68013
+ var MAX_BLANK_LINES = 2;
68014
+ function readEnum(name, value, allowed, fallback, issues) {
68015
+ if (value === void 0)
68016
+ return fallback;
68017
+ if (typeof value === "string" && allowed.includes(value))
68018
+ return value;
68019
+ issues.push({ setting: name, value, reason: `expected one of ${allowed.map((a2) => `'${a2}'`).join(", ")}` });
68020
+ return fallback;
68021
+ }
68022
+ function readInt(name, value, min2, max, fallback, issues, zeroMeans) {
68023
+ if (value === void 0)
68024
+ return fallback;
68025
+ if (typeof value === "number" && Number.isInteger(value) && (value === 0 && zeroMeans !== void 0 ? true : value >= min2 && value <= max)) {
68026
+ return value;
68027
+ }
68028
+ const range = zeroMeans !== void 0 ? `0 (${zeroMeans}) or ${min2}\u2013${max}` : `${min2}\u2013${max}`;
68029
+ issues.push({ setting: name, value, reason: `expected an integer ${range}` });
68030
+ return fallback;
68031
+ }
68032
+ function resolveFormatterSettings(raw, base = FORMATTER_DEFAULTS) {
68033
+ const issues = [];
68034
+ const source = raw && typeof raw === "object" ? raw : {};
68035
+ const settings = {
68036
+ indentStyle: readEnum("sysml.format.indentStyle", source.indentStyle, INDENT_STYLES, base.indentStyle, issues),
68037
+ indentSize: readInt("sysml.format.indentSize", source.indentSize, 1, MAX_INDENT_SIZE, base.indentSize, issues, "follow the editor"),
68038
+ blankLines: readInt("sysml.format.blankLines", source.blankLines, 0, MAX_BLANK_LINES, base.blankLines, issues),
68039
+ lineWidth: readInt("sysml.format.lineWidth", source.lineWidth, MIN_LINE_WIDTH, MAX_LINE_WIDTH, base.lineWidth, issues, "never wrap"),
68040
+ emptyBody: readEnum("sysml.format.emptyBody", source.emptyBody, EMPTY_BODY_STYLES, base.emptyBody, issues),
68041
+ quotedNames: readEnum("sysml.format.quotedNames", source.quotedNames, QUOTED_NAME_STYLES, base.quotedNames, issues),
68042
+ documentationIndent: readEnum("sysml.format.documentationIndent", source.documentationIndent, DOC_INDENT_STYLES, base.documentationIndent, issues),
68043
+ keywords: readEnum("sysml.format.keywords", source.keywords, KEYWORD_STYLES, base.keywords, issues)
68044
+ };
68045
+ return { settings, issues };
68046
+ }
68047
+ function resolveFormatterOptions(options, workspace = sysmlFormatterSettings) {
68048
+ const overrides = {};
68049
+ for (const [key, value] of Object.entries(options ?? {})) {
68050
+ if (key.startsWith(OPTION_PREFIX))
68051
+ overrides[key.slice(OPTION_PREFIX.length)] = value;
68052
+ }
68053
+ return resolveFormatterSettings(overrides, workspace);
68054
+ }
68055
+ function effectiveFormattingOptions(options, settings) {
68056
+ const insertSpaces = settings.indentStyle === "editor" ? options.insertSpaces : settings.indentStyle === "spaces";
68057
+ const tabSize = settings.indentSize === 0 ? options.tabSize : settings.indentSize;
68058
+ if (insertSpaces === options.insertSpaces && tabSize === options.tabSize)
68059
+ return options;
68060
+ return { ...options, insertSpaces, tabSize };
68061
+ }
68062
+ function indentUnit(options) {
68063
+ return options.insertSpaces ? " ".repeat(Math.max(1, options.tabSize)) : " ";
68064
+ }
68065
+
68066
+ // ../language-server/out/src/services/formatter-style.js
68067
+ var SYMBOLIC_KEYWORDS = Object.freeze({
68068
+ specializes: ":>",
68069
+ subsets: ":>",
68070
+ redefines: ":>>",
68071
+ references: "::>",
68072
+ crosses: "=>"
68073
+ });
68074
+ var TYPING_PHRASES = ["typed", "defined"];
68075
+ var BARE_ID2 = /^[_a-zA-Z]\w*$/u;
68076
+ var ESCAPED_NAME = /^'([^'\\]|\\.)*'$/u;
68077
+ function projectFormatted(document, edits) {
68078
+ const doc = document.textDocument;
68079
+ const source = doc.getText();
68080
+ const list = edits.map((edit) => ({
68081
+ start: doc.offsetAt(edit.range.start),
68082
+ end: doc.offsetAt(edit.range.end),
68083
+ newText: edit.newText
68084
+ })).sort((a2, b) => a2.start - b.start);
68085
+ let out = "";
68086
+ let cursor = 0;
68087
+ let delta = 0;
68088
+ const marks = [];
68089
+ for (const edit of list) {
68090
+ if (edit.start < cursor)
68091
+ continue;
68092
+ out += source.slice(cursor, edit.start) + edit.newText;
68093
+ delta += edit.newText.length - (edit.end - edit.start);
68094
+ marks.push({ at: edit.end, delta });
68095
+ cursor = edit.end;
68096
+ }
68097
+ out += source.slice(cursor);
68098
+ return {
68099
+ text: out,
68100
+ project(offset) {
68101
+ let low = 0;
68102
+ let high = marks.length - 1;
68103
+ let applied = 0;
68104
+ while (low <= high) {
68105
+ const mid = low + high >> 1;
68106
+ if (marks[mid].at <= offset) {
68107
+ applied = marks[mid].delta;
68108
+ low = mid + 1;
68109
+ } else {
68110
+ high = mid - 1;
68111
+ }
68112
+ }
68113
+ return offset + applied;
68114
+ }
68115
+ };
68116
+ }
68117
+ function lineAt(text, offset) {
68118
+ const start = text.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
68119
+ const nl = text.indexOf("\n", offset);
68120
+ const end = nl < 0 ? text.length : nl;
68121
+ const indent = /^[ \t]*/u.exec(text.slice(start, end))?.[0] ?? "";
68122
+ return { start, end, indent };
68123
+ }
68124
+ function leavesOf(document) {
68125
+ const rootCst = document.parseResult?.value?.$cstNode;
68126
+ return rootCst ? cst_utils_exports.flattenCst(rootCst).toArray() : [];
68127
+ }
68128
+ function nextLeaf(leaves, index, skipHidden) {
68129
+ for (let scan = index + 1; scan < leaves.length; scan++) {
68130
+ if (!skipHidden || !leaves[scan].hidden)
68131
+ return leaves[scan];
68132
+ }
68133
+ return void 0;
68134
+ }
68135
+ function keywordRewrites(leaves, out) {
68136
+ for (let index = 0; index < leaves.length; index++) {
68137
+ const leaf = leaves[index];
68138
+ if (leaf.hidden)
68139
+ continue;
68140
+ const symbol = SYMBOLIC_KEYWORDS[leaf.text];
68141
+ if (symbol) {
68142
+ out.push({ offset: leaf.offset, end: leaf.end, newText: symbol });
68143
+ continue;
68144
+ }
68145
+ if (TYPING_PHRASES.includes(leaf.text) && leaf.astNode?.$type === "Typing") {
68146
+ const by = nextLeaf(leaves, index, true);
68147
+ if (by?.text === "by" && by.astNode?.$type === "Typing") {
68148
+ out.push({ offset: leaf.offset, end: by.end, newText: ":" });
68149
+ }
68150
+ }
68151
+ }
68152
+ }
68153
+ var BRACE_ONLY_BODIES = /* @__PURE__ */ new Set([
68154
+ "InvShorthand",
68155
+ "ActionBlock",
68156
+ "EffectClause",
68157
+ "Import"
68158
+ ]);
68159
+ function emptyBraces(node, leaves, source) {
68160
+ const cst = node.$cstNode;
68161
+ if (!cst)
68162
+ return void 0;
68163
+ let open;
68164
+ let close;
68165
+ for (const leaf of leaves) {
68166
+ if (leaf.offset < cst.offset || leaf.end > cst.end || leaf.astNode !== node)
68167
+ continue;
68168
+ if (leaf.text === "{" && !open)
68169
+ open = leaf;
68170
+ if (leaf.text === "}")
68171
+ close = leaf;
68172
+ }
68173
+ if (!open || !close || close.offset < open.end)
68174
+ return void 0;
68175
+ return source.slice(open.end, close.offset).trim().length === 0 ? { open, close } : void 0;
68176
+ }
68177
+ function terminatingSemicolon(node, leaves) {
68178
+ const cst = node.$cstNode;
68179
+ if (!cst)
68180
+ return void 0;
68181
+ for (let index = leaves.length - 1; index >= 0; index--) {
68182
+ const leaf = leaves[index];
68183
+ if (leaf.end !== cst.end)
68184
+ continue;
68185
+ return leaf.text === ";" && leaf.astNode === node ? leaf : void 0;
68186
+ }
68187
+ return void 0;
68188
+ }
68189
+ function emptyBodyRewrites(document, leaves, projection, settings, options, out) {
68190
+ const root3 = document.parseResult?.value;
68191
+ if (!root3)
68192
+ return;
68193
+ const source = document.textDocument.getText();
68194
+ const leafBefore = /* @__PURE__ */ new Map();
68195
+ for (let index = 1; index < leaves.length; index++)
68196
+ leafBefore.set(leaves[index].offset, leaves[index - 1]);
68197
+ for (const node of ast_utils_exports.streamAllContents(root3)) {
68198
+ const members = node.members;
68199
+ if (!Array.isArray(members) || members.length > 0)
68200
+ continue;
68201
+ if (BRACE_ONLY_BODIES.has(node.$type))
68202
+ continue;
68203
+ if (settings.emptyBody === "semicolon") {
68204
+ const braces = emptyBraces(node, leaves, source);
68205
+ if (!braces)
68206
+ continue;
68207
+ const previous = leafBefore.get(braces.open.offset);
68208
+ const from = previous && previous.end <= braces.open.offset ? previous.end : braces.open.offset;
68209
+ out.push({ offset: from, end: braces.close.end, newText: ";" });
68210
+ continue;
68211
+ }
68212
+ if (settings.emptyBody === "braces") {
68213
+ if (emptyBraces(node, leaves, source))
68214
+ continue;
68215
+ const semicolon = terminatingSemicolon(node, leaves);
68216
+ if (!semicolon)
68217
+ continue;
68218
+ const indent = lineAt(projection.text, projection.project(node.$cstNode.offset)).indent;
68219
+ out.push({
68220
+ offset: semicolon.offset,
68221
+ end: semicolon.end,
68222
+ newText: ` {
68223
+ ${indent}}`
68224
+ });
68225
+ }
68226
+ }
68227
+ }
68228
+ function quotedNameRewrites(leaves, reservedKeywords, out) {
68229
+ const occurrences = /* @__PURE__ */ new Map();
68230
+ const declared = /* @__PURE__ */ new Set();
68231
+ const bareSpellings = /* @__PURE__ */ new Set();
68232
+ for (const leaf of leaves) {
68233
+ if (leaf.hidden)
68234
+ continue;
68235
+ if (BARE_ID2.test(leaf.text) && !reservedKeywords.has(leaf.text)) {
68236
+ bareSpellings.add(leaf.text);
68237
+ continue;
68238
+ }
68239
+ if (!ESCAPED_NAME.test(leaf.text))
68240
+ continue;
68241
+ const body = leaf.text.slice(1, -1);
68242
+ if (!BARE_ID2.test(body) || reservedKeywords.has(body))
68243
+ continue;
68244
+ const list = occurrences.get(body);
68245
+ if (list)
68246
+ list.push(leaf);
68247
+ else
68248
+ occurrences.set(body, [leaf]);
68249
+ if (grammar_utils_exports.findAssignment(leaf)?.feature === "name")
68250
+ declared.add(body);
68251
+ }
68252
+ for (const [body, list] of occurrences) {
68253
+ if (!declared.has(body) || bareSpellings.has(body))
68254
+ continue;
68255
+ for (const leaf of list)
68256
+ out.push({ offset: leaf.offset, end: leaf.end, newText: body });
68257
+ }
68258
+ }
68259
+ function documentationRewrites(document, leaves, projection, options, out) {
68260
+ for (const leaf of leaves) {
68261
+ if (leaf.hidden || !leaf.text.includes("\n"))
68262
+ continue;
68263
+ const owner = leaf.astNode?.$type;
68264
+ if (owner !== "DocCommentMember" && owner !== "CommentStmt")
68265
+ continue;
68266
+ if (!leaf.text.includes("/*"))
68267
+ continue;
68268
+ const projected = projection.project(leaf.offset);
68269
+ const line = lineAt(projection.text, projected);
68270
+ if (projection.text.slice(line.start, projected).trim().length > 0)
68271
+ continue;
68272
+ const target = line.indent;
68273
+ const bodyLines = leaf.text.split("\n");
68274
+ let common;
68275
+ for (let index = 1; index < bodyLines.length; index++) {
68276
+ const raw = bodyLines[index].replace(/\r$/u, "");
68277
+ if (raw.trim().length === 0)
68278
+ continue;
68279
+ const lead = /^[ \t]*/u.exec(raw)?.[0] ?? "";
68280
+ if (common === void 0 || lead.length < common.length)
68281
+ common = lead;
68282
+ }
68283
+ if (common === void 0)
68284
+ continue;
68285
+ let cursor = leaf.offset + bodyLines[0].length + 1;
68286
+ for (let index = 1; index < bodyLines.length; index++) {
68287
+ const raw = bodyLines[index];
68288
+ const lead = /^[ \t]*/u.exec(raw)?.[0] ?? "";
68289
+ if (raw.replace(/\r$/u, "").trim().length > 0) {
68290
+ const shifted = target + lead.slice(Math.min(common.length, lead.length));
68291
+ if (shifted !== lead)
68292
+ out.push({ offset: cursor, end: cursor + lead.length, newText: shifted });
68293
+ }
68294
+ cursor += raw.length + 1;
68295
+ }
68296
+ }
68297
+ }
68298
+ function wrapRewrites(leaves, projection, options, lineWidth, out) {
68299
+ const unit = indentUnit(options);
68300
+ for (let index = 0; index < leaves.length; index++) {
68301
+ const leaf = leaves[index];
68302
+ if (leaf.hidden || leaf.text !== ",")
68303
+ continue;
68304
+ const following = nextLeaf(leaves, index, false);
68305
+ if (!following)
68306
+ continue;
68307
+ const projected = projection.project(leaf.offset);
68308
+ const line = lineAt(projection.text, projected);
68309
+ if (line.end - line.start <= lineWidth)
68310
+ continue;
68311
+ if (projection.text.slice(projection.project(leaf.end), line.end).trim().length === 0)
68312
+ continue;
68313
+ if (projection.text.slice(projected, projection.project(following.offset)).includes("\n"))
68314
+ continue;
68315
+ out.push({
68316
+ offset: leaf.end,
68317
+ end: following.offset,
68318
+ newText: `
68319
+ ${line.indent}${unit}`
68320
+ });
68321
+ }
68322
+ }
68323
+ function withinRange(document, rewrite, range) {
68324
+ if (!range)
68325
+ return true;
68326
+ const doc = document.textDocument;
68327
+ return doc.offsetAt(range.start) <= rewrite.offset && rewrite.end <= doc.offsetAt(range.end);
68328
+ }
68329
+ function styleEdits(input) {
68330
+ const { document, settings, options, baseEdits, protectedRanges, range, reservedKeywords } = input;
68331
+ const wantsRewrite = settings.emptyBody !== "preserve" || settings.quotedNames !== "preserve" || settings.documentationIndent !== "preserve" || settings.keywords !== "preserve" || settings.lineWidth > 0;
68332
+ if (!wantsRewrite)
68333
+ return [];
68334
+ const leaves = leavesOf(document);
68335
+ if (leaves.length === 0)
68336
+ return [];
68337
+ const projection = projectFormatted(document, baseEdits);
68338
+ const rewrites = [];
68339
+ if (settings.keywords === "symbolic")
68340
+ keywordRewrites(leaves, rewrites);
68341
+ if (settings.emptyBody !== "preserve") {
68342
+ emptyBodyRewrites(document, leaves, projection, settings, options, rewrites);
68343
+ }
68344
+ if (settings.quotedNames === "unquoteSafe")
68345
+ quotedNameRewrites(leaves, reservedKeywords, rewrites);
68346
+ if (settings.documentationIndent === "align") {
68347
+ documentationRewrites(document, leaves, projection, options, rewrites);
68348
+ }
68349
+ if (settings.lineWidth > 0)
68350
+ wrapRewrites(leaves, projection, options, settings.lineWidth, rewrites);
68351
+ const doc = document.textDocument;
68352
+ const source = doc.getText();
68353
+ const accepted = [];
68354
+ let lastEnd = -1;
68355
+ for (const rewrite of rewrites.sort((a2, b) => a2.offset - b.offset || a2.end - b.end)) {
68356
+ if (rewrite.offset < lastEnd)
68357
+ continue;
68358
+ if (source.slice(rewrite.offset, rewrite.end) === rewrite.newText)
68359
+ continue;
68360
+ if (intersectsProtected(protectedRanges, rewrite.offset, rewrite.end))
68361
+ continue;
68362
+ if (!withinRange(document, rewrite, range))
68363
+ continue;
68364
+ accepted.push(rewrite);
68365
+ lastEnd = rewrite.end;
68366
+ }
68367
+ return accepted.map((rewrite) => ({
68368
+ range: { start: doc.positionAt(rewrite.offset), end: doc.positionAt(rewrite.end) },
68369
+ newText: rewrite.newText
68370
+ }));
68371
+ }
68372
+
67305
68373
  // ../language-server/out/src/services/formatter.js
67306
68374
  var INDENT = Formatting.indent({ allowMore: true });
67307
68375
  var DEDENT = Formatting.noIndent();
67308
68376
  var NL = Formatting.newLine();
67309
- var NL2 = Formatting.newLines(2);
67310
68377
  var SPACE = Formatting.oneSpace();
67311
68378
  var NO_SPACE = Formatting.noSpace();
67312
68379
  var SysmlFormatter = class extends AbstractFormatter {
68380
+ services;
68381
+ /**
68382
+ * The settings in force for the request being served. Formatting is
68383
+ * synchronous from `formatDocument` through the AST pass, so a field is the
68384
+ * whole of the plumbing the base class does not offer.
68385
+ */
68386
+ active = FORMATTER_DEFAULTS;
68387
+ keywordCache;
68388
+ constructor(services) {
68389
+ super();
68390
+ this.services = services;
68391
+ }
68392
+ // REQ-392, REQ-393 — Full-document formatting.
68393
+ formatDocument(document, params) {
68394
+ return this.formatWith(document, params.options, (options) => super.formatDocument(document, { ...params, options }));
68395
+ }
68396
+ // REQ-392, REQ-393 — Range formatting. A range that reaches into a preserved
68397
+ // element formats the rest of the range and leaves that element whole: the
68398
+ // base formatter narrows to the range, and the same filter then drops
68399
+ // whatever would have rewritten a frozen slice, so a partial rewrite is
68400
+ // impossible. The settings are resolved identically to the full-document
68401
+ // path, so the two cannot produce different style.
68402
+ formatDocumentRange(document, params) {
68403
+ return this.formatWith(document, params.options, (options) => super.formatDocumentRange(document, { ...params, options }), params.range);
68404
+ }
68405
+ // REQ-392, REQ-393 — Format-on-type formats the current line.
68406
+ formatDocumentOnType(document, params) {
68407
+ const line = { start: { line: params.position.line, character: 0 }, end: params.position };
68408
+ return this.formatWith(document, params.options, (options) => super.formatDocumentOnType(document, { ...params, options }), line);
68409
+ }
68410
+ /**
68411
+ * REQ-392, REQ-393 — One pipeline for all three entry points: resolve the
68412
+ * settings, run the base whitespace pass under them, drop whatever a
68413
+ * `sysml-format ignore` froze, then add the token-level rewrites the style
68414
+ * options ask for.
68415
+ */
68416
+ formatWith(document, requested, base, range) {
68417
+ const { settings } = resolveFormatterOptions(requested);
68418
+ const options = effectiveFormattingOptions(requested, settings);
68419
+ this.active = settings;
68420
+ const protectedRanges = scanFormatDirectives(document).protectedRanges;
68421
+ const baseEdits = this.preserveIgnored(document, base(options), protectedRanges);
68422
+ const style = styleEdits({
68423
+ document,
68424
+ settings,
68425
+ options,
68426
+ baseEdits,
68427
+ protectedRanges,
68428
+ range,
68429
+ reservedKeywords: this.reservedKeywords()
68430
+ });
68431
+ return this.merge(document, baseEdits, style);
68432
+ }
68433
+ /**
68434
+ * REQ-392 — Drop every edit that reaches inside a preserved element.
68435
+ *
68436
+ * Langium only ever edits the whitespace BETWEEN two tokens, so an edit is
68437
+ * either wholly inside a preserved slice (dropped, leaving the slice
68438
+ * byte-for-byte identical, line endings included) or wholly outside it
68439
+ * (kept, so the element's own indentation and its surroundings are still
68440
+ * formatted).
68441
+ */
68442
+ preserveIgnored(document, edits, ranges) {
68443
+ if (edits.length === 0 || ranges.length === 0)
68444
+ return edits;
68445
+ const text = document.textDocument;
68446
+ return edits.filter((edit) => !intersectsProtected(ranges, text.offsetAt(edit.range.start), text.offsetAt(edit.range.end)));
68447
+ }
68448
+ /**
68449
+ * REQ-393 — Combine the whitespace pass with the token rewrites. A token
68450
+ * rewrite wins where the two touch: `part def P { }` → `part def P;` has to
68451
+ * swallow the space the whitespace pass wanted to put before the brace.
68452
+ */
68453
+ merge(document, baseEdits, style) {
68454
+ if (style.length === 0)
68455
+ return baseEdits;
68456
+ const text = document.textDocument;
68457
+ const spans = style.map((edit) => ({
68458
+ offset: text.offsetAt(edit.range.start),
68459
+ end: text.offsetAt(edit.range.end)
68460
+ }));
68461
+ const kept = baseEdits.filter((edit) => {
68462
+ const start = text.offsetAt(edit.range.start);
68463
+ const end = text.offsetAt(edit.range.end);
68464
+ return !spans.some((span) => start < span.end && end > span.offset);
68465
+ });
68466
+ return [...kept, ...style].sort((a2, b) => text.offsetAt(a2.range.start) - text.offsetAt(b.range.start));
68467
+ }
68468
+ /**
68469
+ * REQ-393 — Every keyword the grammar reserves. A quoted name may only lose
68470
+ * its quotes when the bare spelling would still be a name and not a keyword,
68471
+ * and the grammar is the only honest source for that list.
68472
+ */
68473
+ reservedKeywords() {
68474
+ if (this.keywordCache)
68475
+ return this.keywordCache;
68476
+ const keywords = /* @__PURE__ */ new Set();
68477
+ const grammar = this.services?.Grammar;
68478
+ if (grammar) {
68479
+ for (const node of ast_utils_exports.streamAllContents(grammar)) {
68480
+ if (node.$type === "Keyword") {
68481
+ const value = node.value;
68482
+ if (value && /^[_a-zA-Z]\w*$/u.test(value))
68483
+ keywords.add(value);
68484
+ }
68485
+ }
68486
+ }
68487
+ this.keywordCache = keywords;
68488
+ return keywords;
68489
+ }
68490
+ /**
68491
+ * REQ-392 — Keep the indentation level a comment interrupts.
68492
+ *
68493
+ * Langium 3.5's `createTextEdit` returns early for a hidden node and never
68494
+ * applies the formatting action's indentation move to the running context.
68495
+ * `{` carries the block's `indent` action, so a comment sitting between `{`
68496
+ * and the first member — which is exactly where a `sysml-format ignore`
68497
+ * directive goes — dropped the whole block back to column 0. Applying the
68498
+ * move ourselves restores the level a composite node would have kept, and
68499
+ * leaves every other hidden-node path untouched.
68500
+ */
68501
+ createTextEdit(a2, b, formatting, context) {
68502
+ const edits = super.createTextEdit(a2, b, formatting, context);
68503
+ if (!b.hidden)
68504
+ return edits;
68505
+ const startRange = {
68506
+ start: { line: b.range.start.line, character: 0 },
68507
+ end: b.range.start
68508
+ };
68509
+ context.indentation += this.findFittingMove(startRange, formatting.moves, context)?.tabs ?? 0;
68510
+ return edits;
68511
+ }
67313
68512
  // REQ-259 — Dispatch formatting from AST node kind
67314
68513
  format(node) {
67315
68514
  if (isDocument(node)) {
@@ -67417,13 +68616,28 @@ var SysmlFormatter = class extends AbstractFormatter {
67417
68616
  f.keyword(first2).append(SPACE);
67418
68617
  f.keyword(second).prepend(SPACE);
67419
68618
  }
68619
+ /**
68620
+ * REQ-393 — Top-level element separation is decided HERE and nowhere else.
68621
+ *
68622
+ * The first element gets `noSpace`, which erases leading blank lines rather
68623
+ * than adding two of them: the document-level rule and the element's own
68624
+ * `prepend` used to fire at the same offset, and two insertions at one
68625
+ * position stack, so every file that began with `package` gained blank
68626
+ * lines at the top on every format. `blankLines` decides the rest.
68627
+ */
67420
68628
  fmtDocument(node) {
67421
68629
  const f = this.getNodeFormatter(node);
67422
- f.properties("elements").prepend(NL2);
68630
+ const separator = Formatting.newLines(1 + this.active.blankLines);
68631
+ for (let index = 0; index < node.elements.length; index++) {
68632
+ f.property("elements", index).prepend(index === 0 ? NO_SPACE : separator);
68633
+ }
67423
68634
  }
67424
68635
  fmtBlock(node, keyword) {
67425
68636
  const f = this.getNodeFormatter(node);
67426
- f.keyword(keyword).prepend(NL2).append(SPACE);
68637
+ if (!isDocument(node.$container)) {
68638
+ f.keyword(keyword).prepend(Formatting.newLines(1 + this.active.blankLines));
68639
+ }
68640
+ f.keyword(keyword).append(SPACE);
67427
68641
  this.fmtBraces(f);
67428
68642
  }
67429
68643
  // REQ-355 — Filtered imports keep the `[@Meta]` condition tight against the path
@@ -67443,7 +68657,9 @@ var SysmlFormatter = class extends AbstractFormatter {
67443
68657
  // REQ-258, REQ-260 — Format one element per line with canonical keyword spacing
67444
68658
  fmtElement(node, keyword) {
67445
68659
  const f = this.getNodeFormatter(node);
67446
- f.keyword(keyword).prepend(NL).append(SPACE);
68660
+ if (!isDocument(node.$container))
68661
+ f.keyword(keyword).prepend(NL);
68662
+ f.keyword(keyword).append(SPACE);
67447
68663
  if (node.isDef) {
67448
68664
  f.keyword("def").prepend(SPACE).append(SPACE);
67449
68665
  }
@@ -67457,7 +68673,9 @@ var SysmlFormatter = class extends AbstractFormatter {
67457
68673
  // REQ-260 — Keep multi-word `verification case` keyword phrase together
67458
68674
  fmtVerificationCase(node) {
67459
68675
  const f = this.getNodeFormatter(node);
67460
- f.keyword("verification").prepend(NL).append(SPACE);
68676
+ if (!isDocument(node.$container))
68677
+ f.keyword("verification").prepend(NL);
68678
+ f.keyword("verification").append(SPACE);
67461
68679
  f.keyword("case").append(SPACE);
67462
68680
  if (node.isDef)
67463
68681
  f.keyword("def").append(SPACE);
@@ -67468,7 +68686,9 @@ var SysmlFormatter = class extends AbstractFormatter {
67468
68686
  // REQ-260 — Keep multi-word `analysis case` keyword phrase together
67469
68687
  fmtAnalysisCase(node) {
67470
68688
  const f = this.getNodeFormatter(node);
67471
- f.keyword("analysis").prepend(NL).append(SPACE);
68689
+ if (!isDocument(node.$container))
68690
+ f.keyword("analysis").prepend(NL);
68691
+ f.keyword("analysis").append(SPACE);
67472
68692
  f.keyword("case").append(SPACE);
67473
68693
  if (node.isDef)
67474
68694
  f.keyword("def").append(SPACE);
@@ -67479,7 +68699,9 @@ var SysmlFormatter = class extends AbstractFormatter {
67479
68699
  // REQ-260 — Keep multi-word `use case` keyword phrase together
67480
68700
  fmtUseCase(node) {
67481
68701
  const f = this.getNodeFormatter(node);
67482
- f.keyword("use").prepend(NL).append(SPACE);
68702
+ if (!isDocument(node.$container))
68703
+ f.keyword("use").prepend(NL);
68704
+ f.keyword("use").append(SPACE);
67483
68705
  f.keyword("case").append(SPACE);
67484
68706
  if (node.isDef)
67485
68707
  f.keyword("def").append(SPACE);
@@ -67668,14 +68890,87 @@ function typeAnchor(node, refText) {
67668
68890
  }
67669
68891
  return void 0;
67670
68892
  }
68893
+ var EXPLICIT_SPECIALIZATION_KINDS = /* @__PURE__ */ new Set([
68894
+ ":>",
68895
+ ":>>",
68896
+ "::>",
68897
+ "=>",
68898
+ "specializes",
68899
+ "subsets",
68900
+ "redefines",
68901
+ "references",
68902
+ "crosses",
68903
+ "conjugates"
68904
+ ]);
68905
+ var REDEFINITION_KINDS2 = /* @__PURE__ */ new Set([":>>", "redefines"]);
68906
+ var IMPLICIT_BASES = Object.freeze({
68907
+ PartDecl: { def: "Parts::Part", usage: "Parts::parts" },
68908
+ ItemDecl: { def: "Items::Item", usage: "Items::items" },
68909
+ PortDecl: { def: "Ports::Port", usage: "Ports::ports" },
68910
+ ActionDecl: { def: "Actions::Action", usage: "Actions::actions" },
68911
+ StateDecl: { def: "States::StateAction", usage: "States::stateActions" },
68912
+ ConnectionDecl: { def: "Connections::Connection", usage: "Connections::connections" },
68913
+ InterfaceDecl: { def: "Interfaces::Interface", usage: "Interfaces::interfaces" },
68914
+ CaseDecl: { def: "Cases::Case", usage: "Cases::cases" },
68915
+ UseCaseDecl: { def: "UseCases::UseCase", usage: "UseCases::useCases" },
68916
+ AnalysisCaseDecl: { def: "AnalysisCases::AnalysisCase", usage: "AnalysisCases::analysisCases" },
68917
+ VerificationCaseDecl: { def: "VerificationCases::VerificationCase", usage: "VerificationCases::verificationCases" },
68918
+ ViewDecl: { def: "Views::View", usage: "Views::views" },
68919
+ ViewpointDecl: { def: "Views::ViewpointCheck", usage: "Views::viewpointChecks" },
68920
+ RenderingDecl: { def: "Views::Rendering", usage: "Views::renderings" },
68921
+ RequirementDecl: { def: "Requirements::RequirementCheck", usage: "Requirements::requirementChecks" },
68922
+ ConcernDecl: { def: "Requirements::ConcernCheck", usage: "Requirements::concernChecks" },
68923
+ ConstraintDecl: { def: "Constraints::ConstraintCheck", usage: "Constraints::constraintChecks" },
68924
+ CalcDecl: { def: "Calculations::Calculation", usage: "Calculations::calculations" },
68925
+ AllocationDecl: { def: "Allocations::Allocation", usage: "Allocations::allocations" },
68926
+ AttributeDecl: { def: "Attributes::AttributeValue", usage: "Attributes::attributeValues" },
68927
+ OccurrenceDecl: { def: "Occurrences::Occurrence", usage: "Occurrences::occurrences" },
68928
+ MetadataDecl: { def: "Metadata::MetadataItem", usage: "Metadata::metadataItems" }
68929
+ });
68930
+ function markdown(value) {
68931
+ return { kind: import_vscode_languageserver20.MarkupKind.Markdown, value };
68932
+ }
68933
+ function allRelationships2(node) {
68934
+ return [...node.preRelationships ?? [], ...node.relationships ?? []];
68935
+ }
68936
+ function hasExplicitSpecialization(node) {
68937
+ if (node.typing?.type?.$refText)
68938
+ return true;
68939
+ return allRelationships2(node).some((rel) => rel.kind && EXPLICIT_SPECIALIZATION_KINDS.has(rel.kind));
68940
+ }
68941
+ function keywordLeaf(node, keyword) {
68942
+ const cst = node.$cstNode;
68943
+ if (!cst)
68944
+ return void 0;
68945
+ return cst_utils_exports.flattenCst(cst).find((leaf) => leaf.text === keyword && !leaf.hidden);
68946
+ }
68947
+ function descriptionRange(description) {
68948
+ const segment = description.nameSegment ?? description.selectionSegment;
68949
+ return segment?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
68950
+ }
67671
68951
  var SysmlInlayHintProvider = class {
68952
+ services;
68953
+ /**
68954
+ * Resolved implicit bases, keyed by the qualified name in
68955
+ * {@link IMPLICIT_BASES}. The standard library does not change once it is
68956
+ * loaded, so this is built at most once for real; while it is still empty —
68957
+ * before the library has been indexed — the rebuild is over a tiny index and
68958
+ * costs nothing, which is what keeps the hint path free of a full index scan
68959
+ * on every request.
68960
+ */
68961
+ baseCache = /* @__PURE__ */ new Map();
68962
+ constructor(services) {
68963
+ this.services = services;
68964
+ }
67672
68965
  getInlayHints(document, _params) {
67673
68966
  const root3 = document.parseResult?.value;
67674
- if (!root3)
68967
+ if (!root3 || !sysmlInlayHintSettings.enabled)
67675
68968
  return void 0;
68969
+ const settings = sysmlInlayHintSettings;
67676
68970
  const hints = [];
67677
68971
  for (const node of allAstNodes(root3)) {
67678
- if ((isPartDecl(node) || isPortDecl(node) || isAttributeDecl(node)) && !node.isDef && !node.multiplicity && node.$cstNode && node.name) {
68972
+ const decl = node;
68973
+ if (settings.multiplicity && (isPartDecl(node) || isPortDecl(node) || isAttributeDecl(node)) && !decl.isDef && !decl.multiplicity && node.$cstNode && decl.name) {
67679
68974
  hints.push({
67680
68975
  position: multiplicityAnchor(node.$cstNode),
67681
68976
  label: " [1]",
@@ -67683,7 +68978,65 @@ var SysmlInlayHintProvider = class {
67683
68978
  paddingLeft: true
67684
68979
  });
67685
68980
  }
67686
- if (!sysmlInlayHintSettings.dimension)
68981
+ if (settings.visibility && isImport(node) && !decl.visibility) {
68982
+ const keyword = keywordLeaf(node, "import");
68983
+ if (keyword) {
68984
+ const at = keyword.range.start;
68985
+ hints.push({
68986
+ position: at,
68987
+ label: "private",
68988
+ kind: import_vscode_languageserver20.InlayHintKind.Type,
68989
+ paddingRight: true,
68990
+ tooltip: markdown("An `import` that declares no visibility is **private**: the names it brings in are not re-exported from this namespace."),
68991
+ // Writing the modifier is mechanically safe — `private`
68992
+ // is exactly what the grammar accepts here, and the text
68993
+ // then says what it already meant.
68994
+ textEdits: [{ range: { start: at, end: at }, newText: "private " }]
68995
+ });
68996
+ }
68997
+ }
68998
+ if (settings.modifiers && isAttributeDecl(node) && !decl.isDef && !(decl.modifiers ?? []).includes("ref")) {
68999
+ const keyword = keywordLeaf(node, "attribute");
69000
+ if (keyword) {
69001
+ const at = keyword.range.start;
69002
+ hints.push({
69003
+ position: at,
69004
+ label: "ref",
69005
+ kind: import_vscode_languageserver20.InlayHintKind.Type,
69006
+ paddingRight: true,
69007
+ tooltip: markdown("An attribute usage is always referential (`ref`); it is never a composite feature of its owner."),
69008
+ textEdits: [{ range: { start: at, end: at }, newText: "ref " }]
69009
+ });
69010
+ }
69011
+ }
69012
+ if (settings.specialization && node.$cstNode && decl.name && !hasExplicitSpecialization(decl)) {
69013
+ const entry = IMPLICIT_BASES[node.$type];
69014
+ const qualified = entry ? decl.isDef ? entry.def : entry.usage : void 0;
69015
+ const description = qualified ? this.resolveImplicitBase(qualified, decl.isDef !== true) : void 0;
69016
+ if (qualified && description) {
69017
+ hints.push({
69018
+ position: multiplicityAnchor(node.$cstNode),
69019
+ label: [{
69020
+ value: ` :> ${qualified}`,
69021
+ location: {
69022
+ uri: description.documentUri.toString(),
69023
+ range: descriptionRange(description)
69024
+ }
69025
+ }],
69026
+ kind: import_vscode_languageserver20.InlayHintKind.Type,
69027
+ paddingLeft: true,
69028
+ tooltip: markdown(`Implicitly specializes \`${qualified}\` from the standard library.`)
69029
+ });
69030
+ }
69031
+ }
69032
+ if (settings.redefinition)
69033
+ hints.push(...this.parameterRedefinitionHints(decl));
69034
+ if (settings.effectiveNames) {
69035
+ const effective = effectiveNameHint(decl);
69036
+ if (effective)
69037
+ hints.push(effective);
69038
+ }
69039
+ if (!settings.dimension)
67687
69040
  continue;
67688
69041
  if (isNumericPrimary(node)) {
67689
69042
  const symbol = node.unit?.$refText;
@@ -67699,7 +69052,7 @@ var SysmlInlayHintProvider = class {
67699
69052
  continue;
67700
69053
  }
67701
69054
  if (isAttributeDecl(node)) {
67702
- const refText = node.typing?.type?.$refText;
69055
+ const refText = decl.typing?.type?.$refText;
67703
69056
  const label = refText ? quantityTypeDimensionLabel(refText) : void 0;
67704
69057
  const position = refText ? typeAnchor(node, refText) : void 0;
67705
69058
  if (label && position) {
@@ -67714,7 +69067,102 @@ var SysmlInlayHintProvider = class {
67714
69067
  }
67715
69068
  return hints;
67716
69069
  }
69070
+ /**
69071
+ * REQ-395 — Positional parameter correspondence.
69072
+ *
69073
+ * A usage typed by a definition redefines that definition's directed
69074
+ * parameters by position ([OMG SysML v2 Part 1 §7.16.4] — the same rule
69075
+ * `SSM025` checks). The definition has to be RESOLVED for the hint to exist,
69076
+ * and a parameter that already redefines something explicitly is left alone.
69077
+ */
69078
+ parameterRedefinitionHints(node) {
69079
+ const definition = node.typing?.type?.ref;
69080
+ if (!definition || node.isDef)
69081
+ return [];
69082
+ const mine = directedParameters(node);
69083
+ if (mine.length === 0)
69084
+ return [];
69085
+ const theirs = directedParameters(definition);
69086
+ const hints = [];
69087
+ for (let index = 0; index < mine.length && index < theirs.length; index++) {
69088
+ const parameter = mine[index].node;
69089
+ if (!parameter.name || !parameter.$cstNode)
69090
+ continue;
69091
+ if (allRelationships2(parameter).some((rel) => rel.kind && REDEFINITION_KINDS2.has(rel.kind)))
69092
+ continue;
69093
+ const target = theirs[index].node;
69094
+ if (!target.name || target.name === parameter.name)
69095
+ continue;
69096
+ const uri = ast_utils_exports.getDocument(target).uri.toString();
69097
+ const range = target.$cstNode?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
69098
+ hints.push({
69099
+ position: multiplicityAnchor(parameter.$cstNode),
69100
+ label: [{ value: ` :>> ${target.name}`, location: { uri, range } }],
69101
+ kind: import_vscode_languageserver20.InlayHintKind.Type,
69102
+ paddingLeft: true,
69103
+ tooltip: markdown(`Parameter ${index + 1} implicitly redefines \`${target.name}\` of \`${node.typing?.type?.$refText ?? "its definition"}\`.`)
69104
+ });
69105
+ }
69106
+ return hints;
69107
+ }
69108
+ /**
69109
+ * REQ-395 — Find one implicit base in the index.
69110
+ *
69111
+ * A description whose name is the full qualified spelling wins outright. The
69112
+ * index otherwise keys library symbols by their simple name, so the fallback
69113
+ * requires BOTH a standard-library document and the right side of the
69114
+ * definition/usage split, which the precomputed index records — that is what
69115
+ * keeps `Parts::Part` from matching a `part Part` somewhere else.
69116
+ */
69117
+ resolveImplicitBase(qualified, wantUsage) {
69118
+ const cached = this.baseCache.get(qualified);
69119
+ if (cached)
69120
+ return cached;
69121
+ const index = this.services?.shared.workspace.IndexManager;
69122
+ if (!index)
69123
+ return void 0;
69124
+ const simple = lastSegment3(qualified);
69125
+ let fallback;
69126
+ for (const description of index.allElements()) {
69127
+ if (description.name === qualified) {
69128
+ this.baseCache.set(qualified, description);
69129
+ return description;
69130
+ }
69131
+ if (fallback || description.name !== simple)
69132
+ continue;
69133
+ const uri = description.documentUri instanceof URI2 ? description.documentUri : URI2.parse(String(description.documentUri));
69134
+ if (!isStandardLibraryUri(uri))
69135
+ continue;
69136
+ if (description.isUsage === true !== wantUsage)
69137
+ continue;
69138
+ fallback = description;
69139
+ }
69140
+ if (fallback)
69141
+ this.baseCache.set(qualified, fallback);
69142
+ return fallback;
69143
+ }
67717
69144
  };
69145
+ function effectiveNameHint(node) {
69146
+ if (node.name || node.shortName?.name || !node.$cstNode)
69147
+ return void 0;
69148
+ const redefinition = allRelationships2(node).find((rel) => rel.kind && REDEFINITION_KINDS2.has(rel.kind) && (rel.targets?.length ?? 0) > 0);
69149
+ const target = redefinition?.targets?.[0];
69150
+ if (!target)
69151
+ return void 0;
69152
+ const effective = lastSegment3(target);
69153
+ if (!effective)
69154
+ return void 0;
69155
+ const operator = cst_utils_exports.flattenCst(node.$cstNode).find((leaf) => !leaf.hidden && redefinition?.kind !== void 0 && leaf.text === redefinition.kind);
69156
+ if (!operator)
69157
+ return void 0;
69158
+ return {
69159
+ position: operator.range.start,
69160
+ label: effective,
69161
+ kind: import_vscode_languageserver20.InlayHintKind.Parameter,
69162
+ paddingRight: true,
69163
+ tooltip: markdown(`Effective name: \`${effective}\`, taken from the feature this one redefines.`)
69164
+ };
69165
+ }
67718
69166
 
67719
69167
  // ../language-server/out/src/services/requirement-eval.js
67720
69168
  var UNRESOLVED = { kind: "unresolved" };
@@ -68923,9 +70371,13 @@ var SysmlLspModule = {
68923
70371
  // Epic G
68924
70372
  RenameProvider: (services) => new SysmlRenameProvider(services),
68925
70373
  CodeActionProvider: (services) => new SysmlCodeActionProvider(services),
68926
- Formatter: () => new SysmlFormatter(),
70374
+ // REQ-393 the formatter reads the grammar's keyword set for the
70375
+ // quoted-name decision, so it is built with services.
70376
+ Formatter: (services) => new SysmlFormatter(services),
68927
70377
  SemanticTokenProvider: (services) => new SysmlSemanticTokenProvider(services),
68928
- InlayHintProvider: () => new SysmlInlayHintProvider(),
70378
+ // REQ-395 the provider resolves implicit standard-library bases
70379
+ // through the workspace/library index, so it is built with services.
70380
+ InlayHintProvider: (services) => new SysmlInlayHintProvider(services),
68929
70381
  CodeLensProvider: (services) => new SysmlCodeLensProvider(services),
68930
70382
  DocumentHighlightProvider: (services) => new SysmlDocumentHighlightProvider(services),
68931
70383
  DefinitionProvider: (services) => new SysmlDefinitionProvider(services),
@@ -70024,7 +71476,7 @@ async function runValidation(command) {
70024
71476
  }
70025
71477
 
70026
71478
  // src/main.ts
70027
- var VERSION2 = true ? "0.15.8" : "dev";
71479
+ var VERSION2 = true ? "0.17.0" : "dev";
70028
71480
  async function main(argv) {
70029
71481
  const command = parseArgs(argv);
70030
71482
  if (command.kind === "help") {