@cratis/screenplay-language 4.1.0 → 4.2.1

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 (41) hide show
  1. package/README.md +1 -1
  2. package/diagnostic-codes.ts +27 -0
  3. package/diagnostics.ts +1 -0
  4. package/dist/diagnostic-codes.d.ts +16 -0
  5. package/dist/diagnostic-codes.d.ts.map +1 -0
  6. package/dist/diagnostic-codes.js +25 -0
  7. package/dist/diagnostic-codes.js.map +1 -0
  8. package/dist/diagnostics.d.ts.map +1 -1
  9. package/dist/diagnostics.js +1 -0
  10. package/dist/diagnostics.js.map +1 -1
  11. package/dist/file-references.d.ts +11 -0
  12. package/dist/file-references.d.ts.map +1 -0
  13. package/dist/file-references.js +70 -0
  14. package/dist/file-references.js.map +1 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/symbols.d.ts.map +1 -1
  20. package/dist/symbols.js +5 -1
  21. package/dist/symbols.js.map +1 -1
  22. package/dist/validation.d.ts +2 -0
  23. package/dist/validation.d.ts.map +1 -1
  24. package/dist/validation.js +18 -17
  25. package/dist/validation.js.map +1 -1
  26. package/file-references.ts +87 -0
  27. package/for_fileReferences/when_recognizing_an_absolute_path/with_paths_of_every_shape.ts +26 -0
  28. package/for_fileReferences/when_scanning_a_document/inside_a_code_fence.ts +26 -0
  29. package/for_fileReferences/when_scanning_a_document/with_a_bare_file_keyword.ts +19 -0
  30. package/for_fileReferences/when_scanning_a_document/with_a_file_directive.ts +30 -0
  31. package/for_fileReferences/when_scanning_a_document/with_a_property_named_file.ts +25 -0
  32. package/for_fileReferences/when_scanning_a_document/with_a_trailing_comment.ts +25 -0
  33. package/for_fileReferences/when_scanning_a_document/with_directives_on_several_constructs.ts +41 -0
  34. package/for_scanDocument/when_scanning_a_query/with_a_dotted_return_type.ts +34 -0
  35. package/for_validateLines/when_validating_a_document/with_a_problem_of_every_coded_kind.ts +52 -0
  36. package/for_validateLines/when_validating_a_document/with_an_unknown_policy.ts +33 -0
  37. package/index.ts +9 -0
  38. package/package.json +8 -4
  39. package/symbols.ts +5 -1
  40. package/validation.ts +98 -12
  41. package/vitest.setup.ts +5 -0
package/validation.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { causedByProperties, contextRoots, identityProperties, primitiveTypes, sliceTypes } from './language';
5
+ import { DiagnosticCode, diagnosticCodes } from './diagnostic-codes';
5
6
  import { fenceMap, indentOf } from './document-context';
6
7
  import {
7
8
  DocumentSymbols,
@@ -19,6 +20,10 @@ export interface ValidationIssue {
19
20
  endColumn: number;
20
21
  message: string;
21
22
  severity: ValidationSeverity;
23
+
24
+ // The compiler code for this condition, absent on the structural checks the editor makes and the
25
+ // compiler does not. See ./diagnostic-codes.ts.
26
+ code?: DiagnosticCode;
22
27
  }
23
28
 
24
29
  function issue(
@@ -27,8 +32,9 @@ function issue(
27
32
  startColumn: number,
28
33
  length: number,
29
34
  message: string,
35
+ code: DiagnosticCode,
30
36
  ): ValidationIssue {
31
- return { severity, message, line, startColumn, endColumn: startColumn + length };
37
+ return { severity, message, line, startColumn, endColumn: startColumn + length, code };
32
38
  }
33
39
 
34
40
  function tokenIssue(
@@ -37,9 +43,10 @@ function tokenIssue(
37
43
  text: string,
38
44
  token: string,
39
45
  message: string,
46
+ code: DiagnosticCode,
40
47
  ): ValidationIssue {
41
48
  const column = text.indexOf(token) + 1;
42
- return issue(severity, line, column, token.length, message);
49
+ return issue(severity, line, column, token.length, message, code);
43
50
  }
44
51
 
45
52
  // Reports every property whose type reference names nothing the document declares, and
@@ -60,6 +67,7 @@ function validateDeclarations(lines: string[], symbols: DocumentSymbols): Valida
60
67
  lines[property.line],
61
68
  property.type,
62
69
  `Unknown type '${bare}' on '${property.name}' of ${owner} — declare it with 'concept ${bare} : <Primitive>' or 'type ${bare}'.`,
70
+ diagnosticCodes.unknownType,
63
71
  ),
64
72
  );
65
73
  }
