ts-codemod-lib 2.2.2 → 2.2.4

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 (42) hide show
  1. package/README.md +0 -12
  2. package/dist/cmd/append-as-const.mjs +1 -1
  3. package/dist/cmd/append-as-const.mjs.map +1 -1
  4. package/dist/cmd/convert-interface-to-type.mjs +1 -1
  5. package/dist/cmd/convert-interface-to-type.mjs.map +1 -1
  6. package/dist/cmd/convert-to-readonly.mjs +1 -1
  7. package/dist/cmd/convert-to-readonly.mjs.map +1 -1
  8. package/dist/cmd/replace-any-with-unknown.mjs +1 -1
  9. package/dist/cmd/replace-any-with-unknown.mjs.map +1 -1
  10. package/dist/cmd/replace-record-with-unknown-record.mjs +1 -1
  11. package/dist/cmd/replace-record-with-unknown-record.mjs.map +1 -1
  12. package/dist/cmd/run-transformer-cli.mjs +1 -1
  13. package/dist/cmd/run-transformer-cli.mjs.map +1 -1
  14. package/dist/functions/ast-transformers/append-as-const.mjs +1 -1
  15. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  16. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +4 -4
  17. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
  18. package/dist/functions/ast-transformers/convert-to-readonly.mjs +10 -10
  19. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  20. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -1
  21. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +13 -13
  22. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  23. package/dist/functions/ast-transformers/transform-source-code.mjs +2 -2
  24. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  25. package/dist/functions/functions/is-as-const-node.mjs +1 -1
  26. package/dist/functions/functions/is-as-const-node.mjs.map +1 -1
  27. package/dist/functions/functions/is-readonly-node.mjs +1 -1
  28. package/dist/functions/functions/is-readonly-node.mjs.map +1 -1
  29. package/package.json +47 -40
  30. package/src/cmd/append-as-const.mts +1 -1
  31. package/src/cmd/convert-interface-to-type.mts +1 -1
  32. package/src/cmd/convert-to-readonly.mts +1 -1
  33. package/src/cmd/replace-any-with-unknown.mts +1 -1
  34. package/src/cmd/replace-record-with-unknown-record.mts +1 -1
  35. package/src/cmd/run-transformer-cli.mts +1 -1
  36. package/src/functions/ast-transformers/append-as-const.mts +1 -1
  37. package/src/functions/ast-transformers/convert-interface-to-type.mts +4 -4
  38. package/src/functions/ast-transformers/convert-to-readonly.mts +11 -16
  39. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +18 -29
  40. package/src/functions/ast-transformers/transform-source-code.mts +2 -2
  41. package/src/functions/functions/is-as-const-node.mts +1 -1
  42. package/src/functions/functions/is-readonly-node.mts +1 -1
@@ -16,7 +16,7 @@ const isAsConstNode = (node) => {
16
16
  if (!typeNameNode.isKind(tsm.SyntaxKind.Identifier)) {
17
17
  return false;
18
18
  }
19
- return typeNameNode.getText() === "const" && Arr.isArrayOfLength(typeNode.getTypeArguments(), 0);
19
+ return typeNameNode.getText() === "const" && Arr.isEmpty(typeNode.getTypeArguments());
20
20
  };
21
21
 
22
22
  export { isAsConstNode };
@@ -1 +1 @@
1
- {"version":3,"file":"is-as-const-node.mjs","sources":["../../../src/functions/functions/is-as-const-node.mts"],"sourcesContent":["import { Arr } from 'ts-data-forge';\nimport * as ts from 'ts-morph';\n\nexport const isAsConstNode = (\n node: ts.Node,\n): node is ts.AsExpression &\n Readonly<{\n type: ts.TypeReferenceNode &\n Readonly<{\n typeName: ts.Identifier &\n Readonly<{\n text: 'const';\n }>;\n typeArguments: undefined;\n }>;\n }> => {\n if (!node.isKind(ts.SyntaxKind.AsExpression)) {\n return false;\n }\n\n // 2. Get the 'type' node from the AsExpression\n const typeNode = node.getTypeNode();\n\n if (typeNode === undefined) {\n return false; // Should have a type node for 'as const'\n }\n\n // 3. Check if the 'type' node is a TypeReference\n if (!typeNode.isKind(ts.SyntaxKind.TypeReference)) {\n return false;\n }\n\n // 4. Get the 'typeName' from the TypeReference\n const typeNameNode = typeNode.getTypeName();\n\n // 5. Check if the 'typeName' is an Identifier\n if (!typeNameNode.isKind(ts.SyntaxKind.Identifier)) {\n // 'as const' uses a simple Identifier 'const', not a QualifiedName\n return false;\n }\n\n // 6. Check if the Identifier's text is 'const'\n // and that there are no type arguments (as const doesn't have them)\n return (\n typeNameNode.getText() === 'const' &&\n Arr.isArrayOfLength(typeNode.getTypeArguments(), 0)\n );\n};\n"],"names":["ts"],"mappings":";;;AAGO,MAAM,aAAA,GAAgB,CAC3B,IAAA,KAWM;AACN,EAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAOA,GAAA,CAAG,UAAA,CAAW,YAAY,CAAA,EAAG;AAC5C,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,QAAA,GAAW,KAAK,WAAA,EAAY;AAElC,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,CAAC,QAAA,CAAS,MAAA,CAAOA,GAAA,CAAG,UAAA,CAAW,aAAa,CAAA,EAAG;AACjD,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,YAAA,GAAe,SAAS,WAAA,EAAY;AAG1C,EAAA,IAAI,CAAC,YAAA,CAAa,MAAA,CAAOA,GAAA,CAAG,UAAA,CAAW,UAAU,CAAA,EAAG;AAElD,IAAA,OAAO,KAAA;AAAA,EACT;AAIA,EAAA,OACE,YAAA,CAAa,SAAQ,KAAM,OAAA,IAC3B,IAAI,eAAA,CAAgB,QAAA,CAAS,gBAAA,EAAiB,EAAG,CAAC,CAAA;AAEtD;;;;"}
1
+ {"version":3,"file":"is-as-const-node.mjs","sources":["../../../src/functions/functions/is-as-const-node.mts"],"sourcesContent":["import { Arr } from 'ts-data-forge';\nimport * as ts from 'ts-morph';\n\nexport const isAsConstNode = (\n node: ts.Node,\n): node is ts.AsExpression &\n Readonly<{\n type: ts.TypeReferenceNode &\n Readonly<{\n typeName: ts.Identifier &\n Readonly<{\n text: 'const';\n }>;\n typeArguments: undefined;\n }>;\n }> => {\n if (!node.isKind(ts.SyntaxKind.AsExpression)) {\n return false;\n }\n\n // 2. Get the 'type' node from the AsExpression\n const typeNode = node.getTypeNode();\n\n if (typeNode === undefined) {\n return false; // Should have a type node for 'as const'\n }\n\n // 3. Check if the 'type' node is a TypeReference\n if (!typeNode.isKind(ts.SyntaxKind.TypeReference)) {\n return false;\n }\n\n // 4. Get the 'typeName' from the TypeReference\n const typeNameNode = typeNode.getTypeName();\n\n // 5. Check if the 'typeName' is an Identifier\n if (!typeNameNode.isKind(ts.SyntaxKind.Identifier)) {\n // 'as const' uses a simple Identifier 'const', not a QualifiedName\n return false;\n }\n\n // 6. Check if the Identifier's text is 'const'\n // and that there are no type arguments (as const doesn't have them)\n return (\n typeNameNode.getText() === 'const' &&\n Arr.isEmpty(typeNode.getTypeArguments())\n );\n};\n"],"names":["ts"],"mappings":";;;AAGO,MAAM,aAAA,GAAgB,CAC3B,IAAA,KAWM;AACN,EAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAOA,GAAA,CAAG,UAAA,CAAW,YAAY,CAAA,EAAG;AAC5C,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,QAAA,GAAW,KAAK,WAAA,EAAY;AAElC,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,CAAC,QAAA,CAAS,MAAA,CAAOA,GAAA,CAAG,UAAA,CAAW,aAAa,CAAA,EAAG;AACjD,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,MAAM,YAAA,GAAe,SAAS,WAAA,EAAY;AAG1C,EAAA,IAAI,CAAC,YAAA,CAAa,MAAA,CAAOA,GAAA,CAAG,UAAA,CAAW,UAAU,CAAA,EAAG;AAElD,IAAA,OAAO,KAAA;AAAA,EACT;AAIA,EAAA,OACE,YAAA,CAAa,SAAQ,KAAM,OAAA,IAC3B,IAAI,OAAA,CAAQ,QAAA,CAAS,kBAAkB,CAAA;AAE3C;;;;"}
@@ -13,7 +13,7 @@ const isReadonlyTypeReferenceNode = (node) => {
13
13
  }
