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

peterjwest / unlinted / 10e04181-3506-4e4d-b1d7-d0764693b0d3

18 Aug 2023 10:26AM UTC coverage: 12.976% (+6.7%) from 6.254%
10e04181-3506-4e4d-b1d7-d0764693b0d3

push

circleci

peterjwest
Fix dev dependencies

33 of 49 branches covered (67.35%)

Branch coverage included in aggregate %.

168 of 1500 relevant lines covered (11.2%)

0.24 hits per line

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

98.53
/src/util.ts
1
import { promises as fs, constants } from 'fs';
1✔
2
import { promisify } from 'util';
1✔
3
import childProcess from 'child_process';
1✔
4
import lodash from 'lodash';
1✔
5

1✔
6
import { Failure } from './failures';
1✔
7

1✔
8
const exec = promisify(childProcess.exec);
1✔
9

1✔
10
export const GIT_LIST_BUFFER_SIZE = 10 * 1024 * 1024;
1✔
11

1✔
12
/** Expands a named type to show its contents */
1✔
13
export type Expand<Type> = Type extends infer Obj ? { [Key in keyof Obj]: Obj[Key] } : never;
1✔
14

1✔
15
/** Expands a named type to show its contents recursively */
1✔
16
export type ExpandRecursive<Type> = (
1✔
17
  Type extends (...args: infer Args) => infer Return ? (...args: ExpandRecursive<Args>) => ExpandRecursive<Return> :
1✔
18
  Type extends object ? (Type extends infer O ? { [K in keyof O]: ExpandRecursive<O[K]> } : never) : Type
1✔
19
);
1✔
20

1✔
21
/** Takes a string tuple and converts it into an object where the key & value are identical */
1✔
22
type ObjectFromTuple<Type extends readonly string[]> = {
1✔
23
  [Key in (keyof Type & `${number}`) as Type[Key]]: Type[Key]
1✔
24
}
1✔
25

1✔
26
/** Takes a string tuple and inverts it to an object */
1✔
27
type InvertTuple<Type extends readonly string[]> = {
1✔
28
  [Key in (keyof Type & `${number}`) as Type[Key]]: Key
1✔
29
}
1✔
30

1✔
31
/** Creates an enum from a string tuple */
1✔
32
export function createEnum<const T extends readonly string[]>(arr: T): Expand<ObjectFromTuple<T>> {
1✔
33
  return Object.fromEntries(arr.map((value) => [value, value])) as Expand<ObjectFromTuple<T>>;
2✔
34
}
2✔
35

1✔
36
/** Creates an enum from a string tuple */
1✔
37
export function createEnumNumeric<const T extends readonly string[]>(arr: T): Expand<InvertTuple<T>> {
1✔
38
  return Object.fromEntries(arr.map((value, index) => [value, index])) as Expand<InvertTuple<T>>;
2✔
39
}
2✔
40

1✔
41
/** Checks if a file is readable */
1✔
42
export async function fileReadable(path: string, deps = { access: fs.access }): Promise<boolean> {
2✔
43
  return deps.access(path, constants.R_OK).then(() => true).catch(() => false);
2✔
44
}
2✔
45

1✔
46
/** Returns the result of a `git ls-files` command with the full-name option */
1✔
47
export async function gitListFiles(directory: string, options: string, deps = { exec }): Promise<string[]> {
3✔
48
  const data = (await deps.exec(`git ls-files --full-name ${options}`.trim(), { cwd: directory, maxBuffer: GIT_LIST_BUFFER_SIZE })).stdout;
3✔
49
  return data.trim().split('\n').filter((file) => file);
2✔
50
}
2✔
51

1✔
52
/** Get all committed files which should be ignored */
1✔
53
export async function getIgnoredCommittedFiles(directory: string, deps = { gitListFiles }): Promise<string[]> {
2✔
54
  return deps.gitListFiles(directory, '--cached --ignored --exclude-standard');
2✔
55
}
2✔
56

1✔
57
/** Get all committed & untracked files */
1✔
58
export async function getProjectFiles(directory: string, deps = { gitListFiles }): Promise<string[]> {
3✔
59
  const deleted = await deps.gitListFiles(directory, '--deleted --exclude-standard');
3✔
60
  const files = await deps.gitListFiles(directory, '--cached --others --exclude-standard');
3✔
61
  return lodash.difference(files, deleted);
3✔
62
}
3✔
63

1✔
64
/** Returns the Git root directory of a directory */
1✔
65
export async function getProjectDir(directory: string, deps = { exec }) {
2✔
66
  return (await deps.exec(`git rev-parse --show-toplevel`, { cwd: directory })).stdout.trim();
2✔
67
}
1✔
68