@@ -79,6 +87,7 @@ function validateDeclarations(lines: string[], symbols: DocumentSymbols): Valida
79
87
  lines[extra.line],
80
88
  'identifier',
81
89
  `Command '${command.name}' already marks '${identifiers[0].name}' as identifier — only one property can be the identifier.`,
90
+ diagnosticCodes.duplicateCommandIdentifier,
82
91
  ),
83
92
  );
84
93
  }
@@ -98,6 +107,7 @@ function validateDeclarations(lines: string[], symbols: DocumentSymbols): Valida
98
107
  lines[declaration.line],
99
108
  declaration.name,
100
109
  `Duplicate declaration of '${declaration.name}' — concept and type names must be unique.`,
110
+ diagnosticCodes.duplicateDeclaration,
101
111
  ),
102
112
  );
103
113
  }
@@ -117,7 +127,14 @@ export function validateLines(lines: string[]): ValidationIssue[] {
117
127
  const checkEvent = (line: number, text: string, name: string) => {
118
128
  if (!events.has(name)) {
119
129
  issues.push(
120
- tokenIssue('warning', line, text, name, `Unknown event type '${name}' — declare it in a slice or import it.`),
130
+ tokenIssue(
131
+ 'warning',
132
+ line,
133
+ text,
134
+ name,
135
+ `Unknown event type '${name}' — declare it in a slice or import it.`,
136
+ diagnosticCodes.unknownEvent,
137
+ ),
121
138
  );
122
139
  }
123
140
  };
@@ -133,7 +150,14 @@ export function validateLines(lines: string[]): ValidationIssue[] {
133
150
  const leading = line.match(/^[ ]*(\t+)/);
134
151
  if (leading) {
135
152
  issues.push(
136
- issue('warning', index, leading[0].length - leading[1].length + 1, leading[1].length, 'Screenplay is indentation-based — use spaces, not tabs.'),
153
+ issue(
154
+ 'warning',
155
+ index,
156
+ leading[0].length - leading[1].length + 1,
157
+ leading[1].length,
158
+ 'Screenplay is indentation-based — use spaces, not tabs.',
159
+ diagnosticCodes.tabIndentation,
160
+ ),
137
161
  );
138
162
  }
139
163
 
@@ -145,7 +169,16 @@ export function validateLines(lines: string[]): ValidationIssue[] {
145
169
  ) {
146
170
  for (const name of trimmed.split(/\s+/).filter((token) => token !== 'or')) {
147
171
  if (!policies.has(name)) {
148
- issues.push(tokenIssue('warning', index, line, name, `Unknown policy '${name}' — declare it with 'policy ${name}'.`));
172
+ issues.push(
173
+ tokenIssue(
174
+ 'warning',
175
+ index,
176
+ line,
177
+ name,
178
+ `Unknown policy '${name}' — declare it with 'policy ${name}'.`,
179
+ diagnosticCodes.unknownPolicy,
180
+ ),
181
+ );
149
182
  }
150
183
  }
151
184
  continue;
@@ -156,14 +189,28 @@ export function validateLines(lines: string[]): ValidationIssue[] {
156
189
  const slice = trimmed.match(/^slice\s+(\w+)/);
157
190
  if (slice && !sliceTypes.includes(slice[1])) {
158
191
  issues.push(
159
- tokenIssue('error', index, line, slice[1], `Unknown slice type '${slice[1]}' — expected ${sliceTypes.join(', ')}.`),
192
+ tokenIssue(
193
+ 'error',
194
+ index,
195
+ line,
196
+ slice[1],
197
+ `Unknown slice type '${slice[1]}' — expected ${sliceTypes.join(', ')}.`,
198
+ diagnosticCodes.unknownSliceType,
199
+ ),
160
200
  );
161
201
  }
162
202
 
163
203
  const concept = trimmed.match(/^concept\s+\w+\s*:\s*(\w+)/);
164
204
  if (concept && concept[1] !== 'Enum' && !primitiveTypes.includes(concept[1])) {
165
205
  issues.push(
166
- tokenIssue('error', index, line, concept[1], `Unknown primitive type '${concept[1]}' — expected ${primitiveTypes.join(', ')} or Enum.`),
206
+ tokenIssue(
207
+ 'error',
208
+ index,
209
+ line,
210
+ concept[1],
211
+ `Unknown primitive type '${concept[1]}' — expected ${primitiveTypes.join(', ')} or Enum.`,
212
+ diagnosticCodes.unknownPrimitiveType,
213
+ ),
167
214
  );
168
215
  }
169
216
 
@@ -172,7 +219,16 @@ export function validateLines(lines: string[]): ValidationIssue[] {
172
219
  authorizeIndent = indentOf(line);
173
220
  for (const name of authorize[1].split(/\s+/).filter((token) => token !== 'or' && token.length > 0)) {
174
221
  if (!policies.has(name)) {
175
- issues.push(tokenIssue('warning', index, line, name, `Unknown policy '${name}' — declare it with 'policy ${name}'.`));
222
+ issues.push(
223
+ tokenIssue(
224
+ 'warning',
225
+ index,
226
+ line,
227
+ name,
228
+ `Unknown policy '${name}' — declare it with 'policy ${name}'.`,
229
+ diagnosticCodes.unknownPolicy,
230
+ ),
231
+ );
176
232
  }
177
233
  }
178
234
  }
@@ -206,7 +262,14 @@ export function validateLines(lines: string[]): ValidationIssue[] {
206
262
  const segments = path.substring('$context.'.length).split('.');
207
263
  if (!contextRoots.includes(segments[0])) {
208
264
  issues.push(
209
- tokenIssue('warning', index, line, path, `Unknown $context path '${segments.join('.')}' — expected one of ${contextRoots.join(', ')}.`),
265
+ tokenIssue(
266
+ 'warning',
267
+ index,
268
+ line,
269
+ path,
270
+ `Unknown $context path '${segments.join('.')}' — expected one of ${contextRoots.join(', ')}.`,
271
+ diagnosticCodes.unknownContextPath,
272
+ ),
210
273
  );
211
274
  } else if (
212
275
  segments[0] === 'causedBy' &&
@@ -214,7 +277,14 @@ export function validateLines(lines: string[]): ValidationIssue[] {
214
277
  !causedByProperties.includes(segments[1])
215
278
  ) {
216
279
  issues.push(
217
- tokenIssue('warning', index, line, path, `Unknown $context.causedBy property '${segments[1]}' — expected ${causedByProperties.join(', ')}.`),
280
+ tokenIssue(
281
+ 'warning',
282
+ index,
283
+ line,
284
+ path,
285
+ `Unknown $context.causedBy property '${segments[1]}' — expected ${causedByProperties.join(', ')}.`,
286
+ diagnosticCodes.unknownContextCausedByProperty,
287
+ ),
218
288
  );
219
289
  } else if (
220
290
  segments[0] === 'identity' &&
@@ -222,7 +292,14 @@ export function validateLines(lines: string[]): ValidationIssue[] {
222
292
  !identityProperties.includes(segments[1])
223
293
  ) {
224
294
  issues.push(
225
- tokenIssue('warning', index, line, path, `Unknown $context.identity property '${segments[1]}' — expected ${identityProperties.join(', ')}.`),
295
+ tokenIssue(
296
+ 'warning',
297
+ index,
298
+ line,
299
+ path,
300
+ `Unknown $context.identity property '${segments[1]}' — expected ${identityProperties.join(', ')}.`,
301
+ diagnosticCodes.unknownContextIdentityProperty,
302
+ ),
226
303
  );
227
304
  }
228
305
  }
@@ -233,7 +310,16 @@ export function validateLines(lines: string[]): ValidationIssue[] {
233
310
  .filter(({ line }) => /^\s*```\s*$/.test(line));
234
311
  if (fenceLines.length % 2 === 1) {
235
312
  const last = fenceLines[fenceLines.length - 1];
236
- issues.push(issue('error', last.index, 1, last.line.length + 1, 'Unclosed inline code block — expected a closing ``` line.'));
313
+ issues.push(
314
+ issue(
315
+ 'error',
316
+ last.index,
317
+ 1,
318
+ last.line.length + 1,
319
+ 'Unclosed inline code block — expected a closing ``` line.',
320
+ diagnosticCodes.unclosedCodeBlock,
321
+ ),
322
+ );
237
323
  }
238
324
 
239
325
  return issues;
@@ -0,0 +1,5 @@
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
+ // Installs Chai's `.should` fluent interface, which every spec asserts through.
5
+ import 'chai/register-should';