@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.
Files changed (206) hide show
  1. package/README.md +7 -0
  2. package/lib/browser.d.ts +2 -0
  3. package/lib/browser.js +2 -0
  4. package/lib/common/Dev.d.ts +11 -0
  5. package/lib/common/Dev.js +90 -0
  6. package/lib/{service → common}/Logger.d.ts +0 -0
  7. package/lib/{service → common}/Logger.js +2 -5
  8. package/lib/common/Operations.d.ts +12 -0
  9. package/lib/common/Operations.js +33 -0
  10. package/lib/common/ReadonlyProxy.d.ts +9 -0
  11. package/lib/common/ReadonlyProxy.js +23 -0
  12. package/lib/common/StateProxy.d.ts +35 -0
  13. package/lib/common/StateProxy.js +69 -0
  14. package/lib/common/TwoWayMap.d.ts +21 -0
  15. package/lib/common/TwoWayMap.js +69 -0
  16. package/lib/common/externals/BrowserExternals.d.ts +3 -0
  17. package/lib/common/externals/BrowserExternals.js +191 -0
  18. package/lib/common/externals/NodeJsExternals.d.ts +3 -0
  19. package/lib/common/externals/NodeJsExternals.js +152 -0
  20. package/lib/common/externals/downloader.d.ts +31 -0
  21. package/lib/common/externals/downloader.js +32 -0
  22. package/lib/common/externals/index.d.ts +96 -0
  23. package/lib/common/externals/index.js +2 -0
  24. package/lib/common/index.d.ts +8 -1
  25. package/lib/common/index.js +8 -13
  26. package/lib/common/util.d.ts +50 -39
  27. package/lib/common/util.js +73 -122
  28. package/lib/index.d.ts +7 -7
  29. package/lib/index.js +7 -19
  30. package/lib/node/AstNode.d.ts +12 -11
  31. package/lib/node/AstNode.js +10 -16
  32. package/lib/node/BooleanNode.d.ts +2 -2
  33. package/lib/node/BooleanNode.js +4 -7
  34. package/lib/node/CommentNode.d.ts +6 -5
  35. package/lib/node/CommentNode.js +4 -9
  36. package/lib/node/ErrorNode.d.ts +1 -1
  37. package/lib/node/ErrorNode.js +2 -5
  38. package/lib/node/FileNode.d.ts +8 -4
  39. package/lib/node/FileNode.js +3 -6
  40. package/lib/node/FloatNode.d.ts +3 -3
  41. package/lib/node/FloatNode.js +4 -7
  42. package/lib/node/IntegerNode.d.ts +3 -3
  43. package/lib/node/IntegerNode.js +4 -7
  44. package/lib/node/ListNode.d.ts +2 -2
  45. package/lib/node/ListNode.js +2 -5
  46. package/lib/node/LiteralNode.d.ts +5 -5
  47. package/lib/node/LiteralNode.js +5 -8
  48. package/lib/node/LongNode.d.ts +3 -3
  49. package/lib/node/LongNode.js +4 -7
  50. package/lib/node/RecordNode.d.ts +2 -2
  51. package/lib/node/RecordNode.js +2 -5
  52. package/lib/node/ResourceLocationNode.d.ts +8 -10
  53. package/lib/node/ResourceLocationNode.js +9 -12
  54. package/lib/node/Sequence.d.ts +4 -3
  55. package/lib/node/Sequence.js +4 -6
  56. package/lib/node/StringNode.d.ts +5 -5
  57. package/lib/node/StringNode.js +9 -12
  58. package/lib/node/SymbolNode.d.ts +5 -5
  59. package/lib/node/SymbolNode.js +5 -8
  60. package/lib/node/index.d.ts +15 -15
  61. package/lib/node/index.js +15 -27
  62. package/lib/nodejs.d.ts +2 -0
  63. package/lib/nodejs.js +2 -0
  64. package/lib/parser/Parser.d.ts +3 -3
  65. package/lib/parser/Parser.js +1 -4
  66. package/lib/parser/boolean.d.ts +2 -2
  67. package/lib/parser/boolean.js +3 -6
  68. package/lib/parser/comment.d.ts +2 -2
  69. package/lib/parser/comment.js +5 -9
  70. package/lib/parser/empty.d.ts +1 -1
  71. package/lib/parser/empty.js +1 -5
  72. package/lib/parser/error.d.ts +2 -2
  73. package/lib/parser/error.js +5 -9
  74. package/lib/parser/file.d.ts +3 -3
  75. package/lib/parser/file.js +9 -13
  76. package/lib/parser/float.d.ts +4 -4
  77. package/lib/parser/float.js +12 -16
  78. package/lib/parser/index.d.ts +16 -16
  79. package/lib/parser/index.js +16 -34
  80. package/lib/parser/integer.d.ts +4 -4
  81. package/lib/parser/integer.js +10 -14
  82. package/lib/parser/list.d.ts +2 -2
  83. package/lib/parser/list.js +15 -19
  84. package/lib/parser/literal.d.ts +2 -2
  85. package/lib/parser/literal.js +5 -9
  86. package/lib/parser/long.d.ts +4 -4
  87. package/lib/parser/long.js +10 -14
  88. package/lib/parser/record.d.ts +3 -3
  89. package/lib/parser/record.js +20 -24
  90. package/lib/parser/resourceLocation.d.ts +2 -2
  91. package/lib/parser/resourceLocation.js +12 -24
  92. package/lib/parser/string.d.ts +7 -7
  93. package/lib/parser/string.js +40 -44
  94. package/lib/parser/symbol.d.ts +3 -3
  95. package/lib/parser/symbol.js +4 -14
  96. package/lib/parser/util.d.ts +30 -12
  97. package/lib/parser/util.js +78 -65
  98. package/lib/processor/ColorInfoProvider.d.ts +1 -1
  99. package/lib/processor/ColorInfoProvider.js +6 -9
  100. package/lib/processor/InlayHintProvider.d.ts +4 -3
  101. package/lib/processor/InlayHintProvider.js +1 -2
  102. package/lib/processor/SignatureHelpProvider.d.ts +4 -3
  103. package/lib/processor/SignatureHelpProvider.js +1 -2
  104. package/lib/processor/binder/Binder.d.ts +26 -0
  105. package/lib/processor/binder/Binder.js +18 -0
  106. package/lib/processor/binder/builtin.d.ts +27 -0
  107. package/lib/processor/binder/builtin.js +116 -0
  108. package/lib/processor/binder/index.d.ts +3 -0
  109. package/lib/processor/binder/index.js +3 -0
  110. package/lib/processor/checker/Checker.d.ts +2 -2
  111. package/lib/processor/checker/Checker.js +1 -5
  112. package/lib/processor/checker/builtin.d.ts +4 -4
  113. package/lib/processor/checker/builtin.js +22 -33
  114. package/lib/processor/checker/index.d.ts +2 -2
  115. package/lib/processor/checker/index.js +2 -27
  116. package/lib/processor/colorizer/Colorizer.d.ts +6 -5
  117. package/lib/processor/colorizer/Colorizer.js +8 -11
  118. package/lib/processor/colorizer/builtin.d.ts +3 -3
  119. package/lib/processor/colorizer/builtin.js +33 -46
  120. package/lib/processor/colorizer/index.d.ts +2 -2
  121. package/lib/processor/colorizer/index.js +2 -27
  122. package/lib/processor/completer/Completer.d.ts +6 -5
  123. package/lib/processor/completer/Completer.js +14 -33
  124. package/lib/processor/completer/builtin.d.ts +10 -9
  125. package/lib/processor/completer/builtin.js +59 -75
  126. package/lib/processor/completer/index.d.ts +2 -2
  127. package/lib/processor/completer/index.js +2 -27
  128. package/lib/processor/formatter/Formatter.d.ts +4 -3
  129. package/lib/processor/formatter/Formatter.js +2 -7
  130. package/lib/processor/formatter/builtin.d.ts +3 -3
  131. package/lib/processor/formatter/builtin.js +22 -36
  132. package/lib/processor/formatter/index.d.ts +2 -2
  133. package/lib/processor/formatter/index.js +2 -27
  134. package/lib/processor/index.d.ts +10 -9
  135. package/lib/processor/index.js +10 -21
  136. package/lib/processor/linter/Linter.d.ts +4 -3
  137. package/lib/processor/linter/Linter.js +1 -2
  138. package/lib/processor/linter/builtin/undeclaredSymbol.d.ts +2 -2
  139. package/lib/processor/linter/builtin/undeclaredSymbol.js +23 -27
  140. package/lib/processor/linter/builtin.d.ts +4 -3
  141. package/lib/processor/linter/builtin.js +19 -26
  142. package/lib/processor/linter/index.d.ts +2 -2
  143. package/lib/processor/linter/index.js +2 -27
  144. package/lib/processor/util.d.ts +4 -14
  145. package/lib/processor/util.js +1 -16
  146. package/lib/service/CacheService.d.ts +8 -6
  147. package/lib/service/CacheService.js +35 -56
  148. package/lib/service/Config.d.ts +17 -16
  149. package/lib/service/Config.js +56 -51
  150. package/lib/service/Context.d.ts +24 -21
  151. package/lib/service/Context.js +40 -36
  152. package/lib/service/Dependency.js +2 -5
  153. package/lib/service/Downloader.d.ts +9 -39
  154. package/lib/service/Downloader.js +37 -106
  155. package/lib/service/ErrorReporter.d.ts +2 -2
  156. package/lib/service/ErrorReporter.js +11 -15
  157. package/lib/service/FileService.d.ts +40 -14
  158. package/lib/service/FileService.js +107 -81
  159. package/lib/service/Hover.d.ts +2 -2
  160. package/lib/service/Hover.js +4 -7
  161. package/lib/service/MetaRegistry.d.ts +17 -12
  162. package/lib/service/MetaRegistry.js +81 -73
  163. package/lib/service/Profiler.d.ts +3 -2
  164. package/lib/service/Profiler.js +81 -45
  165. package/lib/service/Project.d.ts +101 -73
  166. package/lib/service/Project.js +438 -367
  167. package/lib/service/Service.d.ts +19 -29
  168. package/lib/service/Service.js +57 -53
  169. package/lib/service/SymbolLocations.d.ts +3 -3
  170. package/lib/service/SymbolLocations.js +4 -7
  171. package/lib/service/SymbolRegistrar.d.ts +1 -1
  172. package/lib/service/SymbolRegistrar.js +1 -2
  173. package/lib/service/UriProcessor.d.ts +5 -0
  174. package/lib/service/UriProcessor.js +2 -0
  175. package/lib/service/fileUtil.d.ts +21 -41
  176. package/lib/service/fileUtil.js +59 -129
  177. package/lib/service/index.d.ts +16 -17
  178. package/lib/service/index.js +16 -31
  179. package/lib/source/IndexMap.d.ts +1 -1
  180. package/lib/source/IndexMap.js +7 -10
  181. package/lib/source/LanguageError.d.ts +2 -2
  182. package/lib/source/LanguageError.js +3 -6
  183. package/lib/source/Location.d.ts +3 -3
  184. package/lib/source/Location.js +6 -9
  185. package/lib/source/Offset.d.ts +1 -1
  186. package/lib/source/Offset.js +4 -7
  187. package/lib/source/Position.js +2 -5
  188. package/lib/source/PositionRange.d.ts +2 -2
  189. package/lib/source/PositionRange.js +14 -17
  190. package/lib/source/Range.d.ts +1 -1
  191. package/lib/source/Range.js +7 -10
  192. package/lib/source/Source.d.ts +9 -4
  193. package/lib/source/Source.js +58 -31
  194. package/lib/source/index.d.ts +8 -8
  195. package/lib/source/index.js +8 -20
  196. package/lib/symbol/Symbol.d.ts +15 -14
  197. package/lib/symbol/Symbol.js +53 -68
  198. package/lib/symbol/SymbolUtil.d.ts +30 -39
  199. package/lib/symbol/SymbolUtil.js +189 -168
  200. package/lib/symbol/index.d.ts +2 -3
  201. package/lib/symbol/index.js +2 -15
  202. package/package.json +7 -4
  203. package/lib/service/Operations.d.ts +0 -8
  204. package/lib/service/Operations.js +0 -26
  205. package/lib/symbol/UriBinder.d.ts +0 -3
  206. package/lib/symbol/UriBinder.js +0 -3
