@spyglassmc/core 0.4.46 → 0.4.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/common/Dev.d.ts +1 -1
- package/lib/common/EventDispatcher.d.ts +6 -0
- package/lib/common/EventDispatcher.js +15 -0
- package/lib/common/ReadonlyProxy.js +2 -2
- package/lib/common/StateProxy.js +2 -2
- package/lib/common/externals/BrowserExternals.d.ts +1 -9
- package/lib/common/externals/BrowserExternals.js +0 -59
- package/lib/common/externals/NodeJsExternals.d.ts +7 -23
- package/lib/common/externals/NodeJsExternals.js +192 -46
- package/lib/common/externals/index.d.ts +0 -17
- package/lib/common/index.d.ts +1 -0
- package/lib/common/index.js +1 -0
- package/lib/common/util.d.ts +15 -7
- package/lib/common/util.js +53 -18
- package/lib/node/StringNode.d.ts +1 -1
- package/lib/parser/string.d.ts +2 -2
- package/lib/processor/colorizer/Colorizer.d.ts +2 -2
- package/lib/service/CacheService.js +4 -5
- package/lib/service/Config.d.ts +6 -10
- package/lib/service/Config.js +3 -15
- package/lib/service/FileService.js +5 -5
- package/lib/service/FileWatcher.d.ts +9 -12
- package/lib/service/Project.d.ts +14 -27
- package/lib/service/Project.js +6 -18
- package/lib/service/fetcher.d.ts +8 -1
- package/lib/service/fetcher.js +62 -9
- package/lib/service/fileUtil.js +3 -3
- package/lib/symbol/Symbol.d.ts +10 -10
- package/lib/symbol/Symbol.js +15 -7
- package/lib/symbol/SymbolUtil.d.ts +9 -18
- package/lib/symbol/SymbolUtil.js +5 -19
- package/package.json +4 -5
package/lib/common/util.js
CHANGED
|
@@ -146,8 +146,8 @@ export function promisifyAsyncIterable(iterable, joiner) {
|
|
|
146
146
|
return joiner(chunks);
|
|
147
147
|
})();
|
|
148
148
|
}
|
|
149
|
-
export async function parseGzippedJson(
|
|
150
|
-
return JSON.parse(bufferToString(await
|
|
149
|
+
export async function parseGzippedJson(bytes) {
|
|
150
|
+
return JSON.parse(bufferToString(await decompressBytes(bytes, 'gzip')));
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
153
|
* @returns Is Plain Old JavaScript Object (POJO).
|
|
@@ -218,26 +218,33 @@ export function isIterable(value) {
|
|
|
218
218
|
return !!value[Symbol.iterator];
|
|
219
219
|
}
|
|
220
220
|
// #region ESNext functions polyfill
|
|
221
|
-
export function
|
|
222
|
-
|
|
221
|
+
export function getOrInsert(map, key, defaultValue) {
|
|
222
|
+
if (!map.has(key)) {
|
|
223
|
+
map.set(key, defaultValue);
|
|
224
|
+
}
|
|
225
|
+
return map.get(key);
|
|
223
226
|
}
|
|
224
|
-
export function
|
|
225
|
-
if (map.has(key)) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
export function getOrInsertComputed(map, key, callbackFunction) {
|
|
228
|
+
if (!map.has(key)) {
|
|
229
|
+
map.set(key, callbackFunction(key));
|
|
230
|
+
}
|
|
231
|
+
return map.get(key);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* TODO: replace with ESNext Uint8Array.prototype.toHex once it's widely supported
|
|
235
|
+
*/
|
|
236
|
+
export function bytesToHex(bytes) {
|
|
237
|
+
if ('Buffer' in globalThis && bytes instanceof Buffer) {
|
|
238
|
+
return bytes.toString('hex');
|
|
232
239
|
}
|
|
233
|
-
else if (
|
|
234
|
-
|
|
235
|
-
map.set(key, value);
|
|
236
|
-
return value;
|
|
240
|
+
else if ('toHex' in Uint8Array.prototype && typeof Uint8Array.prototype.toHex === 'function') {
|
|
241
|
+
return Uint8Array.prototype.toHex.call(bytes);
|
|
237
242
|
}
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
let ans = '';
|
|
244
|
+
for (const v of bytes) {
|
|
245
|
+
ans += v.toString(16).padStart(2, '0');
|
|
240
246
|
}
|
|
247
|
+
return ans;
|
|
241
248
|
}
|
|
242
249
|
// #endregion
|
|
243
250
|
/**
|
|
@@ -267,6 +274,34 @@ export function normalizeUri(uri) {
|
|
|
267
274
|
obj.pathname = normalizeUriPathname(obj.pathname);
|
|
268
275
|
return obj.toString();
|
|
269
276
|
}
|
|
277
|
+
export async function getSha1(data) {
|
|
278
|
+
if (typeof data === 'string') {
|
|
279
|
+
data = new TextEncoder().encode(data);
|
|
280
|
+
}
|
|
281
|
+
const hash = await crypto.subtle.digest('SHA-1', data.buffer);
|
|
282
|
+
return bytesToHex(new Uint8Array(hash));
|
|
283
|
+
}
|
|
284
|
+
export function compressBytes(bytes, algorithm) {
|
|
285
|
+
return streamToBytes(compressStream(bytesToStream(bytes), algorithm));
|
|
286
|
+
}
|
|
287
|
+
export function compressStream(stream, algorithm) {
|
|
288
|
+
return stream.pipeThrough(new CompressionStream(algorithm));
|
|
289
|
+
}
|
|
290
|
+
export function decompressBytes(bytes, algorithm) {
|
|
291
|
+
return streamToBytes(decompressStream(bytesToStream(bytes), algorithm));
|
|
292
|
+
}
|
|
293
|
+
export function decompressStream(stream, algorithm) {
|
|
294
|
+
return stream.pipeThrough(new DecompressionStream(algorithm));
|
|
295
|
+
}
|
|
296
|
+
export function bytesToStream(bytes) {
|
|
297
|
+
return new Blob([bytes]).stream();
|
|
298
|
+
}
|
|
299
|
+
export function streamToBytes(stream) {
|
|
300
|
+
return new Response(stream).bytes();
|
|
301
|
+
}
|
|
302
|
+
export function sleep(delayMs) {
|
|
303
|
+
return new Promise(resolve => setTimeout(resolve, delayMs));
|
|
304
|
+
}
|
|
270
305
|
/**
|
|
271
306
|
* Checks if the numeric value of a number or bigint is the same. Undefined is **not** the same as
|
|
272
307
|
* 0 for this function.
|
package/lib/node/StringNode.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Parser } from '../parser/index.js';
|
|
|
2
2
|
import type { ColorTokenType } from '../processor/index.js';
|
|
3
3
|
import type { IndexMap, RangeLike } from '../source/index.js';
|
|
4
4
|
import type { AstNode } from './AstNode.js';
|
|
5
|
-
export declare const EscapeChars: readonly ["
|
|
5
|
+
export declare const EscapeChars: readonly ['"', "'", '\\', 'b', 'f', 'n', 'r', 's', 't'];
|
|
6
6
|
export type EscapeChar = (typeof EscapeChars)[number];
|
|
7
7
|
export declare namespace EscapeChar {
|
|
8
8
|
function is(expected: EscapeChar[] | undefined, c: string): c is EscapeChar;
|
package/lib/parser/string.d.ts
CHANGED
|
@@ -6,11 +6,11 @@ import type { Parser, Result, Returnable } from './Parser.js';
|
|
|
6
6
|
export declare function string(options: StringOptions): InfallibleParser<StringNode>;
|
|
7
7
|
export declare function parseStringValue<T extends Returnable>(parser: Parser<T>, value: string, map: IndexMap, ctx: ParserContext): Result<T>;
|
|
8
8
|
export declare const BrigadierUnquotableCharacters: readonly ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "_", ".", "+", "-"];
|
|
9
|
-
export declare const BrigadierUnquotableCharacterSet: Set<"
|
|
9
|
+
export declare const BrigadierUnquotableCharacterSet: Set<"+" | "-" | "." | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "_" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z">;
|
|
10
10
|
export declare const BrigadierUnquotablePattern: RegExp;
|
|
11
11
|
export declare const BrigadierUnquotableOption: {
|
|
12
12
|
allowEmpty: boolean;
|
|
13
|
-
allowList: Set<"
|
|
13
|
+
allowList: Set<"+" | "-" | "." | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "_" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z">;
|
|
14
14
|
};
|
|
15
15
|
export declare const BrigadierStringOptions: StringOptions;
|
|
16
16
|
export declare const brigadierString: InfallibleParser<StringNode>;
|
|
@@ -18,7 +18,7 @@ export declare namespace ColorToken {
|
|
|
18
18
|
function fillGap(tokens: readonly ColorToken[], targetRange: Range, type: ColorTokenType, modifiers?: ColorTokenModifier[]): ColorToken[];
|
|
19
19
|
}
|
|
20
20
|
export declare const ColorTokenTypes: readonly ["comment", "enum", "enumMember", "escape", "function", "keyword", "modifier", "number", "property", "string", "struct", "type", "variable", "error", "literal", "operator", "resourceLocation", "vector"];
|
|
21
|
-
export type ColorTokenType = typeof ColorTokenTypes[number];
|
|
21
|
+
export type ColorTokenType = (typeof ColorTokenTypes)[number];
|
|
22
22
|
export declare const ColorTokenModifiers: readonly ["declaration", "defaultLibrary", "definition", "deprecated", "documentation", "modification", "readonly"];
|
|
23
|
-
export type ColorTokenModifier = typeof ColorTokenModifiers[number];
|
|
23
|
+
export type ColorTokenModifier = (typeof ColorTokenModifiers)[number];
|
|
24
24
|
//# sourceMappingURL=Colorizer.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bigintJsonLosslessReplacer, bigintJsonLosslessReviver, Uri } from '../common/index.js';
|
|
1
|
+
import { bigintJsonLosslessReplacer, bigintJsonLosslessReviver, getSha1, Uri, } from '../common/index.js';
|
|
2
2
|
import { SymbolTable } from '../symbol/index.js';
|
|
3
3
|
import { ArchiveUriSupporter } from './FileService.js';
|
|
4
4
|
import { fileUtil } from './fileUtil.js';
|
|
@@ -36,7 +36,7 @@ export class CacheService {
|
|
|
36
36
|
}
|
|
37
37
|
try {
|
|
38
38
|
// TODO: Don't update this for every single change.
|
|
39
|
-
this.checksums.files[doc.uri] = await
|
|
39
|
+
this.checksums.files[doc.uri] = await getSha1(doc.getText());
|
|
40
40
|
}
|
|
41
41
|
catch (e) {
|
|
42
42
|
if (!this.project.externals.error.isKind(e, 'EISDIR')) {
|
|
@@ -72,7 +72,7 @@ export class CacheService {
|
|
|
72
72
|
async getCacheFileUri() {
|
|
73
73
|
if (!this.#cacheFilePath) {
|
|
74
74
|
const sortedRoots = [...this.project.projectRoots].sort();
|
|
75
|
-
const hash = await
|
|
75
|
+
const hash = await getSha1(sortedRoots.join(':'));
|
|
76
76
|
this.#cacheFilePath = new Uri(`symbols/${hash}.json.gz`, this.cacheRoot).toString();
|
|
77
77
|
}
|
|
78
78
|
return this.#cacheFilePath;
|
|
@@ -196,8 +196,7 @@ export class CacheService {
|
|
|
196
196
|
return false;
|
|
197
197
|
}
|
|
198
198
|
async hasFileChangedSinceCache(doc) {
|
|
199
|
-
return (this.checksums.files[doc.uri]
|
|
200
|
-
!== (await this.project.externals.crypto.getSha1(doc.getText())));
|
|
199
|
+
return (this.checksums.files[doc.uri] !== (await getSha1(doc.getText())));
|
|
201
200
|
}
|
|
202
201
|
reset() {
|
|
203
202
|
this.#hasValidatedFiles = false;
|
package/lib/service/Config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DeepPartial
|
|
2
|
-
import { Arrayable } from '../common/index.js';
|
|
1
|
+
import type { DeepPartial } from '../common/index.js';
|
|
2
|
+
import { Arrayable, EventDispatcher } from '../common/index.js';
|
|
3
3
|
import { ErrorSeverity } from '../source/index.js';
|
|
4
4
|
import type { Project } from './Project.js';
|
|
5
5
|
export interface Config {
|
|
@@ -239,19 +239,15 @@ type ErrorEvent = {
|
|
|
239
239
|
error: unknown;
|
|
240
240
|
uri: string;
|
|
241
241
|
};
|
|
242
|
-
export declare class ConfigService
|
|
243
|
-
|
|
242
|
+
export declare class ConfigService extends EventDispatcher<{
|
|
243
|
+
changed: ConfigEvent;
|
|
244
|
+
error: ErrorEvent;
|
|
245
|
+
}> {
|
|
244
246
|
private readonly project;
|
|
245
247
|
private readonly defaultConfig;
|
|
246
248
|
static readonly ConfigFileNames: readonly ["spyglass.json", ".spyglassrc", ".spyglassrc.json"];
|
|
247
249
|
private currentEditorConfiguration;
|
|
248
250
|
constructor(project: Project, defaultConfig?: Config);
|
|
249
|
-
on(event: 'changed', callbackFn: (data: ConfigEvent) => void): this;
|
|
250
|
-
on(event: 'error', callbackFn: (data: ErrorEvent) => void): this;
|
|
251
|
-
once(event: 'changed', callbackFn: (data: ConfigEvent) => void): this;
|
|
252
|
-
once(event: 'error', callbackFn: (data: ErrorEvent) => void): this;
|
|
253
|
-
emit(event: 'changed', data: ConfigEvent): boolean;
|
|
254
|
-
emit(event: 'error', data: ErrorEvent): boolean;
|
|
255
251
|
onEditorConfigurationUpdate(editorConfiguration: PartialConfig): Promise<void>;
|
|
256
252
|
load(): Promise<Config>;
|
|
257
253
|
static isConfigFile(this: void, uri: string): boolean;
|
package/lib/service/Config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import rfdc from 'rfdc';
|
|
2
|
-
import { Arrayable, bufferToString, merge, TypePredicates } from '../common/index.js';
|
|
2
|
+
import { Arrayable, bufferToString, EventDispatcher, merge, TypePredicates, } from '../common/index.js';
|
|
3
3
|
import { ErrorSeverity } from '../source/index.js';
|
|
4
4
|
import { DataFileCategories, RegistryCategories } from '../symbol/index.js';
|
|
5
5
|
export var LinterSeverity;
|
|
@@ -296,16 +296,15 @@ export var PartialConfig;
|
|
|
296
296
|
}
|
|
297
297
|
PartialConfig.buildConfigFromEditorSettingsSafe = buildConfigFromEditorSettingsSafe;
|
|
298
298
|
})(PartialConfig || (PartialConfig = {}));
|
|
299
|
-
export class ConfigService {
|
|
299
|
+
export class ConfigService extends EventDispatcher {
|
|
300
300
|
project;
|
|
301
301
|
defaultConfig;
|
|
302
302
|
static ConfigFileNames = Object.freeze(['spyglass.json', '.spyglassrc', '.spyglassrc.json']);
|
|
303
|
-
#eventEmitter;
|
|
304
303
|
currentEditorConfiguration = {};
|
|
305
304
|
constructor(project, defaultConfig = VanillaConfig) {
|
|
305
|
+
super();
|
|
306
306
|
this.project = project;
|
|
307
307
|
this.defaultConfig = defaultConfig;
|
|
308
|
-
this.#eventEmitter = new project.externals.event.EventEmitter();
|
|
309
308
|
const handler = async ({ uri }) => {
|
|
310
309
|
if (ConfigService.isConfigFile(uri)) {
|
|
311
310
|
this.emit('changed', { config: await this.load() });
|
|
@@ -315,17 +314,6 @@ export class ConfigService {
|
|
|
315
314
|
project.on('fileModified', handler);
|
|
316
315
|
project.on('fileDeleted', handler);
|
|
317
316
|
}
|
|
318
|
-
on(event, callbackFn) {
|
|
319
|
-
this.#eventEmitter.on(event, callbackFn);
|
|
320
|
-
return this;
|
|
321
|
-
}
|
|
322
|
-
once(event, callbackFn) {
|
|
323
|
-
this.#eventEmitter.once(event, callbackFn);
|
|
324
|
-
return this;
|
|
325
|
-
}
|
|
326
|
-
emit(event, ...args) {
|
|
327
|
-
return this.#eventEmitter.emit(event, ...args);
|
|
328
|
-
}
|
|
329
317
|
async onEditorConfigurationUpdate(editorConfiguration) {
|
|
330
318
|
this.currentEditorConfiguration = editorConfiguration;
|
|
331
319
|
this.emit('changed', { config: await this.load() });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
|
-
import { Uri } from '../common/index.js';
|
|
2
|
+
import { getSha1, Uri } from '../common/index.js';
|
|
3
3
|
import { TwoWayMap } from '../common/TwoWayMap.js';
|
|
4
4
|
import { fileUtil } from './fileUtil.js';
|
|
5
5
|
export var FileService;
|
|
@@ -77,7 +77,7 @@ export class FileServiceImpl {
|
|
|
77
77
|
try {
|
|
78
78
|
let mappedUri = this.map.getKey(virtualUri);
|
|
79
79
|
if (mappedUri === undefined) {
|
|
80
|
-
mappedUri = `${this.virtualUrisRoot}${await
|
|
80
|
+
mappedUri = `${this.virtualUrisRoot}${await getSha1(virtualUri)}/${fileUtil.basename(virtualUri)}`;
|
|
81
81
|
// Delete old mapped file if it exists. This makes sure the
|
|
82
82
|
// readonly permission on the file is not removed by it being
|
|
83
83
|
// overwritten.
|
|
@@ -183,7 +183,7 @@ export class ArchiveUriSupporter {
|
|
|
183
183
|
}
|
|
184
184
|
else {
|
|
185
185
|
// Hash the corresponding file.
|
|
186
|
-
return
|
|
186
|
+
return getSha1(this.getDataInArchive(archiveName, pathInArchive));
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
async readFile(uri) {
|
|
@@ -258,7 +258,7 @@ export class ArchiveUriSupporter {
|
|
|
258
258
|
const files = await externals.archive.decompressBall(bytes, { stripLevel: dependency.stripLevel ?? 0 });
|
|
259
259
|
/// Debug message for #1609
|
|
260
260
|
logger.info(`[ArchiveUriSupporter#create] Extracted ${files.length} files from ${archiveName}`);
|
|
261
|
-
const hash = await
|
|
261
|
+
const hash = await getSha1(bytes);
|
|
262
262
|
archiveHashes.set(archiveName, hash);
|
|
263
263
|
entries.set(archiveName, new Map(files.map((f) => [f.path.replace(/\\/g, '/'), f])));
|
|
264
264
|
}
|
|
@@ -270,6 +270,6 @@ export class ArchiveUriSupporter {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
async function hashFile(externals, uri) {
|
|
273
|
-
return
|
|
273
|
+
return getSha1(await externals.fs.readFile(uri));
|
|
274
274
|
}
|
|
275
275
|
//# sourceMappingURL=FileService.js.map
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import type { UriStore } from '../common/index.js';
|
|
1
|
+
import type { EventDispatcher, UriStore } from '../common/index.js';
|
|
2
|
+
export type FileWatcherEventMap = {
|
|
3
|
+
ready: void;
|
|
4
|
+
add: string;
|
|
5
|
+
change: string;
|
|
6
|
+
unlink: string;
|
|
7
|
+
error: Error;
|
|
8
|
+
};
|
|
2
9
|
/**
|
|
3
10
|
* A file watcher that reports additions, changes, and deletions of files.
|
|
4
11
|
* Changes to directories should not be reported.
|
|
5
12
|
*/
|
|
6
|
-
export interface FileWatcher {
|
|
13
|
+
export interface FileWatcher extends EventDispatcher<FileWatcherEventMap> {
|
|
7
14
|
get watchedFiles(): UriStore;
|
|
8
15
|
ready(): Promise<void>;
|
|
9
|
-
on(eventName: 'ready', listener: () => unknown): this;
|
|
10
|
-
once(eventName: 'ready', listener: () => unknown): this;
|
|
11
|
-
on(eventName: 'add', listener: (uri: string) => unknown): this;
|
|
12
|
-
once(eventName: 'add', listener: (uri: string) => unknown): this;
|
|
13
|
-
on(eventName: 'change', listener: (uri: string) => unknown): this;
|
|
14
|
-
once(eventName: 'change', listener: (uri: string) => unknown): this;
|
|
15
|
-
on(eventName: 'unlink', listener: (uri: string) => unknown): this;
|
|
16
|
-
once(eventName: 'unlink', listener: (uri: string) => unknown): this;
|
|
17
|
-
on(eventName: 'error', listener: (error: Error) => unknown): this;
|
|
18
|
-
once(eventName: 'error', listener: (error: Error) => unknown): this;
|
|
19
16
|
close(): Promise<void>;
|
|
20
17
|
}
|
|
21
18
|
//# sourceMappingURL=FileWatcher.d.ts.map
|
package/lib/service/Project.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TextDocumentContentChangeEvent } from 'vscode-languageserver-textdocument';
|
|
2
2
|
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
3
|
-
import type {
|
|
4
|
-
import { Logger, UriStore } from '../common/index.js';
|
|
3
|
+
import type { Externals } from '../common/index.js';
|
|
4
|
+
import { EventDispatcher, Logger, UriStore } from '../common/index.js';
|
|
5
5
|
import type { AstNode } from '../node/index.js';
|
|
6
6
|
import { FileNode } from '../node/index.js';
|
|
7
7
|
import type { PosRangeLanguageError } from '../source/index.js';
|
|
@@ -100,7 +100,18 @@ export type ProjectData = Pick<Project, 'cacheRoot' | 'config' | 'ensureBindingS
|
|
|
100
100
|
*
|
|
101
101
|
* 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.
|
|
102
102
|
*/
|
|
103
|
-
export declare class Project
|
|
103
|
+
export declare class Project extends EventDispatcher<{
|
|
104
|
+
documentErrored: DocumentErrorEvent;
|
|
105
|
+
documentUpdated: DocumentEvent;
|
|
106
|
+
documentRemoved: FileEvent;
|
|
107
|
+
fileCreated: FileEvent;
|
|
108
|
+
fileModified: FileEvent;
|
|
109
|
+
fileDeleted: FileEvent;
|
|
110
|
+
ready: EmptyEvent;
|
|
111
|
+
rootsUpdated: RootsEvent;
|
|
112
|
+
symbolRegistrarExecuted: SymbolRegistrarEvent;
|
|
113
|
+
configChanged: ConfigChangeEvent;
|
|
114
|
+
}> {
|
|
104
115
|
#private;
|
|
105
116
|
private static readonly RootSuffix;
|
|
106
117
|
readonly cacheService: CacheService;
|
|
@@ -134,30 +145,6 @@ export declare class Project implements ExternalEventEmitter {
|
|
|
134
145
|
*/
|
|
135
146
|
get cacheRoot(): RootUriString;
|
|
136
147
|
private updateRoots;
|
|
137
|
-
on(event: 'documentErrored', callbackFn: (data: DocumentErrorEvent) => void): this;
|
|
138
|
-
on(event: 'documentUpdated', callbackFn: (data: DocumentEvent) => void): this;
|
|
139
|
-
on(event: 'documentRemoved', callbackFn: (data: FileEvent) => void): this;
|
|
140
|
-
on(event: `file${'Created' | 'Modified' | 'Deleted'}`, callbackFn: (data: FileEvent) => void): this;
|
|
141
|
-
on(event: 'ready', callbackFn: (data: EmptyEvent) => void): this;
|
|
142
|
-
on(event: 'rootsUpdated', callbackFn: (data: RootsEvent) => void): this;
|
|
143
|
-
on(event: 'symbolRegistrarExecuted', callbackFn: (data: SymbolRegistrarEvent) => void): this;
|
|
144
|
-
on(event: 'configChanged', callbackFn: (data: ConfigChangeEvent) => void): this;
|
|
145
|
-
once(event: 'documentErrored', callbackFn: (data: DocumentErrorEvent) => void): this;
|
|
146
|
-
once(event: 'documentUpdated', callbackFn: (data: DocumentEvent) => void): this;
|
|
147
|
-
once(event: 'documentRemoved', callbackFn: (data: FileEvent) => void): this;
|
|
148
|
-
once(event: `file${'Created' | 'Modified' | 'Deleted'}`, callbackFn: (data: FileEvent) => void): this;
|
|
149
|
-
once(event: 'ready', callbackFn: (data: EmptyEvent) => void): this;
|
|
150
|
-
once(event: 'rootsUpdated', callbackFn: (data: RootsEvent) => void): this;
|
|
151
|
-
once(event: 'symbolRegistrarExecuted', callbackFn: (data: SymbolRegistrarEvent) => void): this;
|
|
152
|
-
once(event: 'configChanged', callbackFn: (data: ConfigChangeEvent) => void): this;
|
|
153
|
-
emit(event: 'documentErrored', data: DocumentErrorEvent): boolean;
|
|
154
|
-
emit(event: 'documentUpdated', data: DocumentEvent): boolean;
|
|
155
|
-
emit(event: 'documentRemoved', data: FileEvent): boolean;
|
|
156
|
-
emit(event: `file${'Created' | 'Modified' | 'Deleted'}`, data: FileEvent): boolean;
|
|
157
|
-
emit(event: 'ready', data: EmptyEvent): boolean;
|
|
158
|
-
emit(event: 'rootsUpdated', data: RootsEvent): boolean;
|
|
159
|
-
emit(event: 'symbolRegistrarExecuted', data: SymbolRegistrarEvent): boolean;
|
|
160
|
-
emit(event: 'configChanged', data: ConfigChangeEvent): boolean;
|
|
161
148
|
/**
|
|
162
149
|
* Get all files that are tracked and supported.
|
|
163
150
|
*
|
package/lib/service/Project.js
CHANGED
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import picomatch from 'picomatch';
|
|
8
8
|
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
9
|
-
import { bufferToString, Logger, normalizeUri, SingletonPromise, StateProxy, UriStore, } from '../common/index.js';
|
|
9
|
+
import { bufferToString, EventDispatcher, Logger, normalizeUri, SingletonPromise, StateProxy, UriStore, } from '../common/index.js';
|
|
10
10
|
import { FileNode } from '../node/index.js';
|
|
11
11
|
import { file } from '../parser/index.js';
|
|
12
12
|
import { traversePreOrder } from '../processor/index.js';
|
|
@@ -60,7 +60,7 @@ const CacheAutoSaveInterval = 600_000; // 10 Minutes.
|
|
|
60
60
|
*
|
|
61
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.
|
|
62
62
|
*/
|
|
63
|
-
export class Project {
|
|
63
|
+
export class Project extends EventDispatcher {
|
|
64
64
|
static RootSuffix = '/pack.mcmeta';
|
|
65
65
|
/** Prevent circular binding. */
|
|
66
66
|
#bindingInProgressUris = new Set();
|
|
@@ -71,7 +71,6 @@ export class Project {
|
|
|
71
71
|
#clientManagedDocAndNodes = new Map();
|
|
72
72
|
#configService;
|
|
73
73
|
#symbolUpToDateUris = new Set();
|
|
74
|
-
#eventEmitter;
|
|
75
74
|
#initializers;
|
|
76
75
|
#watcher;
|
|
77
76
|
get watchedFiles() {
|
|
@@ -134,17 +133,6 @@ export class Project {
|
|
|
134
133
|
this.#roots = [...ans].sort((a, b) => b.length - a.length);
|
|
135
134
|
this.emit('rootsUpdated', { roots: this.#roots });
|
|
136
135
|
}
|
|
137
|
-
on(event, callbackFn) {
|
|
138
|
-
this.#eventEmitter.on(event, callbackFn);
|
|
139
|
-
return this;
|
|
140
|
-
}
|
|
141
|
-
once(event, callbackFn) {
|
|
142
|
-
this.#eventEmitter.once(event, callbackFn);
|
|
143
|
-
return this;
|
|
144
|
-
}
|
|
145
|
-
emit(event, ...args) {
|
|
146
|
-
return this.#eventEmitter.emit(event, ...args);
|
|
147
|
-
}
|
|
148
136
|
/**
|
|
149
137
|
* Get all files that are tracked and supported.
|
|
150
138
|
*
|
|
@@ -157,8 +145,8 @@ export class Project {
|
|
|
157
145
|
return supportedFiles;
|
|
158
146
|
}
|
|
159
147
|
constructor({ cacheRoot, defaultConfig, externals, fs = FileService.create(externals, cacheRoot), initializers = [], isDebugging = false, logger = Logger.create(), profilers = ProfilerFactory.noop(), projectRoots, }) {
|
|
148
|
+
super();
|
|
160
149
|
this.#cacheRoot = cacheRoot;
|
|
161
|
-
this.#eventEmitter = new externals.event.EventEmitter();
|
|
162
150
|
this.externals = externals;
|
|
163
151
|
this.fs = fs;
|
|
164
152
|
this.#initializers = initializers;
|
|
@@ -168,7 +156,7 @@ export class Project {
|
|
|
168
156
|
this.projectRoots = projectRoots;
|
|
169
157
|
this.cacheService = new CacheService(cacheRoot, this);
|
|
170
158
|
this.#configService = new ConfigService(this, defaultConfig);
|
|
171
|
-
this.symbols = new SymbolUtil({}
|
|
159
|
+
this.symbols = new SymbolUtil({});
|
|
172
160
|
this.#ctx = {};
|
|
173
161
|
this.logger.info(`[Project] [init] cacheRoot = ${cacheRoot}`);
|
|
174
162
|
this.logger.info(`[Project] [init] projectRoots = ${projectRoots.join(' ')}`);
|
|
@@ -250,7 +238,7 @@ export class Project {
|
|
|
250
238
|
};
|
|
251
239
|
const __profiler = this.profilers.get('project#init');
|
|
252
240
|
const { symbols } = await this.cacheService.load();
|
|
253
|
-
this.symbols = new SymbolUtil(symbols
|
|
241
|
+
this.symbols = new SymbolUtil(symbols);
|
|
254
242
|
this.symbols.buildCache();
|
|
255
243
|
__profiler.task('Load Cache');
|
|
256
244
|
this.config = await this.#configService.load();
|
|
@@ -425,7 +413,7 @@ export class Project {
|
|
|
425
413
|
}
|
|
426
414
|
// Reset cache.
|
|
427
415
|
const { symbols } = this.cacheService.reset();
|
|
428
|
-
this.symbols = new SymbolUtil(symbols
|
|
416
|
+
this.symbols = new SymbolUtil(symbols);
|
|
429
417
|
this.symbols.buildCache();
|
|
430
418
|
return this.restart();
|
|
431
419
|
}
|
package/lib/service/fetcher.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import type { Externals, Logger } from '../common/index.js';
|
|
2
|
-
export
|
|
2
|
+
export interface FetcherOptions {
|
|
3
|
+
retryBaseMs?: number;
|
|
4
|
+
retryMaxAttempts?: number;
|
|
5
|
+
perTryTimeoutMs?: number;
|
|
6
|
+
totalTimeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function fetchWithCache({ web }: Externals, logger: Logger, input: RequestInfo | URL, init?: RequestInit, options?: FetcherOptions): Promise<Response>;
|
|
9
|
+
export declare function isStaleFetcherResponse(response: Response): boolean;
|
|
3
10
|
//# sourceMappingURL=fetcher.d.ts.map
|
package/lib/service/fetcher.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import { Dev } from '../common/index.js';
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
import { Dev, sleep } from '../common/index.js';
|
|
2
|
+
const FETCH_RETRY_BASE_MS = 1_000;
|
|
3
|
+
const FETCH_RETRY_MAX_ATTEMPTS = 3;
|
|
4
|
+
const FETCH_PER_TRY_TIMEOUT_MS = 10_000;
|
|
5
|
+
const FETCH_TOTAL_TIMEOUT_MS = 15_000;
|
|
6
|
+
const STALE_HEADER = 'spyglassmc-is-stale';
|
|
7
|
+
export async function fetchWithCache({ web }, logger, input, init, options) {
|
|
4
8
|
const cache = await web.getCache();
|
|
5
9
|
const request = new Request(input, init);
|
|
6
10
|
const cachedResponse = await cache.match(request);
|
|
7
|
-
const cachedEtag = cachedResponse?.headers.get('
|
|
11
|
+
const cachedEtag = cachedResponse?.headers.get('etag');
|
|
12
|
+
const cachedLastModified = cachedResponse?.headers.get('last-modified');
|
|
8
13
|
if (cachedEtag) {
|
|
9
|
-
request.headers.set('
|
|
14
|
+
request.headers.set('if-none-match', cachedEtag);
|
|
10
15
|
}
|
|
11
|
-
|
|
16
|
+
else if (cachedLastModified) {
|
|
17
|
+
request.headers.set('if-modified-since', cachedLastModified);
|
|
18
|
+
}
|
|
19
|
+
request.headers.set('user-agent', 'SpyglassMC (+https://spyglassmc.com)');
|
|
12
20
|
try {
|
|
13
|
-
const response = await
|
|
21
|
+
const response = await fetchWithRetries(request, options);
|
|
14
22
|
if (response.status === 304) {
|
|
15
23
|
Dev.assertDefined(cachedResponse);
|
|
16
24
|
logger.info(`[fetchWithCache] reusing cache for ${request.url}`);
|
|
@@ -19,7 +27,7 @@ export async function fetchWithCache({ web }, logger, input, init) {
|
|
|
19
27
|
else if (!response.ok) {
|
|
20
28
|
let message = response.statusText;
|
|
21
29
|
try {
|
|
22
|
-
message =
|
|
30
|
+
message = await response.text();
|
|
23
31
|
}
|
|
24
32
|
catch { }
|
|
25
33
|
throw new TypeError(`${response.status} ${message}`);
|
|
@@ -32,6 +40,12 @@ export async function fetchWithCache({ web }, logger, input, init) {
|
|
|
32
40
|
catch (e) {
|
|
33
41
|
logger.warn('[fetchWithCache] put cache', e);
|
|
34
42
|
}
|
|
43
|
+
try {
|
|
44
|
+
await cachedResponse?.body?.cancel();
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
logger.warn('[fetchWithCache] failed cancelling cachedResponse body stream', e);
|
|
48
|
+
}
|
|
35
49
|
return response;
|
|
36
50
|
}
|
|
37
51
|
}
|
|
@@ -39,10 +53,49 @@ export async function fetchWithCache({ web }, logger, input, init) {
|
|
|
39
53
|
logger.warn('[fetchWithCache] fetch', e);
|
|
40
54
|
if (cachedResponse) {
|
|
41
55
|
logger.info(`[fetchWithCache] falling back to cache for ${request.url}`);
|
|
42
|
-
|
|
56
|
+
// Set the stale header when fallback is used
|
|
57
|
+
const newHeaders = new Headers(cachedResponse.headers);
|
|
58
|
+
newHeaders.set(STALE_HEADER, '1');
|
|
59
|
+
return new Response(cachedResponse.body, {
|
|
60
|
+
status: cachedResponse.status,
|
|
61
|
+
statusText: cachedResponse.statusText,
|
|
62
|
+
headers: newHeaders,
|
|
63
|
+
});
|
|
43
64
|
}
|
|
44
65
|
throw e;
|
|
45
66
|
}
|
|
46
67
|
}
|
|
68
|
+
export function isStaleFetcherResponse(response) {
|
|
69
|
+
return response.headers.has(STALE_HEADER);
|
|
70
|
+
}
|
|
71
|
+
async function fetchWithRetries(request, { perTryTimeoutMs = FETCH_PER_TRY_TIMEOUT_MS, retryBaseMs = FETCH_RETRY_BASE_MS, retryMaxAttempts = FETCH_RETRY_MAX_ATTEMPTS, totalTimeoutMs = FETCH_TOTAL_TIMEOUT_MS, } = {}) {
|
|
72
|
+
let lastResult;
|
|
73
|
+
const totalSignal = AbortSignal.timeout(totalTimeoutMs);
|
|
74
|
+
Dev.assertTrue(retryMaxAttempts > 0, 'Number of attempts must be greater than 0');
|
|
75
|
+
for (let i = 0; i < retryMaxAttempts; i++) {
|
|
76
|
+
const isLastAttempt = i === retryMaxAttempts - 1;
|
|
77
|
+
try {
|
|
78
|
+
lastResult = await fetch(request.clone(), {
|
|
79
|
+
signal: AbortSignal.any([
|
|
80
|
+
totalSignal,
|
|
81
|
+
AbortSignal.timeout(perTryTimeoutMs),
|
|
82
|
+
]),
|
|
83
|
+
});
|
|
84
|
+
if (lastResult.status < 500) {
|
|
85
|
+
return lastResult;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
lastResult = e;
|
|
90
|
+
}
|
|
91
|
+
if (!isLastAttempt) {
|
|
92
|
+
await sleep(Math.round(Math.random() * (2 ** i) * retryBaseMs));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (lastResult instanceof Response) {
|
|
96
|
+
return lastResult;
|
|
97
|
+
}
|
|
98
|
+
throw lastResult;
|
|
99
|
+
}
|
|
47
100
|
// Fetchr? I hardly know her: https://github.com/NeunEinser/bingo
|
|
48
101
|
//# sourceMappingURL=fetcher.js.map
|
package/lib/service/fileUtil.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bigintJsonNumberReplacer, bigintJsonNumberReviver, bufferToString, Uri, } from '../common/index.js';
|
|
1
|
+
import { bigintJsonNumberReplacer, bigintJsonNumberReviver, bufferToString, compressBytes, decompressBytes, Uri, } from '../common/index.js';
|
|
2
2
|
export var fileUtil;
|
|
3
3
|
(function (fileUtil) {
|
|
4
4
|
/**
|
|
@@ -245,7 +245,7 @@ export var fileUtil;
|
|
|
245
245
|
* @throws
|
|
246
246
|
*/
|
|
247
247
|
async function readGzippedFile(externals, path) {
|
|
248
|
-
return
|
|
248
|
+
return decompressBytes(await readFile(externals, path), 'gzip');
|
|
249
249
|
}
|
|
250
250
|
fileUtil.readGzippedFile = readGzippedFile;
|
|
251
251
|
/**
|
|
@@ -255,7 +255,7 @@ export var fileUtil;
|
|
|
255
255
|
if (typeof buffer === 'string') {
|
|
256
256
|
buffer = new TextEncoder().encode(buffer);
|
|
257
257
|
}
|
|
258
|
-
return writeFile(externals, path, await
|
|
258
|
+
return writeFile(externals, path, await compressBytes(buffer, 'gzip'));
|
|
259
259
|
}
|
|
260
260
|
fileUtil.writeGzippedFile = writeGzippedFile;
|
|
261
261
|
/**
|