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

RobinTail / zod-sockets / 25652227545

11 May 2026 05:32AM UTC coverage: 99.04% (-1.0%) from 100.0%
25652227545

Pull #675

github

pullfrog[bot]
Changelog: include Node 26 in supported versions.
Pull Request #675: Next: v6

294 of 334 branches covered (88.02%)

94 of 99 new or added lines in 3 files covered. (94.95%)

516 of 521 relevant lines covered (99.04%)

319.22 hits per line

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

88.89
/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
  #primitives: ts.KeywordTypeSyntaxKind[];
19
  static #safePropRegex = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
24✔
20

21
  constructor(typescript: typeof ts) {
22
    this.ts = typescript;
18✔
23
    this.f = this.ts.factory;
18✔
24
    this.exportModifier = [
18✔
25
      this.f.createModifier(this.ts.SyntaxKind.ExportKeyword),
26
    ];
27
    this.#primitives = [
18✔
28
      this.ts.SyntaxKind.AnyKeyword,
29
      this.ts.SyntaxKind.BigIntKeyword,
30
      this.ts.SyntaxKind.BooleanKeyword,
31
      this.ts.SyntaxKind.NeverKeyword,
32
      this.ts.SyntaxKind.NumberKeyword,
33
      this.ts.SyntaxKind.ObjectKeyword,
34
      this.ts.SyntaxKind.StringKeyword,
35
      this.ts.SyntaxKind.SymbolKeyword,
36
      this.ts.SyntaxKind.UndefinedKeyword,
37
      this.ts.SyntaxKind.UnknownKeyword,
38
      this.ts.SyntaxKind.VoidKeyword,
39
    ];
40
  }
41

42
  public addJsDoc = <T extends ts.Node>(node: T, text: string) =>
36✔
43
    this.ts.addSyntheticLeadingComment(
36✔
44
      node,
45
      this.ts.SyntaxKind.MultiLineCommentTrivia,
46
      `* ${text} `,
47
      true,
48
    );
49

50
  public printNode = (node: ts.Node, printerOptions?: ts.PrinterOptions) => {
312✔
51
    const sourceFile = this.ts.createSourceFile(
312✔
52
      "print.ts",
53
      "",
54
      this.ts.ScriptTarget.Latest,
55
      false,
56
      this.ts.ScriptKind.TS,
57
    );
58
    const printer = this.ts.createPrinter(printerOptions);
312✔
59
    return printer.printNode(this.ts.EmitHint.Unspecified, node, sourceFile);
312✔
60
  };
61

62
  public makeId = (name: string) => this.f.createIdentifier(name);
750✔
63

64
  public makePropertyIdentifier = (name: string | number) =>
804✔
65
    typeof name === "string" && TypescriptAPI.#safePropRegex.test(name)
804✔
66
      ? this.makeId(name)
67
      : this.literally(name);
68

69
  public ensureTypeNode = (
1,938✔
70
    subject: Typeable,
71
    args?: Typeable[], // only for string and id
72
  ): ts.TypeNode =>
73
    typeof subject === "number"
1,938✔
74
      ? this.f.createKeywordTypeNode(subject)
75
      : typeof subject === "string" || this.ts.isIdentifier(subject)
2,700✔
76
        ? this.f.createTypeReferenceNode(
77
            subject,
78
            args && R.map(this.ensureTypeNode, args),
102✔
79
          )
80
        : subject;
81

82
  /**
83
   * @internal
84
   * ensures distinct union (unique primitives)
85
   * */
86
  public makeUnion = (entries: ts.TypeNode[]) => {
192✔
87
    const nodes = new Map<
192✔
88
      ts.TypeNode | ts.KeywordTypeSyntaxKind,
89
      ts.TypeNode
90
    >();
91
    for (const entry of entries)
192✔
92
      nodes.set(this.isPrimitive(entry) ? entry.kind : entry, entry);
468✔
93
    return this.f.createUnionTypeNode(Array.from(nodes.values()));
192✔
94
  };
95

