Skip to content

Commit

Permalink
Generate CLIENT_TYPES_MAP by processing client types from v2 (#313)
Browse files Browse the repository at this point in the history
  • Loading branch information
trivikr authored Jan 5, 2023
1 parent ed9ca3d commit 2f3a713
Show file tree
Hide file tree
Showing 6 changed files with 24,507 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/thick-boats-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"aws-sdk-js-codemod": patch
---

Generate CLIENT_TYPES_MAP by processing client types from v2
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@types/node": "^14.18.33",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"aws-sdk": "2.1288.0",
"eslint": "^8.27.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"jest": "^29.3.1",
Expand Down
130 changes: 130 additions & 0 deletions scripts/generateClientTypesMap/getClientTypeMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { readFile } from "fs/promises";
import jscodeshift, { Identifier, TSArrayType, TSTypeLiteral, TSTypeReference } from "jscodeshift";
import { join } from "path";

const TYPES_TO_SKIP = ["apiVersion", "ClientConfiguration"];
const ElementTypeToNativeTypeMap = {
TSStringKeyword: "string",
TSNumberKeyword: "number",
TSBooleanKeyword: "boolean",
};

export const getClientTypeMap = async (clientName: string): Promise<Record<string, string>> => {
const clientTypesMap = {};

const typesPath = join("node_modules", "aws-sdk", "clients", `${clientName.toLowerCase()}.d.ts`);
const relativeTypesPath = join(__dirname, "..", "..", typesPath);

const typesCode = await readFile(relativeTypesPath, "utf8");

const j = jscodeshift.withParser("ts");
const source = j(typesCode);

source.find(j.TSModuleDeclaration, { id: { name: clientName } }).forEach((moduleDeclaration) => {
const tsTypes = j(moduleDeclaration).find(j.TSTypeAliasDeclaration).nodes();

for (const [type, value] of Object.entries(ElementTypeToNativeTypeMap)) {
tsTypes
.filter((tsType) => tsType.typeAnnotation.type === type)
.forEach((tsType) => {
clientTypesMap[tsType.id.name] = value;
});
}

tsTypes
.filter((tsType) => tsType.typeAnnotation.type === "TSTypeReference")
.forEach((tsType) => {
const name = tsType.id.name;
const typeName = ((tsType.typeAnnotation as TSTypeReference).typeName as Identifier).name;
if (typeName === "Date") {
clientTypesMap[name] = typeName;
} else if (typeName === "EventStream") {
// Exception for SelectObjectContentEventStream
clientTypesMap[name] = "AsyncIterable<KEY>";
} else {
console.log("TSTypeReference with unsupported type:", name, typeName);
}
});

tsTypes
.filter((tsType) => tsType.typeAnnotation.type === "TSUnionType")
.forEach((tsType) => {
const name = tsType.id.name;
if (name.endsWith("Blob")) {
clientTypesMap[name] = "Uint8Array";
}
});

tsTypes
.filter((tsType) => tsType.typeAnnotation.type === "TSArrayType")
.forEach((tsType) => {
const name = tsType.id.name;
const elementType = (tsType.typeAnnotation as TSArrayType).elementType;
if (elementType.type === "TSTypeReference") {
const typeName = elementType.typeName;
if (typeName.type === "Identifier") {
if (clientTypesMap[typeName.name]) {
clientTypesMap[name] = `Array<${clientTypesMap[typeName.name]}>`;
} else {
// Assume it's an interface which would be available in v3.
clientTypesMap[name] = `Array<${typeName.name}>`;
}
} else {
console.log("TSArrayType TSTypeReference without Identifier type:", name);
}
} else if (Object.keys(ElementTypeToNativeTypeMap).includes(elementType.type)) {
clientTypesMap[name] = `Array<${ElementTypeToNativeTypeMap[elementType.type]}>`;
} else {
console.log("TSArrayType with unsupported elemental type:", name);
}
});

tsTypes
.filter((tsType) => tsType.typeAnnotation.type === "TSTypeLiteral")
.forEach((tsType) => {
const name = tsType.id.name;
const member = (tsType.typeAnnotation as TSTypeLiteral).members[0];
if (member.type === "TSIndexSignature") {
if (member.typeAnnotation) {
if (member.typeAnnotation.typeAnnotation) {
const typeAnnotation = member.typeAnnotation.typeAnnotation;
if (typeAnnotation.type === "TSTypeReference") {
const typeName = typeAnnotation.typeName;
if (typeName.type === "Identifier") {
if (clientTypesMap[typeName.name]) {
clientTypesMap[name] = `Record<string, ${clientTypesMap[typeName.name]}>`;
} else {
// Assume it's an interface which would be available in v3.
clientTypesMap[name] = `Record<string, ${typeName.name}>`;
}
} else {
console.log("TSTypeLiteral TSTypeReference without Identifier type:", name);
}
} else if (Object.keys(ElementTypeToNativeTypeMap).includes(typeAnnotation.type)) {
clientTypesMap[name] = `Record<string, ${
ElementTypeToNativeTypeMap[typeAnnotation.type]
}>`;
} else {
console.log("TSTypeLiteral with unsupported typeAnnotation type:", name);
}
}
}
}
});

tsTypes.forEach((tsType) => {
const name = tsType.id.name;
const type = tsType.typeAnnotation.type;
if (!TYPES_TO_SKIP.includes(name) && !clientTypesMap[name] && type !== "TSUnionType") {
console.log("Unsupported type:", name);
}
});
});

return Object.entries(clientTypesMap)
.sort(([key1], [key2]) => key1.localeCompare(key2))
.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
};
30 changes: 30 additions & 0 deletions scripts/generateClientTypesMap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { writeFile } from "fs/promises";
import { join } from "path";
import { format } from "prettier";

import { CLIENT_NAMES } from "../../src/transforms/v2-to-v3/config";
import { getClientTypeMap } from "./getClientTypeMap";

const codegenComment = `// This file is generated by scripts/generateClientTypesMap/index.ts
// Do not edit this file directly. Instead, edit the script and run it to regenerate this file.`;

const filePath = join("src", "transforms", "v2-to-v3", "config", "CLIENT_TYPES_MAP.ts");
const relativeFilePath = join(__dirname, "..", "..", filePath);

(async () => {
let fileContent = codegenComment;

fileContent += `\n\n/* eslint-disable @typescript-eslint/naming-convention */`;
fileContent += `\nexport const CLIENT_TYPES_MAP: Record<string, Record<string, string>> = `;

const clientTypesMap = {};

for (const clientName of CLIENT_NAMES) {
clientTypesMap[clientName] = await getClientTypeMap(clientName);
}

fileContent += JSON.stringify(clientTypesMap);
fileContent += `;\n`;

await writeFile(relativeFilePath, format(fileContent, { parser: "typescript", printWidth: 100 }));
})();
Loading

0 comments on commit 2f3a713

Please sign in to comment.