ts-codemod-lib 3.3.0 → 3.3.1

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 (69) hide show
  1. package/dist/entry-point.mjs +2 -24
  2. package/dist/entry-point.mjs.map +1 -1
  3. package/dist/functions/ast-transformers/append-as-const.mjs +330 -292
  4. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  5. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +85 -70
  6. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
  7. package/dist/functions/ast-transformers/convert-to-readonly.mjs +969 -840
  8. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  9. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs +276 -134
  10. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs.map +1 -1
  11. package/dist/functions/ast-transformers/index.mjs +10 -12
  12. package/dist/functions/ast-transformers/index.mjs.map +1 -1
  13. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs +14 -7
  14. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -1
  15. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs +12 -15
  16. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs.map +1 -1
  17. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +63 -58
  18. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
  19. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs +5 -5
  20. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs.map +1 -1
  21. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs +15 -20
  22. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs.map +1 -1
  23. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +30 -34
  24. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -1
  25. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +197 -156
  26. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  27. package/dist/functions/ast-transformers/transform-source-code.mjs +74 -75
  28. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  29. package/dist/functions/ast-transformers/types.mjs +2 -2
  30. package/dist/functions/ast-transformers/types.mjs.map +1 -1
  31. package/dist/functions/constants/ignore-comment-text.mjs +11 -13
  32. package/dist/functions/constants/ignore-comment-text.mjs.map +1 -1
  33. package/dist/functions/constants/index.mjs +2 -2
  34. package/dist/functions/constants/index.mjs.map +1 -1
  35. package/dist/functions/functions/has-disable-next-line-comment.mjs +67 -38
  36. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
  37. package/dist/functions/functions/index.mjs +11 -11
  38. package/dist/functions/functions/index.mjs.map +1 -1
  39. package/dist/functions/functions/is-as-const-node.mjs +25 -20
  40. package/dist/functions/functions/is-as-const-node.mjs.map +1 -1
  41. package/dist/functions/functions/is-atomic-type-node.mjs +245 -25
  42. package/dist/functions/functions/is-atomic-type-node.mjs.map +1 -1
  43. package/dist/functions/functions/is-conditional-type-distribution-guard.mjs +34 -13
  44. package/dist/functions/functions/is-conditional-type-distribution-guard.mjs.map +1 -1
  45. package/dist/functions/functions/is-readonly-node.mjs +120 -14
  46. package/dist/functions/functions/is-readonly-node.mjs.map +1 -1
  47. package/dist/functions/functions/is-spread-parameter-node.mjs +5 -6
  48. package/dist/functions/functions/is-spread-parameter-node.mjs.map +1 -1
  49. package/dist/functions/functions/remove-parentheses.mjs +4 -4
  50. package/dist/functions/functions/remove-parentheses.mjs.map +1 -1
  51. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs +23 -9
  52. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs.map +1 -1
  53. package/dist/functions/functions/unwrap-readonly.mjs +3 -4
  54. package/dist/functions/functions/unwrap-readonly.mjs.map +1 -1
  55. package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
  56. package/dist/functions/functions/wrap-with-parentheses.mjs +54 -29
  57. package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
  58. package/dist/functions/index.mjs +5 -24
  59. package/dist/functions/index.mjs.map +1 -1
  60. package/dist/functions/utils/index.mjs +2 -2
  61. package/dist/functions/utils/index.mjs.map +1 -1
  62. package/dist/functions/utils/replace-with-debug.mjs +4 -6
  63. package/dist/functions/utils/replace-with-debug.mjs.map +1 -1
  64. package/dist/index.mjs +2 -24
  65. package/dist/index.mjs.map +1 -1
  66. package/package.json +11 -12
  67. package/src/functions/ast-transformers/convert-to-readonly.test.mts +10 -1
  68. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +76 -22
  69. package/src/functions/functions/wrap-with-parentheses.mts +5 -2
