From d4d1185ab1de4eed571cb888bc8759cb4bb05905 Mon Sep 17 00:00:00 2001 From: Edward Drapkin Date: Tue, 19 Feb 2019 13:55:17 -0500 Subject: [PATCH] Add option for "allow-namespace-imports" to noDuplicateImports rule --- src/rules/noDuplicateImportsRule.ts | 45 ++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/rules/noDuplicateImportsRule.ts b/src/rules/noDuplicateImportsRule.ts index f662ae35de7..a999b570c85 100644 --- a/src/rules/noDuplicateImportsRule.ts +++ b/src/rules/noDuplicateImportsRule.ts @@ -15,11 +15,13 @@ * limitations under the License. */ -import { isImportDeclaration, isLiteralExpression, isModuleDeclaration } from "tsutils"; +import { isImportDeclaration, isLiteralExpression, isModuleDeclaration, isNamespaceImport, } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; +const OPTION_ALLOW_SEPARATE_NAMESPACE_IMPORTS = "allow-namespace-imports"; + export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { @@ -29,9 +31,13 @@ export class Rule extends Lint.Rules.AbstractRule { rationale: Lint.Utils.dedent` Using a single import statement per module will make the code clearer because you can see everything being imported from that module on one line.`, - optionsDescription: "Not configurable", - options: null, - optionExamples: [true], + optionsDescription: Lint.Utils.dedent` + "${OPTION_ALLOW_SEPARATE_NAMESPACE_IMPORTS}" allows you to import namespaces on separate lines.`, + options: { + type: "string", + enum: [OPTION_ALLOW_SEPARATE_NAMESPACE_IMPORTS], + }, + optionExamples: [OPTION_ALLOW_SEPARATE_NAMESPACE_IMPORTS], type: "maintainability", typescriptOnly: false, }; @@ -41,21 +47,43 @@ export class Rule extends Lint.Rules.AbstractRule { } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, (ctx: Lint.WalkContext) => + walk(ctx, this.getAllowNamespaceImports()), + ); + } + + private getAllowNamespaceImports() { + return !!( + this.getOptions().ruleArguments.length > 0 && + this.getOptions().ruleArguments[0] === OPTION_ALLOW_SEPARATE_NAMESPACE_IMPORTS + ); } } -function walk(ctx: Lint.WalkContext): void { - walkWorker(ctx, ctx.sourceFile.statements, new Set()); +function walk(ctx: Lint.WalkContext, allowNamespaceImports: boolean): void { + walkWorker(ctx, ctx.sourceFile.statements, new Set(), allowNamespaceImports); +} + +function statementIsNamespaceImport(statement: ts.ImportDeclaration) { + return !!( + statement.importClause !== undefined && + statement.importClause.namedBindings !== undefined && + isNamespaceImport(statement.importClause.namedBindings) + ); } function walkWorker( ctx: Lint.WalkContext, statements: ReadonlyArray, seen: Set, + allowNamespaceImports: boolean, ): void { for (const statement of statements) { - if (isImportDeclaration(statement) && isLiteralExpression(statement.moduleSpecifier)) { + if ( + isImportDeclaration(statement) && + isLiteralExpression(statement.moduleSpecifier) && + (!statementIsNamespaceImport(statement) || !allowNamespaceImports) + ) { const { text } = statement.moduleSpecifier; if (seen.has(text)) { ctx.addFailureAtNode(statement, Rule.FAILURE_STRING(text)); @@ -75,6 +103,7 @@ function walkWorker( ctx, (statement.body as ts.ModuleBlock).statements, ts.isExternalModule(ctx.sourceFile) ? seen : new Set(), + allowNamespaceImports, ); } }