• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobinTail / zod-sockets / 24285868201

11 Apr 2026 03:43PM UTC coverage: 99.044% (-1.0%) from 100.0%
24285868201

Pull #702

github

web-flow
Merge e5ef2596a into 83ea63ac1
Pull Request #702: Load typescript dynamically

294 of 334 branches covered (88.02%)

96 of 101 new or added lines in 3 files covered. (95.05%)

1 existing line in 1 file now uncovered.

518 of 523 relevant lines covered (99.04%)

212.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.36
/zod-sockets/src/typescript-api.ts
1
import * as R from "ramda";
2
import type ts from "typescript";
3

4
export type Typeable =
5
  | ts.TypeNode
6
  | ts.Identifier
7
  | string
8
  | ts.KeywordTypeSyntaxKind;
9

10
type TypeParams =
11
  | string[]
12
  | Partial<Record<string, Typeable | { type?: ts.TypeNode; init: Typeable }>>;
13

14
export class TypescriptAPI {
15
  public ts: typeof ts;
16
  public f: typeof ts.factory;
17
  public exportModifier: ts.ModifierToken<ts.SyntaxKind.ExportKeyword>[];
18
  public asyncModifier: ts.ModifierToken<ts.SyntaxKind.AsyncKeyword>[];
19
  public accessModifiers: Record<
20
    "public" | "publicStatic" | "protectedReadonly",
21
    ts.Modifier[]
22
  >;
23
  #primitives: ts.KeywordTypeSyntaxKind[];
24
  static #safePropRegex = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
18✔
25

26
  constructor(typescript: typeof ts) {
27
    this.ts = typescript;
12✔
28
    this.f = this.ts.factory;
12✔
29
    this.exportModifier = [
12✔
30
      this.f.createModifier(this.ts.SyntaxKind.ExportKeyword),
31
    ];
32
    this.asyncModifier = [
12✔
33
      this.f.createModifier(this.ts.SyntaxKind.AsyncKeyword),
34
    ];
35
    this.accessModifiers = {
12✔
36
      public: [this.f.createModifier(this.ts.SyntaxKind.PublicKeyword)],
37
      publicStatic: [
38
        this.f.createModifier(this.ts.SyntaxKind.PublicKeyword),
39
        this.f.createModifier(this.ts.SyntaxKind.StaticKeyword),
40
      ],
41
      protectedReadonly: [
42
        this.f.createModifier(this.ts.SyntaxKind.ProtectedKeyword),
43
        this.f.createModifier(this.ts.SyntaxKind.ReadonlyKeyword),
44
      ],
45
    };
46
    this.#primitives = [
12✔
47
      this.ts.SyntaxKind.AnyKeyword,
48
      this.ts.SyntaxKind.BigIntKeyword,
49
      this.ts.SyntaxKind.BooleanKeyword,
50
      this.ts.SyntaxKind.NeverKeyword,
51
      this.ts.SyntaxKind.NumberKeyword,
52
      this.ts.SyntaxKind.ObjectKeyword,
53
      this.ts.SyntaxKind.StringKeyword,
54
      this.ts.SyntaxKind.SymbolKeyword,
55
      this.ts.SyntaxKind.UndefinedKeyword,
56
      this.ts.SyntaxKind.UnknownKeyword,
57
      this.ts.SyntaxKind.VoidKeyword,
58
    ];
59
  }
60

61
  public addJsDoc = <T extends ts.Node>(node: T, text: string) =>
24✔
62
    this.ts.addSyntheticLeadingComment(
24✔
63
      node,
64
      this.ts.SyntaxKind.MultiLineCommentTrivia,
65
      `* ${text} `,
66
      true,
67
    );
68

69
  public printNode = (node: ts.Node, printerOptions?: ts.PrinterOptions) => {
208✔
70
    const sourceFile = this.ts.createSourceFile(
208✔
71
      "print.ts",
72
      "",
73
      this.ts.ScriptTarget.Latest,
74
      false,
75
      this.ts.ScriptKind.TS,
76
    );
77
    const printer = this.ts.createPrinter(printerOptions);
208✔
78
    return printer.printNode(this.ts.EmitHint.Unspecified, node, sourceFile);
208✔
79
  };
80

81
  public makeId = (name: string) => this.f.createIdentifier(name);
500✔
82

83
  public makePropertyIdentifier = (name: string | number) =>
536✔
84
    typeof name === "string" && TypescriptAPI.#safePropRegex.test(name)
536✔
85
      ? this.makeId(name)
86
      : this.literally(name);
87

88
  public ensureTypeNode = (
1,304✔
89
    subject: Typeable,
90
    args?: Typeable[], // only for string and id
91
  ): ts.TypeNode =>
92
    typeof subject === "number"
1,304✔
93
      ? this.f.createKeywordTypeNode(subject)
94
      : typeof subject === "string" || this.ts.isIdentifier(subject)
1,800✔
95
        ? this.f.createTypeReferenceNode(
96
            subject,
97
            args && R.map(this.ensureTypeNode, args),
68✔
98
          )
99
        : subject;
100

101
  /**
102
   * @internal
103
   * ensures distinct union (unique primitives)
104
   * */
105
  public makeUnion = (entries: ts.TypeNode[]) => {
140✔
106
    const nodes = new Map<
140✔
107
      ts.TypeNode | ts.KeywordTypeSyntaxKind,
108
      ts.TypeNode
109
    >();
110
    for (const entry of entries)
140✔
111
      nodes.set(this.isPrimitive(entry) ? entry.kind : entry, entry);
336✔
112
    return this.f.createUnionTypeNode(Array.from(nodes.values()));
140✔
113
  };
