Skip to content

Commit 07b87b3

Browse files
authored
feat(core): find references for files not in proj (#22)
- looks for changed file basename references in cwd - compares referencing file and change file path - if equals adds the found line number to changed files
1 parent b51d992 commit 07b87b3

File tree

19 files changed

+463
-113
lines changed

19 files changed

+463
-113
lines changed

libs/core/src/__fixtures__/monorepo/angular-component/component.html

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2+
// @ts-ignore-next-line
3+
import { Component } from '@angular/core';
4+
5+
@Component({
6+
selector: 'example-component',
7+
templateUrl: './component.html',
8+
styleUrls: ['./component.css'],
9+
})
10+
export class AppComponent {
11+
title = 'example';
12+
}

libs/core/src/git.spec.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('git', () => {
8787
it('should return diff', () => {
8888
const diff = getDiff({
8989
base: branch,
90-
cwd,
90+
cwd: absoluteCwd,
9191
});
9292

9393
expect(diff).toEqual(expect.any(String));
@@ -113,6 +113,35 @@ describe('git', () => {
113113

114114
spy.mockRestore();
115115
});
116+
117+
it('should call execSync without relative if no cwd', () => {
118+
const execSpy = jest.spyOn(childProcess, 'execSync');
119+
(execSpy as jest.Mock).mockReturnValueOnce('diff');
120+
121+
getDiff({
122+
base: branch,
123+
});
124+
125+
expect(execSpy).toHaveBeenCalledWith(
126+
`git diff ${branch} --unified=0 `,
127+
expect.any(Object)
128+
);
129+
});
130+
131+
it('should call execSync with relative if has cwd', () => {
132+
const execSpy = jest.spyOn(childProcess, 'execSync');
133+
(execSpy as jest.Mock).mockReturnValueOnce('diff');
134+
135+
getDiff({
136+
base: branch,
137+
cwd,
138+
});
139+
140+
expect(execSpy).toHaveBeenCalledWith(
141+
expect.stringContaining(`git diff ${branch} --unified=0 --relative`),
142+
expect.any(Object)
143+
);
144+
});
116145
});
117146

118147
describe('getChangedFiles', () => {

libs/core/src/git.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ export function getMergeBase({
4242

4343
export function getDiff({ base, cwd }: BaseGitActionArgs): string {
4444
try {
45-
const diffCommand = cwd
46-
? `git diff ${base} --unified=0 --relative -- ${resolve(cwd)}`
47-
: `git diff ${base} --unified=0 `;
45+
const diffCommand =
46+
cwd != null
47+
? `git diff ${base} --unified=0 --relative -- ${resolve(cwd)}`
48+
: `git diff ${base} --unified=0 `;
4849

4950
return execSync(diffCommand, {
5051
maxBuffer: TEN_MEGABYTES,
@@ -60,7 +61,7 @@ export function getDiff({ base, cwd }: BaseGitActionArgs): string {
6061
}
6162
}
6263

63-
interface GetChangedFiles {
64+
export interface GetChangedFiles {
6465
filePath: string;
6566
changedLines: number[];
6667
}

libs/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './true-affected';
2+
export * from './types';

libs/core/src/true-affected.spec.ts

Lines changed: 41 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
trueAffected,
3-
findRootNode,
4-
getPackageNameByPath,
5-
} from './true-affected';
6-
import { Project, SyntaxKind } from 'ts-morph';
1+
import { trueAffected } from './true-affected';
72
import * as git from './git';
83

94
describe('trueAffected', () => {
@@ -205,6 +200,46 @@ describe('trueAffected', () => {
205200
expect(affected).toEqual([]);
206201
});
207202

203+
it('should find files that are related to changed files which are not in projects files', async () => {
204+
jest.spyOn(git, 'getChangedFiles').mockReturnValue([
205+
{
206+
filePath: 'angular-component/component.html',
207+
changedLines: [6],
208+
},
209+
]);
210+
211+
const affected = await trueAffected({
212+
cwd,
213+
base: 'main',
214+
rootTsConfig: 'tsconfig.json',
215+
projects: [
216+
{
217+
name: 'angular-component',
218+
sourceRoot: 'angular-component/',
219+
tsConfig: 'angular-component/tsconfig.json',
220+
},
221+
{
222+
name: 'proj1',
223+
sourceRoot: 'proj1/',
224+
tsConfig: 'proj1/tsconfig.json',
225+
},
226+
{
227+
name: 'proj2',
228+
sourceRoot: 'proj2/',
229+
tsConfig: 'proj2/tsconfig.json',
230+
},
231+
{
232+
name: 'proj3',
233+
sourceRoot: 'proj3/',
234+
tsConfig: 'proj3/tsconfig.json',
235+
implicitDependencies: ['proj1'],
236+
},
237+
],
238+
});
239+
240+
expect(affected).toEqual(['angular-component']);
241+
});
242+
208243
it("should ignore files when can't find the changed line", async () => {
209244
jest.spyOn(git, 'getChangedFiles').mockReturnValue([
210245
{
@@ -320,55 +355,3 @@ describe('trueAffected', () => {
320355
expect(affected).toEqual(expected);
321356
});
322357
});
323-
324-
describe('findRootNode', () => {
325-
it('should find the root node', () => {
326-
const project = new Project({ useInMemoryFileSystem: true });
327-
328-
const file = project.createSourceFile('file.ts', 'export const foo = 1;');
329-
330-
const node = file.getFirstDescendantByKind(SyntaxKind.Identifier);
331-
332-
const root = findRootNode(node);
333-
334-
expect(root?.getKind()).toEqual(SyntaxKind.VariableStatement);
335-
});
336-
337-
it('should return undefined if could not find root node', () => {
338-
const project = new Project({ useInMemoryFileSystem: true });
339-
340-
const file = project.createSourceFile('file.ts', 'export const foo = 1;');
341-
342-
const node = file.getFirstDescendantByKind(SyntaxKind.FunctionDeclaration);
343-
344-
const root = findRootNode(node);
345-
346-
expect(root?.getKindName()).toBeUndefined();
347-
});
348-
});
349-
350-
describe('getPackageNameByPath', () => {
351-
it('should return the package name by path', () => {
352-
const packageName = getPackageNameByPath('pkg1/index.ts', [
353-
{
354-
name: 'pkg1',
355-
sourceRoot: 'pkg1/',
356-
tsConfig: '',
357-
},
358-
]);
359-
360-
expect(packageName).toEqual('pkg1');
361-
});
362-
363-
it('should return undefined if could not find package name', () => {
364-
const packageName = getPackageNameByPath('pkg1/index.ts', [
365-
{
366-
name: 'pkg2',
367-
sourceRoot: 'pkg2/',
368-
tsConfig: '',
369-
},
370-
]);
371-
372-
expect(packageName).toBeUndefined();
373-
});
374-
});

libs/core/src/true-affected.ts

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
import { existsSync } from 'fs';
22
import { join, resolve } from 'path';
33
import { Project, Node, ts, SyntaxKind } from 'ts-morph';
4-
import { getChangedFiles } from './git';
5-
6-
export interface TrueAffectedProject {
7-
name: string;
8-
sourceRoot: string;
9-
tsConfig?: string;
10-
implicitDependencies?: string[];
11-
}
12-
13-
export interface TrueAffected {
14-
cwd: string;
15-
rootTsConfig?: string;
16-
base?: string;
17-
projects: TrueAffectedProject[];
18-
includeFiles?: string[];
19-
}
4+
import { GetChangedFiles, getChangedFiles } from './git';
5+
import {
6+
findNonSourceAffectedFiles,
7+
findRootNode,
8+
getPackageNameByPath,
9+
} from './utils';
10+
import { TrueAffected, TrueAffectedProject } from './types';
2011

2112
const ignoredRootNodeTypes = [
2213
SyntaxKind.ImportDeclaration,
@@ -26,22 +17,6 @@ const ignoredRootNodeTypes = [
2617
SyntaxKind.IfStatement,
2718
];
2819

29-
export const findRootNode = (
30-
node?: Node<ts.Node>
31-
): Node<ts.Node> | undefined => {
32-
if (node == null) return;
33-
/* istanbul ignore next */
34-
if (node.getParent()?.getKind() === SyntaxKind.SourceFile) return node;
35-
return findRootNode(node.getParent());
36-
};
37-
38-
export const getPackageNameByPath = (
39-
path: string,
40-
projects: TrueAffectedProject[]
41-
): string | undefined => {
42-
return projects.find(({ sourceRoot }) => path.includes(sourceRoot))?.name;
43-
};
44-
4520
export const trueAffected = async ({
4621
cwd,
4722
rootTsConfig,
@@ -89,10 +64,30 @@ export const trueAffected = async ({
8964
}
9065
);
9166

92-
const changedFiles = getChangedFiles({ base, cwd }).filter(
67+
const sourceChangedFiles: GetChangedFiles[] = getChangedFiles({
68+
base,
69+
cwd,
70+
}).filter(
9371
({ filePath }) => project.getSourceFile(resolve(cwd, filePath)) != null
9472
);
9573

74+
const ignoredPaths = ['node_modules', 'dist', '.git'];
75+
76+
const nonSourceChangedFiles: GetChangedFiles[] = getChangedFiles({
77+
base,
78+
cwd,
79+
})
80+
.filter(
81+
({ filePath }) =>
82+
!filePath.match(/.*\.(ts|js)x?$/g) &&
83+
project.getSourceFile(resolve(cwd, filePath)) == null
84+
)
85+
.flatMap(({ filePath: changedFilePath }) =>
86+
findNonSourceAffectedFiles(cwd, changedFilePath, ignoredPaths)
87+
);
88+
89+
const changedFiles = [...sourceChangedFiles, ...nonSourceChangedFiles];
90+
9691
const affectedPackages = new Set<string>();
9792
const visitedIdentifiers = new Map<string, string[]>();
9893

@@ -132,7 +127,9 @@ export const trueAffected = async ({
132127
};
133128

134129
changedFiles.forEach(({ filePath, changedLines }) => {
135-
const sourceFile = project.getSourceFileOrThrow(resolve(cwd, filePath));
130+
const sourceFile = project.getSourceFile(resolve(cwd, filePath));
131+
132+
if (sourceFile == null) return;
136133

137134
changedLines.forEach((line) => {
138135
try {

libs/core/src/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export interface TrueAffectedProject {
2+
name: string;
3+
sourceRoot: string;
4+
tsConfig?: string;
5+
implicitDependencies?: string[];
6+
}
7+
8+
export interface TrueAffected {
9+
cwd: string;
10+
rootTsConfig?: string;
11+
base?: string;
12+
projects: TrueAffectedProject[];
13+
includeFiles?: string[];
14+
}

0 commit comments

Comments
 (0)