fix(linter): use ng-packager for checking secondary entry points in linter (#14425)
This commit is contained in:
parent
5cfd357e8f
commit
c23b74ef89
@ -313,7 +313,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
if (
|
||||
!allowCircularSelfDependency &&
|
||||
!isRelativePath(imp) &&
|
||||
!isAngularSecondaryEntrypoint(targetProjectLocator, imp)
|
||||
!isAngularSecondaryEntrypoint(imp, sourceFilePath)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user