@spyglassmc/core 0.4.27 → 0.4.28

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.
@@ -2,12 +2,12 @@ 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 ["\"", "'", "\\", "b", "f", "n", "r", "t"];
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;
9
9
  }
10
- export declare const EscapeTable: Map<"\"" | "'" | "\\" | "b" | "f" | "n" | "r" | "t", string>;
10
+ export declare const EscapeTable: Map<"\"" | "'" | "\\" | "b" | "f" | "n" | "r" | "s" | "t", string>;
11
11
  export type Quote = "'" | '"';
12
12
  export interface StringOptions {
13
13
  colorTokenType?: ColorTokenType;
@@ -1,5 +1,5 @@
1
1
  import { Range } from '../source/index.js';
2
- export const EscapeChars = ['"', "'", '\\', 'b', 'f', 'n', 'r', 't'];
2
+ export const EscapeChars = ['"', "'", '\\', 'b', 'f', 'n', 'r', 's', 't'];
3
3
  export var EscapeChar;
4
4
  (function (EscapeChar) {
5
5
  /* istanbul ignore next */
@@ -16,6 +16,7 @@ export const EscapeTable = new Map([
16
16
  ['f', '\f'],
17
17
  ['n', '\n'],
18
18
  ['r', '\r'],
19
+ ['s', ' '],
19
20
  ['t', '\t'],
20
21
  ]);
21
22
  export var StringBaseNode;
@@ -1,5 +1,6 @@
1
1
  import { Uri } from '../common/index.js';
2
2
  import { SymbolTable } from '../symbol/index.js';
3
+ import { ArchiveUriSupporter } from './FileService.js';
3
4
  import { fileUtil } from './fileUtil.js';
4
5
  /**
5
6
  * The format version of the cache. Should be increased when any changes that
@@ -27,7 +28,10 @@ export class CacheService {
27
28
  this.cacheRoot = cacheRoot;
28
29
  this.project = project;
29
30
  this.project.on('documentUpdated', async ({ doc }) => {
30
- if (!this.#hasValidatedFiles) {
31
+ if (!this.#hasValidatedFiles
32
+ // Do not save checksums for file schemes that we cannot map to disk (e.g. 'untitled:'
33
+ // for untitled files in VS Code)
34
+ || !(doc.uri.startsWith(ArchiveUriSupporter.Protocol) || doc.uri.startsWith('file:'))) {
31
35
  return;
32
36
  }
33
37
  try {
@@ -153,5 +153,10 @@ export interface UriBinderContext extends ContextBase {
153
153
  export declare namespace UriBinderContext {
154
154
  function create(project: ProjectData): UriBinderContext;
155
155
  }
156
+ export interface UriPredicateContext extends ContextBase {
157
+ }
158
+ export declare namespace UriPredicateContext {
159
+ function create(project: ProjectData): UriPredicateContext;
160
+ }
156
161
  export {};
157
162
  //# sourceMappingURL=Context.d.ts.map
@@ -143,4 +143,11 @@ export var UriBinderContext;
143
143
  }
144
144
  UriBinderContext.create = create;
145
145
  })(UriBinderContext || (UriBinderContext = {}));
146
+ export var UriPredicateContext;
147
+ (function (UriPredicateContext) {
148
+ function create(project) {
149
+ return { ...ContextBase.create(project) };
150
+ }
151
+ UriPredicateContext.create = create;
152
+ })(UriPredicateContext || (UriPredicateContext = {}));
146
153
  //# sourceMappingURL=Context.js.map
@@ -200,10 +200,12 @@ export class ArchiveUriSupporter {
200
200
  return entry.data;
201
201
  }
202
202
  *listFiles() {
203
- for (const [archiveName, files] of this.entries.entries()) {
204
- this.logger.info(`[ArchiveUriSupporter#listFiles] Listing ${files.size} files from ${archiveName}`);
205
- for (const file of files.values()) {
206
- yield ArchiveUriSupporter.getUri(archiveName, file.path);
203
+ for (const [archiveName, entries] of this.entries.entries()) {
204
+ this.logger.info(`[ArchiveUriSupporter#listFiles] Listing ${entries.size} entries from ${archiveName}`);
205
+ for (const entry of entries.values()) {
206
+ if (entry.type === 'file') {
207
+ yield ArchiveUriSupporter.getUri(archiveName, entry.path);
208
+ }
207
209
  }
208
210
  }
209
211
  }
@@ -6,6 +6,7 @@ import type { Formatter } from '../processor/formatter/index.js';
6
6
  import type { Binder, Checker, CodeActionProvider, Colorizer, Completer, InlayHintProvider } from '../processor/index.js';
7
7
  import type { Linter } from '../processor/linter/Linter.js';
8
8
  import type { SignatureHelpProvider } from '../processor/SignatureHelpProvider.js';
9
+ import type { UriPredicateContext } from '../service/index.js';
9
10
  import type { DependencyKey, DependencyProvider } from './Dependency.js';
10
11
  import type { FileExtension } from './fileUtil.js';
11
12
  import type { SymbolRegistrar } from './SymbolRegistrar.js';
@@ -16,10 +17,16 @@ export interface LanguageOptions {
16
17
  * An array of extensions of files corresponding to the language. Each extension should include the leading dot (`.`).
17
18
  */
18
19
  extensions: FileExtension[];
20
+ /**
21
+ * If specified, the URI of the file must pass the predicate for it to be considered to be a file
22
+ * of this language.
23
+ */
24
+ uriPredicate?: UriPredicate;
19
25
  triggerCharacters?: string[];
20
26
  parser?: Parser<AstNode>;
21
27
  completer?: Completer<any>;
22
28
  }
29
+ export type UriPredicate = (uri: string, ctx: UriPredicateContext) => boolean;
23
30
  interface LinterRegistration {
24
31
  configValidator: (ruleName: string, ruleValue: unknown, logger: Logger) => unknown;
25
32
  linter: Linter<AstNode>;
@@ -50,11 +57,7 @@ export declare class MetaRegistry {
50
57
  * An array of all registered language IDs.
51
58
  */
52
59
  getLanguages(): string[];
53
- isSupportedLanguage(language: string): boolean;
54
- /**
55
- * An array of file extensions (including the leading dot (`.`)) that are supported.
56
- */
57
- getSupportedFileExtensions(): FileExtension[];
60
+ getLanguageOptions(language: string): LanguageOptions | undefined;
58
61
  /**
59
62
  * An array of characters that trigger a completion request.
60
63
  */
@@ -48,14 +48,8 @@ export class MetaRegistry {
48
48
  getLanguages() {
49
49
  return Array.from(this.#languages.keys());
50
50
  }
51
- isSupportedLanguage(language) {
52
- return this.#languages.has(language);
53
- }
54
- /**
55
- * An array of file extensions (including the leading dot (`.`)) that are supported.
56
- */
57
- getSupportedFileExtensions() {
58
- return [...this.#languages.values()].flatMap((v) => v.extensions);
51
+ getLanguageOptions(language) {
52
+ return this.#languages.get(language);
59
53
  }
60
54
  /**
61
55
  * An array of characters that trigger a completion request.
@@ -199,7 +199,19 @@ export declare class Project implements ExternalEventEmitter {
199
199
  ensureClientManagedChecked(uri: string): Promise<DocAndNode | undefined>;
200
200
  getClientManaged(uri: string): DocAndNode | undefined;
201
201
  showCacheRoot(): Promise<void>;
202
- shouldExclude(uri: string): boolean;
202
+ /**
203
+ * Returns true iff the URI should be excluded from all Spyglass language support.
204
+ *
205
+ * @param language Optional. If ommitted, a language will be derived from the URI according to
206
+ * its file extension.
207
+ */
208
+ shouldExclude(uri: string, language?: string): boolean;
209
+ private isSupportedLanguage;
210
+ /**
211
+ * Guess a language ID from a URI. The guessed language ID may or may not actually be supported.
212
+ */
213
+ private guessLanguageID;
214
+ private isUserExcluded;
203
215
  private tryClearingCache;
204
216
  private shouldRemove;
205
217
  private isOnlyWatched;
@@ -14,7 +14,7 @@ import { LanguageError, Range, Source } from '../source/index.js';
14
14
  import { SymbolUtil } from '../symbol/index.js';
15
15
  import { CacheService } from './CacheService.js';
16
16
  import { ConfigService, LinterConfigValue } from './Config.js';
17
- import { BinderContext, CheckerContext, LinterContext, ParserContext, UriBinderContext, } from './Context.js';
17
+ import { BinderContext, CheckerContext, LinterContext, ParserContext, UriBinderContext, UriPredicateContext, } from './Context.js';
18
18
  import { DependencyKey } from './Dependency.js';
19
19
  import { Downloader } from './Downloader.js';
20
20
  import { LinterErrorReporter } from './ErrorReporter.js';
@@ -152,10 +152,7 @@ export class Project {
152
152
  * are not loaded into the memory.
153
153
  */
154
154
  getTrackedFiles() {
155
- const extensions = this.meta.getSupportedFileExtensions();
156
- this.logger.info(`[Project#getTrackedFiles] Supported file extensions: ${extensions}`);
157
- const supportedFiles = [...this.#dependencyFiles ?? [], ...this.#watchedFiles]
158
- .filter((file) => extensions.includes(fileUtil.extname(file) ?? ''));
155
+ const supportedFiles = [...this.#dependencyFiles ?? [], ...this.#watchedFiles];
159
156
  this.logger.info(`[Project#getTrackedFiles] Listed ${supportedFiles.length} supported files`);
160
157
  return supportedFiles;
161
158
  }
@@ -334,7 +331,8 @@ export class Project {
334
331
  await this.init();
335
332
  const __profiler = this.profilers.get('project#ready');
336
333
  await Promise.all([listDependencyFiles(), listProjectFiles()]);
337
- this.#dependencyFiles = new Set(this.fs.listFiles());
334
+ this.#dependencyFiles = new Set([...this.fs.listFiles()]
335
+ .filter((uri) => !this.shouldExclude(uri)));
338
336
  this.#dependencyRoots = new Set(this.fs.listRoots());
339
337
  this.updateRoots();
340
338
  __profiler.task('List URIs');
@@ -451,13 +449,9 @@ export class Project {
451
449
  this.#textDocumentCache.delete(uri);
452
450
  }
453
451
  async read(uri) {
454
- const getLanguageID = (uri) => {
455
- const ext = fileUtil.extname(uri) ?? '.plaintext';
456
- return this.meta.getLanguageID(ext) ?? ext.slice(1);
457
- };
458
452
  const createTextDocument = async (uri) => {
459
- const languageId = getLanguageID(uri);
460
- if (!this.meta.isSupportedLanguage(languageId)) {
453
+ const languageId = this.guessLanguageID(uri);
454
+ if (!this.isSupportedLanguage(uri, languageId)) {
461
455
  return undefined;
462
456
  }
463
457
  try {
@@ -642,10 +636,10 @@ export class Project {
642
636
  */
643
637
  async onDidOpen(uri, languageID, version, content) {
644
638
  uri = this.normalizeUri(uri);
645
- if (!fileUtil.isFileUri(uri)) {
646
- return; // We only accept `file:` scheme for client-managed URIs.
639
+ if (uri.startsWith(ArchiveUriSupporter.Protocol)) {
640
+ return; // We do not accept `archive:` scheme for client-managed URIs.
647
641
  }
648
- if (this.shouldExclude(uri)) {
642
+ if (this.shouldExclude(uri, languageID)) {
649
643
  return;
650
644
  }
651
645
  const doc = TextDocument.create(uri, languageID, version, content);
@@ -664,15 +658,16 @@ export class Project {
664
658
  async onDidChange(uri, changes, version) {
665
659
  uri = this.normalizeUri(uri);
666
660
  this.#symbolUpToDateUris.delete(uri);
667
- if (!fileUtil.isFileUri(uri)) {
668
- return; // We only accept `file:` scheme for client-managed URIs.
669
- }
670
- if (this.shouldExclude(uri)) {
671
- return;
661
+ if (uri.startsWith(ArchiveUriSupporter.Protocol)) {
662
+ return; // We do not accept `archive:` scheme for client-managed URIs.
672
663
  }
673
664
  const doc = this.#clientManagedDocAndNodes.get(uri)?.doc;
674
- if (!doc) {
675
- throw new Error(`TextDocument for ${uri} is not cached. 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?`);
665
+ if (!doc || this.shouldExclude(uri, doc.languageId)) {
666
+ // If doc is undefined, it means the document was previously excluded by onDidOpen()
667
+ // based on the language ID supplied by the client, in which case we should return early.
668
+ // Otherwise, we perform the shouldExclude() check with the URI and the saved language ID
669
+ // as usual.
670
+ return;
676
671
  }
677
672
  TextDocument.update(doc, changes, version);
678
673
  const node = this.parse(doc);
@@ -687,8 +682,8 @@ export class Project {
687
682
  */
688
683
  onDidClose(uri) {
689
684
  uri = this.normalizeUri(uri);
690
- if (!fileUtil.isFileUri(uri)) {
691
- return; // We only accept `file:` scheme for client-managed URIs.
685
+ if (uri.startsWith(ArchiveUriSupporter.Protocol)) {
686
+ return; // We do not accept `archive:` scheme for client-managed URIs.
692
687
  }
693
688
  this.#clientManagedUris.delete(uri);
694
689
  this.#clientManagedDocAndNodes.delete(uri);
@@ -724,7 +719,33 @@ export class Project {
724
719
  this.logger.error('[Service#showCacheRoot]', e);
725
720
  }
726
721
  }
727
- shouldExclude(uri) {
722
+ /**
723
+ * Returns true iff the URI should be excluded from all Spyglass language support.
724
+ *
725
+ * @param language Optional. If ommitted, a language will be derived from the URI according to
726
+ * its file extension.
727
+ */
728
+ shouldExclude(uri, language) {
729
+ return !this.isSupportedLanguage(uri, language) || this.isUserExcluded(uri);
730
+ }
731
+ isSupportedLanguage(uri, language) {
732
+ language ??= this.guessLanguageID(uri);
733
+ const languageOptions = this.meta.getLanguageOptions(language);
734
+ if (!languageOptions) {
735
+ // Unsupported language.
736
+ return false;
737
+ }
738
+ const { uriPredicate } = languageOptions;
739
+ return uriPredicate?.(uri, UriPredicateContext.create(this)) ?? true;
740
+ }
741
+ /**
742
+ * Guess a language ID from a URI. The guessed language ID may or may not actually be supported.
743
+ */
744
+ guessLanguageID(uri) {
745
+ const ext = fileUtil.extname(uri) ?? '.spyglassmc-unknown';
746
+ return this.meta.getLanguageID(ext) ?? ext.slice(1);
747
+ }
748
+ isUserExcluded(uri) {
728
749
  if (this.config.env.exclude.length === 0) {
729
750
  return false;
730
751
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spyglassmc/core",
3
- "version": "0.4.27",
3
+ "version": "0.4.28",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "rfdc": "^1.3.0",
25
25
  "vscode-languageserver-textdocument": "^1.0.4",
26
26
  "whatwg-url": "^14.0.0",
27
- "@spyglassmc/locales": "0.3.13"
27
+ "@spyglassmc/locales": "0.3.14"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/decompress": "^4.2.3",