tslint 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-exportinitialize-statics-after-importsnew-instance-after-importsno-instanceof-operator
Usage
Install
npm install -D tslint-circular-dependenciesThis 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] --fixManually configuring tslint.json (optional)
This package will install itself into your tslint.json as follows:
"extends" : [
...
"tslint-circular-dependencies"
...
]Keep the rule names intact.
tslintdoes 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 imports 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
staticinitializers in astaticfunction - executes that
staticfunctionafter allimportstatements
Improvement Suggestion
- Only move initializers that reference imported symbols. This rule currently encapsulates all
staticinitializers into a function. This could be more accurately move only thosestaticinitializers that referenceimported 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.