@typesol/cli 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +10 -6
  3. package/dist/cli.js +147 -20
  4. package/dist/compiler/analyze.d.ts +4 -0
  5. package/dist/compiler/analyze.js +51 -9
  6. package/dist/compiler/emit/base.js +1 -1
  7. package/dist/compiler/emit/calls.js +7 -0
  8. package/dist/compiler/emit/cpi.js +188 -9
  9. package/dist/compiler/emit/expressions.d.ts +11 -0
  10. package/dist/compiler/emit/expressions.js +107 -2
  11. package/dist/compiler/emit/function.js +11 -1
  12. package/dist/compiler/emit/shared.js +3 -2
  13. package/dist/compiler/emit/statements.d.ts +13 -0
  14. package/dist/compiler/emit/statements.js +131 -5
  15. package/dist/compiler/emit/storage.d.ts +2 -1
  16. package/dist/compiler/emit/storage.js +24 -7
  17. package/dist/compiler/idl.js +28 -14
  18. package/dist/compiler/model.d.ts +16 -3
  19. package/dist/compiler/toolchain.d.ts +2 -0
  20. package/dist/compiler/toolchain.js +27 -1
  21. package/dist/onchain.d.ts +30 -0
  22. package/dist/onchain.js +53 -0
  23. package/dist/portgen.d.ts +87 -0
  24. package/dist/portgen.js +311 -0
  25. package/dist/templates.js +49 -154
  26. package/dist/testing.d.ts +66 -0
  27. package/dist/testing.js +337 -0
  28. package/dist/workspace.js +11 -1
  29. package/docs/architecture.md +3 -0
  30. package/docs/cli.md +16 -2
  31. package/docs/language.md +11 -10
  32. package/docs/mainnet.md +5 -4
  33. package/docs/migrate-from-anchor.md +104 -0
  34. package/docs/test-your-program.md +52 -0
  35. package/docs/testing.md +1 -1
  36. package/docs/troubleshooting.md +7 -0
  37. package/lib/typesol.d.ts +68 -12
  38. package/package.json +19 -1
  39. package/runtime/typesol_rt.h +25 -5
  40. package/ts-plugin/index.js +112 -0
  41. package/ts-plugin/package.json +4 -0
