@reqlan/language 1.6.2 → 1.8.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 (109) hide show
  1. package/README.md +18 -0
  2. package/out/generated/ast.d.ts +31 -4
  3. package/out/generated/ast.js +28 -7
  4. package/out/generated/ast.js.map +1 -1
  5. package/out/generated/grammar.js +2 -3318
  6. package/out/generated/grammar.js.map +1 -1
  7. package/out/generated/module.d.ts +2 -2
  8. package/out/generated/module.js +2 -2
  9. package/out/generated/module.js.map +1 -1
  10. package/out/index.d.ts +4 -0
  11. package/out/index.js +4 -0
  12. package/out/index.js.map +1 -1
  13. package/out/reqlan-async-parser.d.ts +37 -0
  14. package/out/reqlan-async-parser.js +117 -0
  15. package/out/reqlan-async-parser.js.map +1 -0
  16. package/out/reqlan-code-action-provider.d.ts +15 -12
  17. package/out/reqlan-code-action-provider.js +55 -20
  18. package/out/reqlan-code-action-provider.js.map +1 -1
  19. package/out/reqlan-comment-resolver.js +5 -0
  20. package/out/reqlan-comment-resolver.js.map +1 -1
  21. package/out/reqlan-completion-provider.d.ts +10 -0
  22. package/out/reqlan-completion-provider.js +64 -30
  23. package/out/reqlan-completion-provider.js.map +1 -1
  24. package/out/reqlan-definition-provider.d.ts +7 -0
  25. package/out/reqlan-definition-provider.js +86 -19
  26. package/out/reqlan-definition-provider.js.map +1 -1
  27. package/out/reqlan-document-factory.d.ts +16 -0
  28. package/out/reqlan-document-factory.js +50 -0
  29. package/out/reqlan-document-factory.js.map +1 -0
  30. package/out/reqlan-document-link-provider.d.ts +2 -1
  31. package/out/reqlan-document-link-provider.js +4 -0
  32. package/out/reqlan-document-link-provider.js.map +1 -1
  33. package/out/reqlan-file-link-resolver.d.ts +17 -4
  34. package/out/reqlan-file-link-resolver.js +89 -14
  35. package/out/reqlan-file-link-resolver.js.map +1 -1
  36. package/out/reqlan-import-edits.d.ts +3 -1
  37. package/out/reqlan-import-edits.js.map +1 -1
  38. package/out/reqlan-inbound-reference-inlay-label.d.ts +11 -0
  39. package/out/reqlan-inbound-reference-inlay-label.js +60 -35
  40. package/out/reqlan-inbound-reference-inlay-label.js.map +1 -1
  41. package/out/reqlan-inlay-hint-provider.d.ts +6 -0
  42. package/out/reqlan-inlay-hint-provider.js +34 -6
  43. package/out/reqlan-inlay-hint-provider.js.map +1 -1
  44. package/out/reqlan-linker.js +2 -2
  45. package/out/reqlan-linker.js.map +1 -1
  46. package/out/reqlan-markdown-links.js +4 -0
  47. package/out/reqlan-markdown-links.js.map +1 -1
  48. package/out/reqlan-module.d.ts +2 -1
  49. package/out/reqlan-module.js +12 -2
  50. package/out/reqlan-module.js.map +1 -1
  51. package/out/reqlan-parse-budget.d.ts +50 -0
  52. package/out/reqlan-parse-budget.js +89 -0
  53. package/out/reqlan-parse-budget.js.map +1 -0
  54. package/out/reqlan-parse-worker.d.ts +1 -0
  55. package/out/reqlan-parse-worker.js +34 -0
  56. package/out/reqlan-parse-worker.js.map +1 -0
  57. package/out/reqlan-path-resolve.d.ts +8 -0
  58. package/out/reqlan-path-resolve.js +18 -4
  59. package/out/reqlan-path-resolve.js.map +1 -1
  60. package/out/reqlan-reference-search-site.d.ts +11 -0
  61. package/out/reqlan-reference-search-site.js +11 -0
  62. package/out/reqlan-reference-search-site.js.map +1 -1
  63. package/out/reqlan-scope.d.ts +11 -0
  64. package/out/reqlan-scope.js +28 -1
  65. package/out/reqlan-scope.js.map +1 -1
  66. package/out/reqlan-semantic-token-provider.js +6 -1
  67. package/out/reqlan-semantic-token-provider.js.map +1 -1
  68. package/out/reqlan-token-builder.d.ts +3 -2
  69. package/out/reqlan-token-builder.js +150 -26
  70. package/out/reqlan-token-builder.js.map +1 -1
  71. package/out/reqlan-validator.d.ts +10 -1
  72. package/out/reqlan-validator.js +36 -2
  73. package/out/reqlan-validator.js.map +1 -1
  74. package/out/reqlan-wildcard-resolve.d.ts +45 -0
  75. package/out/reqlan-wildcard-resolve.js +203 -0
  76. package/out/reqlan-wildcard-resolve.js.map +1 -0
  77. package/out/reqlan-workspace-manager.d.ts +18 -0
  78. package/out/reqlan-workspace-manager.js +31 -0
  79. package/out/reqlan-workspace-manager.js.map +1 -0
  80. package/package.json +1 -1
  81. package/src/generated/ast.ts +41 -10
  82. package/src/generated/grammar.ts +2 -3318
  83. package/src/generated/module.ts +2 -2
  84. package/src/index.ts +4 -0
  85. package/src/reqlan-async-parser.ts +178 -0
  86. package/src/reqlan-code-action-provider.ts +67 -32
  87. package/src/reqlan-comment-resolver.ts +5 -0
  88. package/src/reqlan-completion-provider.ts +82 -28
  89. package/src/reqlan-definition-provider.ts +91 -18
  90. package/src/reqlan-document-factory.ts +75 -0
  91. package/src/reqlan-document-link-provider.ts +9 -1
  92. package/src/reqlan-file-link-resolver.ts +112 -17
  93. package/src/reqlan-import-edits.ts +3 -1
  94. package/src/reqlan-inbound-reference-inlay-label.ts +93 -38
  95. package/src/reqlan-inlay-hint-provider.ts +36 -6
  96. package/src/reqlan-linker.ts +4 -2
  97. package/src/reqlan-markdown-links.ts +4 -0
  98. package/src/reqlan-module.ts +15 -3
  99. package/src/reqlan-parse-budget.ts +155 -0
  100. package/src/reqlan-parse-worker.ts +35 -0
  101. package/src/reqlan-path-resolve.ts +28 -5
  102. package/src/reqlan-reference-search-site.ts +25 -0
  103. package/src/reqlan-scope.ts +34 -1
  104. package/src/reqlan-semantic-token-provider.ts +6 -0
  105. package/src/reqlan-token-builder.ts +174 -28
  106. package/src/reqlan-validator.ts +56 -1
  107. package/src/reqlan-wildcard-resolve.ts +281 -0
  108. package/src/reqlan-workspace-manager.ts +47 -0
  109. package/src/reqlan.langium +17 -8
