@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.
- package/README.md +1 -1
- package/diagnostic-codes.ts +27 -0
- package/diagnostics.ts +1 -0
- package/dist/diagnostic-codes.d.ts +16 -0
- package/dist/diagnostic-codes.d.ts.map +1 -0
- package/dist/diagnostic-codes.js +25 -0
- package/dist/diagnostic-codes.js.map +1 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +1 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/file-references.d.ts +11 -0
- package/dist/file-references.d.ts.map +1 -0
- package/dist/file-references.js +70 -0
- package/dist/file-references.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +5 -1
- package/dist/symbols.js.map +1 -1
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +18 -17
- package/dist/validation.js.map +1 -1
- package/file-references.ts +87 -0
- package/for_fileReferences/when_recognizing_an_absolute_path/with_paths_of_every_shape.ts +26 -0
- package/for_fileReferences/when_scanning_a_document/inside_a_code_fence.ts +26 -0
- package/for_fileReferences/when_scanning_a_document/with_a_bare_file_keyword.ts +19 -0
- package/for_fileReferences/when_scanning_a_document/with_a_file_directive.ts +30 -0
- package/for_fileReferences/when_scanning_a_document/with_a_property_named_file.ts +25 -0
- package/for_fileReferences/when_scanning_a_document/with_a_trailing_comment.ts +25 -0
- package/for_fileReferences/when_scanning_a_document/with_directives_on_several_constructs.ts +41 -0
- package/for_scanDocument/when_scanning_a_query/with_a_dotted_return_type.ts +34 -0
- package/for_validateLines/when_validating_a_document/with_a_problem_of_every_coded_kind.ts +52 -0
- package/for_validateLines/when_validating_a_document/with_an_unknown_policy.ts +33 -0
- package/index.ts +9 -0
- package/package.json +8 -4
- package/symbols.ts +5 -1
- package/validation.ts +98 -12
- package/vitest.setup.ts +5 -0
|
@@ -0,0 +1,87 @@
|
|
|
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 { fenceMap, firstWord, indentOf } from './document-context';
|
|
5
|
+
|
|
6
|
+
// The keyword the directive opens with — the single word the language points at a file with.
|
|
7
|
+
export const fileReferenceKeyword = 'file';
|
|
8
|
+
|
|
9
|
+
// A `file <path>` directive, with the path exactly as written and the span it occupies on its line.
|
|
10
|
+
// The span is measured against the raw line, and its columns are one-based like every other position
|
|
11
|
+
// this package reports, so an editor places a range on it the same way it does a validation issue.
|
|
12
|
+
export interface FileReference {
|
|
13
|
+
path: string;
|
|
14
|
+
line: number;
|
|
15
|
+
startColumn: number;
|
|
16
|
+
endColumn: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// `file String` is a property named `file`; `file Invoices/Register.cs` is the directive. The two are
|
|
20
|
+
// told apart by shape, the way the compiler tells them apart: a type reference is a bare identifier,
|
|
21
|
+
// so anything carrying a separator or an extension is a path and nothing else. The property wins the
|
|
22
|
+
// tie, because a document that used the name before the directive existed keeps meaning what it meant.
|
|
23
|
+
const typeReferencePattern = /^[A-Za-z_]\w*(?:\[\])?\??$/;
|
|
24
|
+
|
|
25
|
+
// A path naming a place on one machine rather than a place in the repository. The compiler warns about
|
|
26
|
+
// these; nothing here rejects one, so a resolver can still probe it and come up empty on other machines.
|
|
27
|
+
const absolutePathPattern = /^(?:[/\\]|[A-Za-z]:[/\\]|~[/\\])/;
|
|
28
|
+
|
|
29
|
+
export function isAbsoluteFileReferencePath(path: string): boolean {
|
|
30
|
+
return absolutePathPattern.test(path);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Drops a trailing `//` comment without cutting into a string or a template, mirroring how the
|
|
34
|
+
// compiler strips one before it reads the line.
|
|
35
|
+
function withoutComment(text: string): string {
|
|
36
|
+
let inString = false;
|
|
37
|
+
let inTemplate = false;
|
|
38
|
+
|
|
39
|
+
for (let index = 0; index < text.length; index++) {
|
|
40
|
+
const current = text[index];
|
|
41
|
+
if (current === '\\' && inString && index + 1 < text.length) {
|
|
42
|
+
index++;
|
|
43
|
+
} else if (current === '"' && !inTemplate) {
|
|
44
|
+
inString = !inString;
|
|
45
|
+
} else if (current === '`' && !inString) {
|
|
46
|
+
inTemplate = !inTemplate;
|
|
47
|
+
} else if (!inString && !inTemplate && current === '/' && text[index + 1] === '/') {
|
|
48
|
+
return text.slice(0, index);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The `file` directive on a single line, or undefined when the line is not one. Callers that scan a
|
|
56
|
+
// whole document go through fileReferences instead, so code fences are accounted for.
|
|
57
|
+
export function fileReferenceOn(line: string, lineIndex: number): FileReference | undefined {
|
|
58
|
+
const indent = indentOf(line);
|
|
59
|
+
const content = withoutComment(line.slice(indent)).trimEnd();
|
|
60
|
+
if (firstWord(content) !== fileReferenceKeyword) return undefined;
|
|
61
|
+
|
|
62
|
+
const afterKeyword = content.slice(fileReferenceKeyword.length);
|
|
63
|
+
const path = afterKeyword.trim();
|
|
64
|
+
|
|
65
|
+
// A bare `file` leaves the word available as a name to whatever the enclosing block reads a bare
|
|
66
|
+
// word as, and a bare identifier after it is a property type rather than a path.
|
|
67
|
+
if (path.length === 0 || typeReferencePattern.test(path)) return undefined;
|
|
68
|
+
|
|
69
|
+
const separatorLength = afterKeyword.length - afterKeyword.trimStart().length;
|
|
70
|
+
const startColumn = indent + fileReferenceKeyword.length + separatorLength + 1;
|
|
71
|
+
return { path, line: lineIndex, startColumn, endColumn: startColumn + path.length };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Every `file` directive in a document, in document order. Lines inside a code fence are skipped, so
|
|
75
|
+
// a `file` line in an inline C# or TypeScript block is never mistaken for one of ours.
|
|
76
|
+
export function fileReferences(lines: string[]): FileReference[] {
|
|
77
|
+
const fences = fenceMap(lines);
|
|
78
|
+
const references: FileReference[] = [];
|
|
79
|
+
|
|
80
|
+
for (let index = 0; index < lines.length; index++) {
|
|
81
|
+
if (fences[index]) continue;
|
|
82
|
+
const reference = fileReferenceOn(lines[index], index);
|
|
83
|
+
if (reference) references.push(reference);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return references;
|
|
87
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { isAbsoluteFileReferencePath } from '../../file-references';
|
|
6
|
+
|
|
7
|
+
// A reference is relative to the repository root, so it means the same thing on every machine. An
|
|
8
|
+
// absolute one names a place on one machine and is wrong without looking anything up.
|
|
9
|
+
describe('when recognizing an absolute path', () => {
|
|
10
|
+
it('should recognize a posix path', () => {
|
|
11
|
+
isAbsoluteFileReferencePath('/Users/someone/Invoices/Register.cs').should.be.true;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should recognize a windows path', () => {
|
|
15
|
+
isAbsoluteFileReferencePath('C:/repos/Invoices/Register.cs').should.be.true;
|
|
16
|
+
isAbsoluteFileReferencePath('C:\\repos\\Invoices\\Register.cs').should.be.true;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should recognize a home-relative path', () => {
|
|
20
|
+
isAbsoluteFileReferencePath('~/repos/Invoices/Register.cs').should.be.true;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should not recognize a repository-relative path', () => {
|
|
24
|
+
isAbsoluteFileReferencePath('Invoices/Register.cs').should.be.false;
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
// An inline C# block may well contain the word `file` at the start of a line — it is a C# modifier —
|
|
8
|
+
// and none of it is Screenplay.
|
|
9
|
+
describe('when scanning a document with a file line inside a code fence', () => {
|
|
10
|
+
let result: FileReference[];
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
result = fileReferences([
|
|
14
|
+
'handler',
|
|
15
|
+
' ```',
|
|
16
|
+
' file static class Helpers/NotAPath.cs',
|
|
17
|
+
' ```',
|
|
18
|
+
' file Handlers/Real.cs',
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should find only the reference outside the fence', () => {
|
|
23
|
+
result.should.have.lengthOf(1);
|
|
24
|
+
result[0].path.should.equal('Handlers/Real.cs');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
// A bare `file` carries no path, so it leaves the word available as a name to whatever the enclosing
|
|
8
|
+
// block reads a bare word as.
|
|
9
|
+
describe('when scanning a document with a bare file keyword', () => {
|
|
10
|
+
let result: FileReference[];
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
result = fileReferences(['reaction', ' file', ' file ', 'filename Something.cs']);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should find no reference', () => {
|
|
17
|
+
result.should.be.empty;
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { DocumentSymbols, scanDocument } from '../../symbols';
|
|
6
|
+
|
|
7
|
+
// The compiler takes a dotted return type - QueryParser's ([\w.]+(?:\[\])?\??) - so the editor has to
|
|
8
|
+
// as well. When it did not, the query never made it into the symbol table and every check and
|
|
9
|
+
// completion reading from it went silently missing.
|
|
10
|
+
describe('when scanning a query with a dotted return type', () => {
|
|
11
|
+
let symbols: DocumentSymbols;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
symbols = scanDocument([
|
|
15
|
+
'module Invoicing',
|
|
16
|
+
' feature Invoices',
|
|
17
|
+
' slice StateView Overdue',
|
|
18
|
+
' query Overdue => Invoicing.OverdueReadModel[]',
|
|
19
|
+
' by invoiceId InvoiceId',
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should register the query', () => {
|
|
24
|
+
symbols.queries.should.have.lengthOf(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should carry the return type as written', () => {
|
|
28
|
+
symbols.queries[0].returnType.should.equal('Invoicing.OverdueReadModel[]');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should collect the parameters it declares', () => {
|
|
32
|
+
symbols.queries[0].parameters.should.have.lengthOf(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { diagnosticCodes } from '../../diagnostic-codes';
|
|
6
|
+
import { ValidationIssue, validateLines } from '../../validation';
|
|
7
|
+
|
|
8
|
+
// One document holding every condition the editor and the compiler both check, so a condition added
|
|
9
|
+
// later without a code fails here rather than reaching a user as a codeless squiggle.
|
|
10
|
+
const document = [
|
|
11
|
+
'concept InvoiceId : Guid',
|
|
12
|
+
'concept InvoiceId : String',
|
|
13
|
+
'concept Amount : Wat',
|
|
14
|
+
'',
|
|
15
|
+
'module Invoicing',
|
|
16
|
+
' feature Invoices',
|
|
17
|
+
' slice Wat Register',
|
|
18
|
+
' event InvoiceRegistered',
|
|
19
|
+
' invoiceId InvoiceId',
|
|
20
|
+
'\t amount Amount',
|
|
21
|
+
' command RegisterInvoice',
|
|
22
|
+
' invoiceId InvoiceId identifier',
|
|
23
|
+
' amount Amount identifier',
|
|
24
|
+
' reference Unknown',
|
|
25
|
+
' authorize CanRegister',
|
|
26
|
+
' produces InvoiceArchived',
|
|
27
|
+
' registeredBy = $context.wat',
|
|
28
|
+
' causedBy = $context.causedBy.wat',
|
|
29
|
+
' department = $context.identity.wat',
|
|
30
|
+
' query Overdue => OverdueReadModel[]',
|
|
31
|
+
' performer',
|
|
32
|
+
' csharp',
|
|
33
|
+
' ```',
|
|
34
|
+
' return readModels;',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
describe('when validating a document with a problem of every coded kind', () => {
|
|
38
|
+
let issues: ValidationIssue[];
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
issues = validateLines(document);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should give every issue it reports a code', () => {
|
|
45
|
+
issues.filter((issue) => !issue.code).should.be.empty;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should report each condition with the code the compiler reports it with', () => {
|
|
49
|
+
const reported = new Set(issues.map((issue) => issue.code));
|
|
50
|
+
[...Object.values(diagnosticCodes)].forEach((code) => reported.has(code).should.be.true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { diagnosticCodes } from '../../diagnostic-codes';
|
|
6
|
+
import { ValidationIssue, validateLines } from '../../validation';
|
|
7
|
+
|
|
8
|
+
describe('when validating a document with an unknown policy', () => {
|
|
9
|
+
let issues: ValidationIssue[];
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
issues = validateLines([
|
|
13
|
+
'module Invoicing',
|
|
14
|
+
' feature Invoices',
|
|
15
|
+
' slice StateView Overdue',
|
|
16
|
+
' query Overdue => OverdueReadModel[]',
|
|
17
|
+
' authorize CanRead',
|
|
18
|
+
' or CanAlsoRead',
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should report the policy named on the authorize line', () => {
|
|
23
|
+
issues[0].should.include({ line: 4, code: diagnosticCodes.unknownPolicy });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should report the policy continued on the line below it', () => {
|
|
27
|
+
issues[1].should.include({ line: 5, code: diagnosticCodes.unknownPolicy });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should report nothing else', () => {
|
|
31
|
+
issues.should.have.lengthOf(2);
|
|
32
|
+
});
|
|
33
|
+
});
|
package/index.ts
CHANGED
|
@@ -79,6 +79,13 @@ export { cdl } from './sub-languages/cdl';
|
|
|
79
79
|
export { screenplayDarkThemeName } from './themes/screenplay-dark';
|
|
80
80
|
export { screenplayLightThemeName } from './themes/screenplay-light';
|
|
81
81
|
export { enclosingChain, fenceMap, firstWord, indentOf } from './document-context';
|
|
82
|
+
export {
|
|
83
|
+
fileReferenceKeyword,
|
|
84
|
+
fileReferenceOn,
|
|
85
|
+
fileReferences,
|
|
86
|
+
isAbsoluteFileReferencePath,
|
|
87
|
+
} from './file-references';
|
|
88
|
+
export type { FileReference } from './file-references';
|
|
82
89
|
export { builtInTriggerNames, knownEventNames, knownTriggerNames, knownTypeNames, scanDocument } from './symbols';
|
|
83
90
|
export type {
|
|
84
91
|
CommandSymbol,
|
|
@@ -100,3 +107,5 @@ export type { CompletionPlan } from './completion-planner';
|
|
|
100
107
|
export { hoverContent } from './hover-content';
|
|
101
108
|
export { validateLines } from './validation';
|
|
102
109
|
export type { ValidationIssue, ValidationSeverity } from './validation';
|
|
110
|
+
export { diagnosticCodes } from './diagnostic-codes';
|
|
111
|
+
export type { DiagnosticCode } from './diagnostic-codes';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/screenplay-language",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "Monaco language service for the Cratis Screenplay DSL — syntax highlighting, IntelliSense, hover and diagnostics for .play files",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,16 +33,20 @@
|
|
|
33
33
|
"**/*.ts"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsc -p tsconfig.json",
|
|
36
|
+
"build": "tsc -p tsconfig.build.json",
|
|
37
37
|
"compile": "tsc --noEmit -p tsconfig.json",
|
|
38
38
|
"lint": "yarn g:lint",
|
|
39
|
-
"lint:ci": "yarn g:lint:ci"
|
|
39
|
+
"lint:ci": "yarn g:lint:ci",
|
|
40
|
+
"test": "vitest run"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
43
|
"monaco-editor": ">=0.44.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
46
|
+
"@types/chai": "^5.2.3",
|
|
47
|
+
"chai": "^6.2.2",
|
|
45
48
|
"monaco-editor": "^0.55.1",
|
|
46
|
-
"typescript": "6.0.3"
|
|
49
|
+
"typescript": "6.0.3",
|
|
50
|
+
"vitest": "^4.1.11"
|
|
47
51
|
}
|
|
48
52
|
}
|
package/symbols.ts
CHANGED
|
@@ -194,7 +194,11 @@ export function scanDocument(lines: string[]): DocumentSymbols {
|
|
|
194
194
|
continue;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
// The return type shape is the compiler's, dots and all - QueryParser's
|
|
198
|
+
// ^query\s+([A-Za-z_]\w*)\s*=>\s*(observable\s+)?([\w.]+(?:\[\])?\??)$. A narrower one here
|
|
199
|
+
// silently drops the query from the symbol table, and with it every check and completion that
|
|
200
|
+
// reads from it.
|
|
201
|
+
const queryMatch = trimmed.match(/^query\s+(\w+)\s*=>\s*(?:observable\s+)?([\w.]+(?:\[\])?\??)\s*$/);
|
|
198
202
|
if (queryMatch) {
|
|
199
203
|
// The return type names a read model, which no construct declares — only the
|
|
200
204
|
// 'by' and 'filter' parameters resolve against the document's own types.
|