nx/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts
Justin Schwartzenberger 3c55f34ca1 fix(schematics): make deep import check work for libs with same prefix
Adds logic to sort app names from longest to shortest so deep import
check of startsWith will work when more than one lib has the same
starting string. Also adds spec to confirm the case.
2017-12-20 10:13:06 -05:00

100 lines
3.2 KiB
TypeScript

import * as path from 'path';
import * as Lint from 'tslint';
import { IOptions } from 'tslint';
import * as ts from 'typescript';
import { readFileSync } from 'fs';
export class Rule extends Lint.Rules.AbstractRule {
constructor(options: IOptions, private path?: string, private npmScope?: string, private appNames?: string[]) {
super(options);
if (!path) {
const cliConfig = this.readCliConfig();
this.path = process.cwd();
this.npmScope = cliConfig.project.npmScope;
this.appNames = cliConfig.apps.map(a => a.name);
}
}
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(
new EnforceModuleBoundariesWalker(sourceFile, this.getOptions(), this.path, this.npmScope, this.appNames)
);
}
private readCliConfig(): any {
return JSON.parse(readFileSync(`.angular-cli.json`, 'UTF-8'));
}
}
class EnforceModuleBoundariesWalker extends Lint.RuleWalker {
constructor(
sourceFile: ts.SourceFile,
options: IOptions,
private projectPath: string,
private npmScope: string,
private appNames: string[]
) {
super(sourceFile, options);
}
public visitImportDeclaration(node: ts.ImportDeclaration) {
const imp = node.moduleSpecifier.getText().substring(1, node.moduleSpecifier.getText().length - 1);
const allow: string[] = Array.isArray(this.getOptions()[0].allow)
? this.getOptions()[0].allow.map(a => `${a}`)
: [];
const lazyLoad: string[] = Array.isArray(this.getOptions()[0].lazyLoad)
? this.getOptions()[0].lazyLoad.map(a => `${a}`)
: [];
// whitelisted import => return
if (allow.indexOf(imp) > -1) {
super.visitImportDeclaration(node);
return;
}
const lazyLoaded = lazyLoad.filter(a => imp.startsWith(`@${this.npmScope}/${a}`))[0];
if (lazyLoaded) {
this.addFailureAt(node.getStart(), node.getWidth(), 'import of lazy-loaded libraries are forbidden');
return;
}
if (this.isRelative(imp) && this.isRelativeImportIntoAnotherProject(imp)) {
this.addFailureAt(node.getStart(), node.getWidth(), 'relative imports of libraries are forbidden');
return;
}
const deepImport = this.appNames.filter(a => imp.startsWith(`@${this.npmScope}/${a}/`))[0];
if (deepImport) {
this.addFailureAt(node.getStart(), node.getWidth(), 'deep imports into libraries are forbidden');
return;
}
super.visitImportDeclaration(node);
}
private isRelativeImportIntoAnotherProject(imp: string): boolean {
const sourceFile = this.getSourceFile().fileName.substring(this.projectPath.length);
const targetFile = path.resolve(path.dirname(sourceFile), imp);
if (this.workspacePath(sourceFile) && this.workspacePath(targetFile)) {
if (this.parseProject(sourceFile) !== this.parseProject(targetFile)) {
return true;
}
}
return false;
}
private workspacePath(s: string): boolean {
return s.startsWith('/apps/') || s.startsWith('/libs/');
}
private parseProject(s: string): string {
const rest = s.substring(6);
const r = rest.split(path.sep);
return r[0];
}
private isRelative(s: string): boolean {
return s.startsWith('.');
}
}