зеркало из https://github.com/microsoft/dtslint.git
Add 'no-import-default-of-export-equals' rule
This commit is contained in:
Родитель
f0ef58f77e
Коммит
5cfeec5cfc
|
@ -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;
|
||||
}
|
||||
|
|
31
src/util.ts
31
src/util.ts
|
@ -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
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче