Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
Implement 'no-default-import' rule
Browse files Browse the repository at this point in the history
  • Loading branch information
pablobirukov committed Jul 12, 2018
1 parent 29fbace commit ac8079f
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/rules/noDefaultImportRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* @license
* Copyright 2016 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { isImportDeclaration, isNamedImports, isStringLiteral } from "tsutils";
import * as ts from "typescript";
import * as Lint from "../index";

const fromModulesConfigOptionName = "fromModules";
interface Options {
[fromModulesConfigOptionName]: RegExp;
}

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "no-default-import",
description: "Disallows importing default members from certain ES6-style modules.",
descriptionDetails: "Import named members instead.",
rationale: Lint.Utils.dedent`
Named imports/exports [promote clarity](https://github.com/palantir/tslint/issues/1182#issue-151780453).
In addition, current tooling differs on the correct way to handle default imports/exports.
Avoiding them all together can help avoid tooling bugs and conflicts.
While "no-default-import" ???????`,
optionsDescription: "optionsDescription",
options: {
type: "object",
required: [fromModulesConfigOptionName],
properties: {
[fromModulesConfigOptionName]: { type: "string" },
},
},
optionExamples: [
[true, { [fromModulesConfigOptionName]: "^palantir-|_internal-*" }],
],
type: "maintainability",
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING = "Import of default members from this module is forbidden. Import named member instead";

public static getNamedDefaultImport(namedBindings: ts.NamedImports): ts.Identifier | null {
for (const importSpecifier of namedBindings.elements) {
if (importSpecifier.propertyName && importSpecifier.propertyName.text === "default") {
return importSpecifier.propertyName;
}
}
return null;
}

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk, this.getRuleOptions());
}
private getRuleOptions(): Options {
const ruleArguments: Partial<Options> = this.ruleArguments[0];
const fromModules = ruleArguments[fromModulesConfigOptionName];
return {
[fromModulesConfigOptionName]: new RegExp(fromModules || ""),
};
}
}

function walk(ctx: Lint.WalkContext<Options>) {
if (ctx.sourceFile.isDeclarationFile || !ts.isExternalModule(ctx.sourceFile)) {
return;
}
for (const statement of ctx.sourceFile.statements) {
if (isImportDeclaration(statement)) {
const { importClause, moduleSpecifier } = statement;
if (
importClause
&& isStringLiteral(moduleSpecifier)
&& ctx.options[fromModulesConfigOptionName].test(moduleSpecifier.text)
) {
// module name matches specified in rule config
if (importClause.name) {
// `import Foo...` syntax
const defaultImportedName = importClause.name;
ctx.addFailureAtNode(defaultImportedName, Rule.FAILURE_STRING);
} else if (importClause.namedBindings && isNamedImports(importClause.namedBindings)) {
// `import { default...` syntax
const defaultMember = Rule.getNamedDefaultImport(importClause.namedBindings);
if (defaultMember) {
ctx.addFailureAtNode(defaultMember, Rule.FAILURE_STRING);
}
}
}
}
}
}
21 changes: 21 additions & 0 deletions test/rules/no-default-import/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * from "tslint-utils"

import TslintUtils from "tslint-utils"
~~~~~~~~~~~ [0]

import Bar, { Foo } from "tslint-misc"
~~~ [0]

import Bar, * as Foo from "tslint-misc"
~~~ [0]

import { default as Foo } from "tslint-misc"
~~~~~~~ [0]

import { default as foo, bar } from "tslint-misc"
~~~~~~~ [0]

import { bar, default as foo } from "tslint-misc"
~~~~~~~ [0]

[0]: Import of default members from this module is forbidden. Import named member instead
7 changes: 7 additions & 0 deletions test/rules/no-default-import/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"rules": {
"no-default-import": [true, {
"fromModules": "^tslint-"
}]
}
}

0 comments on commit ac8079f

Please sign in to comment.