@cratis/screenplay-language 4.52.0 → 4.53.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.
Files changed (190) hide show
  1. package/dist/TypeReferenceSymbol.d.ts +8 -0
  2. package/dist/TypeReferenceSymbol.d.ts.map +1 -0
  3. package/dist/bundles/chunk-7WJEZ72S.js +4463 -0
  4. package/dist/bundles/chunk-7WJEZ72S.js.map +7 -0
  5. package/dist/bundles/index.js +1410 -0
  6. package/dist/bundles/index.js.map +7 -0
  7. package/dist/bundles/sub-languages/capture/index.js +677 -0
  8. package/dist/bundles/sub-languages/capture/index.js.map +7 -0
  9. package/dist/bundles/sub-languages/projection/index.js +1647 -0
  10. package/dist/bundles/sub-languages/projection/index.js.map +7 -0
  11. package/dist/code-actions.d.ts +3 -0
  12. package/dist/code-actions.d.ts.map +1 -0
  13. package/dist/completion-items.d.ts +1 -0
  14. package/dist/completion-items.d.ts.map +1 -1
  15. package/dist/completion-planner.d.ts.map +1 -1
  16. package/dist/completions.d.ts.map +1 -1
  17. package/dist/diagnostic-codes.d.ts +3 -0
  18. package/dist/diagnostic-codes.d.ts.map +1 -1
  19. package/dist/diagnostics.d.ts.map +1 -1
  20. package/dist/hover-content.d.ts.map +1 -1
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/keyword-docs.d.ts.map +1 -1
  24. package/dist/language.d.ts +1 -0
  25. package/dist/language.d.ts.map +1 -1
  26. package/dist/production-destinations.d.ts.map +1 -1
  27. package/dist/symbols.d.ts +4 -0
  28. package/dist/symbols.d.ts.map +1 -1
  29. package/dist/tokens.d.ts.map +1 -1
  30. package/dist/validation.d.ts +1 -0
  31. package/dist/validation.d.ts.map +1 -1
  32. package/package.json +13 -9
  33. package/DestinationHint.ts +0 -8
  34. package/ProductionSymbol.ts +0 -11
  35. package/completion-items.ts +0 -261
  36. package/completion-planner.ts +0 -167
  37. package/completions.ts +0 -156
  38. package/diagnostic-codes.ts +0 -43
  39. package/diagnostics.ts +0 -55
  40. package/dist/DestinationHint.js +0 -4
  41. package/dist/DestinationHint.js.map +0 -1
  42. package/dist/ProductionSymbol.js +0 -4
  43. package/dist/ProductionSymbol.js.map +0 -1
  44. package/dist/completion-items.js +0 -228
  45. package/dist/completion-items.js.map +0 -1
  46. package/dist/completion-planner.js +0 -143
  47. package/dist/completion-planner.js.map +0 -1
  48. package/dist/completions.js +0 -101
  49. package/dist/completions.js.map +0 -1
  50. package/dist/diagnostic-codes.js +0 -41
  51. package/dist/diagnostic-codes.js.map +0 -1
  52. package/dist/diagnostics.js +0 -50
  53. package/dist/diagnostics.js.map +0 -1
  54. package/dist/document-context.js +0 -85
  55. package/dist/document-context.js.map +0 -1
  56. package/dist/event-analysis-source.js +0 -19
  57. package/dist/event-analysis-source.js.map +0 -1
  58. package/dist/event-context-catalog.js +0 -116
  59. package/dist/event-context-catalog.js.map +0 -1
  60. package/dist/event-context.js +0 -88
  61. package/dist/event-context.js.map +0 -1
  62. package/dist/file-imports.js +0 -58
  63. package/dist/file-imports.js.map +0 -1
  64. package/dist/file-references.js +0 -70
  65. package/dist/file-references.js.map +0 -1
  66. package/dist/hover-content.js +0 -98
  67. package/dist/hover-content.js.map +0 -1
  68. package/dist/hover.js +0 -15
  69. package/dist/hover.js.map +0 -1
  70. package/dist/index.js +0 -61
  71. package/dist/index.js.map +0 -1
  72. package/dist/inlay-hints.js +0 -15
  73. package/dist/inlay-hints.js.map +0 -1
  74. package/dist/inline-event-validation.js +0 -131
  75. package/dist/inline-event-validation.js.map +0 -1
  76. package/dist/keyword-docs.js +0 -148
  77. package/dist/keyword-docs.js.map +0 -1
  78. package/dist/language.js +0 -196
  79. package/dist/language.js.map +0 -1
  80. package/dist/production-destinations.js +0 -32
  81. package/dist/production-destinations.js.map +0 -1
  82. package/dist/sub-language-registry.js +0 -18
  83. package/dist/sub-language-registry.js.map +0 -1
  84. package/dist/sub-languages/capture/CompletionProvider.js +0 -332
  85. package/dist/sub-languages/capture/CompletionProvider.js.map +0 -1
  86. package/dist/sub-languages/capture/HoverProvider.js +0 -87
  87. package/dist/sub-languages/capture/HoverProvider.js.map +0 -1
  88. package/dist/sub-languages/capture/Validator.js +0 -72
  89. package/dist/sub-languages/capture/Validator.js.map +0 -1
  90. package/dist/sub-languages/capture/index.js +0 -58
  91. package/dist/sub-languages/capture/index.js.map +0 -1
  92. package/dist/sub-languages/capture/language.js +0 -160
  93. package/dist/sub-languages/capture/language.js.map +0 -1
  94. package/dist/sub-languages/cdl.js +0 -74
  95. package/dist/sub-languages/cdl.js.map +0 -1
  96. package/dist/sub-languages/pdl.js +0 -73
  97. package/dist/sub-languages/pdl.js.map +0 -1
  98. package/dist/sub-languages/projection/CodeActionProvider.js +0 -93
  99. package/dist/sub-languages/projection/CodeActionProvider.js.map +0 -1
  100. package/dist/sub-languages/projection/CompletionProvider.js +0 -595
  101. package/dist/sub-languages/projection/CompletionProvider.js.map +0 -1
  102. package/dist/sub-languages/projection/HoverProvider.js +0 -333
  103. package/dist/sub-languages/projection/HoverProvider.js.map +0 -1
  104. package/dist/sub-languages/projection/Validator.js +0 -388
  105. package/dist/sub-languages/projection/Validator.js.map +0 -1
  106. package/dist/sub-languages/projection/index.js +0 -160
  107. package/dist/sub-languages/projection/index.js.map +0 -1
  108. package/dist/sub-languages/projection/language.js +0 -171
  109. package/dist/sub-languages/projection/language.js.map +0 -1
  110. package/dist/symbols.js +0 -275
  111. package/dist/symbols.js.map +0 -1
  112. package/dist/themes/screenplay-dark.js +0 -30
  113. package/dist/themes/screenplay-dark.js.map +0 -1
  114. package/dist/themes/screenplay-light.js +0 -30
  115. package/dist/themes/screenplay-light.js.map +0 -1
  116. package/dist/tokens.js +0 -147
  117. package/dist/tokens.js.map +0 -1
  118. package/dist/validation.js +0 -248
  119. package/dist/validation.js.map +0 -1
  120. package/document-context.ts +0 -93
  121. package/event-analysis-source.ts +0 -18
  122. package/event-context-catalog.ts +0 -135
  123. package/event-context.ts +0 -119
  124. package/file-imports.ts +0 -70
  125. package/file-references.ts +0 -87
  126. package/for_documentContext/when_stripping_comments.ts +0 -32
  127. package/for_eventContext/when_resolving_generated_catalog_vectors.ts +0 -15
  128. package/for_eventGenerations/when_editing_event_generations.ts +0 -57
  129. package/for_fileImports/when_finding_file_imports.ts +0 -38
  130. package/for_fileReferences/when_recognizing_an_absolute_path/with_paths_of_every_shape.ts +0 -26
  131. package/for_fileReferences/when_scanning_a_document/inside_a_code_fence.ts +0 -26
  132. package/for_fileReferences/when_scanning_a_document/with_a_bare_file_keyword.ts +0 -19
  133. package/for_fileReferences/when_scanning_a_document/with_a_file_directive.ts +0 -30
  134. package/for_fileReferences/when_scanning_a_document/with_a_property_named_file.ts +0 -25
  135. package/for_fileReferences/when_scanning_a_document/with_a_trailing_comment.ts +0 -25
  136. package/for_fileReferences/when_scanning_a_document/with_directives_on_several_constructs.ts +0 -41
  137. package/for_importablePaths/when_listing_what_a_document_can_import.ts +0 -34
  138. package/for_inlineEvents/when_analyzing_many_inline_events.ts +0 -59
  139. package/for_inlineEvents/when_authoring_inline_events.ts +0 -149
  140. package/for_inlineEvents/when_completing_many_events.ts +0 -49
  141. package/for_inlineEvents/when_indexing_command_production_ownership.ts +0 -79
  142. package/for_planCompletions/when_completing_a_file_import.ts +0 -26
  143. package/for_planCompletions/when_completing_a_specification_query.ts +0 -24
  144. package/for_planCompletions/when_completing_a_specification_step.ts +0 -42
  145. package/for_planCompletions/when_completing_an_absent_read_model.ts +0 -14
  146. package/for_planCompletions/when_completing_reaction_reads.ts +0 -36
  147. package/for_projectionCompletionProvider/when_completing_an_event_context_path.ts +0 -102
  148. package/for_projectionHoverProvider/when_hovering_an_event_context_path.ts +0 -44
  149. package/for_projectionValidator/when_validating_a_projection_with_variants.ts +0 -128
  150. package/for_projectionValidator/when_validating_event_context_paths.ts +0 -31
  151. package/for_scanDocument/when_scanning_a_query/with_a_dotted_return_type.ts +0 -34
  152. package/for_scanDocument/when_scanning_aliased_reads.ts +0 -33
  153. package/for_scanDocument/when_scanning_many_commands.ts +0 -47
  154. package/for_tokens/when_highlighting_a_file_import.ts +0 -18
  155. package/for_tokens/when_highlighting_event_metadata.ts +0 -63
  156. package/for_tokens/when_highlighting_specification_steps.ts +0 -28
  157. package/for_validateLines/when_validating_a_document/with_a_problem_of_every_coded_kind.ts +0 -77
  158. package/for_validateLines/when_validating_a_document/with_an_unknown_policy.ts +0 -33
  159. package/for_validateLines/when_validating_a_file_placed_by_an_import/with_names_declared_in_other_files.ts +0 -43
  160. package/for_validateLines/when_validating_event_context_paths/in_a_projection.ts +0 -97
  161. package/for_validateLines/when_validating_file_imports/at_every_level.ts +0 -36
  162. package/for_validateLines/when_validating_production_destinations/with_an_available_identifier.ts +0 -120
  163. package/hover-content.ts +0 -117
  164. package/hover.ts +0 -23
  165. package/index.ts +0 -125
  166. package/inlay-hints.ts +0 -17
  167. package/inline-event-validation.ts +0 -115
  168. package/keyword-docs.ts +0 -152
  169. package/language.ts +0 -212
  170. package/production-destinations.ts +0 -32
  171. package/sub-language-registry.ts +0 -42
  172. package/sub-languages/capture/CompletionProvider.ts +0 -365
  173. package/sub-languages/capture/HoverProvider.ts +0 -99
  174. package/sub-languages/capture/Validator.ts +0 -97
  175. package/sub-languages/capture/index.ts +0 -72
  176. package/sub-languages/capture/language.ts +0 -176
  177. package/sub-languages/cdl.ts +0 -76
  178. package/sub-languages/pdl.ts +0 -75
  179. package/sub-languages/projection/CodeActionProvider.ts +0 -123
  180. package/sub-languages/projection/CompletionProvider.ts +0 -660
  181. package/sub-languages/projection/HoverProvider.ts +0 -377
  182. package/sub-languages/projection/Validator.ts +0 -441
  183. package/sub-languages/projection/index.ts +0 -223
  184. package/sub-languages/projection/language.ts +0 -193
  185. package/symbols.ts +0 -374
  186. package/themes/screenplay-dark.ts +0 -33
  187. package/themes/screenplay-light.ts +0 -33
  188. package/tokens.ts +0 -174
  189. package/validation.ts +0 -443
  190. package/vitest.setup.ts +0 -5