@@ -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
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
9
- if (kind === "m") throw new TypeError("Private method is not writable");
10
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
11
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
12
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
13
- };
14
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
15
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
16
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
17
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
18
- };
19
- var __importDefault = (this && this.__importDefault) || function (mod) {
20
- return (mod && mod.__esModule) ? mod : { "default": mod };
21
- };
22
- var _Project_cacheSaverIntervalId, _Project_clientManagedUris, _Project_configService, _Project_docAndNodes, _Project_initializers, _Project_initPromise, _Project_readyPromise, _Project_watchedFiles, _Project_watcher, _Project_watcherReady, _Project_isReady, _Project_dependencyRoots, _Project_dependencyFiles, _Project_roots, _Project_ctx, _Project_cacheRoot;
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.Project = exports.ProjectData = void 0;
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 extends events_1.default {
77
- constructor({ cacheRoot, downloader, fs = FileService_1.FileService.create(), initializers = [], logger = Logger_1.Logger.create(), profilers = Profiler_1.ProfilerFactory.noop(), projectPath, }) {
78
- super();
79
- _Project_cacheSaverIntervalId.set(this, void 0);
80
- /**
81
- * URI of files that are currently managed by the language client.
82
- */
83
- _Project_clientManagedUris.set(this, new Set());
84
- _Project_configService.set(this, void 0);
85
- _Project_docAndNodes.set(this, new Map());
86
- _Project_initializers.set(this, void 0);
87
- _Project_initPromise.set(this, void 0);
88
- _Project_readyPromise.set(this, void 0);
89
- _Project_watchedFiles.set(this, new Set());
90
- _Project_watcher.set(this, void 0);
91
- _Project_watcherReady.set(this, false);
92
- _Project_isReady.set(this, false);
93
- this.meta = new MetaRegistry_1.MetaRegistry();
94
- _Project_dependencyRoots.set(this, void 0);
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 __classPrivateFieldGet(this, _Project_isReady, "f");
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 __classPrivateFieldGet(this, _Project_roots, "f");
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 __classPrivateFieldGet(this, _Project_ctx, "f");
114
+ return this.#ctx;
195
115
  }
116
+ #cacheRoot;
196
117
  /**
197
- * File path to a directory where all cache files of Spyglass should be stored.
118
+ * File URI to a directory where all cache files of Spyglass should be stored.
198
119
  */
199
120
  get cacheRoot() {
200
- return __classPrivateFieldGet(this, _Project_cacheRoot, "f");
121
+ return this.#cacheRoot;
201
122
  }
202
123
  updateRoots() {
203
- const rawRoots = [...__classPrivateFieldGet(this, _Project_dependencyRoots, "f"), this.projectRoot];
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
- __classPrivateFieldSet(this, _Project_roots, [...ans].sort((a, b) => b.length - a.length), "f");
212
- this.emit('rootsUpdated', { roots: __classPrivateFieldGet(this, _Project_roots, "f") });
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 [...__classPrivateFieldGet(this, _Project_dependencyFiles, "f"), ...__classPrivateFieldGet(this, _Project_watchedFiles, "f")]
223
- .filter(file => extensions.includes(fileUtil_1.fileUtil.extname(file) ?? ''));
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 __classPrivateFieldGet(this, _Project_configService, "f").load();
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(__classPrivateFieldGet(this, _Project_initializers, "f").map(init => init(initCtx)));
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}] “${__classPrivateFieldGet(this, _Project_initializers, "f")[i].name}”`, r.reason);
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
- __classPrivateFieldSet(this, _Project_ctx, ctx, "f");
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 symbol_1.SymbolUtil(symbols);
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
- __classPrivateFieldSet(this, _Project_initPromise, init(), "f");
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 (Dependency_1.DependencyKey.is(dependency)) {
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 FileService_1.FileUriSupporter.create(dependencies, this.logger);
291
- const spyglassUriSupporter = await FileService_1.SpyglassUriSupporter.create(dependencies, this.logger, this.cacheService.checksums.roots);
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(common_1.SpyglassUri.Protocol, spyglassUriSupporter, true);
292
+ this.fs.register(ArchiveUriSupporter.Protocol, archiveUriSupporter, true);
294
293
  };
295
294
  const listProjectFiles = () => new Promise(resolve => {
296
- __classPrivateFieldSet(this, _Project_watcherReady, false, "f");
297
- __classPrivateFieldSet(this, _Project_watcher, chokidar_1.default
298
- .watch(this.projectPath, { ignoreInitial: false })
295
+ this.#watcherReady = false;
296
+ this.#watcher = this.externals.fs
297
+ .watch(this.projectRoot)
299
298
  .once('ready', () => {
300
- __classPrivateFieldSet(this, _Project_watcherReady, true, "f");
299
+ this.#watcherReady = true;
301
300
  resolve();
302
301
  })
303
- .on('add', path => {
304
- const uri = fileUtil_1.fileUtil.pathToFileUri(path);
305
- __classPrivateFieldGet(this, _Project_watchedFiles, "f").add(uri);
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', path => {
311
- const uri = fileUtil_1.fileUtil.pathToFileUri(path);
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', path => {
317
- const uri = fileUtil_1.fileUtil.pathToFileUri(path);
318
- __classPrivateFieldGet(this, _Project_watchedFiles, "f").delete(uri);
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
- }), "f");
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
- __classPrivateFieldSet(this, _Project_dependencyFiles, new Set(this.fs.listFiles()), "f");
338
- __classPrivateFieldSet(this, _Project_dependencyRoots, new Set(this.fs.listRoots()), "f");
330
+ this.#dependencyFiles = new Set(this.fs.listFiles());
331
+ this.#dependencyRoots = new Set(this.fs.listRoots());
339
332
  this.updateRoots();
340
- __profiler.task('List Files');
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.bind(addedFiles);
354
+ this.bindUri(addedFiles);
362
355
  }
363
356
  __profiler.task('Bind URIs');
364
- const files = [...addedFiles, ...changedFiles]; // FIXME: nbtdoc files might need to be parsed and checked before others.
365
- // const docAndNodes = (await Promise.all(files.map(uri => limit(ensureParsed, uri)))).filter((r): r is DocAndNode => !!r)
366
- const docAndNodes = (await Promise.all(files.map(uri => ensureParsed(uri)))).filter((r) => !!r);
367
- __profiler.task('Parse Files');
368
- // await Promise.all(docAndNodes.map(({ doc, node }) => limit(ensureChecked, doc, node)))
369
- await Promise.all(docAndNodes.map(({ doc, node }) => ensureChecked(doc, node)));
370
- __profiler.task('Check Files').finalize();
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
- __classPrivateFieldSet(this, _Project_readyPromise, ready(), "f");
369
+ this.#readyPromise = ready();
374
370
  }
371
+ /**
372
+ * Load the config file and initialize parsers and processors.
373
+ */
375
374
  async init() {
376
- await __classPrivateFieldGet(this, _Project_initPromise, "f");
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 __classPrivateFieldGet(this, _Project_readyPromise, "f");
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(__classPrivateFieldGet(this, _Project_cacheSaverIntervalId, "f"));
388
- await __classPrivateFieldGet(this, _Project_watcher, "f").close();
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 __classPrivateFieldGet(this, _Project_watcher, "f").close();
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
- * @returns The language ID of the file, or the file extension without the leading dot.
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
- * @returns The cached `TextDocument` and `AstNode` for the URI, or `undefined` when such data isn't available in cache.
414
- */
415
- get(uri) {
416
- uri = fileUtil_1.fileUtil.normalize(uri);
417
- return __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
409
+ static TextDocumentCacheMaxLength = 268435456;
410
+ #textDocumentCache = new Map();
411
+ #textDocumentCacheLength = 0;
412
+ removeCachedTextDocument(uri) {
413
+ const doc = this.#textDocumentCache.get(uri);
414
+ if (doc && !(doc instanceof Promise)) {
415
+ this.#textDocumentCacheLength -= doc.getText().length;
416
+ }
417
+ this.#textDocumentCache.delete(uri);
418
418
  }
419
- /**
420
- * @throws FS-related errors
421
- */
422
- async ensureParsed(uri) {
423
- uri = fileUtil_1.fileUtil.normalize(uri);
424
- if (__classPrivateFieldGet(this, _Project_docAndNodes, "f").has(uri)) {
425
- return __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
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
- const languageID = this.getLanguageID(uri);
428
- if (!this.meta.isSupportedLanguage(languageID)) {
429
- return undefined;
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
- const content = (0, common_1.bufferToString)(await this.fs.readFile(uri));
433
- const doc = vscode_languageserver_textdocument_1.TextDocument.create(uri, languageID, -1, content);
434
- return this.parseAndCache(doc);
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.warn(`[Project] [ensureParsed] Failed for “${uri}”`, e);
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
- const checker = this.meta.getChecker(node.type);
461
- const ctx = Context_1.CheckerContext.create(this, { doc });
462
- ctx.symbols.clear({ contributor: 'checker', uri: doc.uri });
463
- await ctx.symbols.contributeAsAsync('checker', async () => {
464
- await checker(node, ctx);
465
- node.checkerErrors = ctx.err.dump();
466
- this.cache(doc, node);
467
- this.ensureLinted(doc, node);
468
- });
469
- }
470
- async ensureChecked(doc, node) {
471
- if (!node.checkerErrors) {
472
- try {
473
- return this.check(doc, node);
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
- for (const [ruleName, rawValue] of Object.entries(this.config.lint)) {
483
- const result = Config_1.LinterConfigValue.destruct(rawValue);
484
- if (!result) {
485
- // Rule is disabled (i.e. set to `null`) in the config.
486
- continue;
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
- node.linterErrors.push(...ctx.err.dump());
506
- }
507
- this.cache(doc, node);
508
- }
509
- ensureLinted(doc, node) {
510
- if (!node.linterErrors) {
511
- try {
512
- this.lint(doc, node);
513
- }
514
- catch (e) {
515
- this.logger.error(`[Project] [ensureLinted] Failed for “${doc.uri}” #${doc.version}`, e);
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
- async ensureParsedAndChecked(uri) {
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
- async ensureParsedAndCheckedOnlyWhenReady(uri) {
527
- const result = await this.ensureParsed(uri);
528
- if (__classPrivateFieldGet(this, _Project_isReady, "f") && result) {
529
- await this.ensureChecked(result.doc, result.node);
570
+ // @SingletonPromise()
571
+ async ensureBindingStarted(uri) {
572
+ if (this.#symbolUpToDateUris.has(uri) || this.#bindingInProgressUris.has(uri)) {
573
+ return;
530
574
  }
531
- return result;
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
- bind(param) {
534
- const ctx = Context_1.UriBinderContext.create(this);
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 = fileUtil_1.fileUtil.normalize(uri);
550
- __classPrivateFieldGet(this, _Project_clientManagedUris, "f").add(uri);
551
- const doc = vscode_languageserver_textdocument_1.TextDocument.create(uri, languageID, version, content);
552
- const { node } = this.parseAndCache(doc);
553
- if (__classPrivateFieldGet(this, _Project_isReady, "f")) {
554
- this.check(doc, node);
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
- uri = fileUtil_1.fileUtil.normalize(uri);
563
- const result = this.get(uri);
564
- if (!result) {
565
- throw new Error(`Document for “${uri}” is not cached. This should not happen. Did the language client send a didChange notification without sending a didOpen one?`);
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
- vscode_languageserver_textdocument_1.TextDocument.update(result.doc, changes, version);
568
- const { node } = this.parseAndCache(result.doc);
569
- if (__classPrivateFieldGet(this, _Project_isReady, "f")) {
570
- this.check(result.doc, node);
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 = fileUtil_1.fileUtil.normalize(uri);
578
- __classPrivateFieldGet(this, _Project_clientManagedUris, "f").delete(uri);
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 (!__classPrivateFieldGet(this, _Project_cacheRoot, "f")) {
662
+ if (!this.#cacheRoot) {
583
663
  return;
584
664
  }
585
665
  try {
586
- await fileUtil_1.fileUtil.showFile(__classPrivateFieldGet(this, _Project_cacheRoot, "f"));
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
- __classPrivateFieldGet(this, _Project_docAndNodes, "f").delete(uri);
674
+ this.removeCachedTextDocument(uri);
595
675
  this.emit('documentRemoved', { uri });
596
676
  }
597
677
  }
598
678
  shouldRemove(uri) {
599
- return !__classPrivateFieldGet(this, _Project_clientManagedUris, "f").has(uri) && !__classPrivateFieldGet(this, _Project_dependencyFiles, "f").has(uri) && !__classPrivateFieldGet(this, _Project_watchedFiles, "f").has(uri);
679
+ return !this.#clientManagedUris.has(uri) && !this.#dependencyFiles.has(uri) && !this.#watchedFiles.has(uri);
600
680
  }
601
681
  isOnlyWatched(uri) {
602
- return __classPrivateFieldGet(this, _Project_watchedFiles, "f").has(uri) && !__classPrivateFieldGet(this, _Project_clientManagedUris, "f").has(uri) && !__classPrivateFieldGet(this, _Project_dependencyFiles, "f").has(uri);
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
- (0, common_1.CachePromise)()
609
- ], Project.prototype, "ensureParsed", null);
686
+ SingletonPromise()
687
+ ], Project.prototype, "bind", null);
610
688
  __decorate([
611
- (0, common_1.CachePromise)()
689
+ SingletonPromise()
612
690
  ], Project.prototype, "check", null);
613
691
  __decorate([
614
- (0, common_1.CachePromise)()
615
- ], Project.prototype, "ensureChecked", null);
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