96
  public makeInterfaceProp = (
804✔
97
    name: string | number,
98
    value: Typeable,
99
    {
804✔
100
      isOptional,
101
      hasUndefined = isOptional,
804✔
102
      isDeprecated,
103
      comment,
104
    }: {
105
      isOptional?: boolean;
106
      hasUndefined?: boolean;
107
      isDeprecated?: boolean;
108
      comment?: string;
109
    } = {},
110
  ) => {
111
    const propType = this.ensureTypeNode(value);
804✔
112
    const node = this.f.createPropertySignature(
804✔
113
      undefined,
114
      this.makePropertyIdentifier(name),
115
      isOptional
804✔
116
        ? this.f.createToken(this.ts.SyntaxKind.QuestionToken)
117
        : undefined,
118
      hasUndefined
804✔
119
        ? this.makeUnion([
120
            propType,
121
            this.ensureTypeNode(this.ts.SyntaxKind.UndefinedKeyword),
122
          ])
123
        : propType,
124
    );
125
    const jsdoc = R.reject(R.isNil, [
804✔
126
      isDeprecated ? "@deprecated" : undefined,
804!
127
      comment,
128
    ]);
129
    return jsdoc.length ? this.addJsDoc(node, jsdoc.join(" ")) : node;
804✔
130
  };
131

132
  public makeConst = (
12✔
133
    name: string | ts.Identifier | ts.ArrayBindingPattern,
134
    value: ts.Expression,
135
    { type, expose }: { type?: Typeable; expose?: true } = {},
12✔
136
  ) =>
137
    this.f.createVariableStatement(
12✔
138
      expose && this.exportModifier,
24✔
139
      this.f.createVariableDeclarationList(
140
        [
141
          this.f.createVariableDeclaration(
142
            name,
143
            undefined,
144
            type ? this.ensureTypeNode(type) : undefined,
12!
145
            value,
146
          ),
147
        ],
148
        this.ts.NodeFlags.Const,
149
      ),
150
    );
151

152
  public makeType = (
24✔
153
    name: ts.Identifier | string,
154
    value: ts.TypeNode,
155
    {
24✔
156
      expose,
157
      comment,
158
      params,
159
    }: { expose?: boolean; comment?: string; params?: TypeParams } = {},
160
  ) => {
161
    const node = this.f.createTypeAliasDeclaration(
24✔
162
      expose ? this.exportModifier : undefined,
24✔
163
      name,
164
      params && this.makeTypeParams(params),
24!
165
      value,
166
    );
167
    return comment ? this.addJsDoc(node, comment) : node;
24!
168
  };
169

170
  public makeInterface = (
24✔
171
    name: ts.Identifier | string,
172
    props: ts.PropertySignature[],
173
    { expose, comment }: { expose?: boolean; comment?: string } = {},
24✔
174
  ) => {
175
    const node = this.f.createInterfaceDeclaration(
24✔
176
      expose ? this.exportModifier : undefined,
24!
177
      name,
178
      undefined,
179
      undefined,
180
      props,
181
    );
182
    return comment ? this.addJsDoc(node, comment) : node;
24!
183
  };
184

NEW
185
  public makeTypeParams = (
×
NEW
186
    params:
×
187
      | string[]
188
      | Partial<
189
          Record<string, Typeable | { type?: ts.TypeNode; init: Typeable }>
190
        >,
191
  ) =>
192
    (Array.isArray(params)
×
NEW
193
      ? params.map((name) => R.pair(name, undefined))
×
194
      : Object.entries(params)
195
    ).map(([name, val]) => {
196
      const { type, init } =
NEW
197
        typeof val === "object" && "init" in val ? val : { type: val };
×
NEW
198
      return this.f.createTypeParameterDeclaration(
×
199
        [],
200
        name,
201
        type ? this.ensureTypeNode(type) : undefined,
×
202
        init ? this.ensureTypeNode(init) : undefined,
×
203
      );
204
    });
205

206
  /* eslint-disable prettier/prettier -- shorter and works better this way than overrides */
207
  public literally = <T extends string | null | boolean | number | bigint>(subj: T) => (
342✔
208
      typeof subj === "number" ? this.f.createNumericLiteral(subj)
342✔
209
          : typeof subj === "bigint" ? this.f.createBigIntLiteral(subj.toString())
294!
210
              : typeof subj === "boolean" ? subj ? this.f.createTrue() : this.f.createFalse()
306✔
211
                  : subj === null ? this.f.createNull() : this.f.createStringLiteral(subj)
282✔
212
  ) as T extends string ? ts.StringLiteral : T extends number ? ts.NumericLiteral
213
      : T extends boolean ? ts.BooleanLiteral : ts.NullLiteral;
214
  /* eslint-enable prettier/prettier */
215

216
  public makeLiteralType = (subj: Parameters<typeof this.literally>[0]) =>
288✔
217
    this.f.createLiteralTypeNode(this.literally(subj));
288✔
218

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

© 2026 Coveralls, Inc