sysml-validate 0.15.7 → 0.16.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 +1118 -37
- package/out/main.js.map +4 -4
- package/package.json +1 -1
- package/resources/sysml.dimension-table.json +1 -1
- package/resources/sysml.library.index.json +1 -1
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
|
|
4112
|
-
(function(
|
|
4113
|
-
|
|
4114
|
-
|
|
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 ===
|
|
4117
|
+
return candidate === MarkupKind5.PlainText || candidate === MarkupKind5.Markdown;
|
|
4118
4118
|
}
|
|
4119
|
-
|
|
4120
|
-
})(
|
|
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) &&
|
|
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(
|
|
24192
|
-
|
|
24193
|
-
|
|
24191
|
+
(function(MarkupKind4) {
|
|
24192
|
+
MarkupKind4.PlainText = "plaintext";
|
|
24193
|
+
MarkupKind4.Markdown = "markdown";
|
|
24194
24194
|
function is(value) {
|
|
24195
24195
|
const candidate = value;
|
|
24196
|
-
return candidate ===
|
|
24196
|
+
return candidate === MarkupKind4.PlainText || candidate === MarkupKind4.Markdown;
|
|
24197
24197
|
}
|
|
24198
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -58685,7 +58692,16 @@ var DIAGNOSTIC_MESSAGES = {
|
|
|
58685
58692
|
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
58693
|
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
58694
|
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
|
|
58695
|
+
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.`,
|
|
58696
|
+
// REQ-392 — a `sysml-format` comment the formatter cannot act on. Advisory:
|
|
58697
|
+
// the directive is inert, and saying so beats leaving the author to wonder
|
|
58698
|
+
// why their layout was reformatted anyway.
|
|
58699
|
+
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.",
|
|
58700
|
+
// REQ-345, REQ-393 — the formatter-settings refusal is shown to the user
|
|
58701
|
+
// through `window/showWarningMessage`, so its text lives here like every
|
|
58702
|
+
// other reviewable string rather than inline at the call site.
|
|
58703
|
+
FORMAT_SETTINGS_REFUSED: (detail) => `SysML formatter settings ignored (${detail}). The previous value is still in use.`,
|
|
58704
|
+
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
58705
|
};
|
|
58690
58706
|
var KEYWORD_TOOLTIPS = {
|
|
58691
58707
|
// Package / namespace
|
|
@@ -58882,6 +58898,107 @@ var OPERATOR_TOOLTIPS = {
|
|
|
58882
58898
|
"@@": { 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
58899
|
};
|
|
58884
58900
|
|
|
58901
|
+
// ../language-server/out/src/services/format-directives.js
|
|
58902
|
+
var DIRECTIVE_WORD = "sysml-format";
|
|
58903
|
+
var IGNORE_ARGUMENT = "ignore";
|
|
58904
|
+
var EMPTY_SCAN = { directives: [], protectedRanges: [] };
|
|
58905
|
+
function commentBody(text) {
|
|
58906
|
+
if (text.startsWith("//*") && text.endsWith("*/") && text.length >= 5)
|
|
58907
|
+
return text.slice(3, -2);
|
|
58908
|
+
if (text.startsWith("//"))
|
|
58909
|
+
return text.slice(2);
|
|
58910
|
+
if (text.startsWith("/*") && text.endsWith("*/") && text.length >= 4)
|
|
58911
|
+
return text.slice(2, -2);
|
|
58912
|
+
return void 0;
|
|
58913
|
+
}
|
|
58914
|
+
function classify(text) {
|
|
58915
|
+
const body = commentBody(text);
|
|
58916
|
+
if (body === void 0)
|
|
58917
|
+
return void 0;
|
|
58918
|
+
const trimmed = body.trim();
|
|
58919
|
+
if (trimmed !== DIRECTIVE_WORD && !trimmed.startsWith(`${DIRECTIVE_WORD} `) && !trimmed.startsWith(`${DIRECTIVE_WORD} `)) {
|
|
58920
|
+
return void 0;
|
|
58921
|
+
}
|
|
58922
|
+
const argument = trimmed.slice(DIRECTIVE_WORD.length).trim();
|
|
58923
|
+
return { kind: argument === IGNORE_ARGUMENT ? "ignore" : "malformed", argument };
|
|
58924
|
+
}
|
|
58925
|
+
function looksLikeFormatDirective(text) {
|
|
58926
|
+
return text.includes(DIRECTIVE_WORD);
|
|
58927
|
+
}
|
|
58928
|
+
function nodesByStartOffset(root3) {
|
|
58929
|
+
const byOffset = /* @__PURE__ */ new Map();
|
|
58930
|
+
for (const node of ast_utils_exports.streamAllContents(root3)) {
|
|
58931
|
+
const cst = node.$cstNode;
|
|
58932
|
+
if (!cst)
|
|
58933
|
+
continue;
|
|
58934
|
+
const existing = byOffset.get(cst.offset);
|
|
58935
|
+
if (!existing || (existing.$cstNode?.end ?? 0) < cst.end)
|
|
58936
|
+
byOffset.set(cst.offset, node);
|
|
58937
|
+
}
|
|
58938
|
+
return byOffset;
|
|
58939
|
+
}
|
|
58940
|
+
function mergeSlices(slices) {
|
|
58941
|
+
const sorted = [...slices].sort((a2, b) => a2.offset - b.offset || a2.end - b.end);
|
|
58942
|
+
const merged = [];
|
|
58943
|
+
for (const slice of sorted) {
|
|
58944
|
+
const last2 = merged.at(-1);
|
|
58945
|
+
if (last2 && slice.offset <= last2.end) {
|
|
58946
|
+
last2.end = Math.max(last2.end, slice.end);
|
|
58947
|
+
} else {
|
|
58948
|
+
merged.push({ ...slice });
|
|
58949
|
+
}
|
|
58950
|
+
}
|
|
58951
|
+
return merged;
|
|
58952
|
+
}
|
|
58953
|
+
function scanFormatDirectives(document) {
|
|
58954
|
+
const root3 = document.parseResult?.value;
|
|
58955
|
+
const rootCst = root3?.$cstNode;
|
|
58956
|
+
if (!root3 || !rootCst)
|
|
58957
|
+
return EMPTY_SCAN;
|
|
58958
|
+
const text = document.textDocument.getText();
|
|
58959
|
+
if (!looksLikeFormatDirective(text))
|
|
58960
|
+
return EMPTY_SCAN;
|
|
58961
|
+
const leaves = cst_utils_exports.flattenCst(rootCst).toArray();
|
|
58962
|
+
const directives = [];
|
|
58963
|
+
const protectedRanges = [];
|
|
58964
|
+
let byOffset;
|
|
58965
|
+
for (let index = 0; index < leaves.length; index++) {
|
|
58966
|
+
const leaf = leaves[index];
|
|
58967
|
+
if (!leaf.hidden)
|
|
58968
|
+
continue;
|
|
58969
|
+
const classified = classify(leaf.text);
|
|
58970
|
+
if (!classified)
|
|
58971
|
+
continue;
|
|
58972
|
+
const comment = { offset: leaf.offset, end: leaf.end };
|
|
58973
|
+
if (classified.kind === "malformed") {
|
|
58974
|
+
directives.push({ kind: "malformed", comment, argument: classified.argument });
|
|
58975
|
+
continue;
|
|
58976
|
+
}
|
|
58977
|
+
let next;
|
|
58978
|
+
for (let scan = index + 1; scan < leaves.length; scan++) {
|
|
58979
|
+
if (!leaves[scan].hidden) {
|
|
58980
|
+
next = leaves[scan];
|
|
58981
|
+
break;
|
|
58982
|
+
}
|
|
58983
|
+
}
|
|
58984
|
+
byOffset ??= nodesByStartOffset(root3);
|
|
58985
|
+
const target = next ? byOffset.get(next.offset)?.$cstNode : void 0;
|
|
58986
|
+
if (!target) {
|
|
58987
|
+
directives.push({ kind: "dangling", comment, argument: classified.argument });
|
|
58988
|
+
continue;
|
|
58989
|
+
}
|
|
58990
|
+
const slice = { offset: target.offset, end: target.end };
|
|
58991
|
+
directives.push({ kind: "ignore", comment, argument: classified.argument, target: slice });
|
|
58992
|
+
protectedRanges.push(slice);
|
|
58993
|
+
}
|
|
58994
|
+
if (directives.length === 0)
|
|
58995
|
+
return EMPTY_SCAN;
|
|
58996
|
+
return { directives, protectedRanges: mergeSlices(protectedRanges) };
|
|
58997
|
+
}
|
|
58998
|
+
function intersectsProtected(ranges, offset, end) {
|
|
58999
|
+
return ranges.some((slice) => offset < slice.end && end > slice.offset);
|
|
59000
|
+
}
|
|
59001
|
+
|
|
58885
59002
|
// ../language-server/out/src/services/v1-guardrail.js
|
|
58886
59003
|
var V1_KEYWORD_MAP = {
|
|
58887
59004
|
block: { v2: "part def", note: "SysML v2 replaces the v1 'Block' with 'part def'." },
|
|
@@ -60268,6 +60385,23 @@ var SysmlValidator = class _SysmlValidator {
|
|
|
60268
60385
|
}
|
|
60269
60386
|
this.checkMixedIndentation(node, doc, accept);
|
|
60270
60387
|
this.checkAlphabetizedImportGroups(node, doc, accept);
|
|
60388
|
+
this.checkFormatDirectives(node, doc, accept);
|
|
60389
|
+
}
|
|
60390
|
+
// REQ-392 — STYL008 a `sysml-format` directive the formatter cannot act on.
|
|
60391
|
+
// A well-formed `sysml-format ignore` that found its element is silent; only
|
|
60392
|
+
// an unknown argument and one with nothing left to attach to are reported,
|
|
60393
|
+
// each on the comment itself so the fix is where the cursor lands.
|
|
60394
|
+
checkFormatDirectives(root3, doc, accept) {
|
|
60395
|
+
for (const directive of scanFormatDirectives(doc).directives) {
|
|
60396
|
+
if (directive.kind === "ignore")
|
|
60397
|
+
continue;
|
|
60398
|
+
const range = {
|
|
60399
|
+
start: doc.textDocument.positionAt(directive.comment.offset),
|
|
60400
|
+
end: doc.textDocument.positionAt(directive.comment.end)
|
|
60401
|
+
};
|
|
60402
|
+
const message = directive.kind === "dangling" ? DIAGNOSTIC_MESSAGES.STYL008_FORMAT_DIRECTIVE_UNATTACHED : DIAGNOSTIC_MESSAGES.STYL008_FORMAT_DIRECTIVE_UNKNOWN(directive.argument);
|
|
60403
|
+
accept(severity("STYL008", "info"), message, { node: root3, range, code: "STYL008" });
|
|
60404
|
+
}
|
|
60271
60405
|
}
|
|
60272
60406
|
// REQ-332 — STYL004 mixed tab + space indentation.
|
|
60273
60407
|
checkMixedIndentation(root3, doc, accept) {
|
|
@@ -61187,10 +61321,22 @@ ${baseIndent}}`;
|
|
|
61187
61321
|
// target feature, so each end must be able to hold it (OMG SysML v2 Part 1
|
|
61188
61322
|
// §7.12 ItemFlow / PayloadFeature). Reported per end, and only when the end
|
|
61189
61323
|
// resolves unambiguously and carries exactly one written type.
|
|
61324
|
+
//
|
|
61325
|
+
// A MESSAGE is deliberately exempt, though the grammar folds it into the same
|
|
61326
|
+
// statement. OMG SysML 8.2.2.16 gives `flow` FlowEndMembers — a FlowEnd is a
|
|
61327
|
+
// FlowFeature, the feature that holds the payload — while a message's `from`
|
|
61328
|
+
// and `to` are MessageEventMembers: `message m of i : I from evt1 to evt2` is
|
|
61329
|
+
// "parsed as an abstract FlowUsage, but without any flowEnds", its two ends
|
|
61330
|
+
// redefining Message::sourceEvent and Message::targetEvent (8.4.12.2). The
|
|
61331
|
+
// ends of a message are the two OCCURRENCES that exchange the payload, so
|
|
61332
|
+
// demanding that they be able to hold it reports every sequence-diagram
|
|
61333
|
+
// message as an error.
|
|
61190
61334
|
checkFlowPayload(decl, model, index, accept) {
|
|
61191
61335
|
if (decl.$type !== "FlowStmt")
|
|
61192
61336
|
return;
|
|
61193
61337
|
const flow = decl;
|
|
61338
|
+
if (flow.flowKind === "message")
|
|
61339
|
+
return;
|
|
61194
61340
|
const payload = flowPayloadType(flow.payload);
|
|
61195
61341
|
if (!payload || payload.conjugated)
|
|
61196
61342
|
return;
|
|
@@ -62898,6 +63044,7 @@ var STYLE_ADVISORY_CODES = /* @__PURE__ */ new Set([
|
|
|
62898
63044
|
"STYL005",
|
|
62899
63045
|
"STYL006",
|
|
62900
63046
|
"STYL007",
|
|
63047
|
+
"STYL008",
|
|
62901
63048
|
"LEX009",
|
|
62902
63049
|
// trailing whitespace
|
|
62903
63050
|
"SYN024",
|
|
@@ -63976,6 +64123,18 @@ function resolveEffectiveSubject(node) {
|
|
|
63976
64123
|
|
|
63977
64124
|
// ../language-server/out/src/services/hover-provider.js
|
|
63978
64125
|
var MAX_HOVER_LINES = 30;
|
|
64126
|
+
var MAX_DOC_DEPTH = 16;
|
|
64127
|
+
var MAX_DOC_NODES = 64;
|
|
64128
|
+
var MAX_DOC_SECTION_LINES = 14;
|
|
64129
|
+
var DOC_SPECIALIZATION_KINDS = /* @__PURE__ */ new Set([
|
|
64130
|
+
":>",
|
|
64131
|
+
":>>",
|
|
64132
|
+
"::>",
|
|
64133
|
+
"specializes",
|
|
64134
|
+
"subsets",
|
|
64135
|
+
"redefines",
|
|
64136
|
+
"references"
|
|
64137
|
+
]);
|
|
63979
64138
|
function leafAtOffset(node, offset) {
|
|
63980
64139
|
if (isLeafCstNode(node)) {
|
|
63981
64140
|
return node.offset <= offset && offset < node.offset + node.length ? node : void 0;
|
|
@@ -64130,22 +64289,26 @@ function multiplicityPlainEnglish(node) {
|
|
|
64130
64289
|
return lo === 1 ? "Exactly 1 value" : `Exactly ${lo} values`;
|
|
64131
64290
|
return `Between ${lo} and ${hi ?? "*"} values`;
|
|
64132
64291
|
}
|
|
64133
|
-
function
|
|
64292
|
+
function localDocs(node) {
|
|
64293
|
+
const bodies = [];
|
|
64134
64294
|
const members = node.members;
|
|
64135
64295
|
if (Array.isArray(members)) {
|
|
64136
64296
|
for (const m of members) {
|
|
64137
64297
|
const body = docBody(m);
|
|
64138
64298
|
if (body)
|
|
64139
|
-
|
|
64299
|
+
bodies.push(body);
|
|
64140
64300
|
}
|
|
64141
64301
|
}
|
|
64302
|
+
if (bodies.length > 0)
|
|
64303
|
+
return bodies;
|
|
64142
64304
|
const siblings = node.$container?.members;
|
|
64143
64305
|
if (Array.isArray(siblings)) {
|
|
64144
64306
|
const index = siblings.indexOf(node);
|
|
64145
|
-
|
|
64146
|
-
|
|
64307
|
+
const preceding = index > 0 ? docBody(siblings[index - 1]) : void 0;
|
|
64308
|
+
if (preceding)
|
|
64309
|
+
bodies.push(preceding);
|
|
64147
64310
|
}
|
|
64148
|
-
return
|
|
64311
|
+
return bodies;
|
|
64149
64312
|
}
|
|
64150
64313
|
function docBody(node) {
|
|
64151
64314
|
const member = node;
|
|
@@ -64291,13 +64454,67 @@ function buildCommentHover(node) {
|
|
|
64291
64454
|
lines.push("", sourceFooter(nodeSource(node)));
|
|
64292
64455
|
return lines.join("\n");
|
|
64293
64456
|
}
|
|
64457
|
+
function uniqueLocalTarget(source, targetText) {
|
|
64458
|
+
const root3 = ast_utils_exports.getDocument(source).parseResult?.value;
|
|
64459
|
+
if (!root3)
|
|
64460
|
+
return void 0;
|
|
64461
|
+
let found;
|
|
64462
|
+
for (const candidate of ast_utils_exports.streamAllContents(root3)) {
|
|
64463
|
+
const named = candidate;
|
|
64464
|
+
if (named.name !== targetText && named.shortName?.name !== targetText)
|
|
64465
|
+
continue;
|
|
64466
|
+
if (found)
|
|
64467
|
+
return void 0;
|
|
64468
|
+
found = candidate;
|
|
64469
|
+
}
|
|
64470
|
+
return found;
|
|
64471
|
+
}
|
|
64472
|
+
function declarationLink(node) {
|
|
64473
|
+
const uri = ast_utils_exports.getDocument(node).uri.toString();
|
|
64474
|
+
const line = (node.$cstNode?.range.start.line ?? 0) + 1;
|
|
64475
|
+
return `[${shortUri(uri)}:${line}](${uri}#L${line})`;
|
|
64476
|
+
}
|
|
64477
|
+
function limitDocSection(value) {
|
|
64478
|
+
const lines = value.split(/\r?\n/u);
|
|
64479
|
+
if (lines.length <= MAX_DOC_SECTION_LINES)
|
|
64480
|
+
return value;
|
|
64481
|
+
const kept = lines.slice(0, MAX_DOC_SECTION_LINES);
|
|
64482
|
+
if (kept.filter((line) => line.startsWith("```")).length % 2 !== 0)
|
|
64483
|
+
kept.push("```");
|
|
64484
|
+
kept.push("\u2026");
|
|
64485
|
+
return kept.join("\n");
|
|
64486
|
+
}
|
|
64487
|
+
function renderDocs(bodies, provenance) {
|
|
64488
|
+
const body = limitDocSection(bodies.join("\n\n"));
|
|
64489
|
+
return provenance ? `${provenance}
|
|
64490
|
+
|
|
64491
|
+
${body}` : body;
|
|
64492
|
+
}
|
|
64493
|
+
function supertypeCandidates(node) {
|
|
64494
|
+
const named = node;
|
|
64495
|
+
const out = [];
|
|
64496
|
+
const typing = named.typing;
|
|
64497
|
+
if (typing?.type?.$refText)
|
|
64498
|
+
out.push({ refText: typing.type.$refText, ref: typing.type.ref });
|
|
64499
|
+
for (const more of typing?.moreTypes ?? []) {
|
|
64500
|
+
if (more.$refText)
|
|
64501
|
+
out.push({ refText: more.$refText, ref: more.ref });
|
|
64502
|
+
}
|
|
64503
|
+
for (const rel of allRelationships(named)) {
|
|
64504
|
+
if (!rel.kind || !DOC_SPECIALIZATION_KINDS.has(rel.kind))
|
|
64505
|
+
continue;
|
|
64506
|
+
for (const target of rel.targets ?? [])
|
|
64507
|
+
out.push({ refText: target });
|
|
64508
|
+
}
|
|
64509
|
+
return out;
|
|
64510
|
+
}
|
|
64294
64511
|
function buildContextualHover(node) {
|
|
64295
64512
|
const contextual = nearestAncestor(node, /* @__PURE__ */ new Set(["IfExpr", "MetadataAnnotation", "RepStmt", "CommentStmt"]));
|
|
64296
64513
|
if (!contextual)
|
|
64297
64514
|
return void 0;
|
|
64298
64515
|
return buildConditionalHover(contextual) ?? buildMetadataAnnotationHover(contextual) ?? buildRepresentationHover(contextual) ?? buildCommentHover(contextual);
|
|
64299
64516
|
}
|
|
64300
|
-
function buildHoverMarkdown(node, relationshipDetails) {
|
|
64517
|
+
function buildHoverMarkdown(node, relationshipDetails, documentation) {
|
|
64301
64518
|
const named = node;
|
|
64302
64519
|
const dir = directionLabel(node);
|
|
64303
64520
|
const kind = kindLabel2(node);
|
|
@@ -64306,7 +64523,7 @@ function buildHoverMarkdown(node, relationshipDetails) {
|
|
|
64306
64523
|
const mult = multiplicityLabel(node);
|
|
64307
64524
|
const multHint = multiplicityPlainEnglish(node);
|
|
64308
64525
|
const rels = relationshipLabel(allRelationships(named));
|
|
64309
|
-
const doc =
|
|
64526
|
+
const doc = documentation;
|
|
64310
64527
|
const members = memberList(node);
|
|
64311
64528
|
const headerLines = [`${dir}${kind} ${name}${typing}${mult}${rels}`];
|
|
64312
64529
|
if (members.length > 0) {
|
|
@@ -64659,11 +64876,14 @@ var SysmlHoverProvider = class {
|
|
|
64659
64876
|
astNodeLocator;
|
|
64660
64877
|
documents;
|
|
64661
64878
|
indexManager;
|
|
64879
|
+
/** REQ-394 — the linker's lazy standard-library load, reused rather than duplicated. */
|
|
64880
|
+
linker;
|
|
64662
64881
|
constructor(services) {
|
|
64663
64882
|
this.references = services.references.References;
|
|
64664
64883
|
this.astNodeLocator = services.workspace.AstNodeLocator;
|
|
64665
64884
|
this.documents = services.shared.workspace.LangiumDocuments;
|
|
64666
64885
|
this.indexManager = services.shared.workspace.IndexManager;
|
|
64886
|
+
this.linker = services.references.Linker;
|
|
64667
64887
|
}
|
|
64668
64888
|
// REQ-246, REQ-334, REQ-335, REQ-336, REQ-342, REQ-343 — Route hover requests to markdown tooltip builders
|
|
64669
64889
|
async getHoverContent(document, params) {
|
|
@@ -64737,7 +64957,50 @@ var SysmlHoverProvider = class {
|
|
|
64737
64957
|
};
|
|
64738
64958
|
}
|
|
64739
64959
|
async buildElementHover(node) {
|
|
64740
|
-
return buildHoverMarkdown(node, await this.relationshipDetailSection(node));
|
|
64960
|
+
return buildHoverMarkdown(node, await this.relationshipDetailSection(node), await this.documentationSection(node));
|
|
64961
|
+
}
|
|
64962
|
+
/**
|
|
64963
|
+
* REQ-394 — The documentation an element reads with.
|
|
64964
|
+
*
|
|
64965
|
+
* Its own `doc` members win, all of them, in declaration order. With none of
|
|
64966
|
+
* its own, the walk goes up the typing and specialization graph breadth
|
|
64967
|
+
* first, so the CLOSEST documented type wins over a more general ancestor,
|
|
64968
|
+
* and stops at the first documented one — naming it, and linking to it, so
|
|
64969
|
+
* the reader can see where the prose came from.
|
|
64970
|
+
*
|
|
64971
|
+
* The walk resolves through the same machinery as go-to-definition
|
|
64972
|
+
* (`resolveRelationshipTarget` — local nodes, then the workspace index, then
|
|
64973
|
+
* one lazily parsed library file), so cross-file and standard-library
|
|
64974
|
+
* documentation arrive without a second name resolver. A `visited` set plus
|
|
64975
|
+
* the depth and node budgets make a cyclic or malformed graph terminate.
|
|
64976
|
+
*/
|
|
64977
|
+
async documentationSection(node) {
|
|
64978
|
+
const own = localDocs(node);
|
|
64979
|
+
if (own.length > 0)
|
|
64980
|
+
return renderDocs(own);
|
|
64981
|
+
const visited = /* @__PURE__ */ new Set([node]);
|
|
64982
|
+
let frontier = [node];
|
|
64983
|
+
for (let depth = 0; depth < MAX_DOC_DEPTH && frontier.length > 0; depth++) {
|
|
64984
|
+
const next = [];
|
|
64985
|
+
for (const current2 of frontier) {
|
|
64986
|
+
for (const candidate of supertypeCandidates(current2)) {
|
|
64987
|
+
if (visited.size >= MAX_DOC_NODES)
|
|
64988
|
+
return void 0;
|
|
64989
|
+
const target = candidate.ref ?? this.resolveDocumentationTarget(current2, candidate.refText);
|
|
64990
|
+
if (!target || visited.has(target))
|
|
64991
|
+
continue;
|
|
64992
|
+
visited.add(target);
|
|
64993
|
+
const inherited = localDocs(target);
|
|
64994
|
+
if (inherited.length > 0) {
|
|
64995
|
+
const label = identificationLabel(target) ?? candidate.refText;
|
|
64996
|
+
return renderDocs(inherited, `*Documentation inherited from ${kindLabel2(target)} \`${label}\`* \u2014 ${declarationLink(target)}`);
|
|
64997
|
+
}
|
|
64998
|
+
next.push(target);
|
|
64999
|
+
}
|
|
65000
|
+
}
|
|
65001
|
+
frontier = next;
|
|
65002
|
+
}
|
|
65003
|
+
return void 0;
|
|
64741
65004
|
}
|
|
64742
65005
|
async buildOperatorHover(token) {
|
|
64743
65006
|
const qualifiedName = OPERATOR_FUNCTIONS[token];
|
|
@@ -64787,6 +65050,46 @@ var SysmlHoverProvider = class {
|
|
|
64787
65050
|
return lines.length > 0 ? `*Relationships:*
|
|
64788
65051
|
${lines.join("\n")}` : void 0;
|
|
64789
65052
|
}
|
|
65053
|
+
/**
|
|
65054
|
+
* REQ-394 — Resolve a supertype for the documentation walk without pulling a
|
|
65055
|
+
* document into the workspace.
|
|
65056
|
+
*
|
|
65057
|
+
* The card itself may lazily parse ONE indexed library target (REQ-251), and
|
|
65058
|
+
* that is the budget. Looking for an inherited `doc` must not add to it, so
|
|
65059
|
+
* this reuses the LINKER's own lazy load — the same path a cross-reference
|
|
65060
|
+
* takes, which parses a standard-library file off to the side and caches it
|
|
65061
|
+
* — instead of `getOrCreateDocument`, which would make every hover grow
|
|
65062
|
+
* `LangiumDocuments`.
|
|
65063
|
+
*
|
|
65064
|
+
* The match has to be UNAMBIGUOUS. This is not a scope-aware resolver — it
|
|
65065
|
+
* cannot apply imports or visibility — so it only accepts a name written
|
|
65066
|
+
* exactly as the index holds it, or a final segment that names exactly one
|
|
65067
|
+
* element in the whole index. Two `Vehicle`s in different packages, or an
|
|
65068
|
+
* unresolved reference that happens to share a final segment with something
|
|
65069
|
+
* unrelated, therefore yield NO documentation instead of another element's
|
|
65070
|
+
* prose picked by index order. Wrong documentation reads as authoritative;
|
|
65071
|
+
* none reads as none.
|
|
65072
|
+
*/
|
|
65073
|
+
resolveDocumentationTarget(source, targetText) {
|
|
65074
|
+
const local = uniqueLocalTarget(source, targetText);
|
|
65075
|
+
if (local)
|
|
65076
|
+
return local;
|
|
65077
|
+
const key = relationshipTargetKey(targetText);
|
|
65078
|
+
const exact = /* @__PURE__ */ new Map();
|
|
65079
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
65080
|
+
for (const description of this.indexManager.allElements()) {
|
|
65081
|
+
const id = `${description.documentUri.toString()}#${description.path}`;
|
|
65082
|
+
if (description.name === targetText)
|
|
65083
|
+
exact.set(id, description);
|
|
65084
|
+
else if (description.name === key)
|
|
65085
|
+
byKey.set(id, description);
|
|
65086
|
+
}
|
|
65087
|
+
const pool = exact.size > 0 ? exact : byKey;
|
|
65088
|
+
if (pool.size !== 1)
|
|
65089
|
+
return void 0;
|
|
65090
|
+
const chosen = pool.values().next().value;
|
|
65091
|
+
return this.linker.resolveIndexedNode?.(chosen) ?? chosen.node;
|
|
65092
|
+
}
|
|
64790
65093
|
async resolveRelationshipTarget(source, targetText) {
|
|
64791
65094
|
const local = this.findLocalRelationshipTarget(source, targetText);
|
|
64792
65095
|
if (local)
|
|
@@ -66659,6 +66962,19 @@ var SysmlLinker = class extends DefaultLinker {
|
|
|
66659
66962
|
const root3 = this.parsedLibraryDocument(nodeDescription.documentUri)?.parseResult?.value;
|
|
66660
66963
|
return root3 ? this.astNodeLocator.getAstNode(root3, nodeDescription.path) : void 0;
|
|
66661
66964
|
}
|
|
66965
|
+
/**
|
|
66966
|
+
* REQ-394 — Resolve an indexed description to a node WITHOUT adding a
|
|
66967
|
+
* document to the workspace.
|
|
66968
|
+
*
|
|
66969
|
+
* This is the same path a cross-reference takes (`loadAstNode`): an already
|
|
66970
|
+
* live node, otherwise the standard-library file parsed once off to the side
|
|
66971
|
+
* and cached here. Hover's documentation walk uses it so looking for an
|
|
66972
|
+
* inherited `doc` never pulls a library file into `LangiumDocuments`, and so
|
|
66973
|
+
* there is one lazy-library-load path rather than two.
|
|
66974
|
+
*/
|
|
66975
|
+
resolveIndexedNode(nodeDescription) {
|
|
66976
|
+
return this.loadAstNode(nodeDescription);
|
|
66977
|
+
}
|
|
66662
66978
|
/** Parse (once) the standard-library file a precomputed description points at.
|
|
66663
66979
|
* Prefers an already-live workspace document when one exists (runtime-loaded
|
|
66664
66980
|
* library, or the user opened the file) so there is never a second copy. */
|
|
@@ -67290,14 +67606,526 @@ function getNodeAtRange(document, params) {
|
|
|
67290
67606
|
return leafAtOffset2(root3, offset)?.astNode;
|
|
67291
67607
|
}
|
|
67292
67608
|
|
|
67609
|
+
// ../language-server/out/src/services/formatter-settings.js
|
|
67610
|
+
var FORMATTER_DEFAULTS = Object.freeze({
|
|
67611
|
+
indentStyle: "editor",
|
|
67612
|
+
indentSize: 0,
|
|
67613
|
+
blankLines: 1,
|
|
67614
|
+
lineWidth: 0,
|
|
67615
|
+
emptyBody: "preserve",
|
|
67616
|
+
quotedNames: "preserve",
|
|
67617
|
+
documentationIndent: "preserve",
|
|
67618
|
+
keywords: "preserve"
|
|
67619
|
+
});
|
|
67620
|
+
var sysmlFormatterSettings = { ...FORMATTER_DEFAULTS };
|
|
67621
|
+
var OPTION_PREFIX = "sysml.format.";
|
|
67622
|
+
var INDENT_STYLES = ["editor", "spaces", "tabs"];
|
|
67623
|
+
var EMPTY_BODY_STYLES = ["preserve", "semicolon", "braces"];
|
|
67624
|
+
var QUOTED_NAME_STYLES = ["preserve", "unquoteSafe"];
|
|
67625
|
+
var DOC_INDENT_STYLES = ["preserve", "align"];
|
|
67626
|
+
var KEYWORD_STYLES = ["preserve", "symbolic"];
|
|
67627
|
+
var MAX_INDENT_SIZE = 16;
|
|
67628
|
+
var MIN_LINE_WIDTH = 40;
|
|
67629
|
+
var MAX_LINE_WIDTH = 400;
|
|
67630
|
+
var MAX_BLANK_LINES = 2;
|
|
67631
|
+
function readEnum(name, value, allowed, fallback, issues) {
|
|
67632
|
+
if (value === void 0)
|
|
67633
|
+
return fallback;
|
|
67634
|
+
if (typeof value === "string" && allowed.includes(value))
|
|
67635
|
+
return value;
|
|
67636
|
+
issues.push({ setting: name, value, reason: `expected one of ${allowed.map((a2) => `'${a2}'`).join(", ")}` });
|
|
67637
|
+
return fallback;
|
|
67638
|
+
}
|
|
67639
|
+
function readInt(name, value, min2, max, fallback, issues, zeroMeans) {
|
|
67640
|
+
if (value === void 0)
|
|
67641
|
+
return fallback;
|
|
67642
|
+
if (typeof value === "number" && Number.isInteger(value) && (value === 0 && zeroMeans !== void 0 ? true : value >= min2 && value <= max)) {
|
|
67643
|
+
return value;
|
|
67644
|
+
}
|
|
67645
|
+
const range = zeroMeans !== void 0 ? `0 (${zeroMeans}) or ${min2}\u2013${max}` : `${min2}\u2013${max}`;
|
|
67646
|
+
issues.push({ setting: name, value, reason: `expected an integer ${range}` });
|
|
67647
|
+
return fallback;
|
|
67648
|
+
}
|
|
67649
|
+
function resolveFormatterSettings(raw, base = FORMATTER_DEFAULTS) {
|
|
67650
|
+
const issues = [];
|
|
67651
|
+
const source = raw && typeof raw === "object" ? raw : {};
|
|
67652
|
+
const settings = {
|
|
67653
|
+
indentStyle: readEnum("sysml.format.indentStyle", source.indentStyle, INDENT_STYLES, base.indentStyle, issues),
|
|
67654
|
+
indentSize: readInt("sysml.format.indentSize", source.indentSize, 1, MAX_INDENT_SIZE, base.indentSize, issues, "follow the editor"),
|
|
67655
|
+
blankLines: readInt("sysml.format.blankLines", source.blankLines, 0, MAX_BLANK_LINES, base.blankLines, issues),
|
|
67656
|
+
lineWidth: readInt("sysml.format.lineWidth", source.lineWidth, MIN_LINE_WIDTH, MAX_LINE_WIDTH, base.lineWidth, issues, "never wrap"),
|
|
67657
|
+
emptyBody: readEnum("sysml.format.emptyBody", source.emptyBody, EMPTY_BODY_STYLES, base.emptyBody, issues),
|
|
67658
|
+
quotedNames: readEnum("sysml.format.quotedNames", source.quotedNames, QUOTED_NAME_STYLES, base.quotedNames, issues),
|
|
67659
|
+
documentationIndent: readEnum("sysml.format.documentationIndent", source.documentationIndent, DOC_INDENT_STYLES, base.documentationIndent, issues),
|
|
67660
|
+
keywords: readEnum("sysml.format.keywords", source.keywords, KEYWORD_STYLES, base.keywords, issues)
|
|
67661
|
+
};
|
|
67662
|
+
return { settings, issues };
|
|
67663
|
+
}
|
|
67664
|
+
function resolveFormatterOptions(options, workspace = sysmlFormatterSettings) {
|
|
67665
|
+
const overrides = {};
|
|
67666
|
+
for (const [key, value] of Object.entries(options ?? {})) {
|
|
67667
|
+
if (key.startsWith(OPTION_PREFIX))
|
|
67668
|
+
overrides[key.slice(OPTION_PREFIX.length)] = value;
|
|
67669
|
+
}
|
|
67670
|
+
return resolveFormatterSettings(overrides, workspace);
|
|
67671
|
+
}
|
|
67672
|
+
function effectiveFormattingOptions(options, settings) {
|
|
67673
|
+
const insertSpaces = settings.indentStyle === "editor" ? options.insertSpaces : settings.indentStyle === "spaces";
|
|
67674
|
+
const tabSize = settings.indentSize === 0 ? options.tabSize : settings.indentSize;
|
|
67675
|
+
if (insertSpaces === options.insertSpaces && tabSize === options.tabSize)
|
|
67676
|
+
return options;
|
|
67677
|
+
return { ...options, insertSpaces, tabSize };
|
|
67678
|
+
}
|
|
67679
|
+
function indentUnit(options) {
|
|
67680
|
+
return options.insertSpaces ? " ".repeat(Math.max(1, options.tabSize)) : " ";
|
|
67681
|
+
}
|
|
67682
|
+
|
|
67683
|
+
// ../language-server/out/src/services/formatter-style.js
|
|
67684
|
+
var SYMBOLIC_KEYWORDS = Object.freeze({
|
|
67685
|
+
specializes: ":>",
|
|
67686
|
+
subsets: ":>",
|
|
67687
|
+
redefines: ":>>",
|
|
67688
|
+
references: "::>",
|
|
67689
|
+
crosses: "=>"
|
|
67690
|
+
});
|
|
67691
|
+
var TYPING_PHRASES = ["typed", "defined"];
|
|
67692
|
+
var BARE_ID2 = /^[_a-zA-Z]\w*$/u;
|
|
67693
|
+
var ESCAPED_NAME = /^'([^'\\]|\\.)*'$/u;
|
|
67694
|
+
function projectFormatted(document, edits) {
|
|
67695
|
+
const doc = document.textDocument;
|
|
67696
|
+
const source = doc.getText();
|
|
67697
|
+
const list = edits.map((edit) => ({
|
|
67698
|
+
start: doc.offsetAt(edit.range.start),
|
|
67699
|
+
end: doc.offsetAt(edit.range.end),
|
|
67700
|
+
newText: edit.newText
|
|
67701
|
+
})).sort((a2, b) => a2.start - b.start);
|
|
67702
|
+
let out = "";
|
|
67703
|
+
let cursor = 0;
|
|
67704
|
+
let delta = 0;
|
|
67705
|
+
const marks = [];
|
|
67706
|
+
for (const edit of list) {
|
|
67707
|
+
if (edit.start < cursor)
|
|
67708
|
+
continue;
|
|
67709
|
+
out += source.slice(cursor, edit.start) + edit.newText;
|
|
67710
|
+
delta += edit.newText.length - (edit.end - edit.start);
|
|
67711
|
+
marks.push({ at: edit.end, delta });
|
|
67712
|
+
cursor = edit.end;
|
|
67713
|
+
}
|
|
67714
|
+
out += source.slice(cursor);
|
|
67715
|
+
return {
|
|
67716
|
+
text: out,
|
|
67717
|
+
project(offset) {
|
|
67718
|
+
let low = 0;
|
|
67719
|
+
let high = marks.length - 1;
|
|
67720
|
+
let applied = 0;
|
|
67721
|
+
while (low <= high) {
|
|
67722
|
+
const mid = low + high >> 1;
|
|
67723
|
+
if (marks[mid].at <= offset) {
|
|
67724
|
+
applied = marks[mid].delta;
|
|
67725
|
+
low = mid + 1;
|
|
67726
|
+
} else {
|
|
67727
|
+
high = mid - 1;
|
|
67728
|
+
}
|
|
67729
|
+
}
|
|
67730
|
+
return offset + applied;
|
|
67731
|
+
}
|
|
67732
|
+
};
|
|
67733
|
+
}
|
|
67734
|
+
function lineAt(text, offset) {
|
|
67735
|
+
const start = text.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
|
|
67736
|
+
const nl = text.indexOf("\n", offset);
|
|
67737
|
+
const end = nl < 0 ? text.length : nl;
|
|
67738
|
+
const indent = /^[ \t]*/u.exec(text.slice(start, end))?.[0] ?? "";
|
|
67739
|
+
return { start, end, indent };
|
|
67740
|
+
}
|
|
67741
|
+
function leavesOf(document) {
|
|
67742
|
+
const rootCst = document.parseResult?.value?.$cstNode;
|
|
67743
|
+
return rootCst ? cst_utils_exports.flattenCst(rootCst).toArray() : [];
|
|
67744
|
+
}
|
|
67745
|
+
function nextLeaf(leaves, index, skipHidden) {
|
|
67746
|
+
for (let scan = index + 1; scan < leaves.length; scan++) {
|
|
67747
|
+
if (!skipHidden || !leaves[scan].hidden)
|
|
67748
|
+
return leaves[scan];
|
|
67749
|
+
}
|
|
67750
|
+
return void 0;
|
|
67751
|
+
}
|
|
67752
|
+
function keywordRewrites(leaves, out) {
|
|
67753
|
+
for (let index = 0; index < leaves.length; index++) {
|
|
67754
|
+
const leaf = leaves[index];
|
|
67755
|
+
if (leaf.hidden)
|
|
67756
|
+
continue;
|
|
67757
|
+
const symbol = SYMBOLIC_KEYWORDS[leaf.text];
|
|
67758
|
+
if (symbol) {
|
|
67759
|
+
out.push({ offset: leaf.offset, end: leaf.end, newText: symbol });
|
|
67760
|
+
continue;
|
|
67761
|
+
}
|
|
67762
|
+
if (TYPING_PHRASES.includes(leaf.text) && leaf.astNode?.$type === "Typing") {
|
|
67763
|
+
const by = nextLeaf(leaves, index, true);
|
|
67764
|
+
if (by?.text === "by" && by.astNode?.$type === "Typing") {
|
|
67765
|
+
out.push({ offset: leaf.offset, end: by.end, newText: ":" });
|
|
67766
|
+
}
|
|
67767
|
+
}
|
|
67768
|
+
}
|
|
67769
|
+
}
|
|
67770
|
+
var BRACE_ONLY_BODIES = /* @__PURE__ */ new Set([
|
|
67771
|
+
"InvShorthand",
|
|
67772
|
+
"ActionBlock",
|
|
67773
|
+
"EffectClause",
|
|
67774
|
+
"Import"
|
|
67775
|
+
]);
|
|
67776
|
+
function emptyBraces(node, leaves, source) {
|
|
67777
|
+
const cst = node.$cstNode;
|
|
67778
|
+
if (!cst)
|
|
67779
|
+
return void 0;
|
|
67780
|
+
let open;
|
|
67781
|
+
let close;
|
|
67782
|
+
for (const leaf of leaves) {
|
|
67783
|
+
if (leaf.offset < cst.offset || leaf.end > cst.end || leaf.astNode !== node)
|
|
67784
|
+
continue;
|
|
67785
|
+
if (leaf.text === "{" && !open)
|
|
67786
|
+
open = leaf;
|
|
67787
|
+
if (leaf.text === "}")
|
|
67788
|
+
close = leaf;
|
|
67789
|
+
}
|
|
67790
|
+
if (!open || !close || close.offset < open.end)
|
|
67791
|
+
return void 0;
|
|
67792
|
+
return source.slice(open.end, close.offset).trim().length === 0 ? { open, close } : void 0;
|
|
67793
|
+
}
|
|
67794
|
+
function terminatingSemicolon(node, leaves) {
|
|
67795
|
+
const cst = node.$cstNode;
|
|
67796
|
+
if (!cst)
|
|
67797
|
+
return void 0;
|
|
67798
|
+
for (let index = leaves.length - 1; index >= 0; index--) {
|
|
67799
|
+
const leaf = leaves[index];
|
|
67800
|
+
if (leaf.end !== cst.end)
|
|
67801
|
+
continue;
|
|
67802
|
+
return leaf.text === ";" && leaf.astNode === node ? leaf : void 0;
|
|
67803
|
+
}
|
|
67804
|
+
return void 0;
|
|
67805
|
+
}
|
|
67806
|
+
function emptyBodyRewrites(document, leaves, projection, settings, options, out) {
|
|
67807
|
+
const root3 = document.parseResult?.value;
|
|
67808
|
+
if (!root3)
|
|
67809
|
+
return;
|
|
67810
|
+
const source = document.textDocument.getText();
|
|
67811
|
+
const leafBefore = /* @__PURE__ */ new Map();
|
|
67812
|
+
for (let index = 1; index < leaves.length; index++)
|
|
67813
|
+
leafBefore.set(leaves[index].offset, leaves[index - 1]);
|
|
67814
|
+
for (const node of ast_utils_exports.streamAllContents(root3)) {
|
|
67815
|
+
const members = node.members;
|
|
67816
|
+
if (!Array.isArray(members) || members.length > 0)
|
|
67817
|
+
continue;
|
|
67818
|
+
if (BRACE_ONLY_BODIES.has(node.$type))
|
|
67819
|
+
continue;
|
|
67820
|
+
if (settings.emptyBody === "semicolon") {
|
|
67821
|
+
const braces = emptyBraces(node, leaves, source);
|
|
67822
|
+
if (!braces)
|
|
67823
|
+
continue;
|
|
67824
|
+
const previous = leafBefore.get(braces.open.offset);
|
|
67825
|
+
const from = previous && previous.end <= braces.open.offset ? previous.end : braces.open.offset;
|
|
67826
|
+
out.push({ offset: from, end: braces.close.end, newText: ";" });
|
|
67827
|
+
continue;
|
|
67828
|
+
}
|
|
67829
|
+
if (settings.emptyBody === "braces") {
|
|
67830
|
+
if (emptyBraces(node, leaves, source))
|
|
67831
|
+
continue;
|
|
67832
|
+
const semicolon = terminatingSemicolon(node, leaves);
|
|
67833
|
+
if (!semicolon)
|
|
67834
|
+
continue;
|
|
67835
|
+
const indent = lineAt(projection.text, projection.project(node.$cstNode.offset)).indent;
|
|
67836
|
+
out.push({
|
|
67837
|
+
offset: semicolon.offset,
|
|
67838
|
+
end: semicolon.end,
|
|
67839
|
+
newText: ` {
|
|
67840
|
+
${indent}}`
|
|
67841
|
+
});
|
|
67842
|
+
}
|
|
67843
|
+
}
|
|
67844
|
+
}
|
|
67845
|
+
function quotedNameRewrites(leaves, reservedKeywords, out) {
|
|
67846
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
67847
|
+
const declared = /* @__PURE__ */ new Set();
|
|
67848
|
+
const bareSpellings = /* @__PURE__ */ new Set();
|
|
67849
|
+
for (const leaf of leaves) {
|
|
67850
|
+
if (leaf.hidden)
|
|
67851
|
+
continue;
|
|
67852
|
+
if (BARE_ID2.test(leaf.text) && !reservedKeywords.has(leaf.text)) {
|
|
67853
|
+
bareSpellings.add(leaf.text);
|
|
67854
|
+
continue;
|
|
67855
|
+
}
|
|
67856
|
+
if (!ESCAPED_NAME.test(leaf.text))
|
|
67857
|
+
continue;
|
|
67858
|
+
const body = leaf.text.slice(1, -1);
|
|
67859
|
+
if (!BARE_ID2.test(body) || reservedKeywords.has(body))
|
|
67860
|
+
continue;
|
|
67861
|
+
const list = occurrences.get(body);
|
|
67862
|
+
if (list)
|
|
67863
|
+
list.push(leaf);
|
|
67864
|
+
else
|
|
67865
|
+
occurrences.set(body, [leaf]);
|
|
67866
|
+
if (grammar_utils_exports.findAssignment(leaf)?.feature === "name")
|
|
67867
|
+
declared.add(body);
|
|
67868
|
+
}
|
|
67869
|
+
for (const [body, list] of occurrences) {
|
|
67870
|
+
if (!declared.has(body) || bareSpellings.has(body))
|
|
67871
|
+
continue;
|
|
67872
|
+
for (const leaf of list)
|
|
67873
|
+
out.push({ offset: leaf.offset, end: leaf.end, newText: body });
|
|
67874
|
+
}
|
|
67875
|
+
}
|
|
67876
|
+
function documentationRewrites(document, leaves, projection, options, out) {
|
|
67877
|
+
for (const leaf of leaves) {
|
|
67878
|
+
if (leaf.hidden || !leaf.text.includes("\n"))
|
|
67879
|
+
continue;
|
|
67880
|
+
const owner = leaf.astNode?.$type;
|
|
67881
|
+
if (owner !== "DocCommentMember" && owner !== "CommentStmt")
|
|
67882
|
+
continue;
|
|
67883
|
+
if (!leaf.text.includes("/*"))
|
|
67884
|
+
continue;
|
|
67885
|
+
const projected = projection.project(leaf.offset);
|
|
67886
|
+
const line = lineAt(projection.text, projected);
|
|
67887
|
+
if (projection.text.slice(line.start, projected).trim().length > 0)
|
|
67888
|
+
continue;
|
|
67889
|
+
const target = line.indent;
|
|
67890
|
+
const bodyLines = leaf.text.split("\n");
|
|
67891
|
+
let common;
|
|
67892
|
+
for (let index = 1; index < bodyLines.length; index++) {
|
|
67893
|
+
const raw = bodyLines[index].replace(/\r$/u, "");
|
|
67894
|
+
if (raw.trim().length === 0)
|
|
67895
|
+
continue;
|
|
67896
|
+
const lead = /^[ \t]*/u.exec(raw)?.[0] ?? "";
|
|
67897
|
+
if (common === void 0 || lead.length < common.length)
|
|
67898
|
+
common = lead;
|
|
67899
|
+
}
|
|
67900
|
+
if (common === void 0)
|
|
67901
|
+
continue;
|
|
67902
|
+
let cursor = leaf.offset + bodyLines[0].length + 1;
|
|
67903
|
+
for (let index = 1; index < bodyLines.length; index++) {
|
|
67904
|
+
const raw = bodyLines[index];
|
|
67905
|
+
const lead = /^[ \t]*/u.exec(raw)?.[0] ?? "";
|
|
67906
|
+
if (raw.replace(/\r$/u, "").trim().length > 0) {
|
|
67907
|
+
const shifted = target + lead.slice(Math.min(common.length, lead.length));
|
|
67908
|
+
if (shifted !== lead)
|
|
67909
|
+
out.push({ offset: cursor, end: cursor + lead.length, newText: shifted });
|
|
67910
|
+
}
|
|
67911
|
+
cursor += raw.length + 1;
|
|
67912
|
+
}
|
|
67913
|
+
}
|
|
67914
|
+
}
|
|
67915
|
+
function wrapRewrites(leaves, projection, options, lineWidth, out) {
|
|
67916
|
+
const unit = indentUnit(options);
|
|
67917
|
+
for (let index = 0; index < leaves.length; index++) {
|
|
67918
|
+
const leaf = leaves[index];
|
|
67919
|
+
if (leaf.hidden || leaf.text !== ",")
|
|
67920
|
+
continue;
|
|
67921
|
+
const following = nextLeaf(leaves, index, false);
|
|
67922
|
+
if (!following)
|
|
67923
|
+
continue;
|
|
67924
|
+
const projected = projection.project(leaf.offset);
|
|
67925
|
+
const line = lineAt(projection.text, projected);
|
|
67926
|
+
if (line.end - line.start <= lineWidth)
|
|
67927
|
+
continue;
|
|
67928
|
+
if (projection.text.slice(projection.project(leaf.end), line.end).trim().length === 0)
|
|
67929
|
+
continue;
|
|
67930
|
+
if (projection.text.slice(projected, projection.project(following.offset)).includes("\n"))
|
|
67931
|
+
continue;
|
|
67932
|
+
out.push({
|
|
67933
|
+
offset: leaf.end,
|
|
67934
|
+
end: following.offset,
|
|
67935
|
+
newText: `
|
|
67936
|
+
${line.indent}${unit}`
|
|
67937
|
+
});
|
|
67938
|
+
}
|
|
67939
|
+
}
|
|
67940
|
+
function withinRange(document, rewrite, range) {
|
|
67941
|
+
if (!range)
|
|
67942
|
+
return true;
|
|
67943
|
+
const doc = document.textDocument;
|
|
67944
|
+
return doc.offsetAt(range.start) <= rewrite.offset && rewrite.end <= doc.offsetAt(range.end);
|
|
67945
|
+
}
|
|
67946
|
+
function styleEdits(input) {
|
|
67947
|
+
const { document, settings, options, baseEdits, protectedRanges, range, reservedKeywords } = input;
|
|
67948
|
+
const wantsRewrite = settings.emptyBody !== "preserve" || settings.quotedNames !== "preserve" || settings.documentationIndent !== "preserve" || settings.keywords !== "preserve" || settings.lineWidth > 0;
|
|
67949
|
+
if (!wantsRewrite)
|
|
67950
|
+
return [];
|
|
67951
|
+
const leaves = leavesOf(document);
|
|
67952
|
+
if (leaves.length === 0)
|
|
67953
|
+
return [];
|
|
67954
|
+
const projection = projectFormatted(document, baseEdits);
|
|
67955
|
+
const rewrites = [];
|
|
67956
|
+
if (settings.keywords === "symbolic")
|
|
67957
|
+
keywordRewrites(leaves, rewrites);
|
|
67958
|
+
if (settings.emptyBody !== "preserve") {
|
|
67959
|
+
emptyBodyRewrites(document, leaves, projection, settings, options, rewrites);
|
|
67960
|
+
}
|
|
67961
|
+
if (settings.quotedNames === "unquoteSafe")
|
|
67962
|
+
quotedNameRewrites(leaves, reservedKeywords, rewrites);
|
|
67963
|
+
if (settings.documentationIndent === "align") {
|
|
67964
|
+
documentationRewrites(document, leaves, projection, options, rewrites);
|
|
67965
|
+
}
|
|
67966
|
+
if (settings.lineWidth > 0)
|
|
67967
|
+
wrapRewrites(leaves, projection, options, settings.lineWidth, rewrites);
|
|
67968
|
+
const doc = document.textDocument;
|
|
67969
|
+
const source = doc.getText();
|
|
67970
|
+
const accepted = [];
|
|
67971
|
+
let lastEnd = -1;
|
|
67972
|
+
for (const rewrite of rewrites.sort((a2, b) => a2.offset - b.offset || a2.end - b.end)) {
|
|
67973
|
+
if (rewrite.offset < lastEnd)
|
|
67974
|
+
continue;
|
|
67975
|
+
if (source.slice(rewrite.offset, rewrite.end) === rewrite.newText)
|
|
67976
|
+
continue;
|
|
67977
|
+
if (intersectsProtected(protectedRanges, rewrite.offset, rewrite.end))
|
|
67978
|
+
continue;
|
|
67979
|
+
if (!withinRange(document, rewrite, range))
|
|
67980
|
+
continue;
|
|
67981
|
+
accepted.push(rewrite);
|
|
67982
|
+
lastEnd = rewrite.end;
|
|
67983
|
+
}
|
|
67984
|
+
return accepted.map((rewrite) => ({
|
|
67985
|
+
range: { start: doc.positionAt(rewrite.offset), end: doc.positionAt(rewrite.end) },
|
|
67986
|
+
newText: rewrite.newText
|
|
67987
|
+
}));
|
|
67988
|
+
}
|
|
67989
|
+
|
|
67293
67990
|
// ../language-server/out/src/services/formatter.js
|
|
67294
67991
|
var INDENT = Formatting.indent({ allowMore: true });
|
|
67295
67992
|
var DEDENT = Formatting.noIndent();
|
|
67296
67993
|
var NL = Formatting.newLine();
|
|
67297
|
-
var NL2 = Formatting.newLines(2);
|
|
67298
67994
|
var SPACE = Formatting.oneSpace();
|
|
67299
67995
|
var NO_SPACE = Formatting.noSpace();
|
|
67300
67996
|
var SysmlFormatter = class extends AbstractFormatter {
|
|
67997
|
+
services;
|
|
67998
|
+
/**
|
|
67999
|
+
* The settings in force for the request being served. Formatting is
|
|
68000
|
+
* synchronous from `formatDocument` through the AST pass, so a field is the
|
|
68001
|
+
* whole of the plumbing the base class does not offer.
|
|
68002
|
+
*/
|
|
68003
|
+
active = FORMATTER_DEFAULTS;
|
|
68004
|
+
keywordCache;
|
|
68005
|
+
constructor(services) {
|
|
68006
|
+
super();
|
|
68007
|
+
this.services = services;
|
|
68008
|
+
}
|
|
68009
|
+
// REQ-392, REQ-393 — Full-document formatting.
|
|
68010
|
+
formatDocument(document, params) {
|
|
68011
|
+
return this.formatWith(document, params.options, (options) => super.formatDocument(document, { ...params, options }));
|
|
68012
|
+
}
|
|
68013
|
+
// REQ-392, REQ-393 — Range formatting. A range that reaches into a preserved
|
|
68014
|
+
// element formats the rest of the range and leaves that element whole: the
|
|
68015
|
+
// base formatter narrows to the range, and the same filter then drops
|
|
68016
|
+
// whatever would have rewritten a frozen slice, so a partial rewrite is
|
|
68017
|
+
// impossible. The settings are resolved identically to the full-document
|
|
68018
|
+
// path, so the two cannot produce different style.
|
|
68019
|
+
formatDocumentRange(document, params) {
|
|
68020
|
+
return this.formatWith(document, params.options, (options) => super.formatDocumentRange(document, { ...params, options }), params.range);
|
|
68021
|
+
}
|
|
68022
|
+
// REQ-392, REQ-393 — Format-on-type formats the current line.
|
|
68023
|
+
formatDocumentOnType(document, params) {
|
|
68024
|
+
const line = { start: { line: params.position.line, character: 0 }, end: params.position };
|
|
68025
|
+
return this.formatWith(document, params.options, (options) => super.formatDocumentOnType(document, { ...params, options }), line);
|
|
68026
|
+
}
|
|
68027
|
+
/**
|
|
68028
|
+
* REQ-392, REQ-393 — One pipeline for all three entry points: resolve the
|
|
68029
|
+
* settings, run the base whitespace pass under them, drop whatever a
|
|
68030
|
+
* `sysml-format ignore` froze, then add the token-level rewrites the style
|
|
68031
|
+
* options ask for.
|
|
68032
|
+
*/
|
|
68033
|
+
formatWith(document, requested, base, range) {
|
|
68034
|
+
const { settings } = resolveFormatterOptions(requested);
|
|
68035
|
+
const options = effectiveFormattingOptions(requested, settings);
|
|
68036
|
+
this.active = settings;
|
|
68037
|
+
const protectedRanges = scanFormatDirectives(document).protectedRanges;
|
|
68038
|
+
const baseEdits = this.preserveIgnored(document, base(options), protectedRanges);
|
|
68039
|
+
const style = styleEdits({
|
|
68040
|
+
document,
|
|
68041
|
+
settings,
|
|
68042
|
+
options,
|
|
68043
|
+
baseEdits,
|
|
68044
|
+
protectedRanges,
|
|
68045
|
+
range,
|
|
68046
|
+
reservedKeywords: this.reservedKeywords()
|
|
68047
|
+
});
|
|
68048
|
+
return this.merge(document, baseEdits, style);
|
|
68049
|
+
}
|
|
68050
|
+
/**
|
|
68051
|
+
* REQ-392 — Drop every edit that reaches inside a preserved element.
|
|
68052
|
+
*
|
|
68053
|
+
* Langium only ever edits the whitespace BETWEEN two tokens, so an edit is
|
|
68054
|
+
* either wholly inside a preserved slice (dropped, leaving the slice
|
|
68055
|
+
* byte-for-byte identical, line endings included) or wholly outside it
|
|
68056
|
+
* (kept, so the element's own indentation and its surroundings are still
|
|
68057
|
+
* formatted).
|
|
68058
|
+
*/
|
|
68059
|
+
preserveIgnored(document, edits, ranges) {
|
|
68060
|
+
if (edits.length === 0 || ranges.length === 0)
|
|
68061
|
+
return edits;
|
|
68062
|
+
const text = document.textDocument;
|
|
68063
|
+
return edits.filter((edit) => !intersectsProtected(ranges, text.offsetAt(edit.range.start), text.offsetAt(edit.range.end)));
|
|
68064
|
+
}
|
|
68065
|
+
/**
|
|
68066
|
+
* REQ-393 — Combine the whitespace pass with the token rewrites. A token
|
|
68067
|
+
* rewrite wins where the two touch: `part def P { }` → `part def P;` has to
|
|
68068
|
+
* swallow the space the whitespace pass wanted to put before the brace.
|
|
68069
|
+
*/
|
|
68070
|
+
merge(document, baseEdits, style) {
|
|
68071
|
+
if (style.length === 0)
|
|
68072
|
+
return baseEdits;
|
|
68073
|
+
const text = document.textDocument;
|
|
68074
|
+
const spans = style.map((edit) => ({
|
|
68075
|
+
offset: text.offsetAt(edit.range.start),
|
|
68076
|
+
end: text.offsetAt(edit.range.end)
|
|
68077
|
+
}));
|
|
68078
|
+
const kept = baseEdits.filter((edit) => {
|
|
68079
|
+
const start = text.offsetAt(edit.range.start);
|
|
68080
|
+
const end = text.offsetAt(edit.range.end);
|
|
68081
|
+
return !spans.some((span) => start < span.end && end > span.offset);
|
|
68082
|
+
});
|
|
68083
|
+
return [...kept, ...style].sort((a2, b) => text.offsetAt(a2.range.start) - text.offsetAt(b.range.start));
|
|
68084
|
+
}
|
|
68085
|
+
/**
|
|
68086
|
+
* REQ-393 — Every keyword the grammar reserves. A quoted name may only lose
|
|
68087
|
+
* its quotes when the bare spelling would still be a name and not a keyword,
|
|
68088
|
+
* and the grammar is the only honest source for that list.
|
|
68089
|
+
*/
|
|
68090
|
+
reservedKeywords() {
|
|
68091
|
+
if (this.keywordCache)
|
|
68092
|
+
return this.keywordCache;
|
|
68093
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
68094
|
+
const grammar = this.services?.Grammar;
|
|
68095
|
+
if (grammar) {
|
|
68096
|
+
for (const node of ast_utils_exports.streamAllContents(grammar)) {
|
|
68097
|
+
if (node.$type === "Keyword") {
|
|
68098
|
+
const value = node.value;
|
|
68099
|
+
if (value && /^[_a-zA-Z]\w*$/u.test(value))
|
|
68100
|
+
keywords.add(value);
|
|
68101
|
+
}
|
|
68102
|
+
}
|
|
68103
|
+
}
|
|
68104
|
+
this.keywordCache = keywords;
|
|
68105
|
+
return keywords;
|
|
68106
|
+
}
|
|
68107
|
+
/**
|
|
68108
|
+
* REQ-392 — Keep the indentation level a comment interrupts.
|
|
68109
|
+
*
|
|
68110
|
+
* Langium 3.5's `createTextEdit` returns early for a hidden node and never
|
|
68111
|
+
* applies the formatting action's indentation move to the running context.
|
|
68112
|
+
* `{` carries the block's `indent` action, so a comment sitting between `{`
|
|
68113
|
+
* and the first member — which is exactly where a `sysml-format ignore`
|
|
68114
|
+
* directive goes — dropped the whole block back to column 0. Applying the
|
|
68115
|
+
* move ourselves restores the level a composite node would have kept, and
|
|
68116
|
+
* leaves every other hidden-node path untouched.
|
|
68117
|
+
*/
|
|
68118
|
+
createTextEdit(a2, b, formatting, context) {
|
|
68119
|
+
const edits = super.createTextEdit(a2, b, formatting, context);
|
|
68120
|
+
if (!b.hidden)
|
|
68121
|
+
return edits;
|
|
68122
|
+
const startRange = {
|
|
68123
|
+
start: { line: b.range.start.line, character: 0 },
|
|
68124
|
+
end: b.range.start
|
|
68125
|
+
};
|
|
68126
|
+
context.indentation += this.findFittingMove(startRange, formatting.moves, context)?.tabs ?? 0;
|
|
68127
|
+
return edits;
|
|
68128
|
+
}
|
|
67301
68129
|
// REQ-259 — Dispatch formatting from AST node kind
|
|
67302
68130
|
format(node) {
|
|
67303
68131
|
if (isDocument(node)) {
|
|
@@ -67405,13 +68233,28 @@ var SysmlFormatter = class extends AbstractFormatter {
|
|
|
67405
68233
|
f.keyword(first2).append(SPACE);
|
|
67406
68234
|
f.keyword(second).prepend(SPACE);
|
|
67407
68235
|
}
|
|
68236
|
+
/**
|
|
68237
|
+
* REQ-393 — Top-level element separation is decided HERE and nowhere else.
|
|
68238
|
+
*
|
|
68239
|
+
* The first element gets `noSpace`, which erases leading blank lines rather
|
|
68240
|
+
* than adding two of them: the document-level rule and the element's own
|
|
68241
|
+
* `prepend` used to fire at the same offset, and two insertions at one
|
|
68242
|
+
* position stack, so every file that began with `package` gained blank
|
|
68243
|
+
* lines at the top on every format. `blankLines` decides the rest.
|
|
68244
|
+
*/
|
|
67408
68245
|
fmtDocument(node) {
|
|
67409
68246
|
const f = this.getNodeFormatter(node);
|
|
67410
|
-
|
|
68247
|
+
const separator = Formatting.newLines(1 + this.active.blankLines);
|
|
68248
|
+
for (let index = 0; index < node.elements.length; index++) {
|
|
68249
|
+
f.property("elements", index).prepend(index === 0 ? NO_SPACE : separator);
|
|
68250
|
+
}
|
|
67411
68251
|
}
|
|
67412
68252
|
fmtBlock(node, keyword) {
|
|
67413
68253
|
const f = this.getNodeFormatter(node);
|
|
67414
|
-
|
|
68254
|
+
if (!isDocument(node.$container)) {
|
|
68255
|
+
f.keyword(keyword).prepend(Formatting.newLines(1 + this.active.blankLines));
|
|
68256
|
+
}
|
|
68257
|
+
f.keyword(keyword).append(SPACE);
|
|
67415
68258
|
this.fmtBraces(f);
|
|
67416
68259
|
}
|
|
67417
68260
|
// REQ-355 — Filtered imports keep the `[@Meta]` condition tight against the path
|
|
@@ -67431,7 +68274,9 @@ var SysmlFormatter = class extends AbstractFormatter {
|
|
|
67431
68274
|
// REQ-258, REQ-260 — Format one element per line with canonical keyword spacing
|
|
67432
68275
|
fmtElement(node, keyword) {
|
|
67433
68276
|
const f = this.getNodeFormatter(node);
|
|
67434
|
-
|
|
68277
|
+
if (!isDocument(node.$container))
|
|
68278
|
+
f.keyword(keyword).prepend(NL);
|
|
68279
|
+
f.keyword(keyword).append(SPACE);
|
|
67435
68280
|
if (node.isDef) {
|
|
67436
68281
|
f.keyword("def").prepend(SPACE).append(SPACE);
|
|
67437
68282
|
}
|
|
@@ -67445,7 +68290,9 @@ var SysmlFormatter = class extends AbstractFormatter {
|
|
|
67445
68290
|
// REQ-260 — Keep multi-word `verification case` keyword phrase together
|
|
67446
68291
|
fmtVerificationCase(node) {
|
|
67447
68292
|
const f = this.getNodeFormatter(node);
|
|
67448
|
-
|
|
68293
|
+
if (!isDocument(node.$container))
|
|
68294
|
+
f.keyword("verification").prepend(NL);
|
|
68295
|
+
f.keyword("verification").append(SPACE);
|
|
67449
68296
|
f.keyword("case").append(SPACE);
|
|
67450
68297
|
if (node.isDef)
|
|
67451
68298
|
f.keyword("def").append(SPACE);
|
|
@@ -67456,7 +68303,9 @@ var SysmlFormatter = class extends AbstractFormatter {
|
|
|
67456
68303
|
// REQ-260 — Keep multi-word `analysis case` keyword phrase together
|
|
67457
68304
|
fmtAnalysisCase(node) {
|
|
67458
68305
|
const f = this.getNodeFormatter(node);
|
|
67459
|
-
|
|
68306
|
+
if (!isDocument(node.$container))
|
|
68307
|
+
f.keyword("analysis").prepend(NL);
|
|
68308
|
+
f.keyword("analysis").append(SPACE);
|
|
67460
68309
|
f.keyword("case").append(SPACE);
|
|
67461
68310
|
if (node.isDef)
|
|
67462
68311
|
f.keyword("def").append(SPACE);
|
|
@@ -67467,7 +68316,9 @@ var SysmlFormatter = class extends AbstractFormatter {
|
|
|
67467
68316
|
// REQ-260 — Keep multi-word `use case` keyword phrase together
|
|
67468
68317
|
fmtUseCase(node) {
|
|
67469
68318
|
const f = this.getNodeFormatter(node);
|
|
67470
|
-
|
|
68319
|
+
if (!isDocument(node.$container))
|
|
68320
|
+
f.keyword("use").prepend(NL);
|
|
68321
|
+
f.keyword("use").append(SPACE);
|
|
67471
68322
|
f.keyword("case").append(SPACE);
|
|
67472
68323
|
if (node.isDef)
|
|
67473
68324
|
f.keyword("def").append(SPACE);
|
|
@@ -67656,14 +68507,87 @@ function typeAnchor(node, refText) {
|
|
|
67656
68507
|
}
|
|
67657
68508
|
return void 0;
|
|
67658
68509
|
}
|
|
68510
|
+
var EXPLICIT_SPECIALIZATION_KINDS = /* @__PURE__ */ new Set([
|
|
68511
|
+
":>",
|
|
68512
|
+
":>>",
|
|
68513
|
+
"::>",
|
|
68514
|
+
"=>",
|
|
68515
|
+
"specializes",
|
|
68516
|
+
"subsets",
|
|
68517
|
+
"redefines",
|
|
68518
|
+
"references",
|
|
68519
|
+
"crosses",
|
|
68520
|
+
"conjugates"
|
|
68521
|
+
]);
|
|
68522
|
+
var REDEFINITION_KINDS2 = /* @__PURE__ */ new Set([":>>", "redefines"]);
|
|
68523
|
+
var IMPLICIT_BASES = Object.freeze({
|
|
68524
|
+
PartDecl: { def: "Parts::Part", usage: "Parts::parts" },
|
|
68525
|
+
ItemDecl: { def: "Items::Item", usage: "Items::items" },
|
|
68526
|
+
PortDecl: { def: "Ports::Port", usage: "Ports::ports" },
|
|
68527
|
+
ActionDecl: { def: "Actions::Action", usage: "Actions::actions" },
|
|
68528
|
+
StateDecl: { def: "States::StateAction", usage: "States::stateActions" },
|
|
68529
|
+
ConnectionDecl: { def: "Connections::Connection", usage: "Connections::connections" },
|
|
68530
|
+
InterfaceDecl: { def: "Interfaces::Interface", usage: "Interfaces::interfaces" },
|
|
68531
|
+
CaseDecl: { def: "Cases::Case", usage: "Cases::cases" },
|
|
68532
|
+
UseCaseDecl: { def: "UseCases::UseCase", usage: "UseCases::useCases" },
|
|
68533
|
+
AnalysisCaseDecl: { def: "AnalysisCases::AnalysisCase", usage: "AnalysisCases::analysisCases" },
|
|
68534
|
+
VerificationCaseDecl: { def: "VerificationCases::VerificationCase", usage: "VerificationCases::verificationCases" },
|
|
68535
|
+
ViewDecl: { def: "Views::View", usage: "Views::views" },
|
|
68536
|
+
ViewpointDecl: { def: "Views::ViewpointCheck", usage: "Views::viewpointChecks" },
|
|
68537
|
+
RenderingDecl: { def: "Views::Rendering", usage: "Views::renderings" },
|
|
68538
|
+
RequirementDecl: { def: "Requirements::RequirementCheck", usage: "Requirements::requirementChecks" },
|
|
68539
|
+
ConcernDecl: { def: "Requirements::ConcernCheck", usage: "Requirements::concernChecks" },
|
|
68540
|
+
ConstraintDecl: { def: "Constraints::ConstraintCheck", usage: "Constraints::constraintChecks" },
|
|
68541
|
+
CalcDecl: { def: "Calculations::Calculation", usage: "Calculations::calculations" },
|
|
68542
|
+
AllocationDecl: { def: "Allocations::Allocation", usage: "Allocations::allocations" },
|
|
68543
|
+
AttributeDecl: { def: "Attributes::AttributeValue", usage: "Attributes::attributeValues" },
|
|
68544
|
+
OccurrenceDecl: { def: "Occurrences::Occurrence", usage: "Occurrences::occurrences" },
|
|
68545
|
+
MetadataDecl: { def: "Metadata::MetadataItem", usage: "Metadata::metadataItems" }
|
|
68546
|
+
});
|
|
68547
|
+
function markdown(value) {
|
|
68548
|
+
return { kind: import_vscode_languageserver20.MarkupKind.Markdown, value };
|
|
68549
|
+
}
|
|
68550
|
+
function allRelationships2(node) {
|
|
68551
|
+
return [...node.preRelationships ?? [], ...node.relationships ?? []];
|
|
68552
|
+
}
|
|
68553
|
+
function hasExplicitSpecialization(node) {
|
|
68554
|
+
if (node.typing?.type?.$refText)
|
|
68555
|
+
return true;
|
|
68556
|
+
return allRelationships2(node).some((rel) => rel.kind && EXPLICIT_SPECIALIZATION_KINDS.has(rel.kind));
|
|
68557
|
+
}
|
|
68558
|
+
function keywordLeaf(node, keyword) {
|
|
68559
|
+
const cst = node.$cstNode;
|
|
68560
|
+
if (!cst)
|
|
68561
|
+
return void 0;
|
|
68562
|
+
return cst_utils_exports.flattenCst(cst).find((leaf) => leaf.text === keyword && !leaf.hidden);
|
|
68563
|
+
}
|
|
68564
|
+
function descriptionRange(description) {
|
|
68565
|
+
const segment = description.nameSegment ?? description.selectionSegment;
|
|
68566
|
+
return segment?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
|
|
68567
|
+
}
|
|
67659
68568
|
var SysmlInlayHintProvider = class {
|
|
68569
|
+
services;
|
|
68570
|
+
/**
|
|
68571
|
+
* Resolved implicit bases, keyed by the qualified name in
|
|
68572
|
+
* {@link IMPLICIT_BASES}. The standard library does not change once it is
|
|
68573
|
+
* loaded, so this is built at most once for real; while it is still empty —
|
|
68574
|
+
* before the library has been indexed — the rebuild is over a tiny index and
|
|
68575
|
+
* costs nothing, which is what keeps the hint path free of a full index scan
|
|
68576
|
+
* on every request.
|
|
68577
|
+
*/
|
|
68578
|
+
baseCache = /* @__PURE__ */ new Map();
|
|
68579
|
+
constructor(services) {
|
|
68580
|
+
this.services = services;
|
|
68581
|
+
}
|
|
67660
68582
|
getInlayHints(document, _params) {
|
|
67661
68583
|
const root3 = document.parseResult?.value;
|
|
67662
|
-
if (!root3)
|
|
68584
|
+
if (!root3 || !sysmlInlayHintSettings.enabled)
|
|
67663
68585
|
return void 0;
|
|
68586
|
+
const settings = sysmlInlayHintSettings;
|
|
67664
68587
|
const hints = [];
|
|
67665
68588
|
for (const node of allAstNodes(root3)) {
|
|
67666
|
-
|
|
68589
|
+
const decl = node;
|
|
68590
|
+
if (settings.multiplicity && (isPartDecl(node) || isPortDecl(node) || isAttributeDecl(node)) && !decl.isDef && !decl.multiplicity && node.$cstNode && decl.name) {
|
|
67667
68591
|
hints.push({
|
|
67668
68592
|
position: multiplicityAnchor(node.$cstNode),
|
|
67669
68593
|
label: " [1]",
|
|
@@ -67671,7 +68595,65 @@ var SysmlInlayHintProvider = class {
|
|
|
67671
68595
|
paddingLeft: true
|
|
67672
68596
|
});
|
|
67673
68597
|
}
|
|
67674
|
-
if (!
|
|
68598
|
+
if (settings.visibility && isImport(node) && !decl.visibility) {
|
|
68599
|
+
const keyword = keywordLeaf(node, "import");
|
|
68600
|
+
if (keyword) {
|
|
68601
|
+
const at = keyword.range.start;
|
|
68602
|
+
hints.push({
|
|
68603
|
+
position: at,
|
|
68604
|
+
label: "private",
|
|
68605
|
+
kind: import_vscode_languageserver20.InlayHintKind.Type,
|
|
68606
|
+
paddingRight: true,
|
|
68607
|
+
tooltip: markdown("An `import` that declares no visibility is **private**: the names it brings in are not re-exported from this namespace."),
|
|
68608
|
+
// Writing the modifier is mechanically safe — `private`
|
|
68609
|
+
// is exactly what the grammar accepts here, and the text
|
|
68610
|
+
// then says what it already meant.
|
|
68611
|
+
textEdits: [{ range: { start: at, end: at }, newText: "private " }]
|
|
68612
|
+
});
|
|
68613
|
+
}
|
|
68614
|
+
}
|
|
68615
|
+
if (settings.modifiers && isAttributeDecl(node) && !decl.isDef && !(decl.modifiers ?? []).includes("ref")) {
|
|
68616
|
+
const keyword = keywordLeaf(node, "attribute");
|
|
68617
|
+
if (keyword) {
|
|
68618
|
+
const at = keyword.range.start;
|
|
68619
|
+
hints.push({
|
|
68620
|
+
position: at,
|
|
68621
|
+
label: "ref",
|
|
68622
|
+
kind: import_vscode_languageserver20.InlayHintKind.Type,
|
|
68623
|
+
paddingRight: true,
|
|
68624
|
+
tooltip: markdown("An attribute usage is always referential (`ref`); it is never a composite feature of its owner."),
|
|
68625
|
+
textEdits: [{ range: { start: at, end: at }, newText: "ref " }]
|
|
68626
|
+
});
|
|
68627
|
+
}
|
|
68628
|
+
}
|
|
68629
|
+
if (settings.specialization && node.$cstNode && decl.name && !hasExplicitSpecialization(decl)) {
|
|
68630
|
+
const entry = IMPLICIT_BASES[node.$type];
|
|
68631
|
+
const qualified = entry ? decl.isDef ? entry.def : entry.usage : void 0;
|
|
68632
|
+
const description = qualified ? this.resolveImplicitBase(qualified, decl.isDef !== true) : void 0;
|
|
68633
|
+
if (qualified && description) {
|
|
68634
|
+
hints.push({
|
|
68635
|
+
position: multiplicityAnchor(node.$cstNode),
|
|
68636
|
+
label: [{
|
|
68637
|
+
value: ` :> ${qualified}`,
|
|
68638
|
+
location: {
|
|
68639
|
+
uri: description.documentUri.toString(),
|
|
68640
|
+
range: descriptionRange(description)
|
|
68641
|
+
}
|
|
68642
|
+
}],
|
|
68643
|
+
kind: import_vscode_languageserver20.InlayHintKind.Type,
|
|
68644
|
+
paddingLeft: true,
|
|
68645
|
+
tooltip: markdown(`Implicitly specializes \`${qualified}\` from the standard library.`)
|
|
68646
|
+
});
|
|
68647
|
+
}
|
|
68648
|
+
}
|
|
68649
|
+
if (settings.redefinition)
|
|
68650
|
+
hints.push(...this.parameterRedefinitionHints(decl));
|
|
68651
|
+
if (settings.effectiveNames) {
|
|
68652
|
+
const effective = effectiveNameHint(decl);
|
|
68653
|
+
if (effective)
|
|
68654
|
+
hints.push(effective);
|
|
68655
|
+
}
|
|
68656
|
+
if (!settings.dimension)
|
|
67675
68657
|
continue;
|
|
67676
68658
|
if (isNumericPrimary(node)) {
|
|
67677
68659
|
const symbol = node.unit?.$refText;
|
|
@@ -67687,7 +68669,7 @@ var SysmlInlayHintProvider = class {
|
|
|
67687
68669
|
continue;
|
|
67688
68670
|
}
|
|
67689
68671
|
if (isAttributeDecl(node)) {
|
|
67690
|
-
const refText =
|
|
68672
|
+
const refText = decl.typing?.type?.$refText;
|
|
67691
68673
|
const label = refText ? quantityTypeDimensionLabel(refText) : void 0;
|
|
67692
68674
|
const position = refText ? typeAnchor(node, refText) : void 0;
|
|
67693
68675
|
if (label && position) {
|
|
@@ -67702,7 +68684,102 @@ var SysmlInlayHintProvider = class {
|
|
|
67702
68684
|
}
|
|
67703
68685
|
return hints;
|
|
67704
68686
|
}
|
|
68687
|
+
/**
|
|
68688
|
+
* REQ-395 — Positional parameter correspondence.
|
|
68689
|
+
*
|
|
68690
|
+
* A usage typed by a definition redefines that definition's directed
|
|
68691
|
+
* parameters by position ([OMG SysML v2 Part 1 §7.16.4] — the same rule
|
|
68692
|
+
* `SSM025` checks). The definition has to be RESOLVED for the hint to exist,
|
|
68693
|
+
* and a parameter that already redefines something explicitly is left alone.
|
|
68694
|
+
*/
|
|
68695
|
+
parameterRedefinitionHints(node) {
|
|
68696
|
+
const definition = node.typing?.type?.ref;
|
|
68697
|
+
if (!definition || node.isDef)
|
|
68698
|
+
return [];
|
|
68699
|
+
const mine = directedParameters(node);
|
|
68700
|
+
if (mine.length === 0)
|
|
68701
|
+
return [];
|
|
68702
|
+
const theirs = directedParameters(definition);
|
|
68703
|
+
const hints = [];
|
|
68704
|
+
for (let index = 0; index < mine.length && index < theirs.length; index++) {
|
|
68705
|
+
const parameter = mine[index].node;
|
|
68706
|
+
if (!parameter.name || !parameter.$cstNode)
|
|
68707
|
+
continue;
|
|
68708
|
+
if (allRelationships2(parameter).some((rel) => rel.kind && REDEFINITION_KINDS2.has(rel.kind)))
|
|
68709
|
+
continue;
|
|
68710
|
+
const target = theirs[index].node;
|
|
68711
|
+
if (!target.name || target.name === parameter.name)
|
|
68712
|
+
continue;
|
|
68713
|
+
const uri = ast_utils_exports.getDocument(target).uri.toString();
|
|
68714
|
+
const range = target.$cstNode?.range ?? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
|
|
68715
|
+
hints.push({
|
|
68716
|
+
position: multiplicityAnchor(parameter.$cstNode),
|
|
68717
|
+
label: [{ value: ` :>> ${target.name}`, location: { uri, range } }],
|
|
68718
|
+
kind: import_vscode_languageserver20.InlayHintKind.Type,
|
|
68719
|
+
paddingLeft: true,
|
|
68720
|
+
tooltip: markdown(`Parameter ${index + 1} implicitly redefines \`${target.name}\` of \`${node.typing?.type?.$refText ?? "its definition"}\`.`)
|
|
68721
|
+
});
|
|
68722
|
+
}
|
|
68723
|
+
return hints;
|
|
68724
|
+
}
|
|
68725
|
+
/**
|
|
68726
|
+
* REQ-395 — Find one implicit base in the index.
|
|
68727
|
+
*
|
|
68728
|
+
* A description whose name is the full qualified spelling wins outright. The
|
|
68729
|
+
* index otherwise keys library symbols by their simple name, so the fallback
|
|
68730
|
+
* requires BOTH a standard-library document and the right side of the
|
|
68731
|
+
* definition/usage split, which the precomputed index records — that is what
|
|
68732
|
+
* keeps `Parts::Part` from matching a `part Part` somewhere else.
|
|
68733
|
+
*/
|
|
68734
|
+
resolveImplicitBase(qualified, wantUsage) {
|
|
68735
|
+
const cached = this.baseCache.get(qualified);
|
|
68736
|
+
if (cached)
|
|
68737
|
+
return cached;
|
|
68738
|
+
const index = this.services?.shared.workspace.IndexManager;
|
|
68739
|
+
if (!index)
|
|
68740
|
+
return void 0;
|
|
68741
|
+
const simple = lastSegment3(qualified);
|
|
68742
|
+
let fallback;
|
|
68743
|
+
for (const description of index.allElements()) {
|
|
68744
|
+
if (description.name === qualified) {
|
|
68745
|
+
this.baseCache.set(qualified, description);
|
|
68746
|
+
return description;
|
|
68747
|
+
}
|
|
68748
|
+
if (fallback || description.name !== simple)
|
|
68749
|
+
continue;
|
|
68750
|
+
const uri = description.documentUri instanceof URI2 ? description.documentUri : URI2.parse(String(description.documentUri));
|
|
68751
|
+
if (!isStandardLibraryUri(uri))
|
|
68752
|
+
continue;
|
|
68753
|
+
if (description.isUsage === true !== wantUsage)
|
|
68754
|
+
continue;
|
|
68755
|
+
fallback = description;
|
|
68756
|
+
}
|
|
68757
|
+
if (fallback)
|
|
68758
|
+
this.baseCache.set(qualified, fallback);
|
|
68759
|
+
return fallback;
|
|
68760
|
+
}
|
|
67705
68761
|
};
|
|
68762
|
+
function effectiveNameHint(node) {
|
|
68763
|
+
if (node.name || node.shortName?.name || !node.$cstNode)
|
|
68764
|
+
return void 0;
|
|
68765
|
+
const redefinition = allRelationships2(node).find((rel) => rel.kind && REDEFINITION_KINDS2.has(rel.kind) && (rel.targets?.length ?? 0) > 0);
|
|
68766
|
+
const target = redefinition?.targets?.[0];
|
|
68767
|
+
if (!target)
|
|
68768
|
+
return void 0;
|
|
68769
|
+
const effective = lastSegment3(target);
|
|
68770
|
+
if (!effective)
|
|
68771
|
+
return void 0;
|
|
68772
|
+
const operator = cst_utils_exports.flattenCst(node.$cstNode).find((leaf) => !leaf.hidden && redefinition?.kind !== void 0 && leaf.text === redefinition.kind);
|
|
68773
|
+
if (!operator)
|
|
68774
|
+
return void 0;
|
|
68775
|
+
return {
|
|
68776
|
+
position: operator.range.start,
|
|
68777
|
+
label: effective,
|
|
68778
|
+
kind: import_vscode_languageserver20.InlayHintKind.Parameter,
|
|
68779
|
+
paddingRight: true,
|
|
68780
|
+
tooltip: markdown(`Effective name: \`${effective}\`, taken from the feature this one redefines.`)
|
|
68781
|
+
};
|
|
68782
|
+
}
|
|
67706
68783
|
|
|
67707
68784
|
// ../language-server/out/src/services/requirement-eval.js
|
|
67708
68785
|
var UNRESOLVED = { kind: "unresolved" };
|
|
@@ -68911,9 +69988,13 @@ var SysmlLspModule = {
|
|
|
68911
69988
|
// Epic G
|
|
68912
69989
|
RenameProvider: (services) => new SysmlRenameProvider(services),
|
|
68913
69990
|
CodeActionProvider: (services) => new SysmlCodeActionProvider(services),
|
|
68914
|
-
|
|
69991
|
+
// REQ-393 — the formatter reads the grammar's keyword set for the
|
|
69992
|
+
// quoted-name decision, so it is built with services.
|
|
69993
|
+
Formatter: (services) => new SysmlFormatter(services),
|
|
68915
69994
|
SemanticTokenProvider: (services) => new SysmlSemanticTokenProvider(services),
|
|
68916
|
-
|
|
69995
|
+
// REQ-395 — the provider resolves implicit standard-library bases
|
|
69996
|
+
// through the workspace/library index, so it is built with services.
|
|
69997
|
+
InlayHintProvider: (services) => new SysmlInlayHintProvider(services),
|
|
68917
69998
|
CodeLensProvider: (services) => new SysmlCodeLensProvider(services),
|
|
68918
69999
|
DocumentHighlightProvider: (services) => new SysmlDocumentHighlightProvider(services),
|
|
68919
70000
|
DefinitionProvider: (services) => new SysmlDefinitionProvider(services),
|
|
@@ -70012,7 +71093,7 @@ async function runValidation(command) {
|
|
|
70012
71093
|
}
|
|
70013
71094
|
|
|
70014
71095
|
// src/main.ts
|
|
70015
|
-
var VERSION2 = true ? "0.
|
|
71096
|
+
var VERSION2 = true ? "0.16.0" : "dev";
|
|
70016
71097
|
async function main(argv) {
|
|
70017
71098
|
const command = parseArgs(argv);
|
|
70018
71099
|
if (command.kind === "help") {
|