diff --git a/README.md b/README.md index b4f96ef..ca24d19 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ Now you can add any rule you want from the package like so: "no-async-without-await": true, "no-untyped-public-signature": true, "no-wallaby-file-only": true, - "no-full-package-import": true + "no-full-package-import": true, + "no-explicit-import-from-node-module": true } } ``` @@ -34,3 +35,4 @@ Rule | Description `no-untyped-public-signature` | Does not allow any untyped paramers nor return type on a public method. By default, `any` is forbidden as well, but can be allowed using: `"no-untyped-public-signature": [true, "allow-any"]` `no-wallaby-file-only` | Makes sure no `//file.only` comment (Wallby.js annotation) is left by mistake. `no-full-package-import` | Does not allow to import an entire package, only the required functionality. For example, use `import * as compact from 'lodash/compact'` instead of `import * as _ from 'lodash'` and `_.compact`. This rule is generic, and gets options like so: `"no-full-package-import": [true, "lodash"]`. +`no-explicit-import-from-node-module` | Does not allow explicitly import from `node_modules`. For example, `import * as foo from '../../node_modules/@wix/some-lib...'` diff --git a/src/noExplicitImportFromNodeModuleRule.spec.ts b/src/noExplicitImportFromNodeModuleRule.spec.ts new file mode 100644 index 0000000..d632bac --- /dev/null +++ b/src/noExplicitImportFromNodeModuleRule.spec.ts @@ -0,0 +1,30 @@ +import {helper} from './lintRunner'; +const getRule = (options: string[] = ['a-package']) => ({name: 'no-explicit-import-from-node-module', options: options}); + +describe('noExplicitImportFromNodeModule Rule', () => { + it(`should fail when node_module in import path`, () => { + const src = ` + import * as _ from '../node_modules/a-package'; + `; + const result = helper({src, rule: getRule()}); + expect(result.errorCount).toBe(1); + expect(result.failures[0].getFailure()).toBe(`'node_modules' shouldn't be in import path`); + }); + + it('should not fail on other imports', () => { + const src = ` + import * from 'a-package'; + `; + const result = helper({src, rule: getRule()}); + expect(result.errorCount).toBe(0); + }); + + it('should fail when node_module is last path', () => { + const src = ` + import * from './node_modules'; + `; + const result = helper({src, rule: getRule()}); + expect(result.errorCount).toBe(1); + expect(result.failures[0].getFailure()).toBe(`'node_modules' shouldn't be in import path`); + }); +}); diff --git a/src/noExplicitImportFromNodeModuleRule.ts b/src/noExplicitImportFromNodeModuleRule.ts new file mode 100644 index 0000000..6b6106b --- /dev/null +++ b/src/noExplicitImportFromNodeModuleRule.ts @@ -0,0 +1,19 @@ +import * as Lint from 'tslint'; +import * as ts from 'typescript'; + +export class Rule extends Lint.Rules.AbstractRule { + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new Walk(sourceFile, this.getOptions())); + } +} + +class Walk extends Lint.RuleWalker { + visitImportDeclaration(node: ts.ImportDeclaration) { + const cleanPath = node.moduleSpecifier.getText().slice(1, -1); + const importTextParts = cleanPath.split('/'); + + if(importTextParts.includes('node_modules')) { + this.addFailureAtNode(node, `'node_modules' shouldn't be in import path`); + } + } +}