14
14
  const typeName = node.getTypeName();
15
15
  const typeArguments = node.getTypeArguments();
16
- return typeName.isKind(tsm.SyntaxKind.Identifier) && typeName.getText() === "Readonly" && Arr.isArrayOfLength(typeArguments, 1);
16
+ return typeName.isKind(tsm.SyntaxKind.Identifier) && typeName.getText() === "Readonly" && Arr.isFixedLengthArray(1, typeArguments);
17
17
  };
18
18
 
19
19
  export { isReadonlyArrayTypeNode, isReadonlyTupleOrArrayTypeNode, isReadonlyTupleTypeNode, isReadonlyTypeReferenceNode, isShallowReadonlyTypeNode };
@@ -1 +1 @@
1
- {"version":3,"file":"is-readonly-node.mjs","sources":["../../../src/functions/functions/is-readonly-node.mts"],"sourcesContent":["import { Arr, expectType } from 'ts-data-forge';\nimport * as tsm from 'ts-morph';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { isAtomicTypeNode } from './is-atomic-type-node.mjs';\n\nexport const isShallowReadonlyTypeNode = (node: tsm.Node): boolean =>\n isReadonlyTupleOrArrayTypeNode(node) ||\n isReadonlyTypeReferenceNode(node) ||\n isAtomicTypeNode(node);\n\nexport const isReadonlyTupleOrArrayTypeNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyArrayTypeNode | ReadonlyTupleTypeNode =>\n node.isKind(tsm.SyntaxKind.TypeOperator) &&\n node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword &&\n (node.getTypeNode().isKind(tsm.SyntaxKind.ArrayType) || // Use optional chaining and isKind\n node.getTypeNode().isKind(tsm.SyntaxKind.TupleType));\n\nexport type ReadonlyArrayTypeNode = tsm.TypeNode &\n Omit<tsm.TypeOperatorTypeNode, 'getOperator' | 'getTypeNode'> &\n Readonly<{\n getOperator: () => tsm.SyntaxKind.ReadonlyKeyword;\n getTypeNode: () => tsm.ArrayTypeNode;\n }>;\n\nexport const isReadonlyArrayTypeNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyArrayTypeNode =>\n node.isKind(tsm.SyntaxKind.TypeOperator) &&\n node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword &&\n node.getTypeNode().isKind(tsm.SyntaxKind.ArrayType);\n\n// Helper to get a specific type node from source code\nconst getTypeNodeFromSource = (\n sourceCode: string,\n typeName: string,\n): tsm.Node => {\n const project = new tsm.Project({ useInMemoryFileSystem: true });\n\n const sourceFile = project.createSourceFile('test.ts', sourceCode);\n\n const typeAlias = sourceFile.getTypeAliasOrThrow(typeName);\n\n return typeAlias.getTypeNodeOrThrow();\n};\n\nif (import.meta.vitest !== undefined) {\n describe('isReadonlyArrayTypeNode', () => {\n test('should return true for readonly array type', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly number[];',\n 'Test',\n );\n\n if (isReadonlyArrayTypeNode(node)) {\n const _operator = node.getOperator();\n\n expectType<typeof _operator, tsm.SyntaxKind.ReadonlyKeyword>('=');\n\n const _typeNode = node.getTypeNode();\n\n expectType<typeof _typeNode, tsm.ArrayTypeNode>('=');\n }\n\n assert.isTrue(node.isKind(tsm.SyntaxKind.TypeOperator));\n\n if (!isReadonlyArrayTypeNode(node)) {\n throw new Error('node should be ReadonlyArrayTypeNode');\n }\n\n expect(node.getOperator()).toBe(tsm.SyntaxKind.ReadonlyKeyword);\n\n assert.isTrue(node.getTypeNode().isKind(tsm.SyntaxKind.ArrayType));\n });\n\n test('should return false for non-readonly array', () => {\n const node = getTypeNodeFromSource('type Test = string[];', 'Test');\n\n assert.isFalse(isReadonlyArrayTypeNode(node));\n });\n\n test('should return false for readonly tuple', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly [string];',\n 'Test',\n );\n\n assert.isFalse(isReadonlyArrayTypeNode(node));\n });\n });\n}\n\nexport type ReadonlyTupleTypeNode = tsm.TypeNode &\n Omit<tsm.TypeOperatorTypeNode, 'getOperator' | 'getTypeNode'> &\n Readonly<{\n getOperator: () => tsm.SyntaxKind.ReadonlyKeyword;\n getTypeNode: () => tsm.TupleTypeNode;\n }>;\n\nexport const isReadonlyTupleTypeNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyTupleTypeNode =>\n node.isKind(tsm.SyntaxKind.TypeOperator) &&\n node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword &&\n node.getTypeNode().isKind(tsm.SyntaxKind.TupleType);\n\nif (import.meta.vitest !== undefined) {\n describe('isReadonlyTupleTypeNode', () => {\n test('should return true for readonly tuple type', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly [number, string];',\n 'Test',\n );\n\n assert.isTrue(isReadonlyTupleTypeNode(node));\n\n if (isReadonlyTupleTypeNode(node)) {\n const _operator = node.getOperator();\n\n expectType<typeof _operator, tsm.SyntaxKind.ReadonlyKeyword>('=');\n\n const _typeNode = node.getTypeNode();\n\n expectType<typeof _typeNode, tsm.TupleTypeNode>('=');\n }\n\n assert.isTrue(node.isKind(tsm.SyntaxKind.TypeOperator));\n\n if (!isReadonlyTupleTypeNode(node)) {\n throw new Error('node should be ReadonlyTupleTypeNode');\n }\n\n expect(node.getOperator()).toBe(tsm.SyntaxKind.ReadonlyKeyword);\n\n assert.isTrue(node.getTypeNode().isKind(tsm.SyntaxKind.TupleType));\n });\n\n test('should return false for non-readonly tuple', () => {\n const node = getTypeNodeFromSource('type Test = [boolean];', 'Test');\n\n assert.isFalse(isReadonlyTupleTypeNode(node));\n });\n\n test('should return false for readonly array', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly number[];',\n 'Test',\n );\n\n assert.isFalse(isReadonlyTupleTypeNode(node));\n });\n });\n}\n\nexport type ReadonlyTypeReferenceNode = tsm.TypeNode &\n Omit<tsm.TypeReferenceNode, 'getTypeName' | 'getTypeArguments'> &\n Readonly<{\n getTypeName: () => Omit<tsm.Identifier, 'getText'> &\n Readonly<{\n getText: () => 'Readonly';\n }>;\n getTypeArguments: () => readonly [tsm.TypeNode];\n }>;\n\nexport const isReadonlyTypeReferenceNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyTypeReferenceNode => {\n if (!node.isKind(tsm.SyntaxKind.TypeReference)) {\n return false;\n }\n\n const typeName = node.getTypeName();\n\n const typeArguments = node.getTypeArguments();\n\n return (\n typeName.isKind(tsm.SyntaxKind.Identifier) &&\n typeName.getText() === 'Readonly' &&\n Arr.isArrayOfLength(typeArguments, 1)\n );\n};\n\nif (import.meta.vitest !== undefined) {\n describe('isReadonlyTypeReferenceNode', () => {\n test('should return true for Readonly<T>', () => {\n const node = getTypeNodeFromSource(\n 'type Test = Readonly<{ x: number }>;',\n 'Test',\n );\n\n assert.isTrue(isReadonlyTypeReferenceNode(node));\n\n if (isReadonlyTypeReferenceNode(node)) {\n const _typeNameText = node.getTypeName().getText();\n\n expectType<typeof _typeNameText, 'Readonly'>('=');\n\n const _typeArguments = node.getTypeArguments();\n\n expectType<\n typeof _typeArguments,\n readonly [tsm.TypeNode<tsm.ts.TypeNode>]\n >('=');\n }\n\n assert.isTrue(node.isKind(tsm.SyntaxKind.TypeReference));\n\n if (!isReadonlyTypeReferenceNode(node)) {\n throw new Error('node should be ReadonlyNode');\n }\n\n expect(node.getTypeName().getText()).toBe('Readonly');\n\n expect(node.getTypeArguments()).toHaveLength(1);\n });\n\n test('should return false for other type references', () => {\n const node = getTypeNodeFromSource(\n 'type Test = Partial<{ y: string }>;',\n 'Test',\n );\n\n assert.isFalse(isReadonlyTypeReferenceNode(node));\n });\n\n test('should return false for Readonly without type arguments', () => {\n // Note: This is syntactically incorrect TS, but testing the guard\n const node = getTypeNodeFromSource('type Test = Readonly;', 'Test');\n\n assert.isFalse(isReadonlyTypeReferenceNode(node));\n });\n\n test('should return false for Readonly with multiple type arguments', () => {\n // Note: This is syntactically incorrect TS, but testing the guard\n const project = new tsm.Project({ useInMemoryFileSystem: true });\n\n // Need to create manually as TS parser might reject Readonly<A, B>\n const sourceFile = project.createSourceFile(\n 'test.ts',\n 'type Test = Readonly<string, number>;',\n );\n\n const node = sourceFile.getTypeAliasOrThrow('Test').getTypeNodeOrThrow();\n\n assert.isFalse(isReadonlyTypeReferenceNode(node));\n });\n });\n}\n"],"names":[],"mappings":";;;;MAKa,yBAAA,GAA4B,CAAC,IAAA,KACxC,8BAAA,CAA+B,IAAI,KACnC,2BAAA,CAA4B,IAAI,CAAA,IAChC,gBAAA,CAAiB,IAAI;MAEV,8BAAA,GAAiC,CAC5C,IAAA,KAEA,IAAA,CAAK,OAAO,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IACvC,IAAA,CAAK,aAAY,KAAM,GAAA,CAAI,WAAW,eAAA,KACrC,IAAA,CAAK,aAAY,CAAE,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAS,CAAA;AACjD,IAAA,CAAK,WAAA,EAAY,CAAE,MAAA,CAAO,GAAA,CAAI,WAAW,SAAS,CAAA;MASzC,uBAAA,GAA0B,CACrC,IAAA,KAEA,IAAA,CAAK,OAAO,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IACvC,IAAA,CAAK,aAAY,KAAM,GAAA,CAAI,WAAW,eAAA,IACtC,IAAA,CAAK,aAAY,CAAE,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAS;MAqEvC,uBAAA,GAA0B,CACrC,IAAA,KAEA,IAAA,CAAK,OAAO,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IACvC,IAAA,CAAK,aAAY,KAAM,GAAA,CAAI,WAAW,eAAA,IACtC,IAAA,CAAK,aAAY,CAAE,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAS;AA4D7C,MAAM,2BAAA,GAA8B,CACzC,IAAA,KACsC;AACtC,EAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,aAAa,CAAA,EAAG;AAC9C,IAAA,OAAO,KAAA;AAAA,EAAA;AAGT,EAAA,MAAM,QAAA,GAAW,KAAK,WAAA,EAAY;AAElC,EAAA,MAAM,aAAA,GAAgB,KAAK,gBAAA,EAAiB;AAE5C,EAAA,OACE,QAAA,CAAS,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,UAAU,CAAA,IACzC,QAAA,CAAS,OAAA,EAAQ,KAAM,UAAA,IACvB,GAAA,CAAI,eAAA,CAAgB,eAAe,CAAC,CAAA;AAExC;;;;"}
1
+ {"version":3,"file":"is-readonly-node.mjs","sources":["../../../src/functions/functions/is-readonly-node.mts"],"sourcesContent":["import { Arr, expectType } from 'ts-data-forge';\nimport * as tsm from 'ts-morph';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport { isAtomicTypeNode } from './is-atomic-type-node.mjs';\n\nexport const isShallowReadonlyTypeNode = (node: tsm.Node): boolean =>\n isReadonlyTupleOrArrayTypeNode(node) ||\n isReadonlyTypeReferenceNode(node) ||\n isAtomicTypeNode(node);\n\nexport const isReadonlyTupleOrArrayTypeNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyArrayTypeNode | ReadonlyTupleTypeNode =>\n node.isKind(tsm.SyntaxKind.TypeOperator) &&\n node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword &&\n (node.getTypeNode().isKind(tsm.SyntaxKind.ArrayType) || // Use optional chaining and isKind\n node.getTypeNode().isKind(tsm.SyntaxKind.TupleType));\n\nexport type ReadonlyArrayTypeNode = tsm.TypeNode &\n Omit<tsm.TypeOperatorTypeNode, 'getOperator' | 'getTypeNode'> &\n Readonly<{\n getOperator: () => tsm.SyntaxKind.ReadonlyKeyword;\n getTypeNode: () => tsm.ArrayTypeNode;\n }>;\n\nexport const isReadonlyArrayTypeNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyArrayTypeNode =>\n node.isKind(tsm.SyntaxKind.TypeOperator) &&\n node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword &&\n node.getTypeNode().isKind(tsm.SyntaxKind.ArrayType);\n\n// Helper to get a specific type node from source code\nconst getTypeNodeFromSource = (\n sourceCode: string,\n typeName: string,\n): tsm.Node => {\n const project = new tsm.Project({ useInMemoryFileSystem: true });\n\n const sourceFile = project.createSourceFile('test.ts', sourceCode);\n\n const typeAlias = sourceFile.getTypeAliasOrThrow(typeName);\n\n return typeAlias.getTypeNodeOrThrow();\n};\n\nif (import.meta.vitest !== undefined) {\n describe('isReadonlyArrayTypeNode', () => {\n test('should return true for readonly array type', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly number[];',\n 'Test',\n );\n\n if (isReadonlyArrayTypeNode(node)) {\n const _operator = node.getOperator();\n\n expectType<typeof _operator, tsm.SyntaxKind.ReadonlyKeyword>('=');\n\n const _typeNode = node.getTypeNode();\n\n expectType<typeof _typeNode, tsm.ArrayTypeNode>('=');\n }\n\n assert.isTrue(node.isKind(tsm.SyntaxKind.TypeOperator));\n\n if (!isReadonlyArrayTypeNode(node)) {\n throw new Error('node should be ReadonlyArrayTypeNode');\n }\n\n expect(node.getOperator()).toBe(tsm.SyntaxKind.ReadonlyKeyword);\n\n assert.isTrue(node.getTypeNode().isKind(tsm.SyntaxKind.ArrayType));\n });\n\n test('should return false for non-readonly array', () => {\n const node = getTypeNodeFromSource('type Test = string[];', 'Test');\n\n assert.isFalse(isReadonlyArrayTypeNode(node));\n });\n\n test('should return false for readonly tuple', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly [string];',\n 'Test',\n );\n\n assert.isFalse(isReadonlyArrayTypeNode(node));\n });\n });\n}\n\nexport type ReadonlyTupleTypeNode = tsm.TypeNode &\n Omit<tsm.TypeOperatorTypeNode, 'getOperator' | 'getTypeNode'> &\n Readonly<{\n getOperator: () => tsm.SyntaxKind.ReadonlyKeyword;\n getTypeNode: () => tsm.TupleTypeNode;\n }>;\n\nexport const isReadonlyTupleTypeNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyTupleTypeNode =>\n node.isKind(tsm.SyntaxKind.TypeOperator) &&\n node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword &&\n node.getTypeNode().isKind(tsm.SyntaxKind.TupleType);\n\nif (import.meta.vitest !== undefined) {\n describe('isReadonlyTupleTypeNode', () => {\n test('should return true for readonly tuple type', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly [number, string];',\n 'Test',\n );\n\n assert.isTrue(isReadonlyTupleTypeNode(node));\n\n if (isReadonlyTupleTypeNode(node)) {\n const _operator = node.getOperator();\n\n expectType<typeof _operator, tsm.SyntaxKind.ReadonlyKeyword>('=');\n\n const _typeNode = node.getTypeNode();\n\n expectType<typeof _typeNode, tsm.TupleTypeNode>('=');\n }\n\n assert.isTrue(node.isKind(tsm.SyntaxKind.TypeOperator));\n\n if (!isReadonlyTupleTypeNode(node)) {\n throw new Error('node should be ReadonlyTupleTypeNode');\n }\n\n expect(node.getOperator()).toBe(tsm.SyntaxKind.ReadonlyKeyword);\n\n assert.isTrue(node.getTypeNode().isKind(tsm.SyntaxKind.TupleType));\n });\n\n test('should return false for non-readonly tuple', () => {\n const node = getTypeNodeFromSource('type Test = [boolean];', 'Test');\n\n assert.isFalse(isReadonlyTupleTypeNode(node));\n });\n\n test('should return false for readonly array', () => {\n const node = getTypeNodeFromSource(\n 'type Test = readonly number[];',\n 'Test',\n );\n\n assert.isFalse(isReadonlyTupleTypeNode(node));\n });\n });\n}\n\nexport type ReadonlyTypeReferenceNode = tsm.TypeNode &\n Omit<tsm.TypeReferenceNode, 'getTypeName' | 'getTypeArguments'> &\n Readonly<{\n getTypeName: () => Omit<tsm.Identifier, 'getText'> &\n Readonly<{\n getText: () => 'Readonly';\n }>;\n getTypeArguments: () => readonly [tsm.TypeNode];\n }>;\n\nexport const isReadonlyTypeReferenceNode = (\n node: DeepReadonly<tsm.Node>,\n): node is ReadonlyTypeReferenceNode => {\n if (!node.isKind(tsm.SyntaxKind.TypeReference)) {\n return false;\n }\n\n const typeName = node.getTypeName();\n\n const typeArguments = node.getTypeArguments();\n\n return (\n typeName.isKind(tsm.SyntaxKind.Identifier) &&\n typeName.getText() === 'Readonly' &&\n Arr.isFixedLengthArray(1, typeArguments)\n );\n};\n\nif (import.meta.vitest !== undefined) {\n describe('isReadonlyTypeReferenceNode', () => {\n test('should return true for Readonly<T>', () => {\n const node = getTypeNodeFromSource(\n 'type Test = Readonly<{ x: number }>;',\n 'Test',\n );\n\n assert.isTrue(isReadonlyTypeReferenceNode(node));\n\n if (isReadonlyTypeReferenceNode(node)) {\n const _typeNameText = node.getTypeName().getText();\n\n expectType<typeof _typeNameText, 'Readonly'>('=');\n\n const _typeArguments = node.getTypeArguments();\n\n expectType<\n typeof _typeArguments,\n readonly [tsm.TypeNode<tsm.ts.TypeNode>]\n >('=');\n }\n\n assert.isTrue(node.isKind(tsm.SyntaxKind.TypeReference));\n\n if (!isReadonlyTypeReferenceNode(node)) {\n throw new Error('node should be ReadonlyNode');\n }\n\n expect(node.getTypeName().getText()).toBe('Readonly');\n\n expect(node.getTypeArguments()).toHaveLength(1);\n });\n\n test('should return false for other type references', () => {\n const node = getTypeNodeFromSource(\n 'type Test = Partial<{ y: string }>;',\n 'Test',\n );\n\n assert.isFalse(isReadonlyTypeReferenceNode(node));\n });\n\n test('should return false for Readonly without type arguments', () => {\n // Note: This is syntactically incorrect TS, but testing the guard\n const node = getTypeNodeFromSource('type Test = Readonly;', 'Test');\n\n assert.isFalse(isReadonlyTypeReferenceNode(node));\n });\n\n test('should return false for Readonly with multiple type arguments', () => {\n // Note: This is syntactically incorrect TS, but testing the guard\n const project = new tsm.Project({ useInMemoryFileSystem: true });\n\n // Need to create manually as TS parser might reject Readonly<A, B>\n const sourceFile = project.createSourceFile(\n 'test.ts',\n 'type Test = Readonly<string, number>;',\n );\n\n const node = sourceFile.getTypeAliasOrThrow('Test').getTypeNodeOrThrow();\n\n assert.isFalse(isReadonlyTypeReferenceNode(node));\n });\n });\n}\n"],"names":[],"mappings":";;;;MAKa,yBAAA,GAA4B,CAAC,IAAA,KACxC,8BAAA,CAA+B,IAAI,KACnC,2BAAA,CAA4B,IAAI,CAAA,IAChC,gBAAA,CAAiB,IAAI;MAEV,8BAAA,GAAiC,CAC5C,IAAA,KAEA,IAAA,CAAK,OAAO,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IACvC,IAAA,CAAK,aAAY,KAAM,GAAA,CAAI,WAAW,eAAA,KACrC,IAAA,CAAK,aAAY,CAAE,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAS,CAAA;AACjD,IAAA,CAAK,WAAA,EAAY,CAAE,MAAA,CAAO,GAAA,CAAI,WAAW,SAAS,CAAA;MASzC,uBAAA,GAA0B,CACrC,IAAA,KAEA,IAAA,CAAK,OAAO,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IACvC,IAAA,CAAK,aAAY,KAAM,GAAA,CAAI,WAAW,eAAA,IACtC,IAAA,CAAK,aAAY,CAAE,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAS;MAqEvC,uBAAA,GAA0B,CACrC,IAAA,KAEA,IAAA,CAAK,OAAO,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IACvC,IAAA,CAAK,aAAY,KAAM,GAAA,CAAI,WAAW,eAAA,IACtC,IAAA,CAAK,aAAY,CAAE,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAS;AA4D7C,MAAM,2BAAA,GAA8B,CACzC,IAAA,KACsC;AACtC,EAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,aAAa,CAAA,EAAG;AAC9C,IAAA,OAAO,KAAA;AAAA,EAAA;AAGT,EAAA,MAAM,QAAA,GAAW,KAAK,WAAA,EAAY;AAElC,EAAA,MAAM,aAAA,GAAgB,KAAK,gBAAA,EAAiB;AAE5C,EAAA,OACE,QAAA,CAAS,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,UAAU,CAAA,IACzC,QAAA,CAAS,OAAA,EAAQ,KAAM,UAAA,IACvB,GAAA,CAAI,kBAAA,CAAmB,GAAG,aAAa,CAAA;AAE3C;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -24,6 +24,10 @@
24
24
  "import": {
25
25
  "types": "./dist/types.d.mts",
26
26
  "default": "./dist/entry-point.mjs"
27
+ },
28
+ "default": {
29
+ "types": "./dist/types.d.mts",
30
+ "default": "./dist/entry-point.mjs"
27
31
  }
28
32
  }
