@spyglassmc/core 0.1.2 → 0.4.0

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