Skip to content

Commit

Permalink
refactor(core): make barrel file optional in module type
Browse files Browse the repository at this point in the history
This change is a prerequisite for the upcoming barrel-less mode. The module type
has been updated so that a barrel file is no longer mandatory.

This affects the `path` property, which previously always pointed to the barrel file.
The new implementation supports both barrel-based and barrel-less (inactive) setups

Additionally, the barrel file name is added to the config but remains internal.
  • Loading branch information
rainerhahnekamp committed Oct 5, 2024
1 parent 883a472 commit 849176c
Show file tree
Hide file tree
Showing 25 changed files with 614 additions and 330 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/lib/api/get-project-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ export function getProjectData(

for (const { fileInfo } of traverseFileInfo(projectInfo.fileInfo)) {
const entry: ProjectDataEntry = {
module: fileInfo.moduleInfo.directory || '.',
module: fileInfo.moduleInfo.path || '.',
tags: calcOrGetTags(
fileInfo.moduleInfo.directory,
fileInfo.moduleInfo.path,
projectInfo,
tagsCache,
),
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/lib/checks/check-for-deep-imports.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FsPath } from '../file-info/fs-path';
import { SheriffConfig } from '../config/sheriff-config';
import { ProjectInfo } from '../main/init';
import { FileInfo } from "../modules/file.info";

/**
* verifies if an existing file has deep imports which are forbidden.
Expand All @@ -10,18 +11,19 @@ import { ProjectInfo } from '../main/init';
*/
export function checkForDeepImports(
fsPath: FsPath,
{ rootDir, config, modules, getFileInfo }: ProjectInfo,
{ rootDir, config, getFileInfo }: ProjectInfo,
): string[] {
const deepImports: string[] = [];
const assignedFileInfo = getFileInfo(fsPath);

const isRootAndExcluded = createIsRootAndExcluded(rootDir, config);
const isModuleIndex = (fsPath: FsPath) =>
modules.map((module) => module.path).includes(fsPath);
const isModuleBarrel = (fileInfo: FileInfo) =>
fileInfo.moduleInfo.hasBarrel &&
fileInfo.moduleInfo.barrelPath === fileInfo.path;

for (const importedFileInfo of assignedFileInfo.imports) {
if (
!isModuleIndex(importedFileInfo.path) &&
!isModuleBarrel(importedFileInfo) &&
!isRootAndExcluded(importedFileInfo.moduleInfo.path) &&
importedFileInfo.moduleInfo !== assignedFileInfo.moduleInfo
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ export type DependencyRuleViolation = {

export function checkForDependencyRuleViolation(
fsPath: FsPath,
{ config, getFileInfo, rootDir, modules }: ProjectInfo,
{ config, getFileInfo, rootDir }: ProjectInfo,
): DependencyRuleViolation[] {
const violations: DependencyRuleViolation[] = [];
const modulePaths = modules.map((module) => module.path);

if (config.isConfigFileMissing) {
return [];
}

const assignedFileInfo = getFileInfo(fsPath);
const importedModulePathsWithRawImport = assignedFileInfo.imports
// skip deep imports
.filter((importedFi) => modulePaths.includes(importedFi.path))
// skip imports from barrel of same module
.filter(importedFi => importedFi.moduleInfo.directory !== assignedFileInfo.moduleInfo.directory)
// skip imports of same module
.filter(
(importedFi) =>
importedFi.moduleInfo.path !== assignedFileInfo.moduleInfo.path,
)
.map((fileInfo) => [
fileInfo.moduleInfo.directory,
fileInfo.moduleInfo.path,
assignedFileInfo.getRawImportForImportedFileInfo(fileInfo.path),
]);
const fromModule = toFsPath(assignedFileInfo.moduleInfo.directory);
const fromModule = toFsPath(assignedFileInfo.moduleInfo.path);
const fromTags = calcTagsForModule(
fromModule,
rootDir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('check deep imports', () => {
});

for (const [filename, deepImports] of [
['src/main.ts', []],
// ['src/main.ts', []],
['src/app/app.routes.ts', []],
['src/app/app.component.ts', ['./customers/customer.component']],
['src/app/customers/customer.component.ts', []],
Expand All @@ -32,6 +32,7 @@ describe('check deep imports', () => {
]) {
expect(
checkForDeepImports(toFsPath(`/project/${filename}`), projectInfo),
`failed deep import test for ${filename}`
).toEqual(deepImports);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ describe('check for dependency rule violation', () => {
'customer.component.ts': [],
'customer.ts': [],
'customer.component.spec.ts': ['@app/src/customers', '.index'],
}
},
},
});

Expand All @@ -509,5 +509,44 @@ describe('check for dependency rule violation', () => {
);

expect(violations).toEqual([]);
})
});

it('should check for nested modules that they are not wrongly assigned to the same', () => {
const projectInfo = testInit('src/customers/index.ts', {
'tsconfig.json': tsConfig(),
'sheriff.config.ts': sheriffConfig({
tagging: {
'src/customers': ['domain:customers', 'type:domain'],
'src/customers/feature': ['domain:customers', 'type:feature'],
},
depRules: {
'domain:customers': sameTag,
'type:domain': ['type:feature'],
'type:feature': [],
},
}),
src: {
customers: {
'index.ts': ['./feature', './customer.service.ts'],
'customer.service.ts': [],
feature: {
'index.ts': ['./customer.component.ts'],
'customer.component.ts': ['../customer.service.ts'],
},
},
},
});

const violationsForSuperFolder = checkForDependencyRuleViolation(
toFsPath('/project/src/customers/index.ts'),
projectInfo,
);
expect(violationsForSuperFolder).toEqual([]);

const violationsForSubFolder = checkForDependencyRuleViolation(
toFsPath('/project/src/customers/feature/customer.component.ts'),
projectInfo,
);
expect(violationsForSubFolder).toHaveLength(1)
});
});
2 changes: 1 addition & 1 deletion packages/core/src/lib/cli/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function list(args: string[]) {
Array.from(
projectInfo.modules
.filter((module) => !module.isRoot)
.map((module) => toFsPath(module.directory)),
.map((module) => toFsPath(module.path)),
),
projectInfo,
);
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/lib/config/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export const defaultConfig: SheriffConfig = {
log: false,
entryFile: '',
isConfigFileMissing: false,
barrelFileName: 'index.ts'
};
2 changes: 2 additions & 0 deletions packages/core/src/lib/config/parse-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('parse Config', () => {
'log',
'entryFile',
'isConfigFileMissing',
'barrelFileName',
]);
});

Expand Down Expand Up @@ -70,6 +71,7 @@ export const config: SheriffConfig = {
log: false,
isConfigFileMissing: false,
entryFile: '',
barrelFileName: 'index.ts',
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/lib/config/sheriff-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { UserSheriffConfig } from './user-sheriff-config';
export type SheriffConfig = Required<UserSheriffConfig> & {
// dependency rules will skip if `isConfigFileMissing` is true
isConfigFileMissing: boolean;
barrelFileName: string;
};
1 change: 1 addition & 0 deletions packages/core/src/lib/main/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function init(entryFile: FsPath, options: InitOptions = {}) {
entryFile,
fullOptions.traverse,
tsData,
config,
options.entryFileContent,
),
};
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/lib/main/parse-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { FsPath } from '../file-info/fs-path';
import { FileInfo } from '../modules/file.info';
import { generateUnassignedFileInfo } from '../file-info/generate-unassigned-file-info';
import { getProjectDirsFromFileInfo } from '../modules/get-project-dirs-from-file-info';
import { findModulePaths } from '../modules/find-module-paths';
import { createModules } from '../modules/create-modules';
import { fillFileInfoMap } from '../modules/fill-file-info-map';
import throwIfNull from '../util/throw-if-null';
import { TsData } from '../file-info/ts-data';
import { Module } from '../modules/module';
import { SheriffConfig } from '../config/sheriff-config';
import { findModulePaths } from "../modules/find-module-paths";

export type ParsedResult = {
fileInfo: FileInfo;
Expand All @@ -20,7 +21,8 @@ export const parseProject = (
entryFile: FsPath,
traverse: boolean,
tsData: TsData,
fileContent?: string,
config: SheriffConfig,
fileContent?: string
): ParsedResult => {
const unassignedFileInfo = generateUnassignedFileInfo(
entryFile,
Expand All @@ -36,13 +38,14 @@ export const parseProject = (
const getFileInfo = (path: FsPath) =>
throwIfNull(fileInfoMap.get(path), `cannot find FileInfo for ${path}`);

const modulePaths = findModulePaths(projectDirs);
const modulePaths = findModulePaths(projectDirs, config.tagging, config.barrelFileName);
const modules = createModules(
unassignedFileInfo,
modulePaths,
rootDir,
fileInfoMap,
getFileInfo,
config.barrelFileName
);
fillFileInfoMap(fileInfoMap, modules);

Expand Down
80 changes: 37 additions & 43 deletions packages/core/src/lib/modules/create-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,51 @@ import { Module } from './module';
import { UnassignedFileInfo } from '../file-info/unassigned-file-info';
import traverseUnassignedFileInfo from '../file-info/traverse-unassigned-file-info';
import throwIfNull from '../util/throw-if-null';
import { FsPath } from '../file-info/fs-path';
import { formatModules } from './format-modules';
import get from '../util/get';
import { logger } from '../log';
import { FsPath, toFsPath } from '../file-info/fs-path';
import { FileInfo } from './file.info';
import { ModulePathMap } from './find-module-paths';
import { entries, fromEntries, keys, values } from "../util/typed-object-functions";

const log = logger('core.modules.create');

const findClosestModule = (path: string, moduleInfos: Module[]) => {
return throwIfNull(
moduleInfos
.filter((moduleInfo) => path.startsWith(moduleInfo.directory))
.sort((p1, p2) => (p1.directory.length > p2.directory.length ? -1 : 1))
.at(0),
`findClosestModule for ${path}`,
);
};

export const createModules = (
fileInfo: UnassignedFileInfo,
existingModules: Set<FsPath>,
export function createModules(
entryFileInfo: UnassignedFileInfo,
modulePathMap: ModulePathMap,
rootDir: FsPath,
fileInfoMap: Map<FsPath, FileInfo>,
getFileInfo: (path: FsPath) => FileInfo,
): Module[] => {
const moduleInfos = Array.from(existingModules).map(
(module) => new Module(module, fileInfoMap, getFileInfo, false),
barrelFile: string
): Module[] {
const moduleMap = fromEntries(
entries(modulePathMap).map(([path, hasBarrel]) => [
path,
new Module(toFsPath(path), fileInfoMap, getFileInfo, false, hasBarrel, barrelFile),
]),
);
moduleInfos.push(new Module(rootDir, fileInfoMap, getFileInfo, true));
const moduleInfoMapPerIndexTs = new Map<string, Module>(
moduleInfos.map((moduleInfo) => [moduleInfo.path, moduleInfo]),
);
const moduleInfoMap = new Map<string, Module>(
moduleInfos.map((moduleInfo) => [moduleInfo.directory, moduleInfo]),
// add root module
moduleMap[rootDir] = new Module(
rootDir,
fileInfoMap,
getFileInfo,
true,
false,
barrelFile
);

for (const element of traverseUnassignedFileInfo(fileInfo)) {
const fi = element.fileInfo;
if (isFileInfoAModule(fi, existingModules)) {
get(moduleInfoMapPerIndexTs, fi.path).addFileInfo(fi);
} else {
findClosestModule(fi.path, moduleInfos).addFileInfo(fi);
}
const modulePaths = keys(moduleMap);

for (const { fileInfo } of traverseUnassignedFileInfo(entryFileInfo)) {
const modulePath = findClosestModulePath(fileInfo.path, modulePaths);
moduleMap[modulePath].addFileInfo(fileInfo);
}

const foundModules = Array.from(moduleInfoMap.values());
log.info(formatModules(foundModules));
return foundModules;
};
return values(moduleMap);
}

const isFileInfoAModule = (
{ path }: UnassignedFileInfo,
existingModules: Set<string>,
) => existingModules.has(path);
function findClosestModulePath(path: string, modulePaths: FsPath[]) {
return throwIfNull(
modulePaths
.filter((modulePath) => path.startsWith(modulePath))
.sort((p1, p2) => (p1.length > p2.length ? -1 : 1))
.at(0),
`findClosestModule for ${path}`,
);
}
33 changes: 22 additions & 11 deletions packages/core/src/lib/modules/find-module-paths.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import getFs from '../fs/getFs';
import { FsPath } from '../file-info/fs-path';
import { logger } from '../log';
import { findModulePathsWithoutBarrel } from "./internal/find-module-paths-without-barrel";
import { TagConfig } from "../config/tag-config";
import { findModulePathsWithBarrel } from "./internal/find-module-paths-with-barrel";

const log = logger('core.modules.find-path');
export type ModulePathMap = Record<FsPath, boolean>

export const findModulePaths = (projectDirs: FsPath[]): Set<FsPath> => {
const fs = getFs();
let modules: FsPath[] = [];
/**
* Find module paths which can be defined via having a barrel file or the
* configuration's property `modules`.
*
* If a module has a barrel file and an internal, it is of type barrel file.
*/
export function findModulePaths(projectDirs: FsPath[], moduleConfig: TagConfig, barrelFileName: string): ModulePathMap {
const modulesWithoutBarrel = findModulePathsWithoutBarrel(projectDirs, moduleConfig);
const modulesWithBarrel = findModulePathsWithBarrel(projectDirs, barrelFileName);
const modulePaths: ModulePathMap = {};

for (const projectDir of projectDirs) {
modules = modules.concat(fs.findFiles(projectDir, 'index.ts'));
for (const path of modulesWithoutBarrel) {
modulePaths[path] = false;
}

log.info(modules.join(', '));
return new Set(modules);
};
for (const path of modulesWithBarrel) {
modulePaths[path] = true;
}

return modulePaths;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FsPath, toFsPath } from '../../file-info/fs-path';
import getFs from '../../fs/getFs';

export function findModulePathsWithBarrel(
projectDirs: FsPath[],
barrelFileName: string,
): FsPath[] {
return projectDirs.flatMap((projectDir) =>
getFs()
.findFiles(projectDir, barrelFileName)
.map((path) => path.slice(0, -(barrelFileName.length + 1)))
.map(toFsPath),
);
}
Loading

0 comments on commit 849176c

Please sign in to comment.