From d4e29e53d75ad3597adec3aac94874c7a29533c5 Mon Sep 17 00:00:00 2001 From: HamletDRC Date: Tue, 10 May 2016 08:16:16 +0200 Subject: [PATCH] [Issue #125] new rule: import name must match export name closes #125 --- README.md | 1 + src/importNameRule.ts | 55 +++++++++++++++++++ tests/ImportNameRuleTests.ts | 103 +++++++++++++++++++++++++++++++++++ tslint.json | 1 + 4 files changed, 160 insertions(+) create mode 100644 src/importNameRule.ts create mode 100644 tests/ImportNameRuleTests.ts diff --git a/README.md b/README.md index 9dc365bc4..0c126281c 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Rule Name | Description | Since :---------- | :------------ | ------------- `chai-vague-errors` | Avoid Chai assertions that result in vague errors. For example, asserting `expect(something).to.be.true` will result in the failure message "Expected true received false". This is a vague error message that does not reveal the underlying problem. It is especially vague in TypeScript because stack trace line numbers often do not match the source code. A better pattern to follow is the xUnit Patterns [Assertion Message](http://xunitpatterns.com/Assertion%20Message.html) pattern. The previous code sample could be better written as `expect(something).to.equal(true, 'expected something to have occurred');`| 1.0 `export-name` | The name of the exported module must match the filename of the source file. This is case-sensitive but ignores file extension. Since version 1.0, this rule takes a list of regular expressions as a parameter. Any export name matching that regular expression will be ignored. For example, to allow an exported name like myChartOptions, then configure the rule like this: "export-name": \[true, "myChartOptionsg"\]| 0.0.3 +`import-name` | The name of the imported module must match the name of the thing being imported. For example, it is valid to name imported modules the same as the module name: `import Service = require('x/y/z/Service')` and `import Service from 'x/y/z/Service'`. But it is invalid to change the name being imported, such as: `import MyCoolService = require('x/y/z/Service')` and `import MyCoolService from 'x/y/z/Service'`. | 2.0.5 `jquery-deferred-must-complete` | When a JQuery Deferred instance is created, then either reject() or resolve() must be called on it within all code branches in the scope. For more examples see the [feature request](https://github.com/Microsoft/tslint-microsoft-contrib/issues/26). | 1.0 `max-func-body-length` | Avoid long functions. The line count of a function body must not exceed the value configured within this rule's options.
You can setup a general max function body length applied for every function/method/arrow function e.g. \[true, 30\] or set different maximum length for every type e.g. \[true, \{ "func-body-length": 10 , "arrow-body-length": 5, "method-body-length": 15, "ctor-body-length": 5 \}\]. To specify a function name whose parameters you can ignore for this rule, pass a regular expression as a string(this can be useful for Mocha users to ignore the describe() function) | 2.0.3 `missing-jsdoc` | All files must have a top level [JSDoc](http://usejsdoc.org/) comment. A JSDoc comment starts with /** (not one more or one less asterisk) and a JSDoc at the 'top-level' appears without leading spaces. Trailing spaces are acceptable but not recommended. | 1.0 diff --git a/src/importNameRule.ts b/src/importNameRule.ts new file mode 100644 index 000000000..3a9e7b37b --- /dev/null +++ b/src/importNameRule.ts @@ -0,0 +1,55 @@ +import * as ts from 'typescript'; +import * as Lint from 'tslint/lib/lint'; + +import ErrorTolerantWalker = require('./utils/ErrorTolerantWalker'); +import SyntaxKind = require('./utils/SyntaxKind'); + +/** + * Implementation of the import-name rule. + */ +export class Rule extends Lint.Rules.AbstractRule { + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new ImportNameRuleWalker(sourceFile, this.getOptions())); + } +} + +class ImportNameRuleWalker extends ErrorTolerantWalker { + + protected visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration): void { + let name: string = node.name.text; + + if (node.moduleReference.kind === SyntaxKind.current().ExternalModuleReference) { + let moduleRef: ts.ExternalModuleReference = node.moduleReference; + if (moduleRef.expression.kind === SyntaxKind.current().StringLiteral) { + let moduleName: string = (moduleRef.expression).text; + this.validateImport(node, name, moduleName); + } + } else if (node.moduleReference.kind === SyntaxKind.current().QualifiedName) { + let moduleName = node.moduleReference.getText(); + moduleName = moduleName.replace(/.*\./, ''); // chop off the qualified parts + this.validateImport(node, name, moduleName); + } + super.visitImportEqualsDeclaration(node); + } + + + protected visitImportDeclaration(node: ts.ImportDeclaration): void { + if (node.importClause.name != null) { + let name: string = node.importClause.name.text; + if (node.moduleSpecifier.kind === SyntaxKind.current().StringLiteral) { + let moduleName: string = (node.moduleSpecifier).text; + this.validateImport(node, name, moduleName); + } + } + super.visitImportDeclaration(node); + } + + private validateImport(node: ts.Node, importedName: string, moduleName: string): void { + moduleName = moduleName.replace(/.*\//, ''); // chop off the path + if (moduleName !== importedName) { + let message: string = `Misnamed import. Import should be named '${moduleName}' but found '${importedName}'`; + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), message)); + } + } + +} diff --git a/tests/ImportNameRuleTests.ts b/tests/ImportNameRuleTests.ts new file mode 100644 index 000000000..2cf0b4c6e --- /dev/null +++ b/tests/ImportNameRuleTests.ts @@ -0,0 +1,103 @@ +/// +/// + +/* tslint:disable:quotemark */ +/* tslint:disable:no-multiline-string */ + +import TestHelper = require('./TestHelper'); + +/** + * Unit tests. + */ +describe('importNameRule', () : void => { + + var ruleName : string = 'import-name'; + + it('should pass on matching names of external module', () : void => { + var script : string = ` + import App = require('App'); + import App = require('x/y/z/App'); + `; + + TestHelper.assertViolations(ruleName, script, [ ]); + }); + + it('should pass on matching names of ES6 import', () : void => { + var script : string = ` + import App from 'App'; + import App from 'x/y/z/App'; + `; + + TestHelper.assertViolations(ruleName, script, [ ]); + }); + + it('should pass on matching names of simple import', () : void => { + var script : string = ` + import DependencyManager = DM.DependencyManager; + `; + + TestHelper.assertViolations(ruleName, script, [ ]); + }); + + it('should fail on misnamed external module', () : void => { + var script : string = ` + import MyCoolApp = require('App'); + import MyCoolApp2 = require('x/y/z/App'); + `; + + TestHelper.assertViolations(ruleName, script, [ + { + "failure": "Misnamed import. Import should be named 'App' but found 'MyCoolApp'", + "name": "file.ts", + "ruleName": "import-name", + "startPosition": { "character": 13, "line": 2 } + }, + { + "failure": "Misnamed import. Import should be named 'App' but found 'MyCoolApp2'", + "name": "file.ts", + "ruleName": "import-name", + "startPosition": { "character": 13, "line": 3 } + } + ]); + }); + + it('should fail on misnamed import', () : void => { + var script : string = ` + import MyCoolApp from 'App'; + import MyCoolApp2 from 'x/y/z/App'; + `; + + TestHelper.assertViolations(ruleName, script, [ + { + "failure": "Misnamed import. Import should be named 'App' but found 'MyCoolApp'", + "name": "file.ts", + "ruleName": "import-name", + "startPosition": { "character": 13, "line": 2 } + }, + { + "failure": "Misnamed import. Import should be named 'App' but found 'MyCoolApp2'", + "name": "file.ts", + "ruleName": "import-name", + "startPosition": { "character": 13, "line": 3 } + } + ]); + }); + + it('should fail on misnamed rename', () : void => { + var script : string = ` + import Service = DM.DependencyManager; + `; + + TestHelper.assertViolations(ruleName, script, [ + { + "failure": "Misnamed import. Import should be named 'DependencyManager' but found 'Service'", + "name": "file.ts", + "ruleName": "import-name", + "startPosition": { "character": 13, "line": 2 } + } + ]); + }); + +}); +/* tslint:enable:quotemark */ +/* tslint:enable:no-multiline-string */ diff --git a/tslint.json b/tslint.json index b5e9da9fd..e7fb630fa 100644 --- a/tslint.json +++ b/tslint.json @@ -2,6 +2,7 @@ "rules": { "chai-vague-errors": true, "export-name": true, + "import-name": true, "use-isnan": true, "jquery-deferred-must-complete": true, "missing-jsdoc": true,