sysml-v2-lsp 0.37.0 → 0.38.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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.38.0]
6
+
7
+ ### Added
8
+
9
+ - Experimental LSP request `sysml/validateText`: validates texts against the workspace model without opening or publishing anything. Disabled by default. Enabled by `sysml.experimental.validateText.enabled`, limited by `maxDocuments` (5), `maxLines` (200), `maxCharacters` (8,192 per document), and `maxTotalCharacters` (16,384 per request). Character limits use UTF-16 code units and are checked before parsing.
10
+ - Benchmark suite for `sysml/validateText`.
11
+
12
+ ### Fixed
13
+
14
+ - Keep temporary validation versions outside the LSP integer range so they
15
+ cannot reuse a real document's version or its cached diagnostics.
16
+
5
17
  ## [0.37.0]
6
18
 
7
19
  ### Fixed
package/README.md CHANGED
@@ -40,6 +40,27 @@ usage names or unrelated same-named declarations. Completion and rename insert
40
40
  the required quotes and escapes automatically; editor positions use UTF-16,
41
41
  including after characters outside the Basic Multilingual Plane.
42
42
 
43
+ ### Experimental text validation
44
+
45
+ `sysml/validateText` checks proposed document texts against the current workspace,
46
+ including unsaved documents, without opening documents or publishing diagnostics.
47
+ It is disabled by default. Enable `sysml.experimental.validateText.enabled` to use it.
48
+ Requests contain `documents`, an array of `{ uri, text }` entries. The response
49
+ contains each requested URI and its diagnostics, in request order.
50
+
51
+ The default limits are 5 documents, 200 lines per document, 8,192 characters per
52
+ document, and 16,384 characters across the request. Characters are UTF-16 code
53
+ units, so an emoji outside the Basic Multilingual Plane counts as two.
54
+ The corresponding settings are `maxDocuments`, `maxLines`, `maxCharacters` and
55
+ `maxTotalCharacters` under `sysml.experimental.validateText`.
56
+ Oversized requests fail with `InvalidParams` before parsing or changing cached
57
+ documents. Disabled requests fail with `MethodNotFound`.
58
+
59
+ Only requested documents are checked. Include an affected existing document
60
+ with its current text to check it too. Results use the server's existing
61
+ diagnostic checks; they do not certify complete SysML conformance.
62
+ Input limits reduce work but do not guarantee a fixed response time.
63
+
43
64
  ### Install from Marketplace
44
65
 
45
66
  Install via the VS Code extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JamieD.sysml-v2-support).
@@ -172,7 +193,7 @@ make bench-history # Stable historical benchmark trends
172
193
 
173
194
  ## Benchmarks
174
195
 
175
- A built-in benchmark suite measures parser, symbol table, LSP provider, memory, throughput, and folder-load performance. Results are written as both JSON and Markdown to `benchmarks/results/`.
196
+ A built-in benchmark suite measures parser, symbol table, LSP provider, memory, throughput, folder-load, and `sysml/validateText` performance. Results are written as both JSON and Markdown to `benchmarks/results/`.
176
197
 
177
198
  ### Running Benchmarks
178
199
 
@@ -195,6 +216,7 @@ npm run bench:history # stable historical trends
195
216
  | `memory` | Forced-GC retained-heap estimates; use for coarse trends, not precise allocation counts |
196
217
  | `throughput` | Syntax-valid lexer/parser throughput, including document-level stale-snapshot recovery |
197
218
  | `folderLoad` | Workspace-style file discovery, parse, and symbol build |
219
+ | `validateText` | Main-thread time of a `sysml/validateText` request by workspace, text and request size, and the rebuild it leaves for the next request |
198
220
 
199
221
  ### Regression Detection
200
222
 
@@ -123,30 +123,30 @@
123
123
  Object.defineProperty(exports, "__esModule", { value: true });
124
124
  exports.Message = exports.NotificationType9 = exports.NotificationType8 = exports.NotificationType7 = exports.NotificationType6 = exports.NotificationType5 = exports.NotificationType4 = exports.NotificationType3 = exports.NotificationType2 = exports.NotificationType1 = exports.NotificationType0 = exports.NotificationType = exports.RequestType9 = exports.RequestType8 = exports.RequestType7 = exports.RequestType6 = exports.RequestType5 = exports.RequestType4 = exports.RequestType3 = exports.RequestType2 = exports.RequestType1 = exports.RequestType = exports.RequestType0 = exports.AbstractMessageSignature = exports.ParameterStructures = exports.ResponseError = exports.ErrorCodes = void 0;
125
125
  var is = __importStar(require_is());
126
- var ErrorCodes2;
127
- (function(ErrorCodes3) {
128
- ErrorCodes3.ParseError = -32700;
129
- ErrorCodes3.InvalidRequest = -32600;
130
- ErrorCodes3.MethodNotFound = -32601;
131
- ErrorCodes3.InvalidParams = -32602;
132
- ErrorCodes3.InternalError = -32603;
133
- ErrorCodes3.jsonrpcReservedErrorRangeStart = -32099;
134
- ErrorCodes3.serverErrorStart = -32099;
135
- ErrorCodes3.MessageWriteError = -32099;
136
- ErrorCodes3.MessageReadError = -32098;
137
- ErrorCodes3.PendingResponseRejected = -32097;
138
- ErrorCodes3.ConnectionInactive = -32096;
139
- ErrorCodes3.ServerNotInitialized = -32002;
140
- ErrorCodes3.UnknownErrorCode = -32001;
141
- ErrorCodes3.jsonrpcReservedErrorRangeEnd = -32e3;
142
- ErrorCodes3.serverErrorEnd = -32e3;
143
- })(ErrorCodes2 || (exports.ErrorCodes = ErrorCodes2 = {}));
144
- var ResponseError2 = class _ResponseError extends Error {
126
+ var ErrorCodes3;
127
+ (function(ErrorCodes4) {
128
+ ErrorCodes4.ParseError = -32700;
129
+ ErrorCodes4.InvalidRequest = -32600;
130
+ ErrorCodes4.MethodNotFound = -32601;
131
+ ErrorCodes4.InvalidParams = -32602;
132
+ ErrorCodes4.InternalError = -32603;
133
+ ErrorCodes4.jsonrpcReservedErrorRangeStart = -32099;
134
+ ErrorCodes4.serverErrorStart = -32099;
135
+ ErrorCodes4.MessageWriteError = -32099;
136
+ ErrorCodes4.MessageReadError = -32098;
137
+ ErrorCodes4.PendingResponseRejected = -32097;
138
+ ErrorCodes4.ConnectionInactive = -32096;
139
+ ErrorCodes4.ServerNotInitialized = -32002;
140
+ ErrorCodes4.UnknownErrorCode = -32001;
141
+ ErrorCodes4.jsonrpcReservedErrorRangeEnd = -32e3;
142
+ ErrorCodes4.serverErrorEnd = -32e3;
143
+ })(ErrorCodes3 || (exports.ErrorCodes = ErrorCodes3 = {}));
144
+ var ResponseError3 = class _ResponseError extends Error {
145
145
  code;
146
146
  data;
147
147
  constructor(code, message, data) {
148
148
  super(message);
149
- this.code = is.number(code) ? code : ErrorCodes2.UnknownErrorCode;
149
+ this.code = is.number(code) ? code : ErrorCodes3.UnknownErrorCode;
150
150
  this.data = data;
151
151
  Object.setPrototypeOf(this, _ResponseError.prototype);
152
152
  }
@@ -161,7 +161,7 @@
161
161
  return result;
162
162
  }
163
163
  };