@@ -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
+ }
@@ -0,0 +1,47 @@
1
+ import {
2
+ DefaultWorkspaceManager,
3
+ type LangiumDocument,
4
+ type LangiumSharedCoreServices,
5
+ type Stream,
6
+ type URI
7
+ } from 'langium';
8
+ import type { RqConfig } from './reqlan-path-resolve.js';
9
+
10
+ /**
11
+ * Loads workspace documents independently so one catastrophic parse failure
12
+ * (for example Chevrotain recovery stack overflow) cannot abort LSP workspace init.
13
+ * rq:["../../../reqlan rq/language/syntax.rq".no_name_idea_safe_warning]
14
+ * rq:["../../../reqlan rq/language/parser_lexer.rq".parse_budget_timeout]
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
+
24
+ constructor(services: LangiumSharedCoreServices) {
25
+ super(services);
26
+ }
27
+
28
+ protected override async loadWorkspaceDocuments(
29
+ uris: Stream<URI>,
30
+ collector: (document: LangiumDocument) => void
31
+ ): Promise<void> {
32
+ this.rqConfigCache.clear();
33
+ await Promise.all(
34
+ uris.map(async uri => {
35
+ try {
36
+ const document = await this.langiumDocuments.getOrCreateDocument(uri);
37
+ collector(document);
38
+ } catch (error) {
39
+ console.error(
40
+ `[reqlan] Skipping document that failed to load during workspace init: ${uri.toString()}`,
41
+ error
42
+ );
43
+ }
44
+ })
45
+ );
46
+ }
47
+ }
@@ -17,8 +17,10 @@ entry Model:
17
17
  (NL* elements+=TopLevelElement)*
18
18
  NL*;
19
19
 
20
+ // Nameless `{ ... }` at top level is recoverable (no_name_idea_safe_warning):
21
+ // parse it, warn, and keep later ideas/imports intact — do not abort the file.
20
22
  TopLevelElement:
21
- Idea | IdeaSet | OneLinerIdea;
23
+ Idea | IdeaSet | OneLinerIdea | AnonymousBlock;
22
24
 
23
25
  // ---------------------------------------------------------------------------
24
26
  // idea_name, one_liner_idea, block_idea, ideaset
@@ -78,11 +80,11 @@ CodeSnippet:
78
80
  // ---------------------------------------------------------------------------
79
81
 
80
82
  OneLinerText returns string:
81
- WORD | ID | NUMBER | INLINE_CODE | OTHER | PlainPunctuation | '(' | ')' | '|' | 'from' | 'import' | 'as' | '`';
83
+ WORD | ID | NUMBER | INLINE_CODE | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'from' | 'import' | 'as' | '`';
82
84
 
83
85
  // PlainText must not include INLINE_CODE — that terminal is its own RichTextPart/InlineTextPart alt.
84
86
  PlainText returns string:
85
- WORD | ID | NUMBER | OTHER | PlainPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
87
+ WORD | ID | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
86
88
 
87
89
  PlainPunctuation returns string:
88
90
  '.' | ',' | ':' | ';' | '!' | '?' | '-';
@@ -111,7 +113,7 @@ NonBangInlineTextPart:
111
113
 
112
114
  // No '!' (negated flag) and no leading ':' (attribute separator).
113
115
  NonBangPlainText returns string:
114
- WORD | ID | NUMBER | OTHER | NonBangPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
116
+ WORD | ID | NUMBER | OTHER | WILDCARD_NAME | NonBangPunctuation | '[' | ']' | '|' | 'from' | 'import' | 'as' | '`';
115
117
 
116
118
  NonBangPunctuation returns string:
117
119
  '.' | ',' | ';' | '?' | '-';
@@ -150,7 +152,7 @@ ListItemBody:
150
152
  content+=(MarkdownLink | BracketReference | WikiLink | ListItemText)+;
151
153
 
152
154
  ListItemText returns string:
153
- WORD | ID | NUMBER | INLINE_CODE | OTHER | ListItemPunctuation | '(' | '|' | 'from' | 'import' | 'as' | '`';
155
+ WORD | ID | NUMBER | INLINE_CODE | OTHER | WILDCARD_NAME | ListItemPunctuation | '(' | '|' | 'from' | 'import' | 'as' | '`';
154
156
 
155
157
  ListItemPunctuation returns string:
156
158
  '.' | ':' | ';' | '!' | '?' | '-';
@@ -178,12 +180,13 @@ BracketReference:
178
180
  '[' target=ReferenceTarget ']';
179
181
 
180
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 ?)
181
184
  // - QualifiedReference covers STRING.ID / ID.ID (incl. idea.attribute surface form)
182
185
  // - FileSymbolReference only for STRING.ID.ID.ID+ (3+ symbol segments)
183
186
  // - FileReference is bare STRING
184
187
  // - LocalReference is a bare local name
185
188
  ReferenceTarget:
186
- QualifiedReference | FileSymbolReference | FileReference | LocalReference;
189
+ WildcardReference | QualifiedReference | FileSymbolReference | FileReference | LocalReference;
187
190
 
188
191
  FileReference:
189
192
  file=STRING;
@@ -191,6 +194,10 @@ FileReference:
191
194
  FileSymbolReference:
192
195
  file=STRING '.' symbols+=ID '.' symbols+=ID '.' symbols+=ID ('.' symbols+=ID)*;
193
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
+
194
201
  QualifiedReference:
195
202
  (path=[Import:STRING] | qualifier=[Import:ID]) '.'
196
203
  (ideaset=[IdeaSet:ReferenceName] '.')? idea=[IdeaDeclaration:ReferenceName];
@@ -232,10 +239,10 @@ InvalidFromImport:
232
239
  'from' head=InvalidImportHeadToken tokens+=InvalidImportToken*;
233
240
 
234
241
  InvalidImportHeadToken returns string:
235
- ID | WORD | NUMBER | OTHER | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
242
+ ID | WORD | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
236
243
 
237
244
  InvalidImportToken returns string:
238
- ID | STRING | WORD | NUMBER | OTHER | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
245
+ ID | STRING | WORD | NUMBER | OTHER | WILDCARD_NAME | PlainPunctuation | '(' | ')' | '|' | 'as' | 'import' | 'from' | '`' | '[' | ']';
239
246
 
240
247
  // ---------------------------------------------------------------------------
241
248
  // comments, string_literals
@@ -248,6 +255,8 @@ hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;
248
255
 
249
256
  terminal MARKDOWN_LINK: /\[(?!\[)[^\]]+\]\([^)]+\)/;
250
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*?-]*/;
251
260
  terminal STRING: /"(\\.|[^"\\\r\n])*"|'(\\.|[^'\\\r\n])*'/;
252
261
  terminal NUMBER: /\d+/;
253
262
  terminal WORD: /[A-Za-z_][\w']*/;