29
33
  },
@@ -43,31 +47,33 @@
43
47
  "LICENSE"
44
48
  ],
45
49
  "scripts": {
50
+ "agents:gen": "tsx ./scripts/cmd/gen-agents-md.mts",
51
+ "agents:sync": "tsx ./scripts/cmd/sync-agent-config.mts",
46
52
  "build": "tsx ./scripts/cmd/build.mts",
47
53
  "build:min": "tsx ./scripts/cmd/build.mts --skip-check",
48
- "check:ext": "tsx ./scripts/cmd/check-ext.mts",
49
54
  "check-all": "tsx ./scripts/cmd/check-all.mts",
55
+ "check:ext": "tsx ./scripts/cmd/check-ext.mts",
50
56
  "clean": "pnpm run /clean:.*/",
51
57
  "clean:logs": "npx rimraf ./*.log",
52
58
  "codemod": "run-s codemod:uncommitted:* fmt",
53
59
  "codemod:diff": "run-s codemod:diff:* fmt",
54
- "codemod:diff:as-const": "tsx ./src/cmd/append-as-const.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
55
60
  "codemod:diff:any-to-unknown": "tsx ./src/cmd/replace-any-with-unknown.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
56
- "codemod:diff:unknown-record": "tsx ./src/cmd/replace-record-with-unknown-record.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
61
+ "codemod:diff:as-const": "tsx ./src/cmd/append-as-const.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
57
62
  "codemod:diff:interface-to-type": "tsx ./src/cmd/convert-interface-to-type.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
58
63
  "codemod:diff:readonly": "tsx ./src/cmd/convert-to-readonly.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
64
+ "codemod:diff:unknown-record": "tsx ./src/cmd/replace-record-with-unknown-record.mts --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
59
65
  "codemod:full": "run-s codemod:full:* fmt",
60
- "codemod:full:as-const": "tsx ./src/cmd/append-as-const.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
61
66
  "codemod:full:any-to-unknown": "tsx ./src/cmd/replace-any-with-unknown.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
62
- "codemod:full:unknown-record": "tsx ./src/cmd/replace-record-with-unknown-record.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
67
+ "codemod:full:as-const": "tsx ./src/cmd/append-as-const.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
63
68
  "codemod:full:interface-to-type": "tsx ./src/cmd/convert-interface-to-type.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
64
69
  "codemod:full:readonly": "tsx ./src/cmd/convert-to-readonly.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
65
- "codemod:uncommitted:as-const": "tsx ./src/cmd/append-as-const.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
70
+ "codemod:full:unknown-record": "tsx ./src/cmd/replace-record-with-unknown-record.mts '{src,scripts,samples,test}/**/*.{mts,tsx}'",
66
71
  "codemod:uncommitted:any-to-unknown": "tsx ./src/cmd/replace-any-with-unknown.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
67
- "codemod:uncommitted:unknown-record": "tsx ./src/cmd/replace-record-with-unknown-record.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
72
+ "codemod:uncommitted:as-const": "tsx ./src/cmd/append-as-const.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
68
73
  "codemod:uncommitted:interface-to-type": "tsx ./src/cmd/convert-interface-to-type.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
69
74
  "codemod:uncommitted:readonly": "tsx ./src/cmd/convert-to-readonly.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
70
- "cspell": "cspell \"**\" --gitignore --gitignore-root ./ --no-progress",
75
+ "codemod:uncommitted:unknown-record": "tsx ./src/cmd/replace-record-with-unknown-record.mts --uncommitted '{src,scripts,samples,test}/**/*.{mts,tsx}'",
76
+ "cspell": "cspell \"{**,.changeset/**/*,.github/**/*}\" --gitignore --gitignore-root ./ --no-progress",
71
77
  "doc": "tsx ./scripts/cmd/gen-docs.mts",
72
78
  "doc:embed": "tsx ./scripts/cmd/embed-examples.mts",
73
79
  "doc:preview": "vite preview --config ./configs/vite.doc.config.mts",
@@ -75,19 +81,14 @@
75
81
  "fmt": "format-uncommitted",
76
82
  "fmt:diff": "format-diff-from origin/main",
77
83
  "fmt:full": "prettier --write .",
78
- "gh:apply-all": "gh-apply-all",
79
- "gh:apply-repository-settings": "gh-apply-repository-settings",
80
- "gh:apply-rulesets": "gh-apply-rulesets",
81
- "gh:apply-variables": "gh-apply-variables",
82
- "gh:backup-all": "gh-backup-all",
83
- "gh:backup-repository-settings": "gh-backup-repository-settings",
84
- "gh:backup-rulesets": "gh-backup-rulesets",
85
84
  "gi": "run-s gi:src fmt",
86
85
  "gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude cmd",
87
86
  "lint": "eslint .",
88
87
  "lint:fix": "eslint . --fix",
89
88
  "md": "markdownlint-cli2",
90
89
  "prepare-release": "run-s sync-cli-versions fmt build doc",
90
+ "repo-settings:apply": "repo-settings apply",
91
+ "repo-settings:backup": "repo-settings backup",
91
92
  "run:cmd": "tsx ./src/cmd/convert-to-readonly.mts ./test/sample1.mts",
92
93
  "run:test": "tsx ./scripts/test-code/transform-test-code.mts",
93
94
  "sync-cli-versions": "tsx scripts/cmd/sync-cli-versions.mts",
@@ -100,56 +101,58 @@
100
101
  "tsc": "node ./node_modules/typescript-native/bin/tsc --noEmit",
101
102
  "tscw": "node ./node_modules/typescript-native/bin/tsc --noEmit --watch -p ./tsconfig.json",
102
103
  "type-check": "node ./node_modules/typescript-native/bin/tsc --noEmit",
103
- "update-packages": "pnpm update --latest '!typescript'",
104
104
  "z:vitest": "vitest --config ./configs/vitest.config.mts",
105
105
  "z:vitest:node": "pnpm run z:vitest --project='Node.js'"
106
106
  },