164
- exports.ResponseError = ResponseError2;
164
+ exports.ResponseError = ResponseError3;
165
165
  var ParameterStructures = class _ParameterStructures {
166
166
  kind;
167
167
  /**
@@ -3526,7 +3526,7 @@ ${JSON.stringify(message, null, 4)}`);
3526
3526
  }
3527
3527
  CodeDescription3.is = is;
3528
3528
  })(CodeDescription || (CodeDescription = {}));
3529
- (function(Diagnostic8) {
3529
+ (function(Diagnostic9) {
3530
3530
  function create(range, message, severity, code, source, relatedInformation) {
3531
3531
  const result = { range, message };
3532
3532
  if (Is.defined(severity)) {
@@ -3543,17 +3543,17 @@ ${JSON.stringify(message, null, 4)}`);
3543
3543
  }
3544
3544
  return result;
3545
3545
  }
3546
- Diagnostic8.create = create;
3546
+ Diagnostic9.create = create;
3547
3547
  function is(value) {
3548
3548
  var _a;
3549
3549
  const candidate = value;
3550
3550
  return Is.defined(candidate) && Range.is(candidate.range) && (Is.string(candidate.message) || MarkupContent.is(candidate.message)) && (Is.number(candidate.severity) || Is.undefined(candidate.severity)) && (Is.integer(candidate.code) || Is.string(candidate.code) || Is.undefined(candidate.code)) && (Is.undefined(candidate.codeDescription) || Is.string((_a = candidate.codeDescription) === null || _a === void 0 ? void 0 : _a.href)) && (Is.string(candidate.source) || Is.undefined(candidate.source)) && (Is.undefined(candidate.relatedInformation) || Is.typedArray(candidate.relatedInformation, DiagnosticRelatedInformation.is));
3551
3551
  }
3552
- Diagnostic8.is = is;
3552
+ Diagnostic9.is = is;
3553
3553
  function is3_17(value) {
3554
3554
  return Is.string(value.message);
3555
3555
  }
3556
- Diagnostic8.is3_17 = is3_17;
3556
+ Diagnostic9.is3_17 = is3_17;
3557
3557
  function getMessageString(diagnostic) {
3558
3558
  if (Is.string(diagnostic.message)) {
3559
3559
  return diagnostic.message;
@@ -3563,7 +3563,7 @@ ${JSON.stringify(message, null, 4)}`);
3563
3563
  throw new Error(`Unknown message type ${typeof diagnostic.message}`);
3564
3564
  }
3565
3565
  }
3566
- Diagnostic8.getMessageString = getMessageString;
3566
+ Diagnostic9.getMessageString = getMessageString;
3567
3567
  })(Diagnostic || (Diagnostic = {}));
3568
3568
  (function(Command3) {
3569
3569
  function create(title, command, ...args) {
@@ -9290,28 +9290,28 @@ ${JSON.stringify(message, null, 4)}`);
9290
9290
  Object.defineProperty(exports, "__esModule", { value: true });
9291
9291
  exports.Message = exports.NotificationType9 = exports.NotificationType8 = exports.NotificationType7 = exports.NotificationType6 = exports.NotificationType5 = exports.NotificationType4 = exports.NotificationType3 = exports.NotificationType2 = exports.NotificationType1 = exports.NotificationType0 = exports.NotificationType = exports.RequestType9 = exports.RequestType8 = exports.RequestType7 = exports.RequestType6 = exports.RequestType5 = exports.RequestType4 = exports.RequestType3 = exports.RequestType2 = exports.RequestType1 = exports.RequestType = exports.RequestType0 = exports.AbstractMessageSignature = exports.ParameterStructures = exports.ResponseError = exports.ErrorCodes = void 0;
9292
9292
  var is = require_is4();
9293
- var ErrorCodes2;
9294
- (function(ErrorCodes3) {
9295
- ErrorCodes3.ParseError = -32700;
9296
- ErrorCodes3.InvalidRequest = -32600;
9297
- ErrorCodes3.MethodNotFound = -32601;
9298
- ErrorCodes3.InvalidParams = -32602;
9299
- ErrorCodes3.InternalError = -32603;
9300
- ErrorCodes3.jsonrpcReservedErrorRangeStart = -32099;
9301
- ErrorCodes3.serverErrorStart = -32099;
9302
- ErrorCodes3.MessageWriteError = -32099;
9303
- ErrorCodes3.MessageReadError = -32098;
9304
- ErrorCodes3.PendingResponseRejected = -32097;
9305
- ErrorCodes3.ConnectionInactive = -32096;
9306
- ErrorCodes3.ServerNotInitialized = -32002;
9307
- ErrorCodes3.UnknownErrorCode = -32001;
9308
- ErrorCodes3.jsonrpcReservedErrorRangeEnd = -32e3;
9309
- ErrorCodes3.serverErrorEnd = -32e3;
9310
- })(ErrorCodes2 || (exports.ErrorCodes = ErrorCodes2 = {}));
9311
- var ResponseError2 = class _ResponseError extends Error {
9293
+ var ErrorCodes3;
9294
+ (function(ErrorCodes4) {
9295
+ ErrorCodes4.ParseError = -32700;
9296
+ ErrorCodes4.InvalidRequest = -32600;
9297
+ ErrorCodes4.MethodNotFound = -32601;
9298
+ ErrorCodes4.InvalidParams = -32602;
9299
+ ErrorCodes4.InternalError = -32603;
9300
+ ErrorCodes4.jsonrpcReservedErrorRangeStart = -32099;
9301
+ ErrorCodes4.serverErrorStart = -32099;
9302
+ ErrorCodes4.MessageWriteError = -32099;
9303
+ ErrorCodes4.MessageReadError = -32098;
9304
+ ErrorCodes4.PendingResponseRejected = -32097;
9305
+ ErrorCodes4.ConnectionInactive = -32096;
9306
+ ErrorCodes4.ServerNotInitialized = -32002;
9307
+ ErrorCodes4.UnknownErrorCode = -32001;
9308
+ ErrorCodes4.jsonrpcReservedErrorRangeEnd = -32e3;
9309
+ ErrorCodes4.serverErrorEnd = -32e3;
9310
+ })(ErrorCodes3 || (exports.ErrorCodes = ErrorCodes3 = {}));
9311
+ var ResponseError3 = class _ResponseError extends Error {
9312
9312
  constructor(code, message, data) {
9313
9313
  super(message);
9314
- this.code = is.number(code) ? code : ErrorCodes2.UnknownErrorCode;
9314
+ this.code = is.number(code) ? code : ErrorCodes3.UnknownErrorCode;
9315
9315
  this.data = data;
9316
9316
  Object.setPrototypeOf(this, _ResponseError.prototype);
9317
9317
  }
@@ -9326,7 +9326,7 @@ ${JSON.stringify(message, null, 4)}`);
9326
9326
  return result;
9327
9327
  }
9328
9328
  };
9329
- exports.ResponseError = ResponseError2;
9329
+ exports.ResponseError = ResponseError3;
9330
9330
  var ParameterStructures = class _ParameterStructures {
9331
9331
  constructor(kind) {
9332
9332
  this.kind = kind;
@@ -12599,7 +12599,7 @@ ${JSON.stringify(message, null, 4)}`);
12599
12599
  }
12600
12600
  CodeDescription3.is = is;
12601
12601
  })(CodeDescription2 || (CodeDescription2 = {}));
12602
- (function(Diagnostic8) {
12602
+ (function(Diagnostic9) {
12603
12603
  function create(range, message, severity, code, source, relatedInformation) {
12604
12604
  let result = { range, message };
12605
12605
  if (Is2.defined(severity)) {
@@ -12616,13 +12616,13 @@ ${JSON.stringify(message, null, 4)}`);
12616
12616
  }
12617
12617
  return result;
12618
12618
  }
12619
- Diagnostic8.create = create;
12619
+ Diagnostic9.create = create;
12620
12620
  function is(value) {
12621
12621
  var _a;
12622
12622
  let candidate = value;
12623
12623
  return Is2.defined(candidate) && Range2.is(candidate.range) && Is2.string(candidate.message) && (Is2.number(candidate.severity) || Is2.undefined(candidate.severity)) && (Is2.integer(candidate.code) || Is2.string(candidate.code) || Is2.undefined(candidate.code)) && (Is2.undefined(candidate.codeDescription) || Is2.string((_a = candidate.codeDescription) === null || _a === void 0 ? void 0 : _a.href)) && (Is2.string(candidate.source) || Is2.undefined(candidate.source)) && (Is2.undefined(candidate.relatedInformation) || Is2.typedArray(candidate.relatedInformation, DiagnosticRelatedInformation2.is));
12624
12624
  }
12625
- Diagnostic8.is = is;
12625
+ Diagnostic9.is = is;
12626
12626
  })(Diagnostic2 || (Diagnostic2 = {}));
12627
12627
  (function(Command3) {
12628
12628
  function create(title, command, ...args) {
@@ -17437,7 +17437,7 @@ ${JSON.stringify(message, null, 4)}`);
17437
17437
  });
17438
17438
 
17439
17439
  // server/src/server.ts
17440
- var import_node13 = __toESM(require_main3());
17440
+ var import_node14 = __toESM(require_main3());
17441
17441
 
17442
17442
  // server/src/platform/shims/fs.ts
17443
17443
  function readFileSync(_path, _enc) {
@@ -133028,6 +133028,13 @@ ${JSON.stringify(message, null, 4)}`);
133028
133028
  * indexed yet" apart from "genuinely doesn't exist".
133029
133029
  */
133030
133030
  workspaceScanComplete = true;
133031
+ /**
133032
+ * Last version given to a stand-in text by `withTexts`. Caches keyed by
133033
+ * version assume one text per uri and version, so each stand-in gets a
133034
+ * version outside the LSP integer range (LSP 3.17, base types).
133035
+ * These internal positive versions are never sent to the client.
133036
+ */
133037
+ temporaryVersion = 2 ** 31 - 1;
133031
133038
  /**
133032
133039
  * Parse a document and cache the result.
133033
133040
  * Returns the cached result if the content hasn't changed.
@@ -133231,6 +133238,35 @@ ${JSON.stringify(message, null, 4)}`);
133231
133238
  this.wsSymbolTable.removeUri(uri);
133232
133239
  this.wsBuiltVersions.delete(uri);
133233
133240
  }
133241
+ /**
133242
+ * Runs `fn` with each text standing in for the document at its uri (or
133243
+ * added as a new document), all at once, then restores the previous
133244
+ * state. `fn` must be synchronous: no other request can run in between,
133245
+ * so no other request ever sees the temporary texts. Every text is
133246
+ * parsed before anything changes. The stand-ins get versions no real
133247
+ * document has, so caches keyed by version never confuse them with the
133248
+ * real documents.
133249
+ */
133250
+ withTexts(documents2, fn) {
133251
+ if (!Number.isSafeInteger(this.temporaryVersion + documents2.length)) {
133252
+ throw new RangeError("Temporary document version space exhausted.");
133253
+ }
133254
+ const parsed = documents2.map(({ uri, text }) => ({ uri, text, result: parseDocument(text) }));
133255
+ const previous = new Map(parsed.map(({ uri }) => [uri, this.cache.get(uri)]));
133256
+ for (const { uri, text, result } of parsed) {
133257
+ this.cache.set(uri, { version: ++this.temporaryVersion, text, result, needsParse: false });
133258
+ }
133259
+ try {
133260
+ return fn();
133261
+ } finally {
133262
+ for (const [uri, entry] of previous) {
133263
+ if (entry) this.cache.set(uri, entry);
133264
+ else this.cache.delete(uri);
133265
+ this.wsSymbolTable.removeUri(uri);
133266
+ this.wsBuiltVersions.delete(uri);
133267
+ }
133268
+ }
133269
+ }
133234
133270
  /**
133235
133271
  * Get all cached URIs.
133236
133272
  */
@@ -142068,6 +142104,115 @@ ${info.documentation}`);
142068
142104
  }
142069
142105
  };
142070
142106
 
142107
+ // server/src/providers/textValidationProvider.ts
142108
+ var import_node13 = __toESM(require_main3());
142109
+ var DEFAULT_TEXT_VALIDATION_SETTINGS = {
142110
+ enabled: false,
142111
+ maxDocuments: 5,
142112
+ maxLines: 200,
142113
+ maxCharacters: 8192,
142114
+ maxTotalCharacters: 16384
142115
+ };
142116
+ function readTextValidationSettings(section) {
142117
+ const raw = typeof section === "object" && section !== null ? section : {};
142118
+ const limit = (value, fallback) => Number.isSafeInteger(value) && value > 0 ? value : fallback;
142119
+ return {
142120
+ enabled: raw.enabled === true,
142121
+ maxDocuments: limit(raw.maxDocuments, DEFAULT_TEXT_VALIDATION_SETTINGS.maxDocuments),
142122
+ maxLines: limit(raw.maxLines, DEFAULT_TEXT_VALIDATION_SETTINGS.maxLines),
142123
+ maxCharacters: limit(raw.maxCharacters, DEFAULT_TEXT_VALIDATION_SETTINGS.maxCharacters),
142124
+ maxTotalCharacters: limit(raw.maxTotalCharacters, DEFAULT_TEXT_VALIDATION_SETTINGS.maxTotalCharacters)
142125
+ };
142126
+ }
142127
+ var TextValidationProvider = class {
142128
+ /**
142129
+ * `isReported` filters diagnostics as the server does before
142130
+ * publishing them (`sysml.validation.disabledCodes`), so a text gets
142131
+ * exactly what it would get as an open document.
142132
+ */
142133
+ constructor(documentManager2, semanticValidator2, isReported2 = () => true) {
142134
+ this.documentManager = documentManager2;
142135
+ this.semanticValidator = semanticValidator2;
142136
+ this.isReported = isReported2;
142137
+ }
142138
+ documentManager;
142139
+ semanticValidator;
142140
+ isReported;
142141
+ settings = { ...DEFAULT_TEXT_VALIDATION_SETTINGS };
142142
+ /** Applies the client's settings to the requests that follow. */
142143
+ configure(settings) {
142144
+ this.settings = { ...settings };
142145
+ }
142146
+ /**
142147
+ * The diagnostics each text would get as an open document, with all
142148
+ * texts in place at the same time. Answers `MethodNotFound` while
142149
+ * disabled, and `InvalidParams` for malformed parameters or parameters
142150
+ * over a limit.
142151
+ */
142152
+ validate(params) {
142153
+ if (!this.settings.enabled) {
142154
+ throw new import_node13.ResponseError(
142155
+ import_node13.ErrorCodes.MethodNotFound,
142156
+ "sysml/validateText is experimental and disabled: set sysml.experimental.validateText.enabled to true."
142157
+ );
142158
+ }
142159
+ const documents2 = this.checkDocuments(params?.documents);
142160
+ const known = documents2.map((document) => ({ uri: this.knownSpelling(document.uri), text: document.text }));
142161
+ const diagnostics = this.documentManager.withTexts(known, () => known.map(({ uri }) => this.diagnose(uri)));
142162
+ return { documents: documents2.map((document, index2) => ({ uri: document.uri, diagnostics: diagnostics[index2] })) };
142163
+ }
142164
+ /** `documents` checked: a non-empty array of `{ uri, text }` within the limits, each naming a different document. */
142165
+ checkDocuments(documents2) {
142166
+ if (!Array.isArray(documents2) || documents2.length === 0) invalid('"documents" must be a non-empty array of { uri, text }.');
142167
+ const { maxDocuments, maxLines, maxCharacters, maxTotalCharacters } = this.settings;
142168
+ if (documents2.length > maxDocuments) {
142169
+ invalid(`${documents2.length} documents, more than the ${maxDocuments} allowed (sysml.experimental.validateText.maxDocuments).`);
142170
+ }
142171
+ const checked = documents2;
142172
+ let totalCharacters = 0;
142173
+ checked.forEach((document, index2) => {
142174
+ if (typeof document?.text !== "string") invalid(`documents[${index2}] needs "text", the SysML text to validate.`);
142175
+ if (document.text.length > maxCharacters) {
142176
+ invalid(`documents[${index2}] has ${document.text.length} characters, more than the ${maxCharacters} allowed (sysml.experimental.validateText.maxCharacters).`);
142177
+ }
142178
+ totalCharacters += document.text.length;
142179
+ if (totalCharacters > maxTotalCharacters) {
142180
+ invalid(`${totalCharacters} total characters, more than the ${maxTotalCharacters} allowed (sysml.experimental.validateText.maxTotalCharacters).`);
142181
+ }
142182
+ const lines = lineCount(document.text);
142183
+ if (lines > maxLines) {
142184
+ invalid(`documents[${index2}] has ${lines} lines, more than the ${maxLines} allowed (sysml.experimental.validateText.maxLines).`);
142185
+ }
142186
+ if (!isUri(document.uri)) invalid(`documents[${index2}] needs "uri", the document the text is the content of.`);
142187
+ const earlier = checked.findIndex((other) => isSameDocumentUri(other.uri, document.uri));
142188
+ if (earlier !== index2) invalid(`documents[${earlier}] and documents[${index2}] name the same document.`);
142189
+ });
142190
+ return checked;
142191
+ }
142192
+ /** The syntax, keyword and semantic diagnostics of the document at `uri`, as published for an open document. */
142193
+ diagnose(uri) {
142194
+ const diagnostics = new DiagnosticsProvider(this.documentManager).getDiagnostics(uri);
142195
+ const parseResult = this.documentManager.get(uri);
142196
+ if (parseResult) diagnostics.push(...validateKeywords(parseResult));
142197
+ diagnostics.push(...this.semanticValidator.validate(uri));
142198
+ return diagnostics.filter(this.isReported);
142199
+ }
142200
+ /** The spelling under which the server knows the document `uri` names, else `uri` itself (see `isSameDocumentUri`). */
142201
+ knownSpelling(uri) {
142202
+ return this.documentManager.getUris().find((known) => isSameDocumentUri(known, uri)) ?? uri;
142203
+ }
142204
+ };
142205
+ function lineCount(text) {
142206
+ const breaks = text.match(/\r\n|\r|\n/g)?.length ?? 0;
142207
+ return text === "" || /[\r\n]$/.test(text) ? breaks : breaks + 1;
142208
+ }
142209
+ function isUri(value) {
142210
+ return typeof value === "string" && value.trim() !== "";
142211
+ }
142212
+ function invalid(message) {
142213
+ throw new import_node13.ResponseError(import_node13.ErrorCodes.InvalidParams, `sysml/validateText: ${message}`);
142214
+ }
142215
+
142071
142216
  // server/src/platform/shims/fs-promises.ts
142072
142217
  async function readFile(_path, _enc) {
142073
142218
  throw new Error("fs/promises.readFile is not available in the browser");
@@ -142148,7 +142293,7 @@ ${info.documentation}`);
142148
142293
  return void 0;
