@reqlan/language 1.7.0 → 1.8.2
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 +18 -0
- package/out/generated/ast.d.ts +28 -1
- package/out/generated/ast.js +27 -6
- 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 +1 -0
- package/out/index.js +1 -0
- package/out/index.js.map +1 -1
- package/out/reqlan-code-action-provider.d.ts +15 -12
- package/out/reqlan-code-action-provider.js +55 -20
- package/out/reqlan-code-action-provider.js.map +1 -1
- package/out/reqlan-comment-resolver.js +5 -0
- package/out/reqlan-comment-resolver.js.map +1 -1
- package/out/reqlan-completion-provider.d.ts +10 -0
- package/out/reqlan-completion-provider.js +63 -29
- package/out/reqlan-completion-provider.js.map +1 -1
- package/out/reqlan-definition-provider.d.ts +7 -0
- package/out/reqlan-definition-provider.js +86 -19
- package/out/reqlan-definition-provider.js.map +1 -1
- package/out/reqlan-document-link-provider.d.ts +2 -1
- package/out/reqlan-document-link-provider.js +4 -0
- package/out/reqlan-document-link-provider.js.map +1 -1
- package/out/reqlan-file-link-resolver.d.ts +17 -4
- package/out/reqlan-file-link-resolver.js +89 -14
- package/out/reqlan-file-link-resolver.js.map +1 -1
- package/out/reqlan-import-edits.d.ts +3 -1
- package/out/reqlan-import-edits.js.map +1 -1
- package/out/reqlan-inbound-reference-inlay-label.d.ts +11 -0
- package/out/reqlan-inbound-reference-inlay-label.js +60 -35
- package/out/reqlan-inbound-reference-inlay-label.js.map +1 -1
- package/out/reqlan-inlay-hint-provider.d.ts +6 -0
- package/out/reqlan-inlay-hint-provider.js +34 -6
- package/out/reqlan-inlay-hint-provider.js.map +1 -1
- package/out/reqlan-markdown-links.js +4 -0
- package/out/reqlan-markdown-links.js.map +1 -1
- package/out/reqlan-path-resolve.d.ts +8 -0
- package/out/reqlan-path-resolve.js +18 -4
- package/out/reqlan-path-resolve.js.map +1 -1
- package/out/reqlan-reference-search-site.d.ts +11 -0
- package/out/reqlan-reference-search-site.js +11 -0
- package/out/reqlan-reference-search-site.js.map +1 -1
- package/out/reqlan-scope.d.ts +11 -0
- package/out/reqlan-scope.js +28 -1
- package/out/reqlan-scope.js.map +1 -1
- package/out/reqlan-semantic-token-provider.js +6 -1
- package/out/reqlan-semantic-token-provider.js.map +1 -1
- package/out/reqlan-token-builder.d.ts +3 -2
- package/out/reqlan-token-builder.js +48 -1
- package/out/reqlan-token-builder.js.map +1 -1
- package/out/reqlan-validator.d.ts +1 -0
- package/out/reqlan-validator.js +21 -1
- package/out/reqlan-validator.js.map +1 -1
- package/out/reqlan-wildcard-resolve.d.ts +45 -0
- package/out/reqlan-wildcard-resolve.js +203 -0
- package/out/reqlan-wildcard-resolve.js.map +1 -0
- package/out/reqlan-workspace-manager.d.ts +7 -0
- package/out/reqlan-workspace-manager.js +7 -0
- package/out/reqlan-workspace-manager.js.map +1 -1
- package/package.json +1 -1
- package/src/generated/ast.ts +38 -7
- package/src/generated/grammar.ts +1 -1
- package/src/index.ts +1 -0
- package/src/reqlan-code-action-provider.ts +67 -32
- package/src/reqlan-comment-resolver.ts +5 -0
- package/src/reqlan-completion-provider.ts +81 -27
- package/src/reqlan-definition-provider.ts +91 -18
- package/src/reqlan-document-link-provider.ts +9 -1
- package/src/reqlan-file-link-resolver.ts +112 -17
- package/src/reqlan-import-edits.ts +3 -1
- package/src/reqlan-inbound-reference-inlay-label.ts +93 -38
- package/src/reqlan-inlay-hint-provider.ts +36 -6
- package/src/reqlan-markdown-links.ts +4 -0
- package/src/reqlan-path-resolve.ts +28 -5
- package/src/reqlan-reference-search-site.ts +25 -0
- package/src/reqlan-scope.ts +34 -1
- package/src/reqlan-semantic-token-provider.ts +6 -0
- package/src/reqlan-token-builder.ts +52 -3
- package/src/reqlan-validator.ts +34 -0
- package/src/reqlan-wildcard-resolve.ts +281 -0
- package/src/reqlan-workspace-manager.ts +9 -0
- package/src/reqlan.langium +14 -7
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve qualified path + idea-pattern wildcard references.
|
|
3
|
+
* rq:["../../../reqlan rq/language/imports.rq".wildcard_references]
|
|
4
|
+
*/
|
|
5
|
+
import type { LangiumDocument, LangiumDocuments } from 'langium';
|
|
6
|
+
import { AstUtils, CstUtils, GrammarUtils, URI, UriUtils } from 'langium';
|
|
7
|
+
import type { Range } from 'vscode-languageserver';
|
|
8
|
+
import {
|
|
9
|
+
isIdea,
|
|
10
|
+
isModel,
|
|
11
|
+
isOneLinerIdea,
|
|
12
|
+
isWildcardReference,
|
|
13
|
+
type IdeaDeclaration,
|
|
14
|
+
type WildcardReference
|
|
15
|
+
} from './generated/ast.js';
|
|
16
|
+
import {
|
|
17
|
+
matchImportRootMapping,
|
|
18
|
+
resolveImportRootUri,
|
|
19
|
+
resolveRqConfig,
|
|
20
|
+
type PathResolveContext
|
|
21
|
+
} from './reqlan-path-resolve.js';
|
|
22
|
+
import { unquoteReqlanString } from './reqlan-quoted-strings.js';
|
|
23
|
+
|
|
24
|
+
export const REQLAN_OPEN_WILDCARD_COMMAND = 'reqlan.openWildcardReference';
|
|
25
|
+
export const REQLAN_WILDCARD_REFERENCE_AT_REQUEST = 'reqlan/wildcardReferenceAt';
|
|
26
|
+
|
|
27
|
+
export interface WildcardMatch {
|
|
28
|
+
fileUri: string;
|
|
29
|
+
ideaName: string;
|
|
30
|
+
range?: Range;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Lightweight catalog entry for index-time expansion without full Langium docs. */
|
|
34
|
+
export interface WildcardIdeaCandidate {
|
|
35
|
+
fileUri: string;
|
|
36
|
+
/** Absolute or workspace path used for path-glob matching. */
|
|
37
|
+
filePath: string;
|
|
38
|
+
ideaName: string;
|
|
39
|
+
range?: Range;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WildcardReferenceArgs {
|
|
43
|
+
pathPattern: string;
|
|
44
|
+
ideaPattern: string;
|
|
45
|
+
fromUri: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function pathHasGlobMeta(path: string): boolean {
|
|
49
|
+
return /[*?]/.test(path) || path.includes('**');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ideaHasGlobMeta(name: string): boolean {
|
|
53
|
+
return /[*?]/.test(name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Convert a shell-style glob to an anchored RegExp. */
|
|
57
|
+
export function globToRegExp(glob: string, mode: 'path' | 'name' = 'name'): RegExp {
|
|
58
|
+
let pattern = '';
|
|
59
|
+
for (let i = 0; i < glob.length; i++) {
|
|
60
|
+
const ch = glob[i]!;
|
|
61
|
+
if (ch === '*' && glob[i + 1] === '*') {
|
|
62
|
+
// ** optionally followed by / matches across path segments
|
|
63
|
+
if (glob[i + 2] === '/') {
|
|
64
|
+
pattern += '(?:.*/)?';
|
|
65
|
+
i += 2;
|
|
66
|
+
} else {
|
|
67
|
+
pattern += '.*';
|
|
68
|
+
i += 1;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === '*') {
|
|
73
|
+
pattern += mode === 'path' ? '[^/]*' : '.*';
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (ch === '?') {
|
|
77
|
+
pattern += mode === 'path' ? '[^/]' : '.';
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if ('\\^$+{}[]()|/.'.includes(ch)) {
|
|
81
|
+
pattern += `\\${ch}`;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
pattern += ch;
|
|
85
|
+
}
|
|
86
|
+
return new RegExp(`^${pattern}$`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function wildcardReferenceCommandTarget(args: WildcardReferenceArgs): string {
|
|
90
|
+
return `command:${REQLAN_OPEN_WILDCARD_COMMAND}?${encodeURIComponent(JSON.stringify([args]))}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function wildcardReferenceLabel(pathPattern: string, ideaPattern: string): string {
|
|
94
|
+
return `${pathPattern}.${ideaPattern}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseWildcardPathPattern(pathPatternQuoted: string): string {
|
|
98
|
+
return unquoteReqlanString(pathPatternQuoted);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the path glob against the document directory / import-root alias,
|
|
103
|
+
* returning a regex that matches absolute file paths (posix-style).
|
|
104
|
+
*/
|
|
105
|
+
export function pathPatternToAbsoluteRegex(
|
|
106
|
+
pathPatternQuoted: string,
|
|
107
|
+
document: LangiumDocument,
|
|
108
|
+
context?: PathResolveContext
|
|
109
|
+
): RegExp {
|
|
110
|
+
const raw = parseWildcardPathPattern(pathPatternQuoted);
|
|
111
|
+
const config = resolveRqConfig(document, context);
|
|
112
|
+
const matched = matchImportRootMapping(raw, config.importRoots);
|
|
113
|
+
let absoluteGlob: string;
|
|
114
|
+
if (matched) {
|
|
115
|
+
const importRoot = resolveImportRootUri(document, context, matched.mapping);
|
|
116
|
+
const rootPath = importRoot
|
|
117
|
+
? uriToPath(importRoot)
|
|
118
|
+
: uriToPath(UriUtils.dirname(document.uri));
|
|
119
|
+
absoluteGlob = joinGlobPath(rootPath, matched.remainder);
|
|
120
|
+
} else if (raw.startsWith('/') || /^[A-Za-z]:[\\/]/.test(raw)) {
|
|
121
|
+
absoluteGlob = normalizePathSeparators(raw);
|
|
122
|
+
} else {
|
|
123
|
+
absoluteGlob = joinGlobPath(uriToPath(UriUtils.dirname(document.uri)), raw);
|
|
124
|
+
}
|
|
125
|
+
return globToRegExp(normalizePathSeparators(absoluteGlob), 'path');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function collectIdeaCandidatesFromDocuments(
|
|
129
|
+
documents: Iterable<LangiumDocument>
|
|
130
|
+
): WildcardIdeaCandidate[] {
|
|
131
|
+
const candidates: WildcardIdeaCandidate[] = [];
|
|
132
|
+
for (const document of documents) {
|
|
133
|
+
const model = document.parseResult.value;
|
|
134
|
+
if (!isModel(model)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const fileUri = document.uri.toString();
|
|
138
|
+
const filePath = uriToPath(document.uri);
|
|
139
|
+
for (const idea of collectIdeas(model)) {
|
|
140
|
+
const nameNode = GrammarUtils.findNodeForProperty(idea.$cstNode, 'name');
|
|
141
|
+
candidates.push({
|
|
142
|
+
fileUri,
|
|
143
|
+
filePath,
|
|
144
|
+
ideaName: idea.name,
|
|
145
|
+
range: nameNode?.range
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return candidates;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function matchWildcardAgainstCatalog(
|
|
153
|
+
pathPatternQuoted: string,
|
|
154
|
+
ideaPattern: string,
|
|
155
|
+
document: LangiumDocument,
|
|
156
|
+
candidates: readonly WildcardIdeaCandidate[],
|
|
157
|
+
context?: PathResolveContext
|
|
158
|
+
): WildcardMatch[] {
|
|
159
|
+
const pathRegex = pathPatternToAbsoluteRegex(pathPatternQuoted, document, context);
|
|
160
|
+
const ideaRegex = globToRegExp(ideaPattern, 'name');
|
|
161
|
+
const matches: WildcardMatch[] = [];
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
const path = normalizePathSeparators(candidate.filePath);
|
|
164
|
+
if (!pathRegex.test(path)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (!ideaRegex.test(candidate.ideaName)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
matches.push({
|
|
171
|
+
fileUri: candidate.fileUri,
|
|
172
|
+
ideaName: candidate.ideaName,
|
|
173
|
+
range: candidate.range
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return matches.sort((left, right) => {
|
|
177
|
+
const byFile = left.fileUri.localeCompare(right.fileUri);
|
|
178
|
+
return byFile !== 0 ? byFile : left.ideaName.localeCompare(right.ideaName);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function resolveWildcardReferenceMatches(
|
|
183
|
+
reference: WildcardReference,
|
|
184
|
+
documents: LangiumDocuments | Iterable<LangiumDocument>,
|
|
185
|
+
context?: PathResolveContext
|
|
186
|
+
): WildcardMatch[] {
|
|
187
|
+
const document = AstUtils.getDocument(reference);
|
|
188
|
+
const iterable = isLangiumDocuments(documents) ? documents.all : documents;
|
|
189
|
+
const candidates = collectIdeaCandidatesFromDocuments(iterable);
|
|
190
|
+
return matchWildcardAgainstCatalog(
|
|
191
|
+
reference.pathPattern,
|
|
192
|
+
reference.ideaPattern,
|
|
193
|
+
document,
|
|
194
|
+
candidates,
|
|
195
|
+
context
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function wildcardArgsFromReference(reference: WildcardReference): WildcardReferenceArgs {
|
|
200
|
+
const document = AstUtils.getDocument(reference);
|
|
201
|
+
return {
|
|
202
|
+
pathPattern: parseWildcardPathPattern(reference.pathPattern),
|
|
203
|
+
ideaPattern: reference.ideaPattern,
|
|
204
|
+
fromUri: document.uri.toString()
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function findWildcardReferenceAtPosition(
|
|
209
|
+
document: LangiumDocument,
|
|
210
|
+
offset: number
|
|
211
|
+
): WildcardReference | undefined {
|
|
212
|
+
const root = document.parseResult.value.$cstNode;
|
|
213
|
+
if (!root) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
let current = CstUtils.findLeafNodeAtOffset(root, offset)?.astNode;
|
|
217
|
+
while (current) {
|
|
218
|
+
if (isWildcardReference(current)) {
|
|
219
|
+
return current;
|
|
220
|
+
}
|
|
221
|
+
if ('target' in current && isWildcardReference((current as { target?: unknown }).target)) {
|
|
222
|
+
return (current as { target: WildcardReference }).target;
|
|
223
|
+
}
|
|
224
|
+
current = current.$container;
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function collectIdeas(model: { elements?: unknown[] }): IdeaDeclaration[] {
|
|
230
|
+
const ideas: IdeaDeclaration[] = [];
|
|
231
|
+
for (const node of AstUtils.streamAst(model as Parameters<typeof AstUtils.streamAst>[0])) {
|
|
232
|
+
if (isIdea(node) || isOneLinerIdea(node)) {
|
|
233
|
+
ideas.push(node);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return ideas;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isLangiumDocuments(value: unknown): value is LangiumDocuments {
|
|
240
|
+
return typeof value === 'object' && value !== null && 'all' in value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function uriToPath(uri: URI): string {
|
|
244
|
+
if (uri.scheme === 'file') {
|
|
245
|
+
return normalizePathSeparators(uri.fsPath ?? uri.path);
|
|
246
|
+
}
|
|
247
|
+
return normalizePathSeparators(uri.path);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function normalizePathSeparators(path: string): string {
|
|
251
|
+
return path.replace(/\\/g, '/');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function joinGlobPath(base: string, relative: string): string {
|
|
255
|
+
const left = normalizePathSeparators(base).replace(/\/+$/, '');
|
|
256
|
+
const right = normalizePathSeparators(relative).replace(/^\/+/, '');
|
|
257
|
+
return normalizeGlobPath(`${left}/${right}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Collapse `.` / `..` segments without expanding glob metacharacters. */
|
|
261
|
+
function normalizeGlobPath(path: string): string {
|
|
262
|
+
const absolute = path.startsWith('/');
|
|
263
|
+
const parts = path.split('/');
|
|
264
|
+
const out: string[] = [];
|
|
265
|
+
for (const part of parts) {
|
|
266
|
+
if (!part || part === '.') {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (part === '..') {
|
|
270
|
+
if (out.length > 0 && out[out.length - 1] !== '..' && !/[*?]/.test(out[out.length - 1]!)) {
|
|
271
|
+
out.pop();
|
|
272
|
+
} else if (!absolute) {
|
|
273
|
+
out.push(part);
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
out.push(part);
|
|
278
|
+
}
|
|
279
|
+
const joined = out.join('/');
|
|
280
|
+
return absolute ? `/${joined}` : joined;
|
|
281
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Stream,
|
|
6
6
|
type URI
|
|
7
7
|
} from 'langium';
|
|
8
|
+
import type { RqConfig } from './reqlan-path-resolve.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Loads workspace documents independently so one catastrophic parse failure
|
|
@@ -13,6 +14,13 @@ import {
|
|
|
13
14
|
* rq:["../../../reqlan rq/language/parser_lexer.rq".parse_budget_timeout]
|
|
14
15
|
*/
|
|
15
16
|
export class ReqlanWorkspaceManager extends DefaultWorkspaceManager {
|
|
17
|
+
/**
|
|
18
|
+
* Per-directory cache for resolved `.reqlan/config.json` results.
|
|
19
|
+
* Keyed by directory URI string. `undefined` values mean the directory tree has no `.reqlan/` base.
|
|
20
|
+
* Cleared on workspace reinitialisation so config changes take effect on the next reload.
|
|
21
|
+
*/
|
|
22
|
+
readonly rqConfigCache: Map<string, RqConfig | undefined> = new Map();
|
|
23
|
+
|
|
16
24
|
constructor(services: LangiumSharedCoreServices) {
|
|
17
25
|
super(services);
|
|
18
26
|
}
|
|
@@ -21,6 +29,7 @@ export class ReqlanWorkspaceManager extends DefaultWorkspaceManager {
|
|
|
21
29
|
uris: Stream<URI>,
|
|
22
30
|
collector: (document: LangiumDocument) => void
|
|
23
31
|
): Promise<void> {
|
|
32
|
+
this.rqConfigCache.clear();
|
|
24
33
|
await Promise.all(
|
|
25
34
|
uris.map(async uri => {
|
|
26
35
|
try {
|
package/src/reqlan.langium
CHANGED
|
@@ -80,11 +80,11 @@ CodeSnippet:
|
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
81
|
|
|
82
82
|
OneLinerText returns string:
|
|
83
|
-
WORD | ID | NUMBER | INLINE_CODE | OTHER | PlainPunctuation | '(' | ')' | '|' | 'from' | 'import' | 'as' | '`';
|
|
83
|
+
WORD | ID | NUMBER | INLINE_CODE | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'from' | 'import' | 'as' | '`';
|
|
84
84
|
|
|
85
85
|
// PlainText must not include INLINE_CODE — that terminal is its own RichTextPart/InlineTextPart alt.
|
|
86
86
|
PlainText returns string:
|
|
87
|
-
WORD | ID | NUMBER | OTHER | PlainPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
87
|
+
WORD | ID | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
88
88
|
|
|
89
89
|
PlainPunctuation returns string:
|
|
90
90
|
'.' | ',' | ':' | ';' | '!' | '?' | '-';
|
|
@@ -113,7 +113,7 @@ NonBangInlineTextPart:
|
|
|
113
113
|
|
|
114
114
|
// No '!' (negated flag) and no leading ':' (attribute separator).
|
|
115
115
|
NonBangPlainText returns string:
|
|
116
|
-
WORD | ID | NUMBER | OTHER | NonBangPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
116
|
+
WORD | ID | NUMBER | OTHER | WILDCARD_NAME | NonBangPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
|
|
117
117
|
|
|
118
118
|
NonBangPunctuation returns string:
|
|
119
119
|
'.' | ',' | ';' | '?' | '-';
|
|
@@ -152,7 +152,7 @@ ListItemBody:
|
|
|
152
152
|
content+=(MarkdownLink | BracketReference | WikiLink | ListItemText)+;
|
|
153
153
|
|
|
154
154
|
ListItemText returns string:
|
|
155
|
-
WORD | ID | NUMBER | INLINE_CODE | OTHER | ListItemPunctuation | '(' | '|' | 'from' | 'import' | 'as' | '`';
|
|
155
|
+
WORD | ID | NUMBER | INLINE_CODE | OTHER | WILDCARD_NAME | ListItemPunctuation | '(' | '|' | 'from' | 'import' | 'as' | '`';
|
|
156
156
|
|
|
157
157
|
ListItemPunctuation returns string:
|
|
158
158
|
'.' | ':' | ';' | '!' | '?' | '-';
|
|
@@ -180,12 +180,13 @@ BracketReference:
|
|
|
180
180
|
'[' target=ReferenceTarget ']';
|
|
181
181
|
|
|
182
182
|
// Order and shape chosen so alternatives do not share a common LL prefix:
|
|
183
|
+
// - WildcardReference is STRING.WILDCARD_NAME (idea pattern must contain * or ?)
|
|
183
184
|
// - QualifiedReference covers STRING.ID / ID.ID (incl. idea.attribute surface form)
|
|
184
185
|
// - FileSymbolReference only for STRING.ID.ID.ID+ (3+ symbol segments)
|
|
185
186
|
// - FileReference is bare STRING
|
|
186
187
|
// - LocalReference is a bare local name
|
|
187
188
|
ReferenceTarget:
|
|
188
|
-
QualifiedReference | FileSymbolReference | FileReference | LocalReference;
|
|
189
|
+
WildcardReference | QualifiedReference | FileSymbolReference | FileReference | LocalReference;
|
|
189
190
|
|
|
190
191
|
FileReference:
|
|
191
192
|
file=STRING;
|
|
@@ -193,6 +194,10 @@ FileReference:
|
|
|
193
194
|
FileSymbolReference:
|
|
194
195
|
file=STRING '.' symbols+=ID '.' symbols+=ID '.' symbols+=ID ('.' symbols+=ID)*;
|
|
195
196
|
|
|
197
|
+
// Path may include globs (* ** ?); ideaPattern must include * or ? so exact qualified refs stay QualifiedReference.
|
|
198
|
+
WildcardReference:
|
|
199
|
+
pathPattern=STRING '.' ideaPattern=WILDCARD_NAME;
|
|
200
|
+
|
|
196
201
|
QualifiedReference:
|
|
197
202
|
(path=[Import:STRING] | qualifier=[Import:ID]) '.'
|
|
198
203
|
(ideaset=[IdeaSet:ReferenceName] '.')? idea=[IdeaDeclaration:ReferenceName];
|
|
@@ -234,10 +239,10 @@ InvalidFromImport:
|
|
|
234
239
|
'from' head=InvalidImportHeadToken tokens+=InvalidImportToken*;
|
|
235
240
|
|
|
236
241
|
InvalidImportHeadToken returns string:
|
|
237
|
-
ID | WORD | NUMBER | OTHER | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
242
|
+
ID | WORD | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
238
243
|
|
|
239
244
|
InvalidImportToken returns string:
|
|
240
|
-
ID | STRING | WORD | NUMBER | OTHER | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
245
|
+
ID | STRING | WORD | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
|
|
241
246
|
|
|
242
247
|
// ---------------------------------------------------------------------------
|
|
243
248
|
// comments, string_literals
|
|
@@ -250,6 +255,8 @@ hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;
|
|
|
250
255
|
|
|
251
256
|
terminal MARKDOWN_LINK: /\[(?!\[)[^\]]+\]\([^)]+\)/;
|
|
252
257
|
terminal ID: /[_a-zA-Z][\w-]*/;
|
|
258
|
+
// Must contain * or ? so it does not steal ordinary ID tokens.
|
|
259
|
+
terminal WILDCARD_NAME: /\*[\w*?-]*|\?[\w*?-]*|[_a-zA-Z][\w-]*[*?][\w*?-]*/;
|
|
253
260
|
terminal STRING: /"(\\.|[^"\\\r\n])*"|'(\\.|[^'\\\r\n])*'/;
|
|
254
261
|
terminal NUMBER: /\d+/;
|
|
255
262
|
terminal WORD: /[A-Za-z_][\w']*/;
|