ts-codemod-lib 4.0.0 → 4.1.0

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 (52) hide show
  1. package/README.md +2 -1
  2. package/dist/functions/ast-transformers/append-as-const.d.mts +9 -10
  3. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  4. package/dist/functions/ast-transformers/append-as-const.mjs +123 -114
  5. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  6. package/dist/functions/ast-transformers/convert-to-readonly.d.mts +28 -7
  7. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  8. package/dist/functions/ast-transformers/convert-to-readonly.mjs +452 -253
  9. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  10. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts +8 -8
  11. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs +103 -104
  12. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs.map +1 -1
  13. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts +2 -2
  14. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +51 -51
  15. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  16. package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -1
  17. package/dist/functions/ast-transformers/transform-source-code.mjs +69 -69
  18. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  19. package/dist/functions/constants/mutable-prefixes.d.mts +8 -8
  20. package/dist/functions/functions/has-disable-next-line-comment.mjs +23 -23
  21. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
  22. package/dist/functions/functions/is-readonly-node.d.mts +2 -2
  23. package/dist/functions/functions/is-readonly-node.d.mts.map +1 -1
  24. package/dist/functions/functions/is-readonly-node.mjs +13 -13
  25. package/dist/functions/functions/is-readonly-node.mjs.map +1 -1
  26. package/dist/functions/functions/wrap-with-parentheses.d.mts +2 -2
  27. package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
  28. package/dist/functions/functions/wrap-with-parentheses.mjs +87 -32
  29. package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
  30. package/dist/functions/utils/replace-node-with-text.d.mts +10 -11
  31. package/dist/functions/utils/replace-node-with-text.d.mts.map +1 -1
  32. package/dist/functions/utils/replace-node-with-text.mjs +0 -1
  33. package/dist/functions/utils/replace-node-with-text.mjs.map +1 -1
  34. package/package.json +9 -9
  35. package/src/functions/ast-transformers/append-as-const.mts +242 -231
  36. package/src/functions/ast-transformers/append-as-const.test.mts +39 -6
  37. package/src/functions/ast-transformers/convert-to-readonly-record-import.test.mts +458 -0
  38. package/src/functions/ast-transformers/convert-to-readonly.mts +1003 -694
  39. package/src/functions/ast-transformers/convert-to-readonly.test.mts +80 -33
  40. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.mts +178 -179
  41. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +29 -29
  42. package/src/functions/ast-transformers/replace-any-with-unknown.test.mts +6 -6
  43. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +75 -75
  44. package/src/functions/ast-transformers/replace-record-with-unknown-record.test.mts +6 -6
  45. package/src/functions/ast-transformers/transform-source-code.mts +110 -110
  46. package/src/functions/ast-transformers/transformer-specific-ignore.test.mts +6 -6
  47. package/src/functions/constants/mutable-prefixes.mts +8 -8
  48. package/src/functions/functions/has-disable-next-line-comment.mts +35 -35
  49. package/src/functions/functions/is-readonly-node.mts +25 -25
  50. package/src/functions/functions/wrap-with-parentheses.mts +138 -52
  51. package/src/functions/functions/wrap-with-parentheses.test.mts +115 -0
  52. package/src/functions/utils/replace-node-with-text.mts +10 -11
