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

pmcelhaney / counterfact / 23956520082

03 Apr 2026 06:02PM UTC coverage: 88.19% (+0.2%) from 88.012%
23956520082

push

github

web-flow
Merge pull request #1630 from pmcelhaney/copilot/convert-js-code-to-typescript

Convert remaining JavaScript source files to TypeScript

1771 of 2008 branches covered (88.2%)

Branch coverage included in aggregate %.

853 of 868 new or added lines in 21 files covered. (98.27%)

5562 of 6307 relevant lines covered (88.19%)

69.87 hits per line

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

97.34
/src/typescript-generator/operation-type-coder.ts
1
import nodePath from "node:path";
2✔
2

2✔
3
import { CONTEXT_FILE_TOKEN } from "./context-file-token.js";
2✔
4
import { ParameterExportTypeCoder } from "./parameter-export-type-coder.js";
2✔
5
import { ParametersTypeCoder } from "./parameters-type-coder.js";
2✔
6
import { READ_ONLY_COMMENTS } from "./read-only-comments.js";
2✔
7
import { ResponsesTypeCoder } from "./responses-type-coder.js";
2✔
8
import { SchemaTypeCoder } from "./schema-type-coder.js";
2✔
9
import { TypeCoder } from "./type-coder.js";
2✔
10
import type { Requirement } from "./requirement.js";
2✔
11
import type { Script } from "./script.js";
2✔
12

2✔
13
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
2✔
14
const RESERVED_WORDS = new Set([
2✔
15
  "break",
2✔
16
  "case",
2✔
17
  "catch",
2✔
18
  "class",
2✔
19
  "const",
2✔
20
  "continue",
2✔
21
  "debugger",
2✔
22
  "default",
2✔
23
  "delete",
2✔
24
  "do",
2✔
25
  "else",
2✔
26
  "export",
2✔
27
  "extends",
2✔
28
  "false",
2✔
29
  "finally",
2✔
30
  "for",
2✔
31
  "function",
2✔
32
  "if",
2✔
33
  "import",
2✔
34
  "in",
2✔
35
  "instanceof",
2✔
36
  "new",
2✔
37
  "null",
2✔
38
  "return",
2✔
39
  "static",
2✔
40
  "super",
2✔
41
  "switch",
2✔
42
  "this",
2✔
43
  "throw",
2✔
44
  "true",
2✔
45
  "try",
2✔
46
  "typeof",
2✔
47
  "var",
2✔
48
  "void",
2✔
49
  "while",
2✔
50
  "with",
2✔
51
  "yield",
2✔
52
  "await",
2✔
53
  "enum",
2✔
54
  "implements",
2✔
55
  "interface",
2✔
56
  "let",
2✔
57
  "package",
2✔
58
  "private",
2✔
59
  "protected",
2✔
60
  "public",
2✔
61
  "type",
2✔
62
]);
2✔
63

2✔
64
function sanitizeIdentifier(value: string): string {
192✔
65
  // Treat any run of non-identifier characters as a camelCase separator
192✔
66
  let result = value.replaceAll(/[^\w$]+(?<next>.)/gu, (_, char: string) =>
192✔
67
    char.toUpperCase(),
12✔
68
  );
192✔
69

192✔
70
  // Strip any trailing non-identifier characters (no following char to capitalize)
192✔
71
  result = result.replaceAll(/[^\w$]/gu, "");
192✔
72

192✔
73
  // If the identifier starts with a digit, prefix with an underscore
192✔
74
  if (/^\d/u.test(result)) {
192✔
75
    result = `_${result}`;
2✔
76
  }
2✔
77

192✔
78
  // If the identifier is a reserved word, append an underscore
192✔
79
  if (RESERVED_WORDS.has(result)) {
192✔
80
    result = `${result}_`;
8✔
81
  }
8✔
82

192✔
83
  return result || "_";
192!
84
}
192✔
85

2✔
86
export interface SecurityScheme {
2✔
87
  scheme?: string;
2✔
88
  type?: string;
2✔
89
}
2✔
90

