@spyglassmc/core 0.1.1 → 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/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.d.ts +21 -0
- package/lib/common/TwoWayMap.js +69 -0
- 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 -13
- package/lib/common/util.d.ts +50 -39
- package/lib/common/util.js +73 -122
- package/lib/index.d.ts +7 -7
- package/lib/index.js +7 -19
- 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 +5 -5
- package/lib/node/LiteralNode.js +5 -8
- 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 +8 -10
- package/lib/node/ResourceLocationNode.js +9 -12
- package/lib/node/Sequence.d.ts +4 -3
- package/lib/node/Sequence.js +4 -6
- package/lib/node/StringNode.d.ts +5 -5
- package/lib/node/StringNode.js +9 -12
- package/lib/node/SymbolNode.d.ts +5 -5
- package/lib/node/SymbolNode.js +5 -8
- package/lib/node/index.d.ts +15 -15
- package/lib/node/index.js +15 -27
- 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 -34
- 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 +40 -44
- package/lib/parser/symbol.d.ts +3 -3
- package/lib/parser/symbol.js +4 -14
- package/lib/parser/util.d.ts +30 -12
- package/lib/parser/util.js +78 -65
- 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 -27
- 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 +33 -46
- package/lib/processor/colorizer/index.d.ts +2 -2
- package/lib/processor/colorizer/index.js +2 -27
- 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 +59 -75
- package/lib/processor/completer/index.d.ts +2 -2
- package/lib/processor/completer/index.js +2 -27
- 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 -27
- package/lib/processor/index.d.ts +10 -9
- package/lib/processor/index.js +10 -21
- 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 +23 -27
- 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 -27
- package/lib/processor/util.d.ts +4 -14
- package/lib/processor/util.js +1 -16
- package/lib/service/CacheService.d.ts +8 -6
- package/lib/service/CacheService.js +35 -56
- package/lib/service/Config.d.ts +17 -16
- package/lib/service/Config.js +56 -51
- package/lib/service/Context.d.ts +24 -21
- package/lib/service/Context.js +40 -36
- package/lib/service/Dependency.js +2 -5
- package/lib/service/Downloader.d.ts +9 -39
- package/lib/service/Downloader.js +37 -106
- package/lib/service/ErrorReporter.d.ts +2 -2
- package/lib/service/ErrorReporter.js +11 -15
- package/lib/service/FileService.d.ts +40 -14
- package/lib/service/FileService.js +107 -81
- package/lib/service/Hover.d.ts +2 -2
- package/lib/service/Hover.js +4 -7
- package/lib/service/MetaRegistry.d.ts +17 -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 +101 -73
- package/lib/service/Project.js +438 -367
- package/lib/service/Service.d.ts +19 -29
- package/lib/service/Service.js +57 -53
- 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 +21 -41
- package/lib/service/fileUtil.js +59 -129
- package/lib/service/index.d.ts +16 -17
- package/lib/service/index.js +16 -31
- package/lib/source/IndexMap.d.ts +1 -1
- package/lib/source/IndexMap.js +7 -10
- package/lib/source/LanguageError.d.ts +2 -2
- package/lib/source/LanguageError.js +3 -6
- 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 +2 -2
- package/lib/source/PositionRange.js +14 -17
- package/lib/source/Range.d.ts +1 -1
- package/lib/source/Range.js +7 -10
- package/lib/source/Source.d.ts +9 -4
- package/lib/source/Source.js +58 -31
- package/lib/source/index.d.ts +8 -8
- package/lib/source/index.js +8 -20
- package/lib/symbol/Symbol.d.ts +15 -14
- package/lib/symbol/Symbol.js +53 -68
- package/lib/symbol/SymbolUtil.d.ts +30 -39
- package/lib/symbol/SymbolUtil.js +189 -168
- package/lib/symbol/index.d.ts +2 -3
- package/lib/symbol/index.js +2 -15
- 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 { 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(),
|
|
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,105 @@ 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('documentErrorred', {
|
|
187
|
+
doc,
|
|
188
|
+
errors: FileNode.getErrors(node),
|
|
189
|
+
node,
|
|
190
|
+
});
|
|
191
|
+
})
|
|
192
|
+
.on('fileCreated', async ({ uri }) => {
|
|
193
|
+
if (uri.endsWith(Project.RootSuffix)) {
|
|
194
|
+
this.updateRoots();
|
|
195
|
+
}
|
|
196
|
+
this.bindUri(uri);
|
|
197
|
+
return this.ensureBindingStarted(uri);
|
|
198
|
+
})
|
|
199
|
+
.on('fileModified', async ({ uri }) => {
|
|
200
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
201
|
+
if (this.isOnlyWatched(uri)) {
|
|
202
|
+
await this.ensureBindingStarted(uri);
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
.on('fileDeleted', ({ uri }) => {
|
|
206
|
+
if (uri.endsWith(Project.RootSuffix)) {
|
|
207
|
+
this.updateRoots();
|
|
208
|
+
}
|
|
209
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
210
|
+
this.symbols.clear({ uri });
|
|
211
|
+
this.tryClearingCache(uri);
|
|
212
|
+
})
|
|
213
|
+
.on('ready', () => {
|
|
214
|
+
this.#isReady = true;
|
|
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))
|
|
221
|
+
});
|
|
224
222
|
}
|
|
225
223
|
setInitPromise() {
|
|
226
224
|
const loadConfig = async () => {
|
|
227
|
-
this.config = await
|
|
225
|
+
this.config = await this.#configService.load();
|
|
228
226
|
};
|
|
229
227
|
const callIntializers = async () => {
|
|
230
228
|
const initCtx = {
|
|
231
229
|
cacheRoot: this.cacheRoot,
|
|
232
230
|
config: this.config,
|
|
233
231
|
downloader: this.downloader,
|
|
232
|
+
externals: this.externals,
|
|
234
233
|
logger: this.logger,
|
|
235
234
|
meta: this.meta,
|
|
236
235
|
projectRoot: this.projectRoot,
|
|
237
236
|
};
|
|
238
|
-
const results = await Promise.allSettled(
|
|
237
|
+
const results = await Promise.allSettled(this.#initializers.map(init => init(initCtx)));
|
|
239
238
|
let ctx = {};
|
|
240
239
|
results.forEach(async (r, i) => {
|
|
241
240
|
if (r.status === 'rejected') {
|
|
242
|
-
this.logger.error(`[Project] [callInitializers] [${i}] “${
|
|
241
|
+
this.logger.error(`[Project] [callInitializers] [${i}] “${this.#initializers[i].name}”`, r.reason);
|
|
243
242
|
}
|
|
244
243
|
else if (r.value) {
|
|
245
244
|
ctx = { ...ctx, ...r.value };
|
|
246
245
|
}
|
|
247
246
|
});
|
|
248
|
-
|
|
247
|
+
this.#ctx = ctx;
|
|
249
248
|
};
|
|
250
249
|
const init = async () => {
|
|
251
250
|
const __profiler = this.profilers.get('project#init');
|
|
252
251
|
const { symbols } = await this.cacheService.load();
|
|
253
|
-
this.symbols = new
|
|
252
|
+
this.symbols = new SymbolUtil(symbols, this.externals.event.EventEmitter);
|
|
254
253
|
this.symbols.buildCache();
|
|
255
254
|
__profiler.task('Load Cache');
|
|
256
255
|
await loadConfig();
|
|
@@ -258,13 +257,13 @@ class Project extends events_1.default {
|
|
|
258
257
|
await callIntializers();
|
|
259
258
|
__profiler.task('Initialize').finalize();
|
|
260
259
|
};
|
|
261
|
-
|
|
260
|
+
this.#initPromise = init();
|
|
262
261
|
}
|
|
263
262
|
setReadyPromise() {
|
|
264
263
|
const getDependencies = async () => {
|
|
265
264
|
const ans = [];
|
|
266
265
|
for (const dependency of this.config.env.dependencies) {
|
|
267
|
-
if (
|
|
266
|
+
if (DependencyKey.is(dependency)) {
|
|
268
267
|
const provider = this.meta.getDependencyProvider(dependency);
|
|
269
268
|
if (provider) {
|
|
270
269
|
try {
|
|
@@ -287,57 +286,51 @@ class Project extends events_1.default {
|
|
|
287
286
|
};
|
|
288
287
|
const listDependencyFiles = async () => {
|
|
289
288
|
const dependencies = await getDependencies();
|
|
290
|
-
const fileUriSupporter = await
|
|
291
|
-
const
|
|
289
|
+
const fileUriSupporter = await FileUriSupporter.create(dependencies, this.externals, this.logger);
|
|
290
|
+
const archiveUriSupporter = await ArchiveUriSupporter.create(dependencies, this.externals, this.logger, this.cacheService.checksums.roots);
|
|
292
291
|
this.fs.register('file:', fileUriSupporter, true);
|
|
293
|
-
this.fs.register(
|
|
292
|
+
this.fs.register(ArchiveUriSupporter.Protocol, archiveUriSupporter, true);
|
|
294
293
|
};
|
|
295
294
|
const listProjectFiles = () => new Promise(resolve => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.watch(this.
|
|
295
|
+
this.#watcherReady = false;
|
|
296
|
+
this.#watcher = this.externals.fs
|
|
297
|
+
.watch(this.projectRoot)
|
|
299
298
|
.once('ready', () => {
|
|
300
|
-
|
|
299
|
+
this.#watcherReady = true;
|
|
301
300
|
resolve();
|
|
302
301
|
})
|
|
303
|
-
.on('add',
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (__classPrivateFieldGet(this, _Project_watcherReady, "f")) {
|
|
302
|
+
.on('add', uri => {
|
|
303
|
+
this.#watchedFiles.add(uri);
|
|
304
|
+
if (this.#watcherReady) {
|
|
307
305
|
this.emit('fileCreated', { uri });
|
|
308
306
|
}
|
|
309
307
|
})
|
|
310
|
-
.on('change',
|
|
311
|
-
|
|
312
|
-
if (__classPrivateFieldGet(this, _Project_watcherReady, "f")) {
|
|
308
|
+
.on('change', uri => {
|
|
309
|
+
if (this.#watcherReady) {
|
|
313
310
|
this.emit('fileModified', { uri });
|
|
314
311
|
}
|
|
315
312
|
})
|
|
316
|
-
.on('unlink',
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (__classPrivateFieldGet(this, _Project_watcherReady, "f")) {
|
|
313
|
+
.on('unlink', uri => {
|
|
314
|
+
this.#watchedFiles.delete(uri);
|
|
315
|
+
if (this.#watcherReady) {
|
|
320
316
|
this.emit('fileDeleted', { uri });
|
|
321
317
|
}
|
|
322
318
|
})
|
|
323
319
|
.on('error', e => {
|
|
324
320
|
this.logger.error('[Project] [chokidar]', e);
|
|
325
|
-
})
|
|
321
|
+
});
|
|
326
322
|
});
|
|
327
323
|
const ready = async () => {
|
|
328
324
|
await this.init();
|
|
329
325
|
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
326
|
await Promise.all([
|
|
334
327
|
listDependencyFiles(),
|
|
335
328
|
listProjectFiles(),
|
|
336
329
|
]);
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
this.#dependencyFiles = new Set(this.fs.listFiles());
|
|
331
|
+
this.#dependencyRoots = new Set(this.fs.listRoots());
|
|
339
332
|
this.updateRoots();
|
|
340
|
-
__profiler.task('List
|
|
333
|
+
__profiler.task('List URIs');
|
|
341
334
|
for (const [id, { checksum, registrar }] of this.meta.symbolRegistrars) {
|
|
342
335
|
const cacheChecksum = this.cacheService.checksums.symbolRegistrars[id];
|
|
343
336
|
if (cacheChecksum === undefined || checksum !== cacheChecksum) {
|
|
@@ -358,39 +351,48 @@ class Project extends events_1.default {
|
|
|
358
351
|
}
|
|
359
352
|
__profiler.task('Validate Cache');
|
|
360
353
|
if (addedFiles.length > 0) {
|
|
361
|
-
this.
|
|
354
|
+
this.bindUri(addedFiles);
|
|
362
355
|
}
|
|
363
356
|
__profiler.task('Bind URIs');
|
|
364
|
-
const files = [...addedFiles, ...changedFiles];
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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();
|
|
371
367
|
this.emit('ready', {});
|
|
372
368
|
};
|
|
373
|
-
|
|
369
|
+
this.#readyPromise = ready();
|
|
374
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Load the config file and initialize parsers and processors.
|
|
373
|
+
*/
|
|
375
374
|
async init() {
|
|
376
|
-
await
|
|
375
|
+
await this.#initPromise;
|
|
377
376
|
return this;
|
|
378
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Finish the initial run of parsing, binding, and checking the entire project.
|
|
380
|
+
*/
|
|
379
381
|
async ready() {
|
|
380
|
-
await
|
|
382
|
+
await this.#readyPromise;
|
|
381
383
|
return this;
|
|
382
384
|
}
|
|
383
385
|
/**
|
|
384
386
|
* Behavior of the `Project` instance is undefined after this function has settled.
|
|
385
387
|
*/
|
|
386
388
|
async close() {
|
|
387
|
-
clearInterval(
|
|
388
|
-
await
|
|
389
|
+
clearInterval(this.#cacheSaverIntervalId);
|
|
390
|
+
await this.#watcher.close();
|
|
389
391
|
await this.cacheService.save();
|
|
390
392
|
}
|
|
391
393
|
async restart() {
|
|
392
394
|
try {
|
|
393
|
-
await
|
|
395
|
+
await this.#watcher.close();
|
|
394
396
|
this.setReadyPromise();
|
|
395
397
|
await this.ready();
|
|
396
398
|
}
|
|
@@ -401,137 +403,186 @@ class Project extends events_1.default {
|
|
|
401
403
|
resetCache() {
|
|
402
404
|
return this.cacheService.reset();
|
|
403
405
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
*/
|
|
407
|
-
getLanguageID(uri) {
|
|
408
|
-
uri = fileUtil_1.fileUtil.normalize(uri);
|
|
409
|
-
const ext = fileUtil_1.fileUtil.extname(uri) ?? '.plaintext';
|
|
410
|
-
return this.meta.getLanguageID(ext) ?? ext.slice(1);
|
|
406
|
+
normalizeUri(uri) {
|
|
407
|
+
return this.fs.mapFromDisk(this.externals.uri.normalize(uri));
|
|
411
408
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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);
|
|
418
418
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
+
};
|
|
478
|
+
uri = this.normalizeUri(uri);
|
|
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`);
|
|
426
485
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
486
|
+
return getCacheHandlingPromise(uri);
|
|
487
|
+
}
|
|
488
|
+
parse(doc) {
|
|
489
|
+
const ctx = ParserContext.create(this, { doc });
|
|
490
|
+
const src = new Source(doc.getText());
|
|
491
|
+
const node = file()(src, ctx);
|
|
492
|
+
return node;
|
|
493
|
+
}
|
|
494
|
+
async bind(doc, node) {
|
|
495
|
+
if (node.binderErrors) {
|
|
496
|
+
return;
|
|
430
497
|
}
|
|
431
498
|
try {
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
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);
|
|
435
510
|
}
|
|
436
511
|
catch (e) {
|
|
437
|
-
this.logger.
|
|
438
|
-
return undefined;
|
|
512
|
+
this.logger.error(`[Project] [bind] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
439
513
|
}
|
|
440
514
|
}
|
|
441
|
-
parseAndCache(doc) {
|
|
442
|
-
const node = this.parse(doc);
|
|
443
|
-
return this.cache(doc, node);
|
|
444
|
-
}
|
|
445
|
-
parse(doc) {
|
|
446
|
-
const ctx = Context_1.ParserContext.create(this, { doc });
|
|
447
|
-
const src = new source_1.Source(doc.getText());
|
|
448
|
-
let ans;
|
|
449
|
-
ctx.symbols.clear({ contributor: 'parser', uri: doc.uri });
|
|
450
|
-
ctx.symbols.contributeAs('parser', () => ans = (0, parser_1.file)()(src, ctx));
|
|
451
|
-
return ans;
|
|
452
|
-
}
|
|
453
|
-
cache(doc, node) {
|
|
454
|
-
const data = { doc, node };
|
|
455
|
-
__classPrivateFieldGet(this, _Project_docAndNodes, "f").set(doc.uri, data);
|
|
456
|
-
this.emit('documentUpdated', data);
|
|
457
|
-
return data;
|
|
458
|
-
}
|
|
459
515
|
async check(doc, node) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
catch (e) {
|
|
476
|
-
this.logger.error(`[Project] [ensuredChecked] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
477
|
-
}
|
|
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);
|
|
478
531
|
}
|
|
479
532
|
}
|
|
480
533
|
lint(doc, node) {
|
|
534
|
+
if (node.linterErrors) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
481
537
|
node.linterErrors = [];
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const { ruleSeverity, ruleValue } = result;
|
|
489
|
-
const { configValidator, linter, nodePredicate } = this.meta.getLinter(ruleName);
|
|
490
|
-
if (!configValidator(ruleName, ruleValue, this.logger)) {
|
|
491
|
-
// Config value is invalid.
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
const ctx = Context_1.LinterContext.create(this, {
|
|
495
|
-
doc,
|
|
496
|
-
err: new ErrorReporter_1.LinterErrorReporter(ruleName, ruleSeverity),
|
|
497
|
-
ruleName,
|
|
498
|
-
ruleValue,
|
|
499
|
-
});
|
|
500
|
-
(0, processor_1.traversePreOrder)(node, () => true, () => true, node => {
|
|
501
|
-
if (nodePredicate(node)) {
|
|
502
|
-
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;
|
|
503
544
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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());
|
|
516
564
|
}
|
|
517
565
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const result = await this.ensureParsed(uri);
|
|
521
|
-
if (result) {
|
|
522
|
-
await this.ensureChecked(result.doc, result.node);
|
|
566
|
+
catch (e) {
|
|
567
|
+
this.logger.error(`[Project] [lint] Failed for “${doc.uri}” #${doc.version}`, e);
|
|
523
568
|
}
|
|
524
|
-
return result;
|
|
525
569
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
|
|
570
|
+
// @SingletonPromise()
|
|
571
|
+
async ensureBindingStarted(uri) {
|
|
572
|
+
if (this.#symbolUpToDateUris.has(uri) || this.#bindingInProgressUris.has(uri)) {
|
|
573
|
+
return;
|
|
530
574
|
}
|
|
531
|
-
|
|
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 });
|
|
532
583
|
}
|
|
533
|
-
|
|
534
|
-
const ctx =
|
|
584
|
+
bindUri(param) {
|
|
585
|
+
const ctx = UriBinderContext.create(this);
|
|
535
586
|
if (typeof param === 'string') {
|
|
536
587
|
ctx.symbols.clear({ contributor: 'uri_binder', uri: param });
|
|
537
588
|
}
|
|
@@ -545,45 +596,74 @@ class Project extends events_1.default {
|
|
|
545
596
|
/**
|
|
546
597
|
* Notify that a new document was opened in the editor.
|
|
547
598
|
*/
|
|
548
|
-
onDidOpen(uri, languageID, version, content) {
|
|
549
|
-
uri =
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
599
|
+
async onDidOpen(uri, languageID, version, content) {
|
|
600
|
+
uri = this.normalizeUri(uri);
|
|
601
|
+
if (!fileUtil.isFileUri(uri)) {
|
|
602
|
+
return; // We only accept `file:` scheme for client-managed URIs.
|
|
603
|
+
}
|
|
604
|
+
const doc = TextDocument.create(uri, languageID, version, content);
|
|
605
|
+
const node = this.parse(doc);
|
|
606
|
+
this.#clientManagedUris.add(uri);
|
|
607
|
+
this.#clientManagedDocAndNodes.set(uri, { doc, node });
|
|
608
|
+
if (this.#isReady) {
|
|
609
|
+
await this.bind(doc, node);
|
|
610
|
+
await this.check(doc, node);
|
|
555
611
|
}
|
|
556
612
|
}
|
|
557
613
|
/**
|
|
558
614
|
* Notify that an existing document was changed in the editor.
|
|
559
615
|
* @throws If there is no `TextDocument` corresponding to the URI.
|
|
560
616
|
*/
|
|
561
|
-
onDidChange(uri, changes, version) {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (!
|
|
565
|
-
|
|
617
|
+
async onDidChange(uri, changes, version) {
|
|
618
|
+
this.#symbolUpToDateUris.delete(uri);
|
|
619
|
+
uri = this.normalizeUri(uri);
|
|
620
|
+
if (!fileUtil.isFileUri(uri)) {
|
|
621
|
+
return; // We only accept `file:` scheme for client-managed URIs.
|
|
566
622
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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?`);
|
|
626
|
+
}
|
|
627
|
+
TextDocument.update(doc, changes, version);
|
|
628
|
+
const node = this.parse(doc);
|
|
629
|
+
this.#clientManagedDocAndNodes.set(uri, { doc, node });
|
|
630
|
+
if (this.#isReady) {
|
|
631
|
+
await this.bind(doc, node);
|
|
632
|
+
await this.check(doc, node);
|
|
571
633
|
}
|
|
572
634
|
}
|
|
573
635
|
/**
|
|
574
636
|
* Notify that an existing document was closed in the editor.
|
|
575
637
|
*/
|
|
576
638
|
onDidClose(uri) {
|
|
577
|
-
uri =
|
|
578
|
-
|
|
639
|
+
uri = this.normalizeUri(uri);
|
|
640
|
+
if (!fileUtil.isFileUri(uri)) {
|
|
641
|
+
return; // We only accept `file:` scheme for client-managed URIs.
|
|
642
|
+
}
|
|
643
|
+
this.#clientManagedUris.delete(uri);
|
|
644
|
+
this.#clientManagedDocAndNodes.delete(uri);
|
|
579
645
|
this.tryClearingCache(uri);
|
|
580
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
|
+
}
|
|
581
661
|
async showCacheRoot() {
|
|
582
|
-
if (!
|
|
662
|
+
if (!this.#cacheRoot) {
|
|
583
663
|
return;
|
|
584
664
|
}
|
|
585
665
|
try {
|
|
586
|
-
await
|
|
666
|
+
await this.externals.fs.showFile(this.#cacheRoot);
|
|
587
667
|
}
|
|
588
668
|
catch (e) {
|
|
589
669
|
this.logger.error('[Service#showCacheRoot]', e);
|
|
@@ -591,33 +671,24 @@ class Project extends events_1.default {
|
|
|
591
671
|
}
|
|
592
672
|
tryClearingCache(uri) {
|
|
593
673
|
if (this.shouldRemove(uri)) {
|
|
594
|
-
|
|
674
|
+
this.removeCachedTextDocument(uri);
|
|
595
675
|
this.emit('documentRemoved', { uri });
|
|
596
676
|
}
|
|
597
677
|
}
|
|
598
678
|
shouldRemove(uri) {
|
|
599
|
-
return !
|
|
679
|
+
return !this.#clientManagedUris.has(uri) && !this.#dependencyFiles.has(uri) && !this.#watchedFiles.has(uri);
|
|
600
680
|
}
|
|
601
681
|
isOnlyWatched(uri) {
|
|
602
|
-
return
|
|
682
|
+
return this.#watchedFiles.has(uri) && !this.#clientManagedUris.has(uri) && !this.#dependencyFiles.has(uri);
|
|
603
683
|
}
|
|
604
684
|
}
|
|
605
|
-
_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();
|
|
606
|
-
Project.RootSuffix = '/pack.mcmeta';
|
|
607
685
|
__decorate([
|
|
608
|
-
(
|
|
609
|
-
], Project.prototype, "
|
|
686
|
+
SingletonPromise()
|
|
687
|
+
], Project.prototype, "bind", null);
|
|
610
688
|
__decorate([
|
|
611
|
-
(
|
|
689
|
+
SingletonPromise()
|
|
612
690
|
], Project.prototype, "check", null);
|
|
613
691
|
__decorate([
|
|
614
|
-
(
|
|
615
|
-
], Project.prototype, "
|
|
616
|
-
__decorate([
|
|
617
|
-
(0, common_1.CachePromise)()
|
|
618
|
-
], Project.prototype, "ensureParsedAndChecked", null);
|
|
619
|
-
__decorate([
|
|
620
|
-
(0, common_1.CachePromise)()
|
|
621
|
-
], Project.prototype, "ensureParsedAndCheckedOnlyWhenReady", null);
|
|
622
|
-
exports.Project = Project;
|
|
692
|
+
SingletonPromise()
|
|
693
|
+
], Project.prototype, "ensureClientManagedChecked", null);
|
|
623
694
|
//# sourceMappingURL=Project.js.map
|