@so1ve/eslint-plugin 4.1.8 → 4.2.0
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/index.d.mts +25 -83
- package/dist/index.mjs +13 -1254
- package/dist/rules/function-style.d.mts +8 -0
- package/dist/rules/function-style.mjs +145 -0
- package/dist/rules/html-spaced-comment.d.mts +8 -0
- package/dist/rules/html-spaced-comment.mjs +41 -0
- package/dist/rules/import-dedupe.d.mts +8 -0
- package/dist/rules/import-dedupe.mjs +42 -0
- package/dist/rules/import-export-newline.d.mts +8 -0
- package/dist/rules/import-export-newline.mjs +98 -0
- package/dist/rules/no-import-promises-as.d.mts +8 -0
- package/dist/rules/no-import-promises-as.mjs +45 -0
- package/dist/rules/no-inline-type-modifier.d.mts +8 -0
- package/dist/rules/no-inline-type-modifier.mjs +86 -0
- package/dist/rules/no-negated-comparison.d.mts +8 -0
- package/dist/rules/no-negated-comparison.mjs +45 -0
- package/dist/rules/no-useless-template-string.d.mts +8 -0
- package/dist/rules/no-useless-template-string.mjs +30 -0
- package/dist/rules/prefer-ts-expect-error.d.mts +8 -0
- package/dist/rules/prefer-ts-expect-error.mjs +46 -0
- package/dist/rules/require-async-with-await.d.mts +8 -0
- package/dist/rules/require-async-with-await.mjs +51 -0
- package/dist/rules/sort-exports.d.mts +8 -0
- package/dist/rules/sort-exports.mjs +68 -0
- package/dist/rules/sort-imports.d.mts +10 -0
- package/dist/rules/sort-imports.mjs +104 -0
- package/dist/rules/vue-root-element-sort-attributes.d.mts +11 -0
- package/dist/rules/vue-root-element-sort-attributes.mjs +66 -0
- package/dist/utils/index.mjs +19 -0
- package/dist/utils/sort-imports.mjs +417 -0
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/function-style.d.ts
|
|
4
|
+
type MessageIds = "arrow" | "declaration";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createEslintRule, getPreviousNode } from "../utils/index.mjs";
|
|
2
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
3
|
+
|
|
4
|
+
//#region src/rules/function-style.ts
|
|
5
|
+
const RULE_NAME = "function-style";
|
|
6
|
+
const rule = createEslintRule({
|
|
7
|
+
name: RULE_NAME,
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: { description: "Enforce function style." },
|
|
11
|
+
fixable: "code",
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
arrow: "Expected an arrow function shorthand.",
|
|
15
|
+
declaration: "Expected a function declaration."
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create: (context) => {
|
|
20
|
+
const sourceCode = context.sourceCode;
|
|
21
|
+
const scopeStack = [];
|
|
22
|
+
let haveThisAccess = false;
|
|
23
|
+
function getSingleReturnStatement(node) {
|
|
24
|
+
const { body } = node;
|
|
25
|
+
if (body.type !== AST_NODE_TYPES.BlockStatement) return;
|
|
26
|
+
const { body: blockBody } = body;
|
|
27
|
+
if (blockBody.length !== 1) return;
|
|
28
|
+
const [statement] = blockBody;
|
|
29
|
+
if (statement?.type !== AST_NODE_TYPES.ReturnStatement) return;
|
|
30
|
+
const allComments = sourceCode.getCommentsInside(node);
|
|
31
|
+
const statementComments = sourceCode.getCommentsInside(statement);
|
|
32
|
+
if (allComments.length !== statementComments.length) return;
|
|
33
|
+
return statement;
|
|
34
|
+
}
|
|
35
|
+
const extractFunctionInfo = (node) => ({
|
|
36
|
+
async: node.async,
|
|
37
|
+
generics: node.typeParameters ? sourceCode.getText(node.typeParameters) : "",
|
|
38
|
+
params: node.params.map((param) => sourceCode.getText(param)).join(", "),
|
|
39
|
+
returnType: node.returnType ? sourceCode.getText(node.returnType) : "",
|
|
40
|
+
body: sourceCode.getText(node.body)
|
|
41
|
+
});
|
|
42
|
+
function generateArrowFunction(name, info, returnValue, asVariable = true) {
|
|
43
|
+
const asyncKeyword = info.async ? "async " : "";
|
|
44
|
+
return `${asVariable && name ? `const ${name} = ` : ""}${asyncKeyword}${info.generics}(${info.params})${info.returnType} => ${returnValue};`;
|
|
45
|
+
}
|
|
46
|
+
function generateFunctionDeclaration(name, info) {
|
|
47
|
+
return `${info.async ? "async " : ""}function ${name}${info.generics}(${info.params})${info.returnType} ${info.body}`;
|
|
48
|
+
}
|
|
49
|
+
function setupScope(node) {
|
|
50
|
+
scopeStack.push(sourceCode.getScope(node));
|
|
51
|
+
}
|
|
52
|
+
function clearThisAccess() {
|
|
53
|
+
scopeStack.pop();
|
|
54
|
+
haveThisAccess = false;
|
|
55
|
+
}
|
|
56
|
+
function getFunctionExpressionParentNameString(node) {
|
|
57
|
+
const parent = node.parent;
|
|
58
|
+
if (parent?.id?.typeAnnotation || parent?.type !== AST_NODE_TYPES.VariableDeclarator || haveThisAccess) return null;
|
|
59
|
+
return parent.id.name;
|
|
60
|
+
}
|
|
61
|
+
function shouldConvertFunctionDeclarationToArrow(node) {
|
|
62
|
+
if (haveThisAccess) return { shouldConvert: false };
|
|
63
|
+
const previousNode = getPreviousNode(node.parent);
|
|
64
|
+
if (previousNode?.type === AST_NODE_TYPES.ExportNamedDeclaration && previousNode.declaration?.type === AST_NODE_TYPES.TSDeclareFunction) return { shouldConvert: false };
|
|
65
|
+
const returnStatement = getSingleReturnStatement(node);
|
|
66
|
+
const isExportDefault = node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration;
|
|
67
|
+
if (!returnStatement?.argument || !node.id?.name && !isExportDefault || node.generator) return { shouldConvert: false };
|
|
68
|
+
return {
|
|
69
|
+
shouldConvert: true,
|
|
70
|
+
returnStatement
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
"FunctionExpression": setupScope,
|
|
75
|
+
"FunctionExpression:exit"(node) {
|
|
76
|
+
const name = getFunctionExpressionParentNameString(node);
|
|
77
|
+
if (!name) {
|
|
78
|
+
clearThisAccess();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const info = extractFunctionInfo(node);
|
|
82
|
+
const grandParent = node.parent?.parent;
|
|
83
|
+
if (!grandParent) return;
|
|
84
|
+
context.report({
|
|
85
|
+
node,
|
|
86
|
+
messageId: "declaration",
|
|
87
|
+
fix: (fixer) => fixer.replaceText(grandParent, generateFunctionDeclaration(name, info))
|
|
88
|
+
});
|
|
89
|
+
clearThisAccess();
|
|
90
|
+
},
|
|
91
|
+
"FunctionDeclaration:not(TSDeclareFunction + FunctionDeclaration)": setupScope,
|
|
92
|
+
"FunctionDeclaration:not(TSDeclareFunction + FunctionDeclaration):exit"(node) {
|
|
93
|
+
if (haveThisAccess) return;
|
|
94
|
+
const { shouldConvert, returnStatement } = shouldConvertFunctionDeclarationToArrow(node);
|
|
95
|
+
if (!shouldConvert || !returnStatement?.argument) {
|
|
96
|
+
clearThisAccess();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const info = extractFunctionInfo(node);
|
|
100
|
+
const isExportDefault = node.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration;
|
|
101
|
+
const returnValue = `(${sourceCode.getText(returnStatement.argument)})`;
|
|
102
|
+
context.report({
|
|
103
|
+
node,
|
|
104
|
+
messageId: "arrow",
|
|
105
|
+
fix: (fixer) => fixer.replaceText(node, generateArrowFunction(node.id?.name ?? null, info, returnValue, !isExportDefault))
|
|
106
|
+
});
|
|
107
|
+
clearThisAccess();
|
|
108
|
+
},
|
|
109
|
+
"ArrowFunctionExpression": setupScope,
|
|
110
|
+
"ArrowFunctionExpression:exit"(node) {
|
|
111
|
+
if (haveThisAccess) return;
|
|
112
|
+
const { body, parent } = node;
|
|
113
|
+
const returnStatement = getSingleReturnStatement(node);
|
|
114
|
+
if (returnStatement?.argument) {
|
|
115
|
+
const returnValue = `(${sourceCode.getText(returnStatement.argument)})`;
|
|
116
|
+
context.report({
|
|
117
|
+
node,
|
|
118
|
+
messageId: "arrow",
|
|
119
|
+
fix: (fixer) => fixer.replaceText(node.body, returnValue)
|
|
120
|
+
});
|
|
121
|
+
} else if (body.type === AST_NODE_TYPES.BlockStatement && !parent?.id?.typeAnnotation) {
|
|
122
|
+
const { body: blockBody } = body;
|
|
123
|
+
if (blockBody.length > 0 && node.parent?.parent?.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
124
|
+
const grandParent = node.parent.parent;
|
|
125
|
+
const name = node.parent.id.name;
|
|
126
|
+
const info = extractFunctionInfo(node);
|
|
127
|
+
context.report({
|
|
128
|
+
node: grandParent,
|
|
129
|
+
messageId: "declaration",
|
|
130
|
+
fix: (fixer) => fixer.replaceText(grandParent, generateFunctionDeclaration(name, info))
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
clearThisAccess();
|
|
135
|
+
},
|
|
136
|
+
ThisExpression(node) {
|
|
137
|
+
haveThisAccess = scopeStack.includes(sourceCode.getScope(node));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
var function_style_default = rule;
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { function_style_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/html-spaced-comment.d.ts
|
|
4
|
+
type MessageIds = "expectedSpaceBefore" | "expectedSpaceAfter";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/html-spaced-comment.ts
|
|
4
|
+
const RULE_NAME = "html-spaced-comment";
|
|
5
|
+
const rule = createEslintRule({
|
|
6
|
+
name: RULE_NAME,
|
|
7
|
+
meta: {
|
|
8
|
+
type: "layout",
|
|
9
|
+
docs: { description: "Enforce consistent spacing in HTML comments" },
|
|
10
|
+
fixable: "whitespace",
|
|
11
|
+
schema: [],
|
|
12
|
+
messages: {
|
|
13
|
+
expectedSpaceBefore: "Expected space after '<!--'.",
|
|
14
|
+
expectedSpaceAfter: "Expected space before '-->'."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create: (context) => ({ Comment(node) {
|
|
19
|
+
if (node.value?.type !== "CommentContent") return;
|
|
20
|
+
const rawValue = node.value.value;
|
|
21
|
+
if (rawValue.trim().length === 0) return;
|
|
22
|
+
if (!rawValue.startsWith(" ") && !rawValue.startsWith("\n")) context.report({
|
|
23
|
+
node: node.value,
|
|
24
|
+
messageId: "expectedSpaceBefore",
|
|
25
|
+
fix(fixer) {
|
|
26
|
+
return fixer.insertTextBefore(node.value, " ");
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (!rawValue.endsWith(" ") && !rawValue.endsWith("\n")) context.report({
|
|
30
|
+
node: node.value,
|
|
31
|
+
messageId: "expectedSpaceAfter",
|
|
32
|
+
fix(fixer) {
|
|
33
|
+
return fixer.insertTextAfter(node.value, " ");
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
} })
|
|
37
|
+
});
|
|
38
|
+
var html_spaced_comment_default = rule;
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { html_spaced_comment_default as default };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/import-dedupe.ts
|
|
4
|
+
const RULE_NAME = "import-dedupe";
|
|
5
|
+
const rule = createEslintRule({
|
|
6
|
+
name: RULE_NAME,
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: { description: "Fix duplication in imports." },
|
|
10
|
+
fixable: "code",
|
|
11
|
+
schema: [],
|
|
12
|
+
messages: { importDedupe: "Expect no duplication in imports." }
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: (context) => ({ ImportDeclaration(node) {
|
|
16
|
+
if (node.specifiers.length <= 1) return;
|
|
17
|
+
const names = /* @__PURE__ */ new Set();
|
|
18
|
+
for (const n of node.specifiers) {
|
|
19
|
+
const id = n.local.name;
|
|
20
|
+
if (names.has(id)) context.report({
|
|
21
|
+
node,
|
|
22
|
+
loc: {
|
|
23
|
+
start: n.loc.end,
|
|
24
|
+
end: n.loc.start
|
|
25
|
+
},
|
|
26
|
+
messageId: "importDedupe",
|
|
27
|
+
fix(fixer) {
|
|
28
|
+
const start = n.range[0];
|
|
29
|
+
let end = n.range[1];
|
|
30
|
+
const nextToken = context.sourceCode.getTokenAfter(n);
|
|
31
|
+
if (nextToken?.value === ",") end = nextToken.range[1];
|
|
32
|
+
return fixer.removeRange([start, end]);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
names.add(id);
|
|
36
|
+
}
|
|
37
|
+
} })
|
|
38
|
+
});
|
|
39
|
+
var import_dedupe_default = rule;
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { import_dedupe_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/import-export-newline.d.ts
|
|
4
|
+
type MessageIds = "newlineAfterLastImport" | "newlineBeforeExport" | "newlineAfterExport";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createEslintRule, getNextNode, getPreviousNode } from "../utils/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/import-export-newline.ts
|
|
4
|
+
const RULE_NAME = "import-export-newline";
|
|
5
|
+
const isExportDeclaration = (node) => node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration";
|
|
6
|
+
const rule = createEslintRule({
|
|
7
|
+
name: RULE_NAME,
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: { description: "Enforce spacing between imports and exports." },
|
|
11
|
+
fixable: "code",
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
newlineAfterLastImport: "Expected a blank line after the last import.",
|
|
15
|
+
newlineBeforeExport: "Expected a blank line before the export.",
|
|
16
|
+
newlineAfterExport: "Expected a blank line after the export."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create: (context) => {
|
|
21
|
+
const sourceCode = context.sourceCode;
|
|
22
|
+
let lastImportNode = null;
|
|
23
|
+
const exportNodes = [];
|
|
24
|
+
function checkExport(node) {
|
|
25
|
+
exportNodes.push(node);
|
|
26
|
+
}
|
|
27
|
+
function checkNewline(node, direction) {
|
|
28
|
+
let token;
|
|
29
|
+
let expectedLine;
|
|
30
|
+
let tokenLine;
|
|
31
|
+
if (direction === "after") {
|
|
32
|
+
const endNode = sourceCode.getCommentsAfter(node).find((c) => c.loc.start.line === node.loc.end.line) ?? node;
|
|
33
|
+
token = sourceCode.getTokenAfter(endNode);
|
|
34
|
+
const nextComment = sourceCode.getCommentsAfter(endNode)[0];
|
|
35
|
+
expectedLine = endNode.loc.end.line + 1;
|
|
36
|
+
tokenLine = token?.loc.start.line;
|
|
37
|
+
const commentLine = nextComment?.loc.start.line;
|
|
38
|
+
if (token && (expectedLine === tokenLine || expectedLine === commentLine || tokenLine === endNode.loc.end.line) && token.value !== "}" && token.value !== "<\/script>") return endNode;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const startNode = sourceCode.getCommentsBefore(node)[0] || node;
|
|
42
|
+
token = sourceCode.getTokenBefore(startNode);
|
|
43
|
+
expectedLine = startNode.loc.start.line - 1;
|
|
44
|
+
tokenLine = token?.loc.end.line;
|
|
45
|
+
if (token && expectedLine === tokenLine) return startNode;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
ImportDeclaration(node) {
|
|
50
|
+
lastImportNode = node;
|
|
51
|
+
},
|
|
52
|
+
ExportNamedDeclaration: checkExport,
|
|
53
|
+
ExportDefaultDeclaration: checkExport,
|
|
54
|
+
ExportAllDeclaration: checkExport,
|
|
55
|
+
"Program:exit"() {
|
|
56
|
+
if (lastImportNode) {
|
|
57
|
+
const parent = lastImportNode.parent;
|
|
58
|
+
const nextNode = getNextNode(lastImportNode);
|
|
59
|
+
const isLastInBlock = parent && "body" in parent && Array.isArray(parent.body) && parent.body[parent.body.length - 1] === lastImportNode;
|
|
60
|
+
if (nextNode && !isLastInBlock) {
|
|
61
|
+
const lastImportFixNode = checkNewline(lastImportNode, "after");
|
|
62
|
+
if (lastImportFixNode) context.report({
|
|
63
|
+
node: lastImportNode,
|
|
64
|
+
messageId: "newlineAfterLastImport",
|
|
65
|
+
fix: (fixer) => fixer.insertTextAfter(lastImportFixNode, "\n")
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const node of exportNodes) {
|
|
70
|
+
const prevNode = getPreviousNode(node);
|
|
71
|
+
const parent = node.parent;
|
|
72
|
+
const isFirstInBlock = parent && "body" in parent && Array.isArray(parent.body) && parent.body[0] === node;
|
|
73
|
+
if ((!prevNode || !isExportDeclaration(prevNode)) && (!lastImportNode || prevNode !== lastImportNode) && !isFirstInBlock) {
|
|
74
|
+
const beforeFixNode = checkNewline(node, "before");
|
|
75
|
+
if (beforeFixNode) context.report({
|
|
76
|
+
node,
|
|
77
|
+
messageId: "newlineBeforeExport",
|
|
78
|
+
fix: (fixer) => fixer.insertTextBefore(beforeFixNode, "\n")
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const nextNode = getNextNode(node);
|
|
82
|
+
if (nextNode && !isExportDeclaration(nextNode)) {
|
|
83
|
+
const afterFixNode = checkNewline(node, "after");
|
|
84
|
+
if (afterFixNode) context.report({
|
|
85
|
+
node,
|
|
86
|
+
messageId: "newlineAfterExport",
|
|
87
|
+
fix: (fixer) => fixer.insertTextAfter(afterFixNode, "\n")
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
var import_export_newline_default = rule;
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { import_export_newline_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-import-promises-as.d.ts
|
|
4
|
+
type MessageIds = "noImportPromisesAs";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-import-promises-as.ts
|
|
4
|
+
const RULE_NAME = "no-import-promises-as";
|
|
5
|
+
const POSSIBLE_IMPORT_SOURCES = [
|
|
6
|
+
"dns",
|
|
7
|
+
"fs",
|
|
8
|
+
"readline",
|
|
9
|
+
"stream"
|
|
10
|
+
].flatMap((s) => [s, `node:${s}`]);
|
|
11
|
+
const rule = createEslintRule({
|
|
12
|
+
name: RULE_NAME,
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: { description: "Disallow import promises as." },
|
|
16
|
+
fixable: "code",
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: { noImportPromisesAs: "Expect no import promises as." }
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create: (context) => {
|
|
22
|
+
const { text } = context.sourceCode;
|
|
23
|
+
return { ImportDeclaration(node) {
|
|
24
|
+
if (!POSSIBLE_IMPORT_SOURCES.includes(node.source.value)) return;
|
|
25
|
+
const promisesSpecifier = node.specifiers.find((s) => s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === "promises" && s.local.name !== "promises");
|
|
26
|
+
const as = promisesSpecifier?.local.name;
|
|
27
|
+
if (!promisesSpecifier || !as) return;
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
messageId: "noImportPromisesAs",
|
|
31
|
+
*fix(fixer) {
|
|
32
|
+
const s = promisesSpecifier.range[0];
|
|
33
|
+
let e = promisesSpecifier.range[1];
|
|
34
|
+
if (text[e] === ",") e += 1;
|
|
35
|
+
yield fixer.removeRange([s, e]);
|
|
36
|
+
yield fixer.insertTextAfter(node, `\nimport ${as} from "${node.source.value}/promises";`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
} };
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var no_import_promises_as_default = rule;
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { no_import_promises_as_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-inline-type-modifier.d.ts
|
|
4
|
+
type MessageIds = "noInlineTypeModifier";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
3
|
+
|
|
4
|
+
//#region src/rules/no-inline-type-modifier.ts
|
|
5
|
+
const RULE_NAME = "no-inline-type-modifier";
|
|
6
|
+
const getName = (node) => node.type === AST_NODE_TYPES.Identifier ? node.name : node.raw;
|
|
7
|
+
function getSpecifierText(s) {
|
|
8
|
+
const isExport = s.type === AST_NODE_TYPES.ExportSpecifier;
|
|
9
|
+
const name1 = getName(isExport ? s.local : s.imported);
|
|
10
|
+
const name2 = getName(isExport ? s.exported : s.local);
|
|
11
|
+
return name1 === name2 ? name1 : `${name1} as ${name2}`;
|
|
12
|
+
}
|
|
13
|
+
function generateSpecifiersText(specifiers) {
|
|
14
|
+
return `{ ${specifiers.map(getSpecifierText).join(", ")} }`;
|
|
15
|
+
}
|
|
16
|
+
const generateTypeText = (specifiers) => `type ${generateSpecifiersText(specifiers)}`;
|
|
17
|
+
function generateValueText(valueSpecifiers, defaultSpecifier) {
|
|
18
|
+
const parts = [];
|
|
19
|
+
if (defaultSpecifier) parts.push(defaultSpecifier.local.name);
|
|
20
|
+
if (valueSpecifiers.length > 0) parts.push(generateSpecifiersText(valueSpecifiers));
|
|
21
|
+
return parts.join(", ");
|
|
22
|
+
}
|
|
23
|
+
function classifySpecifiers(specifiers) {
|
|
24
|
+
const typeSpecifiers = [];
|
|
25
|
+
const valueSpecifiers = [];
|
|
26
|
+
let defaultSpecifier;
|
|
27
|
+
for (const s of specifiers) if (s.type === AST_NODE_TYPES.ImportDefaultSpecifier) defaultSpecifier = s;
|
|
28
|
+
else if (s.type === AST_NODE_TYPES.ImportSpecifier) if (s.importKind === "type") typeSpecifiers.push(s);
|
|
29
|
+
else valueSpecifiers.push(s);
|
|
30
|
+
else if (s.type === AST_NODE_TYPES.ExportSpecifier) if (s.exportKind === "type") typeSpecifiers.push(s);
|
|
31
|
+
else valueSpecifiers.push(s);
|
|
32
|
+
return {
|
|
33
|
+
typeSpecifiers,
|
|
34
|
+
valueSpecifiers,
|
|
35
|
+
defaultSpecifier
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const rule = createEslintRule({
|
|
39
|
+
name: RULE_NAME,
|
|
40
|
+
meta: {
|
|
41
|
+
type: "layout",
|
|
42
|
+
docs: { description: "Disallow inline type modifiers in import/export." },
|
|
43
|
+
fixable: "code",
|
|
44
|
+
schema: [],
|
|
45
|
+
messages: { noInlineTypeModifier: "Expected no inline type modifier." }
|
|
46
|
+
},
|
|
47
|
+
defaultOptions: [],
|
|
48
|
+
create: (context) => ({
|
|
49
|
+
ImportDeclaration: (node) => {
|
|
50
|
+
const { typeSpecifiers, valueSpecifiers, defaultSpecifier } = classifySpecifiers(node.specifiers);
|
|
51
|
+
if (typeSpecifiers.length === 0) return;
|
|
52
|
+
const texts = [];
|
|
53
|
+
texts.push(generateTypeText(typeSpecifiers));
|
|
54
|
+
if (defaultSpecifier || valueSpecifiers.length > 0) texts.push(generateValueText(valueSpecifiers, defaultSpecifier));
|
|
55
|
+
const textToReport = texts.map((text) => `import ${text} from "${node.source.value}";`).join("\n");
|
|
56
|
+
context.report({
|
|
57
|
+
node,
|
|
58
|
+
messageId: "noInlineTypeModifier",
|
|
59
|
+
fix(fixer) {
|
|
60
|
+
return fixer.replaceText(node, textToReport);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
ExportNamedDeclaration: (node) => {
|
|
65
|
+
if (node.declaration || node.exportKind === "type") return;
|
|
66
|
+
const { typeSpecifiers, valueSpecifiers } = classifySpecifiers(node.specifiers);
|
|
67
|
+
if (typeSpecifiers.length === 0) return;
|
|
68
|
+
const fromText = node.source ? ` from "${node.source.value}"` : "";
|
|
69
|
+
const texts = [];
|
|
70
|
+
texts.push(generateTypeText(typeSpecifiers));
|
|
71
|
+
if (valueSpecifiers.length > 0) texts.push(generateValueText(valueSpecifiers));
|
|
72
|
+
const textToReport = texts.map((text) => `export ${text}${fromText};`).join("\n");
|
|
73
|
+
context.report({
|
|
74
|
+
node,
|
|
75
|
+
messageId: "noInlineTypeModifier",
|
|
76
|
+
fix(fixer) {
|
|
77
|
+
return fixer.replaceText(node, textToReport);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
var no_inline_type_modifier_default = rule;
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { no_inline_type_modifier_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-negated-comparison.d.ts
|
|
4
|
+
type MessageIds = "noNegatedComparison";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
3
|
+
|
|
4
|
+
//#region src/rules/no-negated-comparison.ts
|
|
5
|
+
const RULE_NAME = "no-negated-comparison";
|
|
6
|
+
const negatedToPositive = {
|
|
7
|
+
"==": "!=",
|
|
8
|
+
"===": "!==",
|
|
9
|
+
"!=": "==",
|
|
10
|
+
"!==": "===",
|
|
11
|
+
"<": ">=",
|
|
12
|
+
"<=": ">",
|
|
13
|
+
">": "<=",
|
|
14
|
+
">=": "<"
|
|
15
|
+
};
|
|
16
|
+
const negatives = Object.keys(negatedToPositive);
|
|
17
|
+
const rule = createEslintRule({
|
|
18
|
+
name: RULE_NAME,
|
|
19
|
+
meta: {
|
|
20
|
+
type: "problem",
|
|
21
|
+
docs: { description: "Disallow negated comparison." },
|
|
22
|
+
fixable: "code",
|
|
23
|
+
schema: [],
|
|
24
|
+
messages: { noNegatedComparison: "Expect no negated comparison." }
|
|
25
|
+
},
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
create: (context) => ({ BinaryExpression(node) {
|
|
28
|
+
const { parent, left, right, operator } = node;
|
|
29
|
+
if (!parent) return;
|
|
30
|
+
if (negatives.includes(operator) && parent.type === AST_NODE_TYPES.UnaryExpression && parent.operator === "!") context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: "noNegatedComparison",
|
|
33
|
+
*fix(fixer) {
|
|
34
|
+
const operatorRange = [left.range[1], right.range[0]];
|
|
35
|
+
const fixedOperator = negatedToPositive[operator];
|
|
36
|
+
yield fixer.replaceTextRange(operatorRange, fixedOperator);
|
|
37
|
+
yield fixer.removeRange([parent.range[0], parent.range[0] + 1]);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
} })
|
|
41
|
+
});
|
|
42
|
+
var no_negated_comparison_default = rule;
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { no_negated_comparison_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-useless-template-string.d.ts
|
|
4
|
+
type MessageIds = "noUselessTemplateString";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-useless-template-string.ts
|
|
4
|
+
const RULE_NAME = "no-useless-template-string";
|
|
5
|
+
const rule = createEslintRule({
|
|
6
|
+
name: RULE_NAME,
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: { description: "No useless template string." },
|
|
10
|
+
fixable: "code",
|
|
11
|
+
schema: [],
|
|
12
|
+
messages: { noUselessTemplateString: "No useless template string." }
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: (context) => ({ "TemplateLiteral:not(TaggedTemplateExpression > TemplateLiteral)"(node) {
|
|
16
|
+
const { quasis } = node;
|
|
17
|
+
const isSafe = !quasis.some(({ value: { raw } }) => raw.includes("\"") || raw.includes("'") || raw.includes("\n"));
|
|
18
|
+
if (node.expressions.length === 0 && isSafe) context.report({
|
|
19
|
+
node,
|
|
20
|
+
messageId: "noUselessTemplateString",
|
|
21
|
+
fix(fixer) {
|
|
22
|
+
return fixer.replaceTextRange(node.range, `"${node.quasis[0].value.raw}"`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
} })
|
|
26
|
+
});
|
|
27
|
+
var no_useless_template_string_default = rule;
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { no_useless_template_string_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/prefer-ts-expect-error.d.ts
|
|
4
|
+
type MessageIds = "preferExpectErrorComment";
|
|
5
|
+
type Options = [];
|
|
6
|
+
declare const rule: ESLintUtils.RuleModule<MessageIds, Options>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { rule };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createEslintRule } from "../utils/index.mjs";
|
|
2
|
+
import { AST_TOKEN_TYPES } from "@typescript-eslint/utils";
|
|
3
|
+
|
|
4
|
+
//#region src/rules/prefer-ts-expect-error.ts
|
|
5
|
+
const rule = createEslintRule({
|
|
6
|
+
name: "prefer-ts-expect-error",
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: { description: "Enforce using `@ts-expect-error` over `@ts-ignore`" },
|
|
10
|
+
fixable: "code",
|
|
11
|
+
messages: { preferExpectErrorComment: "Use \"@ts-expect-error\" to ensure an error is actually being suppressed." },
|
|
12
|
+
replacedBy: ["@typescript-eslint/ban-ts-comment"],
|
|
13
|
+
schema: []
|
|
14
|
+
},
|
|
15
|
+
defaultOptions: [],
|
|
16
|
+
create(context) {
|
|
17
|
+
const tsIgnoreRegExpSingleLine = /^\s*(?:\/\s*)?@ts-ignore/;
|
|
18
|
+
const tsIgnoreRegExpMultiLine = /^\s*(?:(?:\/|\*)+\s*)?@ts-ignore/;
|
|
19
|
+
const isLineComment = (comment) => comment.type === AST_TOKEN_TYPES.Line;
|
|
20
|
+
function getLastCommentLine(comment) {
|
|
21
|
+
if (isLineComment(comment)) return comment.value;
|
|
22
|
+
const commentlines = comment.value.split("\n");
|
|
23
|
+
return commentlines[commentlines.length - 1];
|
|
24
|
+
}
|
|
25
|
+
function isValidTsIgnorePresent(comment) {
|
|
26
|
+
const line = getLastCommentLine(comment);
|
|
27
|
+
return isLineComment(comment) ? tsIgnoreRegExpSingleLine.test(line) : tsIgnoreRegExpMultiLine.test(line);
|
|
28
|
+
}
|
|
29
|
+
return { Program() {
|
|
30
|
+
const comments = context.sourceCode.getAllComments();
|
|
31
|
+
for (const comment of comments) if (isValidTsIgnorePresent(comment)) {
|
|
32
|
+
const lineCommentRuleFixer = (fixer) => fixer.replaceText(comment, `//${comment.value.replace("@ts-ignore", "@ts-expect-error")}`);
|
|
33
|
+
const blockCommentRuleFixer = (fixer) => fixer.replaceText(comment, `/*${comment.value.replace("@ts-ignore", "@ts-expect-error")}*/`);
|
|
34
|
+
context.report({
|
|
35
|
+
node: comment,
|
|
36
|
+
messageId: "preferExpectErrorComment",
|
|
37
|
+
fix: isLineComment(comment) ? lineCommentRuleFixer : blockCommentRuleFixer
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
} };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
var prefer_ts_expect_error_default = rule;
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { prefer_ts_expect_error_default as default };
|