@@ -0,0 +1,52 @@
1
+ # Test your program
2
+
3
+ `typesol/testing` runs your program in LiteSVM, an in-process Solana runtime: no validator, no SOL, a few
4
+ milliseconds per transaction. Projects from `typesol init` already use it in `tests/<name>.test.ts`.
5
+
6
+ ```ts
7
+ import { testProgram, type TestProgram } from "typesol/testing";
8
+ import { beforeAll, expect, it } from "vitest";
9
+
10
+ let t: TestProgram;
11
+ beforeAll(async () => {
12
+ t = await testProgram("counter", import.meta.url); // builds the program of typesol.json and loads it
13
+ });
14
+
15
+ it("counts", async () => {
16
+ const alice = await t.wallet(); // a funded keypair (10 SOL)
17
+ const counter = await t.pda("counter", alice); // seeds: text, addresses or keypairs, bytes, bigints (u64)
18
+ t.ok(await t.send(alice, t.ix("initialize", {}, { authority: alice, counter })));
19
+ t.ok(await t.send(alice, t.ix("increment", { by: 5n }, { authority: alice, counter })));
20
+ expect((await t.account<{ count: bigint }>("Counter", counter))?.count).toBe(5n);
21
+ });
22
+ ```
23
+
24
+ ## Instructions
25
+
26
+ `t.ix(name, args, accounts, remaining?)` builds one instruction.
27
+
28
+ - **Accounts by name**, in camelCase or snake_case. Their signer and writable flags come from the IDL, and accounts
29
+ with a fixed address (the System Program, token programs, sysvars) are filled in, as are PDAs whose seeds are
30
+ constants, arguments or other accounts' keys (`t.ix("initialize", {}, { authority: alice })` derives `counter`). Pass a keypair for an account that
31
+ signs, an address otherwise, and `null` to leave an optional account out.
32
+ - **Arguments as plain values:** bigints or numbers for integers, address strings for Pubkeys, `null` for an empty
33
+ Option, objects for structs (camelCase or snake_case fields), arrays for Vecs.
34
+ - **Remaining accounts:** `[{ address, writable?, signer? }]`.
35
+
36
+ `t.send(payer, ...ixs)` sends them in one transaction; keypairs given as accounts sign too.
37
+
38
+ ## Results
39
+
40
+ | Helper | Gives |
41
+ | --- | --- |
42
+ | `t.ok(res)` | the successful result, or throws with the program's logs |
43
+ | `t.errorCode(res)` | the custom error code of a failed transaction: 6000 + the member's index in your error enum, or a framework code ([errors.md](errors.md)) |
44
+ | `t.account(type, address)` | the account decoded by its IDL type (bigints, address strings), or `null` once closed |
45
+ | `t.returned(instruction, res)` | the instruction's return data: an integer, boolean, Pubkey or struct |
46
+ | `t.balance(address)` | lamports |
47
+
48
+ `t.svm`, `t.programId`, `t.idl` and `t.coder` are there for anything else, e.g. writing a mint straight into the VM
49
+ with `t.svm.setAccount` (see the escrow template). `programHarness(svm, programId, idl)` gives the same helpers for a
50
+ program you loaded yourself.
51
+
52
+ The module needs `litesvm` and `@anchor-lang/core`, which `typesol init` adds to `devDependencies`.
package/docs/testing.md CHANGED
@@ -5,7 +5,7 @@ run prints its seed; reproduce with `FUZZ_SEED=<n>`, widen with `FUZZ_RUNS` / `F
5
5
 
6
6
  | Check | Oracle | What must hold |
7
7
  | --- | --- | --- |
8
- | `test/fuzz-arith.test.ts` | BigInt | `+ - * / %`, `& \| ^ << >> ~`, unary minus, comparisons and casts at every width u8…u128 / i8…i128, plus `mulDiv`, `mulDivCeil`, `isqrt`, `min`, `max`, return the same value or abort with the same code (5100/5101/4102), for edge and random inputs |
8
+ | `test/fuzz-arith.test.ts` | BigInt | `+ - * / % **`, `& \| ^ << >> ~`, unary minus, comparisons and casts at every width u8…u128 / i8…i128, plus `mulDiv`, `mulDivCeil`, `isqrt`, `min`, `max`, return the same value or abort with the same code (5100/5101/4102), for edge and random inputs |
9
9
  | `test/fuzz-state.test.ts` | TypeScript state model | random honest + hostile actions on vault and escrow: every outcome as predicted, balances equal the model, SOL conserved up to fees, token supply exactly conserved; random filings/decisions/resets on ledgerbook: Vec contents, account size and rent exemption equal the model |
10
10
  | `test/differential.test.ts` | Anchor twins in `bench/anchor` | identical transactions to TypeSOL and Anchor programs: same outcomes, same account contents, same wallet and token balances after every step |
11
11
  | `test/ports-*.test.ts` | the Anchor programs of solana-developers/program-examples and their tests | TypeSOL rewrites under `test/ports/` run the originals' flows and failure paths with the same outcomes, error codes and balances; each BLOCKED probe fails once its construct starts compiling, meaning that program can then be ported ([test/ports/README.md](../test/ports/README.md)) |
@@ -26,3 +26,10 @@ or declare `const HEAP_SIZE_KIB = 64n` and add a ComputeBudget `requestHeapFrame
26
26
  **`typesol idl publish` fails on a local validator with "invalid instruction data"** — the System Program of
27
27
  `solana-test-validator` (Agave 3.1 included) rejects a System instruction that the current Program Metadata program
28
28
  uses. Publishing works on mainnet-beta. The error shows the failing transaction's last log lines.
