@cratis/screenplay-language 4.51.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,30 +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 { describe, beforeEach, it } from 'vitest';
5
- import { FileReference, fileReferences } from '../../file-references';
6
-
7
- describe('when scanning a document with a file directive', () => {
8
- let result: FileReference[];
9
-
10
- beforeEach(() => {
11
- result = fileReferences(['command RegisterInvoice', ' handler', ' file Handlers/RegisterInvoice.cs']);
12
- });
13
-
14
- it('should find the reference', () => {
15
- result.should.have.lengthOf(1);
16
- });
17
-
18
- it('should carry the declared path verbatim', () => {
19
- result[0].path.should.equal('Handlers/RegisterInvoice.cs');
20
- });
21
-
22
- it('should report the line it sits on', () => {
23
- result[0].line.should.equal(2);
24
- });
25
-
26
- it('should span the path and not the keyword', () => {
27
- result[0].startColumn.should.equal(14);
28
- result[0].endColumn.should.equal(14 + 'Handlers/RegisterInvoice.cs'.length);
29
- });
30
- });
@@ -1,25 +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 { describe, beforeEach, it } from 'vitest';
5
- import { FileReference, fileReferences } from '../../file-references';
6
-
7
- // `file String` declares a property called `file`; only a path is the directive. The two are told
8
- // apart by shape, and the property has to keep winning the tie.
9
- describe('when scanning a document with a property named file', () => {
10
- let result: FileReference[];
11
-
12
- beforeEach(() => {
13
- result = fileReferences([
14
- 'type Attachment',
15
- ' file String',
16
- ' name String',
17
- ' thumbnails String[]',
18
- ' caption String?',
19
- ]);
20
- });
21
-
22
- it('should find no reference', () => {
23
- result.should.be.empty;
24
- });
25
- });
@@ -1,25 +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 { describe, beforeEach, it } from 'vitest';
5
- import { FileReference, fileReferences } from '../../file-references';
6
-
7
- describe('when scanning a document with a trailing comment after a file directive', () => {
8
- let result: FileReference[];
9
-
10
- beforeEach(() => {
11
- result = fileReferences([' file Screens/InvoiceLineReport.tsx // generated by Arc']);
12
- });
13
-
14
- it('should find the reference', () => {
15
- result.should.have.lengthOf(1);
16
- });
17
-
18
- it('should leave the comment out of the path', () => {
19
- result[0].path.should.equal('Screens/InvoiceLineReport.tsx');
20
- });
21
-
22
- it('should span only the path', () => {
23
- result[0].endColumn.should.equal(result[0].startColumn + 'Screens/InvoiceLineReport.tsx'.length);
24
- });
25
- });
@@ -1,41 +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 { describe, beforeEach, it } from 'vitest';
5
- import { FileReference, fileReferences } from '../../file-references';
6
-
7
- // The shape a captured application actually has — the directive appears under validation rules,
8
- // constraints, handlers, screens, performers and reactions alike, at whatever depth they sit.
9
- describe('when scanning a document with directives on several constructs', () => {
10
- let result: FileReference[];
11
-
12
- beforeEach(() => {
13
- result = fileReferences([
14
- 'rule BeUnusedInvoiceNumber',
15
- ' file Validations/BeUnusedInvoiceNumber.cs',
16
- 'constraint InvoiceStatusTransition',
17
- ' file Constraints/InvoiceStatusTransitionConstraint.cs',
18
- 'screen InvoiceLineReport',
19
- ' file Screens/InvoiceLineReport.tsx',
20
- 'performer',
21
- ' file Queries/InvoiceSummaryPerformer.cs',
22
- ]);
23
- });
24
-
25
- it('should find every reference', () => {
26
- result.should.have.lengthOf(4);
27
- });
28
-
29
- it('should find them in document order', () => {
30
- result.map((reference) => reference.path).should.deep.equal([
31
- 'Validations/BeUnusedInvoiceNumber.cs',
32
- 'Constraints/InvoiceStatusTransitionConstraint.cs',
33
- 'Screens/InvoiceLineReport.tsx',
34
- 'Queries/InvoiceSummaryPerformer.cs',
35
- ]);
36
- });
37
-
38
- it('should report the line each one sits on', () => {
39
- result.map((reference) => reference.line).should.deep.equal([1, 3, 5, 7]);
40
- });
41
- });
@@ -1,34 +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 { describe, beforeEach, it } from 'vitest';
5
- import { importablePaths } from '../file-imports';
6
-
7
- describe('when listing what a document can import', () => {
8
- let paths: string[];
9
-
10
- beforeEach(() => {
11
- paths = importablePaths('Ordering/Ordering.play', [
12
- 'application.play',
13
- 'Ordering/Ordering.play',
14
- 'Ordering/Orders/PlaceOrder.play',
15
- 'Ordering/notes.md',
16
- 'Shared/Concepts.play',
17
- ]);
18
- });
19
-
20
- it('should name every other play file relative to the document, with globs for the folders on the way', () => {
21
- paths.should.deep.equal([
22
- '**/*.play',
23
- '../**/*.play',
24
- '../*.play',
25
- '../Shared/**/*.play',
26
- '../Shared/*.play',
27
- '../Shared/Concepts.play',
28
- '../application.play',
29
- 'Orders/**/*.play',
30
- 'Orders/*.play',
31
- 'Orders/PlaceOrder.play',
32
- ]);
33
- });
34
- });
@@ -1,59 +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 { describe, expect, it, vi } from 'vitest';
5
- import * as context from '../document-context';
6
- import { destinationHints } from '../production-destinations';
7
- import { scanDocument } from '../symbols';
8
- import { validateLines } from '../validation';
9
-
10
- function document(count: number, malformedDocumentation = false): string[] {
11
- return ['module Projects', ' feature Naming', ...Array.from({ length: count }, (_, index) => [
12
- ` slice StateChange Rename${index}`,
13
- ` command Rename${index}`,
14
- ' projectId Uuid identifier',
15
- ' name String',
16
- ` produces event Renamed${index}`,
17
- ' name String = name',
18
- malformedDocumentation ? ' documentation "details"' : ' // mapping complete',
19
- ]).flat()];
20
- }
21
-
22
- // Count actual work, not elapsed time: indentation visits catch ancestry walks,
23
- // replacements catch whole-document normalization per event, and filter input
24
- // sizes catch rebuilding/scanning the event catalog for every declaration.
25
- function work(action: () => void): number {
26
- const indentation = vi.spyOn(context, 'indentOf');
27
- const replacements = vi.spyOn(String.prototype, 'replace');
28
- const trims = vi.spyOn(String.prototype, 'trim');
29
- const filters = vi.spyOn(Array.prototype, 'filter');
30
- try {
31
- action();
32
- return indentation.mock.calls.length + replacements.mock.calls.length + trims.mock.calls.length +
33
- filters.mock.contexts.reduce<number>((sum, receiver) => sum + (receiver as unknown[]).length, 0);
34
- } finally {
35
- indentation.mockRestore();
36
- replacements.mockRestore();
37
- trims.mockRestore();
38
- filters.mockRestore();
39
- }
40
- }
41
-
42
- describe('when analyzing many inline events', () => {
43
- it.each(['validation', 'destination hints', 'malformed documentation'])('should keep %s work linear', path => {
44
- const measure = (count: number) => {
45
- const lines = document(count, path === 'malformed documentation');
46
- const application = scanDocument(Array.from({ length: count }, (_, index) => `import Other.Imported${index}`));
47
- let results = 0;
48
- const units = work(() => {
49
- results = path === 'destination hints' ? destinationHints(lines).length : validateLines(lines, { application }).length;
50
- });
51
- expect(results).toBe(path === 'validation' ? 0 : count);
52
- return units;
53
- };
54
- const small = measure(100);
55
- const large = measure(200);
56
- expect(small).toBeGreaterThan(0);
57
- expect(large).toBeLessThan(small * 2.2);
58
- });
59
- });
@@ -1,149 +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 { describe, it, expect } from 'vitest';
5
- import { planCompletions } from '../completion-planner';
6
- import { hoverContent } from '../hover-content';
7
- import { destinationHints } from '../production-destinations';
8
- import { scanDocument } from '../symbols';
9
- import { validateLines } from '../validation';
10
-
11
- const source = [
12
- 'command Rename', ' projectId Uuid identifier', ' name String', ' produces event Renamed',
13
- ' description "A new name"', ' documentation', ' ```markdown', ' event NotADeclaration', ' ```',
14
- ' @tag String = name', ' name String = name',
15
- ];
16
-
17
- describe('when authoring inline events', () => {
18
- it('should index the event and its typed properties, not its prose', () => {
19
- const symbols = scanDocument(source);
20
- expect(symbols.events.map(event => event.name)).toEqual(['Renamed']);
21
- expect(symbols.events[0].properties.map(property => property.name)).toEqual(['tag', 'name']);
22
- expect(symbols.commands[0].properties.map(property => property.name)).toEqual(['projectId', 'name']);
23
- });
24
- it('should validate its metadata without false positives', () => expect(validateLines(source)).toEqual([]));
25
- it.each([
26
- [' documentation "details"'],
27
- [' documentation'],
28
- [' documentation', ' ```text', ' Details', ' ```'],
29
- [' documentation', ' ```markdown', ' Details', ' ```'],
30
- [' documentation', ' ```markdown', ' ', ' ```'],
31
- [' documentation', ' ```markdown', ' Details'],
32
- ])('should reject malformed documentation without searching later declarations for its fence', (...body) => {
33
- const lines = ['command Rename', ' produces event Renamed', ...body];
34
- expect(validateLines(lines).filter(issue => issue.code === 'PLAY0477').map(issue => issue.line)).toEqual([2]);
35
- });
36
- it('should distinguish adjacent complete documentation blocks from malformed directives', () => {
37
- const lines = ['event First', ' documentation "details"', 'event Second', ' documentation',
38
- ' ```markdown', ' Details', ' ```', 'event Third', ' documentation',
39
- ' ```markdown', ' ', ' ```', 'event Fourth', ' documentation', '',
40
- ' ```markdown', ' More details', ' ```'];
41
- expect(validateLines(lines).filter(issue => issue.code === 'PLAY0477').map(issue => issue.line)).toEqual([1, 8]);
42
- });
43
- it('should offer event metadata and typed mappings inside the body', () => {
44
- const plan = planCompletions([...source, ' '], source.length, ' ');
45
- expect(plan.kind).toBe('entries');
46
- if (plan.kind === 'entries') expect(plan.entries.map(entry => entry.label)).toEqual(expect.arrayContaining(['description', 'documentation', 'id', 'tag', 'for', 'property mapping']));
47
- });
48
- it('should show the implicit command identifier', () => expect(destinationHints(source).map(hint => hint.label)).toEqual(['for projectId']));
49
- it('should suppress hints for uncertain mixed destinations', () => expect(destinationHints([...source, ' produces Another', ' for otherId'])).toEqual([]));
50
- it('should suppress hints for incomplete and repeated explicit destinations', () => {
51
- expect(destinationHints([...source, ' for'])).toEqual([]);
52
- expect(destinationHints([...source, ' for projectId', ' for projectId'])).toEqual([]);
53
- });
54
- it('should not hint a header that has no event name', () => expect(destinationHints(['command Rename', ' produces'])).toEqual([]));
55
- it('should explain the new declaration form', () => expect(hoverContent(source, 3, 'produces', 3, 11)).toContain('produces event'));
56
- it('should preserve information severity for redundant pins', () => expect(validateLines([...source, ' id "Renamed"']).find(issue => issue.code === 'PLAY0471')?.severity).toBe('information'));
57
- it.each(['namespace', 'sequence', 'correlation', 'causation', 'causedBy', 'occurred'])('should reject %s as system metadata', keyword => {
58
- expect(validateLines([...source, ` ${keyword} = name`]).map(issue => issue.code)).toContain('PLAY0476');
59
- });
60
- it('should warn about identifier duplication', () => expect(validateLines([...source, ' projectId Uuid = projectId']).find(issue => issue.code === 'PLAY0469')?.severity).toBe('warning'));
61
- it('should reject an inline event in a reaction', () => expect(validateLines(['reaction React', ' every 1 day', ' produces event Done']).map(issue => issue.code)).toContain('PLAY0474'));
62
- it('should require explicit destinations for mixed inline and plain omissions', () => {
63
- const lines = [...source, ' produces Legacy // retained allocation'];
64
- expect(validateLines(lines).filter(issue => issue.code === 'PLAY0470').map(issue => issue.message)).toContain("Production 'Legacy' must state for explicitly when destinations differ.");
65
- expect(destinationHints(lines)).toEqual([]);
66
- });
67
- it('should suppress legacy allocation hints when a sibling can promote a destination', () => {
68
- expect(destinationHints(['command Rename', ' projectId Uuid identifier', ' produces First', ' for projectId', ' produces Legacy'])).toEqual([]);
69
- });
70
- it.each([
71
- [' produces First', ' name = name', ' for projectId', ' produces Legacy'],
72
- [' produces First', ' name = name', ' for projectId', ' produces Legacy'],
73
- [' produces First', ' name = name', ' for projectId', ' produces Legacy'],
74
- [' produces when name != ""', ' First', ' name = name', ' for projectId', ' produces Legacy'],
75
- ])('should not hint allocation when a differently indented destination is promoted', (...productions) => {
76
- const lines = ['command Rename', ' projectId Uuid identifier', ' name String', ...productions];
77
- expect(scanDocument(lines).commands[0].produces?.[0].target).toBe('projectId');
78
- expect(destinationHints(lines)).toEqual([]);
79
- });
80
- it('should see deeper destinations after fenced event documentation', () => {
81
- const lines = ['command Rename', ' projectId Uuid identifier', ' produces event Renamed',
82
- ' documentation', ' ```markdown', ' Details', ' ```', ' for otherId', ' produces Legacy'];
83
- expect(scanDocument(lines).commands[0].produces?.[0].target).toBe('otherId');
84
- expect(destinationHints(lines)).toEqual([]);
85
- });
86
- it('should suppress hints when a sibling production cannot be classified', () => {
87
- expect(destinationHints(['command Rename', ' projectId Uuid identifier', ' produces First', ' produces event'])).toEqual([]);
88
- expect(destinationHints(['command Rename', ' projectId Uuid identifier', ' produces event'])).toEqual([]);
89
- expect(destinationHints(['command Rename', ' produces First', ' produces when'])).toEqual([]);
90
- });
91
- it('should place a destination hint before a trailing comment', () => {
92
- const lines = ['command Rename', ' projectId Uuid identifier', ' produces event Renamed // note'];
93
- expect(destinationHints(lines)).toEqual([{ line: 2, column: ' produces event Renamed'.length + 1, label: 'for projectId' }]);
94
- });
95
- it('should report duplicate typed properties at the repeated mapping', () => {
96
- const lines = [...source, ' name Uuid = otherId // duplicate'];
97
- expect(validateLines(lines).filter(issue => issue.code === 'PLAY0168').map(issue => ({ line: issue.line, severity: issue.severity })))
98
- .toEqual([{ line: source.length, severity: 'error' }]);
99
- });
100
- it('should treat escaped and unescaped inline property names as the same name', () => {
101
- expect(validateLines([...source, ' @name String = name']).filter(issue => issue.code === 'PLAY0168').map(issue => issue.line)).toEqual([source.length]);
102
- });
103
- it('should detect collisions with commented imports in the document', () => {
104
- expect(validateLines(['import Other.Renamed // note', ...source]).filter(issue => issue.code === 'PLAY0473').map(issue => issue.line)).toEqual([4]);
105
- });
106
- it('should detect collisions with commented imports from other files', () => {
107
- const application = scanDocument(['import Other.Renamed // note']);
108
- expect(validateLines(source, { application }).filter(issue => issue.code === 'PLAY0473').map(issue => issue.line)).toEqual([3]);
109
- });
110
- it('should not scan imports inside event documentation', () => {
111
- const lines = source.map(line => line === ' event NotADeclaration' ? ' import Other.Renamed // prose' : line);
112
- expect(scanDocument(lines).imports).toEqual([]);
113
- expect(validateLines(lines)).toEqual([]);
114
- });
115
- it('should hint allocation when all plain productions omit destinations', () => {
116
- expect(destinationHints(['command Rename', ' produces First', ' produces Legacy']).map(hint => hint.label)).toEqual(['for <new event source>', 'for <new event source>']);
117
- });
118
- it('should analyze commented headers, destinations, mappings and metadata like the compiler', () => {
119
- const lines = source.map(line => line === ' documentation' ? `${line} // details` : line);
120
- lines.push(' projectId Uuid = projectId // note', ' produces Other // sibling', ' for projectId // same identifier');
121
- expect(validateLines(lines).filter(issue => ['PLAY0470', 'PLAY0477'].includes(issue.code ?? ''))).toEqual([]);
122
- expect(validateLines(lines).find(issue => issue.code === 'PLAY0469')?.severity).toBe('warning');
123
- expect(destinationHints(lines).map(hint => hint.label)).toEqual(['for projectId']);
124
- });
125
- it('should suppress hints when a commented sibling targets another source', () => {
126
- const lines = [...source, ' produces Other // sibling', ' for otherId // other source'];
127
- expect(validateLines(lines).some(issue => issue.code === 'PLAY0470')).toBe(true);
128
- expect(destinationHints(lines)).toEqual([]);
129
- });
130
- it('should preserve comment markers inside strings and templates and ignore fences', () => {
131
- const lines = [...source, ' url String = "https://example.org" // url', ' template String = `https://{name}` // template'];
132
- expect(scanDocument(lines).commands[0].produces?.[0].mappings.map(mapping => mapping.source)).toContain('"https://example.org"');
133
- expect(scanDocument(lines).commands[0].produces?.[0].mappings.map(mapping => mapping.source)).toContain('`https://{name}`');
134
- expect(destinationHints(lines).map(hint => hint.label)).toEqual(['for projectId']);
135
- const fenced = ['command Rename', ' documentation', ' ```markdown', ' produces event Fake', ' for otherId', ' ```'];
136
- expect(destinationHints(fenced)).toEqual([]);
137
- expect(validateLines(fenced).filter(issue => issue.code?.startsWith('PLAY047'))).toEqual([]);
138
- });
139
- it('should show metadata hover only on event directives', () => {
140
- expect(hoverContent(['event Done', ' id "Old"'], 1, 'id', 3, 5)).toContain('rename');
141
- expect(hoverContent(['event Done', ' documentation'], 1, 'documentation', 3, 16)).not.toBeNull();
142
- for (const line of [' id String', ' key id', ' name String = id', ' id String = name']) {
143
- const start = line.indexOf('id') + 1;
144
- expect(hoverContent(['event Done', line], 1, 'id', start, start + 2)).toBeNull();
145
- }
146
- expect(hoverContent(['command Done', ' documentation'], 1, 'documentation', 3, 16)).toBeNull();
147
- });
148
- it('should retain standalone property-shaped id and documentation names', () => expect(scanDocument(['event Done', ' id String', ' documentation String']).events[0].properties.map(property => property.name)).toEqual(['id', 'documentation']));
149
- });
@@ -1,49 +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 { describe, expect, it, vi } from 'vitest';
5
- import type { editor, languages, Position } from 'monaco-editor';
6
- import { createCompletionProvider } from '../completions';
7
- import { Monaco } from '../language';
8
-
9
- const monaco = {
10
- Range: class {},
11
- languages: { CompletionItemKind: { Event: 1 }, CompletionItemInsertTextRule: { InsertAsSnippet: 1 } },
12
- } as unknown as Monaco;
13
-
14
- function complete(count: number, inline: boolean): { work: number; suggestions: languages.CompletionItem[] } {
15
- const lines = [...Array.from({ length: count }, (_, index) => inline ? `command Record${index}\n produces event Recorded${index}` : `event Recorded${index}`).join('\n').split('\n'),
16
- 'constraint Unique', ' on '];
17
- const model = {
18
- getLinesContent: () => lines,
19
- getWordUntilPosition: () => ({ word: '', startColumn: 6, endColumn: 6 }),
20
- } as unknown as editor.ITextModel;
21
- const searches = vi.spyOn(Array.prototype, 'some');
22
- const filters = vi.spyOn(Array.prototype, 'filter');
23
- const membership = vi.spyOn(Set.prototype, 'has');
24
- try {
25
- const result = createCompletionProvider(monaco).provideCompletionItems(model, { lineNumber: lines.length, column: 6 } as Position, {} as languages.CompletionContext, {} as never) as languages.CompletionList;
26
- return {
27
- suggestions: result.suggestions,
28
- work: [...searches.mock.contexts, ...filters.mock.contexts].reduce<number>((sum, receiver) => sum + (receiver as unknown[]).length, 0) + membership.mock.calls.length,
29
- };
30
- } finally {
31
- searches.mockRestore();
32
- filters.mockRestore();
33
- membership.mockRestore();
34
- }
35
- }
36
-
37
- describe('when completing many event names in Monaco', () => {
38
- it.each([false, true])('should keep completion work linear for inline declarations: %s', inline => {
39
- const small = complete(500, inline);
40
- const medium = complete(1000, inline);
41
- const large = complete(2000, inline);
42
- expect(small.work).toBeGreaterThan(0);
43
- expect(medium.work).toBeLessThan(small.work * 2.2);
44
- expect(large.work).toBeLessThan(medium.work * 2.2);
45
- expect(large.suggestions).toHaveLength(2000);
46
- expect(large.suggestions.map(item => item.label)).toEqual(Array.from({ length: 2000 }, (_, index) => `Recorded${index}`));
47
- expect(large.suggestions.every(item => item.detail === (inline ? 'inline event' : 'event'))).toBe(true);
48
- });
49
- });
@@ -1,79 +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 { describe, expect, it } from 'vitest';
5
- import { destinationHints } from '../production-destinations';
6
- import { scanDocument } from '../symbols';
7
- import { validateLines } from '../validation';
8
-
9
- describe('when indexing command production ownership', () => {
10
- it('should keep the documented multiline authorization out of command properties', () => {
11
- const lines = ['policy IsAccountant', ' require role "Accountant"', 'policy IsCustomerSelf', ' require authenticated',
12
- 'command Rename', ' projectId Uuid identifier',
13
- ' authorize IsAccountant', ' or IsCustomerSelf', ' produces event Renamed'];
14
- const command = scanDocument(lines).commands[0];
15
- expect(command.properties.map(property => property.name)).toEqual(['projectId']);
16
- expect(command.produces?.map(production => production.name)).toEqual(['Renamed']);
17
- expect(validateLines(lines)).toEqual([]);
18
- expect(destinationHints(lines).map(hint => hint.label)).toEqual(['for projectId']);
19
- });
20
-
21
- it.each(['or IsCustomerSelf', 'and IsCustomerSelf', 'IsCustomerSelf', '(IsCustomerSelf)', 'or IsCüstomerSelf', 'produces Ghost'])('should consume %s as an authorization continuation', continuation => {
22
- const lines = ['command Rename', ' authorize IsAccountant', ` ${continuation}`, ' produces Legacy'];
23
- const command = scanDocument(lines).commands[0];
24
- expect(command.properties).toEqual([]);
25
- expect(command.productionHeaders).toEqual([3]);
26
- expect(destinationHints(lines).map(hint => hint.line)).toEqual([3]);
27
- });
28
-
29
- it('should end authorization ownership at the first nonmatching line, not merely a dedent', () => {
30
- const lines = ['command Rename', ' projectId Uuid identifier', ' authorize IsAccountant',
31
- ' or IsCustomerSelf // continuation', ' names String[]', ' produces event Renamed'];
32
- const command = scanDocument(lines).commands[0];
33
- expect(command.properties.map(property => property.name)).toEqual(['projectId', 'names']);
34
- expect(command.productionHeaders).toEqual([5]);
35
- expect(destinationHints(lines).map(hint => hint.label)).toEqual(['for projectId']);
36
- });
37
-
38
- it.each([
39
- [[' description "Rename the project"']],
40
- [[' description', ' ```text', ' produces Ghost', ' ```']],
41
- ])('should leave deeper syntax after a description in its command', description => {
42
- const lines = ['command Rename', ' projectId Uuid identifier', ...description, ' produces event Renamed'];
43
- const command = scanDocument(lines).commands[0];
44
- expect(command.properties.map(property => property.name)).toEqual(['projectId']);
45
- expect(command.produces?.map(production => production.name)).toEqual(['Renamed']);
46
- expect(destinationHints(lines).map(hint => hint.label)).toEqual(['for projectId']);
47
- });
48
-
49
- it.each(['validate', 'handler', 'concurrency', 'name'])('should keep a deeper production after the %s property in its command', name => {
50
- const lines = ['command Rename', ' projectId Uuid identifier', ` ${name} String`,
51
- ' produces event Renamed', ' value String = "renamed"'];
52
- expect(scanDocument(lines).commands[0].produces?.map(production => production.name)).toEqual(['Renamed']);
53
- expect(validateLines(lines)).toEqual([]);
54
- expect(destinationHints(lines).map(hint => hint.label)).toEqual(['for projectId']);
55
- });
56
-
57
- it.each(['validate', 'handler', 'concurrency'])('should not hint allocation after a deeper destination beneath the %s property', name => {
58
- const lines = ['command Rename', ' projectId Uuid identifier', ` ${name} String // a property, not a block`,
59
- ' produces First', ' for projectId', ' produces Legacy'];
60
- expect(scanDocument(lines).commands[0].produces?.map(production => [production.name, production.target]))
61
- .toEqual([['First', 'projectId'], ['Legacy', undefined]]);
62
- expect(destinationHints(lines)).toEqual([]);
63
- expect(validateLines(lines).filter(issue => issue.code === 'PLAY0478').map(issue => issue.line)).toEqual([5]);
64
- });
65
-
66
- it.each(['validate', 'handler', 'concurrency'])('should suppress hints for an unclassifiable production beneath the %s property', name => {
67
- expect(destinationHints(['command Rename', ' projectId Uuid identifier', ` ${name} String`,
68
- ' produces event', ' produces Legacy'])).toEqual([]);
69
- });
70
-
71
- it.each(['validate', 'validate csharp', 'handler', 'concurrency'])('should keep the %s block opaque to command ownership', directive => {
72
- const lines = ['command Rename', ` ${directive}`, ' ghost Uuid identifier', ' produces First', ' for ghost', ' produces Legacy'];
73
- const command = scanDocument(lines).commands[0];
74
- expect(command.properties).toEqual([]);
75
- expect(command.produces?.map(production => production.name)).toEqual(['Legacy']);
76
- expect(destinationHints(lines).map(hint => hint.line)).toEqual([5]);
77
- expect(validateLines(lines).filter(issue => issue.code === 'PLAY0478')).toEqual([]);
78
- });
79
- });
@@ -1,26 +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 { describe, it } from 'vitest';
5
- import { completionEntriesFor, planCompletions } from '../completion-planner';
6
- import { hoverContent } from '../hover-content';
7
-
8
- describe('when completing a file import', () => {
9
- it('should complete a path inside the quotes, replacing what is typed', () => {
10
- planCompletions(['module Ordering', ' import "Ord'], 1, ' import "Ord').should.deep.equal({ kind: 'playFiles', replaceLength: 3 });
11
- });
12
-
13
- it('should offer nothing after a quote outside an import', () => {
14
- planCompletions(['module Ordering', ' description "'], 1, ' description "').should.deep.equal({ kind: 'none' });
15
- });
16
-
17
- for (const [chain, scope] of [[[], 'top level'], [['module'], 'module'], [['feature', 'module'], 'feature']] as const) {
18
- it(`should offer a file import at the ${scope}`, () => {
19
- completionEntriesFor([...chain]).some((entry) => entry.label === 'import "…"').should.be.true;
20
- });
21
- }
22
-
23
- it('should document both kinds of import on hover', () => {
24
- hoverContent(['import "**/*.play"'], 0, 'import', 1, 7)!.should.contain('places the imported files there');
25
- });
26
- });
@@ -1,24 +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 { beforeEach, describe, it } from 'vitest';
5
- import { completionEntriesFor, CompletionPlan, planCompletions } from '../completion-planner';
6
-
7
- describe('when completing a specification query', () => {
8
- let plan: CompletionPlan;
9
-
10
- beforeEach(() => {
11
- plan = planCompletions([
12
- 'specification LookingUpAProject',
13
- ' then query Pro',
14
- ], 1, ' then query Pro');
15
- });
16
-
17
- it('should offer the queries declared by the document', () => {
18
- plan.kind.should.equal('queries');
19
- });
20
-
21
- it('should offer a query assertion in a specification', () => {
22
- completionEntriesFor(['specification']).some(entry => entry.label === 'then query').should.be.true;
23
- });
24
- });
@@ -1,42 +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 { describe, it } from 'vitest';
5
- import { completionEntriesFor, planCompletions } from '../completion-planner';
6
- import { hoverContent } from '../hover-content';
7
-
8
- const specification = ['slice StateView Hours', ' specification AtOpening'];
9
- const labelsAfter = (textBefore: string): string[] => {
10
- const plan = planCompletions([...specification, textBefore], 2, textBefore);
11
- return plan.kind === 'entries' ? plan.entries.map((entry) => entry.label) : [plan.kind];
12
- };
13
-
14
- describe('when completing a specification step', () => {
15
- it('should offer what can follow given', () => {
16
- labelsAfter(' given ').should.include.members(['clock', 'capture']);
17
- });
18
-
19
- it('should offer what can follow when', () => {
20
- labelsAfter(' when ').should.include.members(['clock', 'trigger', 'capture', 'query']);
21
- });
22
-
23
- it('should offer what can follow then', () => {
24
- labelsAfter(' then res').should.include.members(['result', 'result exactly', 'no result']);
25
- });
26
-
27
- it('should offer the queries after when query', () => {
28
- labelsAfter(' when query Opening').should.deep.equal(['queries']);
29
- });
30
-
31
- it('should offer every step on an empty line', () => {
32
- completionEntriesFor(['specification']).map((entry) => entry.label).should.include.members(['given clock', 'when trigger', 'when capture', 'when query', 'then result', 'then no result']);
33
- });
34
-
35
- it('should document a step word in a specification', () => {
36
- hoverContent([...specification, ' when trigger NightlySync'], 2, 'trigger', 10, 17)!.should.contain('application trigger fires');
37
- });
38
-
39
- it('should document a step word not in a specification as the keyword', () => {
40
- hoverContent(['trigger NightlySync'], 0, 'trigger', 1, 8)!.should.not.contain('application trigger fires');
41
- });
42
- });
@@ -1,14 +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 { describe, it } from 'vitest';
5
- import { completionEntriesFor } from '../completion-planner';
6
-
7
- // VS Code uses the same @cratis/screenplay-language completion entries as Monaco.
8
- describe('when completing a keyed absence assertion', () => {
9
- it('should offer the entire keyed form without child mappings', () => {
10
- const entry = completionEntriesFor(['specification']).find(item => item.label === 'then no readmodel');
11
- (entry !== undefined).should.be.true;
12
- entry!.insertText.should.equal('then no readmodel ${1:ReadModelType} for ${2:key}');
13
- });
14
- });