fix(linter): use ng-packager for checking secondary entry points in linter (#14425)

This commit is contained in:
Miroslav Jonaš 2023-02-01 18:58:50 +01:00 committed by GitHub
parent 5cfd357e8f
commit c23b74ef89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 16 deletions

View File

@ -313,7 +313,7 @@ export default createESLintRule<Options, MessageIds>({
if ( if (
!allowCircularSelfDependency && !allowCircularSelfDependency &&
!isRelativePath(imp) && !isRelativePath(imp) &&
!isAngularSecondaryEntrypoint(targetProjectLocator, imp) !isAngularSecondaryEntrypoint(imp, sourceFilePath)
) { ) {
context.report({ context.report({
node, node,

View File

@ -8,8 +8,15 @@ import {
findTransitiveExternalDependencies, findTransitiveExternalDependencies,
hasBannedDependencies, hasBannedDependencies,
hasBannedImport, hasBannedImport,
isAngularSecondaryEntrypoint,
isTerminalRun, isTerminalRun,
} from './runtime-lint-utils'; } 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', () => { describe('hasBannedImport', () => {
const source: ProjectGraphProjectNode = { const source: ProjectGraphProjectNode = {
@ -313,3 +320,65 @@ describe('is terminal run', () => {
expect(isTerminalRun()).toBe(false); 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);
});
});

View File

@ -12,13 +12,16 @@ import {
workspaceRoot, workspaceRoot,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { getPath, pathExists } from './graph-utils'; import { getPath, pathExists } from './graph-utils';
import { existsSync } from 'fs';
import { readFileIfExisting } from 'nx/src/project-graph/file-utils'; import { readFileIfExisting } from 'nx/src/project-graph/file-utils';
import { TargetProjectLocator } from 'nx/src/utils/target-project-locator'; import { TargetProjectLocator } from 'nx/src/utils/target-project-locator';
import { import {
findProjectForPath, findProjectForPath,
ProjectRootMappings, ProjectRootMappings,
} from 'nx/src/project-graph/utils/find-project-for-path'; } from 'nx/src/project-graph/utils/find-project-for-path';
import {
getRootTsConfigFileName,
resolveModuleByImport,
} from 'nx/src/utils/typescript';
export type Deps = { [projectName: string]: ProjectGraphDependency[] }; export type Deps = { [projectName: string]: ProjectGraphDependency[] };
type SingleSourceTagConstraint = { type SingleSourceTagConstraint = {
@ -415,20 +418,31 @@ export function groupImports(
* @returns * @returns
*/ */
export function isAngularSecondaryEntrypoint( export function isAngularSecondaryEntrypoint(
targetProjectLocator: TargetProjectLocator, importExpr: string,
importExpr: string filePath: string
): boolean { ): boolean {
const targetFiles = targetProjectLocator.findPaths(importExpr); const resolvedModule = resolveModuleByImport(
return ( importExpr,
targetFiles && filePath,
targetFiles.some( join(workspaceRoot, getRootTsConfigFileName())
(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')
)
)
); );
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;
} }