@@ -12,8 +12,12 @@ import { transformSourceCode } from './transform-source-code.mjs';
12
12
  * Declarations every case is prefixed with, so that each `source` can be a
13
13
  * single expression statement. `declare const` keeps them free of index
14
14
  * accesses of their own.
15
+ *
16
+ * `console` is among them because {@link project} loads only the libraries
17
+ * these cases need, and the one that declares `console` is not one of them.
15
18
  */
16
19
  const preamble = dedent`
20
+ declare const console: { log: (...args: readonly unknown[]) => void };
17
21
  declare const xs: readonly number[];
18
22
  declare const mut_ys: number[];
19
23
  declare const pair: readonly [number, number];
@@ -47,9 +51,7 @@ const testFn = async ({
47
51
  }
48
52
 
49
53
  const transformed = await formatter(
50
- transformSourceCode(`${preamble}\n\n${source}`, false, [
51
- enableNoUncheckedIndexedAccessTransformer({ ...options, debug }),
52
- ]),
54
+ transformInProject(`${preamble}\n\n${source}`, { ...options, debug }),
53
55
  );
54
56
 
55
57
  const expectedFormatted = await formatter(`${preamble}\n\n${expected}`);
@@ -57,6 +59,54 @@ const testFn = async ({
57
59
  expect(transformed).toBe(expectedFormatted);
58
60
  };
59
61
 
62
+ /**
63
+ * Runs the transformer over `code` in {@link project}, and returns what it
64
+ * made of it.
65
+ */
66
+ const transformInProject = (
67
+ code: string,
68
+ options?: Parameters<typeof enableNoUncheckedIndexedAccessTransformer>[0],
69
+ ): string => {
70
+ const sourceAst = project.createSourceFile('source.ts', code, {
71
+ overwrite: true,
72
+ });
73
+
74
+ enableNoUncheckedIndexedAccessTransformer(options).transform(sourceAst);
75
+
76
+ return sourceAst.getFullText();
77
+ };
78
+
79
+ /**
80
+ * The one project every case is checked in, rather than one project per case.
81
+ *
82
+ * `transformSourceCode` builds a project per call, and this transformer checks
83
+ * the file twice — once with `noUncheckedIndexedAccess` on and once with it
84
+ * off. A case that went through it therefore paid for the whole default
85
+ * library, `lib.esnext.full.d.ts` with the DOM inside it, being parsed and
86
+ * checked from scratch: ~300 ms a case, of which the file under test was a
87
+ * fraction of a millisecond. Sixty of those took `test:cov` past Vitest's
88
+ * timeout on CI.
89
+ *
90
+ * Reusing the project is half of the answer and the smaller half — measured,
91
+ * it takes a case from ~300 ms to ~260 ms, because what an edit invalidates is
92
+ * the program, not the parsed library behind it. Naming the libraries the
93
+ * cases actually need is the rest: together they bring a case to ~25 ms.
94
+ *
95
+ * What that leaves out — `transformSourceCode` itself, and the transformer
96
+ * against the full library — is what the tests at the bottom of this file
97
+ * still cover.
98
+ */
99
+ const project = new tsm.Project({
100
+ useInMemoryFileSystem: true,
101
+ compilerOptions: {
102
+ target: tsm.ts.ScriptTarget.ESNext,
103
+ module: tsm.ts.ModuleKind.ESNext,
104
+ // `es2015` rather than `es5` for `Symbol.iterator`, which the spread and
105
+ // `for-of` cases need at this target.
106
+ lib: ['lib.es2015.d.ts'],
107
+ },
108
+ });
109
+
60
110
  const formatter = (code: string): Promise<string> =>
61
111
  prettier.format(code, {
62
112
  parser: 'typescript',
@@ -456,6 +506,8 @@ describe(enableNoUncheckedIndexedAccessTransformer, () => {
456
506
 
457
507
  test('the errors the option introduces are gone from the result', () => {
458
508
  const source = dedent`
