@definitelytyped/dtslint 0.0.166 → 0.0.167-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lint.js +3 -1
- package/dist/lint.js.map +1 -1
- package/dist/rules/dt-header.d.ts +2 -0
- package/dist/rules/dt-header.js +62 -0
- package/dist/rules/dt-header.js.map +1 -0
- package/dist/rules/export-just-namespace.d.ts +2 -0
- package/dist/rules/export-just-namespace.js +70 -0
- package/dist/rules/export-just-namespace.js.map +1 -0
- package/dist/rules/no-any-union.d.ts +4 -0
- package/dist/rules/no-any-union.js +34 -0
- package/dist/rules/no-any-union.js.map +1 -0
- package/dist/rules/no-bad-reference.d.ts +2 -0
- package/dist/rules/no-bad-reference.js +55 -0
- package/dist/rules/no-bad-reference.js.map +1 -0
- package/dist/rules/no-const-enum.d.ts +4 -0
- package/dist/rules/no-const-enum.js +30 -0
- package/dist/rules/no-const-enum.js.map +1 -0
- package/dist/rules/no-dead-reference.d.ts +2 -0
- package/dist/rules/no-dead-reference.js +44 -0
- package/dist/rules/no-dead-reference.js.map +1 -0
- package/dist/rules/no-declare-current-package.d.ts +4 -0
- package/dist/rules/no-declare-current-package.js +43 -0
- package/dist/rules/no-declare-current-package.js.map +1 -0
- package/dist/rules/no-import-default-of-export-equals.d.ts +4 -0
- package/dist/rules/no-import-default-of-export-equals.js +87 -0
- package/dist/rules/no-import-default-of-export-equals.js.map +1 -0
- package/dist/rules/no-outside-dependencies.d.ts +2 -0
- package/dist/rules/no-outside-dependencies.js +41 -0
- package/dist/rules/no-outside-dependencies.js.map +1 -0
- package/dist/rules/no-self-import.d.ts +4 -0
- package/dist/rules/no-self-import.js +38 -0
- package/dist/rules/no-self-import.js.map +1 -0
- package/dist/rules/no-single-element-tuple-type.d.ts +5 -0
- package/dist/rules/no-single-element-tuple-type.js +30 -0
- package/dist/rules/no-single-element-tuple-type.js.map +1 -0
- package/dist/rules/no-unnecessary-generics.d.ts +8 -0
- package/dist/rules/no-unnecessary-generics.js +135 -0
- package/dist/rules/no-unnecessary-generics.js.map +1 -0
- package/dist/rules/no-useless-files.d.ts +2 -0
- package/dist/rules/no-useless-files.js +53 -0
- package/dist/rules/no-useless-files.js.map +1 -0
- package/dist/rules/prefer-declare-function.d.ts +5 -0
- package/dist/rules/prefer-declare-function.js +35 -0
- package/dist/rules/prefer-declare-function.js.map +1 -0
- package/dist/rules/redundant-undefined.d.ts +4 -0
- package/dist/rules/redundant-undefined.js +53 -0
- package/dist/rules/redundant-undefined.js.map +1 -0
- package/dist/rules/trim-file.d.ts +2 -0
- package/dist/rules/trim-file.js +43 -0
- package/dist/rules/trim-file.js.map +1 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.js +23 -1
- package/dist/util.js.map +1 -1
- package/docs/dt-header.md +88 -0
- package/docs/export-just-namespace.md +29 -0
- package/docs/no-any-union.md +27 -0
- package/docs/no-bad-reference.md +28 -0
- package/docs/no-const-enum.md +16 -0
- package/docs/no-dead-reference.md +17 -0
- package/docs/no-declare-current-package.md +35 -0
- package/docs/no-import-default-of-export-equals.md +22 -0
- package/docs/no-outside-dependencies.md +23 -0
- package/docs/no-self-import.md +27 -0
- package/docs/no-single-element-tuple-type.md +15 -0
- package/docs/no-unnecessary-generics.md +69 -0
- package/docs/no-useless-files.md +14 -0
- package/docs/prefer-declare-function.md +15 -0
- package/docs/redundant-undefined.md +15 -0
- package/docs/trim-file.md +17 -0
- package/package.json +2 -2
- package/src/lint.ts +3 -1
- package/src/rules/dt-header.ts +74 -0
- package/src/rules/export-just-namespace.ts +83 -0
- package/src/rules/no-any-union.ts +34 -0
- package/src/rules/no-bad-reference.ts +62 -0
- package/src/rules/no-const-enum.ts +30 -0
- package/src/rules/no-dead-reference.ts +46 -0
- package/src/rules/no-declare-current-package.ts +45 -0
- package/src/rules/no-import-default-of-export-equals.ts +68 -0
- package/src/rules/no-outside-dependencies.ts +42 -0
- package/src/rules/no-self-import.ts +40 -0
- package/src/rules/no-single-element-tuple-type.ts +31 -0
- package/src/rules/no-unnecessary-generics.ts +126 -0
- package/src/rules/no-useless-files.ts +58 -0
- package/src/rules/prefer-declare-function.ts +37 -0
- package/src/rules/redundant-undefined.ts +62 -0
- package/src/rules/trim-file.ts +45 -0
- package/src/util.ts +25 -0
- package/test/dt-header.test.ts +189 -0
- package/test/export-just-namespace.test.ts +70 -0
- package/test/no-any-union.test.ts +22 -0
- package/test/no-bad-reference.test.ts +66 -0
- package/test/no-const-enum.test.ts +22 -0
- package/test/no-dead-reference.test.ts +66 -0
- package/test/no-declare-current-package.test.ts +61 -0
- package/test/no-import-default-of-export-equals.test.ts +49 -0
- package/test/no-self-import.test.ts +47 -0
- package/test/no-single-element-tuple-type.test.ts +28 -0
- package/test/no-unnecessary-generics.test.ts +152 -0
- package/test/no-useless-files.test.ts +42 -0
- package/test/prefer-declare-function.test.ts +66 -0
- package/test/redundant-undefined.test.ts +39 -0
- package/test/trim-file.test.ts +46 -0
- package/test/tsconfig.json +10 -0
- package/test/tsconfig.no-declare-current-package.json +11 -0
- package/test/tsconfig.no-declare-current-package2.json +11 -0
- package/test/tsconfig.no-import-default-of-export-equals.json +11 -0
- package/test/tsconfig.no-self-import.json +7 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createRule } from "../util";
|
|
2
|
+
|
|
3
|
+
const rule = createRule({
|
|
4
|
+
name: "no-dead-reference",
|
|
5
|
+
defaultOptions: [],
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Ensures that all `/// <reference>` comments go at the top of the file.",
|
|
10
|
+
recommended: "error",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
referenceAtTop: "`/// <reference>` directive must be at top of file to take effect.",
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const source = context.getSourceCode();
|
|
19
|
+
if (source.ast.body.length) {
|
|
20
|
+
// 'm' flag makes it multiline, so `^` matches the beginning of any line.
|
|
21
|
+
// 'g' flag lets us set rgx.lastIndex
|
|
22
|
+
const rgx = /^\s*(\/\/\/ <reference)/gm;
|
|
23
|
+
|
|
24
|
+
// Start search at the first statement. (`/// <reference>` before that is OK.)
|
|
25
|
+
rgx.lastIndex = source.ast.body[0].range?.[0] ?? 0;
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line no-constant-condition
|
|
28
|
+
while (true) {
|
|
29
|
+
const match = rgx.exec(source.text);
|
|
30
|
+
if (match === null) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const length = match[1].length;
|
|
35
|
+
const start = match.index + match[0].length - length;
|
|
36
|
+
context.report({
|
|
37
|
+
messageId: "referenceAtTop",
|
|
38
|
+
loc: source.getLocFromIndex(start),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export = rule;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getCommonDirectoryName, createRule } from "../util";
|
|
2
|
+
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
3
|
+
const rule = createRule({
|
|
4
|
+
name: "no-declare-current-package",
|
|
5
|
+
defaultOptions: [],
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Don't use an ambient module declaration of the current package; use a normal module.",
|
|
10
|
+
recommended: "error",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
noDeclareCurrentPackage:
|
|
14
|
+
`Instead of declaring a module with \`declare module "{{ text }}"\`, ` +
|
|
15
|
+
`write its contents in directly in {{ preferred }}.`,
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
if (!context.getFilename().endsWith(".d.ts")) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
24
|
+
const packageName = getCommonDirectoryName(parserServices.program.getRootFileNames());
|
|
25
|
+
return {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
27
|
+
TSModuleDeclaration(node) {
|
|
28
|
+
if (
|
|
29
|
+
node.id.type === AST_NODE_TYPES.Literal &&
|
|
30
|
+
(node.id.value === packageName || node.id.value.startsWith(packageName + "/"))
|
|
31
|
+
) {
|
|
32
|
+
const text = node.id.value;
|
|
33
|
+
const preferred = text === packageName ? '"index.d.ts"' : `"${text}.d.ts" or "${text}/index.d.ts`;
|
|
34
|
+
context.report({
|
|
35
|
+
messageId: "noDeclareCurrentPackage",
|
|
36
|
+
data: { text, preferred },
|
|
37
|
+
node,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export = rule;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createRule } from "../util";
|
|
2
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
3
|
+
import * as ts from "typescript";
|
|
4
|
+
|
|
5
|
+
const rule = createRule({
|
|
6
|
+
name: "no-import-default-of-export-equals",
|
|
7
|
+
defaultOptions: [],
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Forbid a default import to reference an `export =` module.",
|
|
12
|
+
recommended: "error",
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
noImportDefaultOfExportEquals: `The module {{moduleName}} uses \`export = \`. Import with \`import {{importName}} = require({{moduleName}})\`.`,
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
21
|
+
const checker = parserServices.program.getTypeChecker();
|
|
22
|
+
if (context.getFilename().endsWith(".d.ts")) {
|
|
23
|
+
return {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
25
|
+
ImportDeclaration(node) {
|
|
26
|
+
const defaultName = node.specifiers.find((spec) => spec.type === "ImportDefaultSpecifier")?.local;
|
|
27
|
+
if (!defaultName) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const importName = defaultName.name;
|
|
31
|
+
const source = parserServices.esTreeNodeToTSNodeMap.get(node.source);
|
|
32
|
+
const sym = checker.getSymbolAtLocation(source);
|
|
33
|
+
if (
|
|
34
|
+
sym?.declarations?.some((d) =>
|
|
35
|
+
getStatements(d)?.some((s) => ts.isExportAssignment(s) && !!s.isExportEquals)
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
context.report({
|
|
39
|
+
messageId: "noImportDefaultOfExportEquals",
|
|
40
|
+
data: { moduleName: node.source.value, importName },
|
|
41
|
+
node: defaultName,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
} else {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function getStatements(decl: ts.Declaration): readonly ts.Statement[] | undefined {
|
|
53
|
+
return ts.isSourceFile(decl)
|
|
54
|
+
? decl.statements
|
|
55
|
+
: ts.isModuleDeclaration(decl)
|
|
56
|
+
? getModuleDeclarationStatements(decl)
|
|
57
|
+
: undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getModuleDeclarationStatements(node: ts.ModuleDeclaration): readonly ts.Statement[] | undefined {
|
|
61
|
+
let { body } = node;
|
|
62
|
+
while (body && body.kind === ts.SyntaxKind.ModuleDeclaration) {
|
|
63
|
+
body = body.body;
|
|
64
|
+
}
|
|
65
|
+
return body && ts.isModuleBlock(body) ? body.statements : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export = rule;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRule, isMainFile } from "../util";
|
|
2
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
3
|
+
const rule = createRule({
|
|
4
|
+
name: "no-outside-dependencies",
|
|
5
|
+
defaultOptions: [],
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Don't import things in `DefinitelyTyped/node_modules`.",
|
|
10
|
+
recommended: "error",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
noOutsideDependencies: `File {{fileName}} comes from a \`node_modules\` but is not declared in this type's \`package.json\`. `,
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
if (isMainFile(context.getFilename(), /*allowNested*/ true)) {
|
|
19
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
20
|
+
const hasNodeReference = parserServices.program
|
|
21
|
+
.getSourceFiles()
|
|
22
|
+
.some((f) => f.typeReferenceDirectives.some((dir) => dir.fileName === "node"));
|
|
23
|
+
for (const sourceFile of parserServices.program.getSourceFiles()) {
|
|
24
|
+
const fileName = sourceFile.fileName;
|
|
25
|
+
if (
|
|
26
|
+
fileName.includes("/DefinitelyTyped/node_modules/") &&
|
|
27
|
+
!parserServices.program.isSourceFileDefaultLibrary(sourceFile) &&
|
|
28
|
+
!(hasNodeReference && fileName.includes("buffer"))
|
|
29
|
+
) {
|
|
30
|
+
context.report({
|
|
31
|
+
messageId: "noOutsideDependencies",
|
|
32
|
+
data: { fileName },
|
|
33
|
+
loc: { column: 0, line: 1 },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {};
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export = rule;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { createRule, getCommonDirectoryName } from "../util";
|
|
3
|
+
|
|
4
|
+
const rule = createRule({
|
|
5
|
+
name: "no-self-import",
|
|
6
|
+
defaultOptions: [],
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Forbids declaration files to import the current package using a global import.",
|
|
11
|
+
recommended: "error",
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
useRelativeImport: "Declaration file should not use a global import of itself. Use a relative import.",
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
if (!context.getFilename().endsWith(".d.ts")) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const services = ESLintUtils.getParserServices(context);
|
|
24
|
+
const packageName = getCommonDirectoryName(services.program.getRootFileNames());
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
if (node.source.value === packageName || node.source.value.startsWith(packageName + "/")) {
|
|
30
|
+
context.report({
|
|
31
|
+
messageId: "useRelativeImport",
|
|
32
|
+
node,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export = rule;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createRule } from "../util";
|
|
2
|
+
import { TSESTree } from "@typescript-eslint/utils";
|
|
3
|
+
|
|
4
|
+
const rule = createRule({
|
|
5
|
+
name: "no-single-element-tuple-type",
|
|
6
|
+
defaultOptions: [],
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Forbids `[T]`, which should be `T[]`.",
|
|
11
|
+
recommended: "error",
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
singleElementTupleType: `Type [T] is a single-element tuple type. You probably meant T[].`,
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
21
|
+
"TSTupleType[elementTypes.length=1]"(node: TSESTree.TSTupleType) {
|
|
22
|
+
context.report({
|
|
23
|
+
messageId: "singleElementTupleType",
|
|
24
|
+
node,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export = rule;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
|
|
4
|
+
import { createRule } from "../util";
|
|
5
|
+
|
|
6
|
+
type ESTreeFunctionLikeWithTypeParameters = TSESTree.FunctionLike & {
|
|
7
|
+
typeParameters: {};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type TSSignatureDeclarationWithTypeParameters = ts.SignatureDeclaration & {
|
|
11
|
+
typeParameters: {};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const rule = createRule({
|
|
15
|
+
defaultOptions: [],
|
|
16
|
+
meta: {
|
|
17
|
+
docs: {
|
|
18
|
+
description: "Forbids signatures using a generic parameter only once.",
|
|
19
|
+
recommended: "error",
|
|
20
|
+
},
|
|
21
|
+
messages: {
|
|
22
|
+
never: "Type parameter {{name}} is never used.",
|
|
23
|
+
sole: "Type parameter {{name}} is used only once.",
|
|
24
|
+
},
|
|
25
|
+
schema: [],
|
|
26
|
+
type: "problem",
|
|
27
|
+
},
|
|
28
|
+
name: "no-relative-import-in-test",
|
|
29
|
+
create(context) {
|
|
30
|
+
return {
|
|
31
|
+
[[
|
|
32
|
+
"ArrowFunctionExpression[typeParameters]",
|
|
33
|
+
"FunctionDeclaration[typeParameters]",
|
|
34
|
+
"FunctionExpression[typeParameters]",
|
|
35
|
+
"TSCallSignatureDeclaration[typeParameters]",
|
|
36
|
+
"TSConstructorType[typeParameters]",
|
|
37
|
+
"TSDeclareFunction[typeParameters]",
|
|
38
|
+
"TSFunctionType[typeParameters]",
|
|
39
|
+
"TSMethodSignature[typeParameters]",
|
|
40
|
+
].join(", ")](esNode: ESTreeFunctionLikeWithTypeParameters) {
|
|
41
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
42
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(esNode) as TSSignatureDeclarationWithTypeParameters;
|
|
43
|
+
if (!tsNode.typeParameters) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const checker = parserServices.program.getTypeChecker();
|
|
48
|
+
|
|
49
|
+
for (const typeParameter of tsNode.typeParameters) {
|
|
50
|
+
const name = typeParameter.name.text;
|
|
51
|
+
const res = getSoleUse(tsNode, assertDefined(checker.getSymbolAtLocation(typeParameter.name)), checker);
|
|
52
|
+
switch (res.type) {
|
|
53
|
+
case "sole":
|
|
54
|
+
context.report({
|
|
55
|
+
data: { name },
|
|
56
|
+
messageId: "sole",
|
|
57
|
+
node: parserServices.tsNodeToESTreeNodeMap.get(res.soleUse),
|
|
58
|
+
});
|
|
59
|
+
break;
|
|
60
|
+
case "never":
|
|
61
|
+
context.report({
|
|
62
|
+
data: { name },
|
|
63
|
+
messageId: "never",
|
|
64
|
+
node: parserServices.tsNodeToESTreeNodeMap.get(typeParameter),
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
type Result = { type: "ok" | "never" } | { type: "sole"; soleUse: ts.Identifier };
|
|
75
|
+
function getSoleUse(sig: ts.SignatureDeclaration, typeParameterSymbol: ts.Symbol, checker: ts.TypeChecker): Result {
|
|
76
|
+
const exit = {};
|
|
77
|
+
let soleUse: ts.Identifier | undefined;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
if (sig.typeParameters) {
|
|
81
|
+
for (const tp of sig.typeParameters) {
|
|
82
|
+
if (tp.constraint) {
|
|
83
|
+
recur(tp.constraint);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const param of sig.parameters) {
|
|
88
|
+
if (param.type) {
|
|
89
|
+
recur(param.type);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (sig.type) {
|
|
93
|
+
recur(sig.type);
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err === exit) {
|
|
97
|
+
return { type: "ok" };
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return soleUse ? { type: "sole", soleUse } : { type: "never" };
|
|
103
|
+
|
|
104
|
+
function recur(node: ts.Node): void {
|
|
105
|
+
if (ts.isIdentifier(node)) {
|
|
106
|
+
if (checker.getSymbolAtLocation(node) === typeParameterSymbol) {
|
|
107
|
+
if (soleUse === undefined) {
|
|
108
|
+
soleUse = node;
|
|
109
|
+
} else {
|
|
110
|
+
throw exit;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
node.forEachChild(recur);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export = rule;
|
|
120
|
+
|
|
121
|
+
function assertDefined<T>(value: T | undefined): T {
|
|
122
|
+
if (value === undefined) {
|
|
123
|
+
throw new Error("unreachable");
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRule } from "../util";
|
|
2
|
+
|
|
3
|
+
const rule = createRule({
|
|
4
|
+
name: "no-useless-files",
|
|
5
|
+
defaultOptions: [],
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Forbids files with no content.",
|
|
10
|
+
recommended: "error",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
noContent: "File has no content.",
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const {
|
|
19
|
+
ast: { tokens, comments },
|
|
20
|
+
} = context.getSourceCode();
|
|
21
|
+
|
|
22
|
+
if (tokens.length === 0) {
|
|
23
|
+
if (comments.length === 0) {
|
|
24
|
+
reportNoContent();
|
|
25
|
+
} else {
|
|
26
|
+
const referenceRegExp = /^\/\s*<reference\s*(types|path)\s*=\s*["|'](.*)["|']/;
|
|
27
|
+
let noReferenceFound = true;
|
|
28
|
+
|
|
29
|
+
for (const comment of comments) {
|
|
30
|
+
const referenceMatch = comment.value.match(referenceRegExp)?.[1];
|
|
31
|
+
if (!referenceMatch) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
noReferenceFound = false;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (noReferenceFound) {
|
|
39
|
+
reportNoContent();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {};
|
|
45
|
+
|
|
46
|
+
function reportNoContent() {
|
|
47
|
+
context.report({
|
|
48
|
+
messageId: "noContent",
|
|
49
|
+
loc: {
|
|
50
|
+
start: { line: 1, column: 0 },
|
|
51
|
+
end: { line: 1, column: 0 },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export = rule;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
import { createRule } from "../util";
|
|
4
|
+
|
|
5
|
+
const rule = createRule({
|
|
6
|
+
defaultOptions: [],
|
|
7
|
+
meta: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Forbids `const x: () => void`.",
|
|
10
|
+
recommended: "error",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
variableFunction: "Use a function declaration instead of a variable of function type.",
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
type: "problem",
|
|
17
|
+
},
|
|
18
|
+
name: "prefer-declare-function",
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
22
|
+
"VariableDeclaration > VariableDeclarator"(node: TSESTree.VariableDeclarator) {
|
|
23
|
+
if (
|
|
24
|
+
node.id.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSFunctionType &&
|
|
25
|
+
context.getFilename().endsWith(".d.ts")
|
|
26
|
+
) {
|
|
27
|
+
context.report({
|
|
28
|
+
messageId: "variableFunction",
|
|
29
|
+
node: node.id,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export = rule;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createRule } from "../util";
|
|
2
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
3
|
+
import { TSESTree } from "@typescript-eslint/types";
|
|
4
|
+
|
|
5
|
+
const rule = createRule({
|
|
6
|
+
name: "redundant-undefined",
|
|
7
|
+
defaultOptions: [],
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description:
|
|
12
|
+
"Forbids optional parameters from including an explicit `undefined` in their type; requires it in optional properties.",
|
|
13
|
+
recommended: "error",
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
redundantUndefined: `Parameter is optional, so no need to include \`undefined\` in the type.`,
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
23
|
+
TSUnionType(node) {
|
|
24
|
+
const hasUndefinedType = node.types.some((t) => t.type === AST_NODE_TYPES.TSUndefinedKeyword);
|
|
25
|
+
if (
|
|
26
|
+
node.parent!.type === AST_NODE_TYPES.TSTypeAnnotation &&
|
|
27
|
+
isParameter(node.parent!.parent!) &&
|
|
28
|
+
node.parent.parent.optional &&
|
|
29
|
+
isFunctionLike(node.parent!.parent!.parent!) &&
|
|
30
|
+
hasUndefinedType
|
|
31
|
+
) {
|
|
32
|
+
context.report({
|
|
33
|
+
messageId: "redundantUndefined",
|
|
34
|
+
node,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function isFunctionLike(node: TSESTree.Node): node is TSESTree.FunctionLike {
|
|
43
|
+
return (
|
|
44
|
+
node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
45
|
+
node.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
46
|
+
node.type === AST_NODE_TYPES.FunctionExpression ||
|
|
47
|
+
node.type === AST_NODE_TYPES.TSDeclareFunction ||
|
|
48
|
+
node.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
/** Note: Does not include parameter properties because those can't be optional */
|
|
52
|
+
function isParameter(node: TSESTree.Node): node is Exclude<TSESTree.Parameter, TSESTree.TSParameterProperty> {
|
|
53
|
+
return (
|
|
54
|
+
node.type === AST_NODE_TYPES.ArrayPattern ||
|
|
55
|
+
node.type === AST_NODE_TYPES.AssignmentPattern ||
|
|
56
|
+
node.type === AST_NODE_TYPES.Identifier ||
|
|
57
|
+
node.type === AST_NODE_TYPES.ObjectPattern ||
|
|
58
|
+
node.type === AST_NODE_TYPES.RestElement
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export = rule;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createRule } from "../util";
|
|
2
|
+
|
|
3
|
+
const rule = createRule({
|
|
4
|
+
name: "trim-file",
|
|
5
|
+
defaultOptions: [],
|
|
6
|
+
meta: {
|
|
7
|
+
type: "layout",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Forbids leading/trailing blank lines in a file. Allows file to end in '\n'",
|
|
10
|
+
recommended: "error",
|
|
11
|
+
},
|
|
12
|
+
messages: {
|
|
13
|
+
leadingBlankLine: "File should not begin with a blank line.",
|
|
14
|
+
trailingBlankLine:
|
|
15
|
+
"File should not end with a blank line. (Ending in one newline OK, ending in two newlines not OK.)",
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
const { lines, text } = context.getSourceCode();
|
|
21
|
+
if (text.startsWith("\r") || text.startsWith("\n")) {
|
|
22
|
+
context.report({
|
|
23
|
+
messageId: "leadingBlankLine",
|
|
24
|
+
loc: {
|
|
25
|
+
start: { line: 1, column: 0 },
|
|
26
|
+
end: { line: 1, column: 0 },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (text.endsWith("\n\n") || text.endsWith("\r\n\r\n")) {
|
|
31
|
+
const line = lines.length;
|
|
32
|
+
context.report({
|
|
33
|
+
messageId: "trailingBlankLine",
|
|
34
|
+
loc: {
|
|
35
|
+
start: { line, column: 0 },
|
|
36
|
+
end: { line, column: 0 },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export = rule;
|
package/src/util.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
1
2
|
import assert = require("assert");
|
|
2
3
|
import { pathExists, readFile } from "fs-extra";
|
|
3
4
|
import { basename, dirname, join } from "path";
|
|
4
5
|
import stripJsonComments = require("strip-json-comments");
|
|
5
6
|
import * as ts from "typescript";
|
|
6
7
|
|
|
8
|
+
export const createRule = ESLintUtils.RuleCreator(
|
|
9
|
+
(name) => `https://github.com/microsoft/DefinitelyTyped-tools/tree/master/packages/dtslint/src/rules/${name}.ts`
|
|
10
|
+
);
|
|
11
|
+
|
|
7
12
|
export async function readJson(path: string) {
|
|
8
13
|
const text = await readFile(path, "utf-8");
|
|
9
14
|
return JSON.parse(stripJsonComments(text));
|
|
@@ -13,6 +18,19 @@ export function failure(ruleName: string, s: string): string {
|
|
|
13
18
|
return `${s} See: https://github.com/microsoft/DefinitelyTyped-tools/blob/master/packages/dtslint/docs/${ruleName}.md`;
|
|
14
19
|
}
|
|
15
20
|
|
|
21
|
+
export function getCommonDirectoryName(files: readonly string[]): string {
|
|
22
|
+
let minLen = 999;
|
|
23
|
+
let minDir = "";
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const dir = dirname(file);
|
|
26
|
+
if (dir.length < minLen) {
|
|
27
|
+
minDir = dir;
|
|
28
|
+
minLen = dir.length;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return basename(minDir);
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export async function getCompilerOptions(dirPath: string): Promise<ts.CompilerOptions> {
|
|
17
35
|
const tsconfigPath = join(dirPath, "tsconfig.json");
|
|
18
36
|
if (!(await pathExists(tsconfigPath))) {
|
|
@@ -30,6 +48,13 @@ export function last<T>(a: readonly T[]): T {
|
|
|
30
48
|
return a[a.length - 1];
|
|
31
49
|
}
|
|
32
50
|
|
|
51
|
+
export function assertDefined<T>(a: T | undefined): T {
|
|
52
|
+
if (a === undefined) {
|
|
53
|
+
throw new Error();
|
|
54
|
+
}
|
|
55
|
+
return a;
|
|
56
|
+
}
|
|
57
|
+
|
|
33
58
|
export async function mapDefinedAsync<T, U>(arr: Iterable<T>, mapper: (t: T) => Promise<U | undefined>): Promise<U[]> {
|
|
34
59
|
const out = [];
|
|
35
60
|
for (const a of arr) {
|