29
+
30
+ **"instruction X needs N bytes of stack; SBF gives each function 4096"** — every function on Solana gets a 4 KiB
31
+ stack frame, and going past it corrupts memory, so the build refuses it. Large structs and arrays copied into typed
32
+ locals (`const q: Big = acct.big`) are the usual cause: bind them without a type (`const q = acct.big`, a reference
33
+ into the account data) or move the work into helpers. Helpers are often inlined into their instruction, so their
34
+ locals count toward its frame.
35
+
package/lib/typesol.d.ts CHANGED
@@ -36,6 +36,8 @@ export interface FixedArray<T, N extends number> {
36
36
  get(index: bigint): T;
37
37
  set(index: bigint, value: T): void;
38
38
  readonly __length?: N;
39
+ /** `for (const x of arr)`: each element in order, as `arr.get(i)` gives it. */
40
+ [Symbol.iterator](): Iterator<T>;
39
41
  }
40
42
 
41
43
  /** Create a local fixed-length array filled with `fill`: `const xs = fixedArray<u64, 4>(0n)`. */
@@ -71,6 +73,8 @@ export interface Vec<T> {
71
73
  pop(): T;
72
74
  /** Remove every element, shrinking the account. */
73
75
  clear(): void;
76
+ /** `for (const x of vec)`: each element in order, as `vec.get(i)` gives it; the length is read again every step. */
77
+ [Symbol.iterator](): Iterator<T>;
74
78
  }
75
79
 
76
80
  /** One extra account passed after the declared ones. Nothing about it is checked until you check it. */