142149
142294
  }
142150
142295
  var connection = createServerConnection();
142151
- var documents = new import_node13.TextDocuments(TextDocument2);
142296
+ var documents = new import_node14.TextDocuments(TextDocument2);
142152
142297
  var serverStartTime = Date.now();
142153
142298
  var documentManager = new DocumentManager();
142154
142299
  var modelProvider = new SysMLModelProvider(documentManager);
@@ -142165,6 +142310,7 @@ ${info.documentation}`);
142165
142310
  var codeActionProvider = new CodeActionProvider(documentManager);
142166
142311
  var formattingProvider = new FormattingProvider(documents);
142167
142312
  var semanticValidator = new SemanticValidator(documentManager);
142313
+ var textValidationProvider = new TextValidationProvider(documentManager, semanticValidator, isReported);
142168
142314
  hoverProvider.setSemanticValidator(semanticValidator);
142169
142315
  var hasConfigurationCapability = false;
142170
142316
  var hasWorkspaceFolderCapability = false;
@@ -142176,11 +142322,11 @@ ${info.documentation}`);
142176
142322
  var isWorkspaceFile = false;
142177
142323
  var preloadOnOpen = "workspaceOnly";
142178
142324
  var disabledDiagnosticCodes = /* @__PURE__ */ new Set();