1✔
69
/** Returns the contents of a file, returns undefined if it is a directory */
1✔
70
export async function getFileContent(path: string, deps = { readFile: fs.readFile }): Promise<Buffer | undefined> {
3✔
71
  return deps.readFile(path).catch((error) => {
3✔
72
    if (isSystemError(error) && error.code === 'EISDIR') return undefined;
2✔
73
    throw error;
1✔
74
  });
3✔
75
}
3✔
76

1✔
77
/** Checks if an error is a Node system error */
1✔
78
export function isSystemError(error: unknown): error is NodeJS.ErrnoException {
1✔
79
  return Boolean(error && error instanceof Error && 'code' in error);
5✔
80
}
5✔
81

1✔
82
/** Async delay */
1✔
83
export async function delay(milliseconds: number) {
1✔
84
  await new Promise(resolve => setTimeout(resolve, milliseconds));
1✔
85
}
1✔
86

1✔
87
/** Outputs a date in the format HH:MM:SS (YYYY-MM-DD) */
1✔
88
export function toDateString(date: Date) {
1✔
89
  return date.toISOString().replace(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).\d{3}Z/, '$4:$5:$6 ($1-$2-$3)');
1✔
90
}
1✔
91

1✔
92

1✔
93
/** Finds the difference between two dates in seconds as a formatted string */
1✔
94
export function differenceInSeconds(start: Date, end: Date): string {
1✔
95
  return ((end.valueOf() - start.valueOf()) / 1000).toFixed(2);
2✔
96
}
2✔
97

1✔
98
/** Error type including specific failures */
1✔
99
export class ErrorWithFailures extends Error {
1✔
100
  failures: string[];
1✔
101

1✔
102
  constructor(message: string, failures: string[]) {
1✔
103
    super(message);
×
104
    this.failures = failures;
×
105
  }
×
106
}
1✔
107

1✔
108
/** Results for a file, with helper methods */
1✔
109
export class FileResult {
1✔
110
  checks: number = 0;
19✔
111
  failures: Failure[] = [];
19✔
112

1✔
113
  /** Adds a number of failures into this result */
1✔
114
  addFailures(failures: Failure[]) {
1✔
115
    this.failures = this.failures.concat(failures);
12✔
116
    return this;
12✔
117
  }
12✔
118

1✔
119
  /** Merges another result into this one */
1✔
120
  mergeWith(fileResult: FileResult) {
1✔
121
    this.addFailures(fileResult.failures);
1✔
122
    this.checks += fileResult.checks;
1✔
123
    return this;
1✔
124
  }
1✔
125
}
1✔
126

1✔
127
export type Results = Record<string, FileResult>;
1✔
128

1✔
129
/** Gets or creates FileResult for a set of results */
1✔
130
export function getFileResult(results: Results, filePath: string): FileResult {
1✔
131
  const fileResult = results[filePath] || new FileResult();
2✔
132
  return results[filePath] = fileResult;
2✔
133
}
2✔
134

1✔
135
export interface ResultStats {
1✔
136
  files: {
1✔
137
    total: number;
1✔
138
    passed: number;
1✔
139
    failed: number;
1✔
140
  };
1✔
141
  checks: {
1✔
142
    total: number;
1✔
143
    passed: number;
1✔
144
    failed: number;
1✔
145
  };
1✔
146
}
1✔
147

1✔
148
/** Counts failures and totals in a Results object */
1✔
149
export function getResultStats(results: Results): ResultStats {
1✔
150
  const fileResults = Object.values(results);
4✔
151
  const failedFileResults = Object.values(results).filter((result) => result.failures.length);
4✔
152

4✔
153
  const fileCount = fileResults.length;
4✔
154
  const checkCount = lodash.sum(fileResults.map((fileData) => fileData.checks));
4✔
155

4✔
156
  const filesFailed = failedFileResults.length;
4✔
157
  const checksFailed = lodash.sum(fileResults.map((fileResult) => Object.values(lodash.groupBy(fileResult.failures, (failure) => failure.type)).length));
4✔
158

4✔
159
  return {
4✔
160
    files: {
4✔
161
      total: fileCount,
4✔
162
      passed: fileCount - filesFailed,
4✔
163
      failed: filesFailed,
4✔
164
    },
4✔
165
    checks: {
4✔
166
      total: checkCount,
4✔
167
      passed: checkCount - checksFailed,
4✔
168
      failed: checksFailed,
4✔
169
    },
4✔
170
  };
4✔
171
}
4✔
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