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

pmcelhaney / counterfact / 22318269207

23 Feb 2026 05:54PM UTC coverage: 82.446% (+0.5%) from 81.959%
22318269207

Pull #1502

github

dethell
feat: changeset
Pull Request #1502: feat(typescript-generator): Update route types to use operationId

1166 of 1301 branches covered (89.62%)

Branch coverage included in aggregate %.

76 of 80 new or added lines in 1 file covered. (95.0%)

2 existing lines in 1 file now uncovered.

3695 of 4595 relevant lines covered (80.41%)

60.61 hits per line

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

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

2✔
3
import { CONTEXT_FILE_TOKEN } from "./context-file-token.js";
2✔
4
import { ParametersTypeCoder } from "./parameters-type-coder.js";
2✔
5
import { READ_ONLY_COMMENTS } from "./read-only-comments.js";
2✔
6
import { ResponsesTypeCoder } from "./responses-type-coder.js";
2✔
7
import { SchemaTypeCoder } from "./schema-type-coder.js";
2✔
8
import { TypeCoder } from "./type-coder.js";
2✔
9

2✔
10
// Helper class for exporting parameter types
2✔
11
class ParameterExportTypeCoder extends TypeCoder {
2✔
12
  constructor(requirement, typeName, typeCode, parameterKind) {
2✔
13
    super(requirement);
62✔
14
    this._typeName = typeName;
62✔
15
    this._typeCode = typeCode;
62✔
16
    this._parameterKind = parameterKind;
62✔
17
  }
62✔
18

2✔
19
  get id() {
2✔
20
    // Make the id unique by including the parameter kind
64✔
21
    return `${super.id}:${this._parameterKind}`;
64✔
22
  }
64✔
23

2✔
24
  *names() {
2✔
25
    yield this._typeName;
62!
NEW
26
  }
×
27

2✔
28
  writeCode() {
2✔
29
    return this._typeCode;
32✔
30
  }
32✔
31

2✔
32
  modulePath() {
2✔
NEW
33
    // Use the same module path as the parent operation
×
NEW
34
    return this._modulePath;
×
NEW
35
  }
×
36
}
2✔
37

