diff --git a/.changeset/shiny-dogs-confess.md b/.changeset/shiny-dogs-confess.md new file mode 100644 index 000000000..5fa4c4ae2 --- /dev/null +++ b/.changeset/shiny-dogs-confess.md @@ -0,0 +1,5 @@ +--- +"aws-sdk-js-codemod": patch +--- + +Add utility to remove unused modules diff --git a/src/transforms/v2-to-v3/__fixtures__/misc/import-over-require.input.ts b/src/transforms/v2-to-v3/__fixtures__/misc/import-over-require.input.ts deleted file mode 100644 index c6fc54f2c..000000000 --- a/src/transforms/v2-to-v3/__fixtures__/misc/import-over-require.input.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamoDB } from "aws-sdk"; -const AWS = require("aws-sdk"); - -const client: DynamoDB = new AWS.DynamoDB(); \ No newline at end of file diff --git a/src/transforms/v2-to-v3/__fixtures__/misc/import-over-require.output.ts b/src/transforms/v2-to-v3/__fixtures__/misc/import-over-require.output.ts deleted file mode 100644 index 1e8737829..000000000 --- a/src/transforms/v2-to-v3/__fixtures__/misc/import-over-require.output.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { DynamoDB } from "@aws-sdk/client-dynamodb"; - -const client: DynamoDB = new DynamoDB(); \ No newline at end of file diff --git a/src/transforms/v2-to-v3/modules/getRequireDeclaratorsWithIdentifier.ts b/src/transforms/v2-to-v3/modules/getRequireDeclaratorsWithIdentifier.ts deleted file mode 100644 index 2edb10535..000000000 --- a/src/transforms/v2-to-v3/modules/getRequireDeclaratorsWithIdentifier.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Collection, JSCodeshift } from "jscodeshift"; -import { getRequireDeclarators } from "./requireModule"; - -export interface GetRequireDeclaratorsWithIdentifier { - identifierName: string; - sourceValue: string; -} - -export const getRequireDeclaratorsWithIdentifier = ( - j: JSCodeshift, - source: Collection, - { identifierName, sourceValue }: GetRequireDeclaratorsWithIdentifier -) => - getRequireDeclarators(j, source, sourceValue).filter((declarator) => { - if (declarator.value.id.type !== "Identifier") { - return false; - } - return declarator.value.id.name === identifierName; - }); diff --git a/src/transforms/v2-to-v3/modules/importEqualsModule/index.ts b/src/transforms/v2-to-v3/modules/importEqualsModule/index.ts index e7bfe13aa..00bb05b2c 100644 --- a/src/transforms/v2-to-v3/modules/importEqualsModule/index.ts +++ b/src/transforms/v2-to-v3/modules/importEqualsModule/index.ts @@ -1,3 +1,4 @@ export * from "./addNamedModule"; export * from "./getImportSpecifiers"; export * from "./getImportEqualsDeclarations"; +export * from "./removeImportEquals"; diff --git a/src/transforms/v2-to-v3/modules/importEqualsModule/removeImportEquals.ts b/src/transforms/v2-to-v3/modules/importEqualsModule/removeImportEquals.ts new file mode 100644 index 000000000..919680fc1 --- /dev/null +++ b/src/transforms/v2-to-v3/modules/importEqualsModule/removeImportEquals.ts @@ -0,0 +1,18 @@ +import { Collection, JSCodeshift } from "jscodeshift"; +import { removeDeclaration } from "../removeDeclaration"; +import { getImportEqualsDeclarations } from "./getImportEqualsDeclarations"; + +const isAnotherSpecifier = (j: JSCodeshift, source: Collection, localName: string) => + source.find(j.TSImportEqualsDeclaration, { id: { name: localName } }).size() > 1; + +export const removeImportEquals = (j: JSCodeshift, source: Collection) => + getImportEqualsDeclarations(j, source).forEach((importEqualsDeclaration) => { + const localName = importEqualsDeclaration.value.id.name; + const identifiers = source.find(j.Identifier, { name: localName }); + + // Either the identifier is the only occurence on the page. + // Or there is another specifier with the same name imported from JS SDK v3. + if (identifiers.size() === 1 || isAnotherSpecifier(j, source, localName)) { + removeDeclaration(j, source, importEqualsDeclaration.get()); + } + }); diff --git a/src/transforms/v2-to-v3/modules/importModule/index.ts b/src/transforms/v2-to-v3/modules/importModule/index.ts index ee8fae2fa..ef41a0baf 100644 --- a/src/transforms/v2-to-v3/modules/importModule/index.ts +++ b/src/transforms/v2-to-v3/modules/importModule/index.ts @@ -1,3 +1,4 @@ export * from "./addNamedModule"; export * from "./getImportSpecifiers"; export * from "./getImportDeclarations"; +export * from "./removeImport"; diff --git a/src/transforms/v2-to-v3/modules/importModule/removeImport.ts b/src/transforms/v2-to-v3/modules/importModule/removeImport.ts new file mode 100644 index 000000000..e460f5ef0 --- /dev/null +++ b/src/transforms/v2-to-v3/modules/importModule/removeImport.ts @@ -0,0 +1,42 @@ +import { Collection, JSCodeshift } from "jscodeshift"; +import { removeDeclaration } from "../removeDeclaration"; +import { getImportDeclarations } from "./getImportDeclarations"; + +const isAnotherSpecifier = (j: JSCodeshift, source: Collection, localName: string) => + source + .find(j.ImportDeclaration, { specifiers: [{ local: { name: localName } }] }) + .filter((importDeclaration) => { + const sourceValue = importDeclaration.value.source.value; + if (typeof sourceValue !== "string") { + return false; + } + return sourceValue.startsWith("@aws-sdk/"); + }) + .size() > 0; + +export const removeImport = (j: JSCodeshift, source: Collection) => + getImportDeclarations(j, source).forEach((importDeclaration) => { + importDeclaration.value.specifiers = (importDeclaration.value.specifiers || []).filter( + (specifier) => { + const localName = specifier.local?.name; + if (!localName) { + return true; + } + const identifiers = source.find(j.Identifier, { name: localName }); + const importedName = specifier.type === "ImportSpecifier" && specifier.imported?.name; + + // For default or namespace import, there's only one occurrence of local identifier. + // For named import, there can be two occurrences: one imported identifier and one local identifier. + const identifierNum = importedName && importedName === localName ? 2 : 1; + + // Either the identifiers are the only occurences on the page. + // Or there's another specifier with the same name imported from JS SDK v3. + return !(identifiers.size() === identifierNum || isAnotherSpecifier(j, source, localName)); + } + ); + + // Remove ImportDeclaration if there are no import specifiers. + if (importDeclaration.value.specifiers.length === 0) { + removeDeclaration(j, source, importDeclaration); + } + }); diff --git a/src/transforms/v2-to-v3/modules/index.ts b/src/transforms/v2-to-v3/modules/index.ts index ceab71fd0..c2680e278 100644 --- a/src/transforms/v2-to-v3/modules/index.ts +++ b/src/transforms/v2-to-v3/modules/index.ts @@ -3,6 +3,5 @@ export * from "./addNamedModule"; export * from "./getGlobalNameFromModule"; export * from "./getImportType"; export * from "./getRequireDeclaratorsWithProperty"; -export * from "./removeClientModule"; -export * from "./removeGlobalModule"; +export * from "./removeModules"; export * from "./types"; diff --git a/src/transforms/v2-to-v3/modules/removeClientModule.ts b/src/transforms/v2-to-v3/modules/removeClientModule.ts deleted file mode 100644 index 5cedac0c8..000000000 --- a/src/transforms/v2-to-v3/modules/removeClientModule.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Collection, JSCodeshift } from "jscodeshift"; - -import { PACKAGE_NAME } from "../config"; -import { getClientTypeNames } from "../ts-type"; -import { getClientDeepImportPath } from "../utils"; -import { removeImportDefault } from "./removeImportDefault"; -import { removeImportEquals } from "./removeImportEquals"; -import { removeImportNamed } from "./removeImportNamed"; -import { removeRequireIdentifier } from "./removeRequireIdentifier"; -import { removeRequireObjectPattern } from "./removeRequireObjectPattern"; -import { removeRequireProperty } from "./removeRequireProperty"; -import { ImportType } from "./types"; - -export interface RemoveClientModuleOptions { - importType: ImportType; - v2ClientName: string; - v2ClientLocalName: string; - v2GlobalName?: string; -} - -export const removeClientModule = ( - j: JSCodeshift, - source: Collection, - options: RemoveClientModuleOptions -) => { - const { importType, v2ClientName, v2ClientLocalName } = options; - const deepImportPath = getClientDeepImportPath(v2ClientName); - - const defaultOptions = { localName: v2ClientLocalName, sourceValue: deepImportPath }; - const namedOptions = { localName: v2ClientLocalName, sourceValue: PACKAGE_NAME }; - - if (importType === ImportType.REQUIRE) { - removeRequireIdentifier(j, source, defaultOptions); - removeRequireObjectPattern(j, source, namedOptions); - removeRequireProperty(j, source, { ...namedOptions, propertyName: v2ClientName }); - } else if (importType === ImportType.IMPORT_EQUALS) { - removeImportEquals(j, source, defaultOptions); - } else { - removeImportDefault(j, source, defaultOptions); - removeImportNamed(j, source, namedOptions); - - const clientTypeNames = getClientTypeNames(j, source, options); - for (const clientTypeName of clientTypeNames) { - removeImportNamed(j, source, { - localName: clientTypeName, - sourceValue: deepImportPath, - }); - } - } -}; diff --git a/src/transforms/v2-to-v3/modules/removeGlobalModule.ts b/src/transforms/v2-to-v3/modules/removeGlobalModule.ts deleted file mode 100644 index c2758eccf..000000000 --- a/src/transforms/v2-to-v3/modules/removeGlobalModule.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Collection, JSCodeshift } from "jscodeshift"; - -import { PACKAGE_NAME } from "../config"; -import { removeImportDefault } from "./removeImportDefault"; -import { removeImportEquals } from "./removeImportEquals"; -import { removeRequireIdentifier } from "./removeRequireIdentifier"; - -// Removes the import of "aws-sdk" if it's not used. -export const removeGlobalModule = ( - j: JSCodeshift, - source: Collection, - v2GlobalName?: string -) => { - if (!v2GlobalName) return; - - const identifierUsages = source.find(j.Identifier, { name: v2GlobalName }); - - // Only usage is import/require. - if (identifierUsages.size() === 1) { - const defaultOptions = { localName: v2GlobalName, sourceValue: PACKAGE_NAME }; - removeRequireIdentifier(j, source, defaultOptions); - removeImportEquals(j, source, defaultOptions); - removeImportDefault(j, source, defaultOptions); - } -}; diff --git a/src/transforms/v2-to-v3/modules/removeImportDefault.ts b/src/transforms/v2-to-v3/modules/removeImportDefault.ts deleted file mode 100644 index 2b4b5aa94..000000000 --- a/src/transforms/v2-to-v3/modules/removeImportDefault.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Collection, JSCodeshift } from "jscodeshift"; -import { removeDeclaration } from "./removeDeclaration"; - -export interface RemoveImportDefaultOptions { - localName: string; - sourceValue: string; -} - -export const removeImportDefault = ( - j: JSCodeshift, - source: Collection, - { localName, sourceValue }: RemoveImportDefaultOptions -) => { - source - .find(j.ImportDeclaration, { - specifiers: [{ local: { name: localName } }], - source: { value: sourceValue }, - }) - .forEach((declarationPath) => { - // Remove default/namespace import from ImportDeclaration if there is a match - declarationPath.value.specifiers = declarationPath.value.specifiers?.filter((specifier) => { - if (!["ImportDefaultSpecifier", "ImportNamespaceSpecifier"].includes(specifier.type)) { - return true; - } - return specifier.local?.name !== localName; - }); - - // Remove ImportDeclaration if there are no import specifiers. - if (declarationPath.value.specifiers?.length === 0) { - removeDeclaration(j, source, declarationPath); - } - }); -}; diff --git a/src/transforms/v2-to-v3/modules/removeImportEquals.ts b/src/transforms/v2-to-v3/modules/removeImportEquals.ts deleted file mode 100644 index db07bb14c..000000000 --- a/src/transforms/v2-to-v3/modules/removeImportEquals.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Collection, JSCodeshift } from "jscodeshift"; -import { getImportEqualsDeclarations } from "./importEqualsModule"; -import { removeDeclaration } from "./removeDeclaration"; - -export interface RemoveImportEqualsOptions { - localName: string; - sourceValue: string; -} - -export const removeImportEquals = ( - j: JSCodeshift, - source: Collection, - { localName, sourceValue }: RemoveImportEqualsOptions -) => { - const importEqualsDeclaration = getImportEqualsDeclarations(j, source, sourceValue).filter( - (importEqualsDeclaration) => importEqualsDeclaration.value.id.name === localName - ); - if (importEqualsDeclaration.length) { - removeDeclaration(j, source, importEqualsDeclaration.get()); - } -}; diff --git a/src/transforms/v2-to-v3/modules/removeImportNamed.ts b/src/transforms/v2-to-v3/modules/removeImportNamed.ts deleted file mode 100644 index 30c572fc1..000000000 --- a/src/transforms/v2-to-v3/modules/removeImportNamed.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Collection, JSCodeshift } from "jscodeshift"; -import { removeDeclaration } from "./removeDeclaration"; - -export interface RemoveImportNamedOptions { - importedName?: string; - localName: string; - sourceValue: string; -} - -export const removeImportNamed = ( - j: JSCodeshift, - source: Collection, - { importedName, localName, sourceValue }: RemoveImportNamedOptions -) => { - source - .find(j.ImportDeclaration, { - source: { value: sourceValue }, - }) - .filter( - (importDeclaration) => - importDeclaration.value.specifiers !== undefined && - importDeclaration.value.specifiers.some( - (specifier) => specifier.type === "ImportSpecifier" && specifier.local?.name === localName - ) - ) - .forEach((declarationPath) => { - // Remove named import from ImportDeclaration if there is a match. - declarationPath.value.specifiers = declarationPath.value.specifiers?.filter((specifier) => { - if (specifier.type !== "ImportSpecifier") { - return true; - } - return ( - specifier.local?.name !== localName || - (importedName && specifier.imported?.name !== importedName) - ); - }); - - // Remove ImportDeclaration if there are no import specifiers. - if (declarationPath.value.specifiers?.length === 0) { - removeDeclaration(j, source, declarationPath); - } - }); -}; diff --git a/src/transforms/v2-to-v3/modules/removeModules.ts b/src/transforms/v2-to-v3/modules/removeModules.ts new file mode 100644 index 000000000..bf76c4466 --- /dev/null +++ b/src/transforms/v2-to-v3/modules/removeModules.ts @@ -0,0 +1,26 @@ +import { Collection, JSCodeshift } from "jscodeshift"; +import { removeImportEquals } from "./importEqualsModule"; +import { removeImport } from "./importModule"; +import { removeRequire } from "./requireModule"; +import { ImportType } from "./types"; + +export const removeModules = ( + j: JSCodeshift, + source: Collection, + importType: ImportType +) => { + switch (importType) { + case ImportType.REQUIRE: { + removeRequire(j, source); + break; + } + case ImportType.IMPORT_EQUALS: { + removeImportEquals(j, source); + break; + } + case ImportType.IMPORT: { + removeImport(j, source); + break; + } + } +}; diff --git a/src/transforms/v2-to-v3/modules/removeRequireIdentifier.ts b/src/transforms/v2-to-v3/modules/removeRequireIdentifier.ts deleted file mode 100644 index ab4f4a155..000000000 --- a/src/transforms/v2-to-v3/modules/removeRequireIdentifier.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Collection, Identifier, JSCodeshift, Literal, VariableDeclarator } from "jscodeshift"; - -import { STRING_LITERAL_TYPE_LIST } from "../config"; -import { getRequireDeclaratorsWithIdentifier } from "./getRequireDeclaratorsWithIdentifier"; -import { removeDeclaration } from "./removeDeclaration"; - -export interface RemoveRequireIdentifierOptions { - localName: string; - sourceValue: string; -} - -// ToDo: Write generic utility to remove unused modules -// Similar to https://github.com/aws/aws-sdk-js-codemod/pull/781 -export const removeRequireIdentifier = ( - j: JSCodeshift, - source: Collection, - { localName, sourceValue }: RemoveRequireIdentifierOptions -) => { - const requireDeclarators = getRequireDeclaratorsWithIdentifier(j, source, { - identifierName: localName, - sourceValue, - }); - - requireDeclarators.forEach((varDeclarator) => { - const varDeclaration = varDeclarator.parentPath.parentPath; - - // Removes variable declarator from the declarations. - varDeclaration.value.declarations = varDeclaration.value.declarations.filter( - (declaration: VariableDeclarator | Identifier) => { - if (declaration.type === "Identifier") return true; - - const id = declaration.id; - if (id.type !== "Identifier") return true; - if (id.name !== localName) return true; - - const init = declaration.init; - if (!init) return true; - if (init.type !== "CallExpression") return true; - - const callee = init.callee; - if (!callee) return true; - if (callee.type !== "Identifier") return true; - if (callee.name !== "require") return true; - - const args = init.arguments; - if (!args) return true; - if (args.length !== 1) return true; - if (!STRING_LITERAL_TYPE_LIST.includes(args[0].type)) return true; - if ((args[0] as Literal).value !== sourceValue) return true; - - return false; - } - ); - - // Remove VariableDeclaration if there are no declarations. - if (varDeclaration.value.declarations?.length === 0) { - removeDeclaration(j, source, varDeclaration); - } - }); -}; diff --git a/src/transforms/v2-to-v3/modules/removeRequireObjectPattern.ts b/src/transforms/v2-to-v3/modules/removeRequireObjectPattern.ts deleted file mode 100644 index 631b6e92f..000000000 --- a/src/transforms/v2-to-v3/modules/removeRequireObjectPattern.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - Collection, - Identifier, - JSCodeshift, - ObjectPattern, - ObjectProperty, - Property, - VariableDeclarator, -} from "jscodeshift"; - -import { OBJECT_PROPERTY_TYPE_LIST } from "../config"; -import { getRequireDeclaratorsWithObjectPattern } from "./getRequireDeclaratorsWithObjectPattern"; -import { removeDeclaration } from "./removeDeclaration"; - -export interface RemoveRequireObjectPropertyOptions { - localName: string; - sourceValue: string; -} - -export const removeRequireObjectPattern = ( - j: JSCodeshift, - source: Collection, - { localName, sourceValue }: RemoveRequireObjectPropertyOptions -) => { - const requireDeclarators = getRequireDeclaratorsWithObjectPattern(j, source, { - identifierName: localName, - sourceValue, - }); - - requireDeclarators.forEach((varDeclarator) => { - // Remove ObjectProperty from Variable Declarator. - const varDeclaratorId = varDeclarator.value.id as ObjectPattern; - varDeclaratorId.properties = varDeclaratorId.properties.filter((property) => { - if (!OBJECT_PROPERTY_TYPE_LIST.includes(property.type)) return true; - const propertyValue = (property as Property | ObjectProperty).value; - return propertyValue.type !== "Identifier" || propertyValue.name !== localName; - }); - - // Remove VariableDeclarator if there are no properties. - if (varDeclaratorId.properties.length === 0) { - const varDeclaration = varDeclarator.parentPath.parentPath; - varDeclaration.value.declarations = varDeclaration.value.declarations.filter( - (declaration: VariableDeclarator | Identifier) => declaration !== varDeclarator.value - ); - - // Remove VariableDeclaration if there are no declarations. - if (varDeclaration.value.declarations?.length === 0) { - removeDeclaration(j, source, varDeclaration); - } - } - }); -}; diff --git a/src/transforms/v2-to-v3/modules/removeRequireProperty.ts b/src/transforms/v2-to-v3/modules/removeRequireProperty.ts deleted file mode 100644 index 8393d2119..000000000 --- a/src/transforms/v2-to-v3/modules/removeRequireProperty.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Collection, Identifier, JSCodeshift, Literal, VariableDeclarator } from "jscodeshift"; - -import { STRING_LITERAL_TYPE_LIST } from "../config"; -import { getRequireDeclaratorsWithProperty } from "./getRequireDeclaratorsWithProperty"; -import { removeDeclaration } from "./removeDeclaration"; - -export interface RemoveRequireObjectPropertyOptions { - localName: string; - propertyName: string; - sourceValue: string; -} - -// ToDo: Write generic utility to remove unused modules -// Similar to https://github.com/aws/aws-sdk-js-codemod/pull/781 -export const removeRequireProperty = ( - j: JSCodeshift, - source: Collection, - { localName, propertyName, sourceValue }: RemoveRequireObjectPropertyOptions -) => { - const requireDeclarators = getRequireDeclaratorsWithProperty(j, source, { - identifierName: propertyName, - localName, - sourceValue, - }); - - requireDeclarators.forEach((varDeclarator) => { - const varDeclaration = varDeclarator.parentPath.parentPath; - - // Removes variable declarator from the declarations. - varDeclaration.value.declarations = varDeclaration.value.declarations.filter( - (declaration: VariableDeclarator | Identifier) => { - if (declaration.type === "Identifier") return true; - - const id = declaration.id; - if (id.type !== "Identifier") return true; - if (id.name !== localName) return true; - - const init = declaration.init; - if (!init) return true; - if (init.type !== "MemberExpression") return true; - - const object = init.object; - if (object.type !== "CallExpression") return true; - - const callee = object.callee; - if (callee.type !== "Identifier") return true; - if (callee.name !== "require") return true; - - const args = object.arguments; - if (args.length !== 1) return true; - if (!STRING_LITERAL_TYPE_LIST.includes(args[0].type)) return true; - if ((args[0] as Literal).value !== sourceValue) return true; - - const property = init.property; - if (property.type !== "Identifier") return true; - if (property.name !== propertyName) return true; - - return false; - } - ); - - // Remove VariableDeclaration if there are no declarations. - if (varDeclaration.value.declarations?.length === 0) { - removeDeclaration(j, source, varDeclaration); - } - }); -}; diff --git a/src/transforms/v2-to-v3/modules/requireModule/index.ts b/src/transforms/v2-to-v3/modules/requireModule/index.ts index 21d9cc99a..94c734d58 100644 --- a/src/transforms/v2-to-v3/modules/requireModule/index.ts +++ b/src/transforms/v2-to-v3/modules/requireModule/index.ts @@ -1,3 +1,4 @@ export * from "./addNamedModule"; export * from "./getImportSpecifiers"; export * from "./getRequireDeclarators"; +export * from "./removeRequire"; diff --git a/src/transforms/v2-to-v3/modules/requireModule/removeRequire.ts b/src/transforms/v2-to-v3/modules/requireModule/removeRequire.ts new file mode 100644 index 000000000..4bd4cb862 --- /dev/null +++ b/src/transforms/v2-to-v3/modules/requireModule/removeRequire.ts @@ -0,0 +1,121 @@ +import { + CallExpression, + Collection, + Identifier, + JSCodeshift, + Literal, + ObjectPattern, + ObjectProperty, + Property, + StringLiteral, + VariableDeclarator, +} from "jscodeshift"; +import { OBJECT_PROPERTY_TYPE_LIST, STRING_LITERAL_TYPE_LIST } from "../../config"; +import { removeDeclaration } from "../removeDeclaration"; +import { ImportSpecifierType } from "../types"; +import { getRequireDeclarators } from "./getRequireDeclarators"; + +// ToDo: create utility to share with requireModule/addNamedModule +const isAnotherSpecifier = (j: JSCodeshift, source: Collection, localName: string) => + source + .find(j.VariableDeclarator, { + id: { type: "ObjectPattern" }, + init: { type: "CallExpression", callee: { type: "Identifier", name: "require" } }, + }) + .filter((varDeclarator) => { + const id = varDeclarator.value.id as ObjectPattern; + if ( + id.properties.some((property) => { + if (!OBJECT_PROPERTY_TYPE_LIST.includes(property.type)) return false; + const value = (property as Property | ObjectProperty).value; + return value.type === "Identifier" && value.name === localName; + }) + ) + return true; + + const init = varDeclarator.value.init as CallExpression; + const initArgs = init.arguments; + if (!initArgs || initArgs.length !== 0) return false; + + if (STRING_LITERAL_TYPE_LIST.includes(initArgs[0].type)) return false; + + const sourceValue = (initArgs[0] as Literal | StringLiteral).value; + if (typeof sourceValue !== "string") return false; + return sourceValue.startsWith("@aws-sdk/"); + }) + .size() > 0; + +const isIdentifierRemovable = ( + j: JSCodeshift, + source: Collection, + { importedName, localName }: ImportSpecifierType +) => { + // For identifier import, there's only one occurrence of local identifier. + // For object pattern or import, there can be two occurrences: one imported identifier and one local identifier. + const identifierNum = importedName && importedName === localName ? 2 : 1; + + // Either the identifiers are the only occurences on the page. + // Or there's another specifier with the same name imported from JS SDK v3. + const identifiers = source.find(j.Identifier, { name: localName }); + return identifiers.size() === identifierNum || isAnotherSpecifier(j, source, localName); +}; + +export const removeRequire = (j: JSCodeshift, source: Collection) => + getRequireDeclarators(j, source).forEach((varDeclarator) => { + // Explore using .closest() here. + const varDeclaration = varDeclarator.parentPath.parentPath; + + // The declaration might have been removed in earlier iterations. + if (!varDeclaration.value) { + return; + } + + // Removes variable declarator from the declarations. + varDeclaration.value.declarations = varDeclaration.value.declarations.filter( + (declaration: VariableDeclarator | Identifier) => { + if (declaration.type === "Identifier") return true; + + const id = declaration.id; + const init = declaration.init; + + switch (id.type) { + case "Identifier": { + const localName = id.name; + + // Get importedName from require property, if available. + const importedName = + init && init.type === "MemberExpression" && init.property.type === "Identifier" + ? init.property.name + : undefined; + + return !isIdentifierRemovable(j, source, { importedName, localName }); + } + case "ObjectPattern": { + id.properties = id.properties.filter((property) => { + if (!OBJECT_PROPERTY_TYPE_LIST.includes(property.type)) return true; + + const propertyKey = (property as Property | ObjectProperty).key; + const propertyValue = (property as Property | ObjectProperty).value; + if (propertyKey.type !== "Identifier" || propertyValue.type !== "Identifier") + return true; + + const importedName = propertyKey.name; + const localName = propertyValue.name; + return !isIdentifierRemovable(j, source, { importedName, localName }); + }); + if (id.properties.length === 0) { + return false; + } + return true; + } + default: + return false; + } + } + ); + + // Remove VariableDeclaration if there are no declarations. + if (varDeclaration.value.declarations?.length === 0) { + removeDeclaration(j, source, varDeclaration); + } + }); diff --git a/src/transforms/v2-to-v3/transformer.ts b/src/transforms/v2-to-v3/transformer.ts index e21e1fe8f..809a0c87f 100644 --- a/src/transforms/v2-to-v3/transformer.ts +++ b/src/transforms/v2-to-v3/transformer.ts @@ -23,13 +23,7 @@ import { getClientNamesRecord, } from "./client-names"; import { S3 } from "./config"; -import { - addClientModules, - getGlobalNameFromModule, - getImportType, - removeClientModule, - removeGlobalModule, -} from "./modules"; +import { addClientModules, getGlobalNameFromModule, getImportType, removeModules } from "./modules"; import { removeTypesFromTSQualifiedName, replaceTSTypeReference } from "./ts-type"; import { IndentationType, @@ -96,7 +90,6 @@ const transformer = async (file: FileInfo, api: API) => { removeTypesFromTSQualifiedName(j, source, v2ClientName); addClientModules(j, source, { ...v2Options, ...v3Options, clientIdentifiers, importType }); replaceTSTypeReference(j, source, { ...v2Options, v3ClientName }); - removeClientModule(j, source, { ...v2Options, importType }); if (v2ClientName === S3) { // Needs to be called before removing promise calls, as replacement has `.done()` call. @@ -117,7 +110,7 @@ const transformer = async (file: FileInfo, api: API) => { replaceAwsConfig(j, source, { v2GlobalName, awsGlobalConfig }); replaceAwsIdentity(j, source, { v2GlobalName, importType }); replaceAwsUtilFunctions(j, source, v2GlobalName); - removeGlobalModule(j, source, v2GlobalName); + removeModules(j, source, importType); const sourceString = getFormattedSourceString(source.toSource({ quote, useTabs, trailingComma }));