@@ -5,6 +5,8 @@ import { hasCallOrConstructSignature, hasDisableNextLineComment, isAtomicTypeNod
5
5
  import { replaceNodeWithDebugPrint, replaceNodeWithText, } from '../utils/index.mjs';
6
6
  import { groupUnionIntersectionTypes, invalidDeepReadonlyTypeName, isMergeableReadonlyTypeReferenceNode, nextReadonlyContext, } from './readonly-transformer-helpers/index.mjs';
7
7
  const TRANSFORMER_NAME = 'convert-to-readonly';
8
+ const READONLY_RECORD_NAME = 'ReadonlyRecord';
9
+ const READONLY_RECORD_MODULE = 'ts-type-forge';
8
10
  export const convertToReadonlyTransformer = (options) => {
9
11
  if (options?.DeepReadonly?.typeName !== undefined &&
10
12
  invalidDeepReadonlyTypeName.has(options.DeepReadonly.typeName)) {
@@ -27,6 +29,7 @@ export const convertToReadonlyTransformer = (options) => {
27
29
  mutableTypePrefixes: ISet.create(options?.mutableTypePrefixes ?? DEFAULT_MUTABLE_TYPE_PREFIXES),
28
30
  mutablePropertyPrefixes: ISet.create(options?.mutablePropertyPrefixes ?? DEFAULT_MUTABLE_PREFIXES),
29
31
  recordStyle: options?.recordStyle ?? 'ReadonlyRecord',
32
+ importStyle: options?.importStyle ?? 'named',
30
33
  debugPrint: options?.debug === true ? console.debug : () => { },
31
34
  replaceNode: options?.debug === true
32
35
  ? replaceNodeWithDebugPrint
@@ -35,8 +38,23 @@ export const convertToReadonlyTransformer = (options) => {
35
38
  return {
36
39
  name: TRANSFORMER_NAME,
37
40
  transform: (sourceAst) => {
41
+ const scope = optionsInternal.recordStyle === 'ReadonlyRecord' &&
42
+ optionsInternal.importStyle === 'named'
43
+ ? readonlyRecordScope(sourceAst.compilerNode)
44
+ : 'in-scope';
45
+
46
+
47
+
48
+
49
+ const mayImport = scope === 'free' && tsm.ts.isExternalModule(sourceAst.compilerNode);
50
+ const fileOptions = scope !== 'in-scope' && !mayImport
51
+ ? { ...optionsInternal, recordStyle: 'Readonly<Record>' }
52
+ : optionsInternal;
38
53
  for (const node of sourceAst.getChildren()) {
39
- transformNode(node, initialReadonlyContext, optionsInternal);
54
+ transformNode(node, initialReadonlyContext, fileOptions);
55
+ }
56
+ if (mayImport && referencesReadonlyRecord(sourceAst.compilerNode)) {
57
+ addReadonlyRecordImport(sourceAst);
40
58
  }
41
59
  },
42
60
  };
@@ -45,6 +63,18 @@ const initialReadonlyContext = {
45
63
  type: 'none',
46
64
  indexedAccessDepth: 0,
47
65
  };
66
+
67
+ const hasMutableTypeName = (name, options) => name !== undefined &&
68
+ options.mutableTypePrefixes.some((p) => name.startsWith(p));
69
+
70
+
71
+
72
+
73
+ const mutablePrefixesOfParameter = (
74
+
75
+ param, options) => param.isParameterProperty()
76
+ ? options.mutablePropertyPrefixes
77
+ : options.mutableVariablePrefixes;
48
78
  const transformNode = (node, readonlyContext, options) => {
49
79
  if (tsm.Node.isTypeNode(node) ||
50
80
  node.isKind(tsm.SyntaxKind.InterfaceDeclaration) ||
@@ -198,6 +228,322 @@ const transformNode = (node, readonlyContext, options) => {
198
228
 
199
229
 
200
230
 
231
+ const topLevelBindings = (
232
+
233
+ statement) => {
234
+ if (tsm.ts.isImportDeclaration(statement)) {
235
+ const clause = statement.importClause;
236
+ const bindings = clause?.namedBindings;
237
+
238
+ const namespaceImport = bindings !== undefined && tsm.ts.isNamespaceImport(bindings)
239
+ ? [{ name: bindings.name.text, isType: false }]
240
+ : [];
241
+ const named = bindings !== undefined && tsm.ts.isNamedImports(bindings)
242
+ ? bindings.elements.map((element) => ({
243
+ name: element.name.text,
244
+ isType: true,
245
+ }))
246
+ : [];
247
+ const defaultImport = clause?.name === undefined
248
+ ? []
249
+ : [{ name: clause.name.text, isType: true }];
250
+ return [...defaultImport, ...namespaceImport, ...named];
251
+ }
252
+ if (tsm.ts.isImportEqualsDeclaration(statement)) {
253
+ return [{ name: statement.name.text, isType: true }];
254
+ }
255
+ if (tsm.ts.isTypeAliasDeclaration(statement) ||
256
+ tsm.ts.isInterfaceDeclaration(statement) ||
257
+ tsm.ts.isClassDeclaration(statement) ||
258
+ tsm.ts.isEnumDeclaration(statement)) {
259
+ return statement.name === undefined
260
+ ? []
261
+ : [{ name: statement.name.text, isType: true }];
262
+ }
263
+ if ((tsm.ts.isFunctionDeclaration(statement) ||
264
+ tsm.ts.isModuleDeclaration(statement)) &&
265
+ statement.name !== undefined &&
266
+ tsm.ts.isIdentifier(statement.name)) {
267
+ return [{ name: statement.name.text, isType: false }];
268
+ }
269
+ if (tsm.ts.isVariableStatement(statement)) {
270
+ return statement.declarationList.declarations.flatMap((declaration) => tsm.ts.isIdentifier(declaration.name)
271
+ ? [{ name: declaration.name.text, isType: false }]
272
+ : []);
273
+ }
274
+ return [];
275
+ };
276
+
277
+
278
+
279
+
280
+ const referencesReadonlyRecord = (node) => (tsm.ts.isTypeReferenceNode(node) &&
281
+ tsm.ts.isIdentifier(node.typeName) &&
282
+ node.typeName.text === READONLY_RECORD_NAME) ||
283
+ (tsm.ts.isExpressionWithTypeArguments(node) &&
284
+ tsm.ts.isIdentifier(node.expression) &&
285
+ node.expression.text === READONLY_RECORD_NAME) ||
286
+ (tsm.ts.forEachChild(node, (child) => referencesReadonlyRecord(child) || undefined) ??
287
+ false);
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+ const readonlyRecordScope = (
303
+
304
+ sourceFile) => {
305
+ const topLevelNames = sourceFile.statements.flatMap(topLevelBindings);
306
+ return topLevelNames.some(({ name, isType }) => isType && name === READONLY_RECORD_NAME) || referencesReadonlyRecord(sourceFile)
307
+ ? 'in-scope'
308
+ : topLevelNames.some(({ name }) => name === READONLY_RECORD_NAME)
309
+ ? 'taken'
310
+ : 'free';
311
+ };
312
+
313
+
314
+
315
+
316
+ const endOfLineComments = (text, end) => tsm.ts.getTrailingCommentRanges(text, end)?.at(-1)?.end ?? end;
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+ const newImportPosition = (
328
+
329
+ sourceFile) => {
330
+ const text = sourceFile.getFullText();
331
+ const first = sourceFile.getStatements().at(0);
332
+ if (first === undefined) {
333
+ return text.length;
334
+ }
335
+ const comments = tsm.ts.getLeadingCommentRanges(text, first.getFullStart()) ?? [];
336
+ let mut_position = first.getStart();
337
+ for (const comment of comments.toReversed()) {
338
+ const commentText = text.slice(comment.pos, comment.end);
339
+ if (/\n[^\S\n]*\n/u.test(text.slice(comment.end, mut_position)) ||
340
+ commentText.startsWith('///') ||
341
+ (commentText.startsWith('/*') && !commentText.startsWith('/**')) ||
342
+ /@(?:ts-nocheck|ts-check|jsx)/u.test(commentText)) {
343
+ break;
344
+ }
345
+ mut_position = comment.pos;
346
+ }
347
+ return mut_position;
348
+ };
349
+
350
+ const directivePrologue = (
351
+
352
+ sourceFile) => {
353
+ const statements = sourceFile.compilerNode.statements;
354
+ const end = statements.findIndex((statement) => !(tsm.ts.isExpressionStatement(statement) &&
355
+ tsm.ts.isStringLiteral(statement.expression)));
356
+ return statements.slice(0, end === -1 ? statements.length : end);
357
+ };
358
+ const insertReadonlyRecordImport = (
359
+
360
+ sourceFile, text) => {
361
+ const imports = sourceFile.getImportDeclarations();
362
+ const hosts = imports.filter((decl) => decl.getModuleSpecifierValue() === READONLY_RECORD_MODULE &&
363
+ tsm.Node.isNamedImports(decl.getImportClause()?.getNamedBindings()));
364
+ const host = hosts.find((decl) => decl.isTypeOnly()) ?? hosts.at(0);
365
+ if (host !== undefined) {
366
+
367
+
368
+ host.addNamedImport({
369
+ name: READONLY_RECORD_NAME,
370
+ isTypeOnly: !host.isTypeOnly(),
371
+ });
372
+ return;
373
+ }
374
+
375
+
376
+ const declaration = `import type { ${READONLY_RECORD_NAME} } from '${READONLY_RECORD_MODULE}';`;
377
+ const anchorEnd = imports.at(-1)?.getEnd() ?? directivePrologue(sourceFile).at(-1)?.end;
378
+ if (anchorEnd === undefined) {
379
+ sourceFile.insertText(newImportPosition(sourceFile), `${declaration}\n\n`);
380
+ }
381
+ else {
382
+ sourceFile.insertText(endOfLineComments(text, anchorEnd), `\n${declaration}`);
383
+ }
384
+ };
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+ const addReadonlyRecordImport = (
393
+
394
+ sourceFile) => {
395
+ const text = sourceFile.getFullText();
396
+
397
+
398
+ const { manipulationSettings } = sourceFile.getProject();
399
+ const newLineKind = manipulationSettings.getNewLineKind();
400
+ manipulationSettings.set({
401
+ newLineKind: text.includes('\r\n')
402
+ ? tsm.NewLineKind.CarriageReturnLineFeed
403
+ : tsm.NewLineKind.LineFeed,
404
+ });
405
+ try {
406
+ insertReadonlyRecordImport(sourceFile, text);
407
+ }
408
+ finally {
409
+ manipulationSettings.set({ newLineKind });
410
+ }
411
+ };
412
+
413
+
414
+
415
+
416
+ const readonlyRecordText = (keyText, valueText, options) => options.recordStyle === 'ReadonlyRecord'
417
+ ? `ReadonlyRecord<${keyText}, ${valueText}>`
418
+ : `Readonly<Record<${keyText}, ${valueText}>>`;
419
+
420
+
421
+
422
+
423
+
424
+ const commentsBetween = (
425
+
426
+ sourceFile, from, to) => {
427
+ if (to <= from) {
428
+ return '';
429
+ }
430
+ const scanner = tsm.ts.createScanner(tsm.ts.ScriptTarget.Latest, false, tsm.ts.LanguageVariant.Standard, sourceFile.getFullText(), undefined, from, to - from);
431
+ const mut_comments = [];
432
+ while (scanner.scan() !== tsm.ts.SyntaxKind.EndOfFileToken) {
433
+ const token = scanner.getToken();
434
+
435
+ const separator = token === tsm.ts.SyntaxKind.SingleLineCommentTrivia
436
+ ? '\n'
437
+ : token === tsm.ts.SyntaxKind.MultiLineCommentTrivia
438
+ ? ' '
439
+ : undefined;
440
+ if (separator !== undefined) {
441
+ mut_comments.push(`${scanner.getTokenText()}${separator}`);
442
+ }
443
+ }
444
+ return mut_comments.join('');
445
+ };
446
+
447
+ const getDeepReadonlyTypeArg = (
448
+
449
+ node, deepReadonlyTypeName) => {
450
+ if (!node.isKind(tsm.SyntaxKind.TypeReference)) {
451
+ return undefined;
452
+ }
453
+ const typeName = node.getTypeName();
454
+ const typeArguments = node.getTypeArguments();
455
+ return typeName.isKind(tsm.SyntaxKind.Identifier) &&
456
+ typeName.getText() === deepReadonlyTypeName &&
457
+ Arr.isFixedLengthArray(1, typeArguments)
458
+ ? typeArguments[0]
459
+ : undefined;
460
+ };
461
+ const checkIfPropertyNameShouldBeIgnored = (
462
+
463
+ nameNode, options) => {
464
+
465
+
466
+ return (
467
+
468
+ (nameNode.isKind(tsm.SyntaxKind.Identifier) &&
469
+ pipe(nameNode.getText()).map((nm) => options.mutablePropertyPrefixes.some((p) => nm.startsWith(p))).value) ||
470
+
471
+ (nameNode.isKind(tsm.SyntaxKind.StringLiteral) &&
472
+ pipe(nameNode.getLiteralValue()).map((nm) => options.mutablePropertyPrefixes.some((p) => nm.startsWith(p))).value) ||
473
+
474
+ (nameNode.isKind(tsm.SyntaxKind.PrivateIdentifier) &&
475
+ pipe(nameNode.getText()).map((nm) => options.mutablePropertyPrefixes.some((p) => nm.startsWith(p) || nm.startsWith(`#${p}`))).value) ||
476
+
477
+ (nameNode.isKind(tsm.SyntaxKind.ComputedPropertyName) &&
478
+ pipe(nameNode.getExpression()).map((exp) => {
479
+ if (exp.isKind(tsm.SyntaxKind.StringLiteral) ||
480
+ exp.isKind(tsm.SyntaxKind.NoSubstitutionTemplateLiteral)) {
481
+ const nm = exp.getLiteralValue();
482
+ return options.mutablePropertyPrefixes.some((p) => nm.startsWith(p));
483
+ }
484
+ return false;
485
+ }).value));
486
+ };
487
+
488
+
489
+
490
+
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+
500
+
501
+
502
+ const isLeftUnwrapped = (
503
+
504
+ node, options) => {
505
+ const members = node.getMembers();
506
+ const isMethodOrAccessor = (
507
+
508
+ mb) => mb.isKind(tsm.SyntaxKind.MethodSignature) ||
509
+ mb.isKind(tsm.SyntaxKind.GetAccessor) ||
510
+ mb.isKind(tsm.SyntaxKind.SetAccessor);
511
+ const isIgnoredMember = (
512
+
513
+ mb) => (mb.isKind(tsm.SyntaxKind.PropertySignature) || isMethodOrAccessor(mb)) &&
514
+ checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options);
515
+ const needsReadonlyWrapper = (
516
+
517
+ mb) => !isIgnoredMember(mb) &&
518
+ (isMethodOrAccessor(mb) ||
519
+ ((mb.isKind(tsm.SyntaxKind.PropertySignature) ||
520
+ mb.isKind(tsm.SyntaxKind.IndexSignature)) &&
521
+ !mb.hasModifier(tsm.SyntaxKind.ReadonlyKeyword) &&
522
+ hasDisableNextLineComment(mb)));
523
+ return members.some(isIgnoredMember) && !members.some(needsReadonlyWrapper);
524
+ };
525
+
526
+ const hasDisableNextLineCommentAround = (node) => {
527
+ if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
528
+ return true;
529
+ }
530
+ const parent = node.getParent();
531
+ return (parent?.isKind(tsm.SyntaxKind.ParenthesizedType) === true &&
532
+ hasDisableNextLineCommentAround(parent));
533
+ };
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+ const takesOffReadonly = (
542
+
543
+ node, options) => isLeftUnwrapped(node, options) &&
544
+ !hasCallOrConstructSignature(node) &&
545
+ !hasDisableNextLineCommentAround(node);
546
+
201
547
 
202
548
 
203
549
 
@@ -520,10 +866,6 @@ node, readonlyContext, options) => {
520
866
  }
521
867
  };
522
868
 
523
- const readonlyRecordText = (keyText, valueText, options) => options.recordStyle === 'ReadonlyRecord'
524
- ? `ReadonlyRecord<${keyText}, ${valueText}>`
525
- : `Readonly<Record<${keyText}, ${valueText}>>`;
526
-
527
869
  const transformArrayTypeNode = (
528
870
 
529
871
  node, readonlyContext, options) => {
@@ -828,104 +1170,6 @@ const transformUnionTypeNode = (
828
1170
  node, readonlyContext, options) => {
829
1171
  transformUnionOrIntersectionTypeNodeImpl(node, readonlyContext, options, '|');
830
1172
  };
831
- const transformUnionOrIntersectionTypeNodeImpl = (
832
-
833
- node, readonlyContext, options, operator) => {
834
- const nextReadonlyContextValue = nextReadonlyContext({
835
- currentReadonlyContext: readonlyContext,
836
- nextReadonlyContextType: 'none',
837
- indexedAccessDepthChange: 'keep',
838
- });
839
-
840
- for (const n of node.getTypeNodes() ) {
841
- transformNode(n, nextReadonlyContextValue, options);
842
- }
843
- const newTypes = node.getTypeNodes();
844
- const withLeadingComments = (
845
-
846
- member, memberText) => `${leadingCommentsOfMember(node, member)}${memberText}`;
847
-
848
-
849
- const wrapsTypeLiterals = nextReadonlyContextValue.type !== 'DeepReadonly' &&
850
- nextReadonlyContextValue.indexedAccessDepth === 0;
851
-
852
-
853
- const underReadonly = readonlyContext.type === 'Readonly' &&
854
- readonlyContext.indexedAccessDepth === 0 &&
855
- isTypeArgumentOfReadonly(node);
856
- const { primitives, arraysAndTuples, typeLiterals, others } = groupUnionIntersectionTypes(newTypes, {
857
- operator,
858
- underReadonly,
859
- deepReadonlyTypeName: options.DeepReadonly.typeName,
860
- keptUnwrapped: (t) => wrapsTypeLiterals && takesOffReadonly(t, options),
861
- });
862
- options.debugPrint({ primitives, arraysAndTuples, typeLiterals, others });
863
-
864
- const isEmptyTypeLiteral = (
865
-
866
- n) => n.isKind(tsm.SyntaxKind.TypeLiteral) && Arr.isEmpty(n.getMembers());
867
- const nonEmptyTypeLiterals = typeLiterals === undefined
868
- ? undefined
869
- : options.ignoreEmptyObjectTypes
870
- ? typeLiterals.nodes.filter((n) => !isEmptyTypeLiteral(n))
871
- : typeLiterals.nodes;
872
- const emptyTypeLiterals = typeLiterals === undefined || !options.ignoreEmptyObjectTypes
873
- ? undefined
874
- : typeLiterals.nodes.filter(isEmptyTypeLiteral);
875
- const typeLiteralsWrappedWithReadonly = nonEmptyTypeLiterals === undefined || Arr.isEmpty(nonEmptyTypeLiterals)
876
- ? []
877
- : [
878
- unionToString({
879
- types: nonEmptyTypeLiterals.map((n) => withLeadingComments(n, isMergeableReadonlyTypeReferenceNode(n, operator)
880
- ?
881
-
882
- readonlyTypeArgTextSplicedInto(n, operator)
883
- : n.isKind(tsm.SyntaxKind.TypeLiteral)
884
- ? n.getText()
885
- :
886
- wrapWithParentheses(n.getText()))),
887
- op: operator,
888
- wrapWithReadonly: wrapsTypeLiterals,
889
- }),
890
- ];
891
-
892
- const sorted = [
893
- mapNullable(primitives, (a) => [
894
- a.nodes.map((n) => withLeadingComments(n, n.getText())),
895
- a.firstPosition,
896
- ]),
897
- mapNullable(arraysAndTuples, (a) => [
898
- a.nodes.map((n) => withLeadingComments(n, n.getText())),
899
- a.firstPosition,
900
- ]),
901
- nonEmptyTypeLiterals !== undefined && Arr.isNonEmpty(nonEmptyTypeLiterals)
902
- ? mapNullable(typeLiterals, (a) => [typeLiteralsWrappedWithReadonly, a.firstPosition])
903
- : undefined,
904
- emptyTypeLiterals !== undefined && Arr.isNonEmpty(emptyTypeLiterals)
905
- ? mapNullable(typeLiterals, (a) => [
906
- emptyTypeLiterals.map((n) => withLeadingComments(n, n.getText())),
907
-
908
- a.firstPosition +
909
- (nonEmptyTypeLiterals !== undefined &&
910
- Arr.isNonEmpty(nonEmptyTypeLiterals)
911
- ? 0.5
912
- : 0),
913
- ])
914
- : undefined,
915
- mapNullable(others, (a) => [
916
-
917
-
918
- operator === '|'
919
- ? mergeDeepReadonlyUnionMembers(a.nodes, options.DeepReadonly.typeName, withLeadingComments)
920
- : a.nodes.map((n) => withLeadingComments(n, wrapWithParentheses(n.getText()))),
921
- a.firstPosition,
922
- ]),
923
- ]
924
- .filter((x) => x !== undefined)
925
- .toSorted((a, b) => a[1] - b[1])
926
- .flatMap((a) => a[0]);
927
-
928
-
929
1173
 
930
1174
 
931
1175
 
@@ -934,14 +1178,28 @@ node, readonlyContext, options, operator) => {
934
1178
 
935
1179
 
936
1180
 
1181
+ const textWithoutParentheses = (
937
1182
 
1183
+ outer,
938
1184
 
1185
+ inner) => `${commentsBetween(outer.getSourceFile(), outer.getPos(), inner.getStart())}${inner.getText()}`;
1186
+ const unionToStringImpl = ({ types, op, wrapWithReadonly, }) => Arr.isEmpty(types)
1187
+ ? 'never'
1188
+ : Arr.isFixedLengthArray(1, types)
1189
+ ? wrapWithReadonly === false
1190
+ ? wrapWithParentheses(types[0])
1191
+ : `${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types[0]}>`
1192
+ : wrapWithReadonly === false
1193
+ ? wrapWithParentheses(types.join(` ${op} `))
1194
+ : `${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types.join(` ${op} `)}>`;
1195
+ const unionToString = ({ types: typesRaw, op, wrapWithReadonly, }) => {
939
1196
 
940
1197
 
941
1198
 
942
1199
 
943
1200
 
944
- options.replaceNode(node, unionToString({ types: sorted, op: operator, wrapWithReadonly: false }));
1201
+ const types = typesRaw.map((s) => s.trim());
1202
+ return unionToStringImpl({ types, op, wrapWithReadonly });
945
1203
  };
946
1204
 
947
1205
 
@@ -1061,79 +1319,121 @@ node, operator) => {
1061
1319
  ? textWithoutParentheses(typeArgument, T)
1062
1320
  : unwrapReadonlyTypeArgText(node);
1063
1321
  };
1322
+ const transformUnionOrIntersectionTypeNodeImpl = (
1064
1323
 
1324
+ node, readonlyContext, options, operator) => {
1325
+ const nextReadonlyContextValue = nextReadonlyContext({
1326
+ currentReadonlyContext: readonlyContext,
1327
+ nextReadonlyContextType: 'none',
1328
+ indexedAccessDepthChange: 'keep',
1329
+ });
1065
1330
 
1331
+ for (const n of node.getTypeNodes() ) {
1332
+ transformNode(n, nextReadonlyContextValue, options);
1333
+ }
1334
+ const newTypes = node.getTypeNodes();
1335
+ const withLeadingComments = (
1066
1336
 
1337
+ member, memberText) => `${leadingCommentsOfMember(node, member)}${memberText}`;
1067
1338
 
1068
1339
 
1340
+ const wrapsTypeLiterals = nextReadonlyContextValue.type !== 'DeepReadonly' &&
1341
+ nextReadonlyContextValue.indexedAccessDepth === 0;
1069
1342
 
1070
1343
 
1344
+ const underReadonly = readonlyContext.type === 'Readonly' &&
1345
+ readonlyContext.indexedAccessDepth === 0 &&
1346
+ isTypeArgumentOfReadonly(node);
1347
+ const { primitives, arraysAndTuples, typeLiterals, others } = groupUnionIntersectionTypes(newTypes, {
1348
+ operator,
1349
+ underReadonly,
1350
+ deepReadonlyTypeName: options.DeepReadonly.typeName,
1351
+ keptUnwrapped: (t) => wrapsTypeLiterals && takesOffReadonly(t, options),
1352
+ });
1353
+ options.debugPrint({ primitives, arraysAndTuples, typeLiterals, others });
1071
1354
 
1072
- const textWithoutParentheses = (
1355
+ const isEmptyTypeLiteral = (
1073
1356
 
1074
- outer,
1357
+ n) => n.isKind(tsm.SyntaxKind.TypeLiteral) && Arr.isEmpty(n.getMembers());
1358
+ const nonEmptyTypeLiterals = typeLiterals === undefined
1359
+ ? undefined
1360
+ : options.ignoreEmptyObjectTypes
1361
+ ? typeLiterals.nodes.filter((n) => !isEmptyTypeLiteral(n))
1362
+ : typeLiterals.nodes;
1363
+ const emptyTypeLiterals = typeLiterals === undefined || !options.ignoreEmptyObjectTypes
1364
+ ? undefined
1365
+ : typeLiterals.nodes.filter(isEmptyTypeLiteral);
1366
+ const typeLiteralsWrappedWithReadonly = nonEmptyTypeLiterals === undefined || Arr.isEmpty(nonEmptyTypeLiterals)
1367
+ ? []
1368
+ : [
1369
+ unionToString({
1370
+ types: nonEmptyTypeLiterals.map((n) => withLeadingComments(n, isMergeableReadonlyTypeReferenceNode(n, operator)
1371
+ ?
1075
1372
 
1076
- inner) => `${commentsBetween(outer.getSourceFile(), outer.getPos(), inner.getStart())}${inner.getText()}`;
1373
+ readonlyTypeArgTextSplicedInto(n, operator)
1374
+ : n.isKind(tsm.SyntaxKind.TypeLiteral)
1375
+ ? n.getText()
1376
+ :
1377
+ wrapWithParentheses(n.getText()))),
1378
+ op: operator,
1379
+ wrapWithReadonly: wrapsTypeLiterals,
1380
+ }),
1381
+ ];
1382
+
1383
+ const sorted = [
1384
+ mapNullable(primitives, (a) => [
1385
+ a.nodes.map((n) => withLeadingComments(n, n.getText())),
1386
+ a.firstPosition,
1387
+ ]),
1388
+ mapNullable(arraysAndTuples, (a) => [
1389
+ a.nodes.map((n) => withLeadingComments(n, n.getText())),
1390
+ a.firstPosition,
1391
+ ]),
1392
+ nonEmptyTypeLiterals !== undefined && Arr.isNonEmpty(nonEmptyTypeLiterals)
1393
+ ? mapNullable(typeLiterals, (a) => [typeLiteralsWrappedWithReadonly, a.firstPosition])
1394
+ : undefined,
1395
+ emptyTypeLiterals !== undefined && Arr.isNonEmpty(emptyTypeLiterals)
1396
+ ? mapNullable(typeLiterals, (a) => [
1397
+ emptyTypeLiterals.map((n) => withLeadingComments(n, n.getText())),
1398
+
1399
+ a.firstPosition +
1400
+ (nonEmptyTypeLiterals !== undefined &&
1401
+ Arr.isNonEmpty(nonEmptyTypeLiterals)
1402
+ ? 0.5
1403
+ : 0),
1404
+ ])
1405
+ : undefined,
1406
+ mapNullable(others, (a) => [
1077
1407
 
1078
1408
 
1409
+ operator === '|'
1410
+ ? mergeDeepReadonlyUnionMembers(a.nodes, options.DeepReadonly.typeName, withLeadingComments)
1411
+ : a.nodes.map((n) => withLeadingComments(n, wrapWithParentheses(n.getText()))),
1412
+ a.firstPosition,
1413
+ ]),
1414
+ ]
1415
+ .filter((x) => x !== undefined)
1416
+ .toSorted((a, b) => a[1] - b[1])
1417
+ .flatMap((a) => a[0]);
1079
1418
 
1080
1419
 
1081
1420
 
1082
- const commentsBetween = (
1083
1421
 
1084
- sourceFile, from, to) => {
1085
- if (to <= from) {
1086
- return '';
1087
- }
1088
- const scanner = tsm.ts.createScanner(tsm.ts.ScriptTarget.Latest, false, tsm.ts.LanguageVariant.Standard, sourceFile.getFullText(), undefined, from, to - from);
1089
- const mut_comments = [];
1090
- while (scanner.scan() !== tsm.ts.SyntaxKind.EndOfFileToken) {
1091
- const token = scanner.getToken();
1092
1422
 
1093
- const separator = token === tsm.ts.SyntaxKind.SingleLineCommentTrivia
1094
- ? '\n'
1095
- : token === tsm.ts.SyntaxKind.MultiLineCommentTrivia
1096
- ? ' '
1097
- : undefined;
1098
- if (separator !== undefined) {
1099
- mut_comments.push(`${scanner.getTokenText()}${separator}`);
1100
- }
1101
- }
1102
- return mut_comments.join('');
1103
- };
1104
1423
 
1105
- const getDeepReadonlyTypeArg = (
1106
1424
 
1107
- node, deepReadonlyTypeName) => {
1108
- if (!node.isKind(tsm.SyntaxKind.TypeReference)) {
1109
- return undefined;
1110
- }
1111
- const typeName = node.getTypeName();
1112
- const typeArguments = node.getTypeArguments();
1113
- return typeName.isKind(tsm.SyntaxKind.Identifier) &&
1114
- typeName.getText() === deepReadonlyTypeName &&
1115
- Arr.isFixedLengthArray(1, typeArguments)
1116
- ? typeArguments[0]
1117
- : undefined;
1118
- };
1119
- const unionToString = ({ types: typesRaw, op, wrapWithReadonly, }) => {
1120
1425
 
1121
1426
 
1122
1427
 
1123
1428
 
1124
1429
 
1125
- const types = typesRaw.map((s) => s.trim());
1126
- return unionToStringImpl({ types, op, wrapWithReadonly });
1430
+
1431
+
1432
+
1433
+
1434
+
1435
+ options.replaceNode(node, unionToString({ types: sorted, op: operator, wrapWithReadonly: false }));
1127
1436
  };
1128
- const unionToStringImpl = ({ types, op, wrapWithReadonly, }) => Arr.isEmpty(types)
1129
- ? 'never'
1130
- : Arr.isFixedLengthArray(1, types)
1131
- ? wrapWithReadonly === false
1132
- ? wrapWithParentheses(types[0])
1133
- : `${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types[0]}>`
1134
- : wrapWithReadonly === false
1135
- ? wrapWithParentheses(types.join(` ${op} `))
1136
- : `${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types.join(` ${op} `)}>`;
1137
1437
 
1138
1438
  const transformParenthesizedTypeNode = (
1139
1439
 
@@ -1147,16 +1447,12 @@ node, readonlyContext, options) => {
1147
1447
  const T = node.getTypeNode();
1148
1448
  if (
1149
1449
 
1150
-
1151
1450
  T.isKind(tsm.SyntaxKind.TypeReference) ||
1152
1451
 
1153
-
1154
1452
  T.isKind(tsm.SyntaxKind.ArrayType) ||
1155
1453
 
1156
-
1157
1454
  T.isKind(tsm.SyntaxKind.TupleType) ||
1158
1455
 
1159
-
1160
1456
  isAtomicTypeNode(T) ||
1161
1457
 
1162
1458
 
@@ -1253,101 +1549,4 @@ node, readonlyModifier, readonlyContext, options) => {
1253
1549
  }
1254
1550
  }
1255
1551
  };
1256
- const checkIfPropertyNameShouldBeIgnored = (
1257
-
1258
- nameNode, options) => {
1259
-
1260
-
1261
- return (
1262
-
1263
- (nameNode.isKind(tsm.SyntaxKind.Identifier) &&
1264
- pipe(nameNode.getText()).map((nm) => options.mutablePropertyPrefixes.some((p) => nm.startsWith(p))).value) ||
1265
-
1266
- (nameNode.isKind(tsm.SyntaxKind.StringLiteral) &&
1267
- pipe(nameNode.getLiteralValue()).map((nm) => options.mutablePropertyPrefixes.some((p) => nm.startsWith(p))).value) ||
1268
-
1269
- (nameNode.isKind(tsm.SyntaxKind.PrivateIdentifier) &&
1270
- pipe(nameNode.getText()).map((nm) => options.mutablePropertyPrefixes.some((p) => nm.startsWith(p) || nm.startsWith(`#${p}`))).value) ||
1271
-
1272
- (nameNode.isKind(tsm.SyntaxKind.ComputedPropertyName) &&
1273
- pipe(nameNode.getExpression()).map((exp) => {
1274
- if (exp.isKind(tsm.SyntaxKind.StringLiteral) ||
1275
- exp.isKind(tsm.SyntaxKind.NoSubstitutionTemplateLiteral)) {
1276
- const nm = exp.getLiteralValue();
1277
- return options.mutablePropertyPrefixes.some((p) => nm.startsWith(p));
1278
- }
1279
- return false;
1280
- }).value));
1281
- };
1282
-
1283
-
1284
-
1285
-
1286
-
1287
-
1288
-
1289
-
1290
-
1291
-
1292
-
1293
-
1294
-
1295
-
1296
-
1297
- const isLeftUnwrapped = (
1298
-
1299
- node, options) => {
1300
- const members = node.getMembers();
1301
- const isMethodOrAccessor = (
1302
-
1303
- mb) => mb.isKind(tsm.SyntaxKind.MethodSignature) ||
1304
- mb.isKind(tsm.SyntaxKind.GetAccessor) ||
1305
- mb.isKind(tsm.SyntaxKind.SetAccessor);
1306
- const isIgnoredMember = (
1307
-
1308
- mb) => (mb.isKind(tsm.SyntaxKind.PropertySignature) || isMethodOrAccessor(mb)) &&
1309
- checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options);
1310
- const needsReadonlyWrapper = (
1311
-
1312
- mb) => !isIgnoredMember(mb) &&
1313
- (isMethodOrAccessor(mb) ||
1314
- ((mb.isKind(tsm.SyntaxKind.PropertySignature) ||
1315
- mb.isKind(tsm.SyntaxKind.IndexSignature)) &&
1316
- !mb.hasModifier(tsm.SyntaxKind.ReadonlyKeyword) &&
1317
- hasDisableNextLineComment(mb)));
1318
- return members.some(isIgnoredMember) && !members.some(needsReadonlyWrapper);
1319
- };
1320
-
1321
- const hasDisableNextLineCommentAround = (node) => {
1322
- if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
1323
- return true;
1324
- }
1325
- const parent = node.getParent();
1326
- return (parent?.isKind(tsm.SyntaxKind.ParenthesizedType) === true &&
1327
- hasDisableNextLineCommentAround(parent));
1328
- };
1329
-
1330
-
1331
-
1332
-
1333
-
1334
-
1335
-
1336
- const takesOffReadonly = (
1337
-
1338
- node, options) => isLeftUnwrapped(node, options) &&
1339
- !hasCallOrConstructSignature(node) &&
1340
- !hasDisableNextLineCommentAround(node);
1341
-
1342
- const hasMutableTypeName = (name, options) => name !== undefined &&
1343
- options.mutableTypePrefixes.some((p) => name.startsWith(p));
1344
-
1345
-
1346
-
1347
-
1348
- const mutablePrefixesOfParameter = (
1349
-
1350
- param, options) => param.isParameterProperty()
1351
- ? options.mutablePropertyPrefixes
1352
- : options.mutableVariablePrefixes;
1353
1552
  //# sourceMappingURL=convert-to-readonly.mjs.map