Skip to content

Commit

Permalink
fix(core): map non-source import references into actual usage (#36)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
EladBezalel authored Feb 4, 2025
1 parent 38fa42c commit abd03a7
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 7 additions & 0 deletions libs/core/src/__fixtures__/monorepo/non-source-proj/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-next-line
import data from './data.json';

export function nonSourceProj() {
return data;
}
14 changes: 14 additions & 0 deletions libs/core/src/__fixtures__/monorepo/non-source-proj/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["**/*.ts"],
"exclude": []
}
6 changes: 6 additions & 0 deletions libs/core/src/__fixtures__/monorepo/proj3/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { nonSourceProj } from '@monorepo/non-source-proj';

export function proj3() {
nonSourceProj();
return 'proj3';
}
3 changes: 3 additions & 0 deletions libs/core/src/__fixtures__/monorepo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"@monorepo/proj3": [
"./proj3/index.ts"
],
"@monorepo/non-source-proj": [
"./non-source-proj/index.ts"
]
}
},
}
60 changes: 47 additions & 13 deletions libs/core/src/true-affected.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,46 @@ describe('trueAffected', () => {
expect(affected).toEqual(['angular-component']);
});

it('should find files and usages that are related to changed files which are not in projects files', async () => {
jest.spyOn(git, 'getChangedFiles').mockReturnValue([
{
filePath: 'non-source-proj/data.json',
changedLines: [0],
},
]);

const affected = await trueAffected({
cwd,
base: 'main',
rootTsConfig: 'tsconfig.json',
projects: [
{
name: 'proj1',
sourceRoot: 'proj1/',
tsConfig: 'proj1/tsconfig.json',
},
{
name: 'proj2',
sourceRoot: 'proj2/',
tsConfig: 'proj2/tsconfig.json',
},
{
name: 'proj3',
sourceRoot: 'proj3/',
tsConfig: 'proj3/tsconfig.json',
implicitDependencies: ['proj1'],
},
{
name: 'non-source-proj',
sourceRoot: 'non-source-proj/',
tsConfig: 'non-source-proj/tsconfig.json',
},
],
});

expect(affected).toEqual(['non-source-proj', 'proj3']);
});

