xshell 1.3.40 → 1.3.42

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/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
- const line_break_pattern = /\r\n|[\r\n\u2028\u2029]/u;
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
- create: context => ({
21
+ // @ts-ignore
22
+ create: (context) => ({
20
23
  Program() {
21
- for (const comment of context.sourceCode.getAllComments())
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
- message: 'Redundant jsdoc comment.',
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 && UNIONS.includes(operator.value)) {
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(node.parent.parent, `${node.async ? 'async ' : ''}function ${node.generator ? '* ' : ''}` +
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,113 +656,109 @@ export const xlint_plugin = {
624
656
  };
625
657
  }
626
658
  },
627
- // 规则太复杂了,暂时写不出来
628
- // ... color ? [ ] : [
629
- // '-c', 'color.status=false',
630
- // '-c', 'color.ui=false',
631
- // '-c', 'color.branch=false',
632
- // '-c', 'color.ui=false',
633
- // ],
634
- // test ?
635
- // ...
636
- // : [ ]
637
- // 'conditional-expresssion-style': {
638
- // meta,
639
- // create (context) {
640
- // const source = context.sourceCode
641
- // const lines = source.getLines()
642
- // return {
643
- // ConditionalExpression (node) {
644
- // const question = source.getTokenAfter(node.test, not_closing_paren)
645
- // const colon = source.getTokenAfter(node.consequent, not_closing_paren)
646
- // const test_tail = source.getTokenBefore(question, { includeComments: true })
647
- // const test_head = source.getFirstToken(node, { includeComments: true })
648
- // const consequent_start = source.getTokenAfter(question, not_opening_paren)
649
- // const consequent_tail = source.getTokenBefore(colon, not_closing_paren)
650
- // const alternate_start = source.getTokenAfter(colon, not_opening_paren)
651
- // const node_tail = source.getLastToken(node)
652
- // // 三元运算符的 ? 应该和 test 条件末尾在同一行
653
- // if (test_tail.loc.start.line !== question.loc.start.line)
654
- // context.report({
655
- // node,
656
- // loc: {
657
- // start: colon.loc.start,
658
- // end: colon.loc.end
659
- // },
660
- // message: '条件表达式的 ? 应该和 test 条件末尾在同一行',
661
- // fix: fixer => fixer.replaceTextRange([test_tail.range[1], colon.range[0]], ' ')
662
- // })
663
- // // 单行的条件表达式
664
- // if (colon.loc.start.line === question.loc.start.line) {
665
- // const alternate_start_with_comments = source.getTokenAfter(colon, { includeComments: true, filter: not_opening_paren })
666
- // if (node_tail.loc.end.line !== colon.loc.start.line)
667
- // context.report({
668
- // node,
669
- // loc: {
670
- // start: node.alternate.loc.start,
671
- // end: node_tail.loc.end
672
- // },
673
- // message: '单行条件表达式的 else 条件也应该在同一行',
674
- // fix: fixer => fixer.replaceTextRange([colon.range[1], alternate_start_with_comments.range[0]], ' ')
675
- // })
676
- // } else { // 多行条件表达式
677
- // if (consequent_tail.loc.end.line === colon.loc.start.line)
678
- // context.report({
679
- // node,
680
- // loc: {
681
- // start: consequent_tail.loc.start,
682
- // end: colon.loc.start
683
- // },
684
- // message: '多行条件表达式的 : 应该在单独的一行',
685
- // fix: fixer =>
686
- // fixer.insertTextBefore(
687
- // colon,
688
- // '\n' +
689
- // ' '.repeat(
690
- // Math.max(split_indent(lines[consequent_tail.loc.end.line - 1]).indent - 4, 0)
691
- // )
692
- // )
693
- // })
694
- // if (
695
- // alternate_start.loc.start.line === colon.loc.start.line &&
696
- // (node.alternate.loc.start.line !== node.alternate.loc.end.line)
697
- // )
698
- // context.report({
699
- // node,
700
- // loc: {
701
- // start: colon.loc.end,
702
- // end: alternate_start.loc.start
703
- // },
704
- // message: '多行条件表达式的 : 应该在单独的一行',
705
- // fix: fixer => fixer.insertTextBefore(
706
- // alternate_start,
707
- // '\n' +
708
- // ' '.repeat(
709
- // split_indent(lines[colon.loc.start.line - 1]).indent + 4
710
- // )
711
- // )
712
- // })
713
- // if (consequent_start.loc.start.line === question.loc.start.line)
714
- // context.report({
715
- // node,
716
- // loc: {
717
- // start: question.loc.end,
718
- // end: test_head.loc.start
719
- // },
720
- // message: '多行条件表达式的 ? 这一行后面不应该有其他表达式,请换行',
721
- // fix: fixer => fixer.insertTextBefore(
722
- // consequent_start,
723
- // '\n' +
724
- // ' '.repeat(
725
- // split_indent(lines[colon.loc.start.line - 1]).indent + 4
726
- // )
727
- // )
728
- // })
729
- // }
730
- // }
731
- // }
732
- // }
733
- // }
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
+ }
734
762
  }
735
763
  };
736
764
  export const xlint_config = {
@@ -755,7 +783,6 @@ export const xlint_config = {
755
783
  plugins: {
756
784
  '@typescript-eslint': ts_plugin,
757
785
  react: react_plugin,
758
- import: import_plugin,
759
786
  '@stylistic': stylistic_plugin,
760
787
  xlint: xlint_plugin,
761
788
  },
@@ -776,6 +803,7 @@ export const xlint_config = {
776
803
  'xlint/keep-indent': 'error',
777
804
  // 函数使用 function 来声明而不是 const foo = () => { }
778
805
  'xlint/func-style': 'error',
806
+ 'xlint/import': 'error',
779
807
  '@stylistic/semi': ['error', 'never'],
780
808
  '@stylistic/no-extra-semi': 'error',
781
809
  '@stylistic/semi-style': ['error', 'first'],
@@ -868,27 +896,12 @@ export const xlint_config = {
868
896
  ],
869
897
  '@typescript-eslint/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports', disallowTypeAnnotations: false }],
870
898
  // () => { 返回 void 的表达式 }
871
- '@typescript-eslint/no-confusing-void-expression': 'error',
872
- // ------------ import
873
- 'import/no-duplicates': ['error', { 'prefer-inline': true }],
874
- 'import/order': [
875
- 'error',
876
- {
877
- 'newlines-between': 'always-and-inside-groups',
878
- pathGroups: [
879
- {
880
- pattern: '@/**',
881
- group: 'parent'
882
- },
883
- {
884
- pattern: '@i18n/**',
885
- group: 'parent'
886
- }
887
- ]
888
- }
889
- ]
899
+ '@typescript-eslint/no-confusing-void-expression': 'error'
890
900
  }
891
901
  };
