tslint-no-circular-imports
Advanced tools
Comparing version 0.5.0 to 0.5.1
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
} | ||
return function (d, b) { | ||
@@ -12,2 +15,6 @@ extendStatics(d, b); | ||
})(); | ||
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { | ||
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } | ||
return cooked; | ||
}; | ||
var __values = (this && this.__values) || function (o) { | ||
@@ -55,4 +62,7 @@ var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
imports.delete(resolvedFile); | ||
var walker = new NoCircularImportsWalker(sourceFile, this.getOptions(), program); | ||
return this.applyWithWalker(walker); | ||
var compilerOptions = program.getCompilerOptions(); | ||
return this.applyWithFunction(sourceFile, walk, { | ||
compilerOptions: compilerOptions, | ||
rootDir: compilerOptions.rootDir || process.cwd() | ||
}, program.getTypeChecker()); | ||
}; | ||
@@ -63,3 +73,3 @@ Rule.FAILURE_STRING = 'circular import detected'; | ||
description: 'Disallows circular imports.', | ||
rationale: (_a = ["\n Circular dependencies cause hard-to-catch runtime exceptions."], _a.raw = ["\n Circular dependencies cause hard-to-catch runtime exceptions."], Lint.Utils.dedent(_a)), | ||
rationale: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n Circular dependencies cause hard-to-catch runtime exceptions."], ["\n Circular dependencies cause hard-to-catch runtime exceptions."]))), | ||
optionsDescription: 'Not configurable.', | ||
@@ -79,60 +89,46 @@ options: null, | ||
var nodeModulesRe = new RegExp("\\" + path_1.sep + "node_modules\\" + path_1.sep); | ||
var NoCircularImportsWalker = /** @class */ (function (_super) { | ||
__extends(NoCircularImportsWalker, _super); | ||
function NoCircularImportsWalker(sourceFile, options, program) { | ||
var _this = _super.call(this, sourceFile, options) || this; | ||
_this.program = program; | ||
return _this; | ||
} | ||
NoCircularImportsWalker.prototype.visitSourceFile = function (sourceFile) { | ||
var _this = this; | ||
// Instead of visiting all children, this is faster. We know imports are statements anyway. | ||
sourceFile.statements.forEach(function (statement) { | ||
// export declarations seem to be missing from the current SyntaxWalker | ||
if (ts.isExportDeclaration(statement)) { | ||
_this.visitImportOrExportDeclaration(statement); | ||
} | ||
else if (ts.isImportDeclaration(statement)) { | ||
_this.visitImportOrExportDeclaration(statement); | ||
} | ||
}); | ||
var fileName = sourceFile.fileName; | ||
// Check for cycles, remove any cycles that have been found already (otherwise we'll report | ||
// false positive on every files that import from the real cycles, and users will be driven | ||
// mad). | ||
// The checkCycle is many order of magnitude faster than getCycle, but does not keep a history | ||
// of the cycle itself. Only get the full cycle if we found one. | ||
if (this.checkCycle(fileName)) { | ||
var allCycles = this.getAllCycles(fileName); | ||
var _loop_1 = function (maybeCycle) { | ||
function walk(context) { | ||
var e_1, _a; | ||
// Instead of visiting all children, this is faster. We know imports are statements anyway. | ||
context.sourceFile.statements.forEach(function (statement) { | ||
// export declarations seem to be missing from the current SyntaxWalker | ||
if (ts.isExportDeclaration(statement)) { | ||
visitImportOrExportDeclaration(statement); | ||
} | ||
else if (ts.isImportDeclaration(statement)) { | ||
visitImportOrExportDeclaration(statement); | ||
} | ||
}); | ||
var fileName = context.sourceFile.fileName; | ||
// Check for cycles, remove any cycles that have been found already (otherwise we'll report | ||
// false positive on every files that import from the real cycles, and users will be driven | ||
// mad). | ||
// The checkCycle is many order of magnitude faster than getCycle, but does not keep a history | ||
// of the cycle itself. Only get the full cycle if we found one. | ||
if (checkCycle(fileName)) { | ||
var allCycles = getAllCycles(fileName); | ||
try { | ||
for (var allCycles_1 = __values(allCycles), allCycles_1_1 = allCycles_1.next(); !allCycles_1_1.done; allCycles_1_1 = allCycles_1.next()) { | ||
var maybeCycle = allCycles_1_1.value; | ||
// Slice the array so we don't match this file twice. | ||
if (maybeCycle.slice(1, -1).some(function (fileName) { return found.has(fileName); })) { | ||
return "continue"; | ||
continue; | ||
} | ||
maybeCycle.forEach(function (x) { return found.add(x); }); | ||
var node = imports.get(fileName).get(maybeCycle[1]); | ||
var compilerOptions = this_1.program.getCompilerOptions(); | ||
this_1.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING + ": " + maybeCycle | ||
context.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING + ': ' + maybeCycle | ||
.concat(fileName) | ||
.map(function (x) { return path_1.relative(compilerOptions.rootDir || process.cwd(), x); }) | ||
.map(function (x) { return path_1.relative(context.options.rootDir, x); }) | ||
.join(' -> ')); | ||
}; | ||
var this_1 = this; | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
for (var allCycles_1 = __values(allCycles), allCycles_1_1 = allCycles_1.next(); !allCycles_1_1.done; allCycles_1_1 = allCycles_1.next()) { | ||
var maybeCycle = allCycles_1_1.value; | ||
_loop_1(maybeCycle); | ||
} | ||
if (allCycles_1_1 && !allCycles_1_1.done && (_a = allCycles_1.return)) _a.call(allCycles_1); | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (allCycles_1_1 && !allCycles_1_1.done && (_a = allCycles_1.return)) _a.call(allCycles_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
var e_1, _a; | ||
}; | ||
NoCircularImportsWalker.prototype.visitImportOrExportDeclaration = function (node) { | ||
} | ||
function visitImportOrExportDeclaration(node) { | ||
if (!node.parent || !ts.isSourceFile(node.parent)) { | ||
@@ -149,4 +145,3 @@ return; | ||
var importFileName = node.moduleSpecifier.text; | ||
var compilerOptions = this.program.getCompilerOptions(); | ||
var resolved = ts.resolveModuleName(importFileName, fileName, compilerOptions, ts.sys); | ||
var resolved = ts.resolveModuleName(importFileName, fileName, context.options.compilerOptions, ts.sys); | ||
if (!resolved || !resolved.resolvedModule) { | ||
@@ -161,57 +156,56 @@ return; | ||
} | ||
this.addToGraph(fileName, resolvedImportFileName, node); | ||
}; | ||
NoCircularImportsWalker.prototype.addToGraph = function (thisFileName, importCanonicalName, node) { | ||
var i = imports.get(thisFileName); | ||
if (!i) { | ||
imports.set(thisFileName, i = new Map); | ||
addToGraph(fileName, resolvedImportFileName, node); | ||
} | ||
} | ||
function addToGraph(thisFileName, importCanonicalName, node) { | ||
var i = imports.get(thisFileName); | ||
if (!i) { | ||
imports.set(thisFileName, i = new Map); | ||
} | ||
i.set(importCanonicalName, node); | ||
} | ||
function checkCycle(moduleName) { | ||
var accumulator = new Set(); | ||
var moduleImport = imports.get(moduleName); | ||
if (!moduleImport) | ||
return false; | ||
var toCheck = Array.from(moduleImport.keys()); | ||
for (var i = 0; i < toCheck.length; i++) { | ||
var current = toCheck[i]; | ||
if (current === moduleName) { | ||
return true; | ||
} | ||
i.set(importCanonicalName, node); | ||
}; | ||
NoCircularImportsWalker.prototype.checkCycle = function (moduleName) { | ||
var accumulator = new Set(); | ||
var moduleImport = imports.get(moduleName); | ||
if (!moduleImport) | ||
return false; | ||
var toCheck = Array.from(moduleImport.keys()); | ||
for (var i = 0; i < toCheck.length; i++) { | ||
var current = toCheck[i]; | ||
if (current == moduleName) { | ||
return true; | ||
} | ||
accumulator.add(current); | ||
toCheck.push.apply(toCheck, __spread(Array.from((imports.get(current) || new Map).keys()) | ||
.filter(function (i) { return !accumulator.has(i); }))); | ||
accumulator.add(current); | ||
toCheck.push.apply(toCheck, __spread(Array.from((imports.get(current) || new Map).keys()) | ||
.filter(function (i) { return !accumulator.has(i); }))); | ||
} | ||
return false; | ||
} | ||
function getAllCycles(moduleName, accumulator) { | ||
if (accumulator === void 0) { accumulator = []; } | ||
var e_2, _a; | ||
var moduleImport = imports.get(moduleName); | ||
if (!moduleImport) | ||
return []; | ||
if (accumulator.indexOf(moduleName) !== -1) | ||
return [accumulator]; | ||
var all = []; | ||
try { | ||
for (var _b = __values(Array.from(moduleImport.keys())), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var imp = _c.value; | ||
var c = getAllCycles(imp, accumulator.concat(moduleName)); | ||
if (c.length) | ||
all.push.apply(all, __spread(c)); | ||
} | ||
return false; | ||
}; | ||
NoCircularImportsWalker.prototype.getAllCycles = function (moduleName, accumulator) { | ||
if (accumulator === void 0) { accumulator = []; } | ||
var moduleImport = imports.get(moduleName); | ||
if (!moduleImport) | ||
return []; | ||
if (accumulator.indexOf(moduleName) !== -1) | ||
return [accumulator]; | ||
var all = []; | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
try { | ||
for (var _a = __values(Array.from(moduleImport.keys())), _b = _a.next(); !_b.done; _b = _a.next()) { | ||
var imp = _b.value; | ||
var c = this.getAllCycles(imp, accumulator.concat(moduleName)); | ||
if (c.length) | ||
all.push.apply(all, __spread(c)); | ||
} | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
try { | ||
if (_b && !_b.done && (_c = _a.return)) _c.call(_a); | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
return all; | ||
var e_2, _c; | ||
}; | ||
return NoCircularImportsWalker; | ||
}(Lint.RuleWalker)); | ||
var _a; | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
return all; | ||
} | ||
var templateObject_1; | ||
//# sourceMappingURL=noCircularImportsRule.js.map |
@@ -1,6 +0,13 @@ | ||
import { relative, sep } from 'path'; | ||
import * as Lint from 'tslint'; | ||
import { IOptions } from 'tslint/lib/language/rule/rule'; | ||
import * as ts from 'typescript'; | ||
import { relative, sep } from 'path' | ||
import * as Lint from 'tslint' | ||
import * as ts from 'typescript' | ||
interface Options { | ||
/** @internal */ | ||
compilerOptions: ts.CompilerOptions | ||
/** @internal */ | ||
rootDir: string | ||
} | ||
export class Rule extends Lint.Rules.TypedRule { | ||
@@ -25,5 +32,12 @@ static FAILURE_STRING = 'circular import detected' | ||
const walker = new NoCircularImportsWalker(sourceFile, this.getOptions(), program) | ||
const compilerOptions = program.getCompilerOptions() | ||
return this.applyWithWalker(walker) | ||
return this.applyWithFunction( | ||
sourceFile, | ||
walk, | ||
{ | ||
compilerOptions, | ||
rootDir: compilerOptions.rootDir || process.cwd() | ||
}, | ||
program.getTypeChecker()) | ||
} | ||
@@ -38,51 +52,43 @@ } | ||
class NoCircularImportsWalker extends Lint.RuleWalker { | ||
constructor(sourceFile: ts.SourceFile, options: IOptions, private program: ts.Program) { | ||
super(sourceFile, options) | ||
} | ||
function walk(context: Lint.WalkContext<Options>) { | ||
// Instead of visiting all children, this is faster. We know imports are statements anyway. | ||
context.sourceFile.statements.forEach(statement => { | ||
// export declarations seem to be missing from the current SyntaxWalker | ||
if (ts.isExportDeclaration(statement)) { | ||
visitImportOrExportDeclaration(statement) | ||
} else if (ts.isImportDeclaration(statement)) { | ||
visitImportOrExportDeclaration(statement) | ||
} | ||
}) | ||
visitSourceFile(sourceFile: ts.SourceFile) { | ||
// Instead of visiting all children, this is faster. We know imports are statements anyway. | ||
sourceFile.statements.forEach(statement => { | ||
// export declarations seem to be missing from the current SyntaxWalker | ||
if (ts.isExportDeclaration(statement)) { | ||
this.visitImportOrExportDeclaration(statement) | ||
} | ||
else if (ts.isImportDeclaration(statement)) { | ||
this.visitImportOrExportDeclaration(statement) | ||
} | ||
}) | ||
const fileName = context.sourceFile.fileName | ||
const fileName = sourceFile.fileName | ||
// Check for cycles, remove any cycles that have been found already (otherwise we'll report | ||
// false positive on every files that import from the real cycles, and users will be driven | ||
// mad). | ||
// The checkCycle is many order of magnitude faster than getCycle, but does not keep a history | ||
// of the cycle itself. Only get the full cycle if we found one. | ||
if (checkCycle(fileName)) { | ||
const allCycles = getAllCycles(fileName) | ||
// Check for cycles, remove any cycles that have been found already (otherwise we'll report | ||
// false positive on every files that import from the real cycles, and users will be driven | ||
// mad). | ||
// The checkCycle is many order of magnitude faster than getCycle, but does not keep a history | ||
// of the cycle itself. Only get the full cycle if we found one. | ||
if (this.checkCycle(fileName)) { | ||
const allCycles = this.getAllCycles(fileName) | ||
for (const maybeCycle of allCycles) { | ||
// Slice the array so we don't match this file twice. | ||
if (maybeCycle.slice(1, -1).some(fileName => found.has(fileName))) { | ||
continue | ||
} | ||
maybeCycle.forEach(x => found.add(x)) | ||
const node = imports.get(fileName) !.get(maybeCycle[1]) ! | ||
for (const maybeCycle of allCycles) { | ||
// Slice the array so we don't match this file twice. | ||
if (maybeCycle.slice(1, -1).some(fileName => found.has(fileName))) { | ||
continue | ||
} | ||
maybeCycle.forEach(x => found.add(x)) | ||
const node = imports.get(fileName) !.get(maybeCycle[1]) ! | ||
const compilerOptions = this.program.getCompilerOptions() | ||
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING + ": " + maybeCycle | ||
.concat(fileName) | ||
.map(x => relative(compilerOptions.rootDir || process.cwd(), x)) | ||
.join(' -> ')) | ||
} | ||
context.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING + ': ' + maybeCycle | ||
.concat(fileName) | ||
.map(x => relative(context.options.rootDir, x)) | ||
.join(' -> ')) | ||
} | ||
} | ||
visitImportOrExportDeclaration(node: ts.ImportDeclaration | ts.ExportDeclaration) { | ||
function visitImportOrExportDeclaration(node: ts.ImportDeclaration | ts.ExportDeclaration) { | ||
if (!node.parent || !ts.isSourceFile(node.parent)) { | ||
return | ||
} | ||
if(!node.moduleSpecifier) { | ||
if (!node.moduleSpecifier) { | ||
return | ||
@@ -96,5 +102,4 @@ } | ||
const importFileName = node.moduleSpecifier.text | ||
const compilerOptions = this.program.getCompilerOptions() | ||
const resolved = ts.resolveModuleName(importFileName, fileName, compilerOptions, ts.sys) | ||
const resolved = ts.resolveModuleName(importFileName, fileName, context.options.compilerOptions, ts.sys) | ||
if (!resolved || !resolved.resolvedModule) { | ||
@@ -111,53 +116,53 @@ return | ||
this.addToGraph(fileName, resolvedImportFileName, node) | ||
addToGraph(fileName, resolvedImportFileName, node) | ||
} | ||
} | ||
private addToGraph(thisFileName: string, importCanonicalName: string, node: ts.Node) { | ||
let i = imports.get(thisFileName) | ||
if (!i) { | ||
imports.set(thisFileName, i = new Map) | ||
} | ||
i.set(importCanonicalName, node) | ||
function addToGraph(thisFileName: string, importCanonicalName: string, node: ts.Node) { | ||
let i = imports.get(thisFileName) | ||
if (!i) { | ||
imports.set(thisFileName, i = new Map) | ||
} | ||
i.set(importCanonicalName, node) | ||
} | ||
private checkCycle(moduleName: string): boolean { | ||
const accumulator = new Set<string>() | ||
function checkCycle(moduleName: string): boolean { | ||
const accumulator = new Set<string>() | ||
const moduleImport = imports.get(moduleName) | ||
if (!moduleImport) | ||
return false | ||
const moduleImport = imports.get(moduleName) | ||
if (!moduleImport) | ||
return false | ||
const toCheck = Array.from(moduleImport.keys()) | ||
for (let i = 0; i < toCheck.length; i++) { | ||
const current = toCheck[i] | ||
if (current == moduleName) { | ||
return true | ||
} | ||
accumulator.add(current) | ||
toCheck.push( | ||
...Array.from((imports.get(current) || new Map).keys()) | ||
.filter(i => !accumulator.has(i)) | ||
) | ||
const toCheck = Array.from(moduleImport.keys()) | ||
for (let i = 0; i < toCheck.length; i++) { | ||
const current = toCheck[i] | ||
if (current === moduleName) { | ||
return true | ||
} | ||
accumulator.add(current) | ||
return false | ||
toCheck.push( | ||
...Array.from((imports.get(current) || new Map).keys()) | ||
.filter(i => !accumulator.has(i)) | ||
) | ||
} | ||
private getAllCycles(moduleName: string, accumulator: string[] = []): string[][] { | ||
const moduleImport = imports.get(moduleName) | ||
if (!moduleImport) return [] | ||
if (accumulator.indexOf(moduleName) !== -1) | ||
return [accumulator] | ||
return false | ||
} | ||
const all: string[][] = [] | ||
for (const imp of Array.from(moduleImport.keys())) { | ||
const c = this.getAllCycles(imp, accumulator.concat(moduleName)) | ||
function getAllCycles(moduleName: string, accumulator: string[] = []): string[][] { | ||
const moduleImport = imports.get(moduleName) | ||
if (!moduleImport) return [] | ||
if (accumulator.indexOf(moduleName) !== -1) | ||
return [accumulator] | ||
if (c.length) | ||
all.push(...c) | ||
} | ||
const all: string[][] = [] | ||
for (const imp of Array.from(moduleImport.keys())) { | ||
const c = getAllCycles(imp, accumulator.concat(moduleName)) | ||
return all | ||
if (c.length) | ||
all.push(...c) | ||
} | ||
return all | ||
} |
{ | ||
"name": "tslint-no-circular-imports", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"description": "TSLint plugin to detect and warn about circular imports", | ||
@@ -28,6 +28,6 @@ "main": "tslint-no-circular-imports.json", | ||
"devDependencies": { | ||
"@types/node": "^4.2.23", | ||
"tslint": "^5.7.0", | ||
"typescript": "^2.5.2" | ||
"@types/node": "^10.7.1", | ||
"tslint": "^5.11.0", | ||
"typescript": "^3.0.1" | ||
} | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert = require("assert"); | ||
const child_process_1 = require("child_process"); | ||
const assert = require("assert"); | ||
console.log(__dirname); | ||
child_process_1.exec('../node_modules/.bin/tslint -c ./tslint.json -r ../ ./*.ts', { cwd: __dirname }, (error, stdout, stderr) => { | ||
assert.equal(stdout, ` | ||
ERROR: case1.ts[1, 1]: circular import detected: case1.ts -> case1.1.ts -> case1.ts | ||
ERROR: case1.ts[2, 1]: circular import detected: case1.ts -> case1.2.ts -> case1.ts | ||
`); | ||
const path_1 = require("path"); | ||
const tslintBin = path_1.join('..', 'node_modules', '.bin', 'tslint'); | ||
const tslintConfig = path_1.join('.', 'tslint.json'); | ||
const tslintFormat = 'json'; | ||
const tsFiles = `${path_1.join('.', '*.ts')} ${path_1.join('.', '*/*.ts')}`; | ||
const tsconfig = path_1.join('.', 'tsconfig.json'); | ||
child_process_1.exec(`${tslintBin} -p ${tsconfig} -c ${tslintConfig} -r .. -t ${tslintFormat} ${tsFiles}`, { cwd: __dirname }, (error, stdout, stderr) => { | ||
// Only validate failures and names. | ||
const actual = JSON.parse(stdout) | ||
.map(x => ({ failure: x.failure, name: x.name })); | ||
assert.deepEqual(actual, [ | ||
// case1 | ||
{ | ||
failure: 'circular import detected: case1.ts -> case1.2.ts -> case1.ts', | ||
name: path_1.join(__dirname, 'case1.ts') | ||
}, | ||
{ | ||
failure: 'circular import detected: case1.1.ts -> case1.ts -> case1.1.ts', | ||
name: path_1.join(__dirname, 'case1.1.ts') | ||
}, | ||
// case2 | ||
{ | ||
failure: 'circular import detected: case2/a.ts -> case2/b.ts -> case2/a.ts', | ||
name: path_1.join(__dirname, 'case2/a.ts') | ||
}, | ||
// case3 | ||
{ | ||
failure: 'circular import detected: case3/a.ts -> case3/b.ts -> case3/a.ts', | ||
name: path_1.join(__dirname, 'case3/a.ts') | ||
}, | ||
// case4 | ||
{ | ||
failure: 'circular import detected: case4/a.ts -> case4/index.ts -> case4/a.ts', | ||
name: path_1.join(__dirname, 'case4/a.ts') | ||
} | ||
]); | ||
}); | ||
//# sourceMappingURL=test.js.map |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
30802
34
590