xshell 1.0.85 → 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.
Files changed (3) hide show
  1. package/package.json +12 -8
  2. package/xlint.d.ts +108 -0
  3. package/xlint.js +748 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.0.85",
3
+ "version": "1.0.86",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -53,11 +53,12 @@
53
53
  ]
54
54
  },
55
55
  "dependencies": {
56
- "@babel/core": "^7.24.3",
57
- "@babel/parser": "^7.24.1",
56
+ "@babel/core": "^7.24.4",
57
+ "@babel/parser": "^7.24.4",
58
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
63
  "archiver": "^7.0.1",
63
64
  "byte-size": "^8.1.1",
@@ -87,9 +88,9 @@
87
88
  "through2": "^4.0.2",
88
89
  "tough-cookie": "^4.1.3",
89
90
  "tslib": "^2.6.2",
90
- "typescript": "^5.4.3",
91
+ "typescript": "^5.4.4",
91
92
  "ua-parser-js": "2.0.0-alpha.2",
92
- "undici": "^6.10.1",
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,19 +106,22 @@
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
115
  "@types/lodash": "^4.17.0",
113
116
  "@types/mime-types": "^2.1.4",
114
- "@types/node": "^20.11.30",
115
- "@types/react": "^18.2.70",
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.87.0"
123
+ "@types/vscode": "^1.88.0",
124
+ "eslint": "^9.0.0"
121
125
  },
122
126
  "pnpm": {
123
127
  "patchedDependencies": {
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