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

RobinTail / zod-sockets / 29920529878

22 Jul 2026 12:39PM UTC coverage: 99.044% (+0.002%) from 99.042%
29920529878

Pull #808

github

web-flow
Merge 5c79aa4ec into 6166ad8b0
Pull Request #808: Next: v7

294 of 334 branches covered (88.02%)

93 of 93 new or added lines in 5 files covered. (100.0%)

5 existing lines in 1 file now uncovered.

518 of 523 relevant lines covered (99.04%)

317.93 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 | ts.Identifier | string | ts.KeywordTypeSyntaxKind;
6

7
type TypeParams =
8
  | string[]
9
  | Partial<Record<string, Typeable | { type?: ts.TypeNode; init: Typeable }>>;
10

11
export class TypescriptAPI {
12
  public ts: typeof ts;
13
  public f: typeof ts.factory;
14
  public exportModifier: ts.ModifierToken<ts.SyntaxKind.ExportKeyword>[];
15
  #primitives: ts.KeywordTypeSyntaxKind[];
16
  static #safePropRegex = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
24✔
17

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

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

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

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

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

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

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

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

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

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

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

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

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

213
  public makeLiteralType = (subj: Parameters<typeof this.literally>[0]) =>
288✔
214
    this.f.createLiteralTypeNode(this.literally(subj));
288✔
215

216
  public isPrimitive = (node: ts.TypeNode): node is ts.KeywordTypeNode =>
468✔
217
    (this.#primitives as ts.SyntaxKind[]).includes(node.kind);
468✔
218
}
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