2✔
38
export class OperationTypeCoder extends TypeCoder {
2✔
39
  constructor(requirement, requestMethod, securitySchemes = []) {
2✔
40
    super(requirement);
92✔
41

92✔
42
    if (requestMethod === undefined) {
92!
UNCOV
43
      throw new Error("requestMethod is required");
×
UNCOV
44
    }
×
45

92✔
46
    this.requestMethod = requestMethod;
92✔
47
    this.securitySchemes = securitySchemes;
92✔
48
  }
92✔
49

2✔
50
  getOperationBaseName() {
2✔
51
    const operationId = this.requirement.get("operationId")?.data;
200✔
52

200✔
53
    return operationId || `HTTP_${this.requestMethod.toUpperCase()}`;
200✔
54
  }
200✔
55

2✔
56
  names() {
2✔
57
    return super.names(this.getOperationBaseName());
122✔
58
  }
122✔
59

2✔
60
  exportParameterType(script, parameterKind, inlineType, baseName, modulePath) {
2✔
61
    if (inlineType === "never") {
234✔
62
      return "never";
172✔
63
    }
172✔
64

62✔
65
    const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
62✔
66
    const typeName = `${baseName}_${capitalize(parameterKind)}`;
62✔
67

62✔
68
    const coder = new ParameterExportTypeCoder(
62✔
69
      this.requirement,
62✔
70
      typeName,
62✔
71
      inlineType,
62✔
72
      parameterKind,
62✔
73
    );
62✔
74
    coder._modulePath = modulePath;
62✔
75

62✔
76
    return script.export(coder, true);
62✔
77
  }
62✔
78

2✔
79
  responseTypes(script) {
2✔
80
    return this.requirement
78✔
81
      .get("responses")
78✔
82
      .flatMap((response, responseCode) => {
78✔
83
        const status =
130✔
84
          responseCode === "default"
130✔
85
            ? "number | undefined"
40✔
86
            : Number.parseInt(responseCode, 10);
90✔
87

130✔
88
        if (response.has("content")) {
130✔
89
          return response.get("content").map(
62✔
90
            (content, contentType) => `{  
62✔
91
              status: ${status}, 
82✔
92
              contentType?: "${contentType}",
82✔
93
              body?: ${new SchemaTypeCoder(content.get("schema")).write(script)}
82✔
94
            }`,
62✔
95
          );
62✔
96
        }
62✔
97

68✔
98
        if (response.has("schema")) {
108✔
99
          const produces =
22✔
100
            this.requirement?.get("produces")?.data ??
22!
101
            this.requirement.specification.rootRequirement.get("produces").data;
22✔
102

22✔
103
          return produces
22✔
104
            .map(
22✔
105
              (contentType) => `{
22✔
106
            status: ${status},
22✔
107
            contentType?: "${contentType}",
22✔
108
            body?: ${new SchemaTypeCoder(response.get("schema")).write(script)}
22✔
109
          }`,
22✔
110
            )
22✔
111
            .join(" | ");
22✔
112
        }
22✔
113

46✔
114
        return `{  
46✔
115
          status: ${status} 
46✔
116
        }`;
46✔
117
      })
46✔
118

78✔
119
      .join(" | ");
78✔
120
  }
78✔
121

2✔
122
  modulePath() {
2✔
123
    const pathString = this.requirement.url
140✔
124
      .split("/")
140✔
125
      .at(-2)
140✔
126
      .replaceAll("~1", "/");
140✔
127

140✔
128
    return `${nodePath
140✔
129
      .join("types/paths", pathString === "/" ? "/index" : pathString)
140✔
130
      .replaceAll("\\", "/")}.types.ts`;
140✔
131
  }
140✔
132

2✔
133
  userType() {
2✔
134
    if (
78✔
135
      this.securitySchemes.some(
78✔
136
        ({ scheme, type }) => type === "http" && scheme === "basic",
78✔
137
      )
78✔
138
    ) {
78✔
139
      return "{username?: string, password?: string}";
14✔
140
    }
14✔
141

64✔
142
    return "never";
64✔
143
  }
64✔
144

2✔
145
  writeCode(script) {
2✔
146
    script.comments = READ_ONLY_COMMENTS;
78✔
147

78✔
148
    const xType = script.importSharedType("WideOperationArgument");
78✔
149

78✔
150
    script.importSharedType("OmitValueWhenNever");
78✔
151
    script.importSharedType("MaybePromise");
78✔
152
    script.importSharedType("COUNTERFACT_RESPONSE");
78✔
153

78✔
154
    const contextTypeImportName = script.importExternalType(
78✔
155
      "Context",
78✔
156
      CONTEXT_FILE_TOKEN,
78✔
157
    );
78✔
158

78✔
159
    const parameters = this.requirement.get("parameters");
78✔
160

78✔
161
    const queryType = new ParametersTypeCoder(parameters, "query").write(
78✔
162
      script,
78✔
163
    );
78✔
164

78✔
165
    const pathType = new ParametersTypeCoder(parameters, "path").write(script);
78✔
166

78✔
167
    const headersType = new ParametersTypeCoder(parameters, "header").write(
78✔
168
      script,
78✔
169
    );
78✔
170

78✔
171
    const bodyRequirement =
78✔
172
      this.requirement.get("consumes") ||
78✔
173
      this.requirement.specification?.rootRequirement?.get("consumes")
74✔
174
        ? parameters
6✔
175
            ?.find((parameter) =>
6✔
176
              ["body", "formData"].includes(parameter.get("in").data),
24✔
177
            )
6✔
178
            ?.get("schema")
6✔
179
        : this.requirement.select(
72✔
180
            "requestBody/content/application~1json/schema",
72✔
181
          );
78✔
182

78✔
183
    const bodyType =
78✔
184
      bodyRequirement === undefined
78✔
185
        ? "never"
58✔
186
        : new SchemaTypeCoder(bodyRequirement).write(script);
20✔
187

78✔
188
    const responseType = new ResponsesTypeCoder(
78✔
189
      this.requirement.get("responses"),
78✔
190
      this.requirement.get("produces")?.data ??
78✔
191
        this.requirement.specification?.rootRequirement?.get("produces")?.data,
68!
192
    ).write(script);
78✔
193

78✔
194
    const proxyType = "(url: string) => COUNTERFACT_RESPONSE";
78✔
195

78✔
196
    const delayType =
78✔
197
      "(milliseconds: number, maxMilliseconds?: number) => Promise<void>";
78✔
198

78✔
199
    // Get the base name for this operation and export parameter types
78✔
200
    const baseName = this.getOperationBaseName();
78✔
201
    const modulePath = this.modulePath();
78✔
202
    const queryTypeName = this.exportParameterType(
78✔
203
      script,
78✔
204
      "query",
78✔
205
      queryType,
78✔
206
      baseName,
78✔
207
      modulePath,
78✔
208
    );
78✔
209
    const pathTypeName = this.exportParameterType(
78✔
210
      script,
78✔
211
      "path",
78✔
212
      pathType,
78✔
213
      baseName,
78✔
214
      modulePath,
78✔
215
    );
78✔
216
    const headersTypeName = this.exportParameterType(
78✔
217
      script,
78✔
218
      "headers",
78✔
219
      headersType,
78✔
220
      baseName,
78✔
221
      modulePath,
78✔
222
    );
78✔
223

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