114

115
  public makeInterfaceProp = (
536✔
116
    name: string | number,
117
    value: Typeable,
118
    {
536✔
119
      isOptional,
120
      hasUndefined = isOptional,
536✔
121
      isDeprecated,
122
      comment,
123
    }: {
124
      isOptional?: boolean;
125
      hasUndefined?: boolean;
126
      isDeprecated?: boolean;
127
      comment?: string;
128
    } = {},
129
  ) => {
130
    const propType = this.ensureTypeNode(value);
536✔
131
    const node = this.f.createPropertySignature(
536✔
132
      undefined,
133
      this.makePropertyIdentifier(name),
134
      isOptional
536✔
135
        ? this.f.createToken(this.ts.SyntaxKind.QuestionToken)
136
        : undefined,
137
      hasUndefined
536✔
138
        ? this.makeUnion([
139
            propType,
140
            this.ensureTypeNode(this.ts.SyntaxKind.UndefinedKeyword),
141
          ])
142
        : propType,
143
    );
144
    const jsdoc = R.reject(R.isNil, [
536✔
145
      isDeprecated ? "@deprecated" : undefined,
536!
146
      comment,
147
    ]);
148
    return jsdoc.length ? this.addJsDoc(node, jsdoc.join(" ")) : node;
536✔
149
  };
150

151
  public makeConst = (
8✔
152
    name: string | ts.Identifier | ts.ArrayBindingPattern,
153
    value: ts.Expression,
154
    { type, expose }: { type?: Typeable; expose?: true } = {},
8✔
155
  ) =>
156
    this.f.createVariableStatement(
8✔
157
      expose && this.exportModifier,
16✔
158
      this.f.createVariableDeclarationList(
159
        [
160
          this.f.createVariableDeclaration(
161
            name,
162
            undefined,
163
            type ? this.ensureTypeNode(type) : undefined,
8!
164
            value,
165
          ),
166
        ],
167
        this.ts.NodeFlags.Const,
168
      ),
169
    );
170

171
  public makeType = (
16✔
172
    name: ts.Identifier | string,
173
    value: ts.TypeNode,
174
    {
16✔
175
      expose,
176
      comment,
177
      params,
178
    }: { expose?: boolean; comment?: string; params?: TypeParams } = {},
179
  ) => {
180
    const node = this.f.createTypeAliasDeclaration(
16✔
181
      expose ? this.exportModifier : undefined,
16✔
182
      name,
183
      params && this.makeTypeParams(params),
16!
184
      value,
185
    );
186
    return comment ? this.addJsDoc(node, comment) : node;
16!
187
  };
188

189
  public makeInterface = (
16✔
190
    name: ts.Identifier | string,
191
    props: ts.PropertySignature[],
192
    { expose, comment }: { expose?: boolean; comment?: string } = {},
16✔
193
  ) => {
194
    const node = this.f.createInterfaceDeclaration(
16✔
195
      expose ? this.exportModifier : undefined,
16!
196
      name,
197
      undefined,
198
      undefined,
199
      props,
200
    );
201
    return comment ? this.addJsDoc(node, comment) : node;
16!
202
  };
203

NEW
204
  public makeTypeParams = (
×
NEW
205
    params:
×
206
      | string[]
207
      | Partial<
208
          Record<string, Typeable | { type?: ts.TypeNode; init: Typeable }>
209
        >,
210
  ) =>
211
    (Array.isArray(params)
×
NEW
UNCOV
212
      ? params.map((name) => R.pair(name, undefined))
×
213
      : Object.entries(params)
214
    ).map(([name, val]) => {
215
      const { type, init } =
NEW
216
        typeof val === "object" && "init" in val ? val : { type: val };
×
NEW
217
      return this.f.createTypeParameterDeclaration(
×
218
        [],
219
        name,
220
        type ? this.ensureTypeNode(type) : undefined,
×
221
        init ? this.ensureTypeNode(init) : undefined,
×
222
      );
223
    });
224

225
  /* eslint-disable prettier/prettier -- shorter and works better this way than overrides */
226
  public literally = <T extends string | null | boolean | number | bigint>(subj: T) => (
228✔
227
      typeof subj === "number" ? this.f.createNumericLiteral(subj)
228✔
228
          : typeof subj === "bigint" ? this.f.createBigIntLiteral(subj.toString())
196!
229
              : typeof subj === "boolean" ? subj ? this.f.createTrue() : this.f.createFalse()
204✔
230
                  : subj === null ? this.f.createNull() : this.f.createStringLiteral(subj)
188✔
231
  ) as T extends string ? ts.StringLiteral : T extends number ? ts.NumericLiteral
232
      : T extends boolean ? ts.BooleanLiteral : ts.NullLiteral;
233
  /* eslint-enable prettier/prettier */
234

235
  public makeLiteralType = (subj: Parameters<typeof this.literally>[0]) =>
192✔
236
    this.f.createLiteralTypeNode(this.literally(subj));
192✔
237

238
  public isPrimitive = (node: ts.TypeNode): node is ts.KeywordTypeNode =>
336✔
239
    (this.#primitives as ts.SyntaxKind[]).includes(node.kind);
336✔
240
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc