Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.

Commit 8e8c16f

Browse files
authored
Refactor file ownership (#48)
* adding a jest mock * silencing logger during tests * refactoring file manipulation to be a separate concern to ownership * leaving some todo's for future jjmschofield
1 parent fbd5f60 commit 8e8c16f

12 files changed

Lines changed: 79 additions & 67 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from 'fs';
2-
import { log } from '../../logger';
2+
import { log } from '../logger';
33

4-
export const countLinesInFile = async (filePath: string): Promise<number> => {
4+
export const countLines = async (filePath: string): Promise<number> => {
55
let i;
66
let count = 0;
77
return new Promise((resolve, reject) => {

src/lib/file/getFilePaths.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { readGit } from './readGit';
2+
import { readDir } from './readDir';
3+
import * as path from 'path';
4+
5+
export enum FILE_DISCOVERY_STRATEGY {
6+
FILE_SYSTEM,
7+
GIT_LS,
8+
}
9+
10+
export const getFilePaths = async (dir: string, strategy: FILE_DISCOVERY_STRATEGY, root?: string) => {
11+
let filePaths;
12+
13+
if (strategy === FILE_DISCOVERY_STRATEGY.GIT_LS) {
14+
filePaths = await readGit(dir);
15+
} else {
16+
filePaths = await readDir(dir, ['.git']);
17+
}
18+
19+
if (root) { // We need to re-add the root so that later ops can find the file
20+
filePaths = filePaths.map(filePath => path.join(root, filePath));
21+
}
22+
23+
filePaths.sort();
24+
25+
return filePaths;
26+
};

src/lib/file/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { getFilePaths, FILE_DISCOVERY_STRATEGY } from './getFilePaths';

src/lib/ownership/lib/readDirRecursively.test.ts renamed to src/lib/file/readDir.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import fs from 'fs';
2-
import * as underTest from './readDirRecursively';
2+
import * as underTest from './readDir';
33
import * as path from 'path';
44

55
jest.mock('fs');
@@ -26,7 +26,7 @@ describe('readDirRecursively', () => {
2626
statSyncMock.mockReturnValue(statFake(STAT_FAKE_TYPES.FILE));
2727

2828
// Act
29-
const result = await underTest.readDirRecursively('root');
29+
const result = await underTest.readDir('root');
3030

3131
// Assert
3232
expect(readdirSyncMock).toHaveBeenCalledWith(path.resolve('root'));
@@ -49,7 +49,7 @@ describe('readDirRecursively', () => {
4949
statSyncMock.mockReturnValue(statFake(STAT_FAKE_TYPES.FILE));
5050

5151
// Act
52-
const result = await underTest.readDirRecursively('root');
52+
const result = await underTest.readDir('root');
5353

5454
// Assert
5555
expect(readdirSyncMock).toHaveBeenCalledWith(path.resolve('root'));
@@ -70,7 +70,7 @@ describe('readDirRecursively', () => {
7070
statSyncMock.mockReturnValue(statFake(STAT_FAKE_TYPES.FILE));
7171

7272
// Act
73-
const result = await underTest.readDirRecursively('root', ['*.js']);
73+
const result = await underTest.readDir('root', ['*.js']);
7474

7575
// Assert
7676
expect(result).toEqual(expectedFiles);
@@ -88,7 +88,7 @@ describe('readDirRecursively', () => {
8888
statSyncMock.mockReturnValue(statFake(STAT_FAKE_TYPES.FILE));
8989

9090
// Act
91-
const result = await underTest.readDirRecursively('root');
91+
const result = await underTest.readDir('root');
9292

9393
// Assert
9494
expect(result).toEqual(expectedFiles);
@@ -113,7 +113,7 @@ describe('readDirRecursively', () => {
113113

114114

115115
// Act
116-
const result = await underTest.readDirRecursively('root');
116+
const result = await underTest.readDir('root');
117117

118118
// Assert
119119
expect(result).toEqual(expectedFiles);
@@ -138,7 +138,7 @@ describe('readDirRecursively', () => {
138138

139139

140140
// Act
141-
const result = await underTest.readDirRecursively('root');
141+
const result = await underTest.readDir('root');
142142

143143
// Assert
144144
expect(result).toEqual(expectedFiles);
@@ -164,7 +164,7 @@ describe('readDirRecursively', () => {
164164

165165

166166
// Act
167-
const result = await underTest.readDirRecursively('root');
167+
const result = await underTest.readDir('root');
168168

169169
// Assert
170170
expect(result).toEqual(expectedFiles);

src/lib/ownership/lib/readDirRecursively.ts renamed to src/lib/file/readDir.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs, { Stats } from 'fs';
22
import ignore, { Ignore } from 'ignore';
33
import path from 'path';
44

5-
export const readDirRecursively = async (dir: string, filters: string[] = []): Promise<string[]> => {
5+
export const readDir = async (dir: string, filters: string[] = []): Promise<string[]> => {
66
return new Promise((resolve, reject) => {
77
try {
88
const ignores = ignore().add(filters);

src/lib/ownership/lib/readTrackedGitFiles.test.ts renamed to src/lib/file/readGit.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
import { exec } from '../../util/exec';
2-
import { readTrackedGitFiles } from './readTrackedGitFiles';
1+
import { exec } from '../util/exec';
2+
import { readGit } from './readGit';
33

4-
jest.mock('../../util/exec');
4+
jest.mock('../util/exec');
55
const execFileMock = exec as jest.Mock;
66

7-
describe('readTrackedGitFiles', () => {
7+
describe('readGit', () => {
88
it('should return the expected list of files when called', async () => {
99
execFileMock.mockResolvedValue({ stdout: 'foo\nbar\n', stderr: '' });
1010

11-
const result = await readTrackedGitFiles('some/dir');
11+
const result = await readGit('some/dir');
1212

1313
expect(result).toStrictEqual(['foo', 'bar']);
1414
});
1515

1616
it('should call git ls-files with the correct directory', async () => {
1717
execFileMock.mockResolvedValue({ stdout: '', stderr: '' });
1818

19-
const result = await readTrackedGitFiles('some/dir');
19+
const result = await readGit('some/dir');
2020

2121
expect(exec).toHaveBeenCalledWith(
2222
'git ls-files',

src/lib/file/readGit.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { exec } from '../util/exec';
2+
3+
export const readGit = async (dir: string): Promise<string[]> => {
4+
const { stdout } = await exec('git ls-files', { cwd: dir });
5+
return stdout.split('\n').filter(x => !!x);
6+
};

src/lib/ownership/file.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@ import { getFileOwnership } from './file';
22

33
import { OwnershipEngine } from './lib/OwnershipEngine';
44

5-
import { readDirRecursively } from './lib/readDirRecursively';
6-
import { readTrackedGitFiles } from './lib/readTrackedGitFiles';
7-
import { countLinesInFile } from './lib/countLinesInFile';
5+
import { readDir } from '../file/readDir';
6+
import { readGit } from '../file/readGit';
7+
import { countLines } from '../file/countLines';
88
import { OwnedFile } from './lib/OwnedFile';
99

1010
jest.mock('./lib/OwnershipEngine');
11-
jest.mock('./lib/readDirRecursively');
12-
jest.mock('./lib/readTrackedGitFiles');
13-
jest.mock('./lib/countLinesInFile');
11+
jest.mock('../file/readDir');
12+
jest.mock('../file/readGit');
13+
jest.mock('../file/countLines');
1414

1515
describe('file', () => {
1616
const mocks = {
17-
readDirRecursively: readDirRecursively as jest.Mock,
18-
readTrackedGitFiles: readTrackedGitFiles as jest.Mock,
19-
countLinesInFile: countLinesInFile as jest.Mock,
17+
readDir: readDir as jest.Mock,
18+
readGit: readGit as jest.Mock,
19+
countLines: countLines as jest.Mock,
2020
};
2121

2222
beforeEach(() => {
2323
jest.resetAllMocks();
24-
mocks.readDirRecursively.mockResolvedValue([]);
25-
mocks.readTrackedGitFiles.mockResolvedValue([]);
26-
mocks.countLinesInFile.mockResolvedValue(0);
24+
mocks.readDir.mockResolvedValue([]);
25+
mocks.readGit.mockResolvedValue([]);
26+
mocks.countLines.mockResolvedValue(0);
2727
});
2828

2929
describe('getFileOwnership', () => {
@@ -46,18 +46,18 @@ describe('file', () => {
4646
await getFileOwnership({ codeowners: expected, dir: expected, onlyGit: false });
4747

4848
// Assert
49-
expect(mocks.readDirRecursively).toHaveBeenCalledWith(expected, ['.git']);
49+
expect(mocks.readDir).toHaveBeenCalledWith(expected, ['.git']);
5050
});
5151

52-
it('should read files from git tracked files when onlyGit is specified', async () => {
52+
it('should read files from git tracked filegs when onlyGit is specified', async () => {
5353
// Arrange
5454
const expected = 'some/dir';
5555

5656
// Act
5757
await getFileOwnership({ codeowners: expected, dir: expected, onlyGit: true });
5858

5959
// Assert
60-
expect(mocks.readTrackedGitFiles).toHaveBeenCalledWith(expected);
60+
expect(mocks.readGit).toHaveBeenCalledWith(expected);
6161
});
6262

6363
it('should return owned files', async () => {
@@ -79,7 +79,7 @@ describe('file', () => {
7979
};
8080
});
8181

82-
mocks.readDirRecursively.mockResolvedValue(expected.map(f => f.path).reverse());
82+
mocks.readDir.mockResolvedValue(expected.map(f => f.path).reverse());
8383

8484
// Act
8585
const result = await getFileOwnership({ codeowners: 'some/file', dir: '/', onlyGit: false });

src/lib/ownership/file.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,25 @@
1-
import * as path from 'path';
21
import { OwnedFile } from './lib/OwnedFile';
32
import { OwnershipEngine } from './lib/OwnershipEngine';
4-
import { readDirRecursively } from './lib/readDirRecursively';
5-
import { readTrackedGitFiles } from './lib/readTrackedGitFiles';
6-
import { countLinesInFile } from './lib/countLinesInFile';
3+
import { countLines } from '../file/countLines';
4+
import { getFilePaths, FILE_DISCOVERY_STRATEGY } from '../file';
75

86
export const getFileOwnership = async (options: { codeowners: string, dir: string, onlyGit: boolean, root?: string }): Promise<OwnedFile[]> => {
9-
const filePaths = await getFilePaths(options.dir, options.onlyGit, options.root);
7+
// TODO - make getting file paths a concern for the caller
8+
const strategy = options.onlyGit ? FILE_DISCOVERY_STRATEGY.GIT_LS : FILE_DISCOVERY_STRATEGY.FILE_SYSTEM;
9+
const filePaths = await getFilePaths(options.dir, strategy, options.root);
1010

1111
const engine = OwnershipEngine.FromCodeownersFile(options.codeowners);
1212

1313
const files: OwnedFile[] = [];
1414

1515
for (const filePath of filePaths) {
1616
const owners = engine.calcFileOwnership(filePath);
17-
const lines = await countLinesInFile(filePath);
17+
18+
// TODO - make counting lines a concern for the caller or for stats
19+
const lines = await countLines(filePath);
1820

1921
files.push(new OwnedFile({ path: filePath, owners, lines }));
2022
}
2123

2224
return files;
2325
};
24-
25-
const getFilePaths = async (dir: string, onlyGit: boolean, root?: string) => {
26-
let filePaths;
27-
28-
if (onlyGit) {
29-
filePaths = await readTrackedGitFiles(dir);
30-
} else {
31-
filePaths = await readDirRecursively(dir, ['.git']);
32-
}
33-
34-
if (root) { // We need to re-add the root so that later ops can find the file
35-
filePaths = filePaths.map(filePath => path.join(root, filePath));
36-
}
37-
38-
filePaths.sort();
39-
40-
return filePaths;
41-
};

src/lib/ownership/lib/OwnedFile.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { OwnershipEngine } from './OwnershipEngine';
2-
import { countLinesInFile } from './countLinesInFile';
2+
import { countLines } from '../../file/countLines';
33

44
export class OwnedFile {
55
// tslint:disable-next-line:variable-name
@@ -33,11 +33,12 @@ export class OwnedFile {
3333
return `${line}\n`;
3434
}
3535

36+
// TODO - remove this, keep this class pure
3637
// tslint:disable-next-line:variable-name
3738
public static FromPath = async (filePath: string, engine: OwnershipEngine, opts = { countLines: true }) => {
3839
return new OwnedFile({
3940
path: filePath,
40-
lines: opts.countLines ? await countLinesInFile(filePath) : 0,
41+
lines: opts.countLines ? await countLines(filePath) : 0,
4142
owners: engine.calcFileOwnership(filePath),
4243
});
4344
}

0 commit comments

Comments
 (0)