xshell 1.3.40 → 1.3.41
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/antd.development.js +4216 -4044
- package/antd.development.js.map +1 -1
- package/antd.production.js +1124 -993
- package/antd.production.js.map +1 -1
- package/git.d.ts +4 -2
- package/git.js +26 -4
- package/package.json +11 -12
- package/path.d.ts +2 -2
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
- package/xlint.js +303 -13
package/xlint.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { builtinModules } from 'node:module';
|
|
1
3
|
import { AST_TOKEN_TYPES, ASTUtils, TSESTree } from '@typescript-eslint/utils';
|
|
2
4
|
import TSParser from '@typescript-eslint/parser';
|
|
3
5
|
import ts_plugin from '@typescript-eslint/eslint-plugin';
|
|
4
6
|
import react_plugin from 'eslint-plugin-react';
|
|
5
|
-
import import_plugin from 'eslint-plugin-import';
|
|
6
7
|
import stylistic_plugin from '@stylistic/eslint-plugin';
|
|
7
|
-
|
|
8
|
+
import { check } from "./utils.js";
|
|
9
|
+
import { path } from "./path.js";
|
|
10
|
+
// 用 ast explorer 选 esprima 来看
|
|
11
|
+
// 或者 https://explorer.eslint.org/
|
|
12
|
+
// node.loc.start.line 下标从 1 开始
|
|
8
13
|
const meta = {
|
|
9
14
|
fixable: 'whitespace',
|
|
10
15
|
type: 'layout',
|
|
11
16
|
};
|
|
12
|
-
const UNIONS = ['|', '&'];
|
|
13
|
-
// 用 ast explorer 选 esprima 来看
|
|
14
|
-
// node.loc.start.line 下标从 1 开始
|
|
15
17
|
export const xlint_plugin = {
|
|
16
18
|
rules: {
|
|
17
19
|
'fold-jsdoc-comments': {
|
|
18
20
|
meta,
|
|
19
|
-
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
create: (context) => ({
|
|
20
23
|
Program() {
|
|
21
|
-
for (const comment of context.sourceCode.
|
|
24
|
+
for (const comment of context.sourceCode.ast.comments)
|
|
22
25
|
if (comment.type === 'Block') {
|
|
23
26
|
const lines = comment.value.split(line_break_pattern);
|
|
24
27
|
if (lines.length >= 3 &&
|
|
@@ -30,7 +33,8 @@ export const xlint_plugin = {
|
|
|
30
33
|
start: comment.loc.start,
|
|
31
34
|
end: comment.loc.end
|
|
32
35
|
},
|
|
33
|
-
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
message: 'jsdoc 注释格式错误',
|
|
34
38
|
fix(fixer) {
|
|
35
39
|
const indent = ' '.repeat(comment.loc.start.column);
|
|
36
40
|
const indenter = (line) => line.replace(/^\s*\*\s?/u, `${indent} `);
|
|
@@ -55,11 +59,13 @@ export const xlint_plugin = {
|
|
|
55
59
|
// body
|
|
56
60
|
'nonblock-statement-body-position-with-indentation': {
|
|
57
61
|
meta,
|
|
62
|
+
// @ts-ignore
|
|
58
63
|
create(context) {
|
|
59
64
|
const source = context.sourceCode;
|
|
60
65
|
const lines = source.getLines();
|
|
61
66
|
function lint(body, node) {
|
|
62
67
|
if (body.type !== 'BlockStatement') {
|
|
68
|
+
// @ts-ignore
|
|
63
69
|
const token_before = source.getTokenBefore(body);
|
|
64
70
|
let indent = 0;
|
|
65
71
|
const line = lines[node.loc.start.line - 1];
|
|
@@ -73,6 +79,7 @@ export const xlint_plugin = {
|
|
|
73
79
|
if (token_before.loc.end.line === body.loc.start.line ||
|
|
74
80
|
body.loc.start.column !== indent + 4)
|
|
75
81
|
context.report({
|
|
82
|
+
// @ts-ignore
|
|
76
83
|
node: body,
|
|
77
84
|
message: 'Expected a linebreak before this statement.',
|
|
78
85
|
fix: fixer => fixer.replaceTextRange([token_before.range[1], body.range[0]], '\n' + ' '.repeat(indent + 4))
|
|
@@ -99,6 +106,7 @@ export const xlint_plugin = {
|
|
|
99
106
|
// function foo () { }
|
|
100
107
|
'empty-bracket-spacing': {
|
|
101
108
|
meta,
|
|
109
|
+
// @ts-ignore
|
|
102
110
|
create(context) {
|
|
103
111
|
const source = context.sourceCode;
|
|
104
112
|
return {
|
|
@@ -107,6 +115,7 @@ export const xlint_plugin = {
|
|
|
107
115
|
if (node.properties.length === 0 && source.getText(node) === '{}')
|
|
108
116
|
context.report({
|
|
109
117
|
node,
|
|
118
|
+
// @ts-ignore
|
|
110
119
|
message: 'Expected a space in this empty object.',
|
|
111
120
|
fix: fixer => fixer.replaceText(node, '{ }')
|
|
112
121
|
});
|
|
@@ -116,6 +125,7 @@ export const xlint_plugin = {
|
|
|
116
125
|
if (node.elements.length === 0 && source.getText(node) === '[]')
|
|
117
126
|
context.report({
|
|
118
127
|
node,
|
|
128
|
+
// @ts-ignore
|
|
119
129
|
message: 'Expected a space in this empty array.',
|
|
120
130
|
fix: fixer => fixer.replaceText(node, '[ ]')
|
|
121
131
|
});
|
|
@@ -124,6 +134,7 @@ export const xlint_plugin = {
|
|
|
124
134
|
if (node.body.length === 0 && source.getText(node) === '{}')
|
|
125
135
|
context.report({
|
|
126
136
|
node,
|
|
137
|
+
// @ts-ignore
|
|
127
138
|
message: 'Expected a space in this empty block.',
|
|
128
139
|
fix: fixer => fixer.replaceText(node, '{ }')
|
|
129
140
|
});
|
|
@@ -318,7 +329,7 @@ export const xlint_plugin = {
|
|
|
318
329
|
types.forEach(type => {
|
|
319
330
|
const skipFunctionParenthesis = type.type === TSESTree.AST_NODE_TYPES.TSFunctionType ? ASTUtils.isNotOpeningBraceToken : 0;
|
|
320
331
|
const operator = sourceCode.getTokenBefore(type, skipFunctionParenthesis);
|
|
321
|
-
if (operator &&
|
|
332
|
+
if (operator && unions.includes(operator.value)) {
|
|
322
333
|
const prev = sourceCode.getTokenBefore(operator);
|
|
323
334
|
const next = sourceCode.getTokenAfter(operator);
|
|
324
335
|
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next))
|
|
@@ -354,11 +365,12 @@ export const xlint_plugin = {
|
|
|
354
365
|
}
|
|
355
366
|
},
|
|
356
367
|
// for (;;)
|
|
357
|
-
// 1
|
|
368
|
+
// 1
|
|
358
369
|
// for (let i = 0; i < 10; i++)
|
|
359
370
|
// for ( ; i < 10; )
|
|
360
371
|
'space-in-for-statement': {
|
|
361
372
|
meta,
|
|
373
|
+
// @ts-ignore
|
|
362
374
|
create(context) {
|
|
363
375
|
const source = context.sourceCode;
|
|
364
376
|
return {
|
|
@@ -382,6 +394,7 @@ export const xlint_plugin = {
|
|
|
382
394
|
start: semi0_prev.loc.end,
|
|
383
395
|
end: semi0.loc.start
|
|
384
396
|
},
|
|
397
|
+
// @ts-ignore
|
|
385
398
|
message: 'for 语句第一个分号前面应该没有多余空格',
|
|
386
399
|
node,
|
|
387
400
|
fix: fixer => fixer.removeRange([semi0_prev.range[1], semi0.range[0]])
|
|
@@ -395,6 +408,7 @@ export const xlint_plugin = {
|
|
|
395
408
|
start: semi0_prev.loc.end,
|
|
396
409
|
end: semi0.loc.start
|
|
397
410
|
},
|
|
411
|
+
// @ts-ignore
|
|
398
412
|
message: 'for 语句第一个分号前面应该为 2 个空格',
|
|
399
413
|
node,
|
|
400
414
|
fix: fixer => fixer.replaceTextRange([semi0_prev.range[1], semi0.range[0]], ' ')
|
|
@@ -407,6 +421,7 @@ export const xlint_plugin = {
|
|
|
407
421
|
start: semi0.loc.end,
|
|
408
422
|
end: semi0_next.loc.start
|
|
409
423
|
},
|
|
424
|
+
// @ts-ignore
|
|
410
425
|
message: 'for 语句第一个分号后面应为 2 个空格',
|
|
411
426
|
node,
|
|
412
427
|
fix: fixer => fixer.replaceTextRange([semi0.range[1], semi0_next.range[0]], ' ')
|
|
@@ -421,6 +436,7 @@ export const xlint_plugin = {
|
|
|
421
436
|
start: test_end.loc.end,
|
|
422
437
|
end: semi1.loc.start,
|
|
423
438
|
},
|
|
439
|
+
// @ts-ignore
|
|
424
440
|
message: 'for 语句第二个分号前面没有多余的空格',
|
|
425
441
|
node,
|
|
426
442
|
fix: fixer => fixer.removeRange([test_end.range[1], semi1.range[0]])
|
|
@@ -433,6 +449,7 @@ export const xlint_plugin = {
|
|
|
433
449
|
start: semi1.loc.end,
|
|
434
450
|
end: semi1_next.loc.start
|
|
435
451
|
},
|
|
452
|
+
// @ts-ignore
|
|
436
453
|
message: 'for 语句第二个分号后面应为 2 个空格',
|
|
437
454
|
node,
|
|
438
455
|
fix: fixer => fixer.replaceTextRange([semi1.range[1], semi1_next.range[0]], ' ')
|
|
@@ -446,6 +463,7 @@ export const xlint_plugin = {
|
|
|
446
463
|
start: semi0_prev.loc.end,
|
|
447
464
|
end: semi0.loc.start
|
|
448
465
|
},
|
|
466
|
+
// @ts-ignore
|
|
449
467
|
message: '无条件 for 语句第一个分号前面应该没有空格',
|
|
450
468
|
node,
|
|
451
469
|
fix: fixer => fixer.removeRange([semi0_prev.range[1], semi0.range[0]])
|
|
@@ -457,6 +475,7 @@ export const xlint_plugin = {
|
|
|
457
475
|
start: semi0.loc.end,
|
|
458
476
|
end: semi0_next.loc.start
|
|
459
477
|
},
|
|
478
|
+
// @ts-ignore
|
|
460
479
|
message: '无条件 for 语句第一个分号后面应该没有空格',
|
|
461
480
|
node,
|
|
462
481
|
fix: fixer => fixer.removeRange([semi0.range[1], semi0_next.range[0]])
|
|
@@ -468,12 +487,13 @@ export const xlint_plugin = {
|
|
|
468
487
|
start: semi1.loc.end,
|
|
469
488
|
end: semi1_next.loc.start
|
|
470
489
|
},
|
|
490
|
+
// @ts-ignore
|
|
471
491
|
message: '无条件 for 语句第二个分号后面应该没有空格',
|
|
472
492
|
node,
|
|
473
493
|
fix: fixer => fixer.removeRange([semi1.range[1], semi1_next.range[0]])
|
|
474
494
|
});
|
|
475
495
|
}
|
|
476
|
-
}
|
|
496
|
+
}
|
|
477
497
|
};
|
|
478
498
|
}
|
|
479
499
|
},
|
|
@@ -484,6 +504,7 @@ export const xlint_plugin = {
|
|
|
484
504
|
// return <div>1234</div>
|
|
485
505
|
'jsx-no-redundant-parenthesis-in-return': {
|
|
486
506
|
meta,
|
|
507
|
+
// @ts-ignore
|
|
487
508
|
create(context) {
|
|
488
509
|
const source = context.sourceCode;
|
|
489
510
|
return {
|
|
@@ -500,6 +521,7 @@ export const xlint_plugin = {
|
|
|
500
521
|
start: paren0.loc.start,
|
|
501
522
|
end: paren1.loc.end
|
|
502
523
|
},
|
|
524
|
+
// @ts-ignore
|
|
503
525
|
message: 'return jsx 表达式不应该有多余的括号',
|
|
504
526
|
node,
|
|
505
527
|
fix: fixer => ([
|
|
@@ -523,6 +545,7 @@ export const xlint_plugin = {
|
|
|
523
545
|
start: paren0.loc.start,
|
|
524
546
|
end: paren1.loc.end
|
|
525
547
|
},
|
|
548
|
+
// @ts-ignore
|
|
526
549
|
message: 'return jsx 表达式不应该有多余的括号',
|
|
527
550
|
node,
|
|
528
551
|
fix: fixer => ([
|
|
@@ -538,6 +561,7 @@ export const xlint_plugin = {
|
|
|
538
561
|
},
|
|
539
562
|
'keep-indent': {
|
|
540
563
|
meta,
|
|
564
|
+
// @ts-ignore
|
|
541
565
|
create(context) {
|
|
542
566
|
const source = context.sourceCode;
|
|
543
567
|
const lines = source.getLines();
|
|
@@ -554,6 +578,7 @@ export const xlint_plugin = {
|
|
|
554
578
|
const node = source.getNodeByRangeIndex(index);
|
|
555
579
|
if (node && node.type !== 'Program')
|
|
556
580
|
context.report({
|
|
581
|
+
// @ts-ignore
|
|
557
582
|
message: '空行应保留与上一个区块相同的缩进',
|
|
558
583
|
loc: { start: pos, end: pos },
|
|
559
584
|
fix: fixer => fixer.replaceTextRange([index, index], ' '.repeat(last_line.indent))
|
|
@@ -568,15 +593,20 @@ export const xlint_plugin = {
|
|
|
568
593
|
},
|
|
569
594
|
'func-style': {
|
|
570
595
|
meta,
|
|
596
|
+
// @ts-ignore
|
|
571
597
|
create(context) {
|
|
572
598
|
const source = context.sourceCode;
|
|
573
599
|
let stack = [];
|
|
574
|
-
const fix = (fixer, node) => fixer.replaceText(
|
|
600
|
+
const fix = (fixer, node) => fixer.replaceText(
|
|
601
|
+
// @ts-ignore
|
|
602
|
+
node.parent.parent, `${node.async ? 'async ' : ''}function ${node.generator ? '* ' : ''}` +
|
|
575
603
|
// todo: 丢失了泛型参数 const foo = <D = Zippable> (xxx: D) => { },如何加回来?
|
|
576
604
|
node.parent.id.name +
|
|
577
605
|
' (' +
|
|
606
|
+
// @ts-ignore
|
|
578
607
|
node.params.map(param => source.getText(param)).join(', ') +
|
|
579
608
|
') ' +
|
|
609
|
+
// @ts-ignore
|
|
580
610
|
source.getText(node.body));
|
|
581
611
|
return {
|
|
582
612
|
FunctionDeclaration(node) {
|
|
@@ -592,6 +622,7 @@ export const xlint_plugin = {
|
|
|
592
622
|
node.parent.parent.declarations.length === 1)
|
|
593
623
|
context.report({
|
|
594
624
|
node: node.parent.parent,
|
|
625
|
+
// @ts-ignore
|
|
595
626
|
message: '函数应该使用 function foo { } 这样来声明而不是 const foo = function () { }',
|
|
596
627
|
loc: node.parent.parent.loc,
|
|
597
628
|
fix: fixer => fix(fixer, node)
|
|
@@ -616,6 +647,7 @@ export const xlint_plugin = {
|
|
|
616
647
|
node.body.type === 'BlockStatement' && node.body.body.length)
|
|
617
648
|
context.report({
|
|
618
649
|
node: node.parent.parent,
|
|
650
|
+
// @ts-ignore
|
|
619
651
|
message: '多条语句的函数应该使用 function foo { } 这样来声明而不是 const foo = () => { }',
|
|
620
652
|
loc: node.parent.parent.loc,
|
|
621
653
|
fix: fixer => fix(fixer, node)
|
|
@@ -624,6 +656,109 @@ export const xlint_plugin = {
|
|
|
624
656
|
};
|
|
625
657
|
}
|
|
626
658
|
},
|
|
659
|
+
// 读取文件中所有的 ImportDeclaration,每个 decl 包括前面的注释,收集起来
|
|
660
|
+
// 检查这些 decl 是连续的,即中间没有其他语句(中间有语句分割的,需要手动修复)
|
|
661
|
+
// 检查、解析 decl 中的 source (from '...' 中的 ...) 做解析和替换:
|
|
662
|
+
// 处理 alias, 相对路径,区分外部包,解析 .ts, .tsx
|
|
663
|
+
// 排序 decl
|
|
664
|
+
// 在 Program:exit 的时候,检查是否有顺序不一致的,一旦有,就通过 replaceTextRange 用已排序的 decls 重新生成替换
|
|
665
|
+
import: {
|
|
666
|
+
meta,
|
|
667
|
+
// @ts-ignore
|
|
668
|
+
create(context) {
|
|
669
|
+
let imports = [];
|
|
670
|
+
// 检查、解析 decl 中的 source (from '...' 中的 ...) 做解析和替换
|
|
671
|
+
// 这里不能用异步操作,很恶心
|
|
672
|
+
const cwd = context.cwd.fpd;
|
|
673
|
+
const pkgs = get_packages(cwd);
|
|
674
|
+
const aliases = get_tsconfig_aliases(cwd);
|
|
675
|
+
// console.log('pkgs:', pkgs)
|
|
676
|
+
// console.log('aliases:', aliases)
|
|
677
|
+
let need_fix_source = false;
|
|
678
|
+
return {
|
|
679
|
+
// 格式化 source, 收集 decl
|
|
680
|
+
ImportDeclaration(node) {
|
|
681
|
+
const source = node.source.value;
|
|
682
|
+
const [type, source_] = format_source(pkgs, aliases, cwd, context.filename.fp, source);
|
|
683
|
+
if (!source_ || source_ === source) {
|
|
684
|
+
imports.push({
|
|
685
|
+
node,
|
|
686
|
+
type,
|
|
687
|
+
style: style_fexts.has(source.fext)
|
|
688
|
+
});
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
need_fix_source = true;
|
|
692
|
+
console.log(`自动修复 ${source} -> ${source_}`);
|
|
693
|
+
context.report({
|
|
694
|
+
// @ts-ignore
|
|
695
|
+
message: `import source ${source} 应该改为 ${source_}`,
|
|
696
|
+
node,
|
|
697
|
+
loc: node.source.loc,
|
|
698
|
+
fix: fixer => fixer.replaceTextRange(node.source.range, source_.quote())
|
|
699
|
+
});
|
|
700
|
+
},
|
|
701
|
+
// 排序 decl 整体替换修复
|
|
702
|
+
'Program:exit'() {
|
|
703
|
+
if (need_fix_source || imports.length <= 1)
|
|
704
|
+
return;
|
|
705
|
+
const { sourceCode: source } = context;
|
|
706
|
+
const { body } = source.ast;
|
|
707
|
+
const first = imports[0].node;
|
|
708
|
+
const ifirst = body.indexOf(first);
|
|
709
|
+
if (body.indexOf(imports.last.node, ifirst + 1) - ifirst !== imports.length - 1) {
|
|
710
|
+
context.report({
|
|
711
|
+
// @ts-ignore
|
|
712
|
+
message: 'import 语句不连续,中间有其他语句,无法自动排序修复,需手动修复',
|
|
713
|
+
node: first
|
|
714
|
+
});
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
// 检查顺序并修复: 样式 xxx.sass < nodejs builtin < package < alias < subpath
|
|
718
|
+
// 如果为样式,则放前面,其他的就按照数组来
|
|
719
|
+
let imports_ = imports.toSorted((a, b) => {
|
|
720
|
+
if (a.style !== b.style)
|
|
721
|
+
return a.style ? -1 : 1;
|
|
722
|
+
return imtypes.indexOf(a.type) - imtypes.indexOf(b.type);
|
|
723
|
+
});
|
|
724
|
+
for (let i = 0; i < imports.length; i++) {
|
|
725
|
+
let im = imports[i];
|
|
726
|
+
let im_ = imports_[i];
|
|
727
|
+
if (im.node === im_.node)
|
|
728
|
+
continue;
|
|
729
|
+
context.report({
|
|
730
|
+
// @ts-ignore
|
|
731
|
+
message: `第 ${i} 个 import 顺序不对`,
|
|
732
|
+
node: im.node,
|
|
733
|
+
fix(fixer) {
|
|
734
|
+
let _start = Number.MAX_SAFE_INTEGER, _end = 0;
|
|
735
|
+
let type;
|
|
736
|
+
// 收集所有排序后的 import 文本
|
|
737
|
+
const texts = imports_.map((im, i) => {
|
|
738
|
+
const [start, end] = get_range_with_comments(source, im.node);
|
|
739
|
+
if (start < _start)
|
|
740
|
+
_start = start;
|
|
741
|
+
if (end > _end)
|
|
742
|
+
_end = end;
|
|
743
|
+
let text = source.text.slice(start, end);
|
|
744
|
+
// 分组之间空一行
|
|
745
|
+
if (im.type !== type) {
|
|
746
|
+
if (type)
|
|
747
|
+
text = '\n' + text;
|
|
748
|
+
type = im.type;
|
|
749
|
+
}
|
|
750
|
+
return text;
|
|
751
|
+
});
|
|
752
|
+
// 一次性替换所有 import
|
|
753
|
+
return fixer.replaceTextRange([_start, _end], texts.join_lines());
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
}
|
|
627
762
|
// 规则太复杂了,暂时写不出来
|
|
628
763
|
// ... color ? [ ] : [
|
|
629
764
|
// '-c', 'color.status=false',
|
|
@@ -755,7 +890,6 @@ export const xlint_config = {
|
|
|
755
890
|
plugins: {
|
|
756
891
|
'@typescript-eslint': ts_plugin,
|
|
757
892
|
react: react_plugin,
|
|
758
|
-
import: import_plugin,
|
|
759
893
|
'@stylistic': stylistic_plugin,
|
|
760
894
|
xlint: xlint_plugin,
|
|
761
895
|
},
|
|
@@ -889,6 +1023,9 @@ export const xlint_config = {
|
|
|
889
1023
|
]
|
|
890
1024
|
}
|
|
891
1025
|
};
|
|
1026
|
+
const line_break_pattern = /\r\n|[\r\n\u2028\u2029]/u;
|
|
1027
|
+
const style_fexts = new Set(['sass', 'css', 'scss', 'less', 'styl']);
|
|
1028
|
+
const unions = ['|', '&'];
|
|
892
1029
|
function split_indent(line) {
|
|
893
1030
|
let i = 0;
|
|
894
1031
|
let indent = 0;
|
|
@@ -910,4 +1047,157 @@ function not_opening_paren(token) {
|
|
|
910
1047
|
function not_closing_paren(token) {
|
|
911
1048
|
return !([')', ']', '}'].includes(token.value) && token.type === 'Punctuator');
|
|
912
1049
|
}
|
|
1050
|
+
function all_space(str, len = str.length) {
|
|
1051
|
+
for (let i = 0; i < len; ++i)
|
|
1052
|
+
if (str[i] !== ' ')
|
|
1053
|
+
return false;
|
|
1054
|
+
return true;
|
|
1055
|
+
}
|
|
1056
|
+
/** 获取节点前面的注释,并计算包含注释的范围 */
|
|
1057
|
+
function get_range_with_comments(source, node) {
|
|
1058
|
+
let [start, end] = node.range;
|
|
1059
|
+
// 获取节点前的所有注释
|
|
1060
|
+
let comments = source.getCommentsBefore(node);
|
|
1061
|
+
if (comments.length !== 0) {
|
|
1062
|
+
// 从最后一个注释往前找,遇到属于上一条语句的行尾注释或非注释时退出
|
|
1063
|
+
// 判断标准:独立注释以多个空格 + // 或者 /* 开头
|
|
1064
|
+
// 行尾注释:// 前面含有非空格
|
|
1065
|
+
let valid_comment;
|
|
1066
|
+
for (let i = comments.length - 1; i >= 0; --i) {
|
|
1067
|
+
let comment = comments[i];
|
|
1068
|
+
// 获取 comment 所在行的内容
|
|
1069
|
+
let line = source.lines[comment.loc.start.line - 1];
|
|
1070
|
+
// 找到注释开始标记 // 或 /* 在行中的位置
|
|
1071
|
+
let istart;
|
|
1072
|
+
if (comment.type === 'Line')
|
|
1073
|
+
istart = line.indexOf('//');
|
|
1074
|
+
else // Block
|
|
1075
|
+
istart = line.indexOf('/*');
|
|
1076
|
+
// 注释标记之前是否全是空格
|
|
1077
|
+
if (!all_space(line.slice(0, istart)))
|
|
1078
|
+
break;
|
|
1079
|
+
valid_comment = comment;
|
|
1080
|
+
}
|
|
1081
|
+
// 如果有有效的注释,更新 start
|
|
1082
|
+
if (valid_comment)
|
|
1083
|
+
start = valid_comment.range[0];
|
|
1084
|
+
}
|
|
1085
|
+
let comments_ = source.getCommentsAfter(node);
|
|
1086
|
+
// 和导入在同一行,在尾部的注释
|
|
1087
|
+
if (comments_[0]?.loc.start.line === node.loc.end.line)
|
|
1088
|
+
end = comments_[0].range[1];
|
|
1089
|
+
return [start, end];
|
|
1090
|
+
}
|
|
1091
|
+
let cache_alias_cwd;
|
|
1092
|
+
let cache_alias;
|
|
1093
|
+
/** 解析 tsconfig.json 中的 paths 配置
|
|
1094
|
+
返回 `{ alias: string, fp: string }[]`
|
|
1095
|
+
- alias: 导入别名前缀(如 `@lib/`)
|
|
1096
|
+
- fp: 对应的路径前缀(如 `./lib/`)
|
|
1097
|
+
|
|
1098
|
+
处理规则:
|
|
1099
|
+
- value 数组必须只有一项
|
|
1100
|
+
- 如果 key 和 value 都以 `/*` 结尾,去掉 `*`
|
|
1101
|
+
- 否则 value 就是具体文件路径 */
|
|
1102
|
+
function get_tsconfig_aliases(cwd) {
|
|
1103
|
+
if (cwd === cache_alias_cwd)
|
|
1104
|
+
return cache_alias;
|
|
1105
|
+
// 读取文件内容,移除 // 行注释
|
|
1106
|
+
const { compilerOptions } = JSON.parse(fs.readFileSync(`${cwd}tsconfig.json`, 'utf-8')
|
|
1107
|
+
.split_lines()
|
|
1108
|
+
.filter(line => !line.trimStart().startsWith('//'))
|
|
1109
|
+
.join_lines());
|
|
1110
|
+
const paths = compilerOptions?.paths;
|
|
1111
|
+
if (!paths)
|
|
1112
|
+
return [];
|
|
1113
|
+
const aliases = Object.entries(paths).map(([key, values]) => {
|
|
1114
|
+
// 检查 value 数组只有一项
|
|
1115
|
+
check(values?.length === 1, `paths 的 value 数组必须只有一项`);
|
|
1116
|
+
const value = values[0];
|
|
1117
|
+
// key 和 value 都以 /* 结尾,去掉 *
|
|
1118
|
+
if (key.endsWith('/*') && value.endsWith('/*'))
|
|
1119
|
+
return {
|
|
1120
|
+
alias: key.slice(0, -1), // @lib/* -> @lib/
|
|
1121
|
+
fp: value.slice(0, -1).strip_if_start('./') // ./lib/* -> lib/
|
|
1122
|
+
};
|
|
1123
|
+
// 具体文件路径的情况
|
|
1124
|
+
return {
|
|
1125
|
+
alias: key,
|
|
1126
|
+
fp: value.strip_if_start('./')
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1129
|
+
cache_alias_cwd = cwd;
|
|
1130
|
+
return cache_alias = aliases;
|
|
1131
|
+
}
|
|
1132
|
+
let cache_pkgs_cwd;
|
|
1133
|
+
let cache_pkgs;
|
|
1134
|
+
function get_packages(cwd) {
|
|
1135
|
+
if (cache_pkgs_cwd === cwd)
|
|
1136
|
+
return cache_pkgs;
|
|
1137
|
+
const { dependencies = {}, devDependencies = {} } = JSON.parse(fs.readFileSync(`${cwd}package.json`, 'utf-8'));
|
|
1138
|
+
const pkgs = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
|
|
1139
|
+
cache_pkgs_cwd = cwd;
|
|
1140
|
+
return cache_pkgs = pkgs;
|
|
1141
|
+
}
|
|
1142
|
+
/** import types */
|
|
1143
|
+
const imtypes = ['builtin', 'package', 'alias', 'subpath'];
|
|
1144
|
+
/** 将一个有后缀或无后缀的文件,解析到实际文件,并通过 alias 简化 */
|
|
1145
|
+
function format_source(pkgs, aliases, cwd, fp, source) {
|
|
1146
|
+
check(path.isAbsolute(fp), 'fp 应该是绝对路径');
|
|
1147
|
+
if (source.startsWith('node:'))
|
|
1148
|
+
return ['builtin'];
|
|
1149
|
+
if (builtins.has(source))
|
|
1150
|
+
return ['builtin', `node:${source}`];
|
|
1151
|
+
const pkg = pkgs.find(p => source === p || source.startsWith(p) && source[p.length] === '/');
|
|
1152
|
+
if (pkg) {
|
|
1153
|
+
if (pkgs.includes(pkg) || source.fext)
|
|
1154
|
+
return ['package'];
|
|
1155
|
+
// package.json 中有 exports 的,也允许无后缀(可能 .js 没有导出)
|
|
1156
|
+
const pkg_json = JSON.parse(fs.readFileSync(`${cwd}node_modules/${pkg}/package.json`, 'utf-8'));
|
|
1157
|
+
if (pkg_json.exports)
|
|
1158
|
+
return ['package'];
|
|
1159
|
+
return ['package', `${source}.js`];
|
|
1160
|
+
}
|
|
1161
|
+
// 剩下的全是内部路径
|
|
1162
|
+
// 相对子路径
|
|
1163
|
+
if (source.startsWith('./')) {
|
|
1164
|
+
const { fext } = source;
|
|
1165
|
+
// 已经完美了
|
|
1166
|
+
if (fext && fext !== 'js')
|
|
1167
|
+
return ['subpath'];
|
|
1168
|
+
source = source.strip_if_end('.js');
|
|
1169
|
+
return ['subpath', `${source}.${resolve_suffix(path.resolve(fp.fdir, source))}`];
|
|
1170
|
+
}
|
|
1171
|
+
// ../ 父级路径,或者 @/... 等 alias 路径
|
|
1172
|
+
// 先 resolve 到完整路径,再修复后缀,最后通过 alias 简化
|
|
1173
|
+
const alias = aliases.find(({ alias }) => source.startsWith(alias));
|
|
1174
|
+
if (alias)
|
|
1175
|
+
source = cwd + alias.fp + source.strip_start(alias.alias);
|
|
1176
|
+
// 是完整路径了
|
|
1177
|
+
source = path.resolve(fp.fdir, source);
|
|
1178
|
+
const { fext } = source;
|
|
1179
|
+
if (!fext || fext === 'js') {
|
|
1180
|
+
source = source.strip_if_end('.js');
|
|
1181
|
+
source = `${source}.${resolve_suffix(source)}`;
|
|
1182
|
+
}
|
|
1183
|
+
const relative = source.strip_start(cwd, true);
|
|
1184
|
+
const alias_ = aliases.find(({ fp }) => relative.startsWith(fp));
|
|
1185
|
+
check(alias_, '一定能找到匹配的 alias');
|
|
1186
|
+
return ['alias', alias_.alias + relative.strip_start(alias_.fp)];
|
|
1187
|
+
}
|
|
1188
|
+
let cache_exists = new Map();
|
|
1189
|
+
function resolve_suffix(fp_base) {
|
|
1190
|
+
for (const suffix of ['ts', 'tsx']) {
|
|
1191
|
+
const fp = `${fp_base}.${suffix}`;
|
|
1192
|
+
const c = cache_exists.get(fp);
|
|
1193
|
+
if (c !== undefined)
|
|
1194
|
+
return suffix;
|
|
1195
|
+
const e = fs.existsSync(fp);
|
|
1196
|
+
cache_exists.set(fp, e);
|
|
1197
|
+
if (e)
|
|
1198
|
+
return suffix;
|
|
1199
|
+
}
|
|
1200
|
+
throw new Error(`无法解析 ${fp_base} 到文件`);
|
|
1201
|
+
}
|
|
1202
|
+
const builtins = new Set(builtinModules);
|
|
913
1203
|
//# sourceMappingURL=xlint.js.map
|