@cratis/screenplay-language 4.46.4 → 4.48.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 (75) hide show
  1. package/completion-items.ts +37 -1
  2. package/completion-planner.ts +15 -0
  3. package/completions.ts +26 -3
  4. package/diagnostic-codes.ts +1 -0
  5. package/dist/completion-items.d.ts +1 -0
  6. package/dist/completion-items.d.ts.map +1 -1
  7. package/dist/completion-items.js +36 -1
  8. package/dist/completion-items.js.map +1 -1
  9. package/dist/completion-planner.d.ts +3 -0
  10. package/dist/completion-planner.d.ts.map +1 -1
  11. package/dist/completion-planner.js +15 -0
  12. package/dist/completion-planner.js.map +1 -1
  13. package/dist/completions.d.ts +5 -2
  14. package/dist/completions.d.ts.map +1 -1
  15. package/dist/completions.js +13 -2
  16. package/dist/completions.js.map +1 -1
  17. package/dist/diagnostic-codes.d.ts +1 -0
  18. package/dist/diagnostic-codes.d.ts.map +1 -1
  19. package/dist/diagnostic-codes.js +1 -0
  20. package/dist/diagnostic-codes.js.map +1 -1
  21. package/dist/file-imports.d.ts +11 -0
  22. package/dist/file-imports.d.ts.map +1 -0
  23. package/dist/file-imports.js +58 -0
  24. package/dist/file-imports.js.map +1 -0
  25. package/dist/hover-content.d.ts.map +1 -1
  26. package/dist/hover-content.js +5 -1
  27. package/dist/hover-content.js.map +1 -1
  28. package/dist/index.d.ts +10 -5
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +6 -5
  31. package/dist/index.js.map +1 -1
  32. package/dist/keyword-docs.d.ts +1 -0
  33. package/dist/keyword-docs.d.ts.map +1 -1
  34. package/dist/keyword-docs.js +13 -2
  35. package/dist/keyword-docs.js.map +1 -1
  36. package/dist/language.d.ts.map +1 -1
  37. package/dist/language.js +2 -0
  38. package/dist/language.js.map +1 -1
  39. package/dist/symbols.d.ts +1 -0
  40. package/dist/symbols.d.ts.map +1 -1
  41. package/dist/symbols.js +15 -0
  42. package/dist/symbols.js.map +1 -1
  43. package/dist/themes/screenplay-dark.d.ts.map +1 -1
  44. package/dist/themes/screenplay-dark.js +1 -0
  45. package/dist/themes/screenplay-dark.js.map +1 -1
  46. package/dist/themes/screenplay-light.d.ts.map +1 -1
  47. package/dist/themes/screenplay-light.js +1 -0
  48. package/dist/themes/screenplay-light.js.map +1 -1
  49. package/dist/tokens.d.ts.map +1 -1
  50. package/dist/tokens.js +6 -0
  51. package/dist/tokens.js.map +1 -1
  52. package/dist/validation.d.ts +5 -1
  53. package/dist/validation.d.ts.map +1 -1
  54. package/dist/validation.js +29 -8
  55. package/dist/validation.js.map +1 -1
  56. package/file-imports.ts +70 -0
  57. package/for_fileImports/when_finding_file_imports.ts +38 -0
  58. package/for_importablePaths/when_listing_what_a_document_can_import.ts +34 -0
  59. package/for_planCompletions/when_completing_a_file_import.ts +26 -0
  60. package/for_planCompletions/when_completing_a_specification_step.ts +42 -0
  61. package/for_tokens/when_highlighting_a_file_import.ts +18 -0
  62. package/for_tokens/when_highlighting_specification_steps.ts +28 -0
  63. package/for_validateLines/when_validating_a_document/with_a_problem_of_every_coded_kind.ts +1 -0
  64. package/for_validateLines/when_validating_a_file_placed_by_an_import/with_names_declared_in_other_files.ts +43 -0
  65. package/for_validateLines/when_validating_file_imports/at_every_level.ts +36 -0
  66. package/hover-content.ts +6 -1
  67. package/index.ts +13 -7
  68. package/keyword-docs.ts +14 -2
  69. package/language.ts +2 -0
  70. package/package.json +1 -1
  71. package/symbols.ts +16 -0
  72. package/themes/screenplay-dark.ts +1 -0
  73. package/themes/screenplay-light.ts +1 -0
  74. package/tokens.ts +6 -0
  75. package/validation.ts +43 -7
package/validation.ts CHANGED
@@ -3,15 +3,17 @@
3
3
 
4
4
  import { causedByProperties, contextRoots, identityProperties, primitiveTypes, sliceTypes } from './language';
5
5
  import { DiagnosticCode, diagnosticCodes } from './diagnostic-codes';
6
- import { fenceMap, indentOf } from './document-context';
6
+ import { enclosingChain, fenceMap, indentOf } from './document-context';
7
7
  import { resolveEventContextPath } from './event-context';
8
8
  import {
9
9
  DocumentSymbols,
10
10
  PropertySymbol,
11
11
  knownEventNames,
12
12
  knownTypeNames,
13
+ mergeSymbols,
13
14
  scanDocument,
14
15
  } from './symbols';