@@ -1,441 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import type { editor } from 'monaco-editor';
5
- import type { JsonSchema, ReadModelInfo, DraftReadModelInfo } from './index';
6
- import { fenceMap } from '../../document-context';
7
- import { validateEventContextPaths } from '../../validation';
8
-
9
- export class Validator {
10
- private readModels: ReadModelInfo[] = [];
11
- private eventSchemas: Record<string, JsonSchema> = {};
12
- private draftReadModel: DraftReadModelInfo | null = null;
13
-
14
- setReadModels(readModels: ReadModelInfo[]): void {
15
- this.readModels = readModels || [];
16
- }
17
-
18
- setDraftReadModel(draft: DraftReadModelInfo | null): void {
19
- this.draftReadModel = draft;
20
- }
21
-
22
- // Keep for backwards compatibility
23
- setReadModelSchemas(schemas: JsonSchema[]): void {
24
- this.readModels = schemas.map(schema => ({
25
- identifier: this.getSchemaName(schema) || '',
26
- displayName: this.getSchemaName(schema) || '',
27
- schema
28
- }));
29
- }
30
-
31
- setEventSchemas(schemas: Record<string, JsonSchema> | JsonSchema[]): void {
32
- if (!schemas) {
33
- this.eventSchemas = {};
34
- return;
35
- }
36
-
37
- if (Array.isArray(schemas)) {
38
- const map: Record<string, JsonSchema> = {};
39
- schemas.forEach((s, i) => {
40
- if (!s) return;
41
- const name = this.getSchemaName(s) || `Event${i + 1}`;
42
- map[name] = s;
43
- });
44
- this.eventSchemas = map;
45
- return;
46
- }
47
-
48
- this.eventSchemas = schemas;
49
- }
50
-
51
- validate(model: editor.ITextModel): editor.IMarkerData[] {
52
- const markers: editor.IMarkerData[] = [];
53
- const content = model.getValue();
54
-
55
- // Skip validation if content is empty or only whitespace
56
- // This prevents spurious errors when the model is first created before content is loaded
57
- if (!content.trim()) {
58
- return markers;
59
- }
60
-
61
- // Normalize line endings and split
62
- const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
63
-
64
- // Find the first non-empty, non-comment line
65
- let firstNonEmptyLineIndex = 0;
66
- let firstLine = '';
67
- for (let i = 0; i < lines.length; i++) {
68
- const trimmed = lines[i].trim();
69
- if (trimmed && !trimmed.startsWith('#')) {
70
- firstNonEmptyLineIndex = i;
71
- firstLine = trimmed;
72
- break;
73
- }
74
- }
75
-
76
- if (!firstLine) {
77
- // This should not happen since we already checked for empty content above,
78
- // but if it does (content is all comments), just return without error
79
- return markers;
80
- }
81
-
82
- // Validate first meaningful line - projection declaration
83
- // Supports both "projection Name => ReadModel" and ad hoc "projection Name"
84
- const projectionWithReadModelMatch = firstLine.match(/^projection\s+([\w.]+)\s*=>\s*([\w.]+)\s*$/);
85
- const projectionAdHocMatch = !projectionWithReadModelMatch && firstLine.match(/^projection\s+([\w.]+)\s*$/);
86
-
87
- if (!projectionWithReadModelMatch && !projectionAdHocMatch) {
88
- const lineNumber = firstNonEmptyLineIndex + 1;
89
- markers.push(this.createError(lineNumber, 1, firstLine.length + 1, 'Projection definition must start with "projection <ProjectionName>" or "projection <ProjectionName> => <ReadModelIdentifier>"'));
90
- return markers;
91
- }
92
-
93
- let activeSchema: JsonSchema | undefined;
94
- let readModelName: string | null = null;
95
-
96
- if (projectionWithReadModelMatch) {
97
- readModelName = projectionWithReadModelMatch[2];
98
-
99
- // Validate read model exists in schemas or is a draft
100
- const readModelExists = this.resolveReadModelIdentifier(readModelName) !== null;
101
- const isDraftReadModel = this.isDraftReadModel(readModelName);
102
- const readModelStartColumn = this.getReadModelStartColumn(firstLine, readModelName);
103
-
104
- if (!readModelExists && !isDraftReadModel) {
105
- const lineNumber = firstNonEmptyLineIndex + 1;
106
- const col = readModelStartColumn;
107
- markers.push(this.createError(lineNumber, col, col + readModelName.length, `Read model '${readModelName}' not found`));
108
- } else if (isDraftReadModel) {
109
- // Show info marker for draft read models - they exist but haven't been saved yet
110
- const lineNumber = firstNonEmptyLineIndex + 1;
111
- const col = readModelStartColumn;
112
- markers.push(this.createInfo(lineNumber, col, col + readModelName.length, `Read model '${readModelName}' is a draft (not yet saved)`));
113
- }
114
-
115
- // Get the active read model schema for property validation (check draft first)
116
- const isDraft = this.isDraftReadModel(readModelName);
117
- const activeReadModel = isDraft ? null : this.resolveReadModelInfo(readModelName);
118
- activeSchema = isDraft ? this.draftReadModel!.schema : activeReadModel?.schema;
119
- }
120
- // For ad hoc projections (no read model), activeSchema stays undefined — property validation is skipped
121
-
122
- // Validate subsequent lines
123
- const contextStack: JsonSchema[] = [activeSchema!];
124
- const indentStack: number[] = [0]; // Track indentation levels for context
125
- let lastIndent = 0;
126
-
127
- // Track event types at each context level to detect duplicates
128
- // Each entry in the stack represents events seen at that context level
129
- type EventInfo = { lineNumber: number; col: number };
130
- const eventContextStack: Map<string, EventInfo>[] = [new Map()];
131
-
132
- for (let i = 1; i < lines.length; i++) {
133
- const lineNumber = i + 1;
134
- const line = lines[i];
135
- const trimmed = line.trim();
136
-
137
- if (!trimmed || trimmed.startsWith('#')) continue;
138
-
139
- // Calculate current indentation
140
- const currentIndent = line.search(/\S/);
141
-
142
- // Pop context when dedenting (exiting a children block)
143
- while (currentIndent < lastIndent && indentStack.length > 1) {
144
- const poppedIndent = indentStack.pop();
145
- if (poppedIndent !== undefined && currentIndent < poppedIndent) {
146
- contextStack.pop();
147
- eventContextStack.pop();
148
- }
149
- }
150
- lastIndent = currentIndent;
151
-
152
- // Get the current event context (events seen at this level)
153
- const currentEventContext = eventContextStack[eventContextStack.length - 1];
154
-
155
- // Validate event types in from/every blocks
156
- const fromMatch = trimmed.match(/^from\s+([\w-]+)/);
157
- if (fromMatch) {
158
- const eventType = fromMatch[1];
159
- const col = line.indexOf(eventType) + 1;
160
-
161
- if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
162
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
163
- }
164
-
165
- // Check for duplicate events at this level
166
- if (currentEventContext.has(eventType)) {
167
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
168
- } else {
169
- currentEventContext.set(eventType, { lineNumber, col });
170
- }
171
- }
172
-
173
- // Validate variant declarations - each names its own read model, which becomes the active
174
- // schema for property validation inside the block (mirrors the projection-header handling above).
175
- const variantMatch = trimmed.match(/^variant\s+([\w.]+)\s*$/);
176
- if (variantMatch) {
177
- const [, variantName] = variantMatch;
178
- const col = line.indexOf(variantName) + 1;
179
- const isDraft = this.isDraftReadModel(variantName);
180
- const readModelExists = this.resolveReadModelIdentifier(variantName) !== null;
181
-
182
- if (!readModelExists && !isDraft) {
183
- markers.push(this.createError(lineNumber, col, col + variantName.length, `Read model '${variantName}' not found`));
184
- } else if (isDraft) {
185
- markers.push(this.createInfo(lineNumber, col, col + variantName.length, `Read model '${variantName}' is a draft (not yet saved)`));
186
- }
187
-
188
- const variantSchema = isDraft ? this.draftReadModel!.schema : this.resolveReadModelInfo(variantName)?.schema;
189
- contextStack.push(variantSchema!);
190
- indentStack.push(currentIndent);
191
- eventContextStack.push(new Map());
192
- }
193
-
194
- // Validate 'enters on' - the event that activates a variant. Read exactly like a 'from' block for
195
- // event-existence and duplicate-at-this-level purposes.
196
- const entersOnMatch = trimmed.match(/^enters\s+on\s+([\w-]+)/);
197
- if (entersOnMatch) {
198
- const eventType = entersOnMatch[1];
199
- const col = line.indexOf(eventType) + 1;
200
-
201
- if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
202
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
203
- }
204
-
205
- if (currentEventContext.has(eventType)) {
206
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
207
- } else {
208
- currentEventContext.set(eventType, { lineNumber, col });
209
- }
210
- }
211
-
212
- // Validate event types in join/remove blocks
213
- const eventsMatch = trimmed.match(/^events\s+([\w-]+)/);
214
- if (eventsMatch) {
215
- const eventType = eventsMatch[1];
216
- const col = line.indexOf(eventType) + 1;
217
-
218
- if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
219
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
220
- }
221
-
222
- // Check for duplicate events at this level (within join blocks)
223
- if (currentEventContext.has(eventType)) {
224
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
225
- } else {
226
- currentEventContext.set(eventType, { lineNumber, col });
227
- }
228
- }
229
-
230
- const removeWithMatch = trimmed.match(/^remove\s+(?:with|via\s+join\s+on)\s+([\w-]+)/);
231
- if (removeWithMatch) {
232
- const eventType = removeWithMatch[1];
233
- const col = line.indexOf(eventType) + 1;
234
-
235
- if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
236
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
237
- }
238
-
239
- // Check for duplicate events at this level
240
- if (currentEventContext.has(eventType)) {
241
- markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
242
- } else {
243
- currentEventContext.set(eventType, { lineNumber, col });
244
- }
245
- }
246
-
247
- // Validate property assignments
248
- const assignmentMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
249
- if (assignmentMatch) {
250
- const currentSchema = contextStack[contextStack.length - 1];
251
- if (currentSchema?.properties) {
252
- const [, propertyName] = assignmentMatch;
253
- if (!currentSchema.properties[propertyName]) {
254
- const col = line.indexOf(propertyName) + 1;
255
- const schemaName = this.getSchemaName(currentSchema) || 'current schema';
256
- markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' not found in ${schemaName}`));
257
- }
258
- }
259
- }
260
-
261
- // Validate increment/decrement/count targets
262
- const numericOpMatch = trimmed.match(/^(?:increment|decrement|count)\s+(\w+)$/);
263
- if (numericOpMatch) {
264
- const currentSchema = contextStack[contextStack.length - 1];
265
- if (currentSchema?.properties) {
266
- const [, propertyName] = numericOpMatch;
267
- if (!currentSchema.properties[propertyName]) {
268
- const col = line.indexOf(propertyName) + 1;
269
- const schemaName = this.getSchemaName(currentSchema) || 'current schema';
270
- markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' not found in ${schemaName}`));
271
- }
272
- }
273
- }
274
-
275
- // Validate add/subtract targets
276
- const addSubMatch = trimmed.match(/^(?:add|subtract)\s+(\w+)\s+by\s+/);
277
- if (addSubMatch) {
278
- const currentSchema = contextStack[contextStack.length - 1];
279
- if (currentSchema?.properties) {
280
- const [, propertyName] = addSubMatch;
281
- if (!currentSchema.properties[propertyName]) {
282
- const col = line.indexOf(propertyName) + 1;
283
- const schemaName = this.getSchemaName(currentSchema) || 'current schema';
284
- markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' not found in ${schemaName}`));
285
- }
286
- }
287
- }
288
-
289
- // Validate children references
290
- const childrenMatch = trimmed.match(/^children\s+(\w+)\s+identified\s+by\s+/);
291
- if (childrenMatch) {
292
- const currentSchema = contextStack[contextStack.length - 1];
293
- if (currentSchema?.properties) {
294
- const [, propertyName] = childrenMatch;
295
- const prop = currentSchema.properties[propertyName];
296
- if (!prop) {
297
- const col = line.indexOf(propertyName) + 1;
298
- const schemaName = this.getSchemaName(currentSchema) || 'current schema';
299
- markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Collection property '${propertyName}' not found in ${schemaName}`));
300
- } else if (prop.type !== 'array' && !prop.items) {
301
- const col = line.indexOf(propertyName) + 1;
302
- markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' is not a collection (array) type`));
303
- } else {
304
- // Push child schema onto context stack for validating nested properties
305
- const childSchema = prop.items;
306
- if (childSchema) {
307
- contextStack.push(childSchema);
308
- indentStack.push(currentIndent);
309
- // Push a new event context for the children block
310
- eventContextStack.push(new Map());
311
- }
312
- }
313
- }
314
- }
315
-
316
- // Validate join references
317
- const joinMatch = trimmed.match(/^join\s+(\w+)\s+on\s+(\w+)/);
318
- if (joinMatch && activeSchema?.properties) {
319
- const [, collectionName, joinProperty] = joinMatch;
320
- const collectionProp = activeSchema.properties[collectionName];
321
- if (!collectionProp) {
322
- const col = line.indexOf(collectionName) + 1;
323
- markers.push(this.createWarning(lineNumber, col, col + collectionName.length, `Collection property '${collectionName}' not found in read model '${readModelName}'`));
324
- } else if (collectionProp.type !== 'array' && !collectionProp.items) {
325
- const col = line.indexOf(collectionName) + 1;
326
- markers.push(this.createWarning(lineNumber, col, col + collectionName.length, `Property '${collectionName}' is not a collection (array) type`));
327
- }
328
-
329
- if (!activeSchema.properties[joinProperty]) {
330
- const col = line.indexOf(joinProperty, line.indexOf('on')) + 1;
331
- markers.push(this.createWarning(lineNumber, col, col + joinProperty.length, `Property '${joinProperty}' not found in read model '${readModelName}'`));
332
- }
333
-
334
- // Push a new event context for the join block
335
- eventContextStack.push(new Map());
336
- indentStack.push(currentIndent);
337
- }
338
-
339
- // Validate key references for composite keys
340
- const keyMatch = trimmed.match(/^key\s+(\w+)\s*$/);
341
- if (keyMatch && activeSchema) {
342
- const [, keyName] = keyMatch;
343
- // Check if it's a composite key type (should exist in definitions or as a property)
344
- const isInDefinitions = activeSchema.definitions && activeSchema.definitions[keyName];
345
- const isProperty = activeSchema.properties && activeSchema.properties[keyName];
346
-
347
- if (!isInDefinitions && !isProperty && !this.isBuiltInExpression(keyName)) {
348
- const col = line.indexOf(keyName) + 1;
349
- markers.push(this.createWarning(lineNumber, col, col + keyName.length, `Composite key type '${keyName}' not found in read model schema`));
350
- }
351
- }
352
- }
353
-
354
- // Share the host's event-context path checks and catalog resolution, including dynamic dictionary keys.
355
- for (const issue of validateEventContextPaths(lines, fenceMap(lines))) {
356
- markers.push({
357
- severity: issue.severity === 'error' ? 8 : 4,
358
- startLineNumber: issue.line + 1,
359
- endLineNumber: issue.line + 1,
360
- startColumn: issue.startColumn,
361
- endColumn: issue.endColumn,
362
- message: issue.message,
363
- code: issue.code,
364
- });
365
- }
366
-
367
- return markers;
368
- }
369
-
370
- private resolveReadModelInfo(readModelToken: string): ReadModelInfo | null {
371
- return this.readModels.find(rm => rm.identifier === readModelToken || rm.displayName === readModelToken) || null;
372
- }
373
-
374
- private getReadModelStartColumn(firstLine: string, readModelName: string): number {
375
- const prefixMatch = firstLine.match(/^\s*projection\s+[\w.]+\s*=>\s*/);
376
- if (!prefixMatch) {
377
- const fallbackIndex = firstLine.lastIndexOf(readModelName);
378
- return (fallbackIndex >= 0 ? fallbackIndex : 0) + 1;
379
- }
380
- return prefixMatch[0].length + 1;
381
- }
382
-
383
- private resolveReadModelIdentifier(readModelToken: string): string | null {
384
- if (this.isDraftReadModel(readModelToken)) {
385
- return this.draftReadModel!.identifier;
386
- }
387
- const readModel = this.resolveReadModelInfo(readModelToken);
388
- return readModel?.identifier ?? null;
389
- }
390
-
391
- private isDraftReadModel(readModelToken: string): boolean {
392
- return !!this.draftReadModel && (this.draftReadModel.identifier === readModelToken || this.draftReadModel.displayName === readModelToken);
393
- }
394
-
395
- private isBuiltInExpression(text: string): boolean {
396
- return text.startsWith('$') || ['true', 'false', 'null'].includes(text);
397
- }
398
-
399
- private createError(line: number, startCol: number, endCol: number, message: string): editor.IMarkerData {
400
- return {
401
- severity: 8, // Error
402
- startLineNumber: line,
403
- startColumn: startCol,
404
- endLineNumber: line,
405
- endColumn: endCol,
406
- message,
407
- };
408
- }
409
-
410
- private createWarning(line: number, startCol: number, endCol: number, message: string): editor.IMarkerData {
411
- return {
412
- severity: 4, // Warning
413
- startLineNumber: line,
414
- startColumn: startCol,
415
- endLineNumber: line,
416
- endColumn: endCol,
417
- message,
418
- };
419
- }
420
-
421
- private createInfo(line: number, startCol: number, endCol: number, message: string): editor.IMarkerData {
422
- return {
423
- severity: 2, // Info (hint) - shows as blue/green squiggly
424
- startLineNumber: line,
425
- startColumn: startCol,
426
- endLineNumber: line,
427
- endColumn: endCol,
428
- message,
429
- };
430
- }
431
-
432
- private getSchemaName(schema: JsonSchema): string | undefined {
433
- if (schema.name) return schema.name;
434
- if (schema.title) return schema.title;
435
- if (typeof schema.$id === 'string') {
436
- const parts = schema.$id.split('/');
437
- return parts[parts.length - 1] || schema.$id;
438
- }
439
- return undefined;
440
- }
441
- }
@@ -1,223 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import type * as Monaco from 'monaco-editor';
5
- import { configuration, languageId, monarchLanguage } from './language';
6
- import { CompletionProvider } from './CompletionProvider';
7
- import { Validator } from './Validator';
8
- import { HoverProvider } from './HoverProvider';
9
- import { CodeActionProvider } from './CodeActionProvider';
10
-
11
- export interface JsonSchema {
12
- title?: string;
13
- name?: string;
14
- $id?: string;
15
- $ref?: string;
16
- type?: string;
17
- format?: string;
18
- description?: string;
19
- properties?: Record<string, JsonSchemaProperty>;
20
- items?: JsonSchema;
21
- required?: string[];
22
- definitions?: Record<string, JsonSchema>;
23
- }
24
-
25
- export interface JsonSchemaProperty {
26
- id?: string;
27
- name?: string;
28
- type?: string;
29
- format?: string;
30
- description?: string;
31
- items?: JsonSchema;
32
- properties?: Record<string, JsonSchemaProperty>;
33
- required?: boolean;
34
- $ref?: string;
35
- }
36
-
37
- export interface ReadModelInfo {
38
- identifier: string;
39
- displayName: string;
40
- schema: JsonSchema;
41
- }
42
-
43
- // Kept as its own type (rather than duplicated inline in the validator and code action
44
- // provider, as the Chronicle original had it) since both consume the exact same shape.
45
- export interface DraftReadModelInfo {
46
- identifier: string;
47
- displayName: string;
48
- containerName: string;
49
- schema: JsonSchema;
50
- }
51
-
52
- let validator: Validator | undefined;
53
- let completionProvider: CompletionProvider | undefined;
54
- let hoverProvider: HoverProvider | undefined;
55
- let codeActionProvider: CodeActionProvider | undefined;
56
- let disposables: Monaco.IDisposable[] = [];
57
- let monacoInstance: typeof Monaco | null = null;
58
- let isRegistered = false;
59
- let pendingCreateReadModelCallback: ((readModelName: string) => void) | null = null;
60
- let pendingEditReadModelCallback: ((readModelName: string, currentSchema: JsonSchema) => void) | null = null;
61
-
62
- export function registerProjectionLanguage(monaco: typeof Monaco): { dispose(): void } {
63
- if (!isRegistered) {
64
- isRegistered = true;
65
- monacoInstance = monaco;
66
-
67
- monaco.languages.register({ id: languageId });
68
- monaco.languages.setLanguageConfiguration(languageId, configuration);
69
- monaco.languages.setMonarchTokensProvider(languageId, monarchLanguage);
70
-
71
- validator = new Validator();
72
- completionProvider = new CompletionProvider();
73
- hoverProvider = new HoverProvider();
74
- codeActionProvider = new CodeActionProvider();
75
-
76
- // Apply pending callbacks if they were set before initialization
77
- if (pendingCreateReadModelCallback) {
78
- codeActionProvider.setCreateReadModelCallback(pendingCreateReadModelCallback);
79
- pendingCreateReadModelCallback = null;
80
- }
81
- if (pendingEditReadModelCallback) {
82
- codeActionProvider.setEditReadModelCallback(pendingEditReadModelCallback);
83
- pendingEditReadModelCallback = null;
84
- }
85
-
86
- // Register completion provider with helpful trigger characters
87
- disposables.push(monaco.languages.registerCompletionItemProvider(languageId, {
88
- provideCompletionItems: completionProvider.provideCompletionItems.bind(completionProvider),
89
- triggerCharacters: ['.', ' ', '=', '\n', '$'],
90
- }));
91
-
92
- // Register hover provider
93
- disposables.push(monaco.languages.registerHoverProvider(languageId, {
94
- provideHover: hoverProvider.provideHover.bind(hoverProvider),
95
- }));
96
-
97
- // Register code action provider
98
- disposables.push(monaco.languages.registerCodeActionProvider(languageId, {
99
- provideCodeActions: codeActionProvider.provideCodeActions.bind(codeActionProvider),
100
- }, {
101
- providedCodeActionKinds: ['quickfix']
102
- }));
103
-
104
- // Register command for creating read models
105
- disposables.push(monaco.editor.registerCommand('pdl.createReadModel', (_accessor: unknown, readModelName: string) => {
106
- codeActionProvider?.invokeCreateReadModel(readModelName);
107
- }));
108
-
109
- // Register command for editing read models
110
- disposables.push(monaco.editor.registerCommand('pdl.editReadModel', (_accessor: unknown, readModelName: string, currentSchema: JsonSchema) => {
111
- codeActionProvider?.invokeEditReadModel(readModelName, currentSchema);
112
- }));
113
-
114
- // Register validation on model change
115
- disposables.push(monaco.editor.onDidCreateModel((model: Monaco.editor.ITextModel) => {
116
- if (model.getLanguageId() === languageId) {
117
- validateModel(monaco, model);
118
- disposables.push(model.onDidChangeContent(() => validateModel(monaco, model)));
119
- }
120
- }));
121
-
122
- // Validate existing models
123
- monaco.editor.getModels().forEach((model: Monaco.editor.ITextModel) => {
124
- if (model.getLanguageId() === languageId) {
125
- validateModel(monaco, model);
126
- }
127
- });
128
- }
129
-
130
- return { dispose };
131
- }
132
-
133
- function dispose(): void {
134
- disposables.forEach((disposable) => disposable.dispose());
135
- disposables = [];
136
- isRegistered = false;
137
- monacoInstance = null;
138
- }
139
-
140
- export function setReadModelSchema(schema: JsonSchema): void {
141
- // Backwards compatible single-schema setter
142
- validator?.setReadModelSchemas([schema]);
143
- completionProvider?.setReadModelSchemas([schema]);
144
- }
145
-
146
- export function setReadModelSchemas(readModels: ReadModelInfo[]): void {
147
- validator?.setReadModels(readModels);
148
- completionProvider?.setReadModels(readModels);
149
- hoverProvider?.setReadModels(readModels);
150
- codeActionProvider?.setReadModels(readModels);
151
- revalidateAllModels();
152
- }
153
-
154
- export function setCreateReadModelCallback(callback: (readModelName: string) => void): void {
155
- if (codeActionProvider) {
156
- codeActionProvider.setCreateReadModelCallback(callback);
157
- } else {
158
- // Store callback for later when the provider is initialized
159
- pendingCreateReadModelCallback = callback;
160
- }
161
- }
162
-
163
- export function setEditReadModelCallback(callback: (readModelName: string, currentSchema: JsonSchema) => void): void {
164
- if (codeActionProvider) {
165
- codeActionProvider.setEditReadModelCallback(callback);
166
- } else {
167
- // Store callback for later when the provider is initialized
168
- pendingEditReadModelCallback = callback;
169
- }
170
- }
171
-
172
- export function setDraftReadModel(draft: DraftReadModelInfo | null): void {
173
- codeActionProvider?.setDraftReadModel(draft);
174
- validator?.setDraftReadModel(draft);
175
- hoverProvider?.setDraftReadModel(draft);
176
- revalidateAllModels();
177
- }
178
-
179
- export function setEventSchemas(eventSchemas: JsonSchema[] | Record<string, JsonSchema>): void {
180
- // Normalize either an array of schemas or a keyed record into a record keyed by derived schema name
181
- const normalize = (input: JsonSchema[] | Record<string, JsonSchema>): Record<string, JsonSchema> => {
182
- if (!input) return {};
183
- if (Array.isArray(input)) {
184
- const out: Record<string, JsonSchema> = {};
185
- input.forEach((s, i) => {
186
- if (!s) return;
187
- const name = s.title || s.name || (typeof s.$id === 'string' ? s.$id.split('/').pop() : undefined) || `Event${i + 1}`;
188
- out[name] = s;
189
- });
190
- return out;
191
- }
192
- return input;
193
- };
194
-
195
- const normalized = normalize(eventSchemas);
196
- validator?.setEventSchemas(normalized);
197
- completionProvider?.setEventSchemas(normalized);
198
- hoverProvider?.setEventSchemas(normalized);
199
- revalidateAllModels();
200
- }
201
-
202
- export function setEventSequences(sequences: string[]): void {
203
- completionProvider?.setEventSequences(sequences);
204
- }
205
-
206
- function validateModel(monaco: typeof Monaco, model: Monaco.editor.ITextModel): void {
207
- if (validator && !model.isDisposed()) {
208
- const markers = validator.validate(model);
209
- monaco.editor.setModelMarkers(model, 'pdl-validator', markers);
210
- }
211
- }
212
-
213
- function revalidateAllModels(): void {
214
- if (monacoInstance) {
215
- monacoInstance.editor.getModels().forEach((model: Monaco.editor.ITextModel) => {
216
- if (model.getLanguageId() === languageId) {
217
- validateModel(monacoInstance!, model);
218
- }
219
- });
220
- }
221
- }
222
-
223
- export { languageId };