Skip to content

Commit abd03a7

Browse files
authored
fix(core): map non-source import references into actual usage (#36)
* fix(core): map non-source import references into actual usage Imports are ignored by the reference find recurse, therefor we need to map non-source references (file imports, etc..) into actual usage in the file changed
1 parent 38fa42c commit abd03a7

File tree

9 files changed

+196
-21
lines changed

9 files changed

+196
-21
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2+
// @ts-ignore-next-line
3+
import data from './data.json';
4+
5+
export function nonSourceProj() {
6+
return data;
7+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"module": "commonjs",
5+
"forceConsistentCasingInFileNames": true,
6+
"strict": true,
7+
"noImplicitOverride": true,
8+
"noPropertyAccessFromIndexSignature": true,
9+
"noImplicitReturns": true,
10+
"noFallthroughCasesInSwitch": true
11+
},
12+
"include": ["**/*.ts"],
13+
"exclude": []
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { nonSourceProj } from '@monorepo/non-source-proj';
2+
3+
export function proj3() {
4+
nonSourceProj();
5+
return 'proj3';
6+
}

libs/core/src/__fixtures__/monorepo/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"@monorepo/proj3": [
2525
"./proj3/index.ts"
2626
],
27+
"@monorepo/non-source-proj": [
28+
"./non-source-proj/index.ts"
29+
]
2730
}
2831
},
2932
}

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

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,46 @@ describe('trueAffected', () => {
247247
expect(affected).toEqual(['angular-component']);
248248
});
249249

250+
it('should find files and usages that are related to changed files which are not in projects files', async () => {
251+
jest.spyOn(git, 'getChangedFiles').mockReturnValue([
252+
{
253+
filePath: 'non-source-proj/data.json',
254+
changedLines: [0],
255+
},
256+
]);
257+
258+
const affected = await trueAffected({
259+
cwd,
260+
base: 'main',
261+
rootTsConfig: 'tsconfig.json',
262+
projects: [
263+
{
264+
name: 'proj1',
265+
sourceRoot: 'proj1/',
266+
tsConfig: 'proj1/tsconfig.json',
267+
},
268+
{
269+
name: 'proj2',
270+
sourceRoot: 'proj2/',
271+
tsConfig: 'proj2/tsconfig.json',
272+
},
273+
{
274+
name: 'proj3',
275+
sourceRoot: 'proj3/',
276+
tsConfig: 'proj3/tsconfig.json',
277+
implicitDependencies: ['proj1'],
278+
},
279+
{
280+
name: 'non-source-proj',
281+
sourceRoot: 'non-source-proj/',
282+
tsConfig: 'non-source-proj/tsconfig.json',
283+
},
284+
],
285+
});
286+
287+
expect(affected).toEqual(['non-source-proj', 'proj3']);
288+
});
289+
250290
describe('__experimentalLockfileCheck', () => {
251291
it('should find files that are related to changed modules from lockfile if flag is on', async () => {
252292
jest.spyOn(git, 'getChangedFiles').mockReturnValue([
@@ -491,17 +531,11 @@ describe('trueAffected', () => {
491531

492532
const compilerOptions = {
493533
paths: {
494-
"@monorepo/proj1": [
495-
"./proj1/index.ts"
496-
],
497-
"@monorepo/proj2": [
498-
"./proj2/index.ts"
499-
],
500-
"@monorepo/proj3": [
501-
"./proj3/index.ts"
502-
],
503-
}
504-
}
534+
'@monorepo/proj1': ['./proj1/index.ts'],
535+
'@monorepo/proj2': ['./proj2/index.ts'],
536+
'@monorepo/proj3': ['./proj3/index.ts'],
537+
},
538+
};
505539

506540
const affected = await trueAffected({
507541
cwd,
@@ -523,9 +557,9 @@ describe('trueAffected', () => {
523557
tsConfig: 'proj3/tsconfig.json',
524558
},
525559
],
526-
compilerOptions
560+
compilerOptions,
527561
});
528562

529563
expect(affected).toEqual(['proj1']);
530-
})
564+
});
531565
});

