@atscript/core 0.0.17 → 0.0.19

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/dist/index.cjs CHANGED
@@ -334,11 +334,11 @@ var Token = class Token {
334
334
  * All the import tokens must be marked with this flag
335
335
  */ _define_property$12(this, "imported", void 0);
336
336
  /**
337
- * All the props must be marked with this flag
338
- */ _define_property$12(this, "isProp", void 0);
339
- /**
340
337
  * Refs chained via . or ["propName"] are marked with this flag
341
338
  */ _define_property$12(this, "isChain", void 0);
339
+ /**
340
+ * Prop patterns ([*] or [regexp]) are storing patterns here
341
+ */ _define_property$12(this, "pattern", void 0);
342
342
  _define_property$12(this, "parentNode", void 0);
343
343
  /**
344
344
  * Only for annotation arguments: reference to their annotation token
@@ -509,6 +509,9 @@ var SemanticPropNode = class extends SemanticNode {
509
509
  const token = this.token("identifier");
510
510
  if (token && token.type === "text" && token.multiline) doc.registerMessage(token, "Unexpected end of string");
511
511
  }
512
+ get isPattern() {
513
+ return !!this.token("identifier")?.pattern;
514
+ }
512
515
  get nestedProps() {
513
516
  if (this.definition && isStructure(this.definition)) return this.definition.props;
514
517
  }
@@ -966,7 +969,10 @@ async function findConfigFileName(d) {
966
969
  async function loadTsConfig(configFile, forceFormat) {
967
970
  const file = await bundleTsConfig(configFile, forceFormat);
968
971
  try {
969
- return (await import((0, node_url.pathToFileURL)(file).href)).default;
972
+ return (await import(
973
+ /* @vite-ignore */
974
+ (0, node_url.pathToFileURL)(file).href
975
+ )).default;
970
976
  } catch (error) {
971
977
  console.error("Could not load config file", file, error);
972
978
  return {};
@@ -977,7 +983,10 @@ async function loadTsConfig(configFile, forceFormat) {
977
983
  async function loadConfig(configPath, forceFormat) {
978
984
  const ext = node_path.default.extname(configPath);
979
985
  try {
980
- if (SUPPORTED_JS_CONFIG_FORMATS.includes(ext)) return forceFormat ? await loadTsConfig(node_path.default.resolve(configPath), forceFormat) : (await import((0, node_url.pathToFileURL)(configPath).href)).default;
986
+ if (SUPPORTED_JS_CONFIG_FORMATS.includes(ext)) return forceFormat ? await loadTsConfig(node_path.default.resolve(configPath), forceFormat) : (await import(
987
+ /* @vite-ignore */
988
+ (0, node_url.pathToFileURL)(configPath).href
989
+ )).default;
981
990
  else if (SUPPORTED_TS_CONFIG_FORMATS.includes(ext)) {
982
991
  const rawConfigPath = node_path.default.resolve(configPath);
983
992
  return await loadTsConfig(rawConfigPath, forceFormat);
@@ -1088,9 +1097,6 @@ var NodeIterator = class NodeIterator {
1088
1097
  this.$ = this.nodes[this.i];
1089
1098
  return this;
1090
1099
  }
1091
- /** @deprecated */ killNextNode(n = 1) {
1092
- this.nodes.splice(this.i + 1, n);
1093
- }
1094
1100
  next(skip) {
1095
1101
  return this.fork().move().skip(skip);
1096
1102
  }
@@ -1270,8 +1276,8 @@ const IdentifierToken = new __prostojs_parser.BasicNode({
1270
1276
  //#region packages/core/src/tokenizer/tokens/number.token.ts
1271
1277
  const NumberToken = new __prostojs_parser.BasicNode({
1272
1278
  icon: "N",
1273
- tokens: [RegExp("\\d[\\p{ID_Continue}$]*", "u"), RegExp("[^\\p{ID_Continue}$]", "u")],
1274
- tokenOE: "consume-eject"
1279
+ tokens: [/[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/u, /[^\d]/u],
1280
+ tokenOE: "-eject"
1275
1281
  }).mapContent("text", "join-clear").onMatch((context) => {
1276
1282
  context.customData.type = "number";
1277
1283
  });
@@ -1279,7 +1285,7 @@ const NumberToken = new __prostojs_parser.BasicNode({
1279
1285
  //#endregion
1280
1286
  //#region packages/core/src/tokenizer/tokens/punctuation.token.ts
1281
1287
  const PunctuationToken = new __prostojs_parser.BasicNode({
1282
- tokens: [RegExp("(?<text>[\\n!&+,\\-./:;=?|])", "u"), ""],
1288
+ tokens: [RegExp("(?<text>[\\n!&+,./:;=?|])", "u"), ""],
1283
1289
  tokenOE: "omit-omit",
1284
1290
  icon: "..."
1285
1291
  }).onMatch((context) => {
@@ -1299,6 +1305,19 @@ const TextToken = new __prostojs_parser.BasicNode({
1299
1305
  context.customData.multiline = context.customData.end === "\n";
1300
1306
  });
1301
1307
 
1308
+ //#endregion
1309
+ //#region packages/core/src/tokenizer/tokens/regexp.token.ts
1310
+ const REGEXP_LITERAL_RE = /\/(?![/*])(?:\\.|\[.*?]|[^/\\\n\r[])*\/[dgimsuy]*/;
1311
+ const RegExpToken = new __prostojs_parser.BasicNode({
1312
+ label: "regexp",
1313
+ icon: "RG",
1314
+ tokens: [REGEXP_LITERAL_RE, ""],
1315
+ tokenOE: "omit-omit"
1316
+ }).onMatch((ctx) => {
1317
+ ctx.customData.type = "regexp";
1318
+ ctx.customData.text = ctx.matched[0];
1319
+ });
1320
+
1302
1321
  //#endregion
1303
1322
  //#region packages/core/src/tokenizer/tokens/index.ts
1304
1323
  const tokens = {
@@ -1311,6 +1330,7 @@ const tokens = {
1311
1330
  identifier: IdentifierToken,
1312
1331
  number: NumberToken,
1313
1332
  text: TextToken,
1333
+ regexp: RegExpToken,
1314
1334
  root: undefined
1315
1335
  };
1316
1336
  const root = new __prostojs_parser.BasicNode({
@@ -1318,7 +1338,7 @@ const root = new __prostojs_parser.BasicNode({
1318
1338
  skipToken: /\s/u
1319
1339
  }).addRecognizes(...tokens.comments, tokens.block, tokens.aIdentifier, tokens.identifier, tokens.text, tokens.number, tokens.punctuation);
1320
1340
  tokens.root = root;
1321
- BlockToken.addRecognizes(...tokens.comments, tokens.block, tokens.aIdentifier, tokens.identifier, tokens.text, tokens.number, tokens.punctuation);
1341
+ BlockToken.addRecognizes(tokens.regexp, ...tokens.comments, tokens.block, tokens.aIdentifier, tokens.identifier, tokens.text, tokens.number, tokens.punctuation);
1322
1342
  const mapContent = (content) => content.map((item) => {
1323
1343
  if (typeof item === "string") return {
1324
1344
  type: "unknown",
@@ -1353,6 +1373,7 @@ function $token(name, text$2) {
1353
1373
  node: name,
1354
1374
  text: text$2
1355
1375
  }],
1376
+ contains: undefined,
1356
1377
  isGlobal: false,
1357
1378
  empty: false,
1358
1379
  wrapper: undefined,
@@ -1387,6 +1408,16 @@ function $token(name, text$2) {
1387
1408
  ni.unexpected(false, `Expected empty block`);
1388
1409
  return opts.optional;
1389
1410
  }
1411
+ if (opts.contains) {
1412
+ if (!ni.$.children?.length) {
1413
+ ni.unexpected(false, `Expected ${opts.contains.map((v) => v.text).join(", ")}`);
1414
+ return opts.optional;
1415
+ }
1416
+ if (ni.$.children[0].type !== opts.contains[0].node || ni.$.children[0].text !== opts.contains[0].text) {
1417
+ ni.unexpected(false, `Expected ${opts.contains.map((v) => v.text).join(", ")}`);
1418
+ return opts.optional;
1419
+ }
1420
+ }
1390
1421
  if (opts.saveAs) target.node.saveToken(new Token(ni.$), opts.saveAs);
1391
1422
  ni.accepted();
1392
1423
  if (opts.wrapper) {
@@ -1421,6 +1452,10 @@ else return true;
1421
1452
  opts.empty = true;
1422
1453
  return this;
1423
1454
  },
1455
+ contains(t) {
1456
+ opts.contains = t.expect;
1457
+ return this;
1458
+ },
1424
1459
  skip(...p) {
1425
1460
  opts.skip = p;
1426
1461
  return this;
@@ -1825,6 +1860,52 @@ function unwrap(attr) {
1825
1860
  }
1826
1861
  };
1827
1862
  }
1863
+ function propName() {
1864
+ return { handler(ni, target) {
1865
+ switch (ni.$?.type) {
1866
+ case "identifier":
1867
+ case "text":
1868
+ target.node.saveToken(new Token(ni.$), "identifier");
1869
+ ni.accepted();
1870
+ ni.move();
1871
+ ni.skip(["\n"]);
1872
+ return true;
1873
+ case "block":
1874
+ const childrenLength = ni.$.children?.length || 0;
1875
+ const firstText = ni.$.children?.[0]?.text;
1876
+ if (ni.$.text === "[" && ni.$.children?.[0]?.type === "unknown" && childrenLength === 1 && ni.$.children[0].text === "*" || ni.$.text === "[" && ni.$.children?.[0]?.type === "regexp" && childrenLength === 1) {
1877
+ const t = new Token({
1878
+ ...ni.$,
1879
+ ...ni.$.children[0]
1880
+ });
1881
+ try {
1882
+ t.pattern = firstText === "*" ? /./ : parseRegExpLiteral(firstText);
1883
+ } catch (e) {
1884
+ ni.unexpected(false, e.message);
1885
+ return false;
1886
+ }
1887
+ target.node.saveToken(t, "identifier");
1888
+ ni.accepted();
1889
+ ni.move();
1890
+ ni.skip(["\n"]);
1891
+ return true;
1892
+ }
1893
+ if (ni.$.text === "[" && !childrenLength) ni.unexpected(false, "Wildcard \"*\" or a Regular Expression expected");
1894
+ if (ni.$.text === "[" && childrenLength > 1) ni.unexpected(false, firstText?.startsWith("/") ? "Invalid Regular Expression" : "To many arguments in prop pattern []");
1895
+ else ni.unexpected(false, "Unexpected identifier at property name");
1896
+ return false;
1897
+ default:
1898
+ ni.unexpected(false, "Unexpected identifier at property name");
1899
+ return false;
1900
+ }
1901
+ } };
1902
+ }
1903
+ function parseRegExpLiteral(literal) {
1904
+ const match = literal.match(/^\/(.+)\/([dgimsuy]*)$/);
1905
+ if (!match) throw new Error(`Invalid regexp literal: ${literal}`);
1906
+ const [, pattern, flags] = match;
1907
+ return new RegExp(pattern.replace(/\\\//g, "/"), flags);
1908
+ }
1828
1909
 
1829
1910
  //#endregion
1830
1911
  //#region packages/core/src/parser/pipes/pipes.ts
@@ -1832,9 +1913,9 @@ const ref = $pipe("ref", [refWithChain()]);
1832
1913
  const constText = defineValuePipe("const", "text", false);
1833
1914
  const constNumber = defineValuePipe("const", "number", false);
1834
1915
  const allowedValuesPipeArray = [
1916
+ constNumber,
1835
1917
  ref,
1836
- constText,
1837
- constNumber
1918
+ constText
1838
1919
  ];
1839
1920
  const tuplePipeArray = [
1840
1921
  block("[]").saveAs("identifier"),
@@ -1860,7 +1941,7 @@ const type = $pipe("type", [
1860
1941
  ]).skip("\n", ";");
1861
1942
  const props = $pipe("prop", [
1862
1943
  annotations(),
1863
- identifier().or(text()).saveAs("identifier").skip("\n"),
1944
+ propName(),
1864
1945
  pun("?").saveAs("optional").optional().skip("\n"),
1865
1946
  pun(":").skip("\n"),
1866
1947
  definition(allowedValuesPipeArray).separatedBy("&", "|").skip("\n").respectPriority(),
@@ -1902,6 +1983,16 @@ const pipes = {
1902
1983
  tuple
1903
1984
  };
1904
1985
 
1986
+ //#endregion
1987
+ //#region packages/core/src/parser/types.ts
1988
+ var TSeverity = /*#__PURE__*/ function(TSeverity$1) {
1989
+ TSeverity$1[TSeverity$1["Error"] = 1] = "Error";
1990
+ TSeverity$1[TSeverity$1["Warning"] = 2] = "Warning";
1991
+ TSeverity$1[TSeverity$1["Info"] = 3] = "Info";
1992
+ TSeverity$1[TSeverity$1["Hint"] = 4] = "Hint";
1993
+ return TSeverity$1;
1994
+ }({});
1995
+
1905
1996
  //#endregion
1906
1997
  //#region packages/core/src/token-index/blocks-index.ts
1907
1998
  function _define_property$5(obj, key, value) {
@@ -2448,6 +2539,63 @@ else if (isStructure(def) || isInterface(def) || isPrimitive(def)) def = def.pro
2448
2539
  for (const [key, token] of Array.from(this.registry.definitions.entries())) if (!refSet.has(key) && !this.exports.has(key)) tokens$1.push(token);
2449
2540
  return tokens$1;
2450
2541
  }
2542
+ renderDiagMessage(m, addSourceLinses = false, colors = false) {
2543
+ const c = {
2544
+ red: colors ? "\x1B[31m" : "",
2545
+ blue: colors ? "\x1B[34m" : "",
2546
+ cyan: colors ? "\x1B[36m" : "",
2547
+ yellow: colors ? "\x1B[33m" : "",
2548
+ dim: colors ? "\x1B[2m" : "",
2549
+ reset: colors ? "\x1B[0m" : ""
2550
+ };
2551
+ let sc = "";
2552
+ let banner = "[atscript]";
2553
+ switch (m.severity) {
2554
+ case TSeverity.Error:
2555
+ sc = c.red;
2556
+ banner += "[Error]";
2557
+ break;
2558
+ case TSeverity.Warning:
2559
+ sc = c.yellow;
2560
+ banner += "[Warning]";
2561
+ break;
2562
+ case TSeverity.Info:
2563
+ sc = "";
2564
+ banner += "[Info]";
2565
+ break;
2566
+ case TSeverity.Hint:
2567
+ sc = c.dim;
2568
+ banner += "[Hint]";
2569
+ break;
2570
+ default: sc = "";
2571
+ }
2572
+ const n = m.range.start.line + 1;
2573
+ let out = `\n${sc}${banner} ${m.message}${c.reset}` + `\nin ${c.blue}${this.id}:${n}:${m.range.start.character + 1}${c.reset}`;
2574
+ if (addSourceLinses) {
2575
+ const lines = this.text.split("\n");
2576
+ const renderLines = [
2577
+ {
2578
+ l: lines[n - 3],
2579
+ i: n - 3
2580
+ },
2581
+ {
2582
+ l: lines[n - 2],
2583
+ i: n - 2
2584
+ },
2585
+ {
2586
+ l: lines[n - 1],
2587
+ i: n - 1
2588
+ }
2589
+ ].filter(Boolean);
2590
+ const nl = String(n).length + 1;
2591
+ for (const { l, i } of renderLines) {
2592
+ const prefix = `${c.dim + c.cyan}${("0" + i).slice(-nl)} | ${c.reset}`;
2593
+ out += `\n${prefix}${l}${c.reset}`;
2594
+ }
2595
+ out += `\n${" ".repeat(nl + 3 + m.range.start.character)}${c.red}${"^".repeat(m.range.end.character - m.range.start.character)}${c.reset}`;
2596
+ }
2597
+ return out + "\n";
2598
+ }
2451
2599
  getDiagMessages() {
2452
2600
  if (!this._allMessages) {
2453
2601
  this._allMessages = [
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  interface TLexicalToken {
2
- type: 'annotation' | 'comment' | 'punctuation' | 'identifier' | 'text' | 'number' | 'block' | 'unknown';
2
+ type: 'annotation' | 'comment' | 'punctuation' | 'identifier' | 'text' | 'number' | 'block' | 'unknown' | 'regexp';
3
3
  text: string;
4
4
  children?: TLexicalToken[];
5
5
  startOffset?: number;
@@ -24,7 +24,7 @@ declare class Token {
24
24
  toString(): string;
25
25
  clone(replace: Partial<TLexicalToken>): Token;
26
26
  get text(): string;
27
- get type(): "number" | "annotation" | "comment" | "punctuation" | "identifier" | "text" | "block" | "unknown";
27
+ get type(): "number" | "annotation" | "comment" | "punctuation" | "identifier" | "text" | "block" | "unknown" | "regexp";
28
28
  get range(): {
29
29
  start: {
30
30
  line: number;
@@ -37,18 +37,48 @@ declare class Token {
37
37
  };
38
38
  get children(): TLexicalToken[];
39
39
  get hasChildren(): boolean;
40
+ /**
41
+ * This is truth if the text token was ended with a newline character
42
+ */
40
43
  get multiline(): boolean | undefined;
44
+ /**
45
+ * Set this to file path (e.g. "./src/file.as") for path token in import statement
46
+ */
41
47
  fromPath?: string;
48
+ /**
49
+ * All definitions that exported must be marked with this flag
50
+ */
42
51
  exported?: boolean;
52
+ /**
53
+ * All the definitions must be marked with this flag
54
+ */
43
55
  isDefinition?: boolean;
56
+ /**
57
+ * All the references must be marked with this flag
58
+ */
44
59
  isReference?: boolean;
60
+ /**
61
+ * All the import tokens must be marked with this flag
62
+ */
45
63
  imported?: boolean;
46
- isProp?: boolean;
64
+ /**
65
+ * Refs chained via . or ["propName"] are marked with this flag
66
+ */
47
67
  isChain?: boolean;
68
+ /**
69
+ * Prop patterns ([*] or [regexp]) are storing patterns here
70
+ */
71
+ pattern?: RegExp;
48
72
  parentNode?: SemanticNode;
49
73
  get isAnnotation(): boolean;
74
+ /**
75
+ * Only for annotation arguments: reference to their annotation token
76
+ */
50
77
  annotationRef?: Token;
51
78
  index?: number;
79
+ /**
80
+ * Block type
81
+ */
52
82
  blockType?: 'structure' | 'type' | 'import';
53
83
  }
54
84
 
@@ -62,9 +92,18 @@ declare class AtscriptRepo {
62
92
  readonly sharedConfig?: TAtscriptConfigInput | undefined;
63
93
  constructor(root?: string, sharedConfig?: TAtscriptConfigInput | undefined);
64
94
  protected configFormat?: 'esm' | 'cjs';
95
+ /**
96
+ * Configs cache
97
+ */
65
98
  protected readonly configs: Map<string, Promise<TPluginManagers>>;
99
+ /**
100
+ * .as Documents cache
101
+ */
66
102
  protected readonly atscripts: Map<string, Promise<AtscriptDoc>>;
67
103
  sharedPluginManager: TPluginManagers | undefined;
104
+ /**
105
+ * cache for raw content of config files
106
+ */
68
107
  protected configFiles: Map<string, Promise<Partial<TAtscriptConfigInput & TAtscriptConfigOutput>>>;
69
108
  getSharedPluginManager(): PluginManager | undefined;
70
109
  getPrimitivesTags(): Promise<Set<string> | undefined>;
@@ -92,7 +131,7 @@ declare class AtscriptRepo {
92
131
  checkImport(atscript: AtscriptDoc, from: Token, imports: Token[]): Promise<AtscriptDoc | undefined>;
93
132
  }
94
133
 
95
- type TPunctuation = '\n' | '&' | '+' | ',' | '\\' | '-' | '.' | '/' | ':' | '=' | '?' | '|' | ';';
134
+ type TPunctuation = '\n' | '&' | '+' | ',' | '\\' | '.' | '/' | ':' | '=' | '?' | '|' | ';';
96
135
 
97
136
  declare enum TSeverity {
98
137
  Error = 1,
@@ -219,6 +258,7 @@ interface TPrimitiveTypeArray {
219
258
  interface TPrimitiveTypeObject {
220
259
  kind: 'object';
221
260
  props: Record<string, TPrimitiveTypeDef>;
261
+ propsPatterns: Record<string, TPrimitiveTypeDef>;
222
262
  optional?: boolean;
223
263
  }
224
264
  type TPrimitiveTypeFinal = 'string' | 'number' | 'boolean' | 'void' | 'null';
@@ -245,9 +285,28 @@ declare class SemanticPrimitiveNode extends SemanticNode {
245
285
  toString(level?: number, prefix?: string): string;
246
286
  }
247
287
 
288
+ /**
289
+ * Interface for a token index system that allows adding tokens and querying the most nested token at a specific position.
290
+ */
248
291
  interface ITokensIndex {
292
+ /**
293
+ * Adds a new token to the index.
294
+ * @param token - The token to add.
295
+ */
249
296
  add: (token: Token) => void;
297
+ /**
298
+ * Finds token at a specific position.
299
+ * @param line - The line number of the position.
300
+ * @param character - The character number of the position.
301
+ * @returns The token at the given position, or `undefined` if no token is found.
302
+ */
250
303
  at: (line: number, character: number) => Token | undefined;
304
+ /**
305
+ * Finds token before specific position.
306
+ * @param line - The line number of the position.
307
+ * @param character - The character number of the position.
308
+ * @returns The token at the given position, or `undefined` if no token is found.
309
+ */
251
310
  before: (line: number, character: number) => Token | undefined;
252
311
  }
253
312
 
@@ -266,17 +325,44 @@ declare class AtscriptDoc {
266
325
  readonly registry: IdRegistry;
267
326
  semanticMessages: TMessages;
268
327
  messages: TMessages;
328
+ /**
329
+ * All the non-blocks tokens, that could be referred
330
+ */
269
331
  tokensIndex: ITokensIndex;
332
+ /**
333
+ * All the block-tokens
334
+ */
270
335
  blocksIndex: ITokensIndex;
336
+ /**
337
+ * Imports map by URI, contains from Token and imports[] tokens
338
+ */
271
339
  imports: Map<string, {
272
340
  from: Token;
273
341
  imports: Token[];
274
342
  }>;
343
+ /**
344
+ * Map of imported definitions by type/interface identifier
345
+ */
275
346
  importedDefs: Map<string, Token>;
347
+ /**
348
+ * Exported nodes by identifier
349
+ */
276
350
  readonly exports: Map<string, SemanticNode>;
351
+ /**
352
+ * Set of documents that this document depend on
353
+ */
277
354
  readonly dependencies: Set<AtscriptDoc>;
355
+ /**
356
+ * Set of documents that depend on this document
357
+ */
278
358
  readonly dependants: Set<AtscriptDoc>;
359
+ /**
360
+ * List of tokens that refer to some type or interface
361
+ */
279
362
  referred: Token[];
363
+ /**
364
+ * Map of dependencies (documents) by URI
365
+ */
280
366
  readonly dependenciesMap: Map<string, AtscriptDoc>;
281
367
  get primitives(): SemanticPrimitiveNode[];
282
368
  render(format: TAtscriptRenderFormat): Promise<TOutputWithSource[] | undefined>;
@@ -291,6 +377,27 @@ declare class AtscriptDoc {
291
377
  resolvedAnnotations: Token[];
292
378
  annotations: TAnnotationTokens[];
293
379
  registerAnnotation(annotationTokens: TAnnotationTokens): void;
380
+ /**
381
+ * Recursively resolves a type reference (and any property chain) to find the final underlying definition.
382
+ *
383
+ * This method performs a **multi-step** resolution:
384
+ * 1. Locates the declaration owner of the type (via `getDeclarationOwnerNode`).
385
+ * 2. Follows references, type aliases, or nested property chains:
386
+ * - For “Ref” nodes, it uses the identifier to look up the next type.
387
+ * - For “Type” nodes, it calls `getDefinition()` to move to the underlying structure.
388
+ * - For property chains (`chain`), it navigates nested properties or nested types,
389
+ * resolving each level until the final type is reached.
390
+ *
391
+ * @param {string} name - The name of the type or identifier to resolve.
392
+ * @param {Array<string | Token>} [chain=[]] - An optional chain of properties or tokens, each of which
393
+ * refines the path to the final type (e.g., for `SomeType.prop1.prop2`, `chain` might be `[ "prop1", "prop2" ]`).
394
+ * @returns {Object | undefined} An object containing:
395
+ * - `doc`: The `AtscriptDoc` where the final definition is located.
396
+ * - `node`: The last encountered `SemanticNode` before reaching the final underlying definition (often a `Prop` node).
397
+ * - `def`: The final resolved `SemanticNode`.
398
+ *
399
+ * If the type cannot be resolved or does not exist, returns `undefined`.
400
+ */
294
401
  unwindType(name: string, chain?: string[] | Token[], watchCb?: (def: SemanticNode) => void, _tracked?: Set<SemanticNode>): {
295
402
  doc: AtscriptDoc;
296
403
  node?: SemanticNode;
@@ -307,6 +414,22 @@ declare class AtscriptDoc {
307
414
  range: Token['range'];
308
415
  token: Token;
309
416
  }> | undefined;
417
+ /**
418
+ * Retrieves the definition (i.e., the “go to definition” target) for a given token.
419
+ *
420
+ * This method provides a **single-step** resolution of the defining token:
421
+ * - If the token is defined in the same document, returns that definition.
422
+ * - If the token is imported, follows the import to return the defining token from the relevant document.
423
+ * - If the token is a reference to a locally defined identifier, retrieves the local definition.
424
+ *
425
+ * @param {Token} token - The token for which to locate the definition.
426
+ * @returns {Object | undefined} An object containing:
427
+ * - `uri`: The file path (URI) of the document where the definition is found.
428
+ * - `doc`: The `AtscriptDoc` instance that owns the definition (if found).
429
+ * - `token`: The defining token itself (if found).
430
+ *
431
+ * If no definition is found, returns `undefined`.
432
+ */
310
433
  getDefinitionFor(token: Token): {
311
434
  uri: string;
312
435
  doc?: AtscriptDoc;
@@ -351,6 +474,25 @@ declare class AtscriptDoc {
351
474
  block: Token;
352
475
  }): void;
353
476
  registerDefinition(token?: Token, asImport?: boolean): void;
477
+ /**
478
+ * Finds the owning document and semantic node responsible for declaring a given identifier.
479
+ *
480
+ * This method checks:
481
+ * 1. Whether the identifier is a known primitive (from this document’s config).
482
+ * 2. If there's a local definition in this document's registry.
483
+ * - If the definition is imported, it resolves the `fromPath`, locates the correct `AtscriptDoc`
484
+ * in the dependency map, and recursively tries to get the declaration owner there.
485
+ * - If the definition is local (not imported), it returns the current document (`this`) along with
486
+ * the parent node that owns the definition and the token itself.
487
+ *
488
+ * @param {string} identifier - The name/identifier whose declaring node should be found.
489
+ * @returns {{ doc: AtscriptDoc; node?: SemanticNode; token?: Token } | undefined} An object containing:
490
+ * - `doc`: The `AtscriptDoc` in which the identifier was ultimately declared.
491
+ * - `node`: The parent `SemanticNode` that defines or owns the declaration (if applicable).
492
+ * - `token`: The specific token for the declaration (if applicable).
493
+ *
494
+ * If no declaration is found, returns `undefined`.
495
+ */
354
496
  getDeclarationOwnerNode(identifier: string): {
355
497
  doc: AtscriptDoc;
356
498
  node?: SemanticNode;
@@ -362,9 +504,22 @@ declare class AtscriptDoc {
362
504
  private _allMessages;
363
505
  clearMessages(): void;
364
506
  getUnusedTokens(): Token[];
507
+ renderDiagMessage(m: TMessages[number], addSourceLinses?: boolean, colors?: boolean): string;
365
508
  getDiagMessages(): TMessages;
366
509
  mergeIntersection(node: SemanticNode): SemanticNode;
367
510
  mergeDefs(_left: SemanticNode, _right: SemanticNode): [SemanticNode] | [SemanticNode, SemanticNode];
511
+ /**
512
+ * Merges two arrays of annotation tokens, ensuring that annotations from the
513
+ * `right` array take precedence over those from the `left` array.
514
+ *
515
+ * - Annotations from `right` are always included.
516
+ * - Annotations from `left` are included only if they are not already present in `right`.
517
+ * - This ensures that if an annotation exists in both arrays, the one from `right` is kept.
518
+ *
519
+ * @param left - An optional array of annotation tokens to merge (lower priority).
520
+ * @param right - An optional array of annotation tokens to merge (higher priority).
521
+ * @returns A merged array of annotation tokens, preserving order while preventing duplicates.
522
+ */
368
523
  mergeNodesAnnotations(left?: TAnnotationTokens[], right?: TAnnotationTokens[]): TAnnotationTokens[];
369
524
  }
370
525
 
@@ -445,6 +600,7 @@ declare class SemanticRefNode extends SemanticNode {
445
600
  declare class SemanticPropNode extends SemanticNode {
446
601
  constructor();
447
602
  registerAtDocument(doc: AtscriptDoc): void;
603
+ get isPattern(): boolean;
448
604
  get nestedProps(): Map<string, SemanticPropNode> | undefined;
449
605
  get nestedType(): SemanticRefNode | undefined;
450
606
  }
@@ -458,6 +614,9 @@ declare class SemanticInterfaceNode extends SemanticNode {
458
614
  declare class SemanticStructureNode extends SemanticGroup {
459
615
  constructor();
460
616
  readonly props: Map<string, SemanticPropNode>;
617
+ /**
618
+ * Shortcut to set props, used as utility
619
+ */
461
620
  setProps(props: SemanticPropNode[]): void;
462
621
  registerAtDocument(doc: AtscriptDoc): void;
463
622
  addVirtualProp(opts: {
@@ -522,8 +681,14 @@ interface TAnnotationsTree {
522
681
  [key: string]: AnnotationSpec | TAnnotationsTree;
523
682
  }
524
683
 
684
+ /**
685
+ * Defines the configuration for the Intertation parser
686
+ */
525
687
  declare function defineConfig(config: TAtscriptConfig): TAtscriptConfig;
526
688
 
689
+ /**
690
+ * Resolves nearest config file
691
+ */
527
692
  declare function resolveConfigFile(docUri: string, _root?: string): Promise<string | undefined>;
528
693
  declare function loadTsConfig(configFile: string, forceFormat?: 'cjs' | 'esm'): Promise<TAtscriptConfig>;
529
694
  declare function loadConfig(configPath: string, forceFormat?: 'cjs' | 'esm'): Promise<TAtscriptConfig>;
package/dist/index.mjs CHANGED
@@ -310,11 +310,11 @@ var Token = class Token {
310
310
  * All the import tokens must be marked with this flag
311
311
  */ _define_property$12(this, "imported", void 0);
312
312
  /**
313
- * All the props must be marked with this flag
314
- */ _define_property$12(this, "isProp", void 0);
315
- /**
316
313
  * Refs chained via . or ["propName"] are marked with this flag
317
314
  */ _define_property$12(this, "isChain", void 0);
315
+ /**
316
+ * Prop patterns ([*] or [regexp]) are storing patterns here
317
+ */ _define_property$12(this, "pattern", void 0);
318
318
  _define_property$12(this, "parentNode", void 0);
319
319
  /**
320
320
  * Only for annotation arguments: reference to their annotation token
@@ -485,6 +485,9 @@ var SemanticPropNode = class extends SemanticNode {
485
485
  const token = this.token("identifier");
486
486
  if (token && token.type === "text" && token.multiline) doc.registerMessage(token, "Unexpected end of string");
487
487
  }
488
+ get isPattern() {
489
+ return !!this.token("identifier")?.pattern;
490
+ }
488
491
  get nestedProps() {
489
492
  if (this.definition && isStructure(this.definition)) return this.definition.props;
490
493
  }
@@ -942,7 +945,10 @@ async function findConfigFileName(d) {
942
945
  async function loadTsConfig(configFile, forceFormat) {
943
946
  const file = await bundleTsConfig(configFile, forceFormat);
944
947
  try {
945
- return (await import(pathToFileURL(file).href)).default;
948
+ return (await import(
949
+ /* @vite-ignore */
950
+ pathToFileURL(file).href
951
+ )).default;
946
952
  } catch (error) {
947
953
  console.error("Could not load config file", file, error);
948
954
  return {};
@@ -953,7 +959,10 @@ async function loadTsConfig(configFile, forceFormat) {
953
959
  async function loadConfig(configPath, forceFormat) {
954
960
  const ext = path.extname(configPath);
955
961
  try {
956
- if (SUPPORTED_JS_CONFIG_FORMATS.includes(ext)) return forceFormat ? await loadTsConfig(path.resolve(configPath), forceFormat) : (await import(pathToFileURL(configPath).href)).default;
962
+ if (SUPPORTED_JS_CONFIG_FORMATS.includes(ext)) return forceFormat ? await loadTsConfig(path.resolve(configPath), forceFormat) : (await import(
963
+ /* @vite-ignore */
964
+ pathToFileURL(configPath).href
965
+ )).default;
957
966
  else if (SUPPORTED_TS_CONFIG_FORMATS.includes(ext)) {
958
967
  const rawConfigPath = path.resolve(configPath);
959
968
  return await loadTsConfig(rawConfigPath, forceFormat);
@@ -1064,9 +1073,6 @@ var NodeIterator = class NodeIterator {
1064
1073
  this.$ = this.nodes[this.i];
1065
1074
  return this;
1066
1075
  }
1067
- /** @deprecated */ killNextNode(n = 1) {
1068
- this.nodes.splice(this.i + 1, n);
1069
- }
1070
1076
  next(skip) {
1071
1077
  return this.fork().move().skip(skip);
1072
1078
  }
@@ -1246,8 +1252,8 @@ const IdentifierToken = new BasicNode({
1246
1252
  //#region packages/core/src/tokenizer/tokens/number.token.ts
1247
1253
  const NumberToken = new BasicNode({
1248
1254
  icon: "N",
1249
- tokens: [RegExp("\\d[\\p{ID_Continue}$]*", "u"), RegExp("[^\\p{ID_Continue}$]", "u")],
1250
- tokenOE: "consume-eject"
1255
+ tokens: [/[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/u, /[^\d]/u],
1256
+ tokenOE: "-eject"
1251
1257
  }).mapContent("text", "join-clear").onMatch((context) => {
1252
1258
  context.customData.type = "number";
1253
1259
  });
@@ -1255,7 +1261,7 @@ const NumberToken = new BasicNode({
1255
1261
  //#endregion
1256
1262
  //#region packages/core/src/tokenizer/tokens/punctuation.token.ts
1257
1263
  const PunctuationToken = new BasicNode({
1258
- tokens: [RegExp("(?<text>[\\n!&+,\\-./:;=?|])", "u"), ""],
1264
+ tokens: [RegExp("(?<text>[\\n!&+,./:;=?|])", "u"), ""],
1259
1265
  tokenOE: "omit-omit",
1260
1266
  icon: "..."
1261
1267
  }).onMatch((context) => {
@@ -1275,6 +1281,19 @@ const TextToken = new BasicNode({
1275
1281
  context.customData.multiline = context.customData.end === "\n";
1276
1282
  });
1277
1283
 
1284
+ //#endregion
1285
+ //#region packages/core/src/tokenizer/tokens/regexp.token.ts
1286
+ const REGEXP_LITERAL_RE = /\/(?![/*])(?:\\.|\[.*?]|[^/\\\n\r[])*\/[dgimsuy]*/;
1287
+ const RegExpToken = new BasicNode({
1288
+ label: "regexp",
1289
+ icon: "RG",
1290
+ tokens: [REGEXP_LITERAL_RE, ""],
1291
+ tokenOE: "omit-omit"
1292
+ }).onMatch((ctx) => {
1293
+ ctx.customData.type = "regexp";
1294
+ ctx.customData.text = ctx.matched[0];
1295
+ });
1296
+
1278
1297
  //#endregion
1279
1298
  //#region packages/core/src/tokenizer/tokens/index.ts
1280
1299
  const tokens = {
@@ -1287,6 +1306,7 @@ const tokens = {
1287
1306
  identifier: IdentifierToken,
1288
1307
  number: NumberToken,
1289
1308
  text: TextToken,
1309
+ regexp: RegExpToken,
1290
1310
  root: undefined
1291
1311
  };
1292
1312
  const root = new BasicNode({
@@ -1294,7 +1314,7 @@ const root = new BasicNode({
1294
1314
  skipToken: /\s/u
1295
1315
  }).addRecognizes(...tokens.comments, tokens.block, tokens.aIdentifier, tokens.identifier, tokens.text, tokens.number, tokens.punctuation);
1296
1316
  tokens.root = root;
1297
- BlockToken.addRecognizes(...tokens.comments, tokens.block, tokens.aIdentifier, tokens.identifier, tokens.text, tokens.number, tokens.punctuation);
1317
+ BlockToken.addRecognizes(tokens.regexp, ...tokens.comments, tokens.block, tokens.aIdentifier, tokens.identifier, tokens.text, tokens.number, tokens.punctuation);
1298
1318
  const mapContent = (content) => content.map((item) => {
1299
1319
  if (typeof item === "string") return {
1300
1320
  type: "unknown",
@@ -1329,6 +1349,7 @@ function $token(name, text$2) {
1329
1349
  node: name,
1330
1350
  text: text$2
1331
1351
  }],
1352
+ contains: undefined,
1332
1353
  isGlobal: false,
1333
1354
  empty: false,
1334
1355
  wrapper: undefined,
@@ -1363,6 +1384,16 @@ function $token(name, text$2) {
1363
1384
  ni.unexpected(false, `Expected empty block`);
1364
1385
  return opts.optional;
1365
1386
  }
1387
+ if (opts.contains) {
1388
+ if (!ni.$.children?.length) {
1389
+ ni.unexpected(false, `Expected ${opts.contains.map((v) => v.text).join(", ")}`);
1390
+ return opts.optional;
1391
+ }
1392
+ if (ni.$.children[0].type !== opts.contains[0].node || ni.$.children[0].text !== opts.contains[0].text) {
1393
+ ni.unexpected(false, `Expected ${opts.contains.map((v) => v.text).join(", ")}`);
1394
+ return opts.optional;
1395
+ }
1396
+ }
1366
1397
  if (opts.saveAs) target.node.saveToken(new Token(ni.$), opts.saveAs);
1367
1398
  ni.accepted();
1368
1399
  if (opts.wrapper) {
@@ -1397,6 +1428,10 @@ else return true;
1397
1428
  opts.empty = true;
1398
1429
  return this;
1399
1430
  },
1431
+ contains(t) {
1432
+ opts.contains = t.expect;
1433
+ return this;
1434
+ },
1400
1435
  skip(...p) {
1401
1436
  opts.skip = p;
1402
1437
  return this;
@@ -1801,6 +1836,52 @@ function unwrap(attr) {
1801
1836
  }
1802
1837
  };
1803
1838
  }
1839
+ function propName() {
1840
+ return { handler(ni, target) {
1841
+ switch (ni.$?.type) {
1842
+ case "identifier":
1843
+ case "text":
1844
+ target.node.saveToken(new Token(ni.$), "identifier");
1845
+ ni.accepted();
1846
+ ni.move();
1847
+ ni.skip(["\n"]);
1848
+ return true;
1849
+ case "block":
1850
+ const childrenLength = ni.$.children?.length || 0;
1851
+ const firstText = ni.$.children?.[0]?.text;
1852
+ if (ni.$.text === "[" && ni.$.children?.[0]?.type === "unknown" && childrenLength === 1 && ni.$.children[0].text === "*" || ni.$.text === "[" && ni.$.children?.[0]?.type === "regexp" && childrenLength === 1) {
1853
+ const t = new Token({
1854
+ ...ni.$,
1855
+ ...ni.$.children[0]
1856
+ });
1857
+ try {
1858
+ t.pattern = firstText === "*" ? /./ : parseRegExpLiteral(firstText);
1859
+ } catch (e) {
1860
+ ni.unexpected(false, e.message);
1861
+ return false;
1862
+ }
1863
+ target.node.saveToken(t, "identifier");
1864
+ ni.accepted();
1865
+ ni.move();
1866
+ ni.skip(["\n"]);
1867
+ return true;
1868
+ }
1869
+ if (ni.$.text === "[" && !childrenLength) ni.unexpected(false, "Wildcard \"*\" or a Regular Expression expected");
1870
+ if (ni.$.text === "[" && childrenLength > 1) ni.unexpected(false, firstText?.startsWith("/") ? "Invalid Regular Expression" : "To many arguments in prop pattern []");
1871
+ else ni.unexpected(false, "Unexpected identifier at property name");
1872
+ return false;
1873
+ default:
1874
+ ni.unexpected(false, "Unexpected identifier at property name");
1875
+ return false;
1876
+ }
1877
+ } };
1878
+ }
1879
+ function parseRegExpLiteral(literal) {
1880
+ const match = literal.match(/^\/(.+)\/([dgimsuy]*)$/);
1881
+ if (!match) throw new Error(`Invalid regexp literal: ${literal}`);
1882
+ const [, pattern, flags] = match;
1883
+ return new RegExp(pattern.replace(/\\\//g, "/"), flags);
1884
+ }
1804
1885
 
1805
1886
  //#endregion
1806
1887
  //#region packages/core/src/parser/pipes/pipes.ts
@@ -1808,9 +1889,9 @@ const ref = $pipe("ref", [refWithChain()]);
1808
1889
  const constText = defineValuePipe("const", "text", false);
1809
1890
  const constNumber = defineValuePipe("const", "number", false);
1810
1891
  const allowedValuesPipeArray = [
1892
+ constNumber,
1811
1893
  ref,
1812
- constText,
1813
- constNumber
1894
+ constText
1814
1895
  ];
1815
1896
  const tuplePipeArray = [
1816
1897
  block("[]").saveAs("identifier"),
@@ -1836,7 +1917,7 @@ const type = $pipe("type", [
1836
1917
  ]).skip("\n", ";");
1837
1918
  const props = $pipe("prop", [
1838
1919
  annotations(),
1839
- identifier().or(text()).saveAs("identifier").skip("\n"),
1920
+ propName(),
1840
1921
  pun("?").saveAs("optional").optional().skip("\n"),
1841
1922
  pun(":").skip("\n"),
1842
1923
  definition(allowedValuesPipeArray).separatedBy("&", "|").skip("\n").respectPriority(),
@@ -1878,6 +1959,16 @@ const pipes = {
1878
1959
  tuple
1879
1960
  };
1880
1961
 
1962
+ //#endregion
1963
+ //#region packages/core/src/parser/types.ts
1964
+ var TSeverity = /*#__PURE__*/ function(TSeverity$1) {
1965
+ TSeverity$1[TSeverity$1["Error"] = 1] = "Error";
1966
+ TSeverity$1[TSeverity$1["Warning"] = 2] = "Warning";
1967
+ TSeverity$1[TSeverity$1["Info"] = 3] = "Info";
1968
+ TSeverity$1[TSeverity$1["Hint"] = 4] = "Hint";
1969
+ return TSeverity$1;
1970
+ }({});
1971
+
1881
1972
  //#endregion
1882
1973
  //#region packages/core/src/token-index/blocks-index.ts
1883
1974
  function _define_property$5(obj, key, value) {
@@ -2424,6 +2515,63 @@ else if (isStructure(def) || isInterface(def) || isPrimitive(def)) def = def.pro
2424
2515
  for (const [key, token] of Array.from(this.registry.definitions.entries())) if (!refSet.has(key) && !this.exports.has(key)) tokens$1.push(token);
2425
2516
  return tokens$1;
2426
2517
  }
2518
+ renderDiagMessage(m, addSourceLinses = false, colors = false) {
2519
+ const c = {
2520
+ red: colors ? "\x1B[31m" : "",
2521
+ blue: colors ? "\x1B[34m" : "",
2522
+ cyan: colors ? "\x1B[36m" : "",
2523
+ yellow: colors ? "\x1B[33m" : "",
2524
+ dim: colors ? "\x1B[2m" : "",
2525
+ reset: colors ? "\x1B[0m" : ""
2526
+ };
2527
+ let sc = "";
2528
+ let banner = "[atscript]";
2529
+ switch (m.severity) {
2530
+ case TSeverity.Error:
2531
+ sc = c.red;
2532
+ banner += "[Error]";
2533
+ break;
2534
+ case TSeverity.Warning:
2535
+ sc = c.yellow;
2536
+ banner += "[Warning]";
2537
+ break;
2538
+ case TSeverity.Info:
2539
+ sc = "";
2540
+ banner += "[Info]";
2541
+ break;
2542
+ case TSeverity.Hint:
2543
+ sc = c.dim;
2544
+ banner += "[Hint]";
2545
+ break;
2546
+ default: sc = "";
2547
+ }
2548
+ const n = m.range.start.line + 1;
2549
+ let out = `\n${sc}${banner} ${m.message}${c.reset}` + `\nin ${c.blue}${this.id}:${n}:${m.range.start.character + 1}${c.reset}`;
2550
+ if (addSourceLinses) {
2551
+ const lines = this.text.split("\n");
2552
+ const renderLines = [
2553
+ {
2554
+ l: lines[n - 3],
2555
+ i: n - 3
2556
+ },
2557
+ {
2558
+ l: lines[n - 2],
2559
+ i: n - 2
2560
+ },
2561
+ {
2562
+ l: lines[n - 1],
2563
+ i: n - 1
2564
+ }
2565
+ ].filter(Boolean);
2566
+ const nl = String(n).length + 1;
2567
+ for (const { l, i } of renderLines) {
2568
+ const prefix = `${c.dim + c.cyan}${("0" + i).slice(-nl)} | ${c.reset}`;
2569
+ out += `\n${prefix}${l}${c.reset}`;
2570
+ }
2571
+ out += `\n${" ".repeat(nl + 3 + m.range.start.character)}${c.red}${"^".repeat(m.range.end.character - m.range.start.character)}${c.reset}`;
2572
+ }
2573
+ return out + "\n";
2574
+ }
2427
2575
  getDiagMessages() {
2428
2576
  if (!this._allMessages) {
2429
2577
  this._allMessages = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/core",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Core library for Atscript parsing and file generation.",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.10.5",
45
- "vitest": "^3.0.0"
45
+ "vitest": "3.2.4"
46
46
  },
47
47
  "scripts": {
48
48
  "pub": "pnpm publish --access public",