Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(hcl2cdk): use correct import path per language #2935

Merged
merged 6 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0

import {
replaceCsharpImports,
replaceGoImports,
replaceJavaImports,
replacePythonImports,
} from "../jsii-rosetta-workarounds";

describe("jsii-rosetta-workarounds", () => {
describe("replacePythonImports", () => {
it("normal imports", () => {
const code = `
import constructs as constructs
# Provider bindings are generated by running cdktf get.
# See https://cdk.tf/provider-generation for more details.
import ...gen.providers.scaleway as scaleway
class MyConvertedCode(constructs.Construct):
def __init__(self, scope, name):
super().__init__(scope, name)
scaleway.provider.ScalewayProvider(self, "scaleway",
region="fr-par",
zone="fr-par-1"
)`;

expect(replacePythonImports(code)).toEqual(
expect.stringContaining("import imports.scaleway as scaleway")
);
});

it("granular imports", () => {
const code = `from ...gen.providers.aws.lib.rdsCluster import RdsCluster`;

expect(replacePythonImports(code)).toEqual(
"from imports.aws.rdsCluster import RdsCluster"
);

const withoutLibCode = `from ...gen.providers.azurerm.resource_group import ResourceGroup`;
expect(replacePythonImports(withoutLibCode)).toEqual(
"from imports.azurerm.resource_group import ResourceGroup"
);
});

it("resource imports", () => {
const code = `import ...gen.providers.aws.lib.rdsCluster as RdsCluster`;

expect(replacePythonImports(code)).toEqual(
"import imports.aws.rdsCluster as RdsCluster"
);
});

it("fixes module imports", () => {
const code = `import ...gen.modules.hello_module as HelloModule`;

expect(replacePythonImports(code)).toEqual(
"import imports.hello_module as HelloModule"
);
});
});

describe("replaceCsharpImports", () => {
it("normal imports", () => {
const code = `using Gen.Providers.Aws.Lib.RdsCluster;`;

expect(replaceCsharpImports(code)).toEqual("using aws.RdsCluster;");
});

it("fixes module imports", () => {
const code = `using Gen.Modules.HelloModule`;

expect(replaceCsharpImports(code)).toEqual("using HelloModule");

const anotherCode = `using Gen.Modules.Hello.Module`;

expect(replaceCsharpImports(anotherCode)).toEqual("using Hello.Module");
});
});

describe("replaceJavaImports", () => {
it("normal imports", () => {
const code = `import gen.providers.scaleway.objectResource.*;`;

expect(replaceJavaImports(code)).toEqual(
"import imports.scaleway.objectResource.*;"
);
});

it("precise imports", () => {
const code = `import gen.providers.aws.lib.s3Bucket.S3Bucket;`;

expect(replaceJavaImports(code)).toEqual(
"import imports.aws.s3Bucket.S3Bucket;"
);

const withoutLibImport = `import gen.providers.aws.s3Bucket.S3Bucket;`;

expect(replaceJavaImports(withoutLibImport)).toEqual(
"import imports.aws.s3Bucket.S3Bucket;"
);
});

it("fixes module imports", () => {
const code = `import gen.modules.hello.module.*;`;

expect(replaceJavaImports(code)).toEqual(
"import imports.hello.module.*;"
);

const anotherCode = `import gen.modules.helloModule.*;`;

expect(replaceJavaImports(anotherCode)).toEqual(
"import imports.helloModule.*;"
);
});
});

describe("replaceGoImports", () => {
it("normal imports", () => {
const code = `import "github.com/aws-samples/dummy/gen/providers/scaleway/objectBucket"`;

expect(replaceGoImports(code)).toEqual(
`import "cdk.tf/go/stack/generated/scaleway/objectBucket"`
);
});

it("precise imports", () => {
const code = `import "github.com/aws-samples/dummy/gen/providers/aws/lib/dbInstance"`;

expect(replaceGoImports(code)).toEqual(
`import "cdk.tf/go/stack/generated/aws/dbInstance"`
);
});

it("fixes module imports", () => {
const code = `import helloModule "github.com/aws-samples/dummy/gen/modules/hello_module"`;

expect(replaceGoImports(code)).toEqual(
// "github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/terraform-aws-modules/aws/vpc"
`import helloModule "cdk.tf/go/stack/generated/hello_module"`
);
});
});
});
51 changes: 35 additions & 16 deletions packages/@cdktf/hcl2cdk/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ import { getProviderRequirements } from "./provider";
import { logger } from "./utils";
import { FQPN } from "@cdktf/provider-generator/lib/get/generator/provider-schema";
import { attributeNameToCdktfName } from "./generation";
import {
replaceCsharpImports,
replaceGoImports,
replaceJavaImports,
replacePythonImports,
} from "./jsii-rosetta-workarounds";

