@spyglassmc/core 0.2.0 → 0.3.0
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/lib/common/Dev.d.ts +11 -0
- package/lib/common/Dev.js +90 -0
- package/lib/{service → common}/Logger.d.ts +0 -0
- package/lib/{service → common}/Logger.js +0 -0
- package/lib/common/Operations.d.ts +12 -0
- package/lib/common/Operations.js +33 -0
- package/lib/common/ReadonlyProxy.d.ts +9 -0
- package/lib/common/ReadonlyProxy.js +23 -0
- package/lib/common/StateProxy.d.ts +35 -0
- package/lib/common/StateProxy.js +69 -0
- package/lib/common/externals/NodeJsExternals.js +1 -0
- package/lib/common/index.d.ts +6 -0
- package/lib/common/index.js +6 -0
- package/lib/common/util.d.ts +33 -5
- package/lib/common/util.js +43 -11
- package/lib/node/AstNode.d.ts +9 -8
- package/lib/node/CommentNode.d.ts +5 -4
- package/lib/node/CommentNode.js +4 -6
- package/lib/node/FileNode.d.ts +5 -1
- package/lib/node/FileNode.js +1 -1
- package/lib/node/FloatNode.d.ts +1 -1
- package/lib/node/IntegerNode.d.ts +1 -1
- package/lib/node/LiteralNode.d.ts +1 -1
- package/lib/node/LongNode.d.ts +1 -1
- package/lib/node/ResourceLocationNode.d.ts +3 -3
- package/lib/node/StringNode.d.ts +1 -1
- package/lib/node/SymbolNode.d.ts +1 -1
- package/lib/parser/resourceLocation.js +0 -8
- package/lib/parser/string.d.ts +2 -2
- package/lib/parser/symbol.js +1 -7
- package/lib/parser/util.js +0 -2
- package/lib/processor/InlayHintProvider.d.ts +2 -1
- package/lib/processor/SignatureHelpProvider.d.ts +2 -1
- package/lib/processor/binder/Binder.d.ts +26 -0
- package/lib/processor/binder/Binder.js +18 -0
- package/lib/processor/binder/builtin.d.ts +27 -0
- package/lib/processor/binder/builtin.js +116 -0
- package/lib/processor/binder/index.d.ts +3 -0
- package/lib/processor/binder/index.js +3 -0
- package/lib/processor/checker/builtin.js +5 -5
- package/lib/processor/colorizer/Colorizer.d.ts +2 -1
- package/lib/processor/completer/Completer.d.ts +2 -1
- package/lib/processor/completer/builtin.d.ts +3 -2
- package/lib/processor/completer/builtin.js +1 -1
- package/lib/processor/formatter/Formatter.d.ts +2 -1
- package/lib/processor/index.d.ts +1 -0
- package/lib/processor/index.js +1 -0
- package/lib/processor/linter/Linter.d.ts +2 -1
- package/lib/processor/linter/builtin.d.ts +2 -1
- package/lib/processor/util.d.ts +3 -13
- package/lib/processor/util.js +0 -10
- package/lib/service/CacheService.d.ts +2 -0
- package/lib/service/CacheService.js +3 -0
- package/lib/service/Context.d.ts +15 -12
- package/lib/service/Context.js +12 -6
- package/lib/service/Downloader.d.ts +1 -2
- package/lib/service/FileService.d.ts +1 -2
- package/lib/service/MetaRegistry.d.ts +8 -3
- package/lib/service/MetaRegistry.js +20 -1
- package/lib/service/Profiler.d.ts +3 -2
- package/lib/service/Profiler.js +58 -6
- package/lib/service/Project.d.ts +55 -24
- package/lib/service/Project.js +265 -166
- package/lib/service/Service.d.ts +1 -1
- package/lib/service/UriProcessor.d.ts +5 -0
- package/lib/service/UriProcessor.js +2 -0
- package/lib/service/index.d.ts +1 -2
- package/lib/service/index.js +1 -2
- package/lib/symbol/Symbol.d.ts +3 -2
- package/lib/symbol/SymbolUtil.d.ts +8 -7
- package/lib/symbol/SymbolUtil.js +31 -9
- package/lib/symbol/index.d.ts +0 -1
- package/lib/symbol/index.js +0 -1
- package/package.json +2 -2
- package/lib/service/Operations.d.ts +0 -8
- package/lib/service/Operations.js +0 -20
- package/lib/symbol/UriBinder.d.ts +0 -3
- package/lib/symbol/UriBinder.js +0 -2
package/lib/service/Project.js
CHANGED
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
8
|
-
import { bufferToString,
|
|
8
|
+
import { bufferToString, Logger, SingletonPromise, StateProxy } from '../common/index.js';
|
|
9
9
|
import { FileNode } from '../node/index.js';
|
|
10
10
|
import { file } from '../parser/index.js';
|
|
11
11
|
import { traversePreOrder } from '../processor/index.js';
|
|
@@ -13,30 +13,64 @@ import { Source } from '../source/index.js';
|
|
|
13
13
|
import { SymbolUtil } from '../symbol/index.js';
|
|
14
14
|
import { CacheService } from './CacheService.js';
|
|
15
15
|
import { ConfigService, LinterConfigValue } from './Config.js';
|
|
16
|
-
import { CheckerContext, LinterContext, ParserContext, UriBinderContext } from './Context.js';
|
|
16
|
+
import { BinderContext, CheckerContext, LinterContext, ParserContext, UriBinderContext } from './Context.js';
|
|
17
17
|
import { DependencyKey } from './Dependency.js';
|
|
18
18
|
import { Downloader } from './Downloader.js';
|
|
19
19
|
import { LinterErrorReporter } from './ErrorReporter.js';
|
|
20
20
|
import { ArchiveUriSupporter, FileService, FileUriSupporter } from './FileService.js';
|
|
21
21
|
import { fileUtil } from './fileUtil.js';
|
|
22
|
-
import { Logger } from './Logger.js';
|
|
23
22
|
import { MetaRegistry } from './MetaRegistry.js';
|
|
24
23
|
import { ProfilerFactory } from './Profiler.js';
|
|
25
24
|
const CacheAutoSaveInterval = 600000; // 10 Minutes.
|
|
26
25
|
/* istanbul ignore next */
|
|
27
26
|
/**
|
|
28
27
|
* Manage all tracked documents and errors.
|
|
28
|
+
*
|
|
29
|
+
* The four stages of processing a document:
|
|
30
|
+
* 1. `read` - read the file from the external file system as a `TextDocument`.
|
|
31
|
+
* 2. `parse` - Parse the `TextDocument` into an `AstNode`.
|
|
32
|
+
* 3. `bind` - Bind the `AstNode` and populate both the global symbol table and the local symbol tables on the nodes.
|
|
33
|
+
* 4. `check` (includes `lint`) - Check the `AstNode` with information from the symbol tables.
|
|
34
|
+
*
|
|
35
|
+
* **Caching**
|
|
36
|
+
*
|
|
37
|
+
* The global symbol table along with a list of file URIs and checksums is cached in memory and is periodically saved to disk.
|
|
38
|
+
*
|
|
39
|
+
* The `TextDocument`s and file `AstNode`s (including their local symbol tables) managed by the client are stored in memory until the client sends a `didClose` notification.
|
|
40
|
+
*
|
|
41
|
+
* Some `TextDocument`s may be cached to avoid excessive reading from the file system.
|
|
42
|
+
*
|
|
43
|
+
* **INIT and READY**
|
|
44
|
+
*
|
|
45
|
+
* When a new instance of the {@link Project} class is constructed, its INIT and READY processes are immediately started in serial.
|
|
46
|
+
*
|
|
47
|
+
* During the INIT process of the project, the config and language feature initialization are processed.
|
|
48
|
+
* The Promise returned by the {@link init} function resolves when the INIT process is complete.
|
|
49
|
+
*
|
|
50
|
+
* During the READY process of the project, the whole project is analyzed mainly to populate the global symbol table.
|
|
51
|
+
* The Promise returned by the {@link ready} function resolves when the READY process is complete.
|
|
52
|
+
*
|
|
53
|
+
* The following generally happens during the READY process:
|
|
54
|
+
* 1. A list of file URIs under the project is obtained.
|
|
55
|
+
* 2. The global symbol cache, if available, is loaded and validated against the know list of files.
|
|
56
|
+
* A list of files that need to be (re)processed is returned in this step.
|
|
57
|
+
* 3. For each files in the new list, the file is read, parsed, bound, and checked.
|
|
58
|
+
*
|
|
59
|
+
* **EDITING**
|
|
60
|
+
*
|
|
61
|
+
* After the READY process is complete, editing text documents as signaled by the client or the file watcher results in the file being re-processed.
|
|
29
62
|
*/
|
|
30
63
|
export class Project {
|
|
31
64
|
static RootSuffix = '/pack.mcmeta';
|
|
65
|
+
/** Prevent circular binding. */
|
|
66
|
+
#bindingInProgressUris = new Set();
|
|
32
67
|
#cacheSaverIntervalId;
|
|
33
68
|
cacheService;
|
|
34
|
-
/**
|
|
35
|
-
* URI of files that are currently managed by the language client.
|
|
36
|
-
*/
|
|
69
|
+
/** URI of files that are currently managed by the language client. */
|
|
37
70
|
#clientManagedUris = new Set();
|
|
71
|
+
#clientManagedDocAndNodes = new Map();
|
|
38
72
|
#configService;
|
|
39
|
-
#
|
|
73
|
+
#symbolUpToDateUris = new Set();
|
|
40
74
|
#eventEmitter;
|
|
41
75
|
#initializers;
|
|
42
76
|
#initPromise;
|
|
@@ -146,9 +180,9 @@ export class Project {
|
|
|
146
180
|
this.#cacheSaverIntervalId = setInterval(() => this.cacheService.save(), CacheAutoSaveInterval);
|
|
147
181
|
this
|
|
148
182
|
.on('documentUpdated', ({ doc, node }) => {
|
|
149
|
-
if (!this.#isReady) {
|
|
150
|
-
|
|
151
|
-
}
|
|
183
|
+
// if (!this.#isReady) {
|
|
184
|
+
// return
|
|
185
|
+
// }
|
|
152
186
|
this.emit('documentErrorred', {
|
|
153
187
|
doc,
|
|
154
188
|
errors: FileNode.getErrors(node),
|
|
@@ -159,33 +193,31 @@ export class Project {
|
|
|
159
193
|
if (uri.endsWith(Project.RootSuffix)) {
|
|
160
194
|
this.updateRoots();
|
|
161
195
|
}
|
|
162
|
-
this.
|
|
163
|
-
return this.
|
|
196
|
+
this.bindUri(uri);
|
|
197
|
+
return this.ensureBindingStarted(uri);
|
|
164
198
|
})
|
|
165
199
|
.on('fileModified', async ({ uri }) => {
|
|
200
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
166
201
|
if (this.isOnlyWatched(uri)) {
|
|
167
|
-
this
|
|
168
|
-
await this.ensureParsedAndChecked(uri);
|
|
202
|
+
await this.ensureBindingStarted(uri);
|
|
169
203
|
}
|
|
170
204
|
})
|
|
171
205
|
.on('fileDeleted', ({ uri }) => {
|
|
172
206
|
if (uri.endsWith(Project.RootSuffix)) {
|
|
173
207
|
this.updateRoots();
|
|
174
208
|
}
|
|
209
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
175
210
|
this.symbols.clear({ uri });
|
|
176
211
|
this.tryClearingCache(uri);
|
|
177
212
|
})
|
|
178
213
|
.on('ready', () => {
|
|
179
214
|
this.#isReady = true;
|
|
180
|
-
// Recheck client managed files.
|
|
181
|
-
const promises = []
|
|
182
|
-
for (const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
Promise.all(promises).catch(e => this.logger.error('[Project#ready] Error occurred when rechecking client managed files after ready', e));
|
|
215
|
+
// // Recheck client managed files after the READY process, as they may have incomplete results and are user-facing.
|
|
216
|
+
// const promises: Promise<unknown>[] = []
|
|
217
|
+
// for (const { doc, node } of this.#clientManagedDocAndNodes.values()) {
|
|
218
|
+
// promises.push(this.check(doc, node))
|
|
219
|
+
// }
|
|
220
|
+
// Promise.all(promises).catch(e => this.logger.error('[Project#ready] Error occurred when rechecking client managed files after READY', e))
|
|
189
221
|
});
|
|
190
222
|
}
|
|
191
223
|
setInitPromise() {
|
|
@@ -291,8 +323,6 @@ export class Project {
|
|
|
291
323
|
const ready = async () => {
|
|
292
324
|
await this.init();
|
|
293
325
|
const __profiler = this.profilers.get('project#ready');
|
|
294
|
-
const ensureParsed = this.ensureParsed.bind(this);
|
|
295
|
-
const ensureChecked = this.ensureChecked.bind(this);
|
|
296
326
|
await Promise.all([
|
|
297
327
|
listDependencyFiles(),
|
|
298
328
|
listProjectFiles(),
|
|
@@ -300,7 +330,7 @@ export class Project {
|
|
|
300
330
|
this.#dependencyFiles = new Set(this.fs.listFiles());
|
|
301
331
|
this.#dependencyRoots = new Set(this.fs.listRoots());
|
|
302
332
|
this.updateRoots();
|
|
303
|
-
__profiler.task('List
|
|
333
|
+
__profiler.task('List URIs');
|
|
304
334
|
for (const [id, { checksum, registrar }] of this.meta.symbolRegistrars) {
|
|
305
335
|
const cacheChecksum = this.cacheService.checksums.symbolRegistrars[id];
|
|
306
336
|
if (cacheChecksum === undefined || checksum !== cacheChecksum) {
|
|
@@ -321,24 +351,33 @@ export class Project {
|
|
|
321
351
|
}
|
|
322
352
|
__profiler.task('Validate Cache');
|
|
323
353
|
if (addedFiles.length > 0) {
|
|
324
|
-
this.
|
|
354
|
+
this.bindUri(addedFiles);
|
|
325
355
|
}
|
|
326
356
|
__profiler.task('Bind URIs');
|
|
327
|
-
const files = [...addedFiles, ...changedFiles];
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
357
|
+
const files = [...addedFiles, ...changedFiles].sort(this.meta.uriSorter);
|
|
358
|
+
__profiler.task('Sort URIs');
|
|
359
|
+
const __bindProfiler = this.profilers.get('project#ready#bind', 'top-n', 50);
|
|
360
|
+
for (const uri of files) {
|
|
361
|
+
await this.ensureBindingStarted(uri);
|
|
362
|
+
__bindProfiler.task(uri);
|
|
363
|
+
}
|
|
364
|
+
__bindProfiler.finalize();
|
|
365
|
+
__profiler.task('Bind Files');
|
|
366
|
+
__profiler.finalize();
|
|
334
367
|
this.emit('ready', {});
|
|
335
368
|
};
|
|
336
369
|
this.#readyPromise = ready();
|
|
337
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Load the config file and initialize parsers and processors.
|
|
373
|
+
*/
|
|
338
374
|
async init() {
|
|
339
375
|
await this.#initPromise;
|
|
340
376
|
return this;
|
|
341
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Finish the initial run of parsing, binding, and checking the entire project.
|
|
380
|
+
*/
|
|
342
381
|
async ready() {
|
|
343
382
|
await this.#readyPromise;
|
|
344
383
|
return this;
|
|
@@ -367,136 +406,182 @@ export class Project {
|
|
|
367
406
|
normalizeUri(uri) {
|
|
368
407
|
return this.fs.mapFromDisk(this.externals.uri.normalize(uri));
|
|
369
408
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
* @returns The cached `TextDocument` and `AstNode` for the URI, or `undefined` when such data isn't available in cache.
|
|
380
|
-
*/
|
|
381
|
-
get(uri) {
|
|
382
|
-
uri = this.normalizeUri(uri);
|
|
383
|
-
return this.#docAndNodes.get(uri);
|
|
409
|
+
static TextDocumentCacheMaxLength = 268435456;
|
|
410
|
+
#textDocumentCache = new Map();
|
|
411
|
+
#textDocumentCacheLength = 0;
|
|
412
|
+
removeCachedTextDocument(uri) {
|
|
413
|
+
const doc = this.#textDocumentCache.get(uri);
|
|
414
|
+
if (doc && !(doc instanceof Promise)) {
|
|
415
|
+
this.#textDocumentCacheLength -= doc.getText().length;
|
|
416
|
+
}
|
|
417
|
+
this.#textDocumentCache.delete(uri);
|
|
384
418
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
419
|
+
async read(uri) {
|
|
420
|
+
const getLanguageID = (uri) => {
|
|
421
|
+
const ext = fileUtil.extname(uri) ?? '.plaintext';
|
|
422
|
+
return this.meta.getLanguageID(ext) ?? ext.slice(1);
|
|
423
|
+
};
|
|
424
|
+
const createTextDocument = async (uri) => {
|
|
425
|
+
const languageId = getLanguageID(uri);
|
|
426
|
+
if (!this.meta.isSupportedLanguage(languageId)) {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
const content = bufferToString(await this.fs.readFile(uri));
|
|
431
|
+
return TextDocument.create(uri, languageId, -1, content);
|
|
432
|
+
}
|
|
433
|
+
catch (e) {
|
|
434
|
+
this.logger.warn(`[Project] [read] Failed creating TextDocument for “${uri}”`, e);
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const trimCache = () => {
|
|
439
|
+
const iterator = this.#textDocumentCache.keys();
|
|
440
|
+
while (this.#textDocumentCacheLength > Project.TextDocumentCacheMaxLength) {
|
|
441
|
+
const result = iterator.next();
|
|
442
|
+
if (result.done) {
|
|
443
|
+
throw new Error(`[Project] [read] Cache is too large with length ${this.#textDocumentCacheLength} even though it's empty; make sure to call 'removeCachedTextDocument()' instead of 'this.#textDocumentCache.delete()'`);
|
|
444
|
+
}
|
|
445
|
+
this.removeCachedTextDocument(result.value);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const getCacheHandlingPromise = async (uri) => {
|
|
449
|
+
if (this.#textDocumentCache.has(uri)) {
|
|
450
|
+
const ans = this.#textDocumentCache.get(uri);
|
|
451
|
+
// Move the entry to the end of the cache.
|
|
452
|
+
// The goal is that more-frequently-used entries are preferably not trimmed.
|
|
453
|
+
this.#textDocumentCache.delete(uri);
|
|
454
|
+
this.#textDocumentCache.set(uri, ans);
|
|
455
|
+
return ans;
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
const promise = createTextDocument(uri);
|
|
459
|
+
this.#textDocumentCache.set(uri, promise);
|
|
460
|
+
// We replace the Promise in the cache with the TextDocument after it resolves,
|
|
461
|
+
// or removes it from the cache if it resolves to undefined.
|
|
462
|
+
const doc = await promise;
|
|
463
|
+
if (this.#textDocumentCache.get(uri) === promise) {
|
|
464
|
+
// The Promise in the cache is the same as the one we created earlier.
|
|
465
|
+
// This check is to make sure we don't set a wrong TextDocument to the cache in case the cache was modified elsewhere.
|
|
466
|
+
if (doc) {
|
|
467
|
+
this.#textDocumentCache.set(uri, doc);
|
|
468
|
+
this.#textDocumentCacheLength += doc.getText().length;
|
|
469
|
+
trimCache();
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
this.#textDocumentCache.delete(uri);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return doc;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
389
478
|
uri = this.normalizeUri(uri);
|
|
390
|
-
if (this.#
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
const content = bufferToString(await this.fs.readFile(uri));
|
|
399
|
-
const doc = TextDocument.create(uri, languageID, -1, content);
|
|
400
|
-
return this.parseAndCache(doc);
|
|
401
|
-
}
|
|
402
|
-
catch (e) {
|
|
403
|
-
this.logger.warn(`[Project] [ensureParsed] Failed for “${uri}”`, e);
|
|
404
|
-
return undefined;
|
|
479
|
+
if (this.#clientManagedUris.has(uri)) {
|
|
480
|
+
const result = this.#clientManagedDocAndNodes.get(uri);
|
|
481
|
+
if (result) {
|
|
482
|
+
return result.doc;
|
|
483
|
+
}
|
|
484
|
+
throw new Error(`[Project] [read] Client-managed URI “${uri}” does not have a TextDocument in the cache`);
|
|
405
485
|
}
|
|
406
|
-
|
|
407
|
-
parseAndCache(doc) {
|
|
408
|
-
const node = this.parse(doc);
|
|
409
|
-
return this.cache(doc, node);
|
|
486
|
+
return getCacheHandlingPromise(uri);
|
|
410
487
|
}
|
|
411
488
|
parse(doc) {
|
|
412
489
|
const ctx = ParserContext.create(this, { doc });
|
|
413
490
|
const src = new Source(doc.getText());
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
ctx.symbols.contributeAs('parser', () => ans = file()(src, ctx));
|
|
417
|
-
return ans;
|
|
491
|
+
const node = file()(src, ctx);
|
|
492
|
+
return node;
|
|
418
493
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
494
|
+
async bind(doc, node) {
|
|
495
|
+
if (node.binderErrors) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
this.#bindingInProgressUris.add(doc.uri);
|
|
500
|
+
const binder = this.meta.getBinder(node.type);
|
|
501
|
+
const ctx = BinderContext.create(this, { doc });
|
|
502
|
+
ctx.symbols.clear({ contributor: 'binder', uri: doc.uri });
|
|
503
|
+
await ctx.symbols.contributeAsAsync('binder', async () => {
|
|
504
|
+
const proxy = StateProxy.create(node);
|
|
505
|
+
await binder(proxy, ctx);
|
|
506
|
+
node.binderErrors = ctx.err.dump();
|
|
507
|
+
});
|
|
508
|
+
this.#bindingInProgressUris.delete(doc.uri);
|
|
509
|
+
this.#symbolUpToDateUris.add(doc.uri);
|
|
510
|
+
}
|
|
511
|
+
catch (e) {
|
|
512
|
+
this.logger.error(`[Project] [bind] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
513
|
+
}
|
|
424
514
|
}
|
|
425
515
|
async check(doc, node) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
catch (e) {
|
|
442
|
-
this.logger.error(`[Project] [ensuredChecked] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
443
|
-
}
|
|
516
|
+
if (node.checkerErrors) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const checker = this.meta.getChecker(node.type);
|
|
521
|
+
const ctx = CheckerContext.create(this, { doc });
|
|
522
|
+
ctx.symbols.clear({ contributor: 'checker', uri: doc.uri });
|
|
523
|
+
await ctx.symbols.contributeAsAsync('checker', async () => {
|
|
524
|
+
await checker(StateProxy.create(node), ctx);
|
|
525
|
+
node.checkerErrors = ctx.err.dump();
|
|
526
|
+
this.lint(doc, node);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
this.logger.error(`[Project] [check] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
444
531
|
}
|
|
445
532
|
}
|
|
446
533
|
lint(doc, node) {
|
|
534
|
+
if (node.linterErrors) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
447
537
|
node.linterErrors = [];
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const { ruleSeverity, ruleValue } = result;
|
|
455
|
-
const { configValidator, linter, nodePredicate } = this.meta.getLinter(ruleName);
|
|
456
|
-
if (!configValidator(ruleName, ruleValue, this.logger)) {
|
|
457
|
-
// Config value is invalid.
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
const ctx = LinterContext.create(this, {
|
|
461
|
-
doc,
|
|
462
|
-
err: new LinterErrorReporter(ruleName, ruleSeverity),
|
|
463
|
-
ruleName,
|
|
464
|
-
ruleValue,
|
|
465
|
-
});
|
|
466
|
-
traversePreOrder(node, () => true, () => true, node => {
|
|
467
|
-
if (nodePredicate(node)) {
|
|
468
|
-
linter(node, ctx);
|
|
538
|
+
try {
|
|
539
|
+
for (const [ruleName, rawValue] of Object.entries(this.config.lint)) {
|
|
540
|
+
const result = LinterConfigValue.destruct(rawValue);
|
|
541
|
+
if (!result) {
|
|
542
|
+
// Rule is disabled (i.e. set to `null`) in the config.
|
|
543
|
+
continue;
|
|
469
544
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
545
|
+
const { ruleSeverity, ruleValue } = result;
|
|
546
|
+
const { configValidator, linter, nodePredicate } = this.meta.getLinter(ruleName);
|
|
547
|
+
if (!configValidator(ruleName, ruleValue, this.logger)) {
|
|
548
|
+
// Config value is invalid.
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const ctx = LinterContext.create(this, {
|
|
552
|
+
doc,
|
|
553
|
+
err: new LinterErrorReporter(ruleName, ruleSeverity),
|
|
554
|
+
ruleName,
|
|
555
|
+
ruleValue,
|
|
556
|
+
});
|
|
557
|
+
traversePreOrder(node, () => true, () => true, node => {
|
|
558
|
+
if (nodePredicate(node)) {
|
|
559
|
+
const proxy = StateProxy.create(node);
|
|
560
|
+
linter(proxy, ctx);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
node.linterErrors.push(...ctx.err.dump());
|
|
482
564
|
}
|
|
483
565
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const result = await this.ensureParsed(uri);
|
|
487
|
-
if (result) {
|
|
488
|
-
await this.ensureChecked(result.doc, result.node);
|
|
566
|
+
catch (e) {
|
|
567
|
+
this.logger.error(`[Project] [lint] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
489
568
|
}
|
|
490
|
-
return result;
|
|
491
569
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (this.#
|
|
495
|
-
|
|
570
|
+
// @SingletonPromise()
|
|
571
|
+
async ensureBindingStarted(uri) {
|
|
572
|
+
if (this.#symbolUpToDateUris.has(uri) || this.#bindingInProgressUris.has(uri)) {
|
|
573
|
+
return;
|
|
496
574
|
}
|
|
497
|
-
|
|
575
|
+
this.#bindingInProgressUris.add(uri);
|
|
576
|
+
const doc = await this.read(uri);
|
|
577
|
+
if (!doc || !(await this.cacheService.hasFileChangedSinceCache(doc))) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const node = this.parse(doc);
|
|
581
|
+
await this.bind(doc, node);
|
|
582
|
+
this.emit('documentUpdated', { doc, node });
|
|
498
583
|
}
|
|
499
|
-
|
|
584
|
+
bindUri(param) {
|
|
500
585
|
const ctx = UriBinderContext.create(this);
|
|
501
586
|
if (typeof param === 'string') {
|
|
502
587
|
ctx.symbols.clear({ contributor: 'uri_binder', uri: param });
|
|
@@ -511,35 +596,40 @@ export class Project {
|
|
|
511
596
|
/**
|
|
512
597
|
* Notify that a new document was opened in the editor.
|
|
513
598
|
*/
|
|
514
|
-
onDidOpen(uri, languageID, version, content) {
|
|
599
|
+
async onDidOpen(uri, languageID, version, content) {
|
|
515
600
|
uri = this.normalizeUri(uri);
|
|
516
601
|
if (!fileUtil.isFileUri(uri)) {
|
|
517
602
|
return; // We only accept `file:` scheme for client-managed URIs.
|
|
518
603
|
}
|
|
519
|
-
this.#clientManagedUris.add(uri);
|
|
520
604
|
const doc = TextDocument.create(uri, languageID, version, content);
|
|
521
|
-
const
|
|
605
|
+
const node = this.parse(doc);
|
|
606
|
+
this.#clientManagedUris.add(uri);
|
|
607
|
+
this.#clientManagedDocAndNodes.set(uri, { doc, node });
|
|
522
608
|
if (this.#isReady) {
|
|
523
|
-
this.
|
|
609
|
+
await this.bind(doc, node);
|
|
610
|
+
await this.check(doc, node);
|
|
524
611
|
}
|
|
525
612
|
}
|
|
526
613
|
/**
|
|
527
614
|
* Notify that an existing document was changed in the editor.
|
|
528
615
|
* @throws If there is no `TextDocument` corresponding to the URI.
|
|
529
616
|
*/
|
|
530
|
-
onDidChange(uri, changes, version) {
|
|
617
|
+
async onDidChange(uri, changes, version) {
|
|
618
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
531
619
|
uri = this.normalizeUri(uri);
|
|
532
620
|
if (!fileUtil.isFileUri(uri)) {
|
|
533
621
|
return; // We only accept `file:` scheme for client-managed URIs.
|
|
534
622
|
}
|
|
535
|
-
const
|
|
536
|
-
if (!
|
|
537
|
-
throw new Error(`
|
|
623
|
+
const doc = this.#clientManagedDocAndNodes.get(uri)?.doc;
|
|
624
|
+
if (!doc) {
|
|
625
|
+
throw new Error(`TextDocument for “${uri}” is ${!doc ? 'not cached' : 'a Promise'}. This should not happen. Did the language client send a didChange notification without sending a didOpen one, or is there a logic error on our side resulting the 'read' function overriding the 'TextDocument' created in the 'didOpen' notification handler?`);
|
|
538
626
|
}
|
|
539
|
-
TextDocument.update(
|
|
540
|
-
const
|
|
627
|
+
TextDocument.update(doc, changes, version);
|
|
628
|
+
const node = this.parse(doc);
|
|
629
|
+
this.#clientManagedDocAndNodes.set(uri, { doc, node });
|
|
541
630
|
if (this.#isReady) {
|
|
542
|
-
this.
|
|
631
|
+
await this.bind(doc, node);
|
|
632
|
+
await this.check(doc, node);
|
|
543
633
|
}
|
|
544
634
|
}
|
|
545
635
|
/**
|
|
@@ -551,8 +641,23 @@ export class Project {
|
|
|
551
641
|
return; // We only accept `file:` scheme for client-managed URIs.
|
|
552
642
|
}
|
|
553
643
|
this.#clientManagedUris.delete(uri);
|
|
644
|
+
this.#clientManagedDocAndNodes.delete(uri);
|
|
554
645
|
this.tryClearingCache(uri);
|
|
555
646
|
}
|
|
647
|
+
async ensureClientManagedChecked(uri) {
|
|
648
|
+
const result = this.#clientManagedDocAndNodes.get(uri);
|
|
649
|
+
if (result && this.#isReady) {
|
|
650
|
+
const { doc, node } = result;
|
|
651
|
+
await this.bind(doc, node);
|
|
652
|
+
await this.check(doc, node);
|
|
653
|
+
this.emit('documentUpdated', { doc, node });
|
|
654
|
+
return { doc, node };
|
|
655
|
+
}
|
|
656
|
+
return undefined;
|
|
657
|
+
}
|
|
658
|
+
getClientManaged(uri) {
|
|
659
|
+
return this.#clientManagedDocAndNodes.get(uri);
|
|
660
|
+
}
|
|
556
661
|
async showCacheRoot() {
|
|
557
662
|
if (!this.#cacheRoot) {
|
|
558
663
|
return;
|
|
@@ -566,7 +671,7 @@ export class Project {
|
|
|
566
671
|
}
|
|
567
672
|
tryClearingCache(uri) {
|
|
568
673
|
if (this.shouldRemove(uri)) {
|
|
569
|
-
this
|
|
674
|
+
this.removeCachedTextDocument(uri);
|
|
570
675
|
this.emit('documentRemoved', { uri });
|
|
571
676
|
}
|
|
572
677
|
}
|
|
@@ -578,18 +683,12 @@ export class Project {
|
|
|
578
683
|
}
|
|
579
684
|
}
|
|
580
685
|
__decorate([
|
|
581
|
-
|
|
582
|
-
], Project.prototype, "
|
|
686
|
+
SingletonPromise()
|
|
687
|
+
], Project.prototype, "bind", null);
|
|
583
688
|
__decorate([
|
|
584
|
-
|
|
689
|
+
SingletonPromise()
|
|
585
690
|
], Project.prototype, "check", null);
|
|
586
691
|
__decorate([
|
|
587
|
-
|
|
588
|
-
], Project.prototype, "
|
|
589
|
-
__decorate([
|
|
590
|
-
CachePromise()
|
|
591
|
-
], Project.prototype, "ensureParsedAndChecked", null);
|
|
592
|
-
__decorate([
|
|
593
|
-
CachePromise()
|
|
594
|
-
], Project.prototype, "ensureParsedAndCheckedOnlyWhenReady", null);
|
|
692
|
+
SingletonPromise()
|
|
693
|
+
], Project.prototype, "ensureClientManagedChecked", null);
|
|
595
694
|
//# sourceMappingURL=Project.js.map
|
package/lib/service/Service.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TextDocument } from 'vscode-languageserver-textdocument';
|
|
2
|
+
import type { Logger } from '../common/index.js';
|
|
2
3
|
import type { FileNode } from '../node/index.js';
|
|
3
4
|
import { AstNode } from '../node/index.js';
|
|
4
5
|
import type { Color, ColorInfo, ColorToken, InlayHint, SignatureHelp } from '../processor/index.js';
|
|
@@ -6,7 +7,6 @@ import { ColorPresentation } from '../processor/index.js';
|
|
|
6
7
|
import { Range } from '../source/index.js';
|
|
7
8
|
import type { SymbolUsageType } from '../symbol/index.js';
|
|
8
9
|
import { Hover } from './Hover.js';
|
|
9
|
-
import type { Logger } from './Logger.js';
|
|
10
10
|
import { ProfilerFactory } from './Profiler.js';
|
|
11
11
|
import type { ProjectOptions } from './Project.js';
|
|
12
12
|
import { Project } from './Project.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UriBinderContext } from './Context.js';
|
|
2
|
+
export declare type UriBinder = (uris: readonly string[], ctx: UriBinderContext) => void;
|
|
3
|
+
export declare type UriSorterRegistration = (this: void, a: string, b: string, next: UriSorter) => number;
|
|
4
|
+
export declare type UriSorter = (this: void, a: string, b: string) => number;
|
|
5
|
+
//# sourceMappingURL=UriProcessor.d.ts.map
|
package/lib/service/index.d.ts
CHANGED
|
@@ -7,12 +7,11 @@ export * from './ErrorReporter.js';
|
|
|
7
7
|
export { FileService, UriProtocolSupporter } from './FileService.js';
|
|
8
8
|
export * from './fileUtil.js';
|
|
9
9
|
export * from './Hover.js';
|
|
10
|
-
export * from './Logger.js';
|
|
11
10
|
export * from './MetaRegistry.js';
|
|
12
|
-
export * from './Operations.js';
|
|
13
11
|
export * from './Profiler.js';
|
|
14
12
|
export * from './Project.js';
|
|
15
13
|
export * from './Service.js';
|
|
16
14
|
export * from './SymbolLocations.js';
|
|
17
15
|
export * from './SymbolRegistrar.js';
|
|
16
|
+
export * from './UriProcessor.js';
|
|
18
17
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/service/index.js
CHANGED
|
@@ -8,12 +8,11 @@ export * from './ErrorReporter.js';
|
|
|
8
8
|
export { FileService } from './FileService.js';
|
|
9
9
|
export * from './fileUtil.js';
|
|
10
10
|
export * from './Hover.js';
|
|
11
|
-
export * from './Logger.js';
|
|
12
11
|
export * from './MetaRegistry.js';
|
|
13
|
-
export * from './Operations.js';
|
|
14
12
|
export * from './Profiler.js';
|
|
15
13
|
export * from './Project.js';
|
|
16
14
|
export * from './Service.js';
|
|
17
15
|
export * from './SymbolLocations.js';
|
|
18
16
|
export * from './SymbolRegistrar.js';
|
|
17
|
+
export * from './UriProcessor.js';
|
|
19
18
|
//# sourceMappingURL=index.js.map
|