fix(linter): disable absolute paths within project (#5555)
* fix(linter): disable absolute paths within project * fix(linter): add missing colon before the import path * feat(linter): add flag to disable self circular check
This commit is contained in:
parent
e20cf134a8
commit
f28097e264
@ -19,28 +19,27 @@ import {
|
|||||||
import { createESLintRule } from '../utils/create-eslint-rule';
|
import { createESLintRule } from '../utils/create-eslint-rule';
|
||||||
import { normalizePath } from '@nrwl/devkit';
|
import { normalizePath } from '@nrwl/devkit';
|
||||||
import {
|
import {
|
||||||
createProjectGraph,
|
|
||||||
isNpmProject,
|
isNpmProject,
|
||||||
ProjectGraph,
|
ProjectGraph,
|
||||||
ProjectType,
|
ProjectType,
|
||||||
} from '@nrwl/workspace/src/core/project-graph';
|
} from '@nrwl/workspace/src/core/project-graph';
|
||||||
import {
|
import { readNxJson } from '@nrwl/workspace/src/core/file-utils';
|
||||||
readNxJson,
|
|
||||||
readWorkspaceJson,
|
|
||||||
} from '@nrwl/workspace/src/core/file-utils';
|
|
||||||
import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator';
|
import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator';
|
||||||
import { checkCircularPath } from '@nrwl/workspace/src/utils/graph-utils';
|
import { checkCircularPath } from '@nrwl/workspace/src/utils/graph-utils';
|
||||||
import { readCurrentProjectGraph } from '@nrwl/workspace/src/core/project-graph/project-graph';
|
import { readCurrentProjectGraph } from '@nrwl/workspace/src/core/project-graph/project-graph';
|
||||||
|
import { isRelativePath } from '@nrwl/workspace/src/utilities/fileutils';
|
||||||
|
|
||||||
type Options = [
|
type Options = [
|
||||||
{
|
{
|
||||||
allow: string[];
|
allow: string[];
|
||||||
depConstraints: DepConstraint[];
|
depConstraints: DepConstraint[];
|
||||||
enforceBuildableLibDependency: boolean;
|
enforceBuildableLibDependency: boolean;
|
||||||
|
allowCircularSelfDependency: boolean;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export type MessageIds =
|
export type MessageIds =
|
||||||
| 'noRelativeOrAbsoluteImportsAcrossLibraries'
|
| 'noRelativeOrAbsoluteImportsAcrossLibraries'
|
||||||
|
| 'noSelfCircularDependencies'
|
||||||
| 'noCircularDependencies'
|
| 'noCircularDependencies'
|
||||||
| 'noImportsOfApps'
|
| 'noImportsOfApps'
|
||||||
| 'noImportsOfE2e'
|
| 'noImportsOfE2e'
|
||||||
@ -83,6 +82,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
messages: {
|
messages: {
|
||||||
noRelativeOrAbsoluteImportsAcrossLibraries: `Libraries cannot be imported by a relative or absolute path, and must begin with a npm scope`,
|
noRelativeOrAbsoluteImportsAcrossLibraries: `Libraries cannot be imported by a relative or absolute path, and must begin with a npm scope`,
|
||||||
noCircularDependencies: `Circular dependency between "{{sourceProjectName}}" and "{{targetProjectName}}" detected: {{path}}`,
|
noCircularDependencies: `Circular dependency between "{{sourceProjectName}}" and "{{targetProjectName}}" detected: {{path}}`,
|
||||||
|
noSelfCircularDependencies: `Only relative imports are allowed within the project. Absolute import found: {{imp}}`,
|
||||||
noImportsOfApps: 'Imports of apps are forbidden',
|
noImportsOfApps: 'Imports of apps are forbidden',
|
||||||
noImportsOfE2e: 'Imports of e2e projects are forbidden',
|
noImportsOfE2e: 'Imports of e2e projects are forbidden',
|
||||||
noImportOfNonBuildableLibraries:
|
noImportOfNonBuildableLibraries:
|
||||||
@ -97,9 +97,20 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
allow: [],
|
allow: [],
|
||||||
depConstraints: [],
|
depConstraints: [],
|
||||||
enforceBuildableLibDependency: false,
|
enforceBuildableLibDependency: false,
|
||||||
|
allowCircularSelfDependency: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
create(context, [{ allow, depConstraints, enforceBuildableLibDependency }]) {
|
create(
|
||||||
|
context,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
allow,
|
||||||
|
depConstraints,
|
||||||
|
enforceBuildableLibDependency,
|
||||||
|
allowCircularSelfDependency,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* Globally cached info about workspace
|
* Globally cached info about workspace
|
||||||
*/
|
*/
|
||||||
@ -193,6 +204,16 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
|
|
||||||
// same project => allow
|
// same project => allow
|
||||||
if (sourceProject === targetProject) {
|
if (sourceProject === targetProject) {
|
||||||
|
// we only allow relative paths within the same project
|
||||||
|
if (!allowCircularSelfDependency && !isRelativePath(imp)) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: 'noSelfCircularDependencies',
|
||||||
|
data: {
|
||||||
|
imp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,11 @@ import {
|
|||||||
import { TSESLint } from '@typescript-eslint/experimental-utils';
|
import { TSESLint } from '@typescript-eslint/experimental-utils';
|
||||||
import * as parser from '@typescript-eslint/parser';
|
import * as parser from '@typescript-eslint/parser';
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
import { extname, join } from 'path';
|
import { extname } from 'path';
|
||||||
import enforceModuleBoundaries, {
|
import enforceModuleBoundaries, {
|
||||||
RULE_NAME as enforceModuleBoundariesRuleName,
|
RULE_NAME as enforceModuleBoundariesRuleName,
|
||||||
} from '../../src/rules/enforce-module-boundaries';
|
} from '../../src/rules/enforce-module-boundaries';
|
||||||
import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator';
|
import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
jest.mock('fs', () => require('memfs').fs);
|
jest.mock('fs', () => require('memfs').fs);
|
||||||
jest.mock('../../../workspace/src/utilities/app-root', () => ({
|
jest.mock('../../../workspace/src/utilities/app-root', () => ({
|
||||||
appRootPath: '/root',
|
appRootPath: '/root',
|
||||||
@ -794,6 +793,130 @@ describe('Enforce Module Boundaries', () => {
|
|||||||
expect(failures[1].message).toEqual(message);
|
expect(failures[1].message).toEqual(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should error when absolute path within project detected', () => {
|
||||||
|
const failures = runRule(
|
||||||
|
{},
|
||||||
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
||||||
|
`
|
||||||
|
import '@mycompany/mylib';
|
||||||
|
import('@mycompany/mylib');
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
nodes: {
|
||||||
|
mylibName: {
|
||||||
|
name: 'mylibName',
|
||||||
|
type: ProjectType.lib,
|
||||||
|
data: {
|
||||||
|
root: 'libs/mylib',
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [],
|
||||||
|
architect: {},
|
||||||
|
files: [createFile(`libs/mylib/src/main.ts`)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
anotherlibName: {
|
||||||
|
name: 'anotherlibName',
|
||||||
|
type: ProjectType.lib,
|
||||||
|
data: {
|
||||||
|
root: 'libs/anotherlib',
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [],
|
||||||
|
architect: {},
|
||||||
|
files: [createFile(`libs/anotherlib/src/main.ts`)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
myappName: {
|
||||||
|
name: 'myappName',
|
||||||
|
type: ProjectType.app,
|
||||||
|
data: {
|
||||||
|
root: 'apps/myapp',
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [],
|
||||||
|
architect: {},
|
||||||
|
files: [createFile(`apps/myapp/src/index.ts`)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
mylibName: [
|
||||||
|
{
|
||||||
|
source: 'mylibName',
|
||||||
|
target: 'anotherlibName',
|
||||||
|
type: DependencyType.static,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const message =
|
||||||
|
'Only relative imports are allowed within the project. Absolute import found: @mycompany/mylib';
|
||||||
|
expect(failures.length).toEqual(2);
|
||||||
|
expect(failures[0].message).toEqual(message);
|
||||||
|
expect(failures[1].message).toEqual(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore detected absolute path within project if allowCircularSelfDependency flag is set', () => {
|
||||||
|
const failures = runRule(
|
||||||
|
{
|
||||||
|
allowCircularSelfDependency: true,
|
||||||
|
},
|
||||||
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
||||||
|
`
|
||||||
|
import '@mycompany/mylib';
|
||||||
|
import('@mycompany/mylib');
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
nodes: {
|
||||||
|
mylibName: {
|
||||||
|
name: 'mylibName',
|
||||||
|
type: ProjectType.lib,
|
||||||
|
data: {
|
||||||
|
root: 'libs/mylib',
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [],
|
||||||
|
architect: {},
|
||||||
|
files: [createFile(`libs/mylib/src/main.ts`)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
anotherlibName: {
|
||||||
|
name: 'anotherlibName',
|
||||||
|
type: ProjectType.lib,
|
||||||
|
data: {
|
||||||
|
root: 'libs/anotherlib',
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [],
|
||||||
|
architect: {},
|
||||||
|
files: [createFile(`libs/anotherlib/src/main.ts`)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
myappName: {
|
||||||
|
name: 'myappName',
|
||||||
|
type: ProjectType.app,
|
||||||
|
data: {
|
||||||
|
root: 'apps/myapp',
|
||||||
|
tags: [],
|
||||||
|
implicitDependencies: [],
|
||||||
|
architect: {},
|
||||||
|
files: [createFile(`apps/myapp/src/index.ts`)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
mylibName: [
|
||||||
|
{
|
||||||
|
source: 'mylibName',
|
||||||
|
target: 'anotherlibName',
|
||||||
|
type: DependencyType.static,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(failures.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('should error when circular dependency detected', () => {
|
it('should error when circular dependency detected', () => {
|
||||||
const failures = runRule(
|
const failures = runRule(
|
||||||
{},
|
{},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user