509
+ declare const console: { log: (...args: readonly unknown[]) => void };
510
+
459
511
  const parseRoute = (path: string): string => {
460
512
  const parts = path.split('/');
461
513
 
@@ -484,11 +536,7 @@ describe(enableNoUncheckedIndexedAccessTransformer, () => {
484
536
  expect(diagnosticsUnderTheOption(source).length).toBeGreaterThan(0);
485
537
 
486
538
  assert.deepStrictEqual(
487
- diagnosticsUnderTheOption(
488
- transformSourceCode(source, false, [
489
- enableNoUncheckedIndexedAccessTransformer(),
490
- ]),
491
- ),
539
+ diagnosticsUnderTheOption(transformInProject(source)),
492
540
  [],
493
541
  );
494
542
  });
@@ -542,19 +590,9 @@ describe(enableNoUncheckedIndexedAccessTransformer, () => {
542
590
  });
543
591
 
544
592
  /** The compiler's own verdict on the code, with the option this transformer prepares for turned on. */
545
- const diagnosticsUnderTheOption = (code: string): readonly string[] => {
546
- const project = new tsm.Project({
547
- useInMemoryFileSystem: true,
548
- compilerOptions: {
549
- target: tsm.ts.ScriptTarget.ESNext,
550
- module: tsm.ts.ModuleKind.ESNext,
551
- strict: true,
552
- noUncheckedIndexedAccess: true,
553
- },
554
- });
555
-
556
- return project
557
- .createSourceFile('source.ts', code)
593
+ const diagnosticsUnderTheOption = (code: string): readonly string[] =>
594
+ diagnosticsProject
595
+ .createSourceFile('source.ts', code, { overwrite: true })
558
596
  .getPreEmitDiagnostics()
559
597
  .map((diagnostic) => diagnostic.getMessageText())
560
598
  .map((messageText) =>
@@ -562,4 +600,20 @@ const diagnosticsUnderTheOption = (code: string): readonly string[] => {
562
600
  ? messageText
563
601
  : messageText.getMessageText(),
564
602
  );
565
- };
603
+
604
+ /**
605
+ * A second project, because {@link project} has to start with the option off,
606
+ * and reused for the reason that one is. `getPreEmitDiagnostics` checks the
607
+ * whole program rather than one expression, so the library it is pointed at
608
+ * costs more here than anywhere else in this file.
609
+ */
610
+ const diagnosticsProject = new tsm.Project({
611
+ useInMemoryFileSystem: true,
612
+ compilerOptions: {
613
+ target: tsm.ts.ScriptTarget.ESNext,
614
+ module: tsm.ts.ModuleKind.ESNext,
615
+ lib: ['lib.es2015.d.ts'],
616
+ strict: true,
617
+ noUncheckedIndexedAccess: true,
618
+ },
619
+ });
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { asUint32, range } from 'ts-data-forge';
12
12
 
13
- const parenDepthDelta = (char: string): number => {
13
+ const parenDepthDelta = (char: string | undefined): number => {
14
14
  switch (char) {
15
15
  case '(':
16
16
  return 1;
@@ -18,6 +18,9 @@ const parenDepthDelta = (char: string): number => {
18
18
  case ')':
19
19
  return -1;
20
20
 
21
+ // `at` returns `undefined` past the end of the string. The loop below
22
+ // never asks for such an index, but the type says it can.
23
+ case undefined:
21
24
  default:
22
25
  return 0;
23
26
  }
@@ -35,7 +38,7 @@ const isWrappedWithParentheses = (str: string): boolean => {
35
38
  let mut_depth = 0;
36
39
 
37
40
  for (const mut_i of range(0, asUint32(trimmed.length))) {
38
- mut_depth += parenDepthDelta(trimmed.charAt(mut_i));
41
+ mut_depth += parenDepthDelta(trimmed.at(mut_i));
39
42
 
40
43
  // If we reach depth 0 before the end, the outer parentheses don't wrap everything
41
44
  if (mut_depth === 0 && mut_i < trimmed.length - 1) {