libs/core/src/true-affected.ts

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { join, resolve } from 'path';
33
import { Project, Node, ts, SyntaxKind } from 'ts-morph';
44
import chalk from 'chalk';
55
import { ChangedFiles, getChangedFiles } from './git';
6-
import { findRootNode, getPackageNameByPath } from './utils';
6+
import { findNodeAtLine, findRootNode, getPackageNameByPath } from './utils';
77
import { TrueAffected, TrueAffectedProject } from './types';
88
import { findNonSourceAffectedFiles } from './assets';
99
import {
@@ -129,6 +129,75 @@ export const trueAffected = async ({
129129
nonSourceChangedFiles.length
130130
)} non-source affected files`
131131
);
132+
133+
logger.debug(
134+
'Mapping non-source affected files imports to actual references'
135+
);
136+
137+
nonSourceChangedFiles = nonSourceChangedFiles.flatMap(
138+
({ filePath, changedLines }) => {
139+
const file = project.getSourceFile(resolve(cwd, filePath));
140+
/* istanbul ignore next */
141+
if (file == null) return [];
142+
143+
return changedLines.reduce(
144+
(acc, line) => {
145+
const changedNode = findNodeAtLine(file, line);
146+
const rootNode = findRootNode(changedNode);
147+
/* istanbul ignore next */
148+
if (!rootNode) return acc;
149+
150+
if (!rootNode.isKind(SyntaxKind.ImportDeclaration)) {
151+
return {
152+
...acc,
153+
changedLines: [...acc.changedLines, ...changedLines],
154+
};
155+
}
156+
157+
logger.debug(
158+
`Found changed node ${chalk.bold(
159+
rootNode?.getText() ?? 'undefined'
160+
)} at line ${chalk.bold(line)} in ${chalk.bold(filePath)}`
161+
);
162+
163+
const identifier =
164+
rootNode.getFirstChildByKind(SyntaxKind.Identifier) ??
165+
rootNode.getFirstDescendantByKind(SyntaxKind.Identifier);
166+
167+
/* istanbul ignore next */
168+
if (identifier == null) return acc;
169+
170+
logger.debug(
171+
`Found identifier ${chalk.bold(
172+
identifier.getText()
173+
)} in ${chalk.bold(filePath)}`
174+
);
175+
176+
const refs = identifier.findReferencesAsNodes();
177+
178+
logger.debug(
179+
`Found ${chalk.bold(
180+
refs.length
181+
)} references for identifier ${chalk.bold(
182+
identifier.getText()
183+
)}`
184+
);
185+
186+
return {
187+
...acc,
188+
changedLines: [
189+
...acc.changedLines,
190+
...refs.map((node) => node.getStartLineNumber()),
191+
],
192+
};
193+
},
194+
{
195+
filePath,
196+
changedLines: [],
197+
} as ChangedFiles
198+
);
199+
}
200+
);
132201
}
133202
}
134203

@@ -177,9 +246,21 @@ export const trueAffected = async ({
177246
const rootNode = findRootNode(node);
178247

179248
/* istanbul ignore next */
180-
if (rootNode == null) return;
249+
if (rootNode == null) {
250+
logger.debug(
251+
`Could not find root node for ${chalk.bold(node.getText())}`
252+
);
253+
return;
254+
}
181255

182-
if (ignoredRootNodeTypes.find((type) => rootNode.isKind(type))) return;
256+
if (ignoredRootNodeTypes.find((type) => rootNode.isKind(type))) {
257+
logger.debug(
258+
`Ignoring root node ${chalk.bold(
259+
rootNode.getText()
260+
)} of type ${chalk.bold(rootNode.getKindName())}`
261+
);
262+
return;
263+
}
183264

184265
const identifier =
185266
rootNode.getFirstChildByKind(SyntaxKind.Identifier) ??
@@ -235,9 +316,7 @@ export const trueAffected = async ({
235316

236317
changedLines.forEach((line) => {
237318
try {
238-
const lineStartPos =
239-
sourceFile.compilerNode.getPositionOfLineAndCharacter(line - 1, 0);
240-
const changedNode = sourceFile.getDescendantAtPos(lineStartPos);
319+
const changedNode = findNodeAtLine(sourceFile, line);
241320

242321
/* istanbul ignore next */
243322
if (!changedNode) return;

libs/core/src/utils.spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { findRootNode, getPackageNameByPath } from './utils';
1+
import { findRootNode, getPackageNameByPath, findNodeAtLine } from './utils';
22
import { Project, SyntaxKind } from 'ts-morph';
33

44
describe('findRootNode', () => {
@@ -52,3 +52,23 @@ describe('getPackageNameByPath', () => {
5252
expect(packageName).toBeUndefined();
5353
});
5454
});
55+
56+
describe('findNodeAtLine', () => {
57+
it('should find the node at line', () => {
58+
const project = new Project({ useInMemoryFileSystem: true });
59+
60+
const file = project.createSourceFile(
61+
'file.ts',
62+
`import { bar } from 'bar';
63+
export const foo = 1;`
64+
);
65+
66+
const importNode = findNodeAtLine(file, 1);
67+
68+
expect(importNode?.getKind()).toEqual(SyntaxKind.ImportKeyword);
69+
70+
const exportNode = findNodeAtLine(file, 2);
71+
72+
expect(exportNode?.getKind()).toEqual(SyntaxKind.ExportKeyword);
73+
});
74+
});

libs/core/src/utils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ts, SyntaxKind, Node } from 'ts-morph';
1+
import { ts, SyntaxKind, Node, SourceFile } from 'ts-morph';
22
import { TrueAffectedProject } from './types';
33

44
export const findRootNode = (
@@ -16,3 +16,14 @@ export const getPackageNameByPath = (
1616
): string | undefined => {
1717
return projects.find(({ sourceRoot }) => path.includes(sourceRoot))?.name;
1818
};
19+
20+
export const findNodeAtLine = (
21+
sourceFile: SourceFile,
22+
line: number
23+
): Node<ts.Node> | undefined => {
24+
const lineStartPos = sourceFile.compilerNode.getPositionOfLineAndCharacter(
25+
line - 1,
26+
0
27+
);
28+
return sourceFile.getDescendantAtPos(lineStartPos);
29+
};

0 commit comments

Comments
 (0)