107
107
  "dependencies": {
108
- "ts-data-forge": "^7.1.1",
108
+ "ts-data-forge": "^14.0.0",
109
109
  "ts-morph": "^28.0.0",
110
- "ts-type-forge": "^4.0.0"
110
+ "ts-type-forge": "^9.1.1"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@rollup/plugin-replace": "6.0.3",
114
114
  "@rollup/plugin-strip": "3.0.4",
115
- "@semantic-release/changelog": "6.0.3",
115
+ "@semantic-release/changelog": "7.0.0",
116
116
  "@semantic-release/commit-analyzer": "13.0.1",
117
117
  "@semantic-release/exec": "7.1.0",
118
- "@semantic-release/git": "10.0.1",
119
- "@semantic-release/github": "12.0.8",
118
+ "@semantic-release/git": "11.0.1",
119
+ "@semantic-release/github": "12.0.9",
120
120
  "@semantic-release/npm": "13.1.5",
121
121
  "@semantic-release/release-notes-generator": "14.1.1",
122
- "@types/node": "26.1.0",
122
+ "@types/node": "26.1.2",
123
123
  "@vitest/browser-playwright": "4.1.10",
124
124
  "@vitest/coverage-v8": "4.1.10",
125
- "@vitest/ui": "4.1.9",
125
+ "@vitest/ui": "4.1.10",
126
126
  "cmd-ts": "0.15.0",
127
127
  "conventional-changelog-conventionalcommits": "10.2.1",
128
128
  "cspell": "10.0.1",
129
129
  "dedent": "1.7.2",
130
130
  "esbuild": "0.28.1",
131
- "eslint": "10.6.0",
132
- "eslint-config-typed": "5.4.0",
133
- "github-settings-as-code": "1.2.9",
131
+ "eslint": "10.8.0",
132
+ "eslint-config-typed": "5.8.2",
133
+ "eslint-plugin-ts-data-forge": "0.3.0",
134
+ "eslint-plugin-ts-fortress": "0.4.0",
135
+ "eslint-plugin-ts-type-forge": "0.4.3",
136
+ "github-settings-as-code": "2.0.1",
134
137
  "jiti": "2.7.0",
135
- "markdownlint": "0.41.0",
136
- "markdownlint-cli2": "0.23.0",
138
+ "markdownlint": "0.41.1",
139
+ "markdownlint-cli2": "0.23.2",
137
140
  "npm-run-all2": "9.0.2",
138
- "playwright": "1.61.1",
139
- "prettier": "3.9.4",
141
+ "playwright": "1.62.0",
142
+ "prettier": "3.9.6",
140
143
  "prettier-plugin-organize-imports": "4.3.0",
141
144
  "prettier-plugin-packagejson": "3.0.2",
142
- "rollup": "4.62.2",
145
+ "rollup": "4.62.3",
143
146
  "rollup-plugin-esbuild": "6.2.1",
144
- "semantic-release": "25.0.5",
145
- "ts-repo-utils": "10.1.2",
146
- "tsx": "4.23.0",
147
- "typedoc": "0.28.19",
147
+ "semantic-release": "25.0.8",
148
+ "ts-repo-utils": "10.1.6",
149
+ "tsx": "4.23.1",
150
+ "typedoc": "0.28.20",
148
151
  "typedoc-github-theme": "0.4.0",
149
152
  "typescript": "6.0.3",
150
- "typescript-native": "npm:typescript@7.0.1-rc",
151
- "vite": "8.1.2",
152
- "vitest": "4.1.9"
153
+ "typescript-native": "npm:typescript@7.0.2",
154
+ "vite": "8.1.5",
155
+ "vitest": "4.1.10"
153
156
  },