902
+ const line_break_pattern = /\r\n|[\r\n\u2028\u2029]/u;
903
+ const style_fexts = new Set(['sass', 'css', 'scss', 'less', 'styl']);
904
+ const unions = ['|', '&'];
892
905
  function split_indent(line) {
893
906
  let i = 0;
894
907
  let indent = 0;
@@ -910,4 +923,157 @@ function not_opening_paren(token) {
910
923
  function not_closing_paren(token) {
911
924
  return !([')', ']', '}'].includes(token.value) && token.type === 'Punctuator');
912
925
  }
926
+ function all_space(str, len = str.length) {
927
+ for (let i = 0; i < len; ++i)
928
+ if (str[i] !== ' ')
929
+ return false;
930
+ return true;
931
+ }
932
+ /** 获取节点前面的注释,并计算包含注释的范围 */
933
+ function get_range_with_comments(source, node) {
934
+ let [start, end] = node.range;
935
+ // 获取节点前的所有注释
936
+ let comments = source.getCommentsBefore(node);
937
+ if (comments.length !== 0) {
938
+ // 从最后一个注释往前找,遇到属于上一条语句的行尾注释或非注释时退出
939
+ // 判断标准:独立注释以多个空格 + // 或者 /* 开头
940
+ // 行尾注释:// 前面含有非空格
941
+ let valid_comment;
942
+ for (let i = comments.length - 1; i >= 0; --i) {
943
+ let comment = comments[i];
944
+ // 获取 comment 所在行的内容
945
+ let line = source.lines[comment.loc.start.line - 1];
946
+ // 找到注释开始标记 // 或 /* 在行中的位置
947
+ let istart;
948
+ if (comment.type === 'Line')
949
+ istart = line.indexOf('//');
950
+ else // Block
951
+ istart = line.indexOf('/*');
952
+ // 注释标记之前是否全是空格
953
+ if (!all_space(line.slice(0, istart)))
954
+ break;
955
+ valid_comment = comment;
956
+ }
957
+ // 如果有有效的注释,更新 start
958
+ if (valid_comment)
959
+ start = valid_comment.range[0];
960
+ }
961
+ let comments_ = source.getCommentsAfter(node);
962
+ // 和导入在同一行,在尾部的注释
963
+ if (comments_[0]?.loc.start.line === node.loc.end.line)
964
+ end = comments_[0].range[1];
965
+ return [start, end];
966
+ }
967
+ let cache_alias_cwd;
968
+ let cache_alias;
969
+ /** 解析 tsconfig.json 中的 paths 配置
970
+ 返回 `{ alias: string, fp: string }[]`
971
+ - alias: 导入别名前缀(如 `@lib/`)
972
+ - fp: 对应的路径前缀(如 `./lib/`)
973
+
974
+ 处理规则:
975
+ - value 数组必须只有一项
976
+ - 如果 key 和 value 都以 `/*` 结尾,去掉 `*`
977
+ - 否则 value 就是具体文件路径 */
978
+ function get_tsconfig_aliases(cwd) {
979
+ if (cwd === cache_alias_cwd)
980
+ return cache_alias;
981
+ // 读取文件内容,移除 // 行注释
982
+ const { compilerOptions } = JSON.parse(fs.readFileSync(`${cwd}tsconfig.json`, 'utf-8')
983
+ .split_lines()
984
+ .filter(line => !line.trimStart().startsWith('//'))
985
+ .join_lines());
986
+ const paths = compilerOptions?.paths;
987
+ if (!paths)
988
+ return [];
989
+ const aliases = Object.entries(paths).map(([key, values]) => {
990
+ // 检查 value 数组只有一项
991
+ check(values?.length === 1, `paths 的 value 数组必须只有一项`);
992
+ const value = values[0];
993
+ // key 和 value 都以 /* 结尾,去掉 *
994
+ if (key.endsWith('/*') && value.endsWith('/*'))
995
+ return {
996
+ alias: key.slice(0, -1), // @lib/* -> @lib/
997
+ fp: value.slice(0, -1).strip_if_start('./') // ./lib/* -> lib/
998
+ };
999
+ // 具体文件路径的情况
1000
+ return {
1001
+ alias: key,
1002
+ fp: value.strip_if_start('./')
1003
+ };
1004
+ });
1005
+ cache_alias_cwd = cwd;
1006
+ return cache_alias = aliases;
1007
+ }
1008
+ let cache_pkgs_cwd;
1009
+ let cache_pkgs;
1010
+ function get_packages(cwd) {
1011
+ if (cache_pkgs_cwd === cwd)
1012
+ return cache_pkgs;
1013
+ const { dependencies = {}, devDependencies = {} } = JSON.parse(fs.readFileSync(`${cwd}package.json`, 'utf-8'));
1014
+ const pkgs = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
1015
+ cache_pkgs_cwd = cwd;
1016
+ return cache_pkgs = pkgs;
1017
+ }
1018
+ /** import types */
1019
+ const imtypes = ['builtin', 'package', 'alias', 'subpath'];
1020
+ /** 将一个有后缀或无后缀的文件,解析到实际文件,并通过 alias 简化 */
1021
+ function format_source(pkgs, aliases, cwd, fp, source) {
1022
+ check(path.isAbsolute(fp), 'fp 应该是绝对路径');
1023
+ if (source.startsWith('node:'))
1024
+ return ['builtin'];
1025
+ if (builtins.has(source))
1026
+ return ['builtin', `node:${source}`];
1027
+ const pkg = pkgs.find(p => source === p || source.startsWith(p) && source[p.length] === '/');
1028
+ if (pkg) {
1029
+ if (pkgs.includes(pkg) || source.fext)
1030
+ return ['package'];
1031
+ // package.json 中有 exports 的,也允许无后缀(可能 .js 没有导出)
1032
+ const pkg_json = JSON.parse(fs.readFileSync(`${cwd}node_modules/${pkg}/package.json`, 'utf-8'));
1033
+ if (pkg_json.exports)
1034
+ return ['package'];
1035
+ return ['package', `${source}.js`];
1036
+ }
1037
+ // 剩下的全是内部路径
1038
+ // 相对子路径
1039
+ if (source.startsWith('./')) {
1040
+ const { fext } = source;
1041
+ // 已经完美了
1042
+ if (fext && fext !== 'js')
1043
+ return ['subpath'];
1044
+ source = source.strip_if_end('.js');
1045
+ return ['subpath', `${source}.${resolve_suffix(path.resolve(fp.fdir, source))}`];
1046
+ }
1047
+ // ../ 父级路径,或者 @/... 等 alias 路径
1048
+ // 先 resolve 到完整路径,再修复后缀,最后通过 alias 简化
1049
+ const alias = aliases.find(({ alias }) => source.startsWith(alias));
1050
+ if (alias)
1051
+ source = cwd + alias.fp + source.strip_start(alias.alias);
1052
+ // 是完整路径了
1053
+ source = path.resolve(fp.fdir, source);
1054
+ const { fext } = source;
1055
+ if (!fext || fext === 'js') {
1056
+ source = source.strip_if_end('.js');
1057
+ source = `${source}.${resolve_suffix(source)}`;
1058
+ }
1059
+ const relative = source.strip_start(cwd, true);
1060
+ const alias_ = aliases.find(({ fp }) => relative.startsWith(fp));
1061
+ check(alias_, '一定能找到匹配的 alias');
1062
+ return ['alias', alias_.alias + relative.strip_start(alias_.fp)];
1063
+ }
1064
+ let cache_exists = new Map();
1065
+ function resolve_suffix(fp_base) {
1066
+ for (const suffix of ['ts', 'tsx']) {
1067
+ const fp = `${fp_base}.${suffix}`;
1068
+ const c = cache_exists.get(fp);
1069
+ if (c !== undefined)
1070
+ return suffix;
1071
+ const e = fs.existsSync(fp);
1072
+ cache_exists.set(fp, e);
1073
+ if (e)
1074
+ return suffix;
1075
+ }
1076
+ throw new Error(`无法解析 ${fp_base} 到文件`);
1077
+ }
1078
+ const builtins = new Set(builtinModules);
913
1079
  //# sourceMappingURL=xlint.js.map