@@ -204,6 +208,13 @@ export interface AssociatedTokenProgram extends AccountMeta {
204
208
  /** The associated token account address of `owner` for `mint` under `tokenProgram`. */
205
209
  export declare function associatedTokenAddress(owner: Pubkey, mint: Pubkey, tokenProgram: Pubkey): Pubkey;
206
210
 
211
+ /** A creator in Metaplex metadata. `verified` can only be true for the update authority when it signs. */
212
+ export interface MetadataCreator {
213
+ address: Pubkey;
214
+ verified: boolean;
215
+ share: u8;
216
+ }
217
+
207
218
  /** Metaplex Token Metadata (metaqbxx…). Its address is checked. */
208
219
  export interface TokenMetadataProgram extends AccountMeta {
209
220
  readonly __mpl_metadata: void;
@@ -222,6 +233,22 @@ export interface TokenMetadataProgram extends AccountMeta {
222
233
  isMutable: boolean,
223
234
  signerSeeds?: Seed[] | SignerSeeds,
224
235
  ): void;
236
+ /** With creators (1–5, shares adding up to 100) and/or an unverified collection mint. */
237
+ createV3(
238
+ metadata: AccountMeta,
239
+ mint: AccountMeta,
240
+ mintAuthority: AccountMeta,
241
+ payer: Mut<Signer>,
242
+ updateAuthority: AccountMeta,
243
+ systemProgram: SystemProgram,
244
+ name: string,
245
+ symbol: string,
246
+ uri: string,
247
+ sellerFeeBasisPoints: u16,
248
+ isMutable: boolean,
249
+ extras: { creators?: MetadataCreator[]; collection?: Pubkey },
250
+ signerSeeds?: Seed[] | SignerSeeds,
251
+ ): void;
225
252
  }
226
253
  /** The canonical program address of `seeds` under `program` (Rust `Pubkey::find_program_address(seeds, program).0`). */
227
254
  export declare function findProgramAddress(seeds: Seed[], program: Pubkey): Pubkey;
@@ -602,16 +629,6 @@ interface TokenOps {
602
629
  mintTo(mint: Mint, to: TokenAccount, authority: AccountMeta, amount: u64, signerSeeds?: Seed[] | SignerSeeds): void;
603
630
  burn(from: TokenAccount, mint: Mint, authority: AccountMeta, amount: u64, signerSeeds?: Seed[] | SignerSeeds): void;
604
631
  closeAccount(account: TokenAccount, destination: AccountMeta, authority: AccountMeta, signerSeeds?: Seed[] | SignerSeeds): void;
605
- /** Token-2022 only: write name/symbol/uri into a mint whose MetadataPointer points at itself (the mint needs lamports for the larger size). */
606
- initializeMetadata(
607
- mint: Mint,
608
- updateAuthority: AccountMeta,
609
- mintAuthority: AccountMeta,
610
- name: string,
611
- symbol: string,
612
- uri: string,
613
- signerSeeds?: Seed[] | SignerSeeds,
614
- ): void;
615
632
  /** Let `delegate` move up to `amount` from `source`. */
616
633
  approve(source: TokenAccount, delegate: AccountMeta, owner: AccountMeta, amount: u64, signerSeeds?: Seed[] | SignerSeeds): void;
617
634
  revoke(source: TokenAccount, owner: AccountMeta, signerSeeds?: Seed[] | SignerSeeds): void;
@@ -627,18 +644,57 @@ interface TokenOps {
627
644
  initializeAccount3(account: AccountMeta, mint: AccountMeta, owner: Pubkey): void;
628
645
  }
629
646
 
647
+ /** Operations only Token-2022 has: on Token2022Program and TokenInterface, not on TokenProgram. */
648
+ interface Token2022Ops {
649
+ /** Token-2022 only: write name/symbol/uri into a mint whose MetadataPointer points at itself (the mint needs lamports for the larger size). */
650
+ initializeMetadata(
651
+ mint: Mint,
652
+ updateAuthority: AccountMeta,
653
+ mintAuthority: AccountMeta,
654
+ name: string,
655
+ symbol: string,
656
+ uri: string,
657
+ signerSeeds?: Seed[] | SignerSeeds,
658
+ ): void;
659
+
660
+ // Token-2022 extensions, set up on a mint created with room for them, before initializeMint2 (or, for the token
661
+ // account ones, before initializeAccount3). Token-2022 only: refused at compile time on TokenProgram.
662
+ /** TransferFeeConfig: `basisPoints` (at most 10000) of each transfer, capped at `maximumFee`. */
663
+ initializeTransferFeeConfig(mint: AccountMeta, configAuthority: Pubkey | null, withdrawAuthority: Pubkey | null, basisPoints: u16, maximumFee: u64): void;
664
+ /** MintCloseAuthority: who may close the mint once its supply is 0. */
665
+ initializeMintCloseAuthority(mint: AccountMeta, closeAuthority: Pubkey | null): void;
666
+ /** NonTransferable: tokens of this mint cannot be transferred (soul-bound). */
667
+ initializeNonTransferableMint(mint: AccountMeta): void;
668
+ /** PermanentDelegate: `delegate` may transfer or burn from any account of this mint. */
669
+ initializePermanentDelegate(mint: AccountMeta, delegate: Pubkey): void;
670
+ /** DefaultAccountState: new token accounts start frozen (true) or initialized (false). */
671
+ initializeDefaultAccountState(mint: AccountMeta, frozen: boolean): void;
672
+ /** MetadataPointer: where the mint's metadata lives (the mint itself for initializeMetadata). */
673
+ initializeMetadataPointer(mint: AccountMeta, authority: Pubkey | null, metadataAddress: Pubkey | null): void;
674
+ /** GroupPointer: where the mint's token-group data lives. */
675
+ initializeGroupPointer(mint: AccountMeta, authority: Pubkey | null, groupAddress: Pubkey | null): void;
676
+ /** GroupMemberPointer: where the mint's group-member data lives. */
677
+ initializeGroupMemberPointer(mint: AccountMeta, authority: Pubkey | null, memberAddress: Pubkey | null): void;
678
+ /** TransferHook: `programId` is invoked on every transfer of this mint. */
679
+ initializeTransferHook(mint: AccountMeta, authority: Pubkey | null, programId: Pubkey | null): void;
680
+ /** InterestBearingConfig: `rate` in basis points per year (UI amounts only; balances do not change). */
681
+ initializeInterestBearingMint(mint: AccountMeta, rateAuthority: Pubkey | null, rate: i16): void;
682
+ /** ImmutableOwner on a token account (before initializeAccount3): its owner can never change. */
683
+ initializeImmutableOwner(account: AccountMeta): void;
684
+ }
685
+
630
686
  /** The SPL Token program (Tokenkeg). Its address is checked. */
631
687
  export interface TokenProgram extends AccountMeta, TokenOps {
632
688
  readonly __token: void;
633
689
  }
634
690
 
635
691
  /** The Token-2022 program (TokenzQdB...). Its address is checked. */
636
- export interface Token2022Program extends AccountMeta, TokenOps {
692
+ export interface Token2022Program extends AccountMeta, TokenOps, Token2022Ops {
637
693
  readonly __token2022: void;
638
694
  }
639
695
 
640
696
  /** Either token program. Token accounts and mints passed alongside must belong to the program given. */
641
- export interface TokenInterface extends AccountMeta, TokenOps {
697
+ export interface TokenInterface extends AccountMeta, TokenOps, Token2022Ops {
642
698
  readonly __token_interface: void;
643
699
  }
644
700
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesol/cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Compile a TypeScript-syntax language directly to Solana SBF programs.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,6 +33,11 @@
33
33
  "types": "./dist/profile.d.ts",
34
34
  "default": "./dist/profile.js"
35
35
  },
36
+ "./testing": {
37
+ "types": "./dist/testing.d.ts",
38
+ "default": "./dist/testing.js"
39
+ },
40
+ "./ts-plugin": "./ts-plugin/index.js",
36
41
  "./idl": {
37
42
  "types": "./dist/idlpublish.d.ts",
38
43
  "default": "./dist/idlpublish.js"
@@ -43,6 +48,7 @@
43
48
  "dist",
44
49
  "lib",
45
50
  "runtime",
51
+ "ts-plugin",
46
52
  "assets",
47
53
  "!assets/readme",
48
54
  "LICENSE",
@@ -73,6 +79,18 @@
73
79
  "@solana/kit": "^8.5.0",
74
80
  "typescript": "6.0.3"
75
81
  },
82
+ "peerDependencies": {
83
+ "@anchor-lang/core": "^1.2.1",
84
+ "litesvm": "^1.6.1"
85
+ },
86
+ "peerDependenciesMeta": {
87
+ "@anchor-lang/core": {
88
+ "optional": true
89
+ },
90
+ "litesvm": {
91
+ "optional": true
92
+ }
93
+ },
76
94
  "devDependencies": {
77
95
  "@anchor-lang/core": "1.2.1",
78
96
  "@noble/curves": "^2.4.0",
@@ -1211,15 +1211,19 @@ static inline uint64_t ts_metadata_address(const SolPubkey *mint, SolPubkey *out
1211
1211
 
1212
1212
  /*
1213
1213
  * Metaplex CreateMetadataAccountV3 (instruction 33): DataV2 { name, symbol, uri, seller_fee_basis_points u16,
1214
- * creators None, collection None, uses None }, is_mutable bool, collection_details None.
1214
+ * creators Option<Vec<Creator>>, collection Option<Collection>, uses None }, is_mutable bool, collection_details None.
1215
+ * `creators` holds `ncreators` packed Creator records (address, verified, share: 34 bytes); ncreators < 0 is None.
1216
+ * `collection` (NULL for None) is written unverified, as Metaplex requires at creation.
1215
1217
  * Accounts: metadata (w), mint, mint authority (s), payer (ws), update authority, system program.
1216
1218
  */
1217
1219
  static inline uint64_t ts_mpl_create_v3(TsParams *params, const TsAccount *program, TsAccount *metadata,
1218
1220
  TsAccount *mint, TsAccount *mint_authority, TsAccount *payer,
1219
1221
  TsAccount *update_authority, TsAccount *system, TS_Str name, TS_Str symbol,
1220
- TS_Str uri, uint16_t fee_bps, bool is_mutable, const SolSignerSeed *seeds, int n) {
1222
+ TS_Str uri, uint16_t fee_bps, bool is_mutable, const uint8_t *creators, int ncreators,
1223
+ const SolPubkey *collection, const SolSignerSeed *seeds, int n) {
1221
1224
  uint8_t *data;
1222
- TS_TRY(ts_alloc(1 + 12 + name.len + symbol.len + uri.len + 2 + 3 + 1 + 1, &data));
1225
+ uint64_t ncr = ncreators > 0 ? (uint64_t)ncreators : 0;
1226
+ TS_TRY(ts_alloc(1 + 12 + name.len + symbol.len + uri.len + 2 + (1 + 4 + 34 * ncr) + (1 + 33) + 1 + 1 + 1, &data));
1223
1227
  uint64_t at = 0;
1224
1228
  data[at++] = 33;
1225
1229
  ts_put_str(data, &at, name);
@@ -1227,8 +1231,24 @@ static inline uint64_t ts_mpl_create_v3(TsParams *params, const TsAccount *progr
1227
1231
  ts_put_str(data, &at, uri);
1228
1232
  ts_memcpy(data + at, &fee_bps, 2);
1229
1233
  at += 2;
1230
- data[at++] = 0; /* creators: None */
1231
- data[at++] = 0; /* collection: None */
1234
+ if (ncreators < 0) {
1235
+ data[at++] = 0; /* creators: None */
1236
+ } else {
1237
+ data[at++] = 1;
1238
+ uint32_t count = (uint32_t)ncreators;
1239
+ ts_memcpy(data + at, &count, 4);
1240
+ at += 4;
1241
+ ts_memcpy(data + at, creators, 34 * ncr);
1242
+ at += 34 * ncr;
1243
+ }
1244
+ if (collection) {
1245
+ data[at++] = 1;
1246
+ data[at++] = 0; /* verified: false */
1247
+ ts_memcpy(data + at, collection, 32);
1248
+ at += 32;
1249
+ } else {
1250
+ data[at++] = 0; /* collection: None */
1251
+ }
1232
1252
  data[at++] = 0; /* uses: None */
1233
1253
  data[at++] = is_mutable ? 1 : 0;
1234
1254
  data[at++] = 0; /* collection_details: None */
@@ -0,0 +1,112 @@
1
+ // TypeScript language-service plugin: TypeSOL's own errors in the editor, next to TypeScript's.
2
+ //
3
+ // programs/tsconfig.json: { "compilerOptions": { …, "plugins": [{ "name": "typesol/ts-plugin" }] } }
4
+ //
5
+ // For each program entry of the project (typesol.json), once TypeScript itself reports no errors, it runs
6
+ // `typesol check --json --stdin` on the editor's text (unsaved changes included) and shows what the compiler refuses
7
+ // (an unsupported construct, a missing account constraint…) at its line and column. Editors load tsconfig plugins
8
+ // from the workspace's TypeScript (VS Code: "TypeScript: Select TypeScript Version" → "Use Workspace Version").
9
+ // CommonJS on purpose: tsserver loads plugins with require().
10
+ "use strict";
11
+ const { execFileSync } = require("node:child_process");
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+
15
+ const CLI = path.join(__dirname, "..", "dist", "cli.js");
16
+ const CODE = 9900;
17
+
18
+ /** The program entry files of the typesol.json at or above `dir` (absolute), or an empty set. */
19
+ function entriesNear(dir) {
20
+ for (let d = dir; ; d = path.dirname(d)) {
21
+ const config = path.join(d, "typesol.json");
22
+ if (fs.existsSync(config)) {
23
+ try {
24
+ const programs = JSON.parse(fs.readFileSync(config, "utf8")).programs || {};
25
+ return new Set(Object.values(programs).map((p) => path.resolve(d, typeof p === "string" ? p : p.path)));
26
+ } catch {
27
+ return new Set();
28
+ }
29
+ }
30
+ if (path.dirname(d) === d) return new Set();
31
+ }
32
+ }
33
+
34
+ function init({ typescript: ts }) {
35
+ function create(info) {
36
+ const ls = info.languageService;
37
+ const log = (m) => info.project.projectService.logger.info(`[typesol] ${m}`);
38
+ const cache = new Map(); // fileName -> { text, found }
39
+
40
+ /** The entry's text plus the modification times of the .ts files beside it (its imports usually live there). */
41
+ function key(fileName, sf) {
42
+ const dir = path.dirname(path.resolve(fileName));
43
+ let stamps = "";
44
+ try {
45
+ for (const f of fs.readdirSync(dir).sort())
46
+ if (f.endsWith(".ts") && path.join(dir, f) !== path.resolve(fileName)) stamps += `${f}:${fs.statSync(path.join(dir, f)).mtimeMs};`;
47
+ } catch {
48
+ stamps = "";
49
+ }
50
+ return `${stamps}\n${sf.text}`;
51
+ }
52
+
53
+ function typesolDiagnostics(fileName, sf) {
54
+ const k = key(fileName, sf);
55
+ const hit = cache.get(fileName);
56
+ if (hit && hit.key === k) return hit.found;
57
+ let found = [];
58
+ try {
59
+ const out = execFileSync(process.execPath, [CLI, "check", fileName, "--json", "--stdin"], {
60
+ input: sf.text,
61
+ encoding: "utf8",
62
+ timeout: 15000,
63
+ // tsserver may run inside an editor's Electron: make it behave as plain Node for the child.
64
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" },
65
+ stdio: ["pipe", "pipe", "pipe"],
66
+ });
67
+ found = JSON.parse(out).diagnostics;
68
+ } catch (e) {
69
+ // Exit 1 with JSON on stdout means diagnostics; anything else is a crash worth logging, not showing.
70
+ try {
71
+ found = JSON.parse(String(e.stdout)).diagnostics;
72
+ } catch {
73
+ log(`check failed for ${fileName}: ${e.message}`);
74
+ found = [];
75
+ }
76
+ }
77
+ cache.set(fileName, { key: k, found });
78
+ return found;
79
+ }
80
+
81
+ const proxy = Object.create(null);
82
+ for (const k of Object.keys(ls)) proxy[k] = (...args) => ls[k](...args);
83
+ proxy.getSemanticDiagnostics = (fileName) => {
84
+ const prior = ls.getSemanticDiagnostics(fileName);
85
+ if (prior.some((d) => d.category === ts.DiagnosticCategory.Error)) return prior; // TypeScript's errors first
86
+ if (!entriesNear(path.dirname(path.resolve(fileName))).has(path.resolve(fileName))) return prior;
87
+ const sf = ls.getProgram() && ls.getProgram().getSourceFile(fileName);
88
+ if (!sf) return prior;
89
+ const own = typesolDiagnostics(fileName, sf)
90
+ .filter((d) => path.resolve(d.file) === path.resolve(fileName))
91
+ .map((d) => {
92
+ const line = Math.max(0, Math.min(d.line - 1, sf.getLineStarts().length - 1));
93
+ const start = Math.min(sf.getPositionOfLineAndCharacter(line, 0) + Math.max(0, d.column - 1), sf.end);
94
+ const word = /^[\w$]+/.exec(sf.text.slice(start));
95
+ return {
96
+ file: sf,
97
+ start,
98
+ length: word ? word[0].length : 1,
99
+ messageText: d.message,
100
+ category: ts.DiagnosticCategory.Error,
101
+ code: CODE,
102
+ source: "typesol",
103
+ };
104
+ });
105
+ return [...prior, ...own];
106
+ };
107
+ return proxy;
108
+ }
109
+ return { create };
110
+ }
111
+
112
+ module.exports = init;
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "commonjs",
3
+ "main": "index.js"
4
+ }