@spyglassmc/core 0.3.0 → 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.
@@ -111,6 +111,7 @@ declare type ExtractFromGettableParser<T extends GettableParser> = T extends {
111
111
  declare type Case = {
112
112
  predicate?: (this: void, src: ReadonlySource) => boolean;
113
113
  prefix?: string;
114
+ regex?: RegExp;
114
115
  parser: GettableParser;
115
116
  };
116
117
  /**
@@ -150,8 +150,8 @@ export function recover(parser, defaultValue) {
150
150
  }
151
151
  export function select(cases) {
152
152
  return (src, ctx) => {
153
- for (const { predicate, prefix, parser } of cases) {
154
- if (predicate?.(src) ?? (prefix !== undefined ? src.tryPeek(prefix) : undefined) ?? true) {
153
+ for (const { predicate, prefix, parser, regex } of cases) {
154
+ if (predicate?.(src) ?? (prefix !== undefined ? src.tryPeek(prefix) : undefined) ?? (regex && src.matchPattern(regex)) ?? true) {
155
155
  const callableParser = typeof parser === 'object' ? parser.get() : parser;
156
156
  return callableParser(src, ctx);
157
157
  }
@@ -20,7 +20,8 @@ export const comment = node => {
20
20
  return [ColorToken.create(node, 'comment')];
21
21
  };
22
22
  export const error = node => {
23
- return [ColorToken.create(node, 'error')];
23
+ // return [ColorToken.create(node, 'error')]
24
+ return [];
24
25
  };
25
26
  export const literal = node => {
26
27
  return [ColorToken.create(node, node.options.colorTokenType ?? 'literal')];
@@ -1,4 +1,5 @@
1
1
  import type { TextDocument } from 'vscode-languageserver-textdocument';
2
+ import type { PosRangeLanguageError } from '../source/index.js';
2
3
  import { SymbolTable } from '../symbol/index.js';
3
4
  import type { RootUriString } from './fileUtil.js';
4
5
  import type { Project } from './Project.js';
@@ -6,7 +7,7 @@ import type { Project } from './Project.js';
6
7
  * The format version of the cache. Should be increased when any changes that
7
8
  * could invalidate the cache are introduced to the Spyglass codebase.
8
9
  */
9
- export declare const LatestCacheVersion = 1;
10
+ export declare const LatestCacheVersion = 2;
10
11
  /**
11
12
  * Checksums of cached files or roots.
12
13
  */
@@ -18,6 +19,7 @@ interface Checksums {
18
19
  declare namespace Checksums {
19
20
  function create(): Checksums;
20
21
  }
22
+ declare type ErrorCache = Record<string, readonly PosRangeLanguageError[]>;
21
23
  interface LoadResult {
22
24
  symbols: SymbolTable;
23
25
  }
@@ -32,6 +34,7 @@ export declare class CacheService {
32
34
  private readonly cacheRoot;
33
35
  private readonly project;
34
36
  checksums: Checksums;
37
+ errors: ErrorCache;
35
38
  /**
36
39
  * @param cacheRoot File path to the directory where cache files by Spyglass should be stored.
37
40
  * @param project
@@ -50,7 +53,7 @@ export declare class CacheService {
50
53
  */
51
54
  save(): Promise<boolean>;
52
55
  hasFileChangedSinceCache(doc: TextDocument): Promise<boolean>;
53
- reset(): void;
56
+ reset(): LoadResult;
54
57
  }
55
58
  export {};
56
59
  //# sourceMappingURL=CacheService.d.ts.map
@@ -5,7 +5,7 @@ import { fileUtil } from './fileUtil.js';
5
5
  * The format version of the cache. Should be increased when any changes that
6
6
  * could invalidate the cache are introduced to the Spyglass codebase.
7
7
  */
8
- export const LatestCacheVersion = 1;
8
+ export const LatestCacheVersion = 2;
9
9
  var Checksums;
10
10
  (function (Checksums) {
11
11
  function create() {
@@ -21,6 +21,7 @@ export class CacheService {
21
21
  cacheRoot;
22
22
  project;
23
23
  checksums = Checksums.create();
24
+ errors = {};
24
25
  #hasValidatedFiles = false;
25
26
  /**
26
27
  * @param cacheRoot File path to the directory where cache files by Spyglass should be stored.
@@ -63,6 +64,9 @@ export class CacheService {
63
64
  this.checksums.symbolRegistrars[id] = checksum;
64
65
  }
65
66
  });
67
+ this.project.on('documentErrored', ({ uri, errors }) => {
68
+ this.errors[uri] = errors;
69
+ });
66
70
  }
67
71
  #cacheFilePath;
68
72
  /**
@@ -84,6 +88,7 @@ export class CacheService {
84
88
  __profiler.task('Read File');
85
89
  if (cache.version === LatestCacheVersion) {
86
90
  this.checksums = cache.checksums;
91
+ this.errors = cache.errors;
87
92
  ans.symbols = SymbolTable.link(cache.symbols);
88
93
  __profiler.task('Link Symbols');
89
94
  }
@@ -163,10 +168,11 @@ export class CacheService {
163
168
  try {
164
169
  filePath = await this.getCacheFileUri();
165
170
  const cache = {
166
- checksums: this.checksums,
171
+ version: LatestCacheVersion,
167
172
  projectRoot: this.project.projectRoot,
173
+ checksums: this.checksums,
168
174
  symbols: SymbolTable.unlink(this.project.symbols.global),
169
- version: LatestCacheVersion,
175
+ errors: this.errors,
170
176
  };
171
177
  __profiler.task('Unlink Symbols');
172
178
  await fileUtil.writeGzippedJson(this.project.externals, filePath, cache);
@@ -182,7 +188,10 @@ export class CacheService {
182
188
  return this.checksums.files[doc.uri] !== await this.project.externals.crypto.getSha1(doc.getText());
183
189
  }
184
190
  reset() {
191
+ this.#hasValidatedFiles = false;
185
192
  this.checksums = Checksums.create();
193
+ this.errors = {};
194
+ return { symbols: {} };
186
195
  }
187
196
  }
188
197
  //# sourceMappingURL=CacheService.js.map
@@ -26,6 +26,11 @@ interface LinterRegistration {
26
26
  }
27
27
  interface SymbolRegistrarRegistration {
28
28
  registrar: SymbolRegistrar;
29
+ /**
30
+ * A checksum associated with this symbol registrar.
31
+ * If the cached checksum is equal to this provided checksum,
32
+ * the symbol registrar is not executed.
33
+ */
29
34
  checksum: string | undefined;
30
35
  }
31
36
  /**
@@ -4,7 +4,7 @@ import type { ExternalEventEmitter, Externals } from '../common/index.js';
4
4
  import { Logger } from '../common/index.js';
5
5
  import type { AstNode } from '../node/index.js';
6
6
  import { FileNode } from '../node/index.js';
7
- import type { LanguageError } from '../source/index.js';
7
+ import type { PosRangeLanguageError } from '../source/index.js';
8
8
  import { SymbolUtil } from '../symbol/index.js';
9
9
  import { CacheService } from './CacheService.js';
10
10
  import type { Config } from './Config.js';
@@ -38,8 +38,10 @@ export interface DocAndNode {
38
38
  }
39
39
  interface DocumentEvent extends DocAndNode {
40
40
  }
41
- interface DocumentErrorEvent extends DocumentEvent {
42
- errors: LanguageError[];
41
+ interface DocumentErrorEvent {
42
+ errors: readonly PosRangeLanguageError[];
43
+ uri: string;
44
+ version?: number;
43
45
  }
44
46
  interface FileEvent {
45
47
  uri: string;
@@ -124,21 +126,21 @@ export declare class Project implements ExternalEventEmitter {
124
126
  */
125
127
  get cacheRoot(): RootUriString;
126
128
  private updateRoots;
127
- on(event: 'documentErrorred', callbackFn: (data: DocumentErrorEvent) => void): this;
129
+ on(event: 'documentErrored', callbackFn: (data: DocumentErrorEvent) => void): this;
128
130
  on(event: 'documentUpdated', callbackFn: (data: DocumentEvent) => void): this;
129
131
  on(event: 'documentRemoved', callbackFn: (data: FileEvent) => void): this;
130
132
  on(event: `file${'Created' | 'Modified' | 'Deleted'}`, callbackFn: (data: FileEvent) => void): this;
131
133
  on(event: 'ready', callbackFn: (data: EmptyEvent) => void): this;
132
134
  on(event: 'rootsUpdated', callbackFn: (data: RootsEvent) => void): this;
133
135
  on(event: 'symbolRegistrarExecuted', callbackFn: (data: SymbolRegistrarEvent) => void): this;
134
- once(event: 'documentErrorred', callbackFn: (data: DocumentErrorEvent) => void): this;
136
+ once(event: 'documentErrored', callbackFn: (data: DocumentErrorEvent) => void): this;
135
137
  once(event: 'documentUpdated', callbackFn: (data: DocumentEvent) => void): this;
136
138
  once(event: 'documentRemoved', callbackFn: (data: FileEvent) => void): this;
137
139
  once(event: `file${'Created' | 'Modified' | 'Deleted'}`, callbackFn: (data: FileEvent) => void): this;
138
140
  once(event: 'ready', callbackFn: (data: EmptyEvent) => void): this;
139
141
  once(event: 'rootsUpdated', callbackFn: (data: RootsEvent) => void): this;
140
142
  once(event: 'symbolRegistrarExecuted', callbackFn: (data: SymbolRegistrarEvent) => void): this;
141
- emit(event: 'documentErrorred', data: DocumentErrorEvent): boolean;
143
+ emit(event: 'documentErrored', data: DocumentErrorEvent): boolean;
142
144
  emit(event: 'documentUpdated', data: DocumentEvent): boolean;
143
145
  emit(event: 'documentRemoved', data: FileEvent): boolean;
144
146
  emit(event: `file${'Created' | 'Modified' | 'Deleted'}`, data: FileEvent): boolean;
@@ -168,7 +170,7 @@ export declare class Project implements ExternalEventEmitter {
168
170
  */
169
171
  close(): Promise<void>;
170
172
  restart(): Promise<void>;
171
- resetCache(): void;
173
+ resetCache(): Promise<void>;
172
174
  normalizeUri(uri: string): string;
173
175
  private static readonly TextDocumentCacheMaxLength;
174
176
  private removeCachedTextDocument;
@@ -9,7 +9,7 @@ import { bufferToString, Logger, SingletonPromise, StateProxy } from '../common/
9
9
  import { FileNode } from '../node/index.js';
10
10
  import { file } from '../parser/index.js';
11
11
  import { traversePreOrder } from '../processor/index.js';
12
- import { Source } from '../source/index.js';
12
+ import { LanguageError, Source } from '../source/index.js';
13
13
  import { SymbolUtil } from '../symbol/index.js';
14
14
  import { CacheService } from './CacheService.js';
15
15
  import { ConfigService, LinterConfigValue } from './Config.js';
@@ -183,11 +183,14 @@ export class Project {
183
183
  // if (!this.#isReady) {
184
184
  // return
185
185
  // }
186
- this.emit('documentErrorred', {
187
- doc,
188
- errors: FileNode.getErrors(node),
189
- node,
186
+ this.emit('documentErrored', {
187
+ errors: FileNode.getErrors(node).map(e => LanguageError.withPosRange(e, doc)),
188
+ uri: doc.uri,
189
+ version: doc.version,
190
190
  });
191
+ })
192
+ .on('documentRemoved', ({ uri }) => {
193
+ this.emit('documentErrored', { errors: [], uri });
191
194
  })
192
195
  .on('fileCreated', async ({ uri }) => {
193
196
  if (uri.endsWith(Project.RootSuffix)) {
@@ -292,6 +295,7 @@ export class Project {
292
295
  this.fs.register(ArchiveUriSupporter.Protocol, archiveUriSupporter, true);
293
296
  };
294
297
  const listProjectFiles = () => new Promise(resolve => {
298
+ this.#watchedFiles.clear();
295
299
  this.#watcherReady = false;
296
300
  this.#watcher = this.externals.fs
297
301
  .watch(this.projectRoot)
@@ -345,9 +349,13 @@ export class Project {
345
349
  }
346
350
  }
347
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');
348
356
  const { addedFiles, changedFiles, removedFiles } = await this.cacheService.validate();
349
357
  for (const uri of removedFiles) {
350
- this.symbols.clear({ uri });
358
+ this.emit('fileDeleted', { uri });
351
359
  }
352
360
  __profiler.task('Validate Cache');
353
361
  if (addedFiles.length > 0) {
@@ -366,6 +374,7 @@ export class Project {
366
374
  __profiler.finalize();
367
375
  this.emit('ready', {});
368
376
  };
377
+ this.#isReady = false;
369
378
  this.#readyPromise = ready();
370
379
  }
371
380
  /**
@@ -393,6 +402,8 @@ export class Project {
393
402
  async restart() {
394
403
  try {
395
404
  await this.#watcher.close();
405
+ this.#bindingInProgressUris.clear();
406
+ this.#symbolUpToDateUris.clear();
396
407
  this.setReadyPromise();
397
408
  await this.ready();
398
409
  }
@@ -401,7 +412,16 @@ export class Project {
401
412
  }
402
413
  }
403
414
  resetCache() {
404
- 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();
405
425
  }
406
426
  normalizeUri(uri) {
407
427
  return this.fs.mapFromDisk(this.externals.uri.normalize(uri));
@@ -1,14 +1,28 @@
1
+ import type { TextDocument } from 'vscode-languageserver-textdocument';
1
2
  import type { Location } from './Location.js';
3
+ import { PositionRange } from './PositionRange.js';
2
4
  import type { Range } from './Range.js';
3
- export interface LanguageError {
5
+ export interface LanguageErrorData {
4
6
  message: string;
5
- range: Range;
6
7
  severity: ErrorSeverity;
7
8
  info?: LanguageErrorInfo;
8
9
  }
9
- export declare namespace LanguageError {
10
- function create(message: string, range: Range, severity?: ErrorSeverity, info?: LanguageErrorInfo): LanguageError;
10
+ export interface LanguageError extends LanguageErrorData {
11
+ range: Range;
12
+ }
13
+ /**
14
+ * A language error that uses {@link PositionRange} instead of {@link Range} to represent the span of the error.
15
+ */
16
+ export interface PosRangeLanguageError extends LanguageErrorData {
17
+ posRange: PositionRange;
11
18
  }
19
+ export declare const LanguageError: Readonly<{
20
+ create(message: string, range: Range, severity?: ErrorSeverity, info?: LanguageErrorInfo): LanguageError;
21
+ /**
22
+ * @returns A {@link PosRangeLanguageError}.
23
+ */
24
+ withPosRange(error: LanguageError, doc: TextDocument): PosRangeLanguageError;
25
+ }>;
12
26
  export declare const enum ErrorSeverity {
13
27
  Hint = 0,
14
28
  Information = 1,
@@ -1,12 +1,22 @@
1
- export var LanguageError;
2
- (function (LanguageError) {
3
- function create(message, range, severity = 3 /* ErrorSeverity.Error */, info) {
1
+ import { PositionRange } from './PositionRange.js';
2
+ export const LanguageError = Object.freeze({
3
+ create(message, range, severity = 3 /* ErrorSeverity.Error */, info) {
4
4
  const ans = { range, message, severity };
5
5
  if (info) {
6
6
  ans.info = info;
7
7
  }
8
8
  return ans;
9
- }
10
- LanguageError.create = create;
11
- })(LanguageError || (LanguageError = {}));
9
+ },
10
+ /**
11
+ * @returns A {@link PosRangeLanguageError}.
12
+ */
13
+ withPosRange(error, doc) {
14
+ return {
15
+ posRange: PositionRange.from(error.range, doc),
16
+ message: error.message,
17
+ severity: error.severity,
18
+ ...error.info && { info: error.info },
19
+ };
20
+ },
21
+ });
12
22
  //# sourceMappingURL=LanguageError.js.map
@@ -12,7 +12,7 @@ export declare namespace PositionRange {
12
12
  /**
13
13
  * @returns A `PositionRange` converted from a `RangeLike`.
14
14
  */
15
- function from(range: RangeLike, doc: TextDocument): PositionRange;
15
+ function from(rangeLike: RangeLike, doc: TextDocument): PositionRange;
16
16
  /**
17
17
  * ```typescript
18
18
  * {
@@ -27,11 +27,11 @@ export var PositionRange;
27
27
  /**
28
28
  * @returns A `PositionRange` converted from a `RangeLike`.
29
29
  */
30
- function from(range, doc) {
31
- const _range = Range.get(range);
30
+ function from(rangeLike, doc) {
31
+ const range = Range.get(rangeLike);
32
32
  const ans = {
33
- start: doc.positionAt(_range.start),
34
- end: doc.positionAt(_range.end),
33
+ start: doc.positionAt(range.start),
34
+ end: doc.positionAt(range.end),
35
35
  };
36
36
  return ans;
37
37
  }
@@ -43,6 +43,8 @@ export declare class ReadonlySource {
43
43
  tryPeekAfterWhitespace(expectedValue: string): boolean;
44
44
  peekUntil(...terminators: string[]): string;
45
45
  peekLine(): string;
46
+ peekRemaining(): string;
47
+ matchPattern(regex: RegExp): boolean;
46
48
  hasNonSpaceAheadInLine(): boolean;
47
49
  slice(start: number, end?: number): string;
48
50
  slice(rangeLike: Range | RangeContainer): string;
@@ -70,6 +70,12 @@ export class ReadonlySource {
70
70
  peekLine() {
71
71
  return this.peekUntil(CR, LF);
72
72
  }
73
+ peekRemaining() {
74
+ return this.string.slice(this.innerCursor);
75
+ }
76
+ matchPattern(regex) {
77
+ return regex.test(this.peekRemaining());
78
+ }
73
79
  hasNonSpaceAheadInLine() {
74
80
  for (let cursor = this.innerCursor; cursor < this.string.length; cursor++) {
75
81
  const c = this.string.charAt(cursor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spyglassmc/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",