xshell 1.0.84 → 1.0.86
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/net.js +1 -1
- package/package.json +19 -15
- package/process.d.ts +1 -2
- package/process.js +1 -2
- package/prototype.browser.d.ts +4 -0
- package/prototype.browser.js +12 -0
- package/prototype.d.ts +4 -0
- package/prototype.js +12 -0
- package/xlint.d.ts +108 -0
- package/xlint.js +748 -0
- /package/patches/{koa@2.15.0.patch → koa@2.15.2.patch} +0 -0
package/net.js
CHANGED
|
@@ -80,7 +80,7 @@ export async function request(url, options = {}) {
|
|
|
80
80
|
let headers = {
|
|
81
81
|
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja-JP;q=0.6,ja;q=0.5',
|
|
82
82
|
'accept-encoding': 'gzip, deflate, br',
|
|
83
|
-
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
83
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
|
|
84
84
|
'sec-ch-ua-platform': '"Windows"',
|
|
85
85
|
'sec-ch-ua-platform-version': '"15.0.0"',
|
|
86
86
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xshell",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
@@ -53,17 +53,18 @@
|
|
|
53
53
|
]
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@babel/core": "^7.24.
|
|
57
|
-
"@babel/parser": "^7.24.
|
|
58
|
-
"@babel/traverse": "^7.24.
|
|
56
|
+
"@babel/core": "^7.24.4",
|
|
57
|
+
"@babel/parser": "^7.24.4",
|
|
58
|
+
"@babel/traverse": "^7.24.1",
|
|
59
59
|
"@koa/cors": "^5.0.0",
|
|
60
60
|
"@types/ws": "^8.5.10",
|
|
61
|
+
"@typescript-eslint/utils": "^7.5.0",
|
|
61
62
|
"ali-oss": "^6.20.0",
|
|
62
|
-
"archiver": "^7.0.
|
|
63
|
+
"archiver": "^7.0.1",
|
|
63
64
|
"byte-size": "^8.1.1",
|
|
64
65
|
"chalk": "^5.3.0",
|
|
65
66
|
"chardet": "^2.0.0",
|
|
66
|
-
"cli-table3": "^0.6.
|
|
67
|
+
"cli-table3": "^0.6.4",
|
|
67
68
|
"cli-truncate": "^4.0.0",
|
|
68
69
|
"colors": "^1.4.0",
|
|
69
70
|
"commander": "^12.0.0",
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
"i18next": "^23.10.1",
|
|
74
75
|
"i18next-scanner": "^4.4.0",
|
|
75
76
|
"js-cookie": "^3.0.5",
|
|
76
|
-
"koa": "^2.15.
|
|
77
|
+
"koa": "^2.15.2",
|
|
77
78
|
"koa-compress": "^5.1.1",
|
|
78
79
|
"lodash": "^4.17.21",
|
|
79
80
|
"map-stream": "0.0.7",
|
|
@@ -81,15 +82,15 @@
|
|
|
81
82
|
"ora": "^8.0.1",
|
|
82
83
|
"react": "^18.2.0",
|
|
83
84
|
"react-i18next": "^14.1.0",
|
|
84
|
-
"react-object-model": "^1.2.
|
|
85
|
+
"react-object-model": "^1.2.3",
|
|
85
86
|
"resolve-path": "^1.4.0",
|
|
86
87
|
"strip-ansi": "^7.1.0",
|
|
87
88
|
"through2": "^4.0.2",
|
|
88
89
|
"tough-cookie": "^4.1.3",
|
|
89
90
|
"tslib": "^2.6.2",
|
|
90
|
-
"typescript": "^5.4.
|
|
91
|
+
"typescript": "^5.4.4",
|
|
91
92
|
"ua-parser-js": "2.0.0-alpha.2",
|
|
92
|
-
"undici": "^6.
|
|
93
|
+
"undici": "^6.11.1",
|
|
93
94
|
"vinyl": "^3.0.0",
|
|
94
95
|
"vinyl-fs": "^4.0.0",
|
|
95
96
|
"ws": "^8.16.0",
|
|
@@ -105,24 +106,27 @@
|
|
|
105
106
|
"@types/babel__traverse": "^7.20.5",
|
|
106
107
|
"@types/byte-size": "^8.1.2",
|
|
107
108
|
"@types/chardet": "^0.8.3",
|
|
109
|
+
"@types/eslint": "^8.56.7",
|
|
110
|
+
"@types/estree": "^1.0.5",
|
|
108
111
|
"@types/gulp-sort": "2.0.4",
|
|
109
112
|
"@types/js-cookie": "^3.0.6",
|
|
110
113
|
"@types/koa": "^2.15.0",
|
|
111
114
|
"@types/koa-compress": "^4.0.6",
|
|
112
|
-
"@types/lodash": "^4.
|
|
115
|
+
"@types/lodash": "^4.17.0",
|
|
113
116
|
"@types/mime-types": "^2.1.4",
|
|
114
|
-
"@types/node": "^20.
|
|
115
|
-
"@types/react": "^18.2.
|
|
117
|
+
"@types/node": "^20.12.5",
|
|
118
|
+
"@types/react": "^18.2.74",
|
|
116
119
|
"@types/through2": "^2.0.41",
|
|
117
120
|
"@types/tough-cookie": "^4.0.5",
|
|
118
121
|
"@types/ua-parser-js": "^0.7.39",
|
|
119
122
|
"@types/vinyl-fs": "^3.0.5",
|
|
120
|
-
"@types/vscode": "^1.
|
|
123
|
+
"@types/vscode": "^1.88.0",
|
|
124
|
+
"eslint": "^9.0.0"
|
|
121
125
|
},
|
|
122
126
|
"pnpm": {
|
|
123
127
|
"patchedDependencies": {
|
|
124
128
|
"@types/byte-size@8.1.2": "patches/@types__byte-size@8.1.2.patch",
|
|
125
|
-
"koa@2.15.
|
|
129
|
+
"koa@2.15.2": "patches/koa@2.15.2.patch"
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
}
|
package/process.d.ts
CHANGED
|
@@ -48,8 +48,7 @@ interface StartOptions {
|
|
|
48
48
|
- encoding?: `'utf-8'` 子进程输出编码 child output encoding
|
|
49
49
|
- print?: `true` print 选项,支持设置细项 print option (with details)
|
|
50
50
|
- stdio?: `'pipe'` 设置为 'ignore' 时忽略 stdio 处理 when 'ignore' then ignore stdio processing
|
|
51
|
-
- detached?: `false` 是否断开和 child 的关系 (ignore stdio, unref) whether to break the connection with child (ignore stdio, unref)
|
|
52
|
-
*/
|
|
51
|
+
- detached?: `false` 是否断开和 child 的关系 (ignore stdio, unref) whether to break the connection with child (ignore stdio, unref) */
|
|
53
52
|
export declare function start(exe: string, args?: string[], { cwd, encoding, print, stdio, detached, envs, window: _window, }?: StartOptions): Promise<ChildProcess>;
|
|
54
53
|
export declare function start_nodejs(js: string, args?: string[], options?: StartOptions): Promise<ChildProcess>;
|
|
55
54
|
export interface CallOptions extends StartOptions {
|
package/process.js
CHANGED
|
@@ -15,8 +15,7 @@ export const username = os.userInfo().username;
|
|
|
15
15
|
- encoding?: `'utf-8'` 子进程输出编码 child output encoding
|
|
16
16
|
- print?: `true` print 选项,支持设置细项 print option (with details)
|
|
17
17
|
- stdio?: `'pipe'` 设置为 'ignore' 时忽略 stdio 处理 when 'ignore' then ignore stdio processing
|
|
18
|
-
- detached?: `false` 是否断开和 child 的关系 (ignore stdio, unref) whether to break the connection with child (ignore stdio, unref)
|
|
19
|
-
*/
|
|
18
|
+
- detached?: `false` 是否断开和 child 的关系 (ignore stdio, unref) whether to break the connection with child (ignore stdio, unref) */
|
|
20
19
|
export async function start(exe, args = [], { cwd, encoding = 'utf-8', print = true, stdio = 'pipe', detached = false, envs, window: _window = true, } = {}) {
|
|
21
20
|
const options = {
|
|
22
21
|
cwd,
|
package/prototype.browser.d.ts
CHANGED
|
@@ -75,6 +75,10 @@ declare global {
|
|
|
75
75
|
text: string;
|
|
76
76
|
};
|
|
77
77
|
space(this: string): string;
|
|
78
|
+
/** assert 确保字符串以 prefix 开头(失败时抛出错误),并返回去掉该 prefix 的字符串 */
|
|
79
|
+
strip_start(this: string, prefix: string): string;
|
|
80
|
+
/** assert 确保字符串以 suffix 结尾(失败时抛出错误),并返回去掉该 suffix 的字符串 */
|
|
81
|
+
strip_end(this: string, suffix: string): string;
|
|
78
82
|
/** 等价于 .endsWith('/') */
|
|
79
83
|
isdir: boolean;
|
|
80
84
|
/** 以 `/` 分割的路径,可能以 / 结尾 */
|
package/prototype.browser.js
CHANGED
|
@@ -259,6 +259,18 @@ Object.defineProperties(String.prototype, {
|
|
|
259
259
|
text: this.slice(i)
|
|
260
260
|
};
|
|
261
261
|
},
|
|
262
|
+
strip_start(prefix) {
|
|
263
|
+
if (this.startsWith(prefix))
|
|
264
|
+
return this.slice(prefix.length);
|
|
265
|
+
else
|
|
266
|
+
throw new Error(`字符串没有以前缀 ${prefix} 开头: ${this}`);
|
|
267
|
+
},
|
|
268
|
+
strip_end(suffix) {
|
|
269
|
+
if (this.endsWith(suffix))
|
|
270
|
+
return this.slice(0, -suffix.length);
|
|
271
|
+
else
|
|
272
|
+
throw new Error(`字符串没有以后缀 ${suffix} 结尾: ${this}`);
|
|
273
|
+
},
|
|
262
274
|
space() {
|
|
263
275
|
if (!this)
|
|
264
276
|
return this;
|
package/prototype.d.ts
CHANGED
|
@@ -98,6 +98,10 @@ declare global {
|
|
|
98
98
|
decode_base64(this: string, buffer: true): Buffer;
|
|
99
99
|
decode_base64(this: string, buffer?: boolean): string | Buffer;
|
|
100
100
|
space(this: string): string;
|
|
101
|
+
/** assert 确保字符串以 prefix 开头(失败时抛出错误),并返回去掉该 prefix 的字符串 */
|
|
102
|
+
strip_start(this: string, prefix: string): string;
|
|
103
|
+
/** assert 确保字符串以 suffix 结尾(失败时抛出错误),并返回去掉该 suffix 的字符串 */
|
|
104
|
+
strip_end(this: string, suffix: string): string;
|
|
101
105
|
/** 等价于 .endsWith('/') */
|
|
102
106
|
isdir: boolean;
|
|
103
107
|
/** 以 `/` 分割的路径,可能以 / 结尾 */
|
package/prototype.js
CHANGED
|
@@ -278,6 +278,18 @@ if (!globalThis.my_prototype_defined) {
|
|
|
278
278
|
strip_ansi() {
|
|
279
279
|
return strip_ansi(this);
|
|
280
280
|
},
|
|
281
|
+
strip_start(prefix) {
|
|
282
|
+
if (this.startsWith(prefix))
|
|
283
|
+
return this.slice(prefix.length);
|
|
284
|
+
else
|
|
285
|
+
throw new Error(`字符串没有以前缀 ${prefix} 开头: ${this}`);
|
|
286
|
+
},
|
|
287
|
+
strip_end(suffix) {
|
|
288
|
+
if (this.endsWith(suffix))
|
|
289
|
+
return this.slice(0, -suffix.length);
|
|
290
|
+
else
|
|
291
|
+
throw new Error(`字符串没有以后缀 ${suffix} 结尾: ${this}`);
|
|
292
|
+
},
|
|
281
293
|
space() {
|
|
282
294
|
if (!this)
|
|
283
295
|
return this;
|
package/xlint.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type ESTree from 'estree';
|
|
2
|
+
import type { Rule } from 'eslint';
|
|
3
|
+
import { TSESTree, type TSESLint } from '@typescript-eslint/utils';
|
|
4
|
+
export declare const xlint_plugin: {
|
|
5
|
+
rules: {
|
|
6
|
+
'fold-jsdoc-comments': {
|
|
7
|
+
meta: {
|
|
8
|
+
readonly fixable: "whitespace";
|
|
9
|
+
readonly type: "layout";
|
|
10
|
+
};
|
|
11
|
+
create: (context: Rule.RuleContext) => {
|
|
12
|
+
Program(): void;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
'nonblock-statement-body-position-with-indentation': {
|
|
16
|
+
meta: {
|
|
17
|
+
readonly fixable: "whitespace";
|
|
18
|
+
readonly type: "layout";
|
|
19
|
+
};
|
|
20
|
+
create(context: Rule.RuleContext): {
|
|
21
|
+
IfStatement(node: ESTree.IfStatement & Rule.NodeParentExtension): void;
|
|
22
|
+
WhileStatement: (node: ESTree.WhileStatement & Rule.NodeParentExtension) => void;
|
|
23
|
+
DoWhileStatement: (node: ESTree.DoWhileStatement & Rule.NodeParentExtension) => void;
|
|
24
|
+
ForStatement: (node: ESTree.ForStatement & Rule.NodeParentExtension) => void;
|
|
25
|
+
ForInStatement: (node: ESTree.ForInStatement & Rule.NodeParentExtension) => void;
|
|
26
|
+
ForOfStatement: (node: ESTree.ForOfStatement & Rule.NodeParentExtension) => void;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
'empty-bracket-spacing': {
|
|
30
|
+
meta: {
|
|
31
|
+
readonly fixable: "whitespace";
|
|
32
|
+
readonly type: "layout";
|
|
33
|
+
};
|
|
34
|
+
create(context: Rule.RuleContext): {
|
|
35
|
+
ObjectExpression(node: ESTree.ObjectExpression & Rule.NodeParentExtension): void;
|
|
36
|
+
ArrayExpression(node: ESTree.ArrayExpression & Rule.NodeParentExtension): void;
|
|
37
|
+
BlockStatement(node: ESTree.BlockStatement & Rule.NodeParentExtension): void;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
'space-infix-ops-except-exponentiation': {
|
|
41
|
+
meta: {
|
|
42
|
+
messages: {
|
|
43
|
+
missingSpace: string;
|
|
44
|
+
redundantSpace: string;
|
|
45
|
+
};
|
|
46
|
+
fixable: "whitespace";
|
|
47
|
+
type: "layout";
|
|
48
|
+
};
|
|
49
|
+
create(context: TSESLint.RuleContext<'missingSpace' | 'redundantSpace', any[]>): {
|
|
50
|
+
AssignmentExpression: (node: any) => void;
|
|
51
|
+
AssignmentPattern: (node: any) => void;
|
|
52
|
+
BinaryExpression: (node: any) => void;
|
|
53
|
+
LogicalExpression: (node: any) => void;
|
|
54
|
+
ConditionalExpression: (node: any) => void;
|
|
55
|
+
VariableDeclarator: (node: any) => void;
|
|
56
|
+
TSEnumMember: (node: TSESTree.TSEnumMember) => void;
|
|
57
|
+
PropertyDefinition: (node: TSESTree.PropertyDefinition) => void;
|
|
58
|
+
TSTypeAliasDeclaration: (node: TSESTree.TSTypeAliasDeclaration) => void;
|
|
59
|
+
TSUnionType: (typeAnnotation: TSESTree.TSIntersectionType | TSESTree.TSUnionType) => void;
|
|
60
|
+
TSIntersectionType: (typeAnnotation: TSESTree.TSIntersectionType | TSESTree.TSUnionType) => void;
|
|
61
|
+
TSConditionalType: (node: TSESTree.TSConditionalType) => void;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
'space-in-for-statement': {
|
|
65
|
+
meta: {
|
|
66
|
+
readonly fixable: "whitespace";
|
|
67
|
+
readonly type: "layout";
|
|
68
|
+
};
|
|
69
|
+
create(context: Rule.RuleContext): {
|
|
70
|
+
ForStatement(node: ESTree.ForStatement & Rule.NodeParentExtension): void;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
'jsx-no-redundant-parenthesis-in-return': {
|
|
74
|
+
meta: {
|
|
75
|
+
readonly fixable: "whitespace";
|
|
76
|
+
readonly type: "layout";
|
|
77
|
+
};
|
|
78
|
+
create(context: Rule.RuleContext): {
|
|
79
|
+
ReturnStatement(node: ESTree.ReturnStatement & Rule.NodeParentExtension): void;
|
|
80
|
+
ArrowFunctionExpression(node: ESTree.ArrowFunctionExpression & Rule.NodeParentExtension): void;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
'keep-indent': {
|
|
84
|
+
meta: {
|
|
85
|
+
readonly fixable: "whitespace";
|
|
86
|
+
readonly type: "layout";
|
|
87
|
+
};
|
|
88
|
+
create(context: Rule.RuleContext): {
|
|
89
|
+
Program(): void;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
'func-style': {
|
|
93
|
+
meta: {
|
|
94
|
+
readonly fixable: "whitespace";
|
|
95
|
+
readonly type: "layout";
|
|
96
|
+
};
|
|
97
|
+
create(context: Rule.RuleContext): {
|
|
98
|
+
FunctionDeclaration(node: ESTree.FunctionDeclaration & Rule.NodeParentExtension): void;
|
|
99
|
+
'FunctionDeclaration:exit'(): void;
|
|
100
|
+
FunctionExpression(node: ESTree.FunctionExpression & Rule.NodeParentExtension): void;
|
|
101
|
+
'FunctionExpression:exit'(): void;
|
|
102
|
+
ThisExpression(): void;
|
|
103
|
+
ArrowFunctionExpression(): void;
|
|
104
|
+
'ArrowFunctionExpression:exit'(node: ESTree.ArrowFunctionExpression & Rule.NodeParentExtension): void;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
};
|
package/xlint.js
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
import { AST_TOKEN_TYPES, ASTUtils, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
const line_break_pattern = /\r\n|[\r\n\u2028\u2029]/u;
|
|
3
|
+
const meta = {
|
|
4
|
+
fixable: 'whitespace',
|
|
5
|
+
type: 'layout',
|
|
6
|
+
};
|
|
7
|
+
const UNIONS = ['|', '&'];
|
|
8
|
+
// 用 ast explorer 选 esprima 来看
|
|
9
|
+
// node.loc.start.line 下标从 1 开始
|
|
10
|
+
export const xlint_plugin = {
|
|
11
|
+
rules: {
|
|
12
|
+
'fold-jsdoc-comments': {
|
|
13
|
+
meta,
|
|
14
|
+
create: context => ({
|
|
15
|
+
Program() {
|
|
16
|
+
for (const comment of context.sourceCode.getAllComments())
|
|
17
|
+
if (comment.type === 'Block') {
|
|
18
|
+
const lines = comment.value.split(line_break_pattern);
|
|
19
|
+
if (lines.length >= 3 &&
|
|
20
|
+
/^\*\s*$/u.test(lines[0]) &&
|
|
21
|
+
lines.slice(1, -1).every(line => /^\s*\*/u.test(line)) &&
|
|
22
|
+
/^[\s\*]*$/u.test(lines.at(-1)))
|
|
23
|
+
context.report({
|
|
24
|
+
loc: {
|
|
25
|
+
start: comment.loc.start,
|
|
26
|
+
end: comment.loc.end
|
|
27
|
+
},
|
|
28
|
+
message: 'Redundant jsdoc comment.',
|
|
29
|
+
fix(fixer) {
|
|
30
|
+
const indent = ' '.repeat(comment.loc.start.column);
|
|
31
|
+
const indenter = (line) => line.replace(/^\s*\*\s?/u, `${indent} `);
|
|
32
|
+
return fixer.replaceTextRange(comment.range, lines.length === 3 ?
|
|
33
|
+
`/** ${lines[1].replace(/^\s*\*\s*/u, '')} */`
|
|
34
|
+
:
|
|
35
|
+
[
|
|
36
|
+
`/** ${lines[1].replace(/^\s*\*\s?/u, '')}`,
|
|
37
|
+
...lines.slice(2, -2).map(indenter),
|
|
38
|
+
`${indenter(lines.at(-2))} */`
|
|
39
|
+
].join('\n'));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
},
|
|
46
|
+
// 一条语句作为 body 时强制换行缩进
|
|
47
|
+
// if (1) body
|
|
48
|
+
// 更改为
|
|
49
|
+
// if (1)
|
|
50
|
+
// body
|
|
51
|
+
'nonblock-statement-body-position-with-indentation': {
|
|
52
|
+
meta,
|
|
53
|
+
create(context) {
|
|
54
|
+
const source = context.sourceCode;
|
|
55
|
+
const lines = source.getLines();
|
|
56
|
+
function lint(body, node) {
|
|
57
|
+
if (body.type !== 'BlockStatement') {
|
|
58
|
+
const token_before = source.getTokenBefore(body);
|
|
59
|
+
let indent = 0;
|
|
60
|
+
const line = lines[node.loc.start.line - 1];
|
|
61
|
+
for (let i = 0; i < line.length; i++)
|
|
62
|
+
if (line[i] === ' ')
|
|
63
|
+
indent++;
|
|
64
|
+
else if (line[i] === '\t')
|
|
65
|
+
indent += 4;
|
|
66
|
+
else
|
|
67
|
+
break;
|
|
68
|
+
if (token_before.loc.end.line === body.loc.start.line ||
|
|
69
|
+
body.loc.start.column !== indent + 4)
|
|
70
|
+
context.report({
|
|
71
|
+
node: body,
|
|
72
|
+
message: 'Expected a linebreak before this statement.',
|
|
73
|
+
fix: fixer => fixer.replaceTextRange([token_before.range[1], body.range[0]], '\n' + ' '.repeat(indent + 4))
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
IfStatement(node) {
|
|
79
|
+
lint(node.consequent, node);
|
|
80
|
+
// Check the `else` node, but don't check 'else if' statements.
|
|
81
|
+
if (node.alternate && node.alternate.type !== 'IfStatement')
|
|
82
|
+
lint(node.alternate, node);
|
|
83
|
+
},
|
|
84
|
+
WhileStatement: node => lint(node.body, node),
|
|
85
|
+
DoWhileStatement: node => lint(node.body, node),
|
|
86
|
+
ForStatement: node => lint(node.body, node),
|
|
87
|
+
ForInStatement: node => lint(node.body, node),
|
|
88
|
+
ForOfStatement: node => lint(node.body, node)
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
// let o = { }
|
|
93
|
+
// let a = [ ]
|
|
94
|
+
// function foo () { }
|
|
95
|
+
'empty-bracket-spacing': {
|
|
96
|
+
meta,
|
|
97
|
+
create(context) {
|
|
98
|
+
const source = context.sourceCode;
|
|
99
|
+
return {
|
|
100
|
+
ObjectExpression(node) {
|
|
101
|
+
// 是 {} 这样的空对象
|
|
102
|
+
if (node.properties.length === 0 && source.getText(node) === '{}')
|
|
103
|
+
context.report({
|
|
104
|
+
node,
|
|
105
|
+
message: 'Expected a space in this empty object.',
|
|
106
|
+
fix: fixer => fixer.replaceText(node, '{ }')
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
ArrayExpression(node) {
|
|
110
|
+
// 是 [] 这样的空数组
|
|
111
|
+
if (node.elements.length === 0 && source.getText(node) === '[]')
|
|
112
|
+
context.report({
|
|
113
|
+
node,
|
|
114
|
+
message: 'Expected a space in this empty array.',
|
|
115
|
+
fix: fixer => fixer.replaceText(node, '[ ]')
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
BlockStatement(node) {
|
|
119
|
+
if (node.body.length === 0 && source.getText(node) === '{}')
|
|
120
|
+
context.report({
|
|
121
|
+
node,
|
|
122
|
+
message: 'Expected a space in this empty block.',
|
|
123
|
+
fix: fixer => fixer.replaceText(node, '{ }')
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
// a + b**c
|
|
130
|
+
'space-infix-ops-except-exponentiation': {
|
|
131
|
+
meta: {
|
|
132
|
+
...meta,
|
|
133
|
+
messages: {
|
|
134
|
+
missingSpace: "Operator '{{operator}}' must be spaced.",
|
|
135
|
+
redundantSpace: "Operator '{{operator}}' must not be spaced."
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
create(context) {
|
|
140
|
+
const int32Hint = true;
|
|
141
|
+
const { sourceCode } = context;
|
|
142
|
+
/** Returns the first token which violates the rule
|
|
143
|
+
@param {ASTNode} left The left node of the main node
|
|
144
|
+
@param {ASTNode} right The right node of the main node
|
|
145
|
+
@param {string} op The operator of the main node
|
|
146
|
+
@returns {Object} The violator token or null
|
|
147
|
+
@private */
|
|
148
|
+
function getFirstNonSpacedToken(left, right, op) {
|
|
149
|
+
const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op);
|
|
150
|
+
const prev = sourceCode.getTokenBefore(operator);
|
|
151
|
+
const next = sourceCode.getTokenAfter(operator);
|
|
152
|
+
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next))
|
|
153
|
+
return operator;
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
Reports an AST node as a rule violation
|
|
158
|
+
@param {ASTNode} mainNode The node to report
|
|
159
|
+
@param {Object} culpritToken The token which has a problem
|
|
160
|
+
@returns {void}
|
|
161
|
+
@private
|
|
162
|
+
*/
|
|
163
|
+
function report(mainNode, culpritToken) {
|
|
164
|
+
context.report({
|
|
165
|
+
node: mainNode,
|
|
166
|
+
loc: culpritToken.loc,
|
|
167
|
+
messageId: 'missingSpace',
|
|
168
|
+
data: {
|
|
169
|
+
operator: culpritToken.value
|
|
170
|
+
},
|
|
171
|
+
fix(fixer) {
|
|
172
|
+
const previousToken = sourceCode.getTokenBefore(culpritToken);
|
|
173
|
+
const afterToken = sourceCode.getTokenAfter(culpritToken);
|
|
174
|
+
let fixString = '';
|
|
175
|
+
if (culpritToken.range[0] - previousToken.range[1] === 0)
|
|
176
|
+
fixString = ' ';
|
|
177
|
+
fixString += culpritToken.value;
|
|
178
|
+
if (afterToken.range[0] - culpritToken.range[1] === 0)
|
|
179
|
+
fixString += ' ';
|
|
180
|
+
return fixer.replaceText(culpritToken, fixString);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
Check if the node is binary then report
|
|
186
|
+
@param {ASTNode} node node to evaluate
|
|
187
|
+
@returns {void}
|
|
188
|
+
@private
|
|
189
|
+
*/
|
|
190
|
+
function checkBinary(node) {
|
|
191
|
+
const leftNode = node.left.typeAnnotation ? node.left.typeAnnotation : node.left;
|
|
192
|
+
const rightNode = node.right;
|
|
193
|
+
// search for = in AssignmentPattern nodes
|
|
194
|
+
const operator = node.operator || '=';
|
|
195
|
+
// 找到 ** operator
|
|
196
|
+
if (operator === '**') {
|
|
197
|
+
const op = sourceCode.getFirstTokenBetween(leftNode, rightNode, token => token.value === operator);
|
|
198
|
+
const prev = sourceCode.getTokenBefore(op);
|
|
199
|
+
const next = sourceCode.getTokenAfter(op);
|
|
200
|
+
if (sourceCode.isSpaceBetween(prev, op) || sourceCode.isSpaceBetween(op, next))
|
|
201
|
+
context.report({
|
|
202
|
+
node,
|
|
203
|
+
loc: op.loc,
|
|
204
|
+
messageId: 'redundantSpace',
|
|
205
|
+
data: {
|
|
206
|
+
operator: op.value
|
|
207
|
+
},
|
|
208
|
+
fix(fixer) {
|
|
209
|
+
const previousToken = sourceCode.getTokenBefore(op);
|
|
210
|
+
const afterToken = sourceCode.getTokenAfter(op);
|
|
211
|
+
let fixes = [];
|
|
212
|
+
if (op.range[0] > previousToken.range[1])
|
|
213
|
+
fixes.push(
|
|
214
|
+
// @ts-ignore
|
|
215
|
+
fixer.removeRange([previousToken.range[1], op.range[0]]));
|
|
216
|
+
if (afterToken.range[0] > op.range[1])
|
|
217
|
+
fixes.push(
|
|
218
|
+
// @ts-ignore
|
|
219
|
+
fixer.removeRange([op.range[1], afterToken.range[0]]));
|
|
220
|
+
return fixes;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
|
|
226
|
+
if (nonSpacedNode)
|
|
227
|
+
if (!(int32Hint && sourceCode.getText(node).endsWith('|0')))
|
|
228
|
+
report(node, nonSpacedNode);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
Check if the node is conditional
|
|
233
|
+
@param {ASTNode} node node to evaluate
|
|
234
|
+
@returns {void}
|
|
235
|
+
@private
|
|
236
|
+
*/
|
|
237
|
+
function checkConditional(node) {
|
|
238
|
+
const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, '?');
|
|
239
|
+
const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ':');
|
|
240
|
+
if (nonSpacedConsequentNode)
|
|
241
|
+
report(node, nonSpacedConsequentNode);
|
|
242
|
+
if (nonSpacedAlternateNode)
|
|
243
|
+
report(node, nonSpacedAlternateNode);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
Check if the node is a variable
|
|
247
|
+
@param {ASTNode} node node to evaluate
|
|
248
|
+
@returns {void}
|
|
249
|
+
@private
|
|
250
|
+
*/
|
|
251
|
+
function checkVar(node) {
|
|
252
|
+
const leftNode = node.id.typeAnnotation ? node.id.typeAnnotation : node.id;
|
|
253
|
+
const rightNode = node.init;
|
|
254
|
+
if (rightNode) {
|
|
255
|
+
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, '=');
|
|
256
|
+
if (nonSpacedNode)
|
|
257
|
+
report(node, nonSpacedNode);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function ts_report(operator) {
|
|
261
|
+
context.report({
|
|
262
|
+
node: operator,
|
|
263
|
+
messageId: 'missingSpace',
|
|
264
|
+
data: {
|
|
265
|
+
operator: operator.value
|
|
266
|
+
},
|
|
267
|
+
fix(fixer) {
|
|
268
|
+
const previousToken = sourceCode.getTokenBefore(operator);
|
|
269
|
+
const afterToken = sourceCode.getTokenAfter(operator);
|
|
270
|
+
let fixString = '';
|
|
271
|
+
if (operator.range[0] - previousToken.range[1] === 0)
|
|
272
|
+
fixString = ' ';
|
|
273
|
+
fixString += operator.value;
|
|
274
|
+
if (afterToken.range[0] - operator.range[1] === 0)
|
|
275
|
+
fixString += ' ';
|
|
276
|
+
return fixer.replaceText(operator, fixString);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function isSpaceChar(token) {
|
|
281
|
+
return token.type === AST_TOKEN_TYPES.Punctuator && /^[=?:]$/.test(token.value);
|
|
282
|
+
}
|
|
283
|
+
function checkAndReportAssignmentSpace(leftNode, rightNode) {
|
|
284
|
+
if (!rightNode || !leftNode)
|
|
285
|
+
return;
|
|
286
|
+
const operator = sourceCode.getFirstTokenBetween(leftNode, rightNode, isSpaceChar);
|
|
287
|
+
const prev = sourceCode.getTokenBefore(operator);
|
|
288
|
+
const next = sourceCode.getTokenAfter(operator);
|
|
289
|
+
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next))
|
|
290
|
+
ts_report(operator);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
Check if it has an assignment char and report if it's faulty
|
|
294
|
+
@param node The node to report
|
|
295
|
+
*/
|
|
296
|
+
function checkForEnumAssignmentSpace(node) {
|
|
297
|
+
checkAndReportAssignmentSpace(node.id, node.initializer);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
Check if it has an assignment char and report if it's faulty
|
|
301
|
+
@param node The node to report
|
|
302
|
+
*/
|
|
303
|
+
function checkForPropertyDefinitionAssignmentSpace(node) {
|
|
304
|
+
const leftNode = node.optional && !node.typeAnnotation ? sourceCode.getTokenAfter(node.key) : node.typeAnnotation ?? node.key;
|
|
305
|
+
checkAndReportAssignmentSpace(leftNode, node.value);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
Check if it is missing spaces between type annotations chaining
|
|
309
|
+
@param typeAnnotation TypeAnnotations list
|
|
310
|
+
*/
|
|
311
|
+
function checkForTypeAnnotationSpace(typeAnnotation) {
|
|
312
|
+
const types = typeAnnotation.types;
|
|
313
|
+
types.forEach(type => {
|
|
314
|
+
const skipFunctionParenthesis = type.type === TSESTree.AST_NODE_TYPES.TSFunctionType ? ASTUtils.isNotOpeningBraceToken : 0;
|
|
315
|
+
const operator = sourceCode.getTokenBefore(type, skipFunctionParenthesis);
|
|
316
|
+
if (operator && UNIONS.includes(operator.value)) {
|
|
317
|
+
const prev = sourceCode.getTokenBefore(operator);
|
|
318
|
+
const next = sourceCode.getTokenAfter(operator);
|
|
319
|
+
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next))
|
|
320
|
+
ts_report(operator);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
Check if it has an assignment char and report if it's faulty
|
|
326
|
+
@param node The node to report
|
|
327
|
+
*/
|
|
328
|
+
function checkForTypeAliasAssignment(node) {
|
|
329
|
+
checkAndReportAssignmentSpace(node.typeParameters ?? node.id, node.typeAnnotation);
|
|
330
|
+
}
|
|
331
|
+
function checkForTypeConditional(node) {
|
|
332
|
+
checkAndReportAssignmentSpace(node.extendsType, node.trueType);
|
|
333
|
+
checkAndReportAssignmentSpace(node.trueType, node.falseType);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
AssignmentExpression: checkBinary,
|
|
337
|
+
AssignmentPattern: checkBinary,
|
|
338
|
+
BinaryExpression: checkBinary,
|
|
339
|
+
LogicalExpression: checkBinary,
|
|
340
|
+
ConditionalExpression: checkConditional,
|
|
341
|
+
VariableDeclarator: checkVar,
|
|
342
|
+
TSEnumMember: checkForEnumAssignmentSpace,
|
|
343
|
+
PropertyDefinition: checkForPropertyDefinitionAssignmentSpace,
|
|
344
|
+
TSTypeAliasDeclaration: checkForTypeAliasAssignment,
|
|
345
|
+
TSUnionType: checkForTypeAnnotationSpace,
|
|
346
|
+
TSIntersectionType: checkForTypeAnnotationSpace,
|
|
347
|
+
TSConditionalType: checkForTypeConditional
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
// for (;;)
|
|
352
|
+
// 1;
|
|
353
|
+
// for (let i = 0; i < 10; i++)
|
|
354
|
+
// for ( ; i < 10; )
|
|
355
|
+
'space-in-for-statement': {
|
|
356
|
+
meta,
|
|
357
|
+
create(context) {
|
|
358
|
+
const source = context.sourceCode;
|
|
359
|
+
return {
|
|
360
|
+
ForStatement(node) {
|
|
361
|
+
const paren_left = source.getFirstToken(node, token => token.value === '(');
|
|
362
|
+
const semi0 = source.getTokenAfter(node.init || paren_left);
|
|
363
|
+
const semi1 = source.getTokenAfter(node.test || semi0);
|
|
364
|
+
const semi0_prev = source.getTokenBefore(semi0, { includeComments: true });
|
|
365
|
+
const semi0_next = source.getTokenAfter(semi0, { includeComments: true });
|
|
366
|
+
const semi1_next = source.getTokenAfter(semi1, { includeComments: true });
|
|
367
|
+
if (node.init || node.test || node.update) {
|
|
368
|
+
// for (let i = 0; i < 10; i++)
|
|
369
|
+
// for ( ; i < 10; )
|
|
370
|
+
// for ( ; ; i++)
|
|
371
|
+
// for (let i = 0;)
|
|
372
|
+
if (node.init) {
|
|
373
|
+
if (semi0.loc.start.line === semi0_prev.loc.end.line &&
|
|
374
|
+
semi0.loc.start.column !== semi0_prev.loc.end.column)
|
|
375
|
+
context.report({
|
|
376
|
+
loc: {
|
|
377
|
+
start: semi0_prev.loc.end,
|
|
378
|
+
end: semi0.loc.start
|
|
379
|
+
},
|
|
380
|
+
message: 'for 语句第一个分号前面应该没有多余空格',
|
|
381
|
+
node,
|
|
382
|
+
fix: fixer => fixer.removeRange([semi0_prev.range[1], semi0.range[0]])
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
else // for ( ;)
|
|
386
|
+
if (semi0.loc.start.line === semi0_prev.loc.end.line &&
|
|
387
|
+
semi0.loc.start.column - semi0_prev.loc.end.column !== 2)
|
|
388
|
+
context.report({
|
|
389
|
+
loc: {
|
|
390
|
+
start: semi0_prev.loc.end,
|
|
391
|
+
end: semi0.loc.start
|
|
392
|
+
},
|
|
393
|
+
message: 'for 语句第一个分号前面应该为 2 个空格',
|
|
394
|
+
node,
|
|
395
|
+
fix: fixer => fixer.replaceTextRange([semi0_prev.range[1], semi0.range[0]], ' ')
|
|
396
|
+
});
|
|
397
|
+
// for ( ; ;)
|
|
398
|
+
if (semi0_next.loc.start.line === semi0.loc.end.line &&
|
|
399
|
+
semi0_next.loc.start.column - semi0.loc.end.column !== 2)
|
|
400
|
+
context.report({
|
|
401
|
+
loc: {
|
|
402
|
+
start: semi0.loc.end,
|
|
403
|
+
end: semi0_next.loc.start
|
|
404
|
+
},
|
|
405
|
+
message: 'for 语句第一个分号后面应为 2 个空格',
|
|
406
|
+
node,
|
|
407
|
+
fix: fixer => fixer.replaceTextRange([semi0.range[1], semi0_next.range[0]], ' ')
|
|
408
|
+
});
|
|
409
|
+
// // for ( ; i > 0;)
|
|
410
|
+
if (node.test) {
|
|
411
|
+
const test_end = source.getLastToken(node.test);
|
|
412
|
+
if (semi1.loc.start.line === test_end.loc.end.line &&
|
|
413
|
+
semi1.loc.start.column !== test_end.loc.end.column)
|
|
414
|
+
context.report({
|
|
415
|
+
loc: {
|
|
416
|
+
start: test_end.loc.end,
|
|
417
|
+
end: semi1.loc.start,
|
|
418
|
+
},
|
|
419
|
+
message: 'for 语句第二个分号前面没有多余的空格',
|
|
420
|
+
node,
|
|
421
|
+
fix: fixer => fixer.removeRange([test_end.range[1], semi1.range[0]])
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (semi1_next.loc.start.line === semi1.loc.end.line &&
|
|
425
|
+
semi1_next.loc.start.column - semi1.loc.end.column !== 2)
|
|
426
|
+
context.report({
|
|
427
|
+
loc: {
|
|
428
|
+
start: semi1.loc.end,
|
|
429
|
+
end: semi1_next.loc.start
|
|
430
|
+
},
|
|
431
|
+
message: 'for 语句第二个分号后面应为 2 个空格',
|
|
432
|
+
node,
|
|
433
|
+
fix: fixer => fixer.replaceTextRange([semi1.range[1], semi1_next.range[0]], ' ')
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
else { // for (;;)
|
|
437
|
+
if (semi0.loc.start.line === semi0_prev.loc.end.line &&
|
|
438
|
+
semi0.loc.start.column !== semi0_prev.loc.end.column)
|
|
439
|
+
context.report({
|
|
440
|
+
loc: {
|
|
441
|
+
start: semi0_prev.loc.end,
|
|
442
|
+
end: semi0.loc.start
|
|
443
|
+
},
|
|
444
|
+
message: '无条件 for 语句第一个分号前面应该没有空格',
|
|
445
|
+
node,
|
|
446
|
+
fix: fixer => fixer.removeRange([semi0_prev.range[1], semi0.range[0]])
|
|
447
|
+
});
|
|
448
|
+
if (semi0_next.loc.start.line === semi0.loc.end.line &&
|
|
449
|
+
semi0_next.loc.start.column !== semi0.loc.end.column)
|
|
450
|
+
context.report({
|
|
451
|
+
loc: {
|
|
452
|
+
start: semi0.loc.end,
|
|
453
|
+
end: semi0_next.loc.start
|
|
454
|
+
},
|
|
455
|
+
message: '无条件 for 语句第一个分号后面应该没有空格',
|
|
456
|
+
node,
|
|
457
|
+
fix: fixer => fixer.removeRange([semi0.range[1], semi0_next.range[0]])
|
|
458
|
+
});
|
|
459
|
+
if (semi1_next.loc.start.line === semi1.loc.end.line &&
|
|
460
|
+
semi1_next.loc.start.column !== semi1.loc.end.column)
|
|
461
|
+
context.report({
|
|
462
|
+
loc: {
|
|
463
|
+
start: semi1.loc.end,
|
|
464
|
+
end: semi1_next.loc.start
|
|
465
|
+
},
|
|
466
|
+
message: '无条件 for 语句第二个分号后面应该没有空格',
|
|
467
|
+
node,
|
|
468
|
+
fix: fixer => fixer.removeRange([semi1.range[1], semi1_next.range[0]])
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
// return (
|
|
476
|
+
// <div>1234</div>
|
|
477
|
+
// )
|
|
478
|
+
// 改为
|
|
479
|
+
// return <div>1234</div>
|
|
480
|
+
'jsx-no-redundant-parenthesis-in-return': {
|
|
481
|
+
meta,
|
|
482
|
+
create(context) {
|
|
483
|
+
const source = context.sourceCode;
|
|
484
|
+
return {
|
|
485
|
+
ReturnStatement(node) {
|
|
486
|
+
if (node.argument?.type === 'JSXElement' || node.argument?.type === 'JSXFragment') {
|
|
487
|
+
const paren0 = source.getTokenBefore(node.argument);
|
|
488
|
+
const paren1 = source.getTokenAfter(node.argument);
|
|
489
|
+
if (paren0.type === 'Punctuator' && paren0.value === '(' &&
|
|
490
|
+
paren1.type === 'Punctuator' && paren1.value === ')') {
|
|
491
|
+
const paren0_next = source.getTokenAfter(paren0);
|
|
492
|
+
const paren1_prev = source.getTokenBefore(paren1);
|
|
493
|
+
context.report({
|
|
494
|
+
loc: {
|
|
495
|
+
start: paren0.loc.start,
|
|
496
|
+
end: paren1.loc.end
|
|
497
|
+
},
|
|
498
|
+
message: 'return jsx 表达式不应该有多余的括号',
|
|
499
|
+
node,
|
|
500
|
+
fix: fixer => ([
|
|
501
|
+
fixer.removeRange([paren0.range[0], paren0_next.range[0]]),
|
|
502
|
+
fixer.removeRange([paren1_prev.range[1], paren1.range[1]])
|
|
503
|
+
])
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
ArrowFunctionExpression(node) {
|
|
509
|
+
if (node.body?.type === 'JSXElement' || node.body?.type === 'JSXFragment') {
|
|
510
|
+
const paren0 = source.getTokenBefore(node.body);
|
|
511
|
+
const paren1 = source.getTokenAfter(node.body);
|
|
512
|
+
if (paren0.type === 'Punctuator' && paren0.value === '(' &&
|
|
513
|
+
paren1.type === 'Punctuator' && paren1.value === ')') {
|
|
514
|
+
const paren0_next = source.getTokenAfter(paren0);
|
|
515
|
+
const paren1_prev = source.getTokenBefore(paren1);
|
|
516
|
+
context.report({
|
|
517
|
+
loc: {
|
|
518
|
+
start: paren0.loc.start,
|
|
519
|
+
end: paren1.loc.end
|
|
520
|
+
},
|
|
521
|
+
message: 'return jsx 表达式不应该有多余的括号',
|
|
522
|
+
node,
|
|
523
|
+
fix: fixer => ([
|
|
524
|
+
fixer.removeRange([paren0.range[0], paren0_next.range[0]]),
|
|
525
|
+
fixer.removeRange([paren1_prev.range[1], paren1.range[1]])
|
|
526
|
+
])
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
'keep-indent': {
|
|
535
|
+
meta,
|
|
536
|
+
create(context) {
|
|
537
|
+
const source = context.sourceCode;
|
|
538
|
+
const lines = source.getLines();
|
|
539
|
+
return {
|
|
540
|
+
Program() {
|
|
541
|
+
// 参考 keep_indent 逻辑
|
|
542
|
+
if (lines.length >= 2) {
|
|
543
|
+
let last_line = { indent: 0, text: '' };
|
|
544
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
545
|
+
const _line = split_indent(lines[i]);
|
|
546
|
+
if (!_line.indent && !_line.text && last_line.indent) {
|
|
547
|
+
const pos = { column: 0, line: i + 1 };
|
|
548
|
+
const index = source.getIndexFromLoc(pos);
|
|
549
|
+
const node = source.getNodeByRangeIndex(index);
|
|
550
|
+
if (node && node.type !== 'Program')
|
|
551
|
+
context.report({
|
|
552
|
+
message: '空行应保留与上一个区块相同的缩进',
|
|
553
|
+
loc: { start: pos, end: pos },
|
|
554
|
+
fix: fixer => fixer.replaceTextRange([index, index], ' '.repeat(last_line.indent))
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
last_line = _line;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
'func-style': {
|
|
565
|
+
meta,
|
|
566
|
+
create(context) {
|
|
567
|
+
const source = context.sourceCode;
|
|
568
|
+
let stack = [];
|
|
569
|
+
const fix = (fixer, node) => fixer.replaceText(node.parent.parent, `${node.async ? 'async ' : ''}function ${node.generator ? '* ' : ''}` +
|
|
570
|
+
// todo: 丢失了泛型参数 const foo = <D = Zippable> (xxx: D) => { },如何加回来?
|
|
571
|
+
node.parent.id.name +
|
|
572
|
+
' (' +
|
|
573
|
+
node.params.map(param => source.getText(param)).join(', ') +
|
|
574
|
+
') ' +
|
|
575
|
+
source.getText(node.body));
|
|
576
|
+
return {
|
|
577
|
+
FunctionDeclaration(node) {
|
|
578
|
+
stack.push(false);
|
|
579
|
+
},
|
|
580
|
+
'FunctionDeclaration:exit'() {
|
|
581
|
+
stack.pop();
|
|
582
|
+
},
|
|
583
|
+
FunctionExpression(node) {
|
|
584
|
+
stack.push(false);
|
|
585
|
+
if (node.parent.type === 'VariableDeclarator')
|
|
586
|
+
context.report({
|
|
587
|
+
node: node.parent.parent,
|
|
588
|
+
message: '函数应该使用 function foo { } 这样来声明而不是 const foo = function () { }',
|
|
589
|
+
loc: node.parent.parent.loc,
|
|
590
|
+
fix: fixer => fix(fixer, node)
|
|
591
|
+
});
|
|
592
|
+
},
|
|
593
|
+
'FunctionExpression:exit'() {
|
|
594
|
+
stack.pop();
|
|
595
|
+
},
|
|
596
|
+
ThisExpression() {
|
|
597
|
+
if (stack.length > 0)
|
|
598
|
+
stack[stack.length - 1] = true;
|
|
599
|
+
},
|
|
600
|
+
ArrowFunctionExpression() {
|
|
601
|
+
stack.push(false);
|
|
602
|
+
},
|
|
603
|
+
'ArrowFunctionExpression:exit'(node) {
|
|
604
|
+
if (!stack.pop() && // not has this expr
|
|
605
|
+
node.parent.type === 'VariableDeclarator' &&
|
|
606
|
+
node.body.type === 'BlockStatement' && node.body.body.length // 多条语句才转换
|
|
607
|
+
)
|
|
608
|
+
context.report({
|
|
609
|
+
node: node.parent.parent,
|
|
610
|
+
message: '多条语句的函数应该使用 function foo { } 这样来声明而不是 const foo = () => { }',
|
|
611
|
+
loc: node.parent.parent.loc,
|
|
612
|
+
fix: fixer => fix(fixer, node)
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
// 规则太复杂了,暂时写不出来
|
|
619
|
+
// ... color ? [ ] : [
|
|
620
|
+
// '-c', 'color.status=false',
|
|
621
|
+
// '-c', 'color.ui=false',
|
|
622
|
+
// '-c', 'color.branch=false',
|
|
623
|
+
// '-c', 'color.ui=false',
|
|
624
|
+
// ],
|
|
625
|
+
// test ?
|
|
626
|
+
// ...
|
|
627
|
+
// : [ ]
|
|
628
|
+
// 'conditional-expresssion-style': {
|
|
629
|
+
// meta,
|
|
630
|
+
// create (context) {
|
|
631
|
+
// const source = context.sourceCode
|
|
632
|
+
// const lines = source.getLines()
|
|
633
|
+
// return {
|
|
634
|
+
// ConditionalExpression (node) {
|
|
635
|
+
// const question = source.getTokenAfter(node.test, not_closing_paren)
|
|
636
|
+
// const colon = source.getTokenAfter(node.consequent, not_closing_paren)
|
|
637
|
+
// const test_tail = source.getTokenBefore(question, { includeComments: true })
|
|
638
|
+
// const test_head = source.getFirstToken(node, { includeComments: true })
|
|
639
|
+
// const consequent_start = source.getTokenAfter(question, not_opening_paren)
|
|
640
|
+
// const consequent_tail = source.getTokenBefore(colon, not_closing_paren)
|
|
641
|
+
// const alternate_start = source.getTokenAfter(colon, not_opening_paren)
|
|
642
|
+
// const node_tail = source.getLastToken(node)
|
|
643
|
+
// // 三元运算符的 ? 应该和 test 条件末尾在同一行
|
|
644
|
+
// if (test_tail.loc.start.line !== question.loc.start.line)
|
|
645
|
+
// context.report({
|
|
646
|
+
// node,
|
|
647
|
+
// loc: {
|
|
648
|
+
// start: colon.loc.start,
|
|
649
|
+
// end: colon.loc.end
|
|
650
|
+
// },
|
|
651
|
+
// message: '条件表达式的 ? 应该和 test 条件末尾在同一行',
|
|
652
|
+
// fix: fixer => fixer.replaceTextRange([test_tail.range[1], colon.range[0]], ' ')
|
|
653
|
+
// })
|
|
654
|
+
// // 单行的条件表达式
|
|
655
|
+
// if (colon.loc.start.line === question.loc.start.line) {
|
|
656
|
+
// const alternate_start_with_comments = source.getTokenAfter(colon, { includeComments: true, filter: not_opening_paren })
|
|
657
|
+
// if (node_tail.loc.end.line !== colon.loc.start.line)
|
|
658
|
+
// context.report({
|
|
659
|
+
// node,
|
|
660
|
+
// loc: {
|
|
661
|
+
// start: node.alternate.loc.start,
|
|
662
|
+
// end: node_tail.loc.end
|
|
663
|
+
// },
|
|
664
|
+
// message: '单行条件表达式的 else 条件也应该在同一行',
|
|
665
|
+
// fix: fixer => fixer.replaceTextRange([colon.range[1], alternate_start_with_comments.range[0]], ' ')
|
|
666
|
+
// })
|
|
667
|
+
// } else { // 多行条件表达式
|
|
668
|
+
// if (consequent_tail.loc.end.line === colon.loc.start.line)
|
|
669
|
+
// context.report({
|
|
670
|
+
// node,
|
|
671
|
+
// loc: {
|
|
672
|
+
// start: consequent_tail.loc.start,
|
|
673
|
+
// end: colon.loc.start
|
|
674
|
+
// },
|
|
675
|
+
// message: '多行条件表达式的 : 应该在单独的一行',
|
|
676
|
+
// fix: fixer =>
|
|
677
|
+
// fixer.insertTextBefore(
|
|
678
|
+
// colon,
|
|
679
|
+
// '\n' +
|
|
680
|
+
// ' '.repeat(
|
|
681
|
+
// Math.max(split_indent(lines[consequent_tail.loc.end.line - 1]).indent - 4, 0)
|
|
682
|
+
// )
|
|
683
|
+
// )
|
|
684
|
+
// })
|
|
685
|
+
// if (
|
|
686
|
+
// alternate_start.loc.start.line === colon.loc.start.line &&
|
|
687
|
+
// (node.alternate.loc.start.line !== node.alternate.loc.end.line)
|
|
688
|
+
// )
|
|
689
|
+
// context.report({
|
|
690
|
+
// node,
|
|
691
|
+
// loc: {
|
|
692
|
+
// start: colon.loc.end,
|
|
693
|
+
// end: alternate_start.loc.start
|
|
694
|
+
// },
|
|
695
|
+
// message: '多行条件表达式的 : 应该在单独的一行',
|
|
696
|
+
// fix: fixer => fixer.insertTextBefore(
|
|
697
|
+
// alternate_start,
|
|
698
|
+
// '\n' +
|
|
699
|
+
// ' '.repeat(
|
|
700
|
+
// split_indent(lines[colon.loc.start.line - 1]).indent + 4
|
|
701
|
+
// )
|
|
702
|
+
// )
|
|
703
|
+
// })
|
|
704
|
+
// if (consequent_start.loc.start.line === question.loc.start.line)
|
|
705
|
+
// context.report({
|
|
706
|
+
// node,
|
|
707
|
+
// loc: {
|
|
708
|
+
// start: question.loc.end,
|
|
709
|
+
// end: test_head.loc.start
|
|
710
|
+
// },
|
|
711
|
+
// message: '多行条件表达式的 ? 这一行后面不应该有其他表达式,请换行',
|
|
712
|
+
// fix: fixer => fixer.insertTextBefore(
|
|
713
|
+
// consequent_start,
|
|
714
|
+
// '\n' +
|
|
715
|
+
// ' '.repeat(
|
|
716
|
+
// split_indent(lines[colon.loc.start.line - 1]).indent + 4
|
|
717
|
+
// )
|
|
718
|
+
// )
|
|
719
|
+
// })
|
|
720
|
+
// }
|
|
721
|
+
// }
|
|
722
|
+
// }
|
|
723
|
+
// }
|
|
724
|
+
// }
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
function split_indent(line) {
|
|
728
|
+
let i = 0;
|
|
729
|
+
let indent = 0;
|
|
730
|
+
for (; i < line.length; i++)
|
|
731
|
+
if (line[i] === ' ')
|
|
732
|
+
indent++;
|
|
733
|
+
else if (line[i] === '\t')
|
|
734
|
+
indent += 4;
|
|
735
|
+
else
|
|
736
|
+
break;
|
|
737
|
+
return {
|
|
738
|
+
indent,
|
|
739
|
+
text: line.slice(i)
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function not_opening_paren(token) {
|
|
743
|
+
return !(['(', '[', '{'].includes(token.value) && token.type === 'Punctuator');
|
|
744
|
+
}
|
|
745
|
+
function not_closing_paren(token) {
|
|
746
|
+
return !([')', ']', '}'].includes(token.value) && token.type === 'Punctuator');
|
|
747
|
+
}
|
|
748
|
+
//# sourceMappingURL=xlint.js.map
|
|
File without changes
|