From 2fcf2631e9379b60a7c078c32b68cd54e77b0d6d Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:58:52 -0700 Subject: [PATCH] Retain type annotation when transforming redundant types (#555) --- .changeset/sweet-spies-attack.md | 5 ++ .../global-require.input.ts | 52 +++++++++---------- .../service-require-deep.input.ts | 52 +++++++++---------- .../service-require.input.ts | 52 +++++++++---------- .../getTSQualifiedNameFromClientName.ts | 24 +++++++++ .../ts-type/replaceTSQualifiedName.ts | 41 +++++---------- .../v2-to-v3/ts-type/updateV2ClientTypeRef.ts | 37 +++++++++++++ 7 files changed, 158 insertions(+), 105 deletions(-) create mode 100644 .changeset/sweet-spies-attack.md create mode 100644 src/transforms/v2-to-v3/ts-type/getTSQualifiedNameFromClientName.ts create mode 100644 src/transforms/v2-to-v3/ts-type/updateV2ClientTypeRef.ts diff --git a/.changeset/sweet-spies-attack.md b/.changeset/sweet-spies-attack.md new file mode 100644 index 000000000..11d8f5078 --- /dev/null +++ b/.changeset/sweet-spies-attack.md @@ -0,0 +1,5 @@ +--- +"aws-sdk-js-codemod": patch +--- + +Retain type annotation when transforming redundant types diff --git a/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/global-require.input.ts b/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/global-require.input.ts index a30bc1ca2..e474d81cd 100644 --- a/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/global-require.input.ts +++ b/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/global-require.input.ts @@ -1,46 +1,46 @@ const AWS = require("aws-sdk"); // Native types -const stringType: AWS.S3.AccountId = "string"; -const booleanType: AWS.S3.BucketKeyEnabled = true; -const numberType: AWS.S3.ContentLength = 123; +const stringType: typeof AWS.S3.AccountId = "string"; +const booleanType: typeof AWS.S3.BucketKeyEnabled = true; +const numberType: typeof AWS.S3.ContentLength = 123; // Date -const dateType: AWS.S3.CreationDate = new Date(); +const dateType: typeof AWS.S3.CreationDate = new Date(); // Uint8Array -const blobType: AWS.RDSDataService._Blob = new Uint8Array(); +const blobType: typeof AWS.RDSDataService._Blob = new Uint8Array(); // Arrays -const stringArray: AWS.S3.AllowedHeaders = ["string1", "string2"]; -const booleanArray: AWS.RDSDataService.BooleanArray = [true, false]; -const numberArray: AWS.RDSDataService.LongArray = [123, 456]; -const blobArray: AWS.IoTFleetWise.NetworkFilesList = [new Uint8Array()]; -const enumArray: AWS.S3.ChecksumAlgorithmList = ["CRC32"]; -const structureArray: AWS.S3.Buckets = [{ Name: "bucketName" }]; +const stringArray: typeof AWS.S3.AllowedHeaders = ["string1", "string2"]; +const booleanArray: typeof AWS.RDSDataService.BooleanArray = [true, false]; +const numberArray: typeof AWS.RDSDataService.LongArray = [123, 456]; +const blobArray: typeof AWS.IoTFleetWise.NetworkFilesList = [new Uint8Array()]; +const enumArray: typeof AWS.S3.ChecksumAlgorithmList = ["CRC32"]; +const structureArray: typeof AWS.S3.Buckets = [{ Name: "bucketName" }]; // Maps -const stringMap: AWS.S3.Metadata = { key: "value" }; -const booleanMap: AWS.APIGateway.MapOfStringToBoolean = { key: true }; -const numberMap: AWS.SSM.AssociationStatusAggregatedCount = { key: 123 }; -const structureMap: AWS.APIGateway.MapOfMethodSnapshot = { key: { apiKeyRequired: true } }; +const stringMap: typeof AWS.S3.Metadata = { key: "value" }; +const booleanMap: typeof AWS.APIGateway.MapOfStringToBoolean = { key: true }; +const numberMap: typeof AWS.SSM.AssociationStatusAggregatedCount = { key: 123 }; +const structureMap: typeof AWS.APIGateway.MapOfMethodSnapshot = { key: { apiKeyRequired: true } }; // Nested arrays -const arrayNestedTwice: AWS.SageMakerGeospatial.LinearRing = [[1, 2], [3, 4]]; -const arrayNestedThrice: AWS.SageMakerGeospatial.LinearRings = [[[1, 2], [3, 4]], [[4, 5], [6, 7]]]; -const arrayNestedFour: AWS.SageMakerGeospatial.LinearRingsList = [ +const arrayNestedTwice: typeof AWS.SageMakerGeospatial.LinearRing = [[1, 2], [3, 4]]; +const arrayNestedThrice: typeof AWS.SageMakerGeospatial.LinearRings = [[[1, 2], [3, 4]], [[4, 5], [6, 7]]]; +const arrayNestedFour: typeof AWS.SageMakerGeospatial.LinearRingsList = [ [[[1], [2]], [[3], [4]]], [[[5], [6]], [[7], [8]]] ]; // Nested maps -const mapNestedTwice: AWS.LexModelsV2.ConditionMap = { key: stringMap }; -const mapNestedTwiceStruct: AWS.APIGateway.PathToMapOfMethodSnapshot = { key: structureMap }; +const mapNestedTwice: typeof AWS.LexModelsV2.ConditionMap = { key: stringMap }; +const mapNestedTwiceStruct: typeof AWS.APIGateway.PathToMapOfMethodSnapshot = { key: structureMap }; // Nested arrays and maps -const mapOfArrays: AWS.NetworkManager.FilterMap = { key: ["value"] }; -const mapOfMapOfArrays: AWS.AppIntegrations.ObjectConfiguration = { key: mapOfArrays }; -const mapOfArrayOfMaps: AWS.DynamoDB.BatchGetResponseMap = { key: [{ key: { S:"A" }}] }; -const mapOfArrayOfArrays: AWS.APIGateway.MapOfKeyUsages = { key: [[1], [2]] }; -const arrayOfMaps: AWS.SSM.InventoryItemEntryList = [stringMap]; -const arrayOfMapOfArrays: AWS.SSM.TargetMaps = [mapOfArrays]; \ No newline at end of file +const mapOfArrays: typeof AWS.NetworkManager.FilterMap = { key: ["value"] }; +const mapOfMapOfArrays: typeof AWS.AppIntegrations.ObjectConfiguration = { key: mapOfArrays }; +const mapOfArrayOfMaps: typeof AWS.DynamoDB.BatchGetResponseMap = { key: [{ key: { S:"A" }}] }; +const mapOfArrayOfArrays: typeof AWS.APIGateway.MapOfKeyUsages = { key: [[1], [2]] }; +const arrayOfMaps: typeof AWS.SSM.InventoryItemEntryList = [stringMap]; +const arrayOfMapOfArrays: typeof AWS.SSM.TargetMaps = [mapOfArrays]; \ No newline at end of file diff --git a/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require-deep.input.ts b/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require-deep.input.ts index 21bf8aeb6..fc0544bf2 100644 --- a/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require-deep.input.ts +++ b/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require-deep.input.ts @@ -10,46 +10,46 @@ const AppIntegrations = require("aws-sdk/clients/appintegrations"); const SSM = require("aws-sdk/clients/ssm"); // Native types -const stringType: S3.AccountId = "string"; -const booleanType: S3.BucketKeyEnabled = true; -const numberType: S3.ContentLength = 123; +const stringType: typeof S3.AccountId = "string"; +const booleanType: typeof S3.BucketKeyEnabled = true; +const numberType: typeof S3.ContentLength = 123; // Date -const dateType: S3.CreationDate = new Date(); +const dateType: typeof S3.CreationDate = new Date(); // Uint8Array -const blobType: RDSDataService._Blob = new Uint8Array(); +const blobType: typeof RDSDataService._Blob = new Uint8Array(); // Arrays -const stringArray: S3.AllowedHeaders = ["string1", "string2"]; -const booleanArray: RDSDataService.BooleanArray = [true, false]; -const numberArray: RDSDataService.LongArray = [123, 456]; -const blobArray: IoTFleetWise.NetworkFilesList = [new Uint8Array()]; -const enumArray: S3.ChecksumAlgorithmList = ["CRC32"]; -const structureArray: S3.Buckets = [{ Name: "bucketName" }]; +const stringArray: typeof S3.AllowedHeaders = ["string1", "string2"]; +const booleanArray: typeof RDSDataService.BooleanArray = [true, false]; +const numberArray: typeof RDSDataService.LongArray = [123, 456]; +const blobArray: typeof IoTFleetWise.NetworkFilesList = [new Uint8Array()]; +const enumArray: typeof S3.ChecksumAlgorithmList = ["CRC32"]; +const structureArray: typeof S3.Buckets = [{ Name: "bucketName" }]; // Maps -const stringMap: S3.Metadata = { key: "value" }; -const booleanMap: APIGateway.MapOfStringToBoolean = { key: true }; -const numberMap: SSM.AssociationStatusAggregatedCount = { key: 123 }; -const structureMap: APIGateway.MapOfMethodSnapshot = { key: { apiKeyRequired: true } }; +const stringMap: typeof S3.Metadata = { key: "value" }; +const booleanMap: typeof APIGateway.MapOfStringToBoolean = { key: true }; +const numberMap: typeof SSM.AssociationStatusAggregatedCount = { key: 123 }; +const structureMap: typeof APIGateway.MapOfMethodSnapshot = { key: { apiKeyRequired: true } }; // Nested arrays -const arrayNestedTwice: SageMakerGeospatial.LinearRing = [[1, 2], [3, 4]]; -const arrayNestedThrice: SageMakerGeospatial.LinearRings = [[[1, 2], [3, 4]], [[4, 5], [6, 7]]]; -const arrayNestedFour: SageMakerGeospatial.LinearRingsList = [ +const arrayNestedTwice: typeof SageMakerGeospatial.LinearRing = [[1, 2], [3, 4]]; +const arrayNestedThrice: typeof SageMakerGeospatial.LinearRings = [[[1, 2], [3, 4]], [[4, 5], [6, 7]]]; +const arrayNestedFour: typeof SageMakerGeospatial.LinearRingsList = [ [[[1], [2]], [[3], [4]]], [[[5], [6]], [[7], [8]]] ]; // Nested maps -const mapNestedTwice: LexModelsV2.ConditionMap = { key: stringMap }; -const mapNestedTwiceStruct: APIGateway.PathToMapOfMethodSnapshot = { key: structureMap }; +const mapNestedTwice: typeof LexModelsV2.ConditionMap = { key: stringMap }; +const mapNestedTwiceStruct: typeof APIGateway.PathToMapOfMethodSnapshot = { key: structureMap }; // Nested arrays and maps -const mapOfArrays: NetworkManager.FilterMap = { key: ["value"] }; -const mapOfMapOfArrays: AppIntegrations.ObjectConfiguration = { key: mapOfArrays }; -const mapOfArrayOfMaps: DynamoDB.BatchGetResponseMap = { key: [{ key: { S:"A" }}] }; -const mapOfArrayOfArrays: APIGateway.MapOfKeyUsages = { key: [[1], [2]] }; -const arrayOfMaps: SSM.InventoryItemEntryList = [stringMap]; -const arrayOfMapOfArrays: SSM.TargetMaps = [mapOfArrays]; \ No newline at end of file +const mapOfArrays: typeof NetworkManager.FilterMap = { key: ["value"] }; +const mapOfMapOfArrays: typeof AppIntegrations.ObjectConfiguration = { key: mapOfArrays }; +const mapOfArrayOfMaps: typeof DynamoDB.BatchGetResponseMap = { key: [{ key: { S:"A" }}] }; +const mapOfArrayOfArrays: typeof APIGateway.MapOfKeyUsages = { key: [[1], [2]] }; +const arrayOfMaps: typeof SSM.InventoryItemEntryList = [stringMap]; +const arrayOfMapOfArrays: typeof SSM.TargetMaps = [mapOfArrays]; \ No newline at end of file diff --git a/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require.input.ts b/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require.input.ts index c9ec473ae..44bb7030a 100644 --- a/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require.input.ts +++ b/src/transforms/v2-to-v3/__fixtures__/api-redundant-type/service-require.input.ts @@ -10,46 +10,46 @@ const { AppIntegrations } = require("aws-sdk"); const { SSM } = require("aws-sdk"); // Native types -const stringType: S3.AccountId = "string"; -const booleanType: S3.BucketKeyEnabled = true; -const numberType: S3.ContentLength = 123; +const stringType: typeof S3.AccountId = "string"; +const booleanType: typeof S3.BucketKeyEnabled = true; +const numberType: typeof S3.ContentLength = 123; // Date -const dateType: S3.CreationDate = new Date(); +const dateType: typeof S3.CreationDate = new Date(); // Uint8Array -const blobType: RDSDataService._Blob = new Uint8Array(); +const blobType: typeof RDSDataService._Blob = new Uint8Array(); // Arrays -const stringArray: S3.AllowedHeaders = ["string1", "string2"]; -const booleanArray: RDSDataService.BooleanArray = [true, false]; -const numberArray: RDSDataService.LongArray = [123, 456]; -const blobArray: IoTFleetWise.NetworkFilesList = [new Uint8Array()]; -const enumArray: S3.ChecksumAlgorithmList = ["CRC32"]; -const structureArray: S3.Buckets = [{ Name: "bucketName" }]; +const stringArray: typeof S3.AllowedHeaders = ["string1", "string2"]; +const booleanArray: typeof RDSDataService.BooleanArray = [true, false]; +const numberArray: typeof RDSDataService.LongArray = [123, 456]; +const blobArray: typeof IoTFleetWise.NetworkFilesList = [new Uint8Array()]; +const enumArray: typeof S3.ChecksumAlgorithmList = ["CRC32"]; +const structureArray: typeof S3.Buckets = [{ Name: "bucketName" }]; // Maps -const stringMap: S3.Metadata = { key: "value" }; -const booleanMap: APIGateway.MapOfStringToBoolean = { key: true }; -const numberMap: SSM.AssociationStatusAggregatedCount = { key: 123 }; -const structureMap: APIGateway.MapOfMethodSnapshot = { key: { apiKeyRequired: true } }; +const stringMap: typeof S3.Metadata = { key: "value" }; +const booleanMap: typeof APIGateway.MapOfStringToBoolean = { key: true }; +const numberMap: typeof SSM.AssociationStatusAggregatedCount = { key: 123 }; +const structureMap: typeof APIGateway.MapOfMethodSnapshot = { key: { apiKeyRequired: true } }; // Nested arrays -const arrayNestedTwice: SageMakerGeospatial.LinearRing = [[1, 2], [3, 4]]; -const arrayNestedThrice: SageMakerGeospatial.LinearRings = [[[1, 2], [3, 4]], [[4, 5], [6, 7]]]; -const arrayNestedFour: SageMakerGeospatial.LinearRingsList = [ +const arrayNestedTwice: typeof SageMakerGeospatial.LinearRing = [[1, 2], [3, 4]]; +const arrayNestedThrice: typeof SageMakerGeospatial.LinearRings = [[[1, 2], [3, 4]], [[4, 5], [6, 7]]]; +const arrayNestedFour: typeof SageMakerGeospatial.LinearRingsList = [ [[[1], [2]], [[3], [4]]], [[[5], [6]], [[7], [8]]] ]; // Nested maps -const mapNestedTwice: LexModelsV2.ConditionMap = { key: stringMap }; -const mapNestedTwiceStruct: APIGateway.PathToMapOfMethodSnapshot = { key: structureMap }; +const mapNestedTwice: typeof LexModelsV2.ConditionMap = { key: stringMap }; +const mapNestedTwiceStruct: typeof APIGateway.PathToMapOfMethodSnapshot = { key: structureMap }; // Nested arrays and maps -const mapOfArrays: NetworkManager.FilterMap = { key: ["value"] }; -const mapOfMapOfArrays: AppIntegrations.ObjectConfiguration = { key: mapOfArrays }; -const mapOfArrayOfMaps: DynamoDB.BatchGetResponseMap = { key: [{ key: { S:"A" }}] }; -const mapOfArrayOfArrays: APIGateway.MapOfKeyUsages = { key: [[1], [2]] }; -const arrayOfMaps: SSM.InventoryItemEntryList = [stringMap]; -const arrayOfMapOfArrays: SSM.TargetMaps = [mapOfArrays]; \ No newline at end of file +const mapOfArrays: typeof NetworkManager.FilterMap = { key: ["value"] }; +const mapOfMapOfArrays: typeof AppIntegrations.ObjectConfiguration = { key: mapOfArrays }; +const mapOfArrayOfMaps: typeof DynamoDB.BatchGetResponseMap = { key: [{ key: { S:"A" }}] }; +const mapOfArrayOfArrays: typeof APIGateway.MapOfKeyUsages = { key: [[1], [2]] }; +const arrayOfMaps: typeof SSM.InventoryItemEntryList = [stringMap]; +const arrayOfMapOfArrays: typeof SSM.TargetMaps = [mapOfArrays]; \ No newline at end of file diff --git a/src/transforms/v2-to-v3/ts-type/getTSQualifiedNameFromClientName.ts b/src/transforms/v2-to-v3/ts-type/getTSQualifiedNameFromClientName.ts new file mode 100644 index 000000000..9dffc8e7c --- /dev/null +++ b/src/transforms/v2-to-v3/ts-type/getTSQualifiedNameFromClientName.ts @@ -0,0 +1,24 @@ +import { TSQualifiedName } from "jscodeshift"; + +export const getTSQualifiedNameFromClientName = ( + v2GlobalName: string, + clientName: string +): TSQualifiedName => { + // Support for DynamoDB.DocumentClient + const [clientNamePrefix, clientNameSuffix] = clientName.split("."); + + if (clientNameSuffix) { + return { + left: { + left: { type: "Identifier", name: v2GlobalName }, + right: { type: "Identifier", name: clientNamePrefix }, + }, + right: { type: "Identifier", name: clientNameSuffix }, + } as TSQualifiedName; + } + + return { + left: { type: "Identifier", name: v2GlobalName }, + right: { type: "Identifier", name: clientNamePrefix }, + } as TSQualifiedName; +}; diff --git a/src/transforms/v2-to-v3/ts-type/replaceTSQualifiedName.ts b/src/transforms/v2-to-v3/ts-type/replaceTSQualifiedName.ts index bec18ad7a..5a23c632b 100644 --- a/src/transforms/v2-to-v3/ts-type/replaceTSQualifiedName.ts +++ b/src/transforms/v2-to-v3/ts-type/replaceTSQualifiedName.ts @@ -2,7 +2,9 @@ import { ASTPath, Collection, Identifier, JSCodeshift, TSQualifiedName } from "j import { DOCUMENT_CLIENT, DYNAMODB, DYNAMODB_DOCUMENT_CLIENT } from "../config"; import { getClientTypeNames } from "./getClientTypeNames"; +import { getTSQualifiedNameFromClientName } from "./getTSQualifiedNameFromClientName"; import { getV3ClientTypeReference } from "./getV3ClientTypeReference"; +import { updateV2ClientTypeRef } from "./updateV2ClientTypeRef"; export interface ReplaceTSQualifiedNameOptions { v2ClientName: string; @@ -18,29 +20,6 @@ const getRightIdentifierName = (node: TSQualifiedName) => (node.right as Identif const isParentTSQualifiedName = (node: ASTPath) => node.parentPath?.value.type === "TSQualifiedName"; -const getTSQualifiedNameFromClientName = ( - v2GlobalName: string, - clientName: string -): TSQualifiedName => { - // Support for DynamoDB.DocumentClient - const [clientNamePrefix, clientNameSuffix] = clientName.split("."); - - if (clientNameSuffix) { - return { - left: { - left: { type: "Identifier", name: v2GlobalName }, - right: { type: "Identifier", name: clientNamePrefix }, - }, - right: { type: "Identifier", name: clientNameSuffix }, - } as TSQualifiedName; - } - - return { - left: { type: "Identifier", name: v2GlobalName }, - right: { type: "Identifier", name: clientNamePrefix }, - } as TSQualifiedName; -}; - // Replace v2 client type reference with v3 client type reference. export const replaceTSQualifiedName = ( j: JSCodeshift, @@ -65,9 +44,13 @@ export const replaceTSQualifiedName = ( (v2ClientType) => isRightSectionIdentifier(v2ClientType.node) && !isParentTSQualifiedName(v2ClientType) ) - .replaceWith((v2ClientType) => { + .forEach((v2ClientType) => { const v2ClientTypeName = getRightIdentifierName(v2ClientType.node); - return getV3ClientTypeReference(j, { v2ClientName, v2ClientTypeName, v2ClientLocalName }); + updateV2ClientTypeRef(j, v2ClientType, { + v2ClientName, + v2ClientTypeName, + v2ClientLocalName, + }); }); } @@ -88,9 +71,13 @@ export const replaceTSQualifiedName = ( (v2ClientType) => isRightSectionIdentifier(v2ClientType.node) && !isParentTSQualifiedName(v2ClientType) ) - .replaceWith((v2ClientType) => { + .forEach((v2ClientType) => { const v2ClientTypeName = getRightIdentifierName(v2ClientType.node); - return getV3ClientTypeReference(j, { v2ClientName, v2ClientTypeName, v2ClientLocalName }); + updateV2ClientTypeRef(j, v2ClientType, { + v2ClientName, + v2ClientTypeName, + v2ClientLocalName, + }); }); // Replace type reference to client type with modules. diff --git a/src/transforms/v2-to-v3/ts-type/updateV2ClientTypeRef.ts b/src/transforms/v2-to-v3/ts-type/updateV2ClientTypeRef.ts new file mode 100644 index 000000000..e0b3d608b --- /dev/null +++ b/src/transforms/v2-to-v3/ts-type/updateV2ClientTypeRef.ts @@ -0,0 +1,37 @@ +import { ASTPath, Identifier, JSCodeshift, TSQualifiedName, TSTypeReference } from "jscodeshift"; +import { getV3ClientTypeReference } from "./getV3ClientTypeReference"; + +const nativeTsRefTypes = ["TSAnyKeyword", "TSStringKeyword", "TSNumberKeyword", "TSBooleanKeyword"]; +const nativeTsIdentifierTypes = ["Date", "Uint8Array", "Array", "Record"]; + +interface UpdateV2ClientTypeRefOptions { + v2ClientName: string; + v2ClientTypeName: string; + v2ClientLocalName: string; +} + +export const updateV2ClientTypeRef = ( + j: JSCodeshift, + v2ClientType: ASTPath, + { v2ClientName, v2ClientTypeName, v2ClientLocalName }: UpdateV2ClientTypeRefOptions +) => { + const v3ClientTypeRef = getV3ClientTypeReference(j, { + v2ClientName, + v2ClientTypeName, + v2ClientLocalName, + }); + + if ( + (v2ClientType.parentPath?.value.type === "TSTypeQuery" && + nativeTsRefTypes.includes(v3ClientTypeRef.type)) || + (v3ClientTypeRef.type === "TSTypeReference" && + (v3ClientTypeRef as TSTypeReference).typeName.type === "Identifier" && + nativeTsIdentifierTypes.includes( + ((v3ClientTypeRef as TSTypeReference).typeName as Identifier).name + )) + ) { + v2ClientType.parentPath?.replace(v3ClientTypeRef); + } else { + v2ClientType.replace(v3ClientTypeRef); + } +};