tslint-circular-dependencies
v0.1.0tslint
rules to work around circular dependencies
This package contains four rules, including fixes, to work around some of the issues that arise with circular imports:
imports-after-export
initialize-statics-after-imports
new-instance-after-imports
no-instanceof-operator
Usage
Install
npm install -D tslint-circular-dependencies
This will install the rules and set up your tslint.json
file.
TypeScript 2.4.1 These rules have been tested with TypeScript 2.4.1. If you're seeing no output when you run these rules, try updating TypeScript to this version.
Run
tslint [path] --fix
Manually configuring tslint.json
(optional)
This package will install itself into your tslint.json
as follows:
"extends" : [
...
"tslint-circular-dependencies"
...
]
Keep the rule names intact.
tslint
does not document a certain execution order for rules, but right now they are executed in alphabetic order. It is important that the rules in this package are executed in a particular order, and thats 1.imports-after-export
, 2.initialize-statics-after-imports
, 3.new-instance-after-imports
.
Inside the Rules
imports-after-export
Moves all import
statements – except those that are used as superclasses in an extends
clause – after the last export
statement.
Why
Circular import
s work if the import
occurs after export
.
The following code will fail:
// a.ts
import { B } from './b';
export class A {
constructor() {
this.b = new B();
}
}
// b.ts
import { A } from './a';
export class B {
constructor() {
this.a = new A();
}
}
This fails because at the time the import
is executed, module.exports
is still undefined
.
Step | Statement | a.exports | b.exports |
---|---|---|---|
1 | import { B } from './b'; |
undefined |
undefined |
2 | import { A } from './a'; |
undefined |
undefined |
3 | export class B {...} |
undefined |
class B |
4 | export class A {...} |
class A |
class B |
The following code will work:
// a.ts
export class A {
constructor() {
this.b = new B();
}
}
import { B } from './b';
// b.ts
export class B {
constructor() {
this.a = new A();
}
}
import { A } from './a';
Step | statement | a.exports | b.exports |
---|---|---|---|
1 | export class A {...} |
class A |
undefined |
2 | import { B } from './b'; |
class A |
undefined |
3 | export class B {...} |
class A |
class B |
4 | import { A } from './a'; |
class A |
class B |
initialize-statics-after-imports
This rule
- encapsulates non-primitive
static
initializers in astatic
function
- executes that
static
function
after allimport
statements
Improvement Suggestion
- Only move initializers that reference imported symbols. This rule currently encapsulates all
static
initializers into a function. This could be more accurately move only thosestatic
initializers that referenceimport
ed symbols. Keep in mind this may mean you'd have to analyze the execution path in case of initializers like this:
class A {
public static x = A.initializeX();
private static initializeX() {
return new X(); // this will fail because X isn't imported yet
}
}
import { X } from 'X';
new-instance-after-imports
This rule moves new
expressions to after the last import
statement to make sure all dependencies have been loaded.
class A {
...
}
new A(); // will be moved to after `import` statement
import { B } from 'B';
no-instanceof-operator
This rule changes any use of the instanceof
operator with a constructor.name
comparison, e.g.
Original code before this rule:
class A {}
let a = new A();
a instanceof A;
Original code after this rule:
class A {}
let a = new A();
a.constructor.name === 'A';
Related Projects
tslint-no-circular-imports - TSLint plugin to detect and warn about circular imports
dependency-cruiser – Validate and visualize dependencies. With your rules. JavaScript. TypeScript. CoffeeScript. ES6, CommonJS, AMD.
Metadata
- MIT
- Whatever
- Andreas Pizsa
- released 9/11/2017