@spyglassmc/core 0.1.2 → 0.4.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/README.md +7 -0
- package/lib/browser.d.ts +2 -0
- package/lib/browser.js +2 -0
- 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 +2 -5
- 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/TwoWayMap.js +4 -10
- package/lib/common/externals/BrowserExternals.d.ts +3 -0
- package/lib/common/externals/BrowserExternals.js +191 -0
- package/lib/common/externals/NodeJsExternals.d.ts +3 -0
- package/lib/common/externals/NodeJsExternals.js +152 -0
- package/lib/common/externals/downloader.d.ts +31 -0
- package/lib/common/externals/downloader.js +32 -0
- package/lib/common/externals/index.d.ts +96 -0
- package/lib/common/externals/index.js +2 -0
- package/lib/common/index.d.ts +8 -1
- package/lib/common/index.js +8 -17
- package/lib/common/util.d.ts +48 -20
- package/lib/common/util.js +71 -86
- package/lib/index.d.ts +7 -7
- package/lib/index.js +7 -23
- package/lib/node/AstNode.d.ts +12 -11
- package/lib/node/AstNode.js +10 -16
- package/lib/node/BooleanNode.d.ts +2 -2
- package/lib/node/BooleanNode.js +4 -7
- package/lib/node/CommentNode.d.ts +6 -5
- package/lib/node/CommentNode.js +4 -9
- package/lib/node/ErrorNode.d.ts +1 -1
- package/lib/node/ErrorNode.js +2 -5
- package/lib/node/FileNode.d.ts +8 -4
- package/lib/node/FileNode.js +3 -6
- package/lib/node/FloatNode.d.ts +3 -3
- package/lib/node/FloatNode.js +4 -7
- package/lib/node/IntegerNode.d.ts +3 -3
- package/lib/node/IntegerNode.js +4 -7
- package/lib/node/ListNode.d.ts +2 -2
- package/lib/node/ListNode.js +2 -5
- package/lib/node/LiteralNode.d.ts +4 -4
- package/lib/node/LiteralNode.js +4 -7
- package/lib/node/LongNode.d.ts +3 -3
- package/lib/node/LongNode.js +4 -7
- package/lib/node/RecordNode.d.ts +2 -2
- package/lib/node/RecordNode.js +2 -5
- package/lib/node/ResourceLocationNode.d.ts +7 -7
- package/lib/node/ResourceLocationNode.js +9 -12
- package/lib/node/Sequence.d.ts +2 -2
- package/lib/node/Sequence.js +4 -7
- package/lib/node/StringNode.d.ts +5 -5
- package/lib/node/StringNode.js +9 -12
- package/lib/node/SymbolNode.d.ts +4 -4
- package/lib/node/SymbolNode.js +4 -7
- package/lib/node/index.d.ts +15 -15
- package/lib/node/index.js +15 -31
- package/lib/nodejs.d.ts +2 -0
- package/lib/nodejs.js +2 -0
- package/lib/parser/Parser.d.ts +3 -3
- package/lib/parser/Parser.js +1 -4
- package/lib/parser/boolean.d.ts +2 -2
- package/lib/parser/boolean.js +3 -6
- package/lib/parser/comment.d.ts +2 -2
- package/lib/parser/comment.js +5 -9
- package/lib/parser/empty.d.ts +1 -1
- package/lib/parser/empty.js +1 -5
- package/lib/parser/error.d.ts +2 -2
- package/lib/parser/error.js +5 -9
- package/lib/parser/file.d.ts +3 -3
- package/lib/parser/file.js +9 -13
- package/lib/parser/float.d.ts +4 -4
- package/lib/parser/float.js +12 -16
- package/lib/parser/index.d.ts +16 -16
- package/lib/parser/index.js +16 -38
- package/lib/parser/integer.d.ts +4 -4
- package/lib/parser/integer.js +10 -14
- package/lib/parser/list.d.ts +2 -2
- package/lib/parser/list.js +15 -19
- package/lib/parser/literal.d.ts +2 -2
- package/lib/parser/literal.js +5 -9
- package/lib/parser/long.d.ts +4 -4
- package/lib/parser/long.js +10 -14
- package/lib/parser/record.d.ts +3 -3
- package/lib/parser/record.js +20 -24
- package/lib/parser/resourceLocation.d.ts +2 -2
- package/lib/parser/resourceLocation.js +12 -24
- package/lib/parser/string.d.ts +7 -7
- package/lib/parser/string.js +36 -42
- package/lib/parser/symbol.d.ts +3 -3
- package/lib/parser/symbol.js +4 -14
- package/lib/parser/util.d.ts +6 -5
- package/lib/parser/util.js +40 -60
- package/lib/processor/ColorInfoProvider.d.ts +1 -1
- package/lib/processor/ColorInfoProvider.js +6 -9
- package/lib/processor/InlayHintProvider.d.ts +4 -3
- package/lib/processor/InlayHintProvider.js +1 -2
- package/lib/processor/SignatureHelpProvider.d.ts +4 -3
- package/lib/processor/SignatureHelpProvider.js +1 -2
- 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/Checker.d.ts +2 -2
- package/lib/processor/checker/Checker.js +1 -5
- package/lib/processor/checker/builtin.d.ts +4 -4
- package/lib/processor/checker/builtin.js +22 -33
- package/lib/processor/checker/index.d.ts +2 -2
- package/lib/processor/checker/index.js +2 -31
- package/lib/processor/colorizer/Colorizer.d.ts +6 -5
- package/lib/processor/colorizer/Colorizer.js +8 -11
- package/lib/processor/colorizer/builtin.d.ts +3 -3
- package/lib/processor/colorizer/builtin.js +34 -46
- package/lib/processor/colorizer/index.d.ts +2 -2
- package/lib/processor/colorizer/index.js +2 -31
- package/lib/processor/completer/Completer.d.ts +6 -5
- package/lib/processor/completer/Completer.js +14 -33
- package/lib/processor/completer/builtin.d.ts +10 -9
- package/lib/processor/completer/builtin.js +47 -63
- package/lib/processor/completer/index.d.ts +2 -2
- package/lib/processor/completer/index.js +2 -31
- package/lib/processor/formatter/Formatter.d.ts +4 -3
- package/lib/processor/formatter/Formatter.js +2 -7
- package/lib/processor/formatter/builtin.d.ts +3 -3
- package/lib/processor/formatter/builtin.js +22 -36
- package/lib/processor/formatter/index.d.ts +2 -2
- package/lib/processor/formatter/index.js +2 -31
- package/lib/processor/index.d.ts +10 -9
- package/lib/processor/index.js +10 -25
- package/lib/processor/linter/Linter.d.ts +4 -3
- package/lib/processor/linter/Linter.js +1 -2
- package/lib/processor/linter/builtin/undeclaredSymbol.d.ts +2 -2
- package/lib/processor/linter/builtin/undeclaredSymbol.js +20 -24
- package/lib/processor/linter/builtin.d.ts +4 -3
- package/lib/processor/linter/builtin.js +19 -26
- package/lib/processor/linter/index.d.ts +2 -2
- package/lib/processor/linter/index.js +2 -31
- package/lib/processor/util.d.ts +4 -14
- package/lib/processor/util.js +1 -16
- package/lib/service/CacheService.d.ts +13 -8
- package/lib/service/CacheService.js +45 -57
- package/lib/service/Config.d.ts +11 -12
- package/lib/service/Config.js +50 -45
- package/lib/service/Context.d.ts +23 -20
- package/lib/service/Context.js +39 -36
- package/lib/service/Dependency.js +2 -5
- package/lib/service/Downloader.d.ts +9 -40
- package/lib/service/Downloader.js +37 -110
- package/lib/service/ErrorReporter.d.ts +2 -2
- package/lib/service/ErrorReporter.js +10 -14
- package/lib/service/FileService.d.ts +15 -14
- package/lib/service/FileService.js +55 -92
- package/lib/service/Hover.d.ts +2 -2
- package/lib/service/Hover.js +4 -7
- package/lib/service/MetaRegistry.d.ts +22 -12
- package/lib/service/MetaRegistry.js +81 -73
- package/lib/service/Profiler.d.ts +3 -2
- package/lib/service/Profiler.js +81 -45
- package/lib/service/Project.d.ts +105 -76
- package/lib/service/Project.js +449 -370
- package/lib/service/Service.d.ts +17 -27
- package/lib/service/Service.js +37 -43
- package/lib/service/SymbolLocations.d.ts +3 -3
- package/lib/service/SymbolLocations.js +4 -7
- package/lib/service/SymbolRegistrar.d.ts +1 -1
- package/lib/service/SymbolRegistrar.js +1 -2
- package/lib/service/UriProcessor.d.ts +5 -0
- package/lib/service/UriProcessor.js +2 -0
- package/lib/service/fileUtil.d.ts +15 -45
- package/lib/service/fileUtil.js +38 -143
- package/lib/service/index.d.ts +16 -17
- package/lib/service/index.js +16 -35
- package/lib/source/IndexMap.d.ts +1 -1
- package/lib/source/IndexMap.js +7 -10
- package/lib/source/LanguageError.d.ts +20 -6
- package/lib/source/LanguageError.js +16 -9
- package/lib/source/Location.d.ts +3 -3
- package/lib/source/Location.js +6 -9
- package/lib/source/Offset.d.ts +1 -1
- package/lib/source/Offset.js +4 -7
- package/lib/source/Position.js +2 -5
- package/lib/source/PositionRange.d.ts +3 -3
- package/lib/source/PositionRange.js +17 -20
- package/lib/source/Range.d.ts +1 -1
- package/lib/source/Range.js +7 -10
- package/lib/source/Source.d.ts +5 -3
- package/lib/source/Source.js +34 -29
- package/lib/source/index.d.ts +8 -8
- package/lib/source/index.js +8 -24
- package/lib/symbol/Symbol.d.ts +5 -4
- package/lib/symbol/Symbol.js +49 -65
- package/lib/symbol/SymbolUtil.d.ts +29 -31
- package/lib/symbol/SymbolUtil.js +178 -157
- package/lib/symbol/index.d.ts +2 -3
- package/lib/symbol/index.js +2 -19
- package/package.json +7 -4
- package/lib/service/Operations.d.ts +0 -8
- package/lib/service/Operations.js +0 -26
- package/lib/symbol/UriBinder.d.ts +0 -3
- package/lib/symbol/UriBinder.js +0 -3
package/lib/service/Project.js
CHANGED
|
@@ -1,180 +1,99 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
6
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const chokidar_1 = __importDefault(require("chokidar"));
|
|
26
|
-
const events_1 = __importDefault(require("events"));
|
|
27
|
-
const p_limit_1 = __importDefault(require("p-limit"));
|
|
28
|
-
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
29
|
-
const _1 = require(".");
|
|
30
|
-
const common_1 = require("../common");
|
|
31
|
-
const node_1 = require("../node");
|
|
32
|
-
const parser_1 = require("../parser");
|
|
33
|
-
const processor_1 = require("../processor");
|
|
34
|
-
const source_1 = require("../source");
|
|
35
|
-
const symbol_1 = require("../symbol");
|
|
36
|
-
const CacheService_1 = require("./CacheService");
|
|
37
|
-
const Config_1 = require("./Config");
|
|
38
|
-
const Context_1 = require("./Context");
|
|
39
|
-
const Dependency_1 = require("./Dependency");
|
|
40
|
-
const Downloader_1 = require("./Downloader");
|
|
41
|
-
const ErrorReporter_1 = require("./ErrorReporter");
|
|
42
|
-
const FileService_1 = require("./FileService");
|
|
43
|
-
const fileUtil_1 = require("./fileUtil");
|
|
44
|
-
const Logger_1 = require("./Logger");
|
|
45
|
-
const MetaRegistry_1 = require("./MetaRegistry");
|
|
46
|
-
const Profiler_1 = require("./Profiler");
|
|
7
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
8
|
+
import { bufferToString, Logger, SingletonPromise, StateProxy } from '../common/index.js';
|
|
9
|
+
import { FileNode } from '../node/index.js';
|
|
10
|
+
import { file } from '../parser/index.js';
|
|
11
|
+
import { traversePreOrder } from '../processor/index.js';
|
|
12
|
+
import { LanguageError, Source } from '../source/index.js';
|
|
13
|
+
import { SymbolUtil } from '../symbol/index.js';
|
|
14
|
+
import { CacheService } from './CacheService.js';
|
|
15
|
+
import { ConfigService, LinterConfigValue } from './Config.js';
|
|
16
|
+
import { BinderContext, CheckerContext, LinterContext, ParserContext, UriBinderContext } from './Context.js';
|
|
17
|
+
import { DependencyKey } from './Dependency.js';
|
|
18
|
+
import { Downloader } from './Downloader.js';
|
|
19
|
+
import { LinterErrorReporter } from './ErrorReporter.js';
|
|
20
|
+
import { ArchiveUriSupporter, FileService, FileUriSupporter } from './FileService.js';
|
|
21
|
+
import { fileUtil } from './fileUtil.js';
|
|
22
|
+
import { MetaRegistry } from './MetaRegistry.js';
|
|
23
|
+
import { ProfilerFactory } from './Profiler.js';
|
|
47
24
|
const CacheAutoSaveInterval = 600000; // 10 Minutes.
|
|
48
|
-
var ProjectData;
|
|
49
|
-
(function (ProjectData) {
|
|
50
|
-
function mock(data = {}) {
|
|
51
|
-
const cacheRoot = data.cacheRoot ?? '/some/random/garbage/path/that/definitely/does/not/exist';
|
|
52
|
-
const logger = data.logger ?? Logger_1.Logger.create();
|
|
53
|
-
const downloader = data.downloader ?? new Downloader_1.Downloader(cacheRoot, logger, _1.LowLevelDownloader.mock({ fixtures: {} }));
|
|
54
|
-
return {
|
|
55
|
-
cacheRoot,
|
|
56
|
-
config: data.config ?? Config_1.VanillaConfig,
|
|
57
|
-
ctx: data.ctx ?? {},
|
|
58
|
-
downloader,
|
|
59
|
-
ensureParsedAndChecked: data.ensureParsedAndChecked,
|
|
60
|
-
fs: data.fs ?? FileService_1.FileService.create('file:///cache/'),
|
|
61
|
-
get: data.get ?? (() => undefined),
|
|
62
|
-
logger,
|
|
63
|
-
meta: data.meta ?? new MetaRegistry_1.MetaRegistry(),
|
|
64
|
-
profilers: data.profilers ?? Profiler_1.ProfilerFactory.noop(),
|
|
65
|
-
projectRoot: data.projectRoot ?? 'file:///',
|
|
66
|
-
roots: data.roots ?? [],
|
|
67
|
-
symbols: data.symbols ?? new symbol_1.SymbolUtil({}),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
ProjectData.mock = mock;
|
|
71
|
-
})(ProjectData = exports.ProjectData || (exports.ProjectData = {}));
|
|
72
25
|
/* istanbul ignore next */
|
|
73
26
|
/**
|
|
74
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.
|
|
75
62
|
*/
|
|
76
|
-
class Project
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
_Project_dependencyFiles.set(this, void 0);
|
|
96
|
-
_Project_roots.set(this, []
|
|
97
|
-
/**
|
|
98
|
-
* All tracked root URIs. Each URI in this array is guaranteed to end with a slash (`/`).
|
|
99
|
-
*
|
|
100
|
-
* Includes the roots of all dependencies, the project root, and all data pack roots identified
|
|
101
|
-
* by `pack.mcmeta` files.
|
|
102
|
-
*
|
|
103
|
-
* Some URIs in the array may overlap with each other. In such cases, the deeper ones are guaranteed to come
|
|
104
|
-
* before the shallower ones (e.g. `file:///foo/bar/` will come before `file:///foo/`).
|
|
105
|
-
*/
|
|
106
|
-
);
|
|
107
|
-
_Project_ctx.set(this, void 0);
|
|
108
|
-
_Project_cacheRoot.set(this, void 0);
|
|
109
|
-
__classPrivateFieldSet(this, _Project_cacheRoot, cacheRoot, "f");
|
|
110
|
-
this.cacheService = new CacheService_1.CacheService(cacheRoot, this);
|
|
111
|
-
__classPrivateFieldSet(this, _Project_configService, new Config_1.ConfigService(this), "f");
|
|
112
|
-
this.downloader = downloader ?? new Downloader_1.Downloader(cacheRoot, logger);
|
|
113
|
-
this.fs = fs;
|
|
114
|
-
__classPrivateFieldSet(this, _Project_initializers, initializers, "f");
|
|
115
|
-
this.logger = logger;
|
|
116
|
-
this.profilers = profilers;
|
|
117
|
-
this.projectPath = projectPath;
|
|
118
|
-
this.projectRoot = fileUtil_1.fileUtil.ensureEndingSlash(fileUtil_1.fileUtil.pathToFileUri(projectPath));
|
|
119
|
-
this.symbols = new symbol_1.SymbolUtil({});
|
|
120
|
-
__classPrivateFieldSet(this, _Project_ctx, {}, "f");
|
|
121
|
-
this.logger.info(`[Project] [init] cacheRoot = “${cacheRoot}”`);
|
|
122
|
-
__classPrivateFieldGet(this, _Project_configService, "f")
|
|
123
|
-
.on('changed', ({ config }) => {
|
|
124
|
-
this.config = config;
|
|
125
|
-
this.logger.info('[Project] [Config] Changed');
|
|
126
|
-
})
|
|
127
|
-
.on('error', ({ error, uri }) => this.logger.error(`[Project] [Config] Failed loading “${uri}”`, error));
|
|
128
|
-
this.setInitPromise();
|
|
129
|
-
this.setReadyPromise();
|
|
130
|
-
__classPrivateFieldSet(this, _Project_cacheSaverIntervalId, setInterval(() => this.cacheService.save(), CacheAutoSaveInterval), "f");
|
|
131
|
-
this
|
|
132
|
-
.on('documentUpdated', ({ doc, node }) => {
|
|
133
|
-
if (!__classPrivateFieldGet(this, _Project_isReady, "f")) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
this.emit('documentErrorred', {
|
|
137
|
-
doc,
|
|
138
|
-
errors: node_1.FileNode.getErrors(node),
|
|
139
|
-
node,
|
|
140
|
-
});
|
|
141
|
-
})
|
|
142
|
-
.on('fileCreated', async ({ uri }) => {
|
|
143
|
-
if (uri.endsWith(Project.RootSuffix)) {
|
|
144
|
-
this.updateRoots();
|
|
145
|
-
}
|
|
146
|
-
this.bind(uri);
|
|
147
|
-
return this.ensureParsedAndChecked(uri);
|
|
148
|
-
})
|
|
149
|
-
.on('fileModified', async ({ uri }) => {
|
|
150
|
-
if (this.isOnlyWatched(uri)) {
|
|
151
|
-
__classPrivateFieldGet(this, _Project_docAndNodes, "f").delete(uri);
|
|
152
|
-
await this.ensureParsedAndChecked(uri);
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
.on('fileDeleted', ({ uri }) => {
|
|
156
|
-
if (uri.endsWith(Project.RootSuffix)) {
|
|
157
|
-
this.updateRoots();
|
|
158
|
-
}
|
|
159
|
-
this.symbols.clear({ uri });
|
|
160
|
-
this.tryClearingCache(uri);
|
|
161
|
-
})
|
|
162
|
-
.on('ready', () => {
|
|
163
|
-
__classPrivateFieldSet(this, _Project_isReady, true, "f");
|
|
164
|
-
// Recheck client managed files.
|
|
165
|
-
const promises = [];
|
|
166
|
-
for (const uri of __classPrivateFieldGet(this, _Project_clientManagedUris, "f")) {
|
|
167
|
-
const result = __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
|
|
168
|
-
if (result) {
|
|
169
|
-
promises.push(this.check(result.doc, result.node));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
Promise.all(promises).catch(e => this.logger.error('[Project#ready] Error occurred when rechecking client managed files after ready', e));
|
|
173
|
-
});
|
|
174
|
-
}
|
|
63
|
+
export class Project {
|
|
64
|
+
static RootSuffix = '/pack.mcmeta';
|
|
65
|
+
/** Prevent circular binding. */
|
|
66
|
+
#bindingInProgressUris = new Set();
|
|
67
|
+
#cacheSaverIntervalId;
|
|
68
|
+
cacheService;
|
|
69
|
+
/** URI of files that are currently managed by the language client. */
|
|
70
|
+
#clientManagedUris = new Set();
|
|
71
|
+
#clientManagedDocAndNodes = new Map();
|
|
72
|
+
#configService;
|
|
73
|
+
#symbolUpToDateUris = new Set();
|
|
74
|
+
#eventEmitter;
|
|
75
|
+
#initializers;
|
|
76
|
+
#initPromise;
|
|
77
|
+
#readyPromise;
|
|
78
|
+
#watchedFiles = new Set();
|
|
79
|
+
#watcher;
|
|
80
|
+
#watcherReady = false;
|
|
81
|
+
#isReady = false;
|
|
175
82
|
get isReady() {
|
|
176
|
-
return
|
|
177
|
-
}
|
|
83
|
+
return this.#isReady;
|
|
84
|
+
}
|
|
85
|
+
config;
|
|
86
|
+
downloader;
|
|
87
|
+
externals;
|
|
88
|
+
fs;
|
|
89
|
+
logger;
|
|
90
|
+
meta = new MetaRegistry();
|
|
91
|
+
profilers;
|
|
92
|
+
projectRoot;
|
|
93
|
+
symbols;
|
|
94
|
+
#dependencyRoots;
|
|
95
|
+
#dependencyFiles;
|
|
96
|
+
#roots = [];
|
|
178
97
|
/**
|
|
179
98
|
* All tracked root URIs. Each URI in this array is guaranteed to end with a slash (`/`).
|
|
180
99
|
*
|
|
@@ -185,22 +104,24 @@ class Project extends events_1.default {
|
|
|
185
104
|
* before the shallower ones (e.g. `file:///foo/bar/` will come before `file:///foo/`).
|
|
186
105
|
*/
|
|
187
106
|
get roots() {
|
|
188
|
-
return
|
|
107
|
+
return this.#roots;
|
|
189
108
|
}
|
|
109
|
+
#ctx;
|
|
190
110
|
/**
|
|
191
111
|
* Arbitrary information that will be included in the `project` property of all `Context`s.
|
|
192
112
|
*/
|
|
193
113
|
get ctx() {
|
|
194
|
-
return
|
|
114
|
+
return this.#ctx;
|
|
195
115
|
}
|
|
116
|
+
#cacheRoot;
|
|
196
117
|
/**
|
|
197
|
-
* File
|
|
118
|
+
* File URI to a directory where all cache files of Spyglass should be stored.
|
|
198
119
|
*/
|
|
199
120
|
get cacheRoot() {
|
|
200
|
-
return
|
|
121
|
+
return this.#cacheRoot;
|
|
201
122
|
}
|
|
202
123
|
updateRoots() {
|
|
203
|
-
const rawRoots = [...
|
|
124
|
+
const rawRoots = [...this.#dependencyRoots, this.projectRoot];
|
|
204
125
|
const ans = new Set(rawRoots);
|
|
205
126
|
// Identify roots indicated by `pack.mcmeta`.
|
|
206
127
|
for (const file of this.getTrackedFiles()) {
|
|
@@ -208,8 +129,19 @@ class Project extends events_1.default {
|
|
|
208
129
|
ans.add(file.slice(0, 1 - Project.RootSuffix.length));
|
|
209
130
|
}
|
|
210
131
|
}
|
|
211
|
-
|
|
212
|
-
this.emit('rootsUpdated', { roots:
|
|
132
|
+
this.#roots = [...ans].sort((a, b) => b.length - a.length);
|
|
133
|
+
this.emit('rootsUpdated', { roots: this.#roots });
|
|
134
|
+
}
|
|
135
|
+
on(event, callbackFn) {
|
|
136
|
+
this.#eventEmitter.on(event, callbackFn);
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
once(event, callbackFn) {
|
|
140
|
+
this.#eventEmitter.once(event, callbackFn);
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
emit(event, ...args) {
|
|
144
|
+
return this.#eventEmitter.emit(event, ...args);
|
|
213
145
|
}
|
|
214
146
|
/**
|
|
215
147
|
* Get all files that are tracked and supported.
|
|
@@ -219,38 +151,108 @@ class Project extends events_1.default {
|
|
|
219
151
|
*/
|
|
220
152
|
getTrackedFiles() {
|
|
221
153
|
const extensions = this.meta.getSupportedFileExtensions();
|
|
222
|
-
return [...
|
|
223
|
-
.filter(file => extensions.includes(
|
|
154
|
+
return [...this.#dependencyFiles, ...this.#watchedFiles]
|
|
155
|
+
.filter(file => extensions.includes(fileUtil.extname(file) ?? ''));
|
|
156
|
+
}
|
|
157
|
+
constructor({ cacheRoot, defaultConfig, downloader, externals, fs = FileService.create(externals, cacheRoot), initializers = [], logger = Logger.create(), profilers = ProfilerFactory.noop(), projectRoot, }) {
|
|
158
|
+
this.#cacheRoot = cacheRoot;
|
|
159
|
+
this.#eventEmitter = new externals.event.EventEmitter();
|
|
160
|
+
this.externals = externals;
|
|
161
|
+
this.fs = fs;
|
|
162
|
+
this.#initializers = initializers;
|
|
163
|
+
this.logger = logger;
|
|
164
|
+
this.profilers = profilers;
|
|
165
|
+
this.projectRoot = projectRoot;
|
|
166
|
+
this.cacheService = new CacheService(cacheRoot, this);
|
|
167
|
+
this.#configService = new ConfigService(this, defaultConfig);
|
|
168
|
+
this.downloader = downloader ?? new Downloader(cacheRoot, externals, logger);
|
|
169
|
+
this.symbols = new SymbolUtil({}, externals.event.EventEmitter);
|
|
170
|
+
this.#ctx = {};
|
|
171
|
+
this.logger.info(`[Project] [init] cacheRoot = “${cacheRoot}”`);
|
|
172
|
+
this.#configService
|
|
173
|
+
.on('changed', ({ config }) => {
|
|
174
|
+
this.config = config;
|
|
175
|
+
this.logger.info('[Project] [Config] Changed');
|
|
176
|
+
})
|
|
177
|
+
.on('error', ({ error, uri }) => this.logger.error(`[Project] [Config] Failed loading “${uri}”`, error));
|
|
178
|
+
this.setInitPromise();
|
|
179
|
+
this.setReadyPromise();
|
|
180
|
+
this.#cacheSaverIntervalId = setInterval(() => this.cacheService.save(), CacheAutoSaveInterval);
|
|
181
|
+
this
|
|
182
|
+
.on('documentUpdated', ({ doc, node }) => {
|
|
183
|
+
// if (!this.#isReady) {
|
|
184
|
+
// return
|
|
185
|
+
// }
|
|
186
|
+
this.emit('documentErrored', {
|
|
187
|
+
errors: FileNode.getErrors(node).map(e => LanguageError.withPosRange(e, doc)),
|
|
188
|
+
uri: doc.uri,
|
|
189
|
+
version: doc.version,
|
|
190
|
+
});
|
|
191
|
+
})
|
|
192
|
+
.on('documentRemoved', ({ uri }) => {
|
|
193
|
+
this.emit('documentErrored', { errors: [], uri });
|
|
194
|
+
})
|
|
195
|
+
.on('fileCreated', async ({ uri }) => {
|
|
196
|
+
if (uri.endsWith(Project.RootSuffix)) {
|
|
197
|
+
this.updateRoots();
|
|
198
|
+
}
|
|
199
|
+
this.bindUri(uri);
|
|
200
|
+
return this.ensureBindingStarted(uri);
|
|
201
|
+
})
|
|
202
|
+
.on('fileModified', async ({ uri }) => {
|
|
203
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
204
|
+
if (this.isOnlyWatched(uri)) {
|
|
205
|
+
await this.ensureBindingStarted(uri);
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
.on('fileDeleted', ({ uri }) => {
|
|
209
|
+
if (uri.endsWith(Project.RootSuffix)) {
|
|
210
|
+
this.updateRoots();
|
|
211
|
+
}
|
|
212
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
213
|
+
this.symbols.clear({ uri });
|
|
214
|
+
this.tryClearingCache(uri);
|
|
215
|
+
})
|
|
216
|
+
.on('ready', () => {
|
|
217
|
+
this.#isReady = true;
|
|
218
|
+
// // Recheck client managed files after the READY process, as they may have incomplete results and are user-facing.
|
|
219
|
+
// const promises: Promise<unknown>[] = []
|
|
220
|
+
// for (const { doc, node } of this.#clientManagedDocAndNodes.values()) {
|
|
221
|
+
// promises.push(this.check(doc, node))
|
|
222
|
+
// }
|
|
223
|
+
// Promise.all(promises).catch(e => this.logger.error('[Project#ready] Error occurred when rechecking client managed files after READY', e))
|
|
224
|
+
});
|
|
224
225
|
}
|
|
225
226
|
setInitPromise() {
|
|
226
227
|
const loadConfig = async () => {
|
|
227
|
-
this.config = await
|
|
228
|
+
this.config = await this.#configService.load();
|
|
228
229
|
};
|
|
229
230
|
const callIntializers = async () => {
|
|
230
231
|
const initCtx = {
|
|
231
232
|
cacheRoot: this.cacheRoot,
|
|
232
233
|
config: this.config,
|
|
233
234
|
downloader: this.downloader,
|
|
235
|
+
externals: this.externals,
|
|
234
236
|
logger: this.logger,
|
|
235
237
|
meta: this.meta,
|
|
236
238
|
projectRoot: this.projectRoot,
|
|
237
239
|
};
|
|
238
|
-
const results = await Promise.allSettled(
|
|
240
|
+
const results = await Promise.allSettled(this.#initializers.map(init => init(initCtx)));
|
|
239
241
|
let ctx = {};
|
|
240
242
|
results.forEach(async (r, i) => {
|
|
241
243
|
if (r.status === 'rejected') {
|
|
242
|
-
this.logger.error(`[Project] [callInitializers] [${i}] “${
|
|
244
|
+
this.logger.error(`[Project] [callInitializers] [${i}] “${this.#initializers[i].name}”`, r.reason);
|
|
243
245
|
}
|
|
244
246
|
else if (r.value) {
|
|
245
247
|
ctx = { ...ctx, ...r.value };
|
|
246
248
|
}
|
|
247
249
|
});
|
|
248
|
-
|
|
250
|
+
this.#ctx = ctx;
|
|
249
251
|
};
|
|
250
252
|
const init = async () => {
|
|
251
253
|
const __profiler = this.profilers.get('project#init');
|
|
252
254
|
const { symbols } = await this.cacheService.load();
|
|
253
|
-
this.symbols = new
|
|
255
|
+
this.symbols = new SymbolUtil(symbols, this.externals.event.EventEmitter);
|
|
254
256
|
this.symbols.buildCache();
|
|
255
257
|
__profiler.task('Load Cache');
|
|
256
258
|
await loadConfig();
|
|
@@ -258,13 +260,13 @@ class Project extends events_1.default {
|
|
|
258
260
|
await callIntializers();
|
|
259
261
|
__profiler.task('Initialize').finalize();
|
|
260
262
|
};
|
|
261
|
-
|
|
263
|
+
this.#initPromise = init();
|
|
262
264
|
}
|
|
263
265
|
setReadyPromise() {
|
|
264
266
|
const getDependencies = async () => {
|
|
265
267
|
const ans = [];
|
|
266
268
|
for (const dependency of this.config.env.dependencies) {
|
|
267
|
-
if (
|
|
269
|
+
if (DependencyKey.is(dependency)) {
|
|
268
270
|
const provider = this.meta.getDependencyProvider(dependency);
|
|
269
271
|
if (provider) {
|
|
270
272
|
try {
|
|
@@ -287,57 +289,52 @@ class Project extends events_1.default {
|
|
|
287
289
|
};
|
|
288
290
|
const listDependencyFiles = async () => {
|
|
289
291
|
const dependencies = await getDependencies();
|
|
290
|
-
const fileUriSupporter = await
|
|
291
|
-
const archiveUriSupporter = await
|
|
292
|
+
const fileUriSupporter = await FileUriSupporter.create(dependencies, this.externals, this.logger);
|
|
293
|
+
const archiveUriSupporter = await ArchiveUriSupporter.create(dependencies, this.externals, this.logger, this.cacheService.checksums.roots);
|
|
292
294
|
this.fs.register('file:', fileUriSupporter, true);
|
|
293
|
-
this.fs.register(
|
|
295
|
+
this.fs.register(ArchiveUriSupporter.Protocol, archiveUriSupporter, true);
|
|
294
296
|
};
|
|
295
297
|
const listProjectFiles = () => new Promise(resolve => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
this.#watchedFiles.clear();
|
|
299
|
+
this.#watcherReady = false;
|
|
300
|
+
this.#watcher = this.externals.fs
|
|
301
|
+
.watch(this.projectRoot)
|
|
299
302
|
.once('ready', () => {
|
|
300
|
-
|
|
303
|
+
this.#watcherReady = true;
|
|
301
304
|
resolve();
|
|
302
305
|
})
|
|
303
|
-
.on('add',
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (__classPrivateFieldGet(this, _Project_watcherReady, "f")) {
|
|
306
|
+
.on('add', uri => {
|
|
307
|
+
this.#watchedFiles.add(uri);
|
|
308
|
+
if (this.#watcherReady) {
|
|
307
309
|
this.emit('fileCreated', { uri });
|
|
308
310
|
}
|
|
309
311
|
})
|
|
310
|
-
.on('change',
|
|
311
|
-
|
|
312
|
-
if (__classPrivateFieldGet(this, _Project_watcherReady, "f")) {
|
|
312
|
+
.on('change', uri => {
|
|
313
|
+
if (this.#watcherReady) {
|
|
313
314
|
this.emit('fileModified', { uri });
|
|
314
315
|
}
|
|
315
316
|
})
|
|
316
|
-
.on('unlink',
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (__classPrivateFieldGet(this, _Project_watcherReady, "f")) {
|
|
317
|
+
.on('unlink', uri => {
|
|
318
|
+
this.#watchedFiles.delete(uri);
|
|
319
|
+
if (this.#watcherReady) {
|
|
320
320
|
this.emit('fileDeleted', { uri });
|
|
321
321
|
}
|
|
322
322
|
})
|
|
323
323
|
.on('error', e => {
|
|
324
324
|
this.logger.error('[Project] [chokidar]', e);
|
|
325
|
-
})
|
|
325
|
+
});
|
|
326
326
|
});
|
|
327
327
|
const ready = async () => {
|
|
328
328
|
await this.init();
|
|
329
329
|
const __profiler = this.profilers.get('project#ready');
|
|
330
|
-
const limit = (0, p_limit_1.default)(8);
|
|
331
|
-
const ensureParsed = this.ensureParsed.bind(this);
|
|
332
|
-
const ensureChecked = this.ensureChecked.bind(this);
|
|
333
330
|
await Promise.all([
|
|
334
331
|
listDependencyFiles(),
|
|
335
332
|
listProjectFiles(),
|
|
336
333
|
]);
|
|
337
|
-
|
|
338
|
-
|
|
334
|
+
this.#dependencyFiles = new Set(this.fs.listFiles());
|
|
335
|
+
this.#dependencyRoots = new Set(this.fs.listRoots());
|
|
339
336
|
this.updateRoots();
|
|
340
|
-
__profiler.task('List
|
|
337
|
+
__profiler.task('List URIs');
|
|
341
338
|
for (const [id, { checksum, registrar }] of this.meta.symbolRegistrars) {
|
|
342
339
|
const cacheChecksum = this.cacheService.checksums.symbolRegistrars[id];
|
|
343
340
|
if (cacheChecksum === undefined || checksum !== cacheChecksum) {
|
|
@@ -352,45 +349,61 @@ class Project extends events_1.default {
|
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
__profiler.task('Register Symbols');
|
|
352
|
+
for (const [uri, values] of Object.entries(this.cacheService.errors)) {
|
|
353
|
+
this.emit('documentErrored', { errors: values, uri });
|
|
354
|
+
}
|
|
355
|
+
__profiler.task('Pop Errors');
|
|
355
356
|
const { addedFiles, changedFiles, removedFiles } = await this.cacheService.validate();
|
|
356
357
|
for (const uri of removedFiles) {
|
|
357
|
-
this.
|
|
358
|
+
this.emit('fileDeleted', { uri });
|
|
358
359
|
}
|
|
359
360
|
__profiler.task('Validate Cache');
|
|
360
361
|
if (addedFiles.length > 0) {
|
|
361
|
-
this.
|
|
362
|
+
this.bindUri(addedFiles);
|
|
362
363
|
}
|
|
363
364
|
__profiler.task('Bind URIs');
|
|
364
|
-
const files = [...addedFiles, ...changedFiles];
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
const files = [...addedFiles, ...changedFiles].sort(this.meta.uriSorter);
|
|
366
|
+
__profiler.task('Sort URIs');
|
|
367
|
+
const __bindProfiler = this.profilers.get('project#ready#bind', 'top-n', 50);
|
|
368
|
+
for (const uri of files) {
|
|
369
|
+
await this.ensureBindingStarted(uri);
|
|
370
|
+
__bindProfiler.task(uri);
|
|
371
|
+
}
|
|
372
|
+
__bindProfiler.finalize();
|
|
373
|
+
__profiler.task('Bind Files');
|
|
374
|
+
__profiler.finalize();
|
|
371
375
|
this.emit('ready', {});
|
|
372
376
|
};
|
|
373
|
-
|
|
377
|
+
this.#isReady = false;
|
|
378
|
+
this.#readyPromise = ready();
|
|
374
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Load the config file and initialize parsers and processors.
|
|
382
|
+
*/
|
|
375
383
|
async init() {
|
|
376
|
-
await
|
|
384
|
+
await this.#initPromise;
|
|
377
385
|
return this;
|
|
378
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Finish the initial run of parsing, binding, and checking the entire project.
|
|
389
|
+
*/
|
|
379
390
|
async ready() {
|
|
380
|
-
await
|
|
391
|
+
await this.#readyPromise;
|
|
381
392
|
return this;
|
|
382
393
|
}
|
|
383
394
|
/**
|
|
384
395
|
* Behavior of the `Project` instance is undefined after this function has settled.
|
|
385
396
|
*/
|
|
386
397
|
async close() {
|
|
387
|
-
clearInterval(
|
|
388
|
-
await
|
|
398
|
+
clearInterval(this.#cacheSaverIntervalId);
|
|
399
|
+
await this.#watcher.close();
|
|
389
400
|
await this.cacheService.save();
|
|
390
401
|
}
|
|
391
402
|
async restart() {
|
|
392
403
|
try {
|
|
393
|
-
await
|
|
404
|
+
await this.#watcher.close();
|
|
405
|
+
this.#bindingInProgressUris.clear();
|
|
406
|
+
this.#symbolUpToDateUris.clear();
|
|
394
407
|
this.setReadyPromise();
|
|
395
408
|
await this.ready();
|
|
396
409
|
}
|
|
@@ -399,142 +412,197 @@ class Project extends events_1.default {
|
|
|
399
412
|
}
|
|
400
413
|
}
|
|
401
414
|
resetCache() {
|
|
402
|
-
|
|
415
|
+
this.logger.info('[Project#resetCache] Initiated...');
|
|
416
|
+
// Clear existing errors.
|
|
417
|
+
for (const uri of Object.keys(this.cacheService.errors)) {
|
|
418
|
+
this.emit('documentErrored', { errors: [], uri });
|
|
419
|
+
}
|
|
420
|
+
// Reset cache.
|
|
421
|
+
const { symbols } = this.cacheService.reset();
|
|
422
|
+
this.symbols = new SymbolUtil(symbols, this.externals.event.EventEmitter);
|
|
423
|
+
this.symbols.buildCache();
|
|
424
|
+
return this.restart();
|
|
403
425
|
}
|
|
404
426
|
normalizeUri(uri) {
|
|
405
|
-
return this.fs.mapFromDisk(
|
|
427
|
+
return this.fs.mapFromDisk(this.externals.uri.normalize(uri));
|
|
428
|
+
}
|
|
429
|
+
static TextDocumentCacheMaxLength = 268435456;
|
|
430
|
+
#textDocumentCache = new Map();
|
|
431
|
+
#textDocumentCacheLength = 0;
|
|
432
|
+
removeCachedTextDocument(uri) {
|
|
433
|
+
const doc = this.#textDocumentCache.get(uri);
|
|
434
|
+
if (doc && !(doc instanceof Promise)) {
|
|
435
|
+
this.#textDocumentCacheLength -= doc.getText().length;
|
|
436
|
+
}
|
|
437
|
+
this.#textDocumentCache.delete(uri);
|
|
406
438
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
439
|
+
async read(uri) {
|
|
440
|
+
const getLanguageID = (uri) => {
|
|
441
|
+
const ext = fileUtil.extname(uri) ?? '.plaintext';
|
|
442
|
+
return this.meta.getLanguageID(ext) ?? ext.slice(1);
|
|
443
|
+
};
|
|
444
|
+
const createTextDocument = async (uri) => {
|
|
445
|
+
const languageId = getLanguageID(uri);
|
|
446
|
+
if (!this.meta.isSupportedLanguage(languageId)) {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const content = bufferToString(await this.fs.readFile(uri));
|
|
451
|
+
return TextDocument.create(uri, languageId, -1, content);
|
|
452
|
+
}
|
|
453
|
+
catch (e) {
|
|
454
|
+
this.logger.warn(`[Project] [read] Failed creating TextDocument for “${uri}”`, e);
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
const trimCache = () => {
|
|
459
|
+
const iterator = this.#textDocumentCache.keys();
|
|
460
|
+
while (this.#textDocumentCacheLength > Project.TextDocumentCacheMaxLength) {
|
|
461
|
+
const result = iterator.next();
|
|
462
|
+
if (result.done) {
|
|
463
|
+
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()'`);
|
|
464
|
+
}
|
|
465
|
+
this.removeCachedTextDocument(result.value);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const getCacheHandlingPromise = async (uri) => {
|
|
469
|
+
if (this.#textDocumentCache.has(uri)) {
|
|
470
|
+
const ans = this.#textDocumentCache.get(uri);
|
|
471
|
+
// Move the entry to the end of the cache.
|
|
472
|
+
// The goal is that more-frequently-used entries are preferably not trimmed.
|
|
473
|
+
this.#textDocumentCache.delete(uri);
|
|
474
|
+
this.#textDocumentCache.set(uri, ans);
|
|
475
|
+
return ans;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
const promise = createTextDocument(uri);
|
|
479
|
+
this.#textDocumentCache.set(uri, promise);
|
|
480
|
+
// We replace the Promise in the cache with the TextDocument after it resolves,
|
|
481
|
+
// or removes it from the cache if it resolves to undefined.
|
|
482
|
+
const doc = await promise;
|
|
483
|
+
if (this.#textDocumentCache.get(uri) === promise) {
|
|
484
|
+
// The Promise in the cache is the same as the one we created earlier.
|
|
485
|
+
// This check is to make sure we don't set a wrong TextDocument to the cache in case the cache was modified elsewhere.
|
|
486
|
+
if (doc) {
|
|
487
|
+
this.#textDocumentCache.set(uri, doc);
|
|
488
|
+
this.#textDocumentCacheLength += doc.getText().length;
|
|
489
|
+
trimCache();
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
this.#textDocumentCache.delete(uri);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return doc;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
411
498
|
uri = this.normalizeUri(uri);
|
|
412
|
-
|
|
413
|
-
|
|
499
|
+
if (this.#clientManagedUris.has(uri)) {
|
|
500
|
+
const result = this.#clientManagedDocAndNodes.get(uri);
|
|
501
|
+
if (result) {
|
|
502
|
+
return result.doc;
|
|
503
|
+
}
|
|
504
|
+
throw new Error(`[Project] [read] Client-managed URI “${uri}” does not have a TextDocument in the cache`);
|
|
505
|
+
}
|
|
506
|
+
return getCacheHandlingPromise(uri);
|
|
414
507
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
|
|
508
|
+
parse(doc) {
|
|
509
|
+
const ctx = ParserContext.create(this, { doc });
|
|
510
|
+
const src = new Source(doc.getText());
|
|
511
|
+
const node = file()(src, ctx);
|
|
512
|
+
return node;
|
|
421
513
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
async ensureParsed(uri) {
|
|
426
|
-
uri = this.normalizeUri(uri);
|
|
427
|
-
if (__classPrivateFieldGet(this, _Project_docAndNodes, "f").has(uri)) {
|
|
428
|
-
return __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
|
|
429
|
-
}
|
|
430
|
-
const languageID = this.getLanguageID(uri);
|
|
431
|
-
if (!this.meta.isSupportedLanguage(languageID)) {
|
|
432
|
-
return undefined;
|
|
514
|
+
async bind(doc, node) {
|
|
515
|
+
if (node.binderErrors) {
|
|
516
|
+
return;
|
|
433
517
|
}
|
|
434
518
|
try {
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
519
|
+
this.#bindingInProgressUris.add(doc.uri);
|
|
520
|
+
const binder = this.meta.getBinder(node.type);
|
|
521
|
+
const ctx = BinderContext.create(this, { doc });
|
|
522
|
+
ctx.symbols.clear({ contributor: 'binder', uri: doc.uri });
|
|
523
|
+
await ctx.symbols.contributeAsAsync('binder', async () => {
|
|
524
|
+
const proxy = StateProxy.create(node);
|
|
525
|
+
await binder(proxy, ctx);
|
|
526
|
+
node.binderErrors = ctx.err.dump();
|
|
527
|
+
});
|
|
528
|
+
this.#bindingInProgressUris.delete(doc.uri);
|
|
529
|
+
this.#symbolUpToDateUris.add(doc.uri);
|
|
438
530
|
}
|
|
439
531
|
catch (e) {
|
|
440
|
-
this.logger.
|
|
441
|
-
return undefined;
|
|
532
|
+
this.logger.error(`[Project] [bind] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
442
533
|
}
|
|
443
534
|
}
|
|
444
|
-
parseAndCache(doc) {
|
|
445
|
-
const node = this.parse(doc);
|
|
446
|
-
return this.cache(doc, node);
|
|
447
|
-
}
|
|
448
|
-
parse(doc) {
|
|
449
|
-
const ctx = Context_1.ParserContext.create(this, { doc });
|
|
450
|
-
const src = new source_1.Source(doc.getText());
|
|
451
|
-
let ans;
|
|
452
|
-
ctx.symbols.clear({ contributor: 'parser', uri: doc.uri });
|
|
453
|
-
ctx.symbols.contributeAs('parser', () => ans = (0, parser_1.file)()(src, ctx));
|
|
454
|
-
return ans;
|
|
455
|
-
}
|
|
456
|
-
cache(doc, node) {
|
|
457
|
-
const data = { doc, node };
|
|
458
|
-
__classPrivateFieldGet(this, _Project_docAndNodes, "f").set(doc.uri, data);
|
|
459
|
-
this.emit('documentUpdated', data);
|
|
460
|
-
return data;
|
|
461
|
-
}
|
|
462
535
|
async check(doc, node) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
catch (e) {
|
|
479
|
-
this.logger.error(`[Project] [ensuredChecked] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
480
|
-
}
|
|
536
|
+
if (node.checkerErrors) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const checker = this.meta.getChecker(node.type);
|
|
541
|
+
const ctx = CheckerContext.create(this, { doc });
|
|
542
|
+
ctx.symbols.clear({ contributor: 'checker', uri: doc.uri });
|
|
543
|
+
await ctx.symbols.contributeAsAsync('checker', async () => {
|
|
544
|
+
await checker(StateProxy.create(node), ctx);
|
|
545
|
+
node.checkerErrors = ctx.err.dump();
|
|
546
|
+
this.lint(doc, node);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
this.logger.error(`[Project] [check] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
481
551
|
}
|
|
482
552
|
}
|
|
483
553
|
lint(doc, node) {
|
|
554
|
+
if (node.linterErrors) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
484
557
|
node.linterErrors = [];
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const { ruleSeverity, ruleValue } = result;
|
|
492
|
-
const { configValidator, linter, nodePredicate } = this.meta.getLinter(ruleName);
|
|
493
|
-
if (!configValidator(ruleName, ruleValue, this.logger)) {
|
|
494
|
-
// Config value is invalid.
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
const ctx = Context_1.LinterContext.create(this, {
|
|
498
|
-
doc,
|
|
499
|
-
err: new ErrorReporter_1.LinterErrorReporter(ruleName, ruleSeverity),
|
|
500
|
-
ruleName,
|
|
501
|
-
ruleValue,
|
|
502
|
-
});
|
|
503
|
-
(0, processor_1.traversePreOrder)(node, () => true, () => true, node => {
|
|
504
|
-
if (nodePredicate(node)) {
|
|
505
|
-
linter(node, ctx);
|
|
558
|
+
try {
|
|
559
|
+
for (const [ruleName, rawValue] of Object.entries(this.config.lint)) {
|
|
560
|
+
const result = LinterConfigValue.destruct(rawValue);
|
|
561
|
+
if (!result) {
|
|
562
|
+
// Rule is disabled (i.e. set to `null`) in the config.
|
|
563
|
+
continue;
|
|
506
564
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
565
|
+
const { ruleSeverity, ruleValue } = result;
|
|
566
|
+
const { configValidator, linter, nodePredicate } = this.meta.getLinter(ruleName);
|
|
567
|
+
if (!configValidator(ruleName, ruleValue, this.logger)) {
|
|
568
|
+
// Config value is invalid.
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const ctx = LinterContext.create(this, {
|
|
572
|
+
doc,
|
|
573
|
+
err: new LinterErrorReporter(ruleName, ruleSeverity),
|
|
574
|
+
ruleName,
|
|
575
|
+
ruleValue,
|
|
576
|
+
});
|
|
577
|
+
traversePreOrder(node, () => true, () => true, node => {
|
|
578
|
+
if (nodePredicate(node)) {
|
|
579
|
+
const proxy = StateProxy.create(node);
|
|
580
|
+
linter(proxy, ctx);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
node.linterErrors.push(...ctx.err.dump());
|
|
519
584
|
}
|
|
520
585
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const result = await this.ensureParsed(uri);
|
|
524
|
-
if (result) {
|
|
525
|
-
await this.ensureChecked(result.doc, result.node);
|
|
586
|
+
catch (e) {
|
|
587
|
+
this.logger.error(`[Project] [lint] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
526
588
|
}
|
|
527
|
-
return result;
|
|
528
589
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (
|
|
532
|
-
|
|
590
|
+
// @SingletonPromise()
|
|
591
|
+
async ensureBindingStarted(uri) {
|
|
592
|
+
if (this.#symbolUpToDateUris.has(uri) || this.#bindingInProgressUris.has(uri)) {
|
|
593
|
+
return;
|
|
533
594
|
}
|
|
534
|
-
|
|
595
|
+
this.#bindingInProgressUris.add(uri);
|
|
596
|
+
const doc = await this.read(uri);
|
|
597
|
+
if (!doc || !(await this.cacheService.hasFileChangedSinceCache(doc))) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const node = this.parse(doc);
|
|
601
|
+
await this.bind(doc, node);
|
|
602
|
+
this.emit('documentUpdated', { doc, node });
|
|
535
603
|
}
|
|
536
|
-
|
|
537
|
-
const ctx =
|
|
604
|
+
bindUri(param) {
|
|
605
|
+
const ctx = UriBinderContext.create(this);
|
|
538
606
|
if (typeof param === 'string') {
|
|
539
607
|
ctx.symbols.clear({ contributor: 'uri_binder', uri: param });
|
|
540
608
|
}
|
|
@@ -548,35 +616,40 @@ class Project extends events_1.default {
|
|
|
548
616
|
/**
|
|
549
617
|
* Notify that a new document was opened in the editor.
|
|
550
618
|
*/
|
|
551
|
-
onDidOpen(uri, languageID, version, content) {
|
|
619
|
+
async onDidOpen(uri, languageID, version, content) {
|
|
552
620
|
uri = this.normalizeUri(uri);
|
|
553
|
-
if (!
|
|
621
|
+
if (!fileUtil.isFileUri(uri)) {
|
|
554
622
|
return; // We only accept `file:` scheme for client-managed URIs.
|
|
555
623
|
}
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
624
|
+
const doc = TextDocument.create(uri, languageID, version, content);
|
|
625
|
+
const node = this.parse(doc);
|
|
626
|
+
this.#clientManagedUris.add(uri);
|
|
627
|
+
this.#clientManagedDocAndNodes.set(uri, { doc, node });
|
|
628
|
+
if (this.#isReady) {
|
|
629
|
+
await this.bind(doc, node);
|
|
630
|
+
await this.check(doc, node);
|
|
561
631
|
}
|
|
562
632
|
}
|
|
563
633
|
/**
|
|
564
634
|
* Notify that an existing document was changed in the editor.
|
|
565
635
|
* @throws If there is no `TextDocument` corresponding to the URI.
|
|
566
636
|
*/
|
|
567
|
-
onDidChange(uri, changes, version) {
|
|
637
|
+
async onDidChange(uri, changes, version) {
|
|
638
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
568
639
|
uri = this.normalizeUri(uri);
|
|
569
|
-
if (!
|
|
640
|
+
if (!fileUtil.isFileUri(uri)) {
|
|
570
641
|
return; // We only accept `file:` scheme for client-managed URIs.
|
|
571
642
|
}
|
|
572
|
-
const
|
|
573
|
-
if (!
|
|
574
|
-
throw new Error(`
|
|
643
|
+
const doc = this.#clientManagedDocAndNodes.get(uri)?.doc;
|
|
644
|
+
if (!doc) {
|
|
645
|
+
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?`);
|
|
575
646
|
}
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
647
|
+
TextDocument.update(doc, changes, version);
|
|
648
|
+
const node = this.parse(doc);
|
|
649
|
+
this.#clientManagedDocAndNodes.set(uri, { doc, node });
|
|
650
|
+
if (this.#isReady) {
|
|
651
|
+
await this.bind(doc, node);
|
|
652
|
+
await this.check(doc, node);
|
|
580
653
|
}
|
|
581
654
|
}
|
|
582
655
|
/**
|
|
@@ -584,18 +657,33 @@ class Project extends events_1.default {
|
|
|
584
657
|
*/
|
|
585
658
|
onDidClose(uri) {
|
|
586
659
|
uri = this.normalizeUri(uri);
|
|
587
|
-
if (!
|
|
660
|
+
if (!fileUtil.isFileUri(uri)) {
|
|
588
661
|
return; // We only accept `file:` scheme for client-managed URIs.
|
|
589
662
|
}
|
|
590
|
-
|
|
663
|
+
this.#clientManagedUris.delete(uri);
|
|
664
|
+
this.#clientManagedDocAndNodes.delete(uri);
|
|
591
665
|
this.tryClearingCache(uri);
|
|
592
666
|
}
|
|
667
|
+
async ensureClientManagedChecked(uri) {
|
|
668
|
+
const result = this.#clientManagedDocAndNodes.get(uri);
|
|
669
|
+
if (result && this.#isReady) {
|
|
670
|
+
const { doc, node } = result;
|
|
671
|
+
await this.bind(doc, node);
|
|
672
|
+
await this.check(doc, node);
|
|
673
|
+
this.emit('documentUpdated', { doc, node });
|
|
674
|
+
return { doc, node };
|
|
675
|
+
}
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
getClientManaged(uri) {
|
|
679
|
+
return this.#clientManagedDocAndNodes.get(uri);
|
|
680
|
+
}
|
|
593
681
|
async showCacheRoot() {
|
|
594
|
-
if (!
|
|
682
|
+
if (!this.#cacheRoot) {
|
|
595
683
|
return;
|
|
596
684
|
}
|
|
597
685
|
try {
|
|
598
|
-
await
|
|
686
|
+
await this.externals.fs.showFile(this.#cacheRoot);
|
|
599
687
|
}
|
|
600
688
|
catch (e) {
|
|
601
689
|
this.logger.error('[Service#showCacheRoot]', e);
|
|
@@ -603,33 +691,24 @@ class Project extends events_1.default {
|
|
|
603
691
|
}
|
|
604
692
|
tryClearingCache(uri) {
|
|
605
693
|
if (this.shouldRemove(uri)) {
|
|
606
|
-
|
|
694
|
+
this.removeCachedTextDocument(uri);
|
|
607
695
|
this.emit('documentRemoved', { uri });
|
|
608
696
|
}
|
|
609
697
|
}
|
|
610
698
|
shouldRemove(uri) {
|
|
611
|
-
return !
|
|
699
|
+
return !this.#clientManagedUris.has(uri) && !this.#dependencyFiles.has(uri) && !this.#watchedFiles.has(uri);
|
|
612
700
|
}
|
|
613
701
|
isOnlyWatched(uri) {
|
|
614
|
-
return
|
|
702
|
+
return this.#watchedFiles.has(uri) && !this.#clientManagedUris.has(uri) && !this.#dependencyFiles.has(uri);
|
|
615
703
|
}
|
|
616
704
|
}
|
|
617
|
-
_Project_cacheSaverIntervalId = new WeakMap(), _Project_clientManagedUris = new WeakMap(), _Project_configService = new WeakMap(), _Project_docAndNodes = new WeakMap(), _Project_initializers = new WeakMap(), _Project_initPromise = new WeakMap(), _Project_readyPromise = new WeakMap(), _Project_watchedFiles = new WeakMap(), _Project_watcher = new WeakMap(), _Project_watcherReady = new WeakMap(), _Project_isReady = new WeakMap(), _Project_dependencyRoots = new WeakMap(), _Project_dependencyFiles = new WeakMap(), _Project_roots = new WeakMap(), _Project_ctx = new WeakMap(), _Project_cacheRoot = new WeakMap();
|
|
618
|
-
Project.RootSuffix = '/pack.mcmeta';
|
|
619
705
|
__decorate([
|
|
620
|
-
(
|
|
621
|
-
], Project.prototype, "
|
|
706
|
+
SingletonPromise()
|
|
707
|
+
], Project.prototype, "bind", null);
|
|
622
708
|
__decorate([
|
|
623
|
-
(
|
|
709
|
+
SingletonPromise()
|
|
624
710
|
], Project.prototype, "check", null);
|
|
625
711
|
__decorate([
|
|
626
|
-
(
|
|
627
|
-
], Project.prototype, "
|
|
628
|
-
__decorate([
|
|
629
|
-
(0, common_1.CachePromise)()
|
|
630
|
-
], Project.prototype, "ensureParsedAndChecked", null);
|
|
631
|
-
__decorate([
|
|
632
|
-
(0, common_1.CachePromise)()
|
|
633
|
-
], Project.prototype, "ensureParsedAndCheckedOnlyWhenReady", null);
|
|
634
|
-
exports.Project = Project;
|
|
712
|
+
SingletonPromise()
|
|
713
|
+
], Project.prototype, "ensureClientManagedChecked", null);
|
|
635
714
|
//# sourceMappingURL=Project.js.map
|