16
+ import { fileImportOn, isFileImportLine } from './file-imports';
15
17
 
16
18
  export type ValidationSeverity = 'error' | 'warning';
17
19
 
@@ -27,6 +29,14 @@ export interface ValidationIssue {
27
29
  code?: DiagnosticCode;
28
30
  }
29
31
 
32
+ // What a document is validated against beyond itself.
33
+ export interface ValidationContext {
34
+ // What the rest of the application declares - the other .play files of a folder, or the files an import
35
+ // brings in - so a name declared in another file is not reported unknown. Merge the scanned symbols of
36
+ // those files with mergeSymbols. The document's own declarations are always known.
37
+ application?: DocumentSymbols;
38
+ }
39
+
30
40
  function issue(
31
41
  severity: ValidationSeverity,
32
42
  line: number,
@@ -52,9 +62,9 @@ function tokenIssue(
52
62
 
53
63
  // Reports every property whose type reference names nothing the document declares, and
54
64
  // every command that marks more than one property as its identifier.
55
- function validateDeclarations(lines: string[], symbols: DocumentSymbols): ValidationIssue[] {
65
+ function validateDeclarations(lines: string[], symbols: DocumentSymbols, application: DocumentSymbols): ValidationIssue[] {
56
66
  const issues: ValidationIssue[] = [];
57
- const types = new Set(knownTypeNames(symbols, primitiveTypes));
67
+ const types = new Set([...knownTypeNames(symbols, primitiveTypes), ...knownTypeNames(application, [])]);
58
68
 
59
69
  const checkProperties = (properties: PropertySymbol[], owner: string) => {
60
70
  for (const property of properties.filter(
@@ -118,12 +128,17 @@ function validateDeclarations(lines: string[], symbols: DocumentSymbols): Valida
118
128
 
119
129
  // Validates a Screenplay document without any editor dependency — both the Monaco
120
130
  // service and the VSCode extension adapt these issues to their marker/diagnostic APIs.
121
- export function validateLines(lines: string[]): ValidationIssue[] {
131
+ //
132
+ // The checks read the document line by line, never as a whole document's top level, so a file an import
133
+ // places in a module or feature - holding slices or features at its top level - validates as it is written.
134
+ // Where it is placed, and whether its imports resolve, is the compiler's to say.
135
+ export function validateLines(lines: string[], context: ValidationContext = {}): ValidationIssue[] {
122
136
  const fences = fenceMap(lines);
123
137
  const symbols = scanDocument(lines);
124
- const events = new Set(knownEventNames(symbols));
125
- const policies = new Set(symbols.policies.map((policy) => policy.name));
126
- const issues: ValidationIssue[] = validateDeclarations(lines, symbols);
138
+ const application = context.application ?? mergeSymbols();
139
+ const events = new Set([...knownEventNames(symbols), ...knownEventNames(application)]);
140
+ const policies = new Set([...symbols.policies, ...application.policies].map((policy) => policy.name));
141
+ const issues: ValidationIssue[] = validateDeclarations(lines, symbols, application);
127
142
 
128
143
  const checkEvent = (line: number, text: string, name: string) => {
129
144
  if (!events.has(name)) {
@@ -187,6 +202,9 @@ export function validateLines(lines: string[]): ValidationIssue[] {
187
202
  authorizeIndent = -1;
188
203
  }
189
204
 
205
+ const fileImportIssue = validateImport(lines, fences, index);
206
+ if (fileImportIssue) issues.push(fileImportIssue);
207
+
190
208
  const slice = trimmed.match(/^slice\s+(\w+)/);
191
209
  if (slice && !sliceTypes.includes(slice[1])) {
192
210
  issues.push(
@@ -328,6 +346,24 @@ export function validateLines(lines: string[]): ValidationIssue[] {
328
346
  return issues;
329
347
  }
330
348
 
349
+ // Inside a module or feature an import names files, and a quoted import anywhere has the compiler's shape.
350
+ function validateImport(lines: string[], fences: boolean[], index: number): ValidationIssue | undefined {
351
+ const line = lines[index];
352
+ const trimmed = line.trim().replace(/\s*\/\/.*$/, '');
353
+ if (!/^import\b/.test(trimmed) || fileImportOn(line, index)) return undefined;
354
+ const indent = indentOf(line);
355
+ const inBody = indent > 0 && ['module', 'feature'].includes(enclosingChain(lines, fences, index, indent)[0]);
356
+ if (!inBody && !isFileImportLine(line)) return undefined;
357
+ return issue(
358
+ 'error',
359
+ index,
360
+ indent + 1,
361
+ trimmed.length,
362
+ `Invalid import '${trimmed}' — inside a module or feature, import names files: 'import "<path or glob>"'.`,
363
+ diagnosticCodes.invalidFileImport,
364
+ );
365
+ }
366
+
331
367
  // A mapping target: the first word of a mapping line, after the keyword that opens it if there is one.
332
368
  const mappingTarget = /^(?:(?:increment|decrement|count|clear|add|subtract|set)\s+)?@?([$\w.]+)(?=\s|=|$)/;
333
369