@reqlan/language 1.9.6 → 1.10.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 +42 -0
- package/README.template.md +2 -0
- package/out/file-path-rewrite.d.ts +1 -1
- package/out/generated/ast.d.ts +2 -2
- package/out/generated/ast.js +1 -1
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.js +1 -1
- package/out/generated/grammar.js.map +1 -1
- package/out/index.d.ts +3 -1
- package/out/index.js +3 -1
- package/out/index.js.map +1 -1
- package/out/reqlan-code-action-provider.d.ts +1 -0
- package/out/reqlan-code-action-provider.js +12 -21
- package/out/reqlan-code-action-provider.js.map +1 -1
- package/out/reqlan-comment-diagnostics.d.ts +11 -0
- package/out/reqlan-comment-diagnostics.js +81 -11
- package/out/reqlan-comment-diagnostics.js.map +1 -1
- package/out/reqlan-comment-resolver.d.ts +1 -1
- package/out/reqlan-completion-provider.d.ts +2 -2
- package/out/reqlan-definition-provider.js +1 -1
- package/out/reqlan-definition-provider.js.map +1 -1
- package/out/reqlan-document-builder.d.ts +9 -0
- package/out/reqlan-document-builder.js +26 -0
- package/out/reqlan-document-builder.js.map +1 -0
- package/out/reqlan-document-link-provider.d.ts +5 -1
- package/out/reqlan-document-link-provider.js +9 -1
- package/out/reqlan-document-link-provider.js.map +1 -1
- package/out/reqlan-embedded-file-references.d.ts +3 -0
- package/out/reqlan-embedded-file-references.js +47 -5
- package/out/reqlan-embedded-file-references.js.map +1 -1
- package/out/reqlan-file-link-resolver.d.ts +8 -0
- package/out/reqlan-file-link-resolver.js +46 -11
- package/out/reqlan-file-link-resolver.js.map +1 -1
- package/out/reqlan-file-references.d.ts +1 -0
- package/out/reqlan-file-references.js +24 -16
- package/out/reqlan-file-references.js.map +1 -1
- package/out/reqlan-idea-move-imports.d.ts +24 -0
- package/out/reqlan-idea-move-imports.js +309 -0
- package/out/reqlan-idea-move-imports.js.map +1 -0
- package/out/reqlan-idea-refactor.d.ts +12 -4
- package/out/reqlan-idea-refactor.js +85 -13
- package/out/reqlan-idea-refactor.js.map +1 -1
- package/out/reqlan-ignore-error.d.ts +2 -1
- package/out/reqlan-ignore-error.js +4 -17
- package/out/reqlan-ignore-error.js.map +1 -1
- package/out/reqlan-import-edits.d.ts +1 -0
- package/out/reqlan-import-edits.js +8 -0
- package/out/reqlan-import-edits.js.map +1 -1
- package/out/reqlan-imports.d.ts +3 -1
- package/out/reqlan-imports.js +2 -5
- package/out/reqlan-imports.js.map +1 -1
- package/out/reqlan-module.js +4 -1
- package/out/reqlan-module.js.map +1 -1
- package/out/reqlan-reference-at-position.d.ts +3 -0
- package/out/reqlan-reference-at-position.js +8 -2
- package/out/reqlan-reference-at-position.js.map +1 -1
- package/out/reqlan-scope.d.ts +14 -0
- package/out/reqlan-scope.js +58 -15
- package/out/reqlan-scope.js.map +1 -1
- package/out/reqlan-validator.d.ts +12 -1
- package/out/reqlan-validator.js +38 -4
- package/out/reqlan-validator.js.map +1 -1
- package/package.json +3 -2
- package/src/file-path-rewrite.ts +1 -1
- package/src/generated/ast.ts +2 -2
- package/src/generated/grammar.ts +1 -1
- package/src/index.ts +9 -0
- package/src/reqlan-code-action-provider.ts +12 -24
- package/src/reqlan-comment-diagnostics.ts +108 -11
- package/src/reqlan-comment-resolver.ts +1 -1
- package/src/reqlan-completion-provider.ts +2 -2
- package/src/reqlan-definition-provider.ts +1 -1
- package/src/reqlan-document-builder.ts +35 -0
- package/src/reqlan-document-link-provider.ts +23 -3
- package/src/reqlan-embedded-file-references.ts +54 -5
- package/src/reqlan-file-link-resolver.ts +56 -11
- package/src/reqlan-file-references.ts +26 -16
- package/src/reqlan-idea-move-imports.ts +398 -0
- package/src/reqlan-idea-refactor.ts +116 -14
- package/src/reqlan-ignore-error.ts +4 -18
- package/src/reqlan-import-edits.ts +9 -0
- package/src/reqlan-imports.ts +4 -5
- package/src/reqlan-module.ts +4 -1
- package/src/reqlan-reference-at-position.ts +16 -2
- package/src/reqlan-scope.ts +73 -16
- package/src/reqlan-validator.ts +48 -4
- package/src/reqlan.langium +4 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Locates file path references at a cursor position and resolves them to files, folders, or missing paths.
|
|
3
|
+
* Missing targets are omitted here so go-to-definition can still try import-path resolution
|
|
4
|
+
* (for example `from "./target"` → `./target.rq`). Diagnostics still underline those refs.
|
|
5
|
+
* rq:["../../../reqlan rq/extension/language-support/language-server-errors.rq".file_reference_errors]
|
|
3
6
|
*/
|
|
4
7
|
import type { CstNode, FileSystemProvider, LangiumDocument, LangiumDocuments } from 'langium';
|
|
5
8
|
import { CstUtils, GrammarUtils } from 'langium';
|
|
@@ -55,15 +58,26 @@ export function findFileReferenceAtPosition(
|
|
|
55
58
|
}
|
|
56
59
|
const embeddedReference = findEmbeddedFileReferenceAt(document, position);
|
|
57
60
|
if (embeddedReference) {
|
|
58
|
-
return
|
|
61
|
+
return navigableFileLink(
|
|
62
|
+
resolveEmbeddedFileReferenceLink(embeddedReference, document, documents, fileSystem, pathContext)
|
|
63
|
+
);
|
|
59
64
|
}
|
|
60
65
|
const fileReference = findFileReferenceNodeAtPosition(document, position);
|
|
61
66
|
if (fileReference) {
|
|
62
|
-
return
|
|
67
|
+
return navigableFileLink(
|
|
68
|
+
resolveFileReferenceLink(fileReference.node, documents, fileSystem, pathContext)
|
|
69
|
+
);
|
|
63
70
|
}
|
|
64
71
|
return undefined;
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
function navigableFileLink(link: ResolvedFileLink | undefined): ResolvedFileLink | undefined {
|
|
75
|
+
if (!link || link.resolution === 'missing') {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return link;
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
function resolvePathReference(
|
|
68
82
|
document: LangiumDocument,
|
|
69
83
|
path: string,
|
package/src/reqlan-scope.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Publishes idea and ideaset declarations for file-local and cross-file reference resolution.
|
|
3
|
+
* Local declarations are collected first; imports are consulted only when a name is not local.
|
|
4
|
+
* rq:["../../../reqlan rq/language/syntax.rq".same_file_reference]
|
|
5
|
+
* rq:["../../../reqlan rq/language/syntax.rq".reference_resolution_order]
|
|
3
6
|
*/
|
|
4
7
|
import type { AstNodeDescription, LangiumDocument, ReferenceInfo } from 'langium';
|
|
5
8
|
import { AstUtils, DefaultScopeComputation, DefaultScopeProvider, StreamScope, stream, type Scope } from 'langium';
|
|
6
9
|
import {
|
|
7
10
|
isFromImport,
|
|
8
11
|
isFromImportSpecifier,
|
|
9
|
-
|
|
12
|
+
isIdeaDeclaration,
|
|
10
13
|
isIdeaSet,
|
|
11
14
|
isInvalidFromImport,
|
|
12
15
|
isLocalReference,
|
|
13
16
|
isModel,
|
|
14
17
|
isNamespaceImport,
|
|
15
|
-
isOneLinerIdea,
|
|
16
18
|
isQualifiedImport,
|
|
17
19
|
isQualifiedReference,
|
|
18
20
|
type FromImportSpecifier,
|
|
@@ -34,7 +36,7 @@ export class ReqlanScopeComputation extends DefaultScopeComputation {
|
|
|
34
36
|
const model = document.parseResult.value;
|
|
35
37
|
if (isModel(model)) {
|
|
36
38
|
for (const element of model.elements) {
|
|
37
|
-
if (
|
|
39
|
+
if (isIdeaDeclaration(element)) {
|
|
38
40
|
this.addExportedSymbol(element, exports, document);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -146,20 +148,49 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
private buildFileIdeasScope(document: LangiumDocument): Scope {
|
|
151
|
+
const locals = this.localDeclarationDescriptions(document);
|
|
152
|
+
return new StreamScope(
|
|
153
|
+
stream(locals),
|
|
154
|
+
new LazyScope(() => this.importedIdeaScope(document)),
|
|
155
|
+
{ concatOuterScope: false }
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Every named top-level declaration in the file, regardless of source order.
|
|
161
|
+
* Bare `[name]` and ideaset members resolve against this set first.
|
|
162
|
+
*/
|
|
163
|
+
private localDeclarationDescriptions(document: LangiumDocument): AstNodeDescription[] {
|
|
149
164
|
const model = document.parseResult.value as Model;
|
|
150
165
|
if (!isModel(model)) {
|
|
151
|
-
return
|
|
166
|
+
return [];
|
|
152
167
|
}
|
|
153
|
-
|
|
154
|
-
.filter(
|
|
155
|
-
.map(
|
|
168
|
+
return model.elements
|
|
169
|
+
.filter(isIdeaDeclaration)
|
|
170
|
+
.map(element => this.descriptions.createDescription(element, element.name, document));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Imported bindings only. Built lazily so a fully-local file never walks import `.ref`s
|
|
175
|
+
* while resolving same-file names.
|
|
176
|
+
*/
|
|
177
|
+
private importedIdeaScope(document: LangiumDocument): Scope {
|
|
178
|
+
return new StreamScope(stream(this.importedIdeaDescriptions(document)));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private importedIdeaDescriptions(document: LangiumDocument): AstNodeDescription[] {
|
|
182
|
+
const model = document.parseResult.value as Model;
|
|
183
|
+
if (!isModel(model)) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
const descriptions: AstNodeDescription[] = [];
|
|
156
187
|
// Imported ideas enter scope under their binding name only (alias when present).
|
|
157
188
|
// See import_tokenisation: aliased base names are not reserved locally.
|
|
158
189
|
for (const importDecl of model.imports) {
|
|
159
190
|
if (isFromImport(importDecl)) {
|
|
160
191
|
for (const specifier of importDecl.specifiers) {
|
|
161
192
|
const target = specifier.idea.ref;
|
|
162
|
-
if (!target ||
|
|
193
|
+
if (!target || !isIdeaDeclaration(target)) {
|
|
163
194
|
continue;
|
|
164
195
|
}
|
|
165
196
|
const name = specifierBindingName(specifier) ?? target.name;
|
|
@@ -169,21 +200,21 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
169
200
|
}
|
|
170
201
|
if (isQualifiedImport(importDecl)) {
|
|
171
202
|
const target = importDecl.idea.ref;
|
|
172
|
-
if (!target ||
|
|
203
|
+
if (!target || !isIdeaDeclaration(target)) {
|
|
173
204
|
continue;
|
|
174
205
|
}
|
|
175
206
|
const name = importDecl.alias ?? target.name;
|
|
176
207
|
descriptions.push(this.descriptions.createDescription(target, name, AstUtils.getDocument(target)));
|
|
177
208
|
}
|
|
178
209
|
}
|
|
179
|
-
return
|
|
210
|
+
return descriptions;
|
|
180
211
|
}
|
|
181
212
|
|
|
182
213
|
private scopeForIdeasetMembers(ideaset: IdeaSet, document: LangiumDocument): Scope {
|
|
183
214
|
const descriptions = ideaset.members
|
|
184
215
|
.map(member => member.ref)
|
|
185
216
|
.filter((target): target is NonNullable<typeof target> =>
|
|
186
|
-
target !== undefined && (
|
|
217
|
+
target !== undefined && isIdeaDeclaration(target)
|
|
187
218
|
)
|
|
188
219
|
.map(target => this.descriptions.createDescription(target, target.name, AstUtils.getDocument(target)));
|
|
189
220
|
if (descriptions.length > 0) {
|
|
@@ -209,7 +240,7 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
209
240
|
return new StreamScope(stream([]));
|
|
210
241
|
}
|
|
211
242
|
const locals = model.elements
|
|
212
|
-
.filter(
|
|
243
|
+
.filter(isIdeaDeclaration)
|
|
213
244
|
.map(element => this.descriptions.createDescription(element, element.name, document));
|
|
214
245
|
const namedImports = model.imports.flatMap(importDecl => {
|
|
215
246
|
if (isFromImport(importDecl)) {
|
|
@@ -247,7 +278,7 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
247
278
|
return undefined;
|
|
248
279
|
}
|
|
249
280
|
const target = importDecl.idea.ref;
|
|
250
|
-
if (!target ||
|
|
281
|
+
if (!target || !isIdeaDeclaration(target)) {
|
|
251
282
|
return undefined;
|
|
252
283
|
}
|
|
253
284
|
const description = this.descriptions.createDescription(target, target.name, imported);
|
|
@@ -261,7 +292,7 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
261
292
|
const descriptions = importDecl.specifiers
|
|
262
293
|
.map(specifier => specifier.idea.ref)
|
|
263
294
|
.filter((target): target is NonNullable<typeof target> =>
|
|
264
|
-
target !== undefined && (
|
|
295
|
+
target !== undefined && isIdeaDeclaration(target)
|
|
265
296
|
)
|
|
266
297
|
.map(target => this.descriptions.createDescription(target, target.name, imported));
|
|
267
298
|
return new StreamScope(stream(descriptions));
|
|
@@ -272,7 +303,7 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
272
303
|
return undefined;
|
|
273
304
|
}
|
|
274
305
|
const target = importDecl.idea.ref;
|
|
275
|
-
if (!target ||
|
|
306
|
+
if (!target || !isIdeaDeclaration(target)) {
|
|
276
307
|
return undefined;
|
|
277
308
|
}
|
|
278
309
|
const description = this.descriptions.createDescription(target, target.name, imported);
|
|
@@ -315,7 +346,7 @@ export class ReqlanScopeProvider extends DefaultScopeProvider {
|
|
|
315
346
|
return undefined;
|
|
316
347
|
}
|
|
317
348
|
const descriptions = model.elements
|
|
318
|
-
.filter(
|
|
349
|
+
.filter(isIdeaDeclaration)
|
|
319
350
|
.map(idea => this.descriptions.createDescription(idea, idea.name, imported));
|
|
320
351
|
const scope = new StreamScope(stream(descriptions));
|
|
321
352
|
this.importPathCache.set(key, { version: imported.textDocument.version, scope });
|
|
@@ -349,3 +380,29 @@ function importAlias(entry: Import): string | undefined {
|
|
|
349
380
|
}
|
|
350
381
|
return entry.alias;
|
|
351
382
|
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Defers outer-scope construction until a local lookup misses.
|
|
386
|
+
* StreamScope `getElement` does not call the outer scope when a local name matches.
|
|
387
|
+
*/
|
|
388
|
+
class LazyScope implements Scope {
|
|
389
|
+
private resolved: Scope | undefined;
|
|
390
|
+
|
|
391
|
+
constructor(private readonly load: () => Scope) {}
|
|
392
|
+
|
|
393
|
+
getElement(name: string): AstNodeDescription | undefined {
|
|
394
|
+
return this.inner().getElement(name);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
getElements(name: string): ReturnType<Scope['getElements']> {
|
|
398
|
+
return this.inner().getElements(name);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
getAllElements(): ReturnType<Scope['getAllElements']> {
|
|
402
|
+
return this.inner().getAllElements();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private inner(): Scope {
|
|
406
|
+
return this.resolved ??= this.load();
|
|
407
|
+
}
|
|
408
|
+
}
|
package/src/reqlan-validator.ts
CHANGED
|
@@ -3,16 +3,18 @@ import { AstUtils } from 'langium';
|
|
|
3
3
|
import type { reqlanAstType } from './generated/ast.js';
|
|
4
4
|
import {
|
|
5
5
|
isFromImport,
|
|
6
|
-
|
|
6
|
+
isIdeaDeclaration,
|
|
7
7
|
isInvalidFromImport,
|
|
8
8
|
isModel,
|
|
9
|
-
isOneLinerIdea,
|
|
10
9
|
isWildcardReference,
|
|
11
10
|
type AnonymousBlock,
|
|
12
11
|
type Model
|
|
13
12
|
} from './generated/ast.js';
|
|
13
|
+
import { collectCommentReferenceIssues } from './reqlan-comment-diagnostics.js';
|
|
14
14
|
import {
|
|
15
15
|
collectFileLinks,
|
|
16
|
+
FILE_REFERENCE_MISSING,
|
|
17
|
+
fileLinkMissingMessage,
|
|
16
18
|
fileLinkTargetIssueMessage
|
|
17
19
|
} from './reqlan-file-link-resolver.js';
|
|
18
20
|
import {
|
|
@@ -33,9 +35,12 @@ import {
|
|
|
33
35
|
/**
|
|
34
36
|
* Registers validation hooks for the requirement graph AST.
|
|
35
37
|
* rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_does_not_exist_error]
|
|
38
|
+
* rq:["../../../reqlan rq/extension/language-support/language-server-errors.rq".file_reference_errors]
|
|
36
39
|
* rq:["../../../reqlan rq/language/imports.rq".import_error_recovery]
|
|
37
40
|
* rq:["../../../reqlan rq/language/imports.rq".import_tokenisation]
|
|
38
41
|
* rq:["../../../reqlan rq/language/syntax.rq".no_name_idea_safe_warning]
|
|
42
|
+
* rq:["../../../reqlan rq/language/syntax.rq".comment_reference_resolution_error]
|
|
43
|
+
* rq:["../../../reqlan rq/extension/features-non-rq-code-comment/functional-code-comment-references.rq".comment_reference_resolution_error_state]
|
|
39
44
|
*/
|
|
40
45
|
export function registerValidationChecks(services: ReqlanServices) {
|
|
41
46
|
const registry = services.validation.ValidationRegistry;
|
|
@@ -50,9 +55,14 @@ export function registerValidationChecks(services: ReqlanServices) {
|
|
|
50
55
|
/**
|
|
51
56
|
* Custom validations for Reqlan documents.
|
|
52
57
|
* rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_does_not_exist_error]
|
|
58
|
+
* rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_folder_targets]
|
|
59
|
+
* rq:["../../../reqlan rq/extension/language-support/language-server-errors.rq".file_reference_errors]
|
|
53
60
|
* rq:["../../../reqlan rq/language/imports.rq".import_error_recovery]
|
|
54
61
|
* rq:["../../../reqlan rq/language/imports.rq".import_tokenisation]
|
|
55
62
|
* rq:["../../../reqlan rq/language/syntax.rq".no_name_idea_safe_warning]
|
|
63
|
+
* rq:["../../../reqlan rq/language/syntax.rq".comment_reference_resolution_error]
|
|
64
|
+
* rq:["../../../reqlan rq/core_analysis/check.rq".check_wildcard_sparse]
|
|
65
|
+
* rq:["../../../reqlan rq/extension/features-non-rq-code-comment/functional-code-comment-references.rq".comment_reference_resolution_error_state]
|
|
56
66
|
*/
|
|
57
67
|
export class ReqlanValidator {
|
|
58
68
|
|
|
@@ -64,6 +74,7 @@ export class ReqlanValidator {
|
|
|
64
74
|
this.checkImportSyntax(model, accept);
|
|
65
75
|
this.checkImportTargets(model, accept);
|
|
66
76
|
this.checkFileReferenceTargets(model, accept);
|
|
77
|
+
this.checkCommentReferences(model, accept);
|
|
67
78
|
this.checkWildcardReferences(model, accept);
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -133,6 +144,8 @@ export class ReqlanValidator {
|
|
|
133
144
|
/**
|
|
134
145
|
* Duplicate check uses import *bindings* only (alias when present, otherwise the idea name).
|
|
135
146
|
* An aliased import does not reserve the imported base name for local ideas.
|
|
147
|
+
* Ideasets share the same name space as ideas in the file.
|
|
148
|
+
* rq:["../../../reqlan rq/language/syntax.rq".idea_name]
|
|
136
149
|
*/
|
|
137
150
|
checkDuplicateIdeaNames(model: Model, accept: ValidationAcceptor): void {
|
|
138
151
|
const seen = new Map<string, AstNode>();
|
|
@@ -142,7 +155,7 @@ export class ReqlanValidator {
|
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
157
|
for (const element of model.elements) {
|
|
145
|
-
if (!
|
|
158
|
+
if (!isIdeaDeclaration(element)) {
|
|
146
159
|
continue;
|
|
147
160
|
}
|
|
148
161
|
const name = element.name;
|
|
@@ -158,7 +171,7 @@ export class ReqlanValidator {
|
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
/**
|
|
161
|
-
* Error underline when an `import` / `from` path does not resolve to a file.
|
|
174
|
+
* Error underline when an `import` / `from` path does not resolve to a file or folder.
|
|
162
175
|
* Missing imported ideas are reported by the linker as unresolved references.
|
|
163
176
|
*/
|
|
164
177
|
checkImportTargets(model: Model, accept: ValidationAcceptor): void {
|
|
@@ -202,6 +215,14 @@ export class ReqlanValidator {
|
|
|
202
215
|
shared.workspace.FileSystemProvider,
|
|
203
216
|
pathResolveContextFromServices(this.services)
|
|
204
217
|
)) {
|
|
218
|
+
if (link.resolution === 'missing') {
|
|
219
|
+
accept('error', fileLinkMissingMessage(link.authoredPath ?? ''), {
|
|
220
|
+
node: model,
|
|
221
|
+
range: link.sourceRange,
|
|
222
|
+
code: FILE_REFERENCE_MISSING
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
205
226
|
if (!link.targetIssue) {
|
|
206
227
|
continue;
|
|
207
228
|
}
|
|
@@ -212,6 +233,23 @@ export class ReqlanValidator {
|
|
|
212
233
|
}
|
|
213
234
|
}
|
|
214
235
|
|
|
236
|
+
checkCommentReferences(model: Model, accept: ValidationAcceptor): void {
|
|
237
|
+
const document = AstUtils.getDocument(model);
|
|
238
|
+
const { shared } = this.services;
|
|
239
|
+
for (const issue of collectCommentReferenceIssues(
|
|
240
|
+
document,
|
|
241
|
+
shared.workspace.LangiumDocuments,
|
|
242
|
+
shared.workspace.FileSystemProvider,
|
|
243
|
+
pathResolveContextFromServices(this.services)
|
|
244
|
+
)) {
|
|
245
|
+
accept('error', issue.message, {
|
|
246
|
+
node: model,
|
|
247
|
+
range: issue.range,
|
|
248
|
+
code: issue.code
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
215
253
|
checkWildcardReferences(model: Model, accept: ValidationAcceptor): void {
|
|
216
254
|
const { shared } = this.services;
|
|
217
255
|
const context = pathResolveContextFromServices(this.services);
|
|
@@ -235,6 +273,12 @@ export class ReqlanValidator {
|
|
|
235
273
|
`No ideas match wildcard reference [${wildcardReferenceLabel(path, node.ideaPattern)}].`,
|
|
236
274
|
{ node }
|
|
237
275
|
);
|
|
276
|
+
} else if (matches.length === 1) {
|
|
277
|
+
accept(
|
|
278
|
+
'warning',
|
|
279
|
+
`Wildcard reference [${wildcardReferenceLabel(path, node.ideaPattern)}] matches only 1 idea.`,
|
|
280
|
+
{ node }
|
|
281
|
+
);
|
|
238
282
|
}
|
|
239
283
|
}
|
|
240
284
|
}
|
package/src/reqlan.langium
CHANGED
|
@@ -38,8 +38,11 @@ OneLinerIdea:
|
|
|
38
38
|
OneLinerBody:
|
|
39
39
|
content+=(MarkdownLink | BracketReference | WikiLink | OneLinerText)+;
|
|
40
40
|
|
|
41
|
+
// Bare [name] refs resolve to any of these. Collect all declarations before linking
|
|
42
|
+
// so a reference may target a name declared later in the same file.
|
|
43
|
+
// rq:["../../../reqlan rq/language/syntax.rq".reference_resolution_order]
|
|
41
44
|
IdeaDeclaration:
|
|
42
|
-
Idea | OneLinerIdea;
|
|
45
|
+
Idea | OneLinerIdea | IdeaSet;
|
|
43
46
|
|
|
44
47
|
Idea:
|
|
45
48
|
name=IdeaName '{'
|