2✔
91
export class OperationTypeCoder extends TypeCoder {
2✔
92
  public requestMethod: string;
2✔
93
  public securitySchemes: SecurityScheme[];
164✔
94

2✔
95
  public constructor(
2✔
96
    requirement: Requirement,
164✔
97
    requestMethod: string,
164✔
98
    securitySchemes: SecurityScheme[] = [],
164✔
99
  ) {
164✔
100
    super(requirement);
164✔
101

164✔
102
    if (requestMethod === undefined) {
164!
103
      throw new Error("requestMethod is required");
×
104
    }
×
105

164✔
106
    this.requestMethod = requestMethod;
164✔
107
    this.securitySchemes = securitySchemes;
164✔
108
  }
164✔
109

2✔
110
  public getOperationBaseName(): string {
2✔
111
    const operationId = this.requirement.get("operationId")?.data as
270✔
112
      | string
270✔
113
      | undefined;
270✔
114

270✔
115
    return operationId
270✔
116
      ? sanitizeIdentifier(operationId)
192✔
117
      : `HTTP_${this.requestMethod.toUpperCase()}`;
270✔
118
  }
270✔
119

2✔
120
  public override names(): Generator<string> {
2✔
121
    return super.names(this.getOperationBaseName());
166✔
122
  }
166✔
123

2✔
124
  public exportParameterType(
2✔
125
    script: Script,
320✔
126
    parameterKind: string,
320✔
127
    inlineType: string,
320✔
128
    baseName: string,
320✔
129
    modulePath: string,
320✔
130
  ): string {
320✔
131
    if (inlineType === "never") {
320✔
132
      return "never";
254✔
133
    }
254✔
134

66✔
135
    const capitalize = (str: string): string =>
66✔
136
      str.charAt(0).toUpperCase() + str.slice(1);
66✔
137
    const typeName = `${baseName}_${capitalize(parameterKind)}`;
66✔
138

66✔
139
    const coder = new ParameterExportTypeCoder(
66✔
140
      this.requirement,
66✔
141
      typeName,
66✔
142
      inlineType,
66✔
143
      parameterKind,
66✔
144
    );
66✔
145
    coder._modulePath = modulePath;
66✔
146

66✔
147
    return script.export(coder, true);
66✔
148
  }
66✔
149

2✔
150
  public responseTypes(script: Script): string {
2✔
151
    return this.requirement
84✔
152
      .get("responses")!
84✔
153
      .flatMap((response, responseCode): string | string[] => {
84✔
154
        const status =
136✔
155
          responseCode === "default"
136✔
156
            ? "number | undefined"
40✔
157
            : Number.parseInt(responseCode, 10);
96✔
158

136✔
159
        if (response.has("content")) {
136✔
160
          return response.get("content")!.map(
68✔
161
            (content, contentType) => `{  
68✔
162
              status: ${status}, 
88✔
163
              contentType?: "${contentType}",
88✔
164
              body?: ${content.has("schema") ? new SchemaTypeCoder(content.get("schema")!).write(script) : "unknown"}
88✔
165
            }`,
68✔
166
          );
68✔
167
        }
68✔
168

68✔
169
        if (response.has("schema")) {
108✔
170
          const producesReq =
22✔
171
            this.requirement?.get("produces") ??
22!
NEW
172
            this.requirement.specification?.rootRequirement?.get("produces");
×
173
          const produces = producesReq?.data as string[] | undefined;
22✔
174

22✔
175
          if (produces) {
22✔
176
            return produces
22✔
177
              .map(
22✔
178
                (contentType) => `{
22✔
179
            status: ${status},
22✔
180
            contentType?: "${contentType}",
22✔
181
            body?: ${new SchemaTypeCoder(response.get("schema")!).write(script)}
22✔
182
          }`,
22✔
183
              )
22✔
184
              .join(" | ");
22✔
185
          }
22✔
186
        }
22✔
187

46✔
188
        return `{  
46✔
189
          status: ${status} 
46✔
190
        }`;
46✔
191
      })
46✔
192

84✔
193
      .join(" | ");
84✔
194
  }
84✔
195

2✔
196
  public override modulePath(): string {
2✔
197
    const pathString = this.requirement.url
142✔
198
      .split("/")
142✔
199
      .at(-2)!
142✔
200
      .replaceAll("~1", "/");
142✔
201

142✔
202
    return `${nodePath
142✔
203
      .join("types/paths", pathString === "/" ? "/index" : pathString)
142✔
204
      .replaceAll("\\", "/")}.types.ts`;
142✔
205
  }
142✔
206

2✔
207
  public userType(): string {
2✔
208
    if (
80✔
209
      this.securitySchemes.some(
80✔
210
        ({ scheme, type }) => type === "http" && scheme === "basic",
80✔
211
      )
80✔
212
    ) {
80✔
213
      return "{username?: string, password?: string}";
14✔
214
    }
14✔
215

66✔
216
    return "never";
66✔
217
  }
66✔
218

2✔
219
  public override writeCode(script: Script): string {
2✔
220
    script.comments = READ_ONLY_COMMENTS;
80✔
221

80✔
222
    const xType = script.importSharedType("WideOperationArgument");
80✔
223

80✔
224
    script.importSharedType("OmitValueWhenNever");
80✔
225
    script.importSharedType("MaybePromise");
80✔
226
    script.importSharedType("COUNTERFACT_RESPONSE");
80✔
227

80✔
228
    const contextTypeImportName = script.importExternalType(
80✔
229
      "Context",
80✔
230
      CONTEXT_FILE_TOKEN,
80✔
231
    );
80✔
232

80✔
233
    const parameters = this.requirement.get("parameters");
80✔
234

80✔
235
    const queryType = new ParametersTypeCoder(parameters!, "query").write(
80✔
236
      script,
80✔
237
    );
80✔
238

80✔
239
    const pathType = new ParametersTypeCoder(parameters!, "path").write(script);
80✔
240

80✔
241
    const headersType = new ParametersTypeCoder(parameters!, "header").write(
80✔
242
      script,
80✔
243
    );
80✔
244

80✔
245
    const cookieType = new ParametersTypeCoder(parameters!, "cookie").write(
80✔
246
      script,
80✔
247
    );
80✔
248

80✔
249
    const bodyRequirement =
80✔
250
      (this.requirement.get("consumes") ??
80✔
251
      this.requirement.specification?.rootRequirement?.get("consumes"))
76✔
252
        ? parameters
6✔
253
            ?.find((parameter) =>
6✔
254
              ["body", "formData"].includes(
24✔
255
                parameter.get("in")?.data as unknown as string,
24✔
256
              ),
6✔
257
            )
6✔
258
            ?.get("schema")
6✔
259
        : this.requirement.select(
74✔
260
            "requestBody/content/application~1json/schema",
74✔
261
          );
80✔
262

80✔
263
    const bodyType =
80✔
264
      bodyRequirement === undefined
80✔
265
        ? "never"
60✔
266
        : new SchemaTypeCoder(bodyRequirement).write(script);
20✔
267

80✔
268
    const responseType = new ResponsesTypeCoder(
80✔
269
      this.requirement.get("responses")!,
80✔
270
      (this.requirement.get("produces")?.data ??
80✔
271
        this.requirement.specification?.rootRequirement?.get("produces")
70!
NEW
272
          ?.data) as string[] | undefined,
×
273
    ).write(script);
80✔
274

80✔
275
    const proxyType = "(url: string) => COUNTERFACT_RESPONSE";
80✔
276

80✔
277
    const delayType =
80✔
278
      "(milliseconds: number, maxMilliseconds?: number) => Promise<void>";
80✔
279

80✔
280
    // Get the base name for this operation and export parameter types
80✔
281
    const baseName = this.getOperationBaseName();
80✔
282
    const modulePath = this.modulePath();
80✔
283
    const queryTypeName = this.exportParameterType(
80✔
284
      script,
80✔
285
      "query",
80✔
286
      queryType,
80✔
287
      baseName,
80✔
288
      modulePath,
80✔
289
    );
80✔
290
    const pathTypeName = this.exportParameterType(
80✔
291
      script,
80✔
292
      "path",
80✔
293
      pathType,
80✔
294
      baseName,
80✔
295
      modulePath,
80✔
296
    );
80✔
297
    const headersTypeName = this.exportParameterType(
80✔
298
      script,
80✔
299
      "headers",
80✔
300
      headersType,
80✔
301
      baseName,
80✔
302
      modulePath,
80✔
303
    );
80✔
304

80✔
305
    const cookieTypeName = this.exportParameterType(
80✔
306
      script,
80✔
307
      "cookie",
80✔
308
      cookieType,
80✔
309
      baseName,
80✔
310
      modulePath,
80✔
311
    );
80✔
312

80✔
313
    return `($: OmitValueWhenNever<{ query: ${queryTypeName}, path: ${pathTypeName}, headers: ${headersTypeName}, cookie: ${cookieTypeName}, body: ${bodyType}, context: ${contextTypeImportName}, response: ${responseType}, x: ${xType}, proxy: ${proxyType}, user: ${this.userType()}, delay: ${delayType} }>) => MaybePromise<${this.responseTypes(
80✔
314
      script,
80✔
315
    )} | { status: 415, contentType: "text/plain", body: string } | COUNTERFACT_RESPONSE | { ALL_REMAINING_HEADERS_ARE_OPTIONAL: COUNTERFACT_RESPONSE }>`;
80✔
316
  }
80✔
317
}
2✔
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