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

pmcelhaney / counterfact / 23956670282

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

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

93.12
/src/typescript-generator/script.ts
1
import nodePath from "node:path";
2✔
2

2✔
3
import createDebugger from "debug";
2✔
4
import { format } from "prettier";
2✔
5

2✔
6
import { escapePathForWindows } from "../util/windows-escape.js";
2✔
7
import type { Coder, ExportStatement } from "./coder.js";
2✔
8
import type { Repository } from "./repository.js";
2✔
9

2✔
10
const debug = createDebugger("counterfact:typescript-generator:script");
2✔
11

2✔
12
interface ImportEntry {
2✔
13
  isDefault: boolean;
2✔
14
  isType: boolean;
2✔
15
  name: string;
2✔
16
  script: Script;
2✔
17
}
2✔
18

2✔
19
interface ExternalImportEntry {
2✔
20
  isDefault?: boolean;
2✔
21
  isType: boolean;
2✔
22
  modulePath: string;
2✔
23
}
2✔
24

2✔
25
export class Script {
2✔
26
  public repository: Repository;
2✔
27
  public comments: string[];
160✔
28
  public exports: Map<string, ExportStatement>;
160✔
29
  public imports: Map<string, ImportEntry>;
160✔
30
  public externalImport: Map<string, ExternalImportEntry>;
160✔
31
  public cache: Map<string, string>;
160✔
32
  public typeCache: Map<string, string>;
160✔
33
  public path: string;
160✔
34

2✔
35
  public constructor(repository: Repository, path: string) {
2✔
36
    this.repository = repository;
160✔
37
    this.comments = [];
160✔
38
    this.exports = new Map();
160✔
39
    this.imports = new Map();
160✔
40
    this.externalImport = new Map();
160✔
41
    this.cache = new Map();
160✔
42
    this.typeCache = new Map();
160✔
43
    this.path = path;
160✔
44
  }
160✔
45

2✔
46
  public get relativePathToBase(): string {
2✔
47
    return this.path
318✔
48
      .split("/")
318✔
49
      .slice(0, -1)
318✔
50
      .map(() => "..")
318✔
51
      .join("/");
318✔
52
  }
318✔
53

2✔
54
  public firstUniqueName(coder: Coder): string {
2✔
55
    for (const name of coder.names()) {
334✔
56
      if (!this.imports.has(name)) {
352✔
57
        return name;
334✔
58
      }
334✔
59
    }
352!
60

×
61
    throw new Error(`could not find a unique name for ${coder.id}`);
×
62
  }
×
63

2✔
64
  public export(coder: Coder, isType = false, isDefault = false): string {
2✔
65
    const cacheKey = isDefault ? "default" : `${coder.id}:${isType}`;
226✔
66

226✔
67
    if (this.cache.has(cacheKey)) {
226✔
68
      return this.cache.get(cacheKey)!;
16✔
69
    }
16✔
70

210✔
71
    const name = this.firstUniqueName(coder);
210✔
72

210✔
73
    this.cache.set(cacheKey, name);
210✔
74

210✔
75
    const exportStatement: ExportStatement = {
210✔
76
      beforeExport: coder.beforeExport(this.path),
210✔
77
      done: false,
210✔
78
      id: coder.id,
210✔
79
      isDefault,
210✔
80
      isType,
210✔
81
      typeDeclaration: coder.typeDeclaration(this.exports, this),
210✔
82
    };
210✔
83

210✔
84
    exportStatement.promise = coder
210✔
85
      .delegate()
210✔
86

210✔
87
      .then((availableCoder) => {
210✔
88
        exportStatement.name = name;
210✔
89
        exportStatement.code = availableCoder.write(this);
210✔
90

210✔
91
        return availableCoder;
210✔
92
      })
210✔
93

210✔
94
      .catch((error: Error) => {
210✔
95
        exportStatement.code = `{/* error creating export "${name}" for ${this.path}: ${error.stack} */}`;
24✔
96
        exportStatement.error = error;
24✔
97
        return undefined;
24✔
98
      })
24✔
99

210✔
100
      .finally(() => {
210✔
101
        exportStatement.done = true;
210✔
102
      });
210✔
103

210✔
104
    this.exports.set(name, exportStatement);
210✔
105

210✔
106
    return name;
210✔
107
  }
210✔
108

2✔
109
  public exportDefault(coder: Coder, isType = false): void {
2✔
110
    this.export(coder, isType, true);
×
111
  }
×
112

2✔
113
  public import(coder: Coder, isType = false, isDefault = false): string {
2✔
114
    debug("import coder: %s", coder.id);
210✔
115

210✔
116
    const modulePath = coder.modulePath();
210✔
117

210✔
118
    const cacheKey = `${coder.id}@${modulePath}:${isType}:${isDefault}`;
210✔
119

210✔
120
    debug("cache key: %s", cacheKey);
210✔
121

210✔
122
    if (this.cache.has(cacheKey)) {
210✔
123
      debug("cache hit: %s", cacheKey);
86✔
124

86✔
125
      return this.cache.get(cacheKey)!;
86✔
126
    }
86✔
127

124✔
128
    debug("cache miss: %s", cacheKey);
124✔
129

124✔
130
    const name = this.firstUniqueName(coder);
124✔
131

124✔
132
    this.cache.set(cacheKey, name);
124✔
133

124✔
134
    const scriptFromWhichToExport = this.repository.get(modulePath);
124✔
135

124✔
136
    const exportedName = scriptFromWhichToExport.export(
124✔
137
      coder,
124✔
138
      isType,
124✔
139
      isDefault,
124✔
140
    );
124✔
141

124✔
142
    this.imports.set(name, {
124✔
143
      isDefault,
124✔
144
      isType,
124✔
145
      name: exportedName,
124✔
146
      script: scriptFromWhichToExport,
124✔
147
    });
124✔
148

124✔
149
    return name;
124✔
150
  }
124✔
151

2✔
152
  public importType(coder: Coder): string {
2✔
153
    return this.import(coder, true);
188✔
154
  }
188✔
155

2✔
156
  public importDefault(coder: Coder, isType = false): string {
2✔
157
    return this.import(coder, isType, true);
4✔
158
  }
4✔
159

2✔
160
  public importExternal(
2✔
161
    name: string,
382✔
162
    modulePath: string,
382✔
163
    isType = false,
382✔
164
  ): string {
382✔
165
    this.externalImport.set(name, { isType, modulePath });
382✔
166

382✔
167
    return name;
382✔
168
  }
382✔
169

2✔
170
  public importExternalType(name: string, modulePath: string): string {
2✔
171
    return this.importExternal(name, modulePath, true);
62✔
172
  }
62✔
173

2✔
174
  public importSharedType(name: string): string {
2✔
175
    return this.importExternal(
318✔
176
      name,
318✔
177
      nodePath
318✔
178
        .join(this.relativePathToBase, "counterfact-types/index.ts")
318✔
179
        .replaceAll("\\", "/"),
318✔
180
      true,
318✔
181
    );
318✔
182
  }
318✔
183

2✔
184
  public exportType(coder: Coder): string {
2✔
185
    return this.export(coder, true);
2✔
186
  }
2✔
187

2✔
188
  public isInProgress(): boolean {
2✔
189
    return Array.from(this.exports.values()).some(
150✔
190
      (exportStatement) => !exportStatement.done,
150✔
191
    );
150✔
192
  }
150✔
193

2✔
194
  public finished(): Promise<(Coder | undefined)[]> {
2✔
195
    return Promise.all(
114✔
196
      Array.from(this.exports.values(), (value) => value.promise!),
114✔
197
    );
114✔
198
  }
114✔
199

2✔
200
  public externalImportStatements(): string[] {
2✔
201
    return Array.from(
130✔
202
      this.externalImport,
130✔
203
      ([name, { isDefault, isType, modulePath }]) =>
130✔
204
        `import${isType ? " type" : ""} ${
308✔
205
          isDefault ? name : `{ ${name} }`
308!
206
        } from "${modulePath}";`,
130✔
207
    );
130✔
208
  }
130✔
209

2✔
210
  public importStatements(): string[] {
2✔
211
    return Array.from(this.imports, ([name, { isDefault, isType, script }]) => {
128✔
212
      const resolvedPath = escapePathForWindows(
114✔
213
        nodePath
114✔
214
          .relative(
114✔
215
            nodePath.dirname(this.path).replaceAll("\\", "/"),
114✔
216
            script.path.replace(/\.ts$/u, ".js"),
114✔
217
          )
114✔
218
          .replaceAll("\\", "/"),
114✔
219
      );
114✔
220

114✔
221
      return `import${isType ? " type" : ""} ${
114✔
222
        isDefault ? name : `{ ${name} }`
114✔
223
      } from "${resolvedPath.includes("../") ? "" : "./"}${resolvedPath}";`;
114✔
224
    });
114✔
225
  }
128✔
226

2✔
227
  public exportStatements(): string[] {
2✔
228
    return Array.from(
124✔
229
      this.exports.values(),
124✔
230
      ({ beforeExport, code, isDefault, isType, name, typeDeclaration }) => {
124✔
231
        if (typeof code === "object" && code !== null && "raw" in code) {
186!
232
          return code.raw;
×
233
        }
×
234

186✔
235
        if (isDefault) {
186!
NEW
236
          return `${beforeExport}export default ${code as string};`;
×
237
        }
×
238

186✔
239
        const keyword = isType ? "type" : "const";
186✔
240
        const typeAnnotation =
186✔
241
          (typeDeclaration ?? "").length === 0
186!
242
            ? ""
128✔
243
            : `:${typeDeclaration ?? ""}`;
186!
244

186✔
245
        return `${beforeExport}export ${keyword} ${name ?? ""}${typeAnnotation} = ${code as string};`;
186!
246
      },
186✔
247
    );
124✔
248
  }
124✔
249

2✔
250
  public contents(): Promise<string> {
2✔
251
    return format(
124✔
252
      [
124✔
253
        this.comments.map((comment) => `// ${comment}`).join("\n"),
124✔
254
        this.comments.length > 0 ? "\n\n" : "",
124✔
255
        this.externalImportStatements().join("\n"),
124✔
256
        this.importStatements().join("\n"),
124✔
257
        "\n\n",
124✔
258
        this.exportStatements().join("\n\n"),
124✔
259
      ].join(""),
124✔
260
      { parser: "typescript" },
124✔
261
    );
124✔
262
  }
124✔
263
}
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