@spyglassmc/core 0.1.1 → 0.1.2

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 (57) hide show
  1. package/lib/common/TwoWayMap.d.ts +21 -0
  2. package/lib/common/TwoWayMap.js +75 -0
  3. package/lib/common/index.js +5 -1
  4. package/lib/common/util.d.ts +3 -20
  5. package/lib/common/util.js +6 -40
  6. package/lib/index.js +5 -1
  7. package/lib/node/LiteralNode.d.ts +1 -1
  8. package/lib/node/LiteralNode.js +1 -1
  9. package/lib/node/ResourceLocationNode.d.ts +2 -4
  10. package/lib/node/Sequence.d.ts +2 -1
  11. package/lib/node/Sequence.js +3 -2
  12. package/lib/node/SymbolNode.d.ts +1 -1
  13. package/lib/node/SymbolNode.js +1 -1
  14. package/lib/node/index.js +5 -1
  15. package/lib/parser/float.js +1 -1
  16. package/lib/parser/index.js +5 -1
  17. package/lib/parser/integer.js +1 -1
  18. package/lib/parser/long.js +1 -1
  19. package/lib/parser/string.js +4 -2
  20. package/lib/parser/util.d.ts +25 -7
  21. package/lib/parser/util.js +50 -17
  22. package/lib/processor/checker/builtin.js +1 -1
  23. package/lib/processor/checker/index.js +5 -1
  24. package/lib/processor/colorizer/index.js +5 -1
  25. package/lib/processor/completer/builtin.js +16 -16
  26. package/lib/processor/completer/index.js +5 -1
  27. package/lib/processor/formatter/index.js +5 -1
  28. package/lib/processor/index.js +5 -1
  29. package/lib/processor/linter/builtin/undeclaredSymbol.js +3 -3
  30. package/lib/processor/linter/builtin.js +1 -1
  31. package/lib/processor/linter/index.js +5 -1
  32. package/lib/service/Config.d.ts +6 -4
  33. package/lib/service/Config.js +6 -6
  34. package/lib/service/Context.d.ts +2 -2
  35. package/lib/service/Context.js +1 -0
  36. package/lib/service/Downloader.d.ts +1 -0
  37. package/lib/service/Downloader.js +4 -0
  38. package/lib/service/ErrorReporter.js +1 -1
  39. package/lib/service/FileService.d.ts +29 -4
  40. package/lib/service/FileService.js +82 -19
  41. package/lib/service/Project.d.ts +1 -0
  42. package/lib/service/Project.js +23 -11
  43. package/lib/service/Service.d.ts +2 -2
  44. package/lib/service/Service.js +25 -15
  45. package/lib/service/fileUtil.d.ts +15 -5
  46. package/lib/service/fileUtil.js +39 -4
  47. package/lib/service/index.js +5 -1
  48. package/lib/source/LanguageError.js +1 -1
  49. package/lib/source/Source.d.ts +6 -1
  50. package/lib/source/Source.js +30 -2
  51. package/lib/source/index.js +5 -1
  52. package/lib/symbol/Symbol.d.ts +10 -10
  53. package/lib/symbol/Symbol.js +7 -6
  54. package/lib/symbol/SymbolUtil.d.ts +1 -8
  55. package/lib/symbol/SymbolUtil.js +12 -12
  56. package/lib/symbol/index.js +5 -1
  57. package/package.json +2 -2
@@ -57,7 +57,7 @@ var ProjectData;
57
57
  ctx: data.ctx ?? {},
58
58
  downloader,
59
59
  ensureParsedAndChecked: data.ensureParsedAndChecked,
60
- fs: data.fs ?? FileService_1.FileService.create(),
60
+ fs: data.fs ?? FileService_1.FileService.create('file:///cache/'),
61
61
  get: data.get ?? (() => undefined),
62
62
  logger,
63
63
  meta: data.meta ?? new MetaRegistry_1.MetaRegistry(),
@@ -74,7 +74,7 @@ var ProjectData;
74
74
  * Manage all tracked documents and errors.
75
75
  */