154
157
  "peerDependencies": {
155
158
  "cmd-ts": "0.14.3",
@@ -167,12 +170,16 @@
167
170
  "optional": true
168
171
  }
169
172
  },
170
- "packageManager": "pnpm@11.10.0+sha512.0b7f8b98060031904c017e3a41eb187a16d40eeb829b95c4f8cb03681761fc4ab53dd219115b9b447f4dce1a05a214764461e7d3703392a9f32f9511ce8c86c8",
173
+ "packageManager": "pnpm@11.20.0",
171
174
  "engines": {
172
175
  "node": ">=22.22.2",
173
176
  "pnpm": ">=9.0.0"
174
177
  },
175
178
  "volta": {
176
179
  "node": "26.3.1"
180
+ },
181
+ "publishConfig": {
182
+ "access": "public",
183
+ "provenance": true
177
184
  }
178
185
  }
@@ -9,7 +9,7 @@ const transformer = appendAsConstTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.2.2',
12
+ version: '2.2.4',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = convertInterfaceToTypeTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.2.2',
12
+ version: '2.2.4',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = convertToReadonlyTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.2.2',
12
+ version: '2.2.4',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = replaceAnyWithUnknownTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.2.2',
12
+ version: '2.2.4',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = replaceRecordWithUnknownRecordTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.2.2',
12
+ version: '2.2.4',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -51,7 +51,7 @@ export const runTransformerCLI = async (
51
51
 
52
52
  const files = filesResult.value;
53
53
 
54
- if (Arr.isArrayOfLength(files, 0)) {
54
+ if (Arr.isEmpty(files)) {
55
55
  echoIfNotSilent(
56
56
  options.diffFrom !== undefined
57
57
  ? `No files found in diff from "${options.diffFrom}" matching pattern: ${options.baseDir}`
@@ -150,7 +150,7 @@ const transformNode = (
150
150
 
151
151
  if (
152
152
  declarationKindKeywords !== undefined &&
153
- Arr.isArrayOfLength(declarationKindKeywords, 1)
153
+ Arr.isFixedLengthArray(1, declarationKindKeywords)
154
154
  ) {
155
155
  transformNode(
156
156
  initializer,
@@ -47,18 +47,18 @@ export const convertInterfaceToTypeTransformer = (): TsMorphTransformer =>
47
47
 
48
48
  // Build type literal from members
49
49
  const mut_typeBody: string = (() => {
50
- if (Arr.isArrayOfLength(extendsExpressions, 0)) {
50
+ if (Arr.isEmpty(extendsExpressions)) {
51
51
  // No extends: simple type literal
52
52
  return buildTypeLiteral(members);
53
53
  }
54
54
 
55
- if (Arr.isArrayOfLength(members, 0)) {
55
+ if (Arr.isEmpty(members)) {
56
56
  // Only extends, no own members: union of extended types
57
57
  const extendedTypes = extendsExpressions.map((ext) =>
58
58
  ext.getText(),
59
59
  );
60
60
 
61
- return Arr.isArrayOfLength(extendedTypes, 1)
61
+ return Arr.isFixedLengthArray(1, extendedTypes)
62
62
  ? extendedTypes[0]
63
63
  : extendedTypes.join(' & ');
64
64
  }
@@ -97,7 +97,7 @@ export const convertInterfaceToTypeTransformer = (): TsMorphTransformer =>
97
97
  const buildTypeLiteral = (
98
98
  members: readonly tsm.TypeElementTypes[],
99
99
  ): string => {
100
- if (Arr.isArrayOfLength(members, 0)) {
100
+ if (Arr.isEmpty(members)) {
101
101
  return 'Record<string, never>';
102
102
  }
103
103
 
@@ -409,7 +409,7 @@ const transformTypeReferenceNode = (
409
409
 
410
410
  // Array<T> / ReadonlyArray<T> to readonly T[]
411
411
  if (typeNameStr === 'Array' || typeNameStr === 'ReadonlyArray') {
412
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
412
+ if (!Arr.isFixedLengthArray(1, typeArguments)) {
413
413
  throw new Error(
414
414
  `Unexpected number of type arguments "${typeArguments.length}" for ${typeNameStr}.`,
415
415
  );
@@ -442,7 +442,7 @@ const transformTypeReferenceNode = (
442
442
 
443
443
  // Set<T> to ReadonlySet<T>
444
444
  if (typeNameStr === 'Set') {
445
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
445
+ if (!Arr.isFixedLengthArray(1, typeArguments)) {
446
446
  throw new Error(
447
447
  `Unexpected number of type arguments "${typeArguments.length}" for Set.`,
448
448
  );
@@ -468,7 +468,7 @@ const transformTypeReferenceNode = (
468
468
 
469
469
  // Map<T> to ReadonlyMap<T>
470
470
  if (typeNameStr === 'Map') {
471
- if (!Arr.isArrayOfLength(typeArguments, 2)) {
471
+ if (!Arr.isFixedLengthArray(2, typeArguments)) {
472
472
  throw new Error(
473
473
  `Unexpected number of type arguments "${typeArguments.length}" for Map.`,
474
474
  );
@@ -498,7 +498,7 @@ const transformTypeReferenceNode = (
498
498
 
499
499
  // remove unnecessary `Readonly` wrapper or convert to readonly operator
500
500
  if (typeNameStr === 'Readonly') {
501
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
501
+ if (!Arr.isFixedLengthArray(1, typeArguments)) {
502
502
  throw new Error(
503
503
  `Unexpected number of type arguments "${typeArguments.length}" for Readonly.`,
504
504
  );
@@ -592,7 +592,7 @@ const transformTypeReferenceNode = (
592
592
 
593
593
  // DeepReadonly
594
594
  if (typeNameStr === options.DeepReadonly.typeName) {
595
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
595
+ if (!Arr.isFixedLengthArray(1, typeArguments)) {
596
596
  throw new Error(
597
597
  `Unexpected number of type arguments "${typeArguments.length}" for Readonly.`,
598
598
  );
@@ -858,10 +858,7 @@ const transformTypeLiteralNode = (
858
858
  >,
859
859
  options: ReadonlyTransformerOptionsInternal,
860
860
  ): void => {
861
- if (
862
- options.ignoreEmptyObjectTypes &&
863
- Arr.isArrayOfLength(node.getMembers(), 0)
864
- ) {
861
+ if (options.ignoreEmptyObjectTypes && Arr.isEmpty(node.getMembers())) {
865
862
  return;
866
863
  }
867
864
 
@@ -1170,8 +1167,7 @@ const transformUnionOrIntersectionTypeNodeImpl = (
1170
1167
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1171
1168
  n: tsm.TypeLiteralNode | ReadonlyTypeReferenceNode,
1172
1169
  ): boolean =>
1173
- n.isKind(tsm.SyntaxKind.TypeLiteral) &&
1174
- Arr.isArrayOfLength(n.getMembers(), 0);
1170
+ n.isKind(tsm.SyntaxKind.TypeLiteral) && Arr.isEmpty(n.getMembers());
1175
1171
 
1176
1172
  const nonEmptyTypeLiterals =
1177
1173
  typeLiterals === undefined
@@ -1183,11 +1179,10 @@ const transformUnionOrIntersectionTypeNodeImpl = (
1183
1179
  const emptyTypeLiterals =
1184
1180
  typeLiterals === undefined || !options.ignoreEmptyObjectTypes
1185
1181
  ? undefined
1186
- : typeLiterals.nodes.filter((n) => isEmptyTypeLiteral(n));
1182
+ : typeLiterals.nodes.filter(isEmptyTypeLiteral);
1187
1183
 
1188
1184
  const typeLiteralsWrappedWithReadonly: readonly [] | readonly [string] =
1189
- nonEmptyTypeLiterals === undefined ||
1190
- Arr.isArrayOfLength(nonEmptyTypeLiterals, 0)
1185
+ nonEmptyTypeLiterals === undefined || Arr.isEmpty(nonEmptyTypeLiterals)
1191
1186
  ? ([] as const)
1192
1187
  : ([
1193
1188
  unionToString({
@@ -1282,9 +1277,9 @@ const unionToString = ({
1282
1277
  op: '&' | '|';
1283
1278
  wrapWithReadonly: boolean | string;
1284
1279
  }>): string =>
1285
- Arr.isArrayOfLength(types, 0)
1280
+ Arr.isEmpty(types)
1286
1281
  ? 'never'
1287
- : Arr.isArrayOfLength(types, 1)
1282
+ : Arr.isFixedLengthArray(1, types)
1288
1283
  ? wrapWithReadonly === false
1289
1284
  ? wrapWithParentheses(types[0])
1290
1285
  : (`${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types[0]}>` as const)
@@ -24,9 +24,7 @@ export const replaceRecordWithUnknownRecordTransformer =
24
24
  name: TRANSFORMER_NAME,
25
25
  transform: (sourceAst) => {
26
26
  const containerModifications = Arr.toUnshifted(
27
- sourceAst
28
- .getModules()
29
- .map((namespace) => processDeclarations(namespace)),
27
+ sourceAst.getModules().map(processDeclarations),
30
28
  processDeclarations(sourceAst),
31
29
  );
32
30
 
@@ -53,7 +51,7 @@ const processDeclarations = (
53
51
 
54
52
  const interfaceModifications = container
55
53
  .getInterfaces()
56
- .map((interfaceDecl) => processInterfaceDeclaration(interfaceDecl));
54
+ .map(processInterfaceDeclaration);
57
55
 
58
56
  return [...typeAliasModifications, ...interfaceModifications].includes(true);
59
57
  };
@@ -81,10 +79,10 @@ const processInterfaceDeclaration = (
81
79
  // (type parameters, extends clauses, declare modifier, default export).
82
80
  if (
83
81
  hasStringUnknownSignature &&
84
- Arr.isArrayOfLength(interfaceDecl.getProperties(), 0) &&
85
- Arr.isArrayOfLength(indexSignatures, 1) &&
86
- Arr.isArrayOfLength(interfaceDecl.getTypeParameters(), 0) &&
87
- Arr.isArrayOfLength(interfaceDecl.getExtends(), 0) &&
82
+ Arr.isEmpty(interfaceDecl.getProperties()) &&
83
+ Arr.isFixedLengthArray(1, indexSignatures) &&
84
+ Arr.isEmpty(interfaceDecl.getTypeParameters()) &&
85
+ Arr.isEmpty(interfaceDecl.getExtends()) &&
88
86
  !interfaceDecl.hasDeclareKeyword() &&
89
87
  !interfaceDecl.hasDefaultKeyword()
90
88
  ) {
@@ -118,7 +116,7 @@ const visitTypeNode = (node: tsm.TypeNode): boolean => {
118
116
  if (node.getTypeName().getText() === 'Readonly') {
119
117
  const typeArgs = node.getTypeArguments();
120
118
 
121
- if (Arr.isArrayOfLength(typeArgs, 1)) {
119
+ if (Arr.isFixedLengthArray(1, typeArgs)) {
122
120
  const typeArg = typeArgs[0];
123
121
 
124
122
  if (tsm.Node.isTypeLiteral(typeArg)) {
@@ -130,8 +128,8 @@ const visitTypeNode = (node: tsm.TypeNode): boolean => {
130
128
 
131
129
  // Check if it has only one index signature [k: string]: unknown
132
130
  if (
133
- Arr.isArrayOfLength(members, 1) &&
134
- Arr.isArrayOfLength(indexSigs, 1) &&
131
+ Arr.isFixedLengthArray(1, members) &&
132
+ Arr.isFixedLengthArray(1, indexSigs) &&
135
133
  isStringUnknownIndexSignature(indexSigs[0])
136
134
  ) {
137
135
  node.replaceWithText('UnknownRecord');
@@ -158,8 +156,8 @@ const visitTypeNode = (node: tsm.TypeNode): boolean => {
158
156
 
159
157
  // Check if it has only one index signature [k: string]: unknown
160
158
  if (
161
- Arr.isArrayOfLength(members, 1) &&
162
- Arr.isArrayOfLength(indexSigs, 1) &&
159
+ Arr.isFixedLengthArray(1, members) &&
160
+ Arr.isFixedLengthArray(1, indexSigs) &&
163
161
  isStringUnknownIndexSignature(indexSigs[0])
164
162
  ) {
165
163
  node.replaceWithText('UnknownRecord');
@@ -188,10 +186,7 @@ const visitTypeNode = (node: tsm.TypeNode): boolean => {
188
186
 
189
187
  // Recursively visit child type nodes
190
188
  if (tsm.Node.isUnionTypeNode(node) || tsm.Node.isIntersectionTypeNode(node)) {
191
- return node
192
- .getTypeNodes()
193
- .map((typeNode) => visitTypeNode(typeNode))
194
- .includes(true);
189
+ return node.getTypeNodes().map(visitTypeNode).includes(true);
195
190
  }
196
191
 
197
192
  if (tsm.Node.isArrayTypeNode(node)) {
@@ -199,10 +194,7 @@ const visitTypeNode = (node: tsm.TypeNode): boolean => {
199
194
  }
200
195
 
201
196
  if (tsm.Node.isTupleTypeNode(node)) {
202
- return node
203
- .getElements()
204
- .map((element) => visitTypeNode(element))
205
- .includes(true);
197
+ return node.getElements().map(visitTypeNode).includes(true);
206
198
  }
207
199
 
208
200
  if (tsm.Node.isParenthesizedTypeNode(node)) {
@@ -244,7 +236,7 @@ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): boolean => {
244
236
  const typeArgs = node.getTypeArguments();
245
237
 
246
238
  if (
247
- Arr.isArrayOfLength(typeArgs, 2) &&
239
+ Arr.isFixedLengthArray(2, typeArgs) &&
248
240
  typeArgs[0].getText() === 'string' &&
249
241
  typeArgs[1].getText() === 'unknown'
250
242
  ) {
@@ -254,13 +246,13 @@ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): boolean => {
254
246
  }
255
247
 
256
248
  // Recurse into type arguments (e.g. Record<string, Record<string, unknown>>)
257
- return typeArgs.map((typeArg) => visitTypeNode(typeArg)).includes(true);
249
+ return typeArgs.map(visitTypeNode).includes(true);
258
250
  }
259
251
  case 'Readonly': {
260
252
  // Check if it's Readonly<Record<string, unknown>>
261
253
  const typeArgs = node.getTypeArguments();
262
254
 
263
- if (Arr.isArrayOfLength(typeArgs, 1)) {
255
+ if (Arr.isFixedLengthArray(1, typeArgs)) {
264
256
  const innerType = typeArgs[0];
265
257
 
266
258
  if (tsm.Node.isTypeReference(innerType)) {
@@ -270,7 +262,7 @@ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): boolean => {
270
262
  const innerTypeArgs = innerType.getTypeArguments();
271
263
 
272
264
  if (
273
- Arr.isArrayOfLength(innerTypeArgs, 2) &&
265
+ Arr.isFixedLengthArray(2, innerTypeArgs) &&
274
266
  innerTypeArgs[0].getText() === 'string' &&
275
267
  innerTypeArgs[1].getText() === 'unknown'
276
268
  ) {
@@ -290,10 +282,7 @@ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): boolean => {
290
282
 
291
283
  default: {
292
284
  // Recurse into type arguments for any other generic type
293
- return node
294
- .getTypeArguments()
295
- .map((typeArg) => visitTypeNode(typeArg))
296
- .includes(true);
285
+ return node.getTypeArguments().map(visitTypeNode).includes(true);
297
286
  }
298
287
  }
299
288
  };
@@ -58,12 +58,12 @@ const shouldSkipFile = (
58
58
 
59
59
  const hasFileIgnoreComment = patterns.some((regex) => regex.test(code));
60
60
 
61
- if (Arr.isArrayOfLength(ignoredTransformers, 0) && !hasFileIgnoreComment) {
61
+ if (Arr.isEmpty(ignoredTransformers) && !hasFileIgnoreComment) {
62
62
  return false;
63
63
  }
64
64
 
65
65
  // Empty array means ignore all transformers (file-level ignore without specific transformers)
66
- if (Arr.isArrayOfLength(ignoredTransformers, 0)) {
66
+ if (Arr.isEmpty(ignoredTransformers)) {
67
67
  return true;
68
68
  }
69
69
 
@@ -43,6 +43,6 @@ export const isAsConstNode = (
43
43
  // and that there are no type arguments (as const doesn't have them)
44
44
  return (
45
45
  typeNameNode.getText() === 'const' &&
46
- Arr.isArrayOfLength(typeNode.getTypeArguments(), 0)
46
+ Arr.isEmpty(typeNode.getTypeArguments())
47
47
  );
48
48
  };
@@ -176,7 +176,7 @@ export const isReadonlyTypeReferenceNode = (
176
176
  return (
177
177
  typeName.isKind(tsm.SyntaxKind.Identifier) &&
178
178
  typeName.getText() === 'Readonly' &&
179
- Arr.isArrayOfLength(typeArguments, 1)
179
+ Arr.isFixedLengthArray(1, typeArguments)
180
180
  );
181
181
  };
182
182