export const CODE_MARKER = "// define resources here";

Expand Down Expand Up @@ -382,6 +388,7 @@ For a more precise conversion please use the --provider flag in convert.`

// We split up the generated code so that users can have more control over what to insert where
return {
// TODO: Remove imports and code because rosetta won't be able to translate them
all: await gen([
...constructImports,
...moduleImports(plan.module),
Expand All @@ -405,9 +412,28 @@ For a more precise conversion please use the --provider flag in convert.`
}

type File = { contents: string; fileName: string };
const translators = {
python: {
visitor: new rosetta.PythonVisitor(),
postTranslationMutation: replacePythonImports,
},
java: {
visitor: new rosetta.JavaVisitor(),
postTranslationMutation: replaceJavaImports,
},
csharp: {
visitor: new rosetta.CSharpVisitor(),
postTranslationMutation: replaceCsharpImports,
},
go: {
visitor: new rosetta.GoVisitor(),
postTranslationMutation: replaceGoImports,
},
};

function translatorForVisitor(visitor: any) {
function translatorForLanguage(language: keyof typeof translators) {
return (file: File, throwOnTranslationError: boolean) => {
const { visitor, postTranslationMutation } = translators[language];
const { translation, diagnostics } = rosetta.translateTypeScript(
file,
visitor,
Expand All @@ -418,33 +444,23 @@ function translatorForVisitor(visitor: any) {
throwOnTranslationError &&
diagnostics.filter((diag) => diag.isError).length > 0
) {
logger.debug(
`Could not translate TS to ${visitor.language}:\n${file.contents}`
);
logger.debug(`Could not translate TS to ${language}:\n${file.contents}`);
throw new Error(
`Could not translate TS to ${visitor.language}: ${diagnostics
`Could not translate TS to ${language}: ${diagnostics
.map((diag) => diag.formattedMessage)
.join("\n")}`
);
}

return translation;
return postTranslationMutation(translation);
};
}

const translations = {
typescript: (file: File, _throwOnTranslationError: boolean) => file.contents,
python: translatorForVisitor(new rosetta.PythonVisitor()),
java: translatorForVisitor(new rosetta.JavaVisitor()),
csharp: translatorForVisitor(new rosetta.CSharpVisitor()),
go: translatorForVisitor(new rosetta.GoVisitor()),
};

type ConvertOptions = {
/**
* The language to convert to
*/
language: keyof typeof translations;
language: keyof typeof translators | "typescript";
/**
* The provider schema to use for conversion
*/
Expand All @@ -470,7 +486,10 @@ export async function convert(
}: ConvertOptions
) {
const fileName = "terraform.tf";
const translater = translations[language];
const translater =
language === "typescript"
? (file: File, _throwOnTranslationError: boolean) => file.contents
: translatorForLanguage(language);

if (!translater) {
throw new Error("Unsupported language used: " + language);
Expand Down
145 changes: 145 additions & 0 deletions packages/@cdktf/hcl2cdk/lib/jsii-rosetta-workarounds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0

export function replacePythonImports(code: string) {
return code
.split("\n")
.map((line) => {
// Replace from-import lines with lib
const fromImportLibRegex =
/from \.\.\.gen\.providers\.([^.]+)(?:\.lib)?\.(.*) import/;
if (fromImportLibRegex.test(line)) {
return line.replace(fromImportLibRegex, "from imports.$1.$2 import");
}

// Replace import lines with lib
const importLibRegex =
/import \.\.\.gen\.providers\.([^.]+)(?:\.lib)?\.(.*) as (.*)/;
if (importLibRegex.test(line)) {
return line.replace(importLibRegex, "import imports.$1.$2 as $3");
}

// Replace from-import lines
if (line.startsWith("from ...gen.providers.")) {
return line.replace("from ...gen.providers.", "from imports.");
}
// Replace import lines
if (line.startsWith("import ...gen.providers.")) {
return line.replace("import ...gen.providers.", "import imports.");
}

// Replace modules
if (line.startsWith("import ...gen.modules.")) {
return line.replace("import ...gen.modules.", "import imports.");
}

return line;
})
.join("\n");
}

export function replaceJavaImports(code: string) {
return code
.split("\n")
.map((line) => {
// Replace using lines with lib and precices import
const importWithLib =
/import gen\.providers\.([^.]+)(?:\.lib)?\.([^.]+)\.(.*);/;
const matchWithLib = line.match(importWithLib);
if (matchWithLib) {
const [, provider, resource, className] = matchWithLib;
return `import imports.${provider}.${resource}.${className};`;
}

// Replace using lines
const importWithoutLib = /import gen\.providers\.([^.]+)\.([^.]+)\.\*;/;
const matchWithoutLib = line.match(importWithoutLib);
if (matchWithoutLib) {
const [, provider, resource] = matchWithoutLib;
return `import imports.${provider}.${resource}.*;`;
}

// Replace using lines
const importModules = /import gen\.modules\.(.+)\.\*;/;
const importModulesMatch = line.match(importModules);
if (importModulesMatch) {
const [, module] = importModulesMatch;
return `import imports.${module}.*;`;
}

return line;
})
.join("\n");
}

export function replaceCsharpImports(code: string) {
return code
.split("\n")
.map((line) => {
// Replace using lines with lib
const fromImportLibRegex =
/using Gen\.Providers\.([^.]*)(?:\.Lib)?\.(.*);/;
const match = line.match(fromImportLibRegex);
if (match) {
const [, provider, resource] = match;
return `using ${lowercaseFirstChar(provider)}.${resource};`;
}

// Replace using lines
if (line.startsWith("using Gen.Providers.")) {
const importLine = line.replace("using Gen.Providers.", "");

return `using ${importLine
.substring(0, 1)
.toLocaleLowerCase()}${importLine.substring(1)}`;
}

if (line.startsWith("using Gen.Modules.")) {
const importLine = line.replace("using Gen.Modules.", "");

return `using ${importLine}`;
}

return line;
})
.join("\n");
}

export function replaceGoImports(code: string) {
return code
.split("\n")
.map((line) => {
// Replace using lines with lib
const fromImportLibRegex =
/import \"github.com\/aws-samples\/dummy\/gen\/providers\/([^\/]*)(?:\/lib)?\/(.*)\"/;
const matchLib = line.match(fromImportLibRegex);
if (matchLib) {
const [, provider, resource] = matchLib;
return `import "cdk.tf/go/stack/generated/${provider}/${resource}"`;
}

// Replace using lines
const fromImportRegex =
/import \"github.com\/aws-samples\/dummy\/gen\/providers\/([^\/]+)\/(.*)\"/;
const match = line.match(fromImportRegex);
if (match) {
const [, provider, resource] = match;
return `import "cdk.tf/go/stack/generated/${provider}/${resource}"`;
}

const importModulesRegex =
/import (.*) \"github.com\/aws-samples\/dummy\/gen\/modules\/(.*)\"/;
const matchModules = line.match(importModulesRegex);
if (matchModules) {
const [, name, module] = matchModules;
return `import ${name} "cdk.tf/go/stack/generated/${module}"`;
}

return line;
})
.join("\n");
}

function lowercaseFirstChar(str: string) {
return str.substring(0, 1).toLocaleLowerCase() + str.substring(1);
}
Loading