76
76
  class Project extends events_1.default {
77
- constructor({ cacheRoot, downloader, fs = FileService_1.FileService.create(), initializers = [], logger = Logger_1.Logger.create(), profilers = Profiler_1.ProfilerFactory.noop(), projectPath, }) {
77
+ constructor({ cacheRoot, downloader, fs = FileService_1.FileService.create(cacheRoot), initializers = [], logger = Logger_1.Logger.create(), profilers = Profiler_1.ProfilerFactory.noop(), projectPath, }) {
78
78
  super();
79
79
  _Project_cacheSaverIntervalId.set(this, void 0);
80
80
  /**
@@ -288,9 +288,9 @@ class Project extends events_1.default {
288
288
  const listDependencyFiles = async () => {
289
289
  const dependencies = await getDependencies();
290
290
  const fileUriSupporter = await FileService_1.FileUriSupporter.create(dependencies, this.logger);
291
- const spyglassUriSupporter = await FileService_1.SpyglassUriSupporter.create(dependencies, this.logger, this.cacheService.checksums.roots);
291
+ const archiveUriSupporter = await FileService_1.ArchiveUriSupporter.create(dependencies, this.logger, this.cacheService.checksums.roots);
292
292
  this.fs.register('file:', fileUriSupporter, true);
293
- this.fs.register(common_1.SpyglassUri.Protocol, spyglassUriSupporter, true);
293
+ this.fs.register(FileService_1.ArchiveUriSupporter.Protocol, archiveUriSupporter, true);
294
294
  };
295
295
  const listProjectFiles = () => new Promise(resolve => {
296
296
  __classPrivateFieldSet(this, _Project_watcherReady, false, "f");
@@ -361,7 +361,7 @@ class Project extends events_1.default {
361
361
  this.bind(addedFiles);
362
362
  }
363
363
  __profiler.task('Bind URIs');
364
- const files = [...addedFiles, ...changedFiles]; // FIXME: nbtdoc files might need to be parsed and checked before others.
364
+ const files = [...addedFiles, ...changedFiles]; // FIXME: mcdoc files might need to be parsed and checked before others.
365
365
  // const docAndNodes = (await Promise.all(files.map(uri => limit(ensureParsed, uri)))).filter((r): r is DocAndNode => !!r)
366
366
  const docAndNodes = (await Promise.all(files.map(uri => ensureParsed(uri)))).filter((r) => !!r);
367
367
  __profiler.task('Parse Files');
@@ -401,11 +401,14 @@ class Project extends events_1.default {
401
401
  resetCache() {
402
402
  return this.cacheService.reset();
403
403
  }
404
+ normalizeUri(uri) {
405
+ return this.fs.mapFromDisk(fileUtil_1.fileUtil.normalize(uri));
406
+ }
404
407
  /**
405
408
  * @returns The language ID of the file, or the file extension without the leading dot.
406
409
  */
407
410
  getLanguageID(uri) {
408
- uri = fileUtil_1.fileUtil.normalize(uri);
411
+ uri = this.normalizeUri(uri);
409
412
  const ext = fileUtil_1.fileUtil.extname(uri) ?? '.plaintext';
410
413
  return this.meta.getLanguageID(ext) ?? ext.slice(1);
411
414
  }
@@ -413,14 +416,14 @@ class Project extends events_1.default {
413
416
  * @returns The cached `TextDocument` and `AstNode` for the URI, or `undefined` when such data isn't available in cache.
414
417
  */
415
418
  get(uri) {
416
- uri = fileUtil_1.fileUtil.normalize(uri);
419
+ uri = this.normalizeUri(uri);
417
420
  return __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
418
421
  }
419
422
  /**
420
423
  * @throws FS-related errors
421
424
  */
422
425
  async ensureParsed(uri) {
423
- uri = fileUtil_1.fileUtil.normalize(uri);
426
+ uri = this.normalizeUri(uri);
424
427
  if (__classPrivateFieldGet(this, _Project_docAndNodes, "f").has(uri)) {
425
428
  return __classPrivateFieldGet(this, _Project_docAndNodes, "f").get(uri);
426
429
  }
@@ -546,7 +549,10 @@ class Project extends events_1.default {
546
549
  * Notify that a new document was opened in the editor.
547
550
  */
548
551
  onDidOpen(uri, languageID, version, content) {
549
- uri = fileUtil_1.fileUtil.normalize(uri);
552
+ uri = this.normalizeUri(uri);
553
+ if (!fileUtil_1.fileUtil.isFileUri(uri)) {
554
+ return; // We only accept `file:` scheme for client-managed URIs.
555
+ }
550
556
  __classPrivateFieldGet(this, _Project_clientManagedUris, "f").add(uri);
551
557
  const doc = vscode_languageserver_textdocument_1.TextDocument.create(uri, languageID, version, content);
552
558
  const { node } = this.parseAndCache(doc);
@@ -559,7 +565,10 @@ class Project extends events_1.default {
559
565
  * @throws If there is no `TextDocument` corresponding to the URI.
560
566
  */
561
567
  onDidChange(uri, changes, version) {
562
- uri = fileUtil_1.fileUtil.normalize(uri);
568
+ uri = this.normalizeUri(uri);
569
+ if (!fileUtil_1.fileUtil.isFileUri(uri)) {
570
+ return; // We only accept `file:` scheme for client-managed URIs.
571
+ }
563
572
  const result = this.get(uri);
564
573
  if (!result) {
565
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?`);
@@ -574,7 +583,10 @@ class Project extends events_1.default {
574
583
  * Notify that an existing document was closed in the editor.
575
584
  */
576
585
  onDidClose(uri) {
577
- uri = fileUtil_1.fileUtil.normalize(uri);
586
+ uri = this.normalizeUri(uri);
587
+ if (!fileUtil_1.fileUtil.isFileUri(uri)) {
588
+ return; // We only accept `file:` scheme for client-managed URIs.
589
+ }
578
590
  __classPrivateFieldGet(this, _Project_clientManagedUris, "f").delete(uri);
579
591
  this.tryClearingCache(uri);
580
592
  }
@@ -39,6 +39,7 @@ export declare class Service extends EventEmitter {
39
39
  getColorPresentation(file: FileNode<AstNode>, doc: TextDocument, range: Range, color: Color): ColorPresentation[];
40
40
  complete(node: FileNode<AstNode>, doc: TextDocument, offset: number, triggerCharacter?: string): import("../processor").CompletionItem[];
41
41
  dataHackPubify(initialism: string): string;
42
+ format(node: FileNode<AstNode>, doc: TextDocument, tabSize: number, insertSpaces: boolean): string;
42
43
  getHover(file: FileNode<AstNode>, doc: TextDocument, offset: number): Hover | undefined;
43
44
  getInlayHints(node: FileNode<AstNode>, doc: TextDocument, range?: Range): InlayHint[];
44
45
  getSignatureHelp(node: FileNode<AstNode>, doc: TextDocument, offset: number): SignatureHelp | undefined;
@@ -48,8 +49,7 @@ export declare class Service extends EventEmitter {
48
49
  *
49
50
  * @returns Symbol locations of the selected symbol at `offset`, or `undefined` if there's no symbol at `offset`.
50
51
  */
51
- getSymbolLocations(file: FileNode<AstNode>, doc: TextDocument, offset: number, searchedUsages?: readonly SymbolUsageType[], currentFileOnly?: boolean): SymbolLocations | undefined;
52
- format(node: FileNode<AstNode>, doc: TextDocument, tabSize: number, insertSpaces: boolean): string;
52
+ getSymbolLocations(file: FileNode<AstNode>, doc: TextDocument, offset: number, searchedUsages?: readonly SymbolUsageType[], currentFileOnly?: boolean): Promise<SymbolLocations | undefined>;
53
53
  }
54
54
  export {};
55
55
  //# sourceMappingURL=Service.d.ts.map
@@ -12,6 +12,7 @@ const symbol_1 = require("../symbol");
12
12
  const Context_1 = require("./Context");
13
13
  const Downloader_1 = require("./Downloader");
14
14
  const FileService_1 = require("./FileService");
15
+ const fileUtil_1 = require("./fileUtil");
15
16
  const Hover_1 = require("./Hover");
16
17
  const Logger_1 = require("./Logger");
17
18
  const Profiler_1 = require("./Profiler");
@@ -19,7 +20,7 @@ const Project_1 = require("./Project");
19
20
  const SymbolLocations_1 = require("./SymbolLocations");
20
21
  /* istanbul ignore next */
21
22
  class Service extends events_1.default {
22
- constructor({ cacheRoot, downloader, fs = FileService_1.FileService.create(), initializers = [], isDebugging = false, logger = Logger_1.Logger.create(), profilers = Profiler_1.ProfilerFactory.noop(), projectPath, }) {
23
+ constructor({ cacheRoot, downloader, fs = FileService_1.FileService.create(cacheRoot), initializers = [], isDebugging = false, logger = Logger_1.Logger.create(), profilers = Profiler_1.ProfilerFactory.noop(), projectPath, }) {
23
24
  super();
24
25
  this.downloader = (downloader ?? (downloader = new Downloader_1.Downloader(cacheRoot, logger)));
25
26
  this.fs = fs;
@@ -107,6 +108,17 @@ class Service extends events_1.default {
107
108
  .map((c, i) => `${c.toUpperCase()}${secrets[i % secrets.length]}`)
108
109
  .join(' ');
109
110
  }
111
+ format(node, doc, tabSize, insertSpaces) {
112
+ try {
113
+ this.debug(`Formatting '${doc.uri}' # ${doc.version}`);
114
+ const formatter = this.project.meta.getFormatter(node.type);
115
+ return formatter(node, Context_1.FormatterContext.create(this.project, { doc, tabSize, insertSpaces }));
116
+ }
117
+ catch (e) {
118
+ this.logger.error(`[Service] [format] Failed for “${doc.uri}” #${doc.version}`, e);
119
+ throw e;
120
+ }
121
+ }
110
122
  getHover(file, doc, offset) {
111
123
  try {
112
124
  this.debug(`Getting hover for '${doc.uri}' # ${doc.version} @ ${offset}`);
@@ -166,20 +178,29 @@ class Service extends events_1.default {
166
178
  *
167
179
  * @returns Symbol locations of the selected symbol at `offset`, or `undefined` if there's no symbol at `offset`.
168
180
  */
169
- getSymbolLocations(file, doc, offset, searchedUsages = symbol_1.SymbolUsageTypes, currentFileOnly = false) {
181
+ async getSymbolLocations(file, doc, offset, searchedUsages = symbol_1.SymbolUsageTypes, currentFileOnly = false) {
170
182
  try {
171
183
  this.debug(`Getting symbol locations of usage '${searchedUsages.join(',')}' for '${doc.uri}' # ${doc.version} @ ${offset} with currentFileOnly=${currentFileOnly}`);
172
184
  let node = node_1.AstNode.findDeepestChild({ node: file, needle: offset });
173
185
  while (node) {
174
186
  const symbol = this.project.symbols.resolveAlias(node.symbol);
175
187
  if (symbol) {
176
- const locations = [];
188
+ const rawLocations = [];
177
189
  for (const usage of searchedUsages) {
178
190
  let locs = symbol[usage] ?? [];
179
191
  if (currentFileOnly) {
180
192
  locs = locs.filter(l => l.uri === doc.uri);
181
193
  }
182
- locations.push(...locs);
194
+ rawLocations.push(...locs);
195
+ }
196
+ const locations = [];
197
+ for (const loc of rawLocations) {
198
+ const mappedUri = fileUtil_1.fileUtil.isFileUri(loc.uri)
199
+ ? loc.uri
200
+ : await this.project.fs.mapToDisk(loc.uri);
201
+ if (mappedUri) {
202
+ locations.push({ ...loc, uri: mappedUri });
203
+ }
183
204
  }
184
205
  return SymbolLocations_1.SymbolLocations.create(node.range, locations.length ? locations : undefined);
185
206
  }
@@ -191,17 +212,6 @@ class Service extends events_1.default {
191
212
  }
192
213
  return undefined;
193
214
  }
194
- format(node, doc, tabSize, insertSpaces) {
195
- try {
196
- this.debug(`Formatting '${doc.uri}' # ${doc.version}`);
197
- const formatter = this.project.meta.getFormatter(node.type);
198
- return formatter(node, Context_1.FormatterContext.create(this.project, { doc, tabSize, insertSpaces }));
199
- }
200
- catch (e) {
201
- this.logger.error(`[Service] [format] Failed for “${doc.uri}” #${doc.version}`, e);
202
- throw e;
203
- }
204
- }
205
215
  }
206
216
  exports.Service = Service;
207
217
  //# sourceMappingURL=Service.js.map
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type fs from 'fs';
2
+ /// <reference types="node" />
3
3
  import { URL as Uri } from 'url';
4
4
  export declare type RootUriString = `${string}/`;
5
5
  export declare type FileExtension = `.${string}`;
@@ -41,6 +41,10 @@ export declare namespace fileUtil {
41
41
  * @returns The part from the last `.` to the end of the URI, or `undefined` if no dots exist. No special treatment for leading dots.
42
42
  */
43
43
  function extname(value: string): FileExtension | undefined;
44
+ /**
45
+ * @returns The part from the last `/` to the end of the URI.
46
+ */
47
+ function basename(uri: string): string;
44
48
  /**
45
49
  * @param fileUri A file URI.
46
50
  * @returns The corresponding file path of the `fileUri` in platform-specific format.
@@ -59,7 +63,7 @@ export declare namespace fileUtil {
59
63
  *
60
64
  * @param mode Default to `0o777` (`rwxrwxrwx`)
61
65
  */
62
- function ensureDir(path: PathLike, mode?: fs.Mode): Promise<void>;
66
+ function ensureDir(path: PathLike, mode?: number): Promise<void>;
63
67
  /**
64
68
  * @throws
65
69
  *
@@ -67,18 +71,24 @@ export declare namespace fileUtil {
67
71
  *
68
72
  * @param mode Default to `0o777` (`rwxrwxrwx`)
69
73
  */
70
- function ensureParentOfFile(path: PathLike, mode?: fs.Mode): Promise<void>;
74
+ function ensureParentOfFile(path: PathLike, mode?: number): Promise<void>;
75
+ function chmod(path: PathLike, mode: number): Promise<void>;
76
+ function ensureWritable(path: PathLike): Promise<void>;
77
+ function markReadOnly(path: PathLike): Promise<void>;
71
78
  function readFile(path: PathLike): Promise<Buffer>;
72
79
  /**
73
80
  * @throws
74
81
  *
75
- * The directory of the file will be created recursively if it doesn'texist.
82
+ * The directory of the file will be created recursively if it doesn't exist.
83
+ *
84
+ * The target file will be given permission `0o666` (`rw-rw-rw-`) before being written into, and changed back to the
85
+ * specified `mode`.
76
86
  *
77
87
  * * Encoding: `utf-8`
78
88
  * * Mode: `0o666` (`rw-rw-rw-`)
79
89
  * * Flag: `w`
80
90
  */
81
- function writeFile(path: PathLike, data: Buffer | string): Promise<void>;
91
+ function writeFile(path: PathLike, data: Buffer | string, mode?: number): Promise<void>;
82
92
  /**
83
93
  * @throws
84
94
  */
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -93,6 +97,14 @@ var fileUtil;
93
97
  return i >= 0 ? value.slice(i) : undefined;
94
98
  }
95
99
  fileUtil.extname = extname;
100
+ /**
101
+ * @returns The part from the last `/` to the end of the URI.
102
+ */
103
+ function basename(uri) {
104
+ const i = uri.lastIndexOf('/');
105
+ return i >= 0 ? uri.slice(i + 1) : uri;
106
+ }
107
+ fileUtil.basename = basename;
96
108
  /* istanbul ignore next */
97
109
  /**
98
110
  * @param fileUri A file URI.
@@ -172,6 +184,25 @@ var fileUtil;
172
184
  return ensureDir(getParentOfFile(path), mode);
173
185
  }
174
186
  fileUtil.ensureParentOfFile = ensureParentOfFile;
187
+ async function chmod(path, mode) {
188
+ return fs_1.promises.chmod(toFsPathLike(path), mode);
189
+ }
190
+ fileUtil.chmod = chmod;
191
+ async function ensureWritable(path) {
192
+ try {
193
+ await chmod(path, 0o666);
194
+ }
195
+ catch (e) {
196
+ if (!(0, common_1.isEnoent)(e)) {
197
+ throw e;
198
+ }
199
+ }
200
+ }
201
+ fileUtil.ensureWritable = ensureWritable;
202
+ async function markReadOnly(path) {
203
+ return chmod(path, 0o444);
204
+ }
205
+ fileUtil.markReadOnly = markReadOnly;
175
206
  async function readFile(path) {
176
207
  return fs_1.promises.readFile(toFsPathLike(path));
177
208
  }
@@ -180,15 +211,19 @@ var fileUtil;
180
211
  /**
181
212
  * @throws
182
213
  *
183
- * The directory of the file will be created recursively if it doesn'texist.
214
+ * The directory of the file will be created recursively if it doesn't exist.
215
+ *
216
+ * The target file will be given permission `0o666` (`rw-rw-rw-`) before being written into, and changed back to the
217
+ * specified `mode`.
184
218
  *
185
219
  * * Encoding: `utf-8`
186
220
  * * Mode: `0o666` (`rw-rw-rw-`)
187
221
  * * Flag: `w`
188
222
  */
189
- async function writeFile(path, data) {
223
+ async function writeFile(path, data, mode = 0o666) {
190
224
  await ensureParentOfFile(path);
191
- return fs_1.promises.writeFile(toFsPathLike(path), data);
225
+ await ensureWritable(path);
226
+ return fs_1.promises.writeFile(toFsPathLike(path), data, { mode });
192
227
  }
193
228
  fileUtil.writeFile = writeFile;
194
229
  /* istanbul ignore next */
@@ -2,7 +2,11 @@
2
2
  /* istanbul ignore file */
3
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
4
  if (k2 === undefined) k2 = k;
5
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
6
10
  }) : (function(o, m, k, k2) {
7
11
  if (k2 === undefined) k2 = k;
8
12
  o[k2] = m[k];
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LanguageError = void 0;
4
4
  var LanguageError;
5
5
  (function (LanguageError) {
6
- function create(message, range, severity = 3 /* Error */, info) {
6
+ function create(message, range, severity = 3 /* ErrorSeverity.Error */, info) {
7
7
  const ans = { range, message, severity };
8
8
  if (info) {
9
9
  ans.info = info;
@@ -35,9 +35,12 @@ export declare class ReadonlySource {
35
35
  /**
36
36
  * If the `expectedValue` is right after the cursor, returns `true`. Otherwise returns `false`.
37
37
  *
38
+ * @param offset Defaults to 0.
39
+ *
38
40
  * @see {@link Source.trySkip}
39
41
  */
40
- tryPeek(expectedValue: string): boolean;
42
+ tryPeek(expectedValue: string, offset?: number): boolean;
43
+ tryPeekAfterWhitespace(expectedValue: string): boolean;
41
44
  peekUntil(...terminators: string[]): string;
42
45
  peekLine(): string;
43
46
  hasNonSpaceAheadInLine(): boolean;
@@ -91,6 +94,8 @@ export declare class Source extends ReadonlySource {
91
94
  skipSpace(): this;
92
95
  readWhitespace(): string;
93
96
  skipWhitespace(): this;
97
+ readIf(predicate: (this: void, char: string) => boolean): string;
98
+ skipIf(predicate: (this: void, char: string) => boolean): this;
94
99
  /**
95
100
  * @param terminators Ending character. Will not be skipped or included in the result.
96
101
  */
@@ -40,10 +40,20 @@ class ReadonlySource {
40
40
  /**
41
41
  * If the `expectedValue` is right after the cursor, returns `true`. Otherwise returns `false`.
42
42
  *
43
+ * @param offset Defaults to 0.
44
+ *
43
45
  * @see {@link Source.trySkip}
44
46
  */
45
- tryPeek(expectedValue) {
46
- return this.peek(expectedValue.length) === expectedValue;
47
+ tryPeek(expectedValue, offset = 0) {
48
+ return this.peek(expectedValue.length, offset) === expectedValue;
49
+ }
50
+ tryPeekAfterWhitespace(expectedValue) {
51
+ const maxOffset = this.string.length - this.innerCursor;
52
+ let offset = 0;
53
+ while (offset < maxOffset && Source.isWhitespace(this.peek(1, offset))) {
54
+ offset++;
55
+ }
56
+ return this.tryPeek(expectedValue, offset);
47
57
  }
48
58
  peekUntil(...terminators) {
49
59
  let ans = '';
@@ -191,6 +201,24 @@ class Source extends ReadonlySource {
191
201
  }
192
202
  return this;
193
203
  }
204
+ readIf(predicate) {
205
+ let ans = '';
206
+ while (this.canRead()) {
207
+ const c = this.peek();
208
+ if (predicate(c)) {
209
+ this.skip();
210
+ ans += c;
211
+ }
212
+ else {
213
+ break;
214
+ }
215
+ }
216
+ return ans;
217
+ }
218
+ skipIf(predicate) {
219
+ this.readIf(predicate);
220
+ return this;
221
+ }
194
222
  /**
195
223
  * @param terminators Ending character. Will not be skipped or included in the result.
196
224
  */
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];