From c23b74ef89802dfae5dd0124f3b179b9d769eec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Jona=C5=A1?= Date: Wed, 1 Feb 2023 18:58:50 +0100 Subject: [PATCH] fix(linter): use ng-packager for checking secondary entry points in linter (#14425) --- .../src/rules/enforce-module-boundaries.ts | 2 +- .../src/utils/runtime-lint-utils.spec.ts | 69 +++++++++++++++++++ .../src/utils/runtime-lint-utils.ts | 44 ++++++++---- 3 files changed, 99 insertions(+), 16 deletions(-) diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts index 4d2a09f1ec..d0fcf60b1b 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts @@ -313,7 +313,7 @@ export default createESLintRule({ if ( !allowCircularSelfDependency && !isRelativePath(imp) && - !isAngularSecondaryEntrypoint(targetProjectLocator, imp) + !isAngularSecondaryEntrypoint(imp, sourceFilePath) ) { context.report({ node, diff --git a/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.spec.ts b/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.spec.ts index 0f18413115..63514aa465 100644 --- a/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.spec.ts +++ b/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.spec.ts @@ -8,8 +8,15 @@ import { findTransitiveExternalDependencies, hasBannedDependencies, hasBannedImport, + isAngularSecondaryEntrypoint, isTerminalRun, } from './runtime-lint-utils'; +import { vol } from 'memfs'; + +jest.mock('nx/src/utils/workspace-root', () => ({ + workspaceRoot: '/root', +})); +jest.mock('fs', () => require('memfs').fs); describe('hasBannedImport', () => { const source: ProjectGraphProjectNode = { @@ -313,3 +320,65 @@ describe('is terminal run', () => { expect(isTerminalRun()).toBe(false); }); }); + +describe('isAngularSecondaryEntrypoint', () => { + beforeEach(() => { + const tsConfig = { + compilerOptions: { + baseUrl: '.', + resolveJsonModule: true, + paths: { + '@project/standard': ['libs/standard/src/index.ts'], + '@project/standard/secondary': [ + 'libs/standard/secondary/src/index.ts', + ], + '@project/standard/tertiary': [ + 'libs/standard/tertiary/src/public_api.ts', + ], + '@project/features': ['libs/features/src/index.ts'], + '@project/features/*': ['libs/features/*/random/folder/api.ts'], + }, + }, + }; + const fsJson = { + 'tsconfig.base.json': JSON.stringify(tsConfig), + 'apps/app.ts': '', + 'libs/standard/package.json': '{ "version": "0.0.0" }', + 'libs/standard/secondary/ng-package.json': JSON.stringify({ + version: '0.0.0', + ngPackage: { lib: { entryFile: 'src/index.ts' } }, + }), + 'libs/standard/secondary/src/index.ts': 'const bla = "foo"', + 'libs/standard/tertiary/ng-package.json': JSON.stringify({ + version: '0.0.0', + ngPackage: { lib: { entryFile: 'src/public_api.ts' } }, + }), + 'libs/standard/tertiary/src/public_api.ts': 'const bla = "foo"', + 'libs/features/package.json': '{ "version": "0.0.0" }', + 'libs/features/secondary/ng-package.json': JSON.stringify({ + version: '0.0.0', + ngPackage: { lib: { entryFile: 'random/folder/api.ts' } }, + }), + 'libs/features/secondary/random/folder/api.ts': 'const bla = "foo"', + }; + vol.fromJSON(fsJson, '/root'); + }); + + it('should return true for secondary entrypoints', () => { + expect( + isAngularSecondaryEntrypoint('@project/standard', 'apps/app.ts') + ).toBe(false); + expect( + isAngularSecondaryEntrypoint('@project/standard/secondary', 'apps/app.ts') + ).toBe(true); + expect( + isAngularSecondaryEntrypoint('@project/standard/tertiary', 'apps/app.ts') + ).toBe(true); + expect( + isAngularSecondaryEntrypoint('@project/features', 'apps/app.ts') + ).toBe(false); + expect( + isAngularSecondaryEntrypoint('@project/features/secondary', 'apps/app.ts') + ).toBe(true); + }); +}); diff --git a/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts b/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts index 7da2aa59b1..ab816546c5 100644 --- a/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts +++ b/packages/eslint-plugin-nx/src/utils/runtime-lint-utils.ts @@ -12,13 +12,16 @@ import { workspaceRoot, } from '@nrwl/devkit'; import { getPath, pathExists } from './graph-utils'; -import { existsSync } from 'fs'; import { readFileIfExisting } from 'nx/src/project-graph/file-utils'; import { TargetProjectLocator } from 'nx/src/utils/target-project-locator'; import { findProjectForPath, ProjectRootMappings, } from 'nx/src/project-graph/utils/find-project-for-path'; +import { + getRootTsConfigFileName, + resolveModuleByImport, +} from 'nx/src/utils/typescript'; export type Deps = { [projectName: string]: ProjectGraphDependency[] }; type SingleSourceTagConstraint = { @@ -415,20 +418,31 @@ export function groupImports( * @returns */ export function isAngularSecondaryEntrypoint( - targetProjectLocator: TargetProjectLocator, - importExpr: string + importExpr: string, + filePath: string ): boolean { - const targetFiles = targetProjectLocator.findPaths(importExpr); - return ( - targetFiles && - targetFiles.some( - (file) => - // The `ng-packagr` defaults to the `src/public_api.ts` entry file to - // the public API if the `lib.entryFile` is not specified explicitly. - (file.endsWith('src/public_api.ts') || file.endsWith('src/index.ts')) && - existsSync( - joinPathFragments(workspaceRoot, file, '../../', 'ng-package.json') - ) - ) + const resolvedModule = resolveModuleByImport( + importExpr, + filePath, + join(workspaceRoot, getRootTsConfigFileName()) ); + + return !!resolvedModule && fileIsSecondaryEntryPoint(resolvedModule); +} + +function fileIsSecondaryEntryPoint(file: string): boolean { + let parent = joinPathFragments(file, '../'); + while (parent !== './') { + // we need to find closest existing ng-package.json + // in order to determine if the file matches the secondary entry point + const ngPackageContent = readFileIfExisting( + joinPathFragments(workspaceRoot, parent, 'ng-package.json') + ); + if (ngPackageContent) { + const entryFile = parseJson(ngPackageContent)?.ngPackage?.lib?.entryFile; + return entryFile && file === joinPathFragments(parent, entryFile); + } + parent = joinPathFragments(parent, '../'); + } + return false; }