@azerothjs/typescript-plugin 0.4.0-beta.3

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 (3) hide show
  1. package/README.md +88 -0
  2. package/dist/index.js +1132 -0
  3. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @azerothjs/typescript-plugin
2
+
3
+ ## Overview
4
+
5
+ A TypeScript language-service plugin that teaches `tsserver` (the engine behind
6
+ VS Code's built-in TypeScript support, and any editor that uses it) to resolve
7
+ `.azeroth` imports from `.ts`/`.tsx` files with their REAL exported types -
8
+ default, named, and type exports. It is the AzerothJS counterpart to
9
+ `@vue/typescript-plugin`.
10
+
11
+ With the plugin installed a consuming app can delete its hand-written
12
+ `declare module '*.azeroth'` shims: a barrel like
13
+
14
+ ```ts
15
+ export { default as Breadcrumb } from './breadcrumb.component.azeroth';
16
+ export type { BreadcrumbCrumb } from './breadcrumb.component.azeroth';
17
+ ```
18
+
19
+ type-checks against the component's actual signature instead of `any`.
20
+
21
+ ## Install
22
+
23
+ ```sh
24
+ npm i -D @azerothjs/typescript-plugin
25
+ ```
26
+
27
+ Register it in the consuming project's `tsconfig.json`:
28
+
29
+ ```json
30
+ {
31
+ "compilerOptions": {
32
+ "plugins": [{ "name": "@azerothjs/typescript-plugin" }]
33
+ }
34
+ }
35
+ ```
36
+
37
+ In VS Code, also select **"Use Workspace Version"** of TypeScript (or rely on
38
+ the AzerothJS VS Code extension, which contributes the plugin automatically), so
39
+ the editor's TypeScript server loads the plugin.
40
+
41
+ ## How it works
42
+
43
+ The plugin reuses the same virtual-code pipeline as the editor language server
44
+ (`@azerothjs/language-service`): a `.azeroth` file is compiled to a virtual
45
+ TypeScript module whose markup is rewritten to `h()` calls and whose surrounding
46
+ code - every `export` included - is preserved verbatim. The plugin decorates the
47
+ host so that:
48
+
49
+ - an `import './x.azeroth'` specifier resolves to a synthetic `x.azeroth.ts`;
50
+ - loading that synthetic file returns the compiled virtual module.
51
+
52
+ Because the virtual module carries the file's real exported declarations,
53
+ TypeScript infers real types across the `.ts` -> `.azeroth` boundary.
54
+
55
+ ## Scope: editors, not `tsc`
56
+
57
+ TypeScript language-service plugins run **only inside `tsserver`** (editors), not
58
+ inside the command-line `tsc`. This is a TypeScript limitation, not a choice
59
+ here - it is also why `vue-tsc` exists. So:
60
+
61
+ - In the editor, this plugin gives real `.azeroth` types with no shim.
62
+ - For a command-line type-check gate over `.azeroth` files, use `azeroth-tsc`
63
+ (from `@azerothjs/language-server`).
64
+
65
+ A single combined `.ts` + `.azeroth` command-line checker (the `vue-tsc`
66
+ equivalent) is tracked as a follow-up.
67
+
68
+ ## Building
69
+
70
+ ```sh
71
+ npm run build -w @azerothjs/typescript-plugin
72
+ ```
73
+
74
+ `tsserver` loads plugins with `require()`, so the entry must be CommonJS; the
75
+ AzerothJS packages it reuses are ESM. `esbuild` bundles `src/index.ts` and those
76
+ ESM dependencies into a single self-contained `dist/index.js` (with `typescript`
77
+ left external - `tsserver` passes its own copy to the plugin factory).
78
+
79
+ ## Testing
80
+
81
+ ```sh
82
+ npx vitest run test/typescript-plugin
83
+ ```
84
+
85
+ The test drives the plugin's host decoration through a constructed
86
+ `ts.LanguageService` (what `tsserver` builds) over a fixture with no `.azeroth`
87
+ shim, asserting default/named/type imports resolve and that a genuine type error
88
+ still surfaces.
package/dist/index.js ADDED
@@ -0,0 +1,1132 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/decorate.ts
26
+ var import_node_path = __toESM(require("node:path"));
27
+
28
+ // ../language-service/src/ts-project.ts
29
+ var import_typescript = __toESM(require("typescript"), 1);
30
+
31
+ // ../compiler/src/scanner.ts
32
+ function isWhitespace(ch) {
33
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
34
+ }
35
+ function isIdentStart(ch) {
36
+ return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$";
37
+ }
38
+ function isIdentPart(ch) {
39
+ return isIdentStart(ch) || ch >= "0" && ch <= "9";
40
+ }
41
+ function skipLineComment(src, i) {
42
+ i += 2;
43
+ while (i < src.length && src[i] !== "\n") {
44
+ i++;
45
+ }
46
+ return i;
47
+ }
48
+ function skipBlockComment(src, i) {
49
+ i += 2;
50
+ while (i < src.length && !(src[i] === "*" && src[i + 1] === "/")) {
51
+ i++;
52
+ }
53
+ return Math.min(i + 2, src.length);
54
+ }
55
+ function skipString(src, i) {
56
+ const quote = src[i];
57
+ i++;
58
+ while (i < src.length) {
59
+ const ch = src[i];
60
+ if (ch === "\\") {
61
+ i += 2;
62
+ continue;
63
+ }
64
+ if (ch === quote) {
65
+ return i + 1;
66
+ }
67
+ i++;
68
+ }
69
+ return i;
70
+ }
71
+ function skipTemplate(src, i) {
72
+ i++;
73
+ while (i < src.length) {
74
+ const ch = src[i];
75
+ if (ch === "\\") {
76
+ i += 2;
77
+ continue;
78
+ }
79
+ if (ch === "`") {
80
+ return i + 1;
81
+ }
82
+ if (ch === "$" && src[i + 1] === "{") {
83
+ i = skipBalanced(src, i + 1);
84
+ continue;
85
+ }
86
+ i++;
87
+ }
88
+ return i;
89
+ }
90
+ function skipRegex(src, i) {
91
+ i++;
92
+ let inClass = false;
93
+ while (i < src.length) {
94
+ const ch = src[i];
95
+ if (ch === "\\") {
96
+ i += 2;
97
+ continue;
98
+ }
99
+ if (ch === "[") {
100
+ inClass = true;
101
+ } else if (ch === "]") {
102
+ inClass = false;
103
+ } else if (ch === "/" && !inClass) {
104
+ i++;
105
+ break;
106
+ } else if (ch === "\n") {
107
+ break;
108
+ }
109
+ i++;
110
+ }
111
+ while (i < src.length && isIdentPart(src[i])) {
112
+ i++;
113
+ }
114
+ return i;
115
+ }
116
+ function skipBalanced(src, openIndex) {
117
+ const open = src[openIndex];
118
+ const close = open === "(" ? ")" : open === "[" ? "]" : "}";
119
+ let depth = 0;
120
+ let i = openIndex;
121
+ while (i < src.length) {
122
+ const ch = src[i];
123
+ if (ch === "/" && src[i + 1] === "/") {
124
+ i = skipLineComment(src, i);
125
+ continue;
126
+ }
127
+ if (ch === "/" && src[i + 1] === "*") {
128
+ i = skipBlockComment(src, i);
129
+ continue;
130
+ }
131
+ if (ch === '"' || ch === "'") {
132
+ i = skipString(src, i);
133
+ continue;
134
+ }
135
+ if (ch === "`") {
136
+ i = skipTemplate(src, i);
137
+ continue;
138
+ }
139
+ if (ch === open) {
140
+ depth++;
141
+ i++;
142
+ continue;
143
+ }
144
+ if (ch === close) {
145
+ depth--;
146
+ i++;
147
+ if (depth === 0) {
148
+ return i;
149
+ }
150
+ continue;
151
+ }
152
+ i++;
153
+ }
154
+ return i;
155
+ }
156
+ var EXPR_KEYWORDS = /* @__PURE__ */ new Set([
157
+ "return",
158
+ "typeof",
159
+ "instanceof",
160
+ "in",
161
+ "of",
162
+ "do",
163
+ "else",
164
+ "yield",
165
+ "await",
166
+ "case",
167
+ "delete",
168
+ "void",
169
+ "new"
170
+ ]);
171
+ var EXPR_CHARS = /* @__PURE__ */ new Set([
172
+ "",
173
+ "(",
174
+ "{",
175
+ "[",
176
+ ",",
177
+ ";",
178
+ ":",
179
+ "?",
180
+ "=",
181
+ ">",
182
+ "<",
183
+ "&",
184
+ "|",
185
+ "!",
186
+ "~",
187
+ "+",
188
+ "-",
189
+ "*",
190
+ "/",
191
+ "%",
192
+ "^",
193
+ "\n"
194
+ ]);
195
+ function isExpressionPosition(prevChar, prevWord) {
196
+ if (prevWord !== "") {
197
+ return EXPR_KEYWORDS.has(prevWord);
198
+ }
199
+ return EXPR_CHARS.has(prevChar);
200
+ }
201
+ function scanTypeParams(src, openIndex) {
202
+ let depth = 0;
203
+ let i = openIndex;
204
+ while (i < src.length) {
205
+ const ch = src[i];
206
+ if (ch === "/" && src[i + 1] === "/") {
207
+ i = skipLineComment(src, i);
208
+ continue;
209
+ }
210
+ if (ch === "/" && src[i + 1] === "*") {
211
+ i = skipBlockComment(src, i);
212
+ continue;
213
+ }
214
+ if (ch === '"' || ch === "'") {
215
+ i = skipString(src, i);
216
+ continue;
217
+ }
218
+ if (ch === "`") {
219
+ i = skipTemplate(src, i);
220
+ continue;
221
+ }
222
+ if (ch === "(" || ch === "[" || ch === "{") {
223
+ i = skipBalanced(src, i);
224
+ continue;
225
+ }
226
+ if (ch === "=" && src[i + 1] === ">") {
227
+ i += 2;
228
+ continue;
229
+ }
230
+ if (ch === "<") {
231
+ depth++;
232
+ i++;
233
+ continue;
234
+ }
235
+ if (ch === ">") {
236
+ depth--;
237
+ i++;
238
+ if (depth === 0) {
239
+ return i;
240
+ }
241
+ continue;
242
+ }
243
+ i++;
244
+ }
245
+ return -1;
246
+ }
247
+ function tryGenericArrow(src, i) {
248
+ const afterAngles = scanTypeParams(src, i);
249
+ if (afterAngles === -1) {
250
+ return -1;
251
+ }
252
+ let k = afterAngles;
253
+ while (k < src.length && isWhitespace(src[k])) {
254
+ k++;
255
+ }
256
+ if (src[k] !== "(") {
257
+ return -1;
258
+ }
259
+ let m = skipBalanced(src, k);
260
+ while (m < src.length && isWhitespace(src[m])) {
261
+ m++;
262
+ }
263
+ if (src[m] === ":" || src[m] === "=" && src[m + 1] === ">") {
264
+ return afterAngles;
265
+ }
266
+ return -1;
267
+ }
268
+ function findMarkupStart(src, from) {
269
+ let i = from;
270
+ let prevChar = "";
271
+ let prevWord = "";
272
+ while (i < src.length) {
273
+ const ch = src[i];
274
+ if (ch === "/" && src[i + 1] === "/") {
275
+ i = skipLineComment(src, i);
276
+ continue;
277
+ }
278
+ if (ch === "/" && src[i + 1] === "*") {
279
+ i = skipBlockComment(src, i);
280
+ continue;
281
+ }
282
+ if (ch === '"' || ch === "'") {
283
+ i = skipString(src, i);
284
+ prevChar = '"';
285
+ prevWord = "";
286
+ continue;
287
+ }
288
+ if (ch === "`") {
289
+ i = skipTemplate(src, i);
290
+ prevChar = "`";
291
+ prevWord = "";
292
+ continue;
293
+ }
294
+ if (ch === "/" && isExpressionPosition(prevChar, prevWord)) {
295
+ i = skipRegex(src, i);
296
+ prevChar = "/";
297
+ prevWord = "";
298
+ continue;
299
+ }
300
+ if (isWhitespace(ch)) {
301
+ if (ch === "\n") {
302
+ }
303
+ i++;
304
+ continue;
305
+ }
306
+ if (ch === "<") {
307
+ const next = src[i + 1];
308
+ if (isExpressionPosition(prevChar, prevWord) && (next === ">" || isIdentStart(next))) {
309
+ if (next !== ">") {
310
+ const past = tryGenericArrow(src, i);
311
+ if (past !== -1) {
312
+ prevChar = ">";
313
+ prevWord = "";
314
+ i = past;
315
+ continue;
316
+ }
317
+ }
318
+ return i;
319
+ }
320
+ prevChar = "<";
321
+ prevWord = "";
322
+ i++;
323
+ continue;
324
+ }
325
+ if (isIdentStart(ch)) {
326
+ let j = i + 1;
327
+ while (j < src.length && isIdentPart(src[j])) {
328
+ j++;
329
+ }
330
+ prevWord = src.slice(i, j);
331
+ prevChar = src[j - 1];
332
+ i = j;
333
+ continue;
334
+ }
335
+ prevChar = ch;
336
+ prevWord = "";
337
+ i++;
338
+ }
339
+ return -1;
340
+ }
341
+
342
+ // ../compiler/src/parser.ts
343
+ var CompileError = class extends Error {
344
+ constructor(message, offset) {
345
+ super(message);
346
+ this.offset = offset;
347
+ this.name = "CompileError";
348
+ }
349
+ offset;
350
+ };
351
+ var MarkupParser = class _MarkupParser {
352
+ constructor(src, pos) {
353
+ this.src = src;
354
+ this.pos = pos;
355
+ }
356
+ src;
357
+ pos;
358
+ /** Entry point; `pos` must be at the opening `<`. */
359
+ parse() {
360
+ return this.parseElement();
361
+ }
362
+ peek(offset = 0) {
363
+ return this.src[this.pos + offset] ?? "";
364
+ }
365
+ skipWs() {
366
+ while (this.pos < this.src.length && isWhitespace(this.src[this.pos])) {
367
+ this.pos++;
368
+ }
369
+ }
370
+ expect(ch) {
371
+ if (this.peek() !== ch) {
372
+ throw new CompileError(
373
+ `Expected '${ch}' but found '${this.peek() || "EOF"}'`,
374
+ this.pos
375
+ );
376
+ }
377
+ this.pos++;
378
+ }
379
+ /** `pos` at `<`. Parses an element or `<>...</>` fragment. */
380
+ parseElement() {
381
+ const start = this.pos;
382
+ this.expect("<");
383
+ if (this.peek() === ">") {
384
+ this.pos++;
385
+ const children2 = this.parseChildren();
386
+ this.expectClosingTag("");
387
+ return { kind: "fragment", children: children2, start, end: this.pos };
388
+ }
389
+ if (this.peek() !== "/" && !isIdentStart(this.peek())) {
390
+ throw new CompileError(
391
+ "Unexpected '<' in markup; write a literal '<' as {'<'} or &lt;",
392
+ start
393
+ );
394
+ }
395
+ const tag = this.readTagName();
396
+ const attributes = this.parseAttributes();
397
+ this.skipWs();
398
+ if (this.peek() === "/") {
399
+ this.pos++;
400
+ this.expect(">");
401
+ return {
402
+ kind: "element",
403
+ tag,
404
+ isComponent: _MarkupParser.isComponentTag(tag),
405
+ attributes,
406
+ children: [],
407
+ start,
408
+ end: this.pos
409
+ };
410
+ }
411
+ this.expect(">");
412
+ const children = this.parseChildren();
413
+ this.expectClosingTag(tag);
414
+ return {
415
+ kind: "element",
416
+ tag,
417
+ isComponent: _MarkupParser.isComponentTag(tag),
418
+ attributes,
419
+ children,
420
+ start,
421
+ end: this.pos
422
+ };
423
+ }
424
+ static isComponentTag(tag) {
425
+ return /[A-Z]/.test(tag[0] ?? "") || tag.includes(".");
426
+ }
427
+ /** Reads a tag name: identifiers plus `.` (`Foo.Bar`) and `-` (custom elements). */
428
+ readTagName() {
429
+ const start = this.pos;
430
+ if (!isIdentStart(this.peek())) {
431
+ throw new CompileError("Expected a tag name", this.pos);
432
+ }
433
+ this.pos++;
434
+ while (this.pos < this.src.length) {
435
+ const ch = this.src[this.pos];
436
+ if (isIdentPart(ch) || ch === "." || ch === "-") {
437
+ this.pos++;
438
+ } else {
439
+ break;
440
+ }
441
+ }
442
+ return this.src.slice(start, this.pos);
443
+ }
444
+ parseAttributes() {
445
+ const attrs = [];
446
+ for (; ; ) {
447
+ this.skipWs();
448
+ const ch = this.peek();
449
+ if (ch === ">" || ch === "/" || ch === "") {
450
+ break;
451
+ }
452
+ const attrStart = this.pos;
453
+ if (ch === "{") {
454
+ const end = skipBalanced(this.src, this.pos);
455
+ const inner = this.src.slice(this.pos + 1, end - 1).trim();
456
+ this.pos = end;
457
+ const code = inner.startsWith("...") ? inner.slice(3).trim() : inner;
458
+ attrs.push({
459
+ kind: "attribute",
460
+ name: null,
461
+ value: { kind: "expression", code },
462
+ spread: true,
463
+ start: attrStart,
464
+ end
465
+ });
466
+ continue;
467
+ }
468
+ const name = this.readAttributeName();
469
+ this.skipWs();
470
+ if (this.peek() !== "=") {
471
+ attrs.push({
472
+ kind: "attribute",
473
+ name,
474
+ value: { kind: "none" },
475
+ spread: false,
476
+ start: attrStart,
477
+ end: this.pos
478
+ });
479
+ continue;
480
+ }
481
+ this.pos++;
482
+ this.skipWs();
483
+ const valueChar = this.peek();
484
+ if (valueChar === "{") {
485
+ const end = skipBalanced(this.src, this.pos);
486
+ const code = this.src.slice(this.pos + 1, end - 1).trim();
487
+ this.pos = end;
488
+ attrs.push({
489
+ kind: "attribute",
490
+ name,
491
+ value: { kind: "expression", code },
492
+ spread: false,
493
+ start: attrStart,
494
+ end
495
+ });
496
+ } else if (valueChar === '"' || valueChar === "'") {
497
+ const end = skipString(this.src, this.pos);
498
+ const value = this.src.slice(this.pos + 1, end - 1);
499
+ this.pos = end;
500
+ attrs.push({
501
+ kind: "attribute",
502
+ name,
503
+ value: { kind: "static", value },
504
+ spread: false,
505
+ start: attrStart,
506
+ end
507
+ });
508
+ } else {
509
+ throw new CompileError(
510
+ `Expected a value for attribute '${name}'`,
511
+ this.pos
512
+ );
513
+ }
514
+ }
515
+ return attrs;
516
+ }
517
+ /** Attribute names: identifiers plus `-` and `:` (`data-x`, `aria-label`). */
518
+ readAttributeName() {
519
+ const start = this.pos;
520
+ while (this.pos < this.src.length) {
521
+ const ch = this.src[this.pos];
522
+ if (isIdentPart(ch) || ch === "-" || ch === ":") {
523
+ this.pos++;
524
+ } else {
525
+ break;
526
+ }
527
+ }
528
+ if (this.pos === start) {
529
+ throw new CompileError("Expected an attribute name", this.pos);
530
+ }
531
+ return this.src.slice(start, this.pos);
532
+ }
533
+ parseChildren() {
534
+ const children = [];
535
+ while (this.pos < this.src.length) {
536
+ if (this.peek() === "<" && this.peek(1) === "/") {
537
+ break;
538
+ }
539
+ if (this.peek() === "<") {
540
+ children.push(this.parseElement());
541
+ continue;
542
+ }
543
+ if (this.peek() === "{") {
544
+ const start = this.pos;
545
+ const end = skipBalanced(this.src, this.pos);
546
+ const code = this.src.slice(this.pos + 1, end - 1);
547
+ this.pos = end;
548
+ if (!_MarkupParser.isEmptyExpression(code)) {
549
+ children.push({ kind: "expression", code, start, end });
550
+ }
551
+ continue;
552
+ }
553
+ const text = this.readText();
554
+ if (text) {
555
+ children.push(text);
556
+ }
557
+ }
558
+ return children;
559
+ }
560
+ /** Reads raw text up to the next `<` or `{`, normalised JSX-style. */
561
+ readText() {
562
+ const start = this.pos;
563
+ while (this.pos < this.src.length && this.peek() !== "<" && this.peek() !== "{") {
564
+ this.pos++;
565
+ }
566
+ const raw = this.src.slice(start, this.pos);
567
+ const lead = raw.length - raw.trimStart().length;
568
+ if (raw.slice(lead).startsWith("//")) {
569
+ throw new CompileError(
570
+ "Line comments (//) are not allowed in markup; use {/* ... */} instead",
571
+ start + lead
572
+ );
573
+ }
574
+ const value = _MarkupParser.normalizeText(raw);
575
+ if (value === "") {
576
+ return null;
577
+ }
578
+ return { kind: "text", value, start, end: this.pos };
579
+ }
580
+ /**
581
+ * JSX-style whitespace handling: collapse runs that are purely formatting
582
+ * (whitespace containing a newline) to a single space, and preserve
583
+ * meaningful same-line spacing like `Count: `.
584
+ */
585
+ static normalizeText(raw) {
586
+ if (/^\s*$/.test(raw)) {
587
+ return "";
588
+ }
589
+ return raw.replace(/\s*\n\s*/g, " ");
590
+ }
591
+ /** True when a `{ ... }` hole has no actual expression (only comments/space). */
592
+ static isEmptyExpression(code) {
593
+ const stripped = code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").trim();
594
+ return stripped === "";
595
+ }
596
+ /** Consumes `</tag>` (or `</>` when `tag === ''`). */
597
+ expectClosingTag(tag) {
598
+ this.expect("<");
599
+ this.expect("/");
600
+ this.skipWs();
601
+ if (tag !== "") {
602
+ const closing = this.readTagName();
603
+ if (closing !== tag) {
604
+ throw new CompileError(
605
+ `Mismatched closing tag: expected </${tag}> but found </${closing}>`,
606
+ this.pos
607
+ );
608
+ }
609
+ }
610
+ this.skipWs();
611
+ this.expect(">");
612
+ }
613
+ };
614
+ function parseMarkup(src, start) {
615
+ const parser = new MarkupParser(src, start);
616
+ const node = parser.parse();
617
+ return { node, end: parser.pos };
618
+ }
619
+
620
+ // ../compiler/src/codegen.ts
621
+ function walkComponentTags(node, visit) {
622
+ if (node.kind === "element" && node.isComponent) {
623
+ visit(node.tag);
624
+ }
625
+ for (const child of node.children) {
626
+ if (child.kind === "element" || child.kind === "fragment") {
627
+ walkComponentTags(child, visit);
628
+ }
629
+ }
630
+ }
631
+
632
+ // ../language-service/src/mapping.ts
633
+ var CodeMapping = class _CodeMapping {
634
+ /** Segments sorted by `sourceStart` (for original -> generated). */
635
+ bySource;
636
+ /** Segments sorted by `generatedStart` (for generated -> original). */
637
+ byGenerated;
638
+ constructor(segments) {
639
+ this.bySource = [...segments].sort((a, b) => a.sourceStart - b.sourceStart);
640
+ this.byGenerated = [...segments].sort((a, b) => a.generatedStart - b.generatedStart);
641
+ }
642
+ /**
643
+ * Maps an offset in the original source to the virtual module, or `null`
644
+ * when it falls in non-mapped generated scaffolding. A position touching a
645
+ * segment's exclusive end still maps (so a caret right after an identifier
646
+ * resolves), preferring the segment that actually contains it.
647
+ */
648
+ toGenerated(sourceOffset) {
649
+ const seg = _CodeMapping.find(this.bySource, sourceOffset, (segment) => segment.sourceStart, (segment) => segment.sourceEnd);
650
+ if (seg === null) {
651
+ return null;
652
+ }
653
+ return seg.generatedStart + (sourceOffset - seg.sourceStart);
654
+ }
655
+ /**
656
+ * Maps an offset in the virtual module back to the original source, or
657
+ * `null` when it lands in generated scaffolding with no original origin.
658
+ */
659
+ toOriginal(generatedOffset) {
660
+ const seg = _CodeMapping.find(this.byGenerated, generatedOffset, (segment) => segment.generatedStart, (segment) => segment.generatedEnd);
661
+ if (seg === null) {
662
+ return null;
663
+ }
664
+ return seg.sourceStart + (generatedOffset - seg.generatedStart);
665
+ }
666
+ /**
667
+ * Maps an original `[start, end)` range to the virtual module. Returns
668
+ * `null` unless both ends land in the *same* segment, which guarantees the
669
+ * translated range is contiguous and meaningful (e.g. a rename edit).
670
+ */
671
+ toGeneratedRange(sourceStart, sourceEnd) {
672
+ const seg = _CodeMapping.find(this.bySource, sourceStart, (segment) => segment.sourceStart, (segment) => segment.sourceEnd);
673
+ if (seg === null || sourceEnd > seg.sourceEnd) {
674
+ return null;
675
+ }
676
+ return {
677
+ start: seg.generatedStart + (sourceStart - seg.sourceStart),
678
+ end: seg.generatedStart + (sourceEnd - seg.sourceStart)
679
+ };
680
+ }
681
+ /**
682
+ * Maps a virtual `[start, end)` range back to the original source. Both
683
+ * ends must share a segment; otherwise the range straddles generated
684
+ * scaffolding and has no faithful original counterpart.
685
+ */
686
+ toOriginalRange(generatedStart, generatedEnd) {
687
+ const seg = _CodeMapping.find(this.byGenerated, generatedStart, (segment) => segment.generatedStart, (segment) => segment.generatedEnd);
688
+ if (seg === null || generatedEnd > seg.generatedEnd) {
689
+ return null;
690
+ }
691
+ return {
692
+ start: seg.sourceStart + (generatedStart - seg.generatedStart),
693
+ end: seg.sourceStart + (generatedEnd - seg.generatedStart)
694
+ };
695
+ }
696
+ /**
697
+ * Binary-searches `segments` (sorted by `start`) for the one whose
698
+ * `[start, end]` span contains `offset`, preferring a span that strictly
699
+ * contains it over one it only touches at the end.
700
+ */
701
+ static find(segments, offset, start, end) {
702
+ let lo = 0;
703
+ let hi = segments.length - 1;
704
+ let touch = null;
705
+ while (lo <= hi) {
706
+ const mid = lo + hi >> 1;
707
+ const seg = segments[mid];
708
+ if (offset < start(seg)) {
709
+ hi = mid - 1;
710
+ } else if (offset > end(seg)) {
711
+ lo = mid + 1;
712
+ } else {
713
+ if (offset < end(seg)) {
714
+ return seg;
715
+ }
716
+ touch = seg;
717
+ hi = mid - 1;
718
+ }
719
+ }
720
+ return touch;
721
+ }
722
+ };
723
+
724
+ // ../language-service/src/virtual-code.ts
725
+ var RUNTIME_MODULE = "@azerothjs/core";
726
+ var BUILTIN_COMPONENTS = [
727
+ "Show",
728
+ "For",
729
+ "Switch",
730
+ "Match",
731
+ "Portal",
732
+ "Dynamic",
733
+ "Suspense",
734
+ "ErrorBoundary",
735
+ "Transition",
736
+ "Outlet"
737
+ ];
738
+ var BUILTIN_SET = new Set(BUILTIN_COMPONENTS);
739
+ var Builder = class {
740
+ constructor(src) {
741
+ this.src = src;
742
+ }
743
+ src;
744
+ out = "";
745
+ segments = [];
746
+ /** Appends generated scaffolding that has no original counterpart. */
747
+ emit(text) {
748
+ this.out += text;
749
+ }
750
+ /** Copies `[start, end)` from the source verbatim and records a mapping. */
751
+ copy(start, end, kind) {
752
+ if (end <= start) {
753
+ return;
754
+ }
755
+ const generatedStart = this.out.length;
756
+ this.out += this.src.slice(start, end);
757
+ this.segments.push({
758
+ sourceStart: start,
759
+ sourceEnd: end,
760
+ generatedStart,
761
+ generatedEnd: this.out.length,
762
+ kind
763
+ });
764
+ }
765
+ };
766
+ function generateVirtualCode(source) {
767
+ const builder = new Builder(source);
768
+ const usedBuiltins = /* @__PURE__ */ new Set();
769
+ const collect = (node) => walkComponentTags(node, (tag) => {
770
+ if (BUILTIN_SET.has(tag)) {
771
+ usedBuiltins.add(tag);
772
+ }
773
+ });
774
+ const emitExpression = (start, end, kind) => {
775
+ let j = start;
776
+ for (; ; ) {
777
+ const m = findMarkupStart(source, j);
778
+ if (m === -1 || m >= end) {
779
+ builder.copy(j, end, kind);
780
+ break;
781
+ }
782
+ builder.copy(j, m, kind);
783
+ let parsed;
784
+ try {
785
+ parsed = parseMarkup(source, m);
786
+ } catch {
787
+ builder.copy(m, end, kind);
788
+ break;
789
+ }
790
+ collect(parsed.node);
791
+ emitNode(parsed.node);
792
+ j = parsed.end;
793
+ }
794
+ };
795
+ const emitDynamic = (span, isEventHandler, kind) => {
796
+ const code = source.slice(span.start, span.end).trim();
797
+ if (isEventHandler || isFunctionLiteral(code) || isBareReference(code) || isCollectionLiteral(code)) {
798
+ emitExpression(span.start, span.end, kind);
799
+ return;
800
+ }
801
+ builder.emit("() => (");
802
+ emitExpression(span.start, span.end, kind);
803
+ builder.emit(")");
804
+ };
805
+ const emitAttribute = (attr, isHost) => {
806
+ if (attr.spread) {
807
+ builder.emit("...");
808
+ emitExpression(spreadSpan(source, attr).start, spreadSpan(source, attr).end, "attribute");
809
+ return;
810
+ }
811
+ const name = attr.name;
812
+ builder.emit(`${objectKey(name)}: `);
813
+ if (attr.value.kind === "none") {
814
+ builder.emit("true");
815
+ return;
816
+ }
817
+ if (attr.value.kind === "static") {
818
+ builder.emit(quoteString(attr.value.value));
819
+ return;
820
+ }
821
+ const span = attrExprSpan(source, attr);
822
+ if (isHost && isEventName(name) && isFunctionLiteral(source.slice(span.start, span.end).trim())) {
823
+ builder.emit("(");
824
+ emitExpression(span.start, span.end, "attribute");
825
+ builder.emit(`) satisfies AzerothHandler<'${name}'>`);
826
+ return;
827
+ }
828
+ emitDynamic(span, isEventName(name), "attribute");
829
+ };
830
+ const emitProps = (attrs, childrenEntry, isHost) => {
831
+ builder.emit("{ ");
832
+ attrs.forEach((attr, index) => {
833
+ if (index > 0) {
834
+ builder.emit(", ");
835
+ }
836
+ emitAttribute(attr, isHost);
837
+ });
838
+ if (childrenEntry) {
839
+ if (attrs.length > 0) {
840
+ builder.emit(", ");
841
+ }
842
+ childrenEntry();
843
+ }
844
+ builder.emit(" }");
845
+ };
846
+ const emitChild = (child) => {
847
+ if (child.kind === "text") {
848
+ builder.emit(quoteString(child.value));
849
+ return;
850
+ }
851
+ if (child.kind === "expression") {
852
+ emitDynamic({ start: child.start + 1, end: child.end - 1 }, false, "expression");
853
+ return;
854
+ }
855
+ emitNode(child);
856
+ };
857
+ const emitComponentChildren = (children) => {
858
+ if (children.length === 0) {
859
+ return null;
860
+ }
861
+ return () => {
862
+ builder.emit("children: ");
863
+ if (children.length === 1) {
864
+ const only = children[0];
865
+ if (only.kind === "expression") {
866
+ const span = { start: only.start + 1, end: only.end - 1 };
867
+ const code = source.slice(span.start, span.end).trim();
868
+ if (isFunctionLiteral(code)) {
869
+ emitExpression(span.start, span.end, "expression");
870
+ } else {
871
+ builder.emit("() => (");
872
+ emitExpression(span.start, span.end, "expression");
873
+ builder.emit(")");
874
+ }
875
+ return;
876
+ }
877
+ builder.emit("() => ");
878
+ emitChild(only);
879
+ return;
880
+ }
881
+ builder.emit("() => [");
882
+ children.forEach((child, index) => {
883
+ if (index > 0) {
884
+ builder.emit(", ");
885
+ }
886
+ emitChild(child);
887
+ });
888
+ builder.emit("]");
889
+ };
890
+ };
891
+ function emitNode(node) {
892
+ if (node.kind === "fragment") {
893
+ builder.emit("[");
894
+ node.children.forEach((child, index) => {
895
+ if (index > 0) {
896
+ builder.emit(", ");
897
+ }
898
+ emitChild(child);
899
+ });
900
+ builder.emit("]");
901
+ return;
902
+ }
903
+ if (node.isComponent) {
904
+ const tagStart = node.start + 1;
905
+ builder.copy(tagStart, tagStart + node.tag.length, "tag");
906
+ builder.emit("(");
907
+ emitProps(node.attributes, emitComponentChildren(node.children), false);
908
+ builder.emit(")");
909
+ return;
910
+ }
911
+ builder.emit(`h(${quoteString(node.tag)}, `);
912
+ emitProps(node.attributes, null, true);
913
+ for (const child of node.children) {
914
+ builder.emit(", ");
915
+ emitChild(child);
916
+ }
917
+ builder.emit(")");
918
+ }
919
+ let i = 0;
920
+ for (; ; ) {
921
+ const start = findMarkupStart(source, i);
922
+ if (start === -1) {
923
+ builder.copy(i, source.length, "script");
924
+ break;
925
+ }
926
+ builder.copy(i, start, "script");
927
+ let parsed;
928
+ try {
929
+ parsed = parseMarkup(source, start);
930
+ } catch {
931
+ builder.copy(start, source.length, "script");
932
+ break;
933
+ }
934
+ collect(parsed.node);
935
+ emitNode(parsed.node);
936
+ i = parsed.end;
937
+ }
938
+ return finalize(builder, source, usedBuiltins);
939
+ }
940
+ function finalize(builder, source, usedBuiltins) {
941
+ const hasMarkup = builder.segments.some((segment) => segment.kind !== "script") || builder.out !== source;
942
+ if (!hasMarkup) {
943
+ return { code: builder.out, mapping: new CodeMapping(builder.segments) };
944
+ }
945
+ const names = ["h", ...usedBuiltins].filter((name) => !alreadyImports(source, name));
946
+ const prefix = names.length > 0 ? `import { ${names.join(", ")} } from '${RUNTIME_MODULE}';
947
+ ` : "";
948
+ const shift = prefix.length;
949
+ const segments = builder.segments.map((segment) => ({
950
+ ...segment,
951
+ generatedStart: segment.generatedStart + shift,
952
+ generatedEnd: segment.generatedEnd + shift
953
+ }));
954
+ return { code: prefix + builder.out, mapping: new CodeMapping(segments) };
955
+ }
956
+ function alreadyImports(source, name) {
957
+ return new RegExp(`import\\s*\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from`).test(source);
958
+ }
959
+ function attrExprSpan(source, attr) {
960
+ const open = source.indexOf("{", attr.start);
961
+ return trimSpan(source, open + 1, attr.end - 1);
962
+ }
963
+ function spreadSpan(source, attr) {
964
+ const open = source.indexOf("{", attr.start);
965
+ const close = skipBalanced(source, open);
966
+ const inner = trimSpan(source, open + 1, close - 1);
967
+ if (source.startsWith("...", inner.start)) {
968
+ return trimSpan(source, inner.start + 3, inner.end);
969
+ }
970
+ return inner;
971
+ }
972
+ function trimSpan(source, start, end) {
973
+ let s = start;
974
+ let e = end;
975
+ while (s < e && isWhitespace(source[s])) {
976
+ s++;
977
+ }
978
+ while (e > s && isWhitespace(source[e - 1])) {
979
+ e--;
980
+ }
981
+ return { start: s, end: e };
982
+ }
983
+ function isFunctionLiteral(code) {
984
+ const t = code.trim();
985
+ if (/^async\s+function\b/.test(t) || /^function\b/.test(t)) {
986
+ return true;
987
+ }
988
+ return /^(async\s+)?(\([^]*?\)|[A-Za-z_$][\w$]*)\s*=>/.test(t);
989
+ }
990
+ function isBareReference(code) {
991
+ return /^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)*$/.test(code.trim());
992
+ }
993
+ function isCollectionLiteral(code) {
994
+ const t = code.trim();
995
+ return t.startsWith("[") || t.startsWith("{");
996
+ }
997
+ function isEventName(name) {
998
+ return name.length > 2 && name.startsWith("on") && name[2] === name[2].toUpperCase();
999
+ }
1000
+ function objectKey(name) {
1001
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : `'${name}'`;
1002
+ }
1003
+ function quoteString(value) {
1004
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n")}'`;
1005
+ }
1006
+
1007
+ // ../language-service/src/ts-project.ts
1008
+ var VIRTUAL_SUFFIX = ".azeroth.ts";
1009
+ function isVirtualFile(fileName) {
1010
+ return fileName.endsWith(VIRTUAL_SUFFIX);
1011
+ }
1012
+ function toVirtualFile(azerothPath) {
1013
+ return `${azerothPath}.ts`;
1014
+ }
1015
+ function toAzerothPath(virtualFile) {
1016
+ return virtualFile.slice(0, -3);
1017
+ }
1018
+
1019
+ // src/decorate.ts
1020
+ var AZEROTH_EXT = ".azeroth";
1021
+ function isAzerothSpecifier(text) {
1022
+ return text.endsWith(AZEROTH_EXT) && (text.startsWith(".") || text.startsWith("/"));
1023
+ }
1024
+ function scriptKindFromName(ts2, fileName) {
1025
+ if (fileName.endsWith(".tsx")) {
1026
+ return ts2.ScriptKind.TSX;
1027
+ }
1028
+ if (fileName.endsWith(".jsx")) {
1029
+ return ts2.ScriptKind.JSX;
1030
+ }
1031
+ if (fileName.endsWith(".json")) {
1032
+ return ts2.ScriptKind.JSON;
1033
+ }
1034
+ if (/\.(?:m|c)?js$/.test(fileName)) {
1035
+ return ts2.ScriptKind.JS;
1036
+ }
1037
+ return ts2.ScriptKind.TS;
1038
+ }
1039
+ function resolveSibling(containingFile, specifier) {
1040
+ return import_node_path.default.resolve(import_node_path.default.dirname(containingFile), specifier).replace(/\\/g, "/");
1041
+ }
1042
+ function decorateLanguageServiceHost(ts2, host, options = {}) {
1043
+ const read = options.readAzeroth ?? ((azerothPath) => ts2.sys.readFile(azerothPath));
1044
+ const cache = /* @__PURE__ */ new Map();
1045
+ const virtualCodeFor = (azerothPath) => {
1046
+ const source = read(azerothPath);
1047
+ if (source === void 0) {
1048
+ return void 0;
1049
+ }
1050
+ const cached = cache.get(azerothPath);
1051
+ if (cached && cached.source === source) {
1052
+ return cached.code;
1053
+ }
1054
+ const code = generateVirtualCode(source).code;
1055
+ cache.set(azerothPath, { source, code });
1056
+ return code;
1057
+ };
1058
+ const origSnapshot = host.getScriptSnapshot?.bind(host);
1059
+ host.getScriptSnapshot = (fileName) => {
1060
+ if (isVirtualFile(fileName)) {
1061
+ const code = virtualCodeFor(toAzerothPath(fileName));
1062
+ return code === void 0 ? void 0 : ts2.ScriptSnapshot.fromString(code);
1063
+ }
1064
+ return origSnapshot ? origSnapshot(fileName) : void 0;
1065
+ };
1066
+ const origKind = host.getScriptKind?.bind(host);
1067
+ host.getScriptKind = (fileName) => {
1068
+ if (isVirtualFile(fileName)) {
1069
+ return ts2.ScriptKind.TS;
1070
+ }
1071
+ return origKind ? origKind(fileName) : scriptKindFromName(ts2, fileName);
1072
+ };
1073
+ const origVersion = host.getScriptVersion.bind(host);
1074
+ host.getScriptVersion = (fileName) => {
1075
+ if (isVirtualFile(fileName)) {
1076
+ const azerothPath = toAzerothPath(fileName);
1077
+ const mtime = ts2.sys.getModifiedTime?.(azerothPath)?.getTime() ?? 0;
1078
+ return `azeroth:${mtime}`;
1079
+ }
1080
+ return origVersion(fileName);
1081
+ };
1082
+ const origExists = host.fileExists?.bind(host);
1083
+ host.fileExists = (fileName) => {
1084
+ if (isVirtualFile(fileName)) {
1085
+ return read(toAzerothPath(fileName)) !== void 0;
1086
+ }
1087
+ return origExists ? origExists(fileName) : ts2.sys.fileExists(fileName);
1088
+ };
1089
+ const origReadFile = host.readFile?.bind(host);
1090
+ host.readFile = (fileName, encoding) => {
1091
+ if (isVirtualFile(fileName)) {
1092
+ return virtualCodeFor(toAzerothPath(fileName));
1093
+ }
1094
+ return origReadFile ? origReadFile(fileName, encoding) : ts2.sys.readFile(fileName, encoding);
1095
+ };
1096
+ const origResolve = host.resolveModuleNameLiterals?.bind(host);
1097
+ host.resolveModuleNameLiterals = (literals, containingFile, redirectedReference, compilerOptions, containingSourceFile, reusedNames) => {
1098
+ const base = origResolve ? origResolve(literals, containingFile, redirectedReference, compilerOptions, containingSourceFile, reusedNames) : literals.map((literal) => ts2.resolveModuleName(literal.text, containingFile, compilerOptions, host));
1099
+ return literals.map((literal, index) => {
1100
+ if (isAzerothSpecifier(literal.text)) {
1101
+ const azerothPath = resolveSibling(containingFile, literal.text);
1102
+ if (read(azerothPath) !== void 0) {
1103
+ return {
1104
+ resolvedModule: {
1105
+ resolvedFileName: toVirtualFile(azerothPath),
1106
+ extension: ts2.Extension.Ts,
1107
+ isExternalLibraryImport: false
1108
+ },
1109
+ failedLookupLocations: []
1110
+ };
1111
+ }
1112
+ }
1113
+ return base[index];
1114
+ });
1115
+ };
1116
+ }
1117
+
1118
+ // src/index.ts
1119
+ function init(modules) {
1120
+ const ts2 = modules.typescript;
1121
+ return {
1122
+ create(info) {
1123
+ decorateLanguageServiceHost(ts2, info.languageServiceHost);
1124
+ info.project.projectService.logger.info("[azerothjs] typescript-plugin: .azeroth module resolution enabled");
1125
+ return info.languageService;
1126
+ },
1127
+ getExternalFiles(project) {
1128
+ return project.getFileNames().filter((fileName) => fileName.endsWith(".azeroth"));
1129
+ }
1130
+ };
1131
+ }
1132
+ module.exports = init;
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@azerothjs/typescript-plugin",
3
+ "version": "0.4.0-beta.3",
4
+ "description": "AzerothJS TypeScript language-service plugin — resolves .azeroth imports with real types in tsserver/editors",
5
+ "main": "./dist/index.js",
6
+ "files":
7
+ [
8
+ "dist"
9
+ ],
10
+ "scripts":
11
+ {
12
+ "build": "node esbuild.mjs",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords":
16
+ [
17
+ "azeroth",
18
+ "typescript",
19
+ "typescript-plugin",
20
+ "tsserver",
21
+ "language-service"
22
+ ],
23
+ "devDependencies":
24
+ {
25
+ "@azerothjs/language-service": "0.4.0-beta.3",
26
+ "esbuild": "^0.28.0"
27
+ },
28
+ "peerDependencies":
29
+ {
30
+ "typescript": ">=5"
31
+ },
32
+ "publishConfig":
33
+ {
34
+ "access": "public"
35
+ },
36
+ "author":
37
+ {
38
+ "name": "IntelligentQuantum",
39
+ "email": "IntelligentQuantum@Gmail.Com",
40
+ "url": "https://IntelligentQuantum.Dev/"
41
+ },
42
+ "license": "MIT",
43
+ "repository":
44
+ {
45
+ "type": "git",
46
+ "url": "git+https://github.com/IntelligentQuantum-Dev/AzerothJS.git",
47
+ "directory": "packages/typescript-plugin"
48
+ },
49
+ "homepage": "https://github.com/IntelligentQuantum-Dev/AzerothJS/tree/main/packages/typescript-plugin",
50
+ "bugs":
51
+ {
52
+ "url": "https://github.com/IntelligentQuantum-Dev/AzerothJS/issues"
53
+ }
54
+ }