fix(linter): add missing self-circular check for tslint (#5613)

* fix(linter): add missing self-circular check for tslint

* feat(linter): add test for tslint self-circular deps
This commit is contained in:
Miroslav Jonaš 2021-05-11 18:39:12 +02:00 committed by GitHub
parent 67e78dc051
commit 6d22f628b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 9 deletions

View File

@ -64,6 +64,7 @@ export default createESLintRule<Options, MessageIds>({
type: 'object', type: 'object',
properties: { properties: {
enforceBuildableLibDependency: { type: 'boolean' }, enforceBuildableLibDependency: { type: 'boolean' },
allowCircularSelfDependency: { type: 'boolean' },
allow: [{ type: 'string' }], allow: [{ type: 'string' }],
depConstraints: [ depConstraints: [
{ {

View File

@ -1,5 +1,5 @@
import { vol } from 'memfs'; import { vol } from 'memfs';
import { extname, join } from 'path'; import { extname } from 'path';
import { RuleFailure } from 'tslint'; import { RuleFailure } from 'tslint';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { import {
@ -9,7 +9,6 @@ import {
} from '../core/project-graph'; } from '../core/project-graph';
import { Rule } from './nxEnforceModuleBoundariesRule'; import { Rule } from './nxEnforceModuleBoundariesRule';
import { TargetProjectLocator } from '../core/target-project-locator'; import { TargetProjectLocator } from '../core/target-project-locator';
import { readFileSync } from 'fs';
jest.mock('fs', () => require('memfs').fs); jest.mock('fs', () => require('memfs').fs);
jest.mock('../utilities/app-root', () => ({ appRootPath: '/root' })); jest.mock('../utilities/app-root', () => ({ appRootPath: '/root' }));
@ -726,6 +725,121 @@ describe('Enforce Module Boundaries', () => {
); );
}); });
it('should error when absolute path within project detected', () => {
const failures = runRule(
{},
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
'import "@mycompany/mylib"',
{
nodes: {
mylibName: {
name: 'mylibName',
type: ProjectType.lib,
data: {
root: 'libs/mylib',
tags: [],
implicitDependencies: [],
targets: {},
files: [createFile(`libs/mylib/src/main.ts`)],
},
},
anotherlibName: {
name: 'anotherlibName',
type: ProjectType.lib,
data: {
root: 'libs/anotherlib',
tags: [],
implicitDependencies: [],
targets: {},
files: [createFile(`libs/anotherlib/src/main.ts`)],
},
},
myappName: {
name: 'myappName',
type: ProjectType.app,
data: {
root: 'apps/myapp',
tags: [],
implicitDependencies: [],
targets: {},
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(1);
expect(failures[0].getFailure()).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"',
{
nodes: {
mylibName: {
name: 'mylibName',
type: ProjectType.lib,
data: {
root: 'libs/mylib',
tags: [],
implicitDependencies: [],
targets: {},
files: [createFile(`libs/mylib/src/main.ts`)],
},
},
anotherlibName: {
name: 'anotherlibName',
type: ProjectType.lib,
data: {
root: 'libs/anotherlib',
tags: [],
implicitDependencies: [],
targets: {},
files: [createFile(`libs/anotherlib/src/main.ts`)],
},
},
myappName: {
name: 'myappName',
type: ProjectType.app,
data: {
root: 'apps/myapp',
tags: [],
implicitDependencies: [],
targets: {},
files: [createFile(`apps/myapp/src/index.ts`)],
},
},
},
dependencies: {
mylibName: [
{
source: 'mylibName',
target: 'anotherlibName',
type: DependencyType.static,
},
],
},
}
);
expect(failures.length).toEqual(0);
});
it('should error when circular dependency detected', () => { it('should error when circular dependency detected', () => {
const failures = runRule( const failures = runRule(
{}, {},

View File

@ -1,12 +1,7 @@
import * as Lint from 'tslint'; import * as Lint from 'tslint';
import { IOptions } from 'tslint'; import { IOptions } from 'tslint';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { import { isNpmProject, ProjectGraph, ProjectType } from '../core/project-graph';
createProjectGraph,
isNpmProject,
ProjectGraph,
ProjectType,
} from '../core/project-graph';
import { appRootPath } from '../utilities/app-root'; import { appRootPath } from '../utilities/app-root';
import { import {
DepConstraint, DepConstraint,
@ -26,6 +21,7 @@ import { readNxJson } from '@nrwl/workspace/src/core/file-utils';
import { TargetProjectLocator } from '../core/target-project-locator'; import { TargetProjectLocator } from '../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 '../utilities/fileutils';
export class Rule extends Lint.Rules.AbstractRule { export class Rule extends Lint.Rules.AbstractRule {
constructor( constructor(
@ -75,6 +71,7 @@ class EnforceModuleBoundariesWalker extends Lint.RuleWalker {
private readonly allow: string[]; private readonly allow: string[];
private readonly enforceBuildableLibDependency: boolean = false; // for backwards compat private readonly enforceBuildableLibDependency: boolean = false; // for backwards compat
private readonly depConstraints: DepConstraint[]; private readonly depConstraints: DepConstraint[];
private readonly allowCircularSelfDependency: boolean = false;
constructor( constructor(
sourceFile: ts.SourceFile, sourceFile: ts.SourceFile,
@ -96,6 +93,9 @@ class EnforceModuleBoundariesWalker extends Lint.RuleWalker {
this.enforceBuildableLibDependency = this.enforceBuildableLibDependency =
this.getOptions()[0].enforceBuildableLibDependency === true; this.getOptions()[0].enforceBuildableLibDependency === true;
this.allowCircularSelfDependency =
this.getOptions()[0].allowCircularSelfDependency === true;
} }
public visitImportDeclaration(node: ts.ImportDeclaration) { public visitImportDeclaration(node: ts.ImportDeclaration) {
@ -152,7 +152,12 @@ class EnforceModuleBoundariesWalker extends Lint.RuleWalker {
// same project => allow // same project => allow
if (sourceProject === targetProject) { if (sourceProject === targetProject) {
if (!this.allowCircularSelfDependency && !isRelativePath(imp)) {
const error = `Only relative imports are allowed within the project. Absolute import found: ${imp}`;
this.addFailureAt(node.getStart(), node.getWidth(), error);
} else {
super.visitImportDeclaration(node); super.visitImportDeclaration(node);
}
return; return;
} }