142325
+ function isReported(diagnostic) {
142326
+ return diagnostic.code === void 0 || !disabledDiagnosticCodes.has(String(diagnostic.code));
142327
+ }
142179
142328
  function publishDiagnostics(uri, diagnostics) {
142180
- connection.sendDiagnostics({
142181
- uri,
142182
- diagnostics: diagnostics.filter((diagnostic) => diagnostic.code === void 0 || !disabledDiagnosticCodes.has(String(diagnostic.code)))
142183
- });
142329
+ connection.sendDiagnostics({ uri, diagnostics: diagnostics.filter(isReported) });
142184
142330
  }
142185
142331
  var skipDirs = new Set(DEFAULT_SKIP_DIRS);
142186
142332
  var serverReady = false;
@@ -142313,7 +142459,7 @@ ${info.documentation}`);
142313
142459
  };
142314
142460
  const result = {
142315
142461
  capabilities: {
142316
- textDocumentSync: import_node13.TextDocumentSyncKind.Incremental,
142462
+ textDocumentSync: import_node14.TextDocumentSyncKind.Incremental,
142317
142463
  // Completion
142318
142464
  completionProvider: {
142319
142465
  resolveProvider: true,
@@ -142341,7 +142487,7 @@ ${info.documentation}`);
142341
142487
  // Code actions (quick fixes)