describe('__experimentalLockfileCheck', () => {
it('should find files that are related to changed modules from lockfile if flag is on', async () => {
jest.spyOn(git, 'getChangedFiles').mockReturnValue([
Expand Down Expand Up @@ -491,17 +531,11 @@ describe('trueAffected', () => {

const compilerOptions = {
paths: {
"@monorepo/proj1": [
"./proj1/index.ts"
],
"@monorepo/proj2": [
"./proj2/index.ts"
],
"@monorepo/proj3": [
"./proj3/index.ts"
],
}
}
'@monorepo/proj1': ['./proj1/index.ts'],
'@monorepo/proj2': ['./proj2/index.ts'],
'@monorepo/proj3': ['./proj3/index.ts'],
},
};

const affected = await trueAffected({
cwd,
Expand All @@ -523,9 +557,9 @@ describe('trueAffected', () => {
tsConfig: 'proj3/tsconfig.json',
},
],
compilerOptions
compilerOptions,
});

expect(affected).toEqual(['proj1']);
})
});
});
91 changes: 85 additions & 6 deletions libs/core/src/true-affected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { join, resolve } from 'path';
import { Project, Node, ts, SyntaxKind } from 'ts-morph';
import chalk from 'chalk';
import { ChangedFiles, getChangedFiles } from './git';
import { findRootNode, getPackageNameByPath } from './utils';
import { findNodeAtLine, findRootNode, getPackageNameByPath } from './utils';
import { TrueAffected, TrueAffectedProject } from './types';
import { findNonSourceAffectedFiles } from './assets';
import {
Expand Down Expand Up @@ -129,6 +129,75 @@ export const trueAffected = async ({
nonSourceChangedFiles.length
)} non-source affected files`
);

logger.debug(
'Mapping non-source affected files imports to actual references'
);

nonSourceChangedFiles = nonSourceChangedFiles.flatMap(
({ filePath, changedLines }) => {
const file = project.getSourceFile(resolve(cwd, filePath));
/* istanbul ignore next */
if (file == null) return [];

return changedLines.reduce(
(acc, line) => {
const changedNode = findNodeAtLine(file, line);
const rootNode = findRootNode(changedNode);
/* istanbul ignore next */
if (!rootNode) return acc;

if (!rootNode.isKind(SyntaxKind.ImportDeclaration)) {
return {
...acc,
changedLines: [...acc.changedLines, ...changedLines],
};
}

logger.debug(
`Found changed node ${chalk.bold(
rootNode?.getText() ?? 'undefined'
)} at line ${chalk.bold(line)} in ${chalk.bold(filePath)}`
);

const identifier =
rootNode.getFirstChildByKind(SyntaxKind.Identifier) ??
rootNode.getFirstDescendantByKind(SyntaxKind.Identifier);

/* istanbul ignore next */
if (identifier == null) return acc;

logger.debug(
`Found identifier ${chalk.bold(
identifier.getText()
)} in ${chalk.bold(filePath)}`
);

const refs = identifier.findReferencesAsNodes();

logger.debug(
`Found ${chalk.bold(
refs.length
)} references for identifier ${chalk.bold(
identifier.getText()
)}`
);

return {
...acc,
changedLines: [
...acc.changedLines,
...refs.map((node) => node.getStartLineNumber()),
],
};
},
{
filePath,
changedLines: [],
} as ChangedFiles
);
}
);
}
}

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

/* istanbul ignore next */
if (rootNode == null) return;
if (rootNode == null) {
logger.debug(
`Could not find root node for ${chalk.bold(node.getText())}`
);
return;
}

if (ignoredRootNodeTypes.find((type) => rootNode.isKind(type))) return;
if (ignoredRootNodeTypes.find((type) => rootNode.isKind(type))) {
logger.debug(
`Ignoring root node ${chalk.bold(
rootNode.getText()
)} of type ${chalk.bold(rootNode.getKindName())}`
);
return;
}

const identifier =
rootNode.getFirstChildByKind(SyntaxKind.Identifier) ??
Expand Down Expand Up @@ -235,9 +316,7 @@ export const trueAffected = async ({

changedLines.forEach((line) => {
try {
const lineStartPos =
sourceFile.compilerNode.getPositionOfLineAndCharacter(line - 1, 0);
const changedNode = sourceFile.getDescendantAtPos(lineStartPos);
const changedNode = findNodeAtLine(sourceFile, line);

/* istanbul ignore next */
if (!changedNode) return;
Expand Down
22 changes: 21 additions & 1 deletion libs/core/src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { findRootNode, getPackageNameByPath } from './utils';
import { findRootNode, getPackageNameByPath, findNodeAtLine } from './utils';
import { Project, SyntaxKind } from 'ts-morph';

describe('findRootNode', () => {
Expand Down Expand Up @@ -52,3 +52,23 @@ describe('getPackageNameByPath', () => {
expect(packageName).toBeUndefined();
});
});

describe('findNodeAtLine', () => {
it('should find the node at line', () => {
const project = new Project({ useInMemoryFileSystem: true });

const file = project.createSourceFile(
'file.ts',
`import { bar } from 'bar';
export const foo = 1;`
);

const importNode = findNodeAtLine(file, 1);

expect(importNode?.getKind()).toEqual(SyntaxKind.ImportKeyword);

const exportNode = findNodeAtLine(file, 2);

expect(exportNode?.getKind()).toEqual(SyntaxKind.ExportKeyword);
});
});
13 changes: 12 additions & 1 deletion libs/core/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ts, SyntaxKind, Node } from 'ts-morph';
import { ts, SyntaxKind, Node, SourceFile } from 'ts-morph';
import { TrueAffectedProject } from './types';

export const findRootNode = (
Expand All @@ -16,3 +16,14 @@ export const getPackageNameByPath = (
): string | undefined => {
return projects.find(({ sourceRoot }) => path.includes(sourceRoot))?.name;
};

export const findNodeAtLine = (
sourceFile: SourceFile,
line: number
): Node<ts.Node> | undefined => {
const lineStartPos = sourceFile.compilerNode.getPositionOfLineAndCharacter(
line - 1,
0
);
return sourceFile.getDescendantAtPos(lineStartPos);
};

0 comments on commit abd03a7

Please sign in to comment.