Add 'no-import-default-of-export-equals' rule

This commit is contained in:
Andy Hanson 2018-02-14 14:31:00 -08:00
Родитель f0ef58f77e
Коммит 5cfeec5cfc
9 изменённых файлов: 128 добавлений и 38 удалений

Просмотреть файл

@ -0,0 +1,22 @@
# no-import-default-of-export-equals
Don't use a default import of a package that uses `export =`.
Users who do not have `--allowSyntheticDefaultExports` or `--esModuleInterop` will get different behavior.
This rule only applies to definition files -- for test files you can use a default import if you prefer.
**Bad**:
```ts
// foo/index.d.ts
declare interface I {}
export = I;
// bar/index.d.ts
import I from "foo";
```
**Good**:
```ts
import I = require("foo");
```

Просмотреть файл

@ -8,6 +8,7 @@
"no-bad-reference": true,
"no-const-enum": true,
"no-dead-reference": true,
"no-import-default-of-export-equals": true,
"no-padding": true,
"no-redundant-undefined": true,
"no-relative-import-in-test": true,

Просмотреть файл

@ -44,7 +44,6 @@ export async function checkTsconfig(dirPath: string, dt: boolean): Promise<void>
module: "commonjs",
noEmit: true,
forceConsistentCasingInFileNames: true,
esModuleInterop: true,
baseUrl,
typeRoots: [baseUrl],
types: [],
@ -65,6 +64,9 @@ export async function checkTsconfig(dirPath: string, dt: boolean): Promise<void>
case "noImplicitThis":
case "strictNullChecks":
case "strictFunctionTypes":
case "esModuleInterop":
case "allowSyntheticDefaultImports":
// Allow any value
break;
case "target":
case "paths":

Просмотреть файл

@ -0,0 +1,51 @@
import * as Lint from "tslint";
import * as ts from "typescript";
import { eachModuleStatement, failure, getModuleDeclarationStatements } from "../util";
export class Rule extends Lint.Rules.TypedRule {
static metadata: Lint.IRuleMetadata = {
ruleName: "no-import-default-of-export-equals",
description: "Forbid a default import to reference an `export =` module.",
optionsDescription: "Not configurable.",
options: null,
type: "functionality",
typescriptOnly: true,
};
static FAILURE_STRING(importName: string, moduleName: string): string {
return failure(
Rule.metadata.ruleName,
`The module ${moduleName} uses \`export = \`. Import with \`import ${importName} = require(${moduleName})\`.`);
}
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, ctx => walk(ctx, program.getTypeChecker()));
}
}
function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
eachModuleStatement(ctx.sourceFile, statement => {
console.log("!", statement.getText());
if (!ts.isImportDeclaration(statement)) {
return;
}
const defaultName = statement.importClause && statement.importClause.name;
if (!defaultName) {
return;
}
const sym = checker.getSymbolAtLocation(statement.moduleSpecifier);
if (sym && sym.declarations && sym.declarations.some(d => {
const statements = getStatements(d);
return statements !== undefined && statements.some(ts.isExportAssignment);
})) {
ctx.addFailureAtNode(defaultName, Rule.FAILURE_STRING(defaultName.text, statement.moduleSpecifier.getText()));
}
});
}
function getStatements(decl: ts.Declaration): ReadonlyArray<ts.Statement> | undefined {
return ts.isSourceFile(decl) ? decl.statements
: ts.isModuleDeclaration(decl) ? getModuleDeclarationStatements(decl)
: undefined;
}

Просмотреть файл

@ -1,7 +1,7 @@
import * as Lint from "tslint";
import * as ts from "typescript";
import { failure } from "../util";
import { eachModuleStatement, failure } from "../util";
export class Rule extends Lint.Rules.AbstractRule {
static metadata: Lint.IRuleMetadata = {
@ -24,7 +24,7 @@ export class Rule extends Lint.Rules.AbstractRule {
function walk(ctx: Lint.WalkContext<void>): void {
eachModuleStatement(ctx.sourceFile, statement => {
if (isVariableStatement(statement)) {
if (ts.isVariableStatement(statement)) {
for (const varDecl of statement.declarationList.declarations) {
if (varDecl.type !== undefined && varDecl.type.kind === ts.SyntaxKind.FunctionType) {
ctx.addFailureAtNode(varDecl, Rule.FAILURE_STRING);
@ -33,38 +33,3 @@ function walk(ctx: Lint.WalkContext<void>): void {
}
});
}
function isVariableStatement(node: ts.Node): node is ts.VariableStatement {
return node.kind === ts.SyntaxKind.VariableStatement;
}
function eachModuleStatement(sourceFile: ts.SourceFile, action: (statement: ts.Statement) => void): void {
if (!sourceFile.isDeclarationFile) {
return;
}
for (const node of sourceFile.statements) {
if (isModuleDeclaration(node)) {
let { body } = node;
if (!body) {
return;
}
while (body.kind === ts.SyntaxKind.ModuleDeclaration) {
body = body.body;
}
if (body.kind === ts.SyntaxKind.ModuleBlock) {
for (const statement of body.statements) {
action(statement);
}
}
} else {
action(node);
}
}
}
function isModuleDeclaration(node: ts.Node): node is ts.ModuleDeclaration {
return node.kind === ts.SyntaxKind.ModuleDeclaration;
}

Просмотреть файл

@ -1,6 +1,7 @@
import { readFile } from "fs-promise";
import { basename, dirname } from "path";
import stripJsonComments = require("strip-json-comments");
import * as ts from "typescript";
export async function readJson(path: string) {
const text = await readFile(path, "utf-8");
@ -23,3 +24,33 @@ export function getCommonDirectoryName(files: ReadonlyArray<string>): string {
}
return basename(minDir);
}
export function eachModuleStatement(sourceFile: ts.SourceFile, action: (statement: ts.Statement) => void): void {
if (!sourceFile.isDeclarationFile) {
return;
}
for (const node of sourceFile.statements) {
if (ts.isModuleDeclaration(node)) {
const statements = getModuleDeclarationStatements(node);
if (statements) {
for (const statement of statements) {
action(statement);
}
}
} else {
action(node);
}
}
}
export function getModuleDeclarationStatements(node: ts.ModuleDeclaration): ReadonlyArray<ts.Statement> | undefined {
let { body } = node;
if (!body) {
return undefined;
}
while (body.kind === ts.SyntaxKind.ModuleDeclaration) {
body = body.body;
}
return ts.isModuleBlock(body) ? body.statements : undefined;
}

Просмотреть файл

@ -0,0 +1,11 @@
declare module "a" {
interface I {}
export = I;
}
declare module "b" {
import a from "a";
~ [0]
}
[0]: The module "a" uses `export = `. Import with `import a = require("a")`. See: https://github.com/Microsoft/dtslint/blob/master/docs/no-import-default-of-export-equals.md

Просмотреть файл

@ -0,0 +1 @@
{}

Просмотреть файл

@ -0,0 +1,6 @@
{
"rulesDirectory": ["../../bin/rules"],
"rules": {
"no-import-default-of-export-equals": true
}
}