142342
142488
  codeActionProvider: {
142343
142489
  codeActionKinds: [
142344
- import_node13.CodeActionKind.QuickFix
142490
+ import_node14.CodeActionKind.QuickFix
142345
142491
  ]
142346
142492
  },
142347
142493
  // Document formatting
@@ -142362,7 +142508,7 @@ ${info.documentation}`);
142362
142508
  connection.onInitialized(async () => {
142363
142509
  if (hasConfigurationCapability) {
142364
142510
  connection.client.register(
142365
- import_node13.DidChangeConfigurationNotification.type,
142511
+ import_node14.DidChangeConfigurationNotification.type,
142366
142512
  void 0
142367
142513
  );
142368
142514
  }
@@ -142423,6 +142569,8 @@ ${info.documentation}`);
142423
142569
  const validation = await connection.workspace.getConfiguration("sysml.validation");
142424
142570
  const codes = validation?.disabledCodes;
142425
142571
  disabledDiagnosticCodes = new Set(Array.isArray(codes) ? codes.filter((code) => typeof code === "string") : []);
142572
+ const experimental = await connection.workspace.getConfiguration("sysml.experimental");
142573
+ textValidationProvider.configure(readTextValidationSettings(experimental?.validateText));
142426
142574
  }
142427
142575
  function applySettings(config) {
142428
142576
  if (!config) return;
@@ -142776,6 +142924,10 @@ ${info.documentation}`);
142776
142924
  await initialization;
142777
142925
  return elementLookupProvider.elementLookup(params);
142778
142926
  });
142927
+ connection.onRequest("sysml/validateText", async (params) => {
142928
+ await initialization;
142929
+ return textValidationProvider.validate(params);
142930
+ });
142779
142931
  connection.onRequest("sysml/serverStats", () => {
142780
142932
  const mem = typeof process !== "undefined" && typeof process.memoryUsage === "function" ? process.memoryUsage() : { heapUsed: 0, heapTotal: 0, rss: 0, external: 0 };
142781
142933
  const toMB = (bytes) => Math.round(bytes / 1024 / 1024);
@@ -118,28 +118,28 @@ var require_messages = __commonJS({
118
118
  Object.defineProperty(exports2, "__esModule", { value: true });
119
119
  exports2.Message = exports2.NotificationType9 = exports2.NotificationType8 = exports2.NotificationType7 = exports2.NotificationType6 = exports2.NotificationType5 = exports2.NotificationType4 = exports2.NotificationType3 = exports2.NotificationType2 = exports2.NotificationType1 = exports2.NotificationType0 = exports2.NotificationType = exports2.RequestType9 = exports2.RequestType8 = exports2.RequestType7 = exports2.RequestType6 = exports2.RequestType5 = exports2.RequestType4 = exports2.RequestType3 = exports2.RequestType2 = exports2.RequestType1 = exports2.RequestType = exports2.RequestType0 = exports2.AbstractMessageSignature = exports2.ParameterStructures = exports2.ResponseError = exports2.ErrorCodes = void 0;
120
120
  var is = require_is2();
121
- var ErrorCodes2;
122
- (function(ErrorCodes3) {
123
- ErrorCodes3.ParseError = -32700;
124
- ErrorCodes3.InvalidRequest = -32600;
125
- ErrorCodes3.MethodNotFound = -32601;
126
- ErrorCodes3.InvalidParams = -32602;
127
- ErrorCodes3.InternalError = -32603;
128
- ErrorCodes3.jsonrpcReservedErrorRangeStart = -32099;
129
- ErrorCodes3.serverErrorStart = -32099;
130
- ErrorCodes3.MessageWriteError = -32099;
131
- ErrorCodes3.MessageReadError = -32098;
132
- ErrorCodes3.PendingResponseRejected = -32097;
133
- ErrorCodes3.ConnectionInactive = -32096;
134
- ErrorCodes3.ServerNotInitialized = -32002;
135
- ErrorCodes3.UnknownErrorCode = -32001;
136
- ErrorCodes3.jsonrpcReservedErrorRangeEnd = -32e3;
137
- ErrorCodes3.serverErrorEnd = -32e3;
138
- })(ErrorCodes2 || (exports2.ErrorCodes = ErrorCodes2 = {}));
139
- var ResponseError2 = class _ResponseError extends Error {
121
+ var ErrorCodes3;
122
+ (function(ErrorCodes4) {
123
+ ErrorCodes4.ParseError = -32700;
124
+ ErrorCodes4.InvalidRequest = -32600;
125
+ ErrorCodes4.MethodNotFound = -32601;
126
+ ErrorCodes4.InvalidParams = -32602;
127
+ ErrorCodes4.InternalError = -32603;
128
+ ErrorCodes4.jsonrpcReservedErrorRangeStart = -32099;
129
+ ErrorCodes4.serverErrorStart = -32099;
130
+ ErrorCodes4.MessageWriteError = -32099;
131
+ ErrorCodes4.MessageReadError = -32098;
132
+ ErrorCodes4.PendingResponseRejected = -32097;
133
+ ErrorCodes4.ConnectionInactive = -32096;
134
+ ErrorCodes4.ServerNotInitialized = -32002;
135
+ ErrorCodes4.UnknownErrorCode = -32001;
136
+ ErrorCodes4.jsonrpcReservedErrorRangeEnd = -32e3;
137
+ ErrorCodes4.serverErrorEnd = -32e3;
138
+ })(ErrorCodes3 || (exports2.ErrorCodes = ErrorCodes3 = {}));
139
+ var ResponseError3 = class _ResponseError extends Error {
140
140
  constructor(code, message, data) {
141
141
  super(message);
142
- this.code = is.number(code) ? code : ErrorCodes2.UnknownErrorCode;
142
+ this.code = is.number(code) ? code : ErrorCodes3.UnknownErrorCode;
143
143
  this.data = data;
144
144
  Object.setPrototypeOf(this, _ResponseError.prototype);
145
145
  }
@@ -154,7 +154,7 @@ var require_messages = __commonJS({
154
154
  return result;
155
155
  }
156
156
  };
157
- exports2.ResponseError = ResponseError2;
157
+ exports2.ResponseError = ResponseError3;
158
158
  var ParameterStructures = class _ParameterStructures {
159
159
  constructor(kind) {
160
160
  this.kind = kind;
@@ -3552,8 +3552,8 @@ var require_main2 = __commonJS({
3552
3552
  }
3553
3553
  CodeDescription2.is = is;
3554
3554
  })(CodeDescription || (exports3.CodeDescription = CodeDescription = {}));
3555
- var Diagnostic6;
3556
- (function(Diagnostic7) {
3555
+ var Diagnostic7;
3556
+ (function(Diagnostic8) {
3557
3557
  function create(range, message, severity, code, source, relatedInformation) {
3558
3558
  var result = { range, message };
3559
3559
  if (Is.defined(severity)) {
@@ -3570,14 +3570,14 @@ var require_main2 = __commonJS({
3570
3570
  }
3571
3571
  return result;
3572
3572
  }
3573
- Diagnostic7.create = create;
3573
+ Diagnostic8.create = create;
3574
3574
  function is(value) {
3575
3575
  var _a;
3576
3576
  var candidate = value;
3577
3577
  return Is.defined(candidate) && Range6.is(candidate.range) && Is.string(candidate.message) && (Is.number(candidate.severity) || Is.undefined(candidate.severity)) && (Is.integer(candidate.code) || Is.string(candidate.code) || Is.undefined(candidate.code)) && (Is.undefined(candidate.codeDescription) || Is.string((_a = candidate.codeDescription) === null || _a === void 0 ? void 0 : _a.href)) && (Is.string(candidate.source) || Is.undefined(candidate.source)) && (Is.undefined(candidate.relatedInformation) || Is.typedArray(candidate.relatedInformation, DiagnosticRelatedInformation.is));
3578
3578
  }
3579
- Diagnostic7.is = is;
3580
- })(Diagnostic6 || (exports3.Diagnostic = Diagnostic6 = {}));
3579
+ Diagnostic8.is = is;
3580
+ })(Diagnostic7 || (exports3.Diagnostic = Diagnostic7 = {}));
3581
3581
  var Command;
3582
3582
  (function(Command2) {
3583
3583
  function create(title, command) {
@@ -4362,7 +4362,7 @@ var require_main2 = __commonJS({
4362
4362
  CodeActionContext2.create = create;
4363
4363
  function is(value) {
4364
4364
  var candidate = value;
4365
- return Is.defined(candidate) && Is.typedArray(candidate.diagnostics, Diagnostic6.is) && (candidate.only === void 0 || Is.typedArray(candidate.only, Is.string)) && (candidate.triggerKind === void 0 || candidate.triggerKind === CodeActionTriggerKind.Invoked || candidate.triggerKind === CodeActionTriggerKind.Automatic);
4365
+ return Is.defined(candidate) && Is.typedArray(candidate.diagnostics, Diagnostic7.is) && (candidate.only === void 0 || Is.typedArray(candidate.only, Is.string)) && (candidate.triggerKind === void 0 || candidate.triggerKind === CodeActionTriggerKind.Invoked || candidate.triggerKind === CodeActionTriggerKind.Automatic);
4366
4366
  }
4367
4367
  CodeActionContext2.is = is;
4368
4368
  })(CodeActionContext || (exports3.CodeActionContext = CodeActionContext = {}));
@@ -4387,7 +4387,7 @@ var require_main2 = __commonJS({
4387
4387
  CodeAction3.create = create;
4388
4388
  function is(value) {
4389
4389
  var candidate = value;
4390
- return candidate && Is.string(candidate.title) && (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, Diagnostic6.is)) && (candidate.kind === void 0 || Is.string(candidate.kind)) && (candidate.edit !== void 0 || candidate.command !== void 0) && (candidate.command === void 0 || Command.is(candidate.command)) && (candidate.isPreferred === void 0 || Is.boolean(candidate.isPreferred)) && (candidate.edit === void 0 || WorkspaceEdit4.is(candidate.edit));
4390
+ return candidate && Is.string(candidate.title) && (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, Diagnostic7.is)) && (candidate.kind === void 0 || Is.string(candidate.kind)) && (candidate.edit !== void 0 || candidate.command !== void 0) && (candidate.command === void 0 || Command.is(candidate.command)) && (candidate.isPreferred === void 0 || Is.boolean(candidate.isPreferred)) && (candidate.edit === void 0 || WorkspaceEdit4.is(candidate.edit));
4391
4391
  }
4392
4392
  CodeAction3.is = is;
4393
4393
  })(CodeAction2 || (exports3.CodeAction = CodeAction2 = {}));
@@ -8880,7 +8880,7 @@ var require_node3 = __commonJS({
8880
8880
  });
8881
8881
 
8882
8882
  // server/src/server.ts
8883
- var import_node14 = __toESM(require_node3());
8883
+ var import_node15 = __toESM(require_node3());
8884
8884
  var fs = __toESM(require("node:fs"));
8885
8885
  var path2 = __toESM(require("node:path"));
8886
8886
  var import_node_url2 = require("node:url");
@@ -124358,6 +124358,13 @@ var DocumentManager = class {
124358
124358
  * indexed yet" apart from "genuinely doesn't exist".
124359
124359
  */
124360
124360
  workspaceScanComplete = true;
124361
+ /**
124362
+ * Last version given to a stand-in text by `withTexts`. Caches keyed by
124363
+ * version assume one text per uri and version, so each stand-in gets a
124364
+ * version outside the LSP integer range (LSP 3.17, base types).
124365
+ * These internal positive versions are never sent to the client.
124366
+ */
124367
+ temporaryVersion = 2 ** 31 - 1;
124361
124368
  /**
124362
124369
  * Parse a document and cache the result.
124363
124370
  * Returns the cached result if the content hasn't changed.
@@ -124561,6 +124568,35 @@ var DocumentManager = class {
124561
124568
  this.wsSymbolTable.removeUri(uri);
124562
124569
  this.wsBuiltVersions.delete(uri);
124563
124570
  }
124571
+ /**
124572
+ * Runs `fn` with each text standing in for the document at its uri (or
124573
+ * added as a new document), all at once, then restores the previous
124574
+ * state. `fn` must be synchronous: no other request can run in between,
124575
+ * so no other request ever sees the temporary texts. Every text is
124576
+ * parsed before anything changes. The stand-ins get versions no real
124577
+ * document has, so caches keyed by version never confuse them with the
124578
+ * real documents.
124579
+ */
124580
+ withTexts(documents2, fn) {
124581
+ if (!Number.isSafeInteger(this.temporaryVersion + documents2.length)) {
124582
+ throw new RangeError("Temporary document version space exhausted.");
124583
+ }
124584
+ const parsed = documents2.map(({ uri, text }) => ({ uri, text, result: parseDocument(text) }));
124585
+ const previous = new Map(parsed.map(({ uri }) => [uri, this.cache.get(uri)]));
124586
+ for (const { uri, text, result } of parsed) {
124587
+ this.cache.set(uri, { version: ++this.temporaryVersion, text, result, needsParse: false });
124588
+ }
124589
+ try {
124590
+ return fn();
124591
+ } finally {
124592
+ for (const [uri, entry] of previous) {
124593
+ if (entry) this.cache.set(uri, entry);
124594
+ else this.cache.delete(uri);
124595
+ this.wsSymbolTable.removeUri(uri);
124596
+ this.wsBuiltVersions.delete(uri);
124597
+ }
124598
+ }
124599
+ }
124564
124600
  /**
124565
124601
  * Get all cached URIs.
124566
124602
  */
@@ -131036,6 +131072,115 @@ var SemanticValidator = class _SemanticValidator {
131036
131072
  }
131037
131073
  };
131038
131074
 
131075
+ // server/src/providers/textValidationProvider.ts
131076
+ var import_node14 = __toESM(require_node3());
131077
+ var DEFAULT_TEXT_VALIDATION_SETTINGS = {
131078
+ enabled: false,
131079
+ maxDocuments: 5,
131080
+ maxLines: 200,
131081
+ maxCharacters: 8192,
131082
+ maxTotalCharacters: 16384
131083
+ };
131084
+ function readTextValidationSettings(section) {
131085
+ const raw = typeof section === "object" && section !== null ? section : {};
131086
+ const limit = (value, fallback) => Number.isSafeInteger(value) && value > 0 ? value : fallback;
131087
+ return {
131088
+ enabled: raw.enabled === true,
131089
+ maxDocuments: limit(raw.maxDocuments, DEFAULT_TEXT_VALIDATION_SETTINGS.maxDocuments),
131090
+ maxLines: limit(raw.maxLines, DEFAULT_TEXT_VALIDATION_SETTINGS.maxLines),
131091
+ maxCharacters: limit(raw.maxCharacters, DEFAULT_TEXT_VALIDATION_SETTINGS.maxCharacters),
131092
+ maxTotalCharacters: limit(raw.maxTotalCharacters, DEFAULT_TEXT_VALIDATION_SETTINGS.maxTotalCharacters)
131093
+ };
131094
+ }
131095
+ var TextValidationProvider = class {
131096
+ /**
131097
+ * `isReported` filters diagnostics as the server does before
131098
+ * publishing them (`sysml.validation.disabledCodes`), so a text gets
131099
+ * exactly what it would get as an open document.
131100
+ */
131101
+ constructor(documentManager2, semanticValidator2, isReported2 = () => true) {
131102
+ this.documentManager = documentManager2;
131103
+ this.semanticValidator = semanticValidator2;
131104
+ this.isReported = isReported2;
131105
+ }
131106
+ documentManager;
131107
+ semanticValidator;
131108
+ isReported;
131109
+ settings = { ...DEFAULT_TEXT_VALIDATION_SETTINGS };
131110
+ /** Applies the client's settings to the requests that follow. */
131111
+ configure(settings) {
131112
+ this.settings = { ...settings };
131113
+ }
131114
+ /**
131115
+ * The diagnostics each text would get as an open document, with all
131116
+ * texts in place at the same time. Answers `MethodNotFound` while
131117
+ * disabled, and `InvalidParams` for malformed parameters or parameters
131118
+ * over a limit.
131119
+ */
131120
+ validate(params) {
131121
+ if (!this.settings.enabled) {
131122
+ throw new import_node14.ResponseError(
131123
+ import_node14.ErrorCodes.MethodNotFound,
131124
+ "sysml/validateText is experimental and disabled: set sysml.experimental.validateText.enabled to true."
131125
+ );
131126
+ }
131127
+ const documents2 = this.checkDocuments(params?.documents);
131128
+ const known = documents2.map((document) => ({ uri: this.knownSpelling(document.uri), text: document.text }));
131129
+ const diagnostics = this.documentManager.withTexts(known, () => known.map(({ uri }) => this.diagnose(uri)));
131130
+ return { documents: documents2.map((document, index2) => ({ uri: document.uri, diagnostics: diagnostics[index2] })) };
131131
+ }
131132
+ /** `documents` checked: a non-empty array of `{ uri, text }` within the limits, each naming a different document. */
131133
+ checkDocuments(documents2) {
131134
+ if (!Array.isArray(documents2) || documents2.length === 0) invalid('"documents" must be a non-empty array of { uri, text }.');
131135
+ const { maxDocuments, maxLines, maxCharacters, maxTotalCharacters } = this.settings;
131136
+ if (documents2.length > maxDocuments) {
131137
+ invalid(`${documents2.length} documents, more than the ${maxDocuments} allowed (sysml.experimental.validateText.maxDocuments).`);
131138
+ }
131139
+ const checked = documents2;
131140
+ let totalCharacters = 0;
131141
+ checked.forEach((document, index2) => {
131142
+ if (typeof document?.text !== "string") invalid(`documents[${index2}] needs "text", the SysML text to validate.`);
131143
+ if (document.text.length > maxCharacters) {
131144
+ invalid(`documents[${index2}] has ${document.text.length} characters, more than the ${maxCharacters} allowed (sysml.experimental.validateText.maxCharacters).`);
131145
+ }
131146
+ totalCharacters += document.text.length;
131147
+ if (totalCharacters > maxTotalCharacters) {
131148
+ invalid(`${totalCharacters} total characters, more than the ${maxTotalCharacters} allowed (sysml.experimental.validateText.maxTotalCharacters).`);
131149
+ }
131150
+ const lines = lineCount(document.text);
131151
+ if (lines > maxLines) {
131152
+ invalid(`documents[${index2}] has ${lines} lines, more than the ${maxLines} allowed (sysml.experimental.validateText.maxLines).`);
131153
+ }
131154
+ if (!isUri(document.uri)) invalid(`documents[${index2}] needs "uri", the document the text is the content of.`);
131155
+ const earlier = checked.findIndex((other) => isSameDocumentUri(other.uri, document.uri));
131156
+ if (earlier !== index2) invalid(`documents[${earlier}] and documents[${index2}] name the same document.`);
131157
+ });
131158
+ return checked;
131159
+ }
131160
+ /** The syntax, keyword and semantic diagnostics of the document at `uri`, as published for an open document. */
131161
+ diagnose(uri) {
131162
+ const diagnostics = new DiagnosticsProvider(this.documentManager).getDiagnostics(uri);
131163
+ const parseResult = this.documentManager.get(uri);
131164
+ if (parseResult) diagnostics.push(...validateKeywords(parseResult));
131165
+ diagnostics.push(...this.semanticValidator.validate(uri));
131166
+ return diagnostics.filter(this.isReported);
131167
+ }
131168
+ /** The spelling under which the server knows the document `uri` names, else `uri` itself (see `isSameDocumentUri`). */
131169
+ knownSpelling(uri) {
131170
+ return this.documentManager.getUris().find((known) => isSameDocumentUri(known, uri)) ?? uri;
131171
+ }
131172
+ };
131173
+ function lineCount(text) {
131174
+ const breaks = text.match(/\r\n|\r|\n/g)?.length ?? 0;
131175
+ return text === "" || /[\r\n]$/.test(text) ? breaks : breaks + 1;
131176
+ }
131177
+ function isUri(value) {
131178
+ return typeof value === "string" && value.trim() !== "";
131179
+ }
131180
+ function invalid(message) {
131181
+ throw new import_node14.ResponseError(import_node14.ErrorCodes.InvalidParams, `sysml/validateText: ${message}`);
131182
+ }
131183
+
131039
131184
  // server/src/utils/fileDiscovery.ts
131040
131185
  var fsp = __toESM(require("node:fs/promises"));
131041
131186
  var path = __toESM(require("node:path"));
@@ -131110,7 +131255,7 @@ function toFsPath(uri) {
131110
131255
  return void 0;
131111
131256
  }
131112
131257
  var connection = createServerConnection();
131113
- var documents = new import_node14.TextDocuments(TextDocument);
131258
+ var documents = new import_node15.TextDocuments(TextDocument);
131114
131259
  var serverStartTime = Date.now();
131115
131260
  var documentManager = new DocumentManager();
131116
131261
  var modelProvider = new SysMLModelProvider(documentManager);
@@ -131127,6 +131272,7 @@ var renameProvider = new RenameProvider(documentManager);
131127
131272
  var codeActionProvider = new CodeActionProvider(documentManager);
131128
131273
  var formattingProvider = new FormattingProvider(documents);
131129
131274
  var semanticValidator = new SemanticValidator(documentManager);
131275
+ var textValidationProvider = new TextValidationProvider(documentManager, semanticValidator, isReported);
131130
131276
  hoverProvider.setSemanticValidator(semanticValidator);
131131
131277
  var hasConfigurationCapability = false;
131132
131278
  var hasWorkspaceFolderCapability = false;
@@ -131138,11 +131284,11 @@ function rootsOf(folderUris) {
131138
131284
  var isWorkspaceFile = false;
131139
131285
  var preloadOnOpen = "workspaceOnly";
131140
131286
  var disabledDiagnosticCodes = /* @__PURE__ */ new Set();
131287
+ function isReported(diagnostic) {
131288
+ return diagnostic.code === void 0 || !disabledDiagnosticCodes.has(String(diagnostic.code));
131289
+ }
131141
131290
  function publishDiagnostics(uri, diagnostics) {
131142
- connection.sendDiagnostics({
131143
- uri,
131144
- diagnostics: diagnostics.filter((diagnostic) => diagnostic.code === void 0 || !disabledDiagnosticCodes.has(String(diagnostic.code)))
131145
- });
131291
+ connection.sendDiagnostics({ uri, diagnostics: diagnostics.filter(isReported) });
131146
131292
  }
131147
131293
  var skipDirs = new Set(DEFAULT_SKIP_DIRS);
131148
131294
  var serverReady = false;
@@ -131275,7 +131421,7 @@ connection.onInitialize((params) => {
131275
131421
  };
131276
131422
  const result = {
131277
131423
  capabilities: {
131278
- textDocumentSync: import_node14.TextDocumentSyncKind.Incremental,
131424
+ textDocumentSync: import_node15.TextDocumentSyncKind.Incremental,
131279
131425
  // Completion
131280
131426
  completionProvider: {
131281
131427
  resolveProvider: true,
@@ -131303,7 +131449,7 @@ connection.onInitialize((params) => {
131303
131449
  // Code actions (quick fixes)
131304
131450
  codeActionProvider: {
131305
131451
  codeActionKinds: [
131306
- import_node14.CodeActionKind.QuickFix
131452
+ import_node15.CodeActionKind.QuickFix
131307
131453
  ]
131308
131454
  },
131309
131455
  // Document formatting
@@ -131324,7 +131470,7 @@ connection.onInitialize((params) => {
131324
131470
  connection.onInitialized(async () => {
131325
131471
  if (hasConfigurationCapability) {
131326
131472
  connection.client.register(
131327
- import_node14.DidChangeConfigurationNotification.type,
131473
+ import_node15.DidChangeConfigurationNotification.type,
131328
131474
  void 0
131329
131475
  );
131330
131476
  }
@@ -131385,6 +131531,8 @@ async function pullSettings() {
131385
131531
  const validation = await connection.workspace.getConfiguration("sysml.validation");
131386
131532
  const codes = validation?.disabledCodes;
131387
131533
  disabledDiagnosticCodes = new Set(Array.isArray(codes) ? codes.filter((code) => typeof code === "string") : []);
131534
+ const experimental = await connection.workspace.getConfiguration("sysml.experimental");
131535
+ textValidationProvider.configure(readTextValidationSettings(experimental?.validateText));
131388
131536
  }
131389
131537
  function applySettings(config) {
131390
131538
  if (!config) return;
@@ -131738,6 +131886,10 @@ connection.onRequest("sysml/elementLookup", async (params) => {
131738
131886
  await initialization;
131739
131887
  return elementLookupProvider.elementLookup(params);
131740
131888
  });
131889
+ connection.onRequest("sysml/validateText", async (params) => {
131890
+ await initialization;
131891
+ return textValidationProvider.validate(params);
131892
+ });
131741
131893
  connection.onRequest("sysml/serverStats", () => {
131742
131894
  const mem = typeof process !== "undefined" && typeof process.memoryUsage === "function" ? process.memoryUsage() : { heapUsed: 0, heapTotal: 0, rss: 0, external: 0 };
131743
131895
  const toMB = (bytes) => Math.round(bytes / 1024 / 1024);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "sysml-v2-lsp",
3
3
  "displayName": "SysML v2 Language Server",
4
4
  "description": "Language Server Protocol implementation for SysML v2, powered by ANTLR4",
5
- "version": "0.37.0",
5
+ "version": "0.38.0",
6
6
  "publisher": "JamieD",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -96,6 +96,40 @@
96
96
  "default": [],
97
97
  "description": "Diagnostic codes to suppress, such as missing-doc. Changes apply to open documents immediately."
98
98
  },
99
+ "sysml.experimental.validateText.enabled": {
100
+ "scope": "window",
101
+ "type": "boolean",
102
+ "default": false,
103
+ "markdownDescription": "Experimental: answer the custom `sysml/validateText` request, which validates SysML text against the workspace model without opening a document or publishing diagnostics. The request is meant for tools that generate or edit SysML text."
104
+ },
105
+ "sysml.experimental.validateText.maxDocuments": {
106
+ "scope": "window",
107
+ "type": "integer",
108
+ "minimum": 1,
109
+ "default": 5,
110
+ "markdownDescription": "Experimental: the most documents one `sysml/validateText` request may validate. Requests with more are rejected."
111
+ },
112
+ "sysml.experimental.validateText.maxLines": {
113
+ "scope": "window",
114
+ "type": "integer",
115
+ "minimum": 1,
116
+ "default": 200,
117
+ "markdownDescription": "Experimental: the most lines of code one document's text may have in a `sysml/validateText` request. Requests with a longer text are rejected. Character limits also apply."
118
+ },
119
+ "sysml.experimental.validateText.maxCharacters": {
120
+ "scope": "window",
121
+ "type": "integer",
122
+ "minimum": 1,
123
+ "default": 8192,
124
+ "markdownDescription": "Experimental: the most characters (UTF-16 code units) per document in a `sysml/validateText` request. Checked before parsing, including for single-line text."
125
+ },
126
+ "sysml.experimental.validateText.maxTotalCharacters": {
127
+ "scope": "window",
128
+ "type": "integer",
129
+ "minimum": 1,
130
+ "default": 16384,
131
+ "markdownDescription": "Experimental: the most characters (UTF-16 code units) across all documents in a `sysml/validateText` request. Checked before parsing."
132
+ },
99
133
  "sysml.maxNumberOfProblems": {
100
134
  "scope": "resource",
101
135
  "type": "number",