From d9028c8cf91297a44cd4b1f128f769716bf0e74e Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Fri, 4 Jun 2021 10:39:21 +0200 Subject: [PATCH] feat(jsii-rosetta transliterate): transliterate a jsii assembly (#2869) The new `jsii-rosetta transliterate` command can be used to transliterate one or more jsii assemblies (the `.jsii` file) to one or more target languages. The output assembly has all code examples transliterated into the correct target language. The current feature is experimental. It does not include renaming API elements within the assembly (only code examples are touched) - this feature may be added in the future. --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0 --- .../lib/targets/dotnet/dotnetdocgenerator.ts | 8 +- packages/jsii-pacmak/lib/targets/java.ts | 8 +- packages/jsii-pacmak/lib/targets/python.ts | 8 +- packages/jsii-rosetta/bin/jsii-rosetta.ts | 56 ++ packages/jsii-rosetta/jest.config.ts | 8 +- packages/jsii-rosetta/jestsetup.js | 4 + .../lib/commands/transliterate.ts | 203 ++++ packages/jsii-rosetta/lib/index.ts | 1 + packages/jsii-rosetta/lib/languages/csharp.ts | 3 +- packages/jsii-rosetta/lib/languages/index.ts | 4 +- packages/jsii-rosetta/lib/languages/java.ts | 3 +- packages/jsii-rosetta/lib/languages/python.ts | 3 +- .../lib/languages/target-language.ts | 5 + packages/jsii-rosetta/lib/rosetta.ts | 37 +- packages/jsii-rosetta/lib/snippet.ts | 6 +- packages/jsii-rosetta/lib/tablets/tablets.ts | 2 +- packages/jsii-rosetta/lib/translate.ts | 5 +- .../test/commands/transliterate.test.ts | 917 ++++++++++++++++++ packages/jsii-rosetta/test/rosetta.test.ts | 33 +- 19 files changed, 1280 insertions(+), 34 deletions(-) create mode 100644 packages/jsii-rosetta/jestsetup.js create mode 100644 packages/jsii-rosetta/lib/commands/transliterate.ts create mode 100644 packages/jsii-rosetta/lib/languages/target-language.ts create mode 100644 packages/jsii-rosetta/test/commands/transliterate.test.ts diff --git a/packages/jsii-pacmak/lib/targets/dotnet/dotnetdocgenerator.ts b/packages/jsii-pacmak/lib/targets/dotnet/dotnetdocgenerator.ts index 193cad5a04..5e2ebb5491 100644 --- a/packages/jsii-pacmak/lib/targets/dotnet/dotnetdocgenerator.ts +++ b/packages/jsii-pacmak/lib/targets/dotnet/dotnetdocgenerator.ts @@ -2,6 +2,7 @@ import * as spec from '@jsii/spec'; import { CodeMaker } from 'codemaker'; import { Rosetta, + TargetLanguage, Translation, enforcesStrictMode, typeScriptSnippetFromSource, @@ -166,7 +167,10 @@ export class DotNetDocGenerator { 'example', enforcesStrictMode(this.assembly), ); - const translated = this.rosetta.translateSnippet(snippet, 'csharp'); + const translated = this.rosetta.translateSnippet( + snippet, + TargetLanguage.CSHARP, + ); if (!translated) { return example; } @@ -176,7 +180,7 @@ export class DotNetDocGenerator { private convertSamplesInMarkdown(markdown: string): string { return this.rosetta.translateSnippetsInMarkdown( markdown, - 'csharp', + TargetLanguage.CSHARP, enforcesStrictMode(this.assembly), (trans) => ({ language: trans.language, diff --git a/packages/jsii-pacmak/lib/targets/java.ts b/packages/jsii-pacmak/lib/targets/java.ts index b7d939165f..154ed3dd75 100644 --- a/packages/jsii-pacmak/lib/targets/java.ts +++ b/packages/jsii-pacmak/lib/targets/java.ts @@ -5,6 +5,7 @@ import * as fs from 'fs-extra'; import * as reflect from 'jsii-reflect'; import { Rosetta, + TargetLanguage, typeScriptSnippetFromSource, Translation, enforcesStrictMode, @@ -2916,7 +2917,10 @@ class JavaGenerator extends Generator { 'example', enforcesStrictMode(this.assembly), ); - const translated = this.rosetta.translateSnippet(snippet, 'java'); + const translated = this.rosetta.translateSnippet( + snippet, + TargetLanguage.JAVA, + ); if (!translated) { return example; } @@ -2926,7 +2930,7 @@ class JavaGenerator extends Generator { private convertSamplesInMarkdown(markdown: string): string { return this.rosetta.translateSnippetsInMarkdown( markdown, - 'java', + TargetLanguage.JAVA, enforcesStrictMode(this.assembly), (trans) => ({ language: trans.language, diff --git a/packages/jsii-pacmak/lib/targets/python.ts b/packages/jsii-pacmak/lib/targets/python.ts index 0a27664e29..9a0ac115eb 100644 --- a/packages/jsii-pacmak/lib/targets/python.ts +++ b/packages/jsii-pacmak/lib/targets/python.ts @@ -4,6 +4,7 @@ import * as escapeStringRegexp from 'escape-string-regexp'; import * as fs from 'fs-extra'; import * as reflect from 'jsii-reflect'; import { + TargetLanguage, Translation, Rosetta, enforcesStrictMode, @@ -2290,7 +2291,10 @@ class PythonGenerator extends Generator { 'example', enforcesStrictMode(this.assembly), ); - const translated = this.rosetta.translateSnippet(snippet, 'python'); + const translated = this.rosetta.translateSnippet( + snippet, + TargetLanguage.PYTHON, + ); if (!translated) { return example; } @@ -2300,7 +2304,7 @@ class PythonGenerator extends Generator { public convertMarkdown(markdown: string): string { return this.rosetta.translateSnippetsInMarkdown( markdown, - 'python', + TargetLanguage.PYTHON, enforcesStrictMode(this.assembly), (trans) => ({ language: trans.language, diff --git a/packages/jsii-rosetta/bin/jsii-rosetta.ts b/packages/jsii-rosetta/bin/jsii-rosetta.ts index 16c461a82c..da6eba97a5 100644 --- a/packages/jsii-rosetta/bin/jsii-rosetta.ts +++ b/packages/jsii-rosetta/bin/jsii-rosetta.ts @@ -10,6 +10,8 @@ import { import { translateMarkdown } from '../lib/commands/convert'; import { extractSnippets } from '../lib/commands/extract'; import { readTablet } from '../lib/commands/read'; +import { transliterateAssembly } from '../lib/commands/transliterate'; +import { TargetLanguage } from '../lib/languages'; import { PythonVisitor } from '../lib/languages/python'; import { VisualizeAstVisitor } from '../lib/languages/visualize'; import * as logging from '../lib/logging'; @@ -166,6 +168,60 @@ function main() { } }), ) + .command( + 'transliterate [ASSEMBLY..]', + '(EXPERIMENTAL) Transliterates the designated assemblies', + (command) => + command + .positional('ASSEMBLY', { + type: 'string', + string: true, + default: new Array(), + required: true, + describe: 'Assembly to transliterate', + }) + .option('language', { + alias: 'l', + type: 'string', + string: true, + default: new Array(), + describe: 'Language ID to transliterate to', + }) + .options('strict', { + alias: 's', + type: 'boolean', + describe: + 'Fail if an example that needs live transliteration fails to compile (which could cause incorrect transpilation results)', + }) + .option('tablet', { + alias: 't', + type: 'string', + describe: + 'Language tablet containing pre-translated code examples to use (these are generated by the `extract` command)', + }), + wrapHandler((args) => { + const assemblies = ( + args.ASSEMBLY.length > 0 ? args.ASSEMBLY : ['.'] + ).map((dir) => path.resolve(process.cwd(), dir)); + const languages = + args.language.length > 0 + ? args.language.map((lang) => { + const target = Object.entries(TargetLanguage).find( + ([k]) => k === lang, + )?.[1]; + if (target == null) { + throw new Error( + `Unknown target language: ${lang}. Expected one of ${Object.keys( + TargetLanguage, + ).join(', ')}`, + ); + } + return target; + }) + : Object.values(TargetLanguage); + return transliterateAssembly(assemblies, languages, args); + }), + ) .command( 'read [KEY] [LANGUAGE]', 'Display snippets in a language tablet file', diff --git a/packages/jsii-rosetta/jest.config.ts b/packages/jsii-rosetta/jest.config.ts index e37577d92f..e13e1670b0 100644 --- a/packages/jsii-rosetta/jest.config.ts +++ b/packages/jsii-rosetta/jest.config.ts @@ -1,3 +1,7 @@ -import config from '../../jest.config'; +import { join } from 'path'; -export default config; +import { overriddenConfig } from '../../jest.config'; + +export default overriddenConfig({ + setupFiles: [join(__dirname, 'jestsetup.js')], +}); diff --git a/packages/jsii-rosetta/jestsetup.js b/packages/jsii-rosetta/jestsetup.js new file mode 100644 index 0000000000..566fd0feb0 --- /dev/null +++ b/packages/jsii-rosetta/jestsetup.js @@ -0,0 +1,4 @@ +// Require `mock-fs` before `jest` initializes, as `mock-fs` relies on +// hijacking the `fs` module, which `jest` also hijacks (and that needs to +// happen last). +require('mock-fs'); diff --git a/packages/jsii-rosetta/lib/commands/transliterate.ts b/packages/jsii-rosetta/lib/commands/transliterate.ts new file mode 100644 index 0000000000..6b342609e1 --- /dev/null +++ b/packages/jsii-rosetta/lib/commands/transliterate.ts @@ -0,0 +1,203 @@ +import { Assembly, Docs, SPEC_FILE_NAME, Type, TypeKind } from '@jsii/spec'; +import { readJson, writeJson } from 'fs-extra'; +import { resolve } from 'path'; + +import { fixturize } from '../fixtures'; +import { TargetLanguage } from '../languages'; +import { debug } from '../logging'; +import { Rosetta } from '../rosetta'; +import { SnippetParameters, typeScriptSnippetFromSource } from '../snippet'; +import { Translation } from '../tablets/tablets'; + +export interface TransliterateAssemblyOptions { + /** + * Whather transliteration should fail upon failing to compile an example that + * required live transliteration. + * + * @default false + */ + readonly strict?: boolean; + + /** + * A pre-build translation tablet (as produced by `jsii-rosetta extract`). + * + * @default - Only the default tablet (`.jsii.tabl.json`) files will be used. + */ + readonly tablet?: string; +} + +/** + * Prepares transliterated versions of the designated assemblies into the + * selected taregt languages. + * + * @param assemblyLocations the directories which contain assemblies to + * transliterate. + * @param targetLanguages the languages into which to transliterate. + * @param tabletLocation an optional Rosetta tablet file to source + * pre-transliterated snippets from. + * + * @experimental + */ +export async function transliterateAssembly( + assemblyLocations: readonly string[], + targetLanguages: readonly TargetLanguage[], + options: TransliterateAssemblyOptions = {}, +): Promise { + const rosetta = new Rosetta({ + includeCompilerDiagnostics: true, + liveConversion: true, + targetLanguages, + }); + if (options.tablet) { + await rosetta.loadTabletFromFile(options.tablet); + } + const assemblies = await loadAssemblies(assemblyLocations, rosetta); + + for (const [location, loadAssembly] of assemblies.entries()) { + for (const language of targetLanguages) { + const now = new Date().getTime(); + // eslint-disable-next-line no-await-in-loop + const result = await loadAssembly(); + if (result.readme?.markdown) { + result.readme.markdown = rosetta.translateSnippetsInMarkdown( + result.readme.markdown, + language, + true /* strict */, + (translation) => ({ + language: translation.language, + source: prefixDisclaimer(translation), + }), + location, + ); + } + for (const type of Object.values(result.types ?? {})) { + transliterateType(type, rosetta, language, location); + } + // eslint-disable-next-line no-await-in-loop + await writeJson( + resolve(location, `${SPEC_FILE_NAME}.${language}`), + result, + { spaces: 2 }, + ); + const then = new Date().getTime(); + debug( + `Done transliterating ${result.name}@${ + result.version + } to ${language} after ${then - now} milliseconds`, + ); + } + } + + rosetta.printDiagnostics(process.stderr); + if (rosetta.hasErrors && options.strict) { + throw new Error( + 'Strict mode is enabled and some examples failed compilation!', + ); + } +} + +/** + * Given a set of directories containing `.jsii` assemblies, load all the + * assemblies into the provided `Rosetta` instance and return a map of + * directories to assembly-loading functions (the function re-loads the original + * assembly from disk on each invocation). + * + * @param directories the assembly-containing directories to traverse. + * @param rosetta the `Rosetta` instance in which to load assemblies. + * + * @returns a map of directories to a function that loads the `.jsii` assembly + * contained therein from disk. + */ +async function loadAssemblies( + directories: readonly string[], + rosetta: Rosetta, +): Promise> { + const result = new Map(); + + for (const directory of directories) { + const loader = () => readJson(resolve(directory, SPEC_FILE_NAME)); + // eslint-disable-next-line no-await-in-loop + await rosetta.addAssembly(await loader(), directory); + result.set(directory, loader); + } + + return result; +} + +type Mutable = { -readonly [K in keyof T]: Mutable }; +type AssemblyLoader = () => Promise>; + +function prefixDisclaimer(translation: Translation): string { + const message = translation.didCompile + ? 'Example automatically generated. See https://github.com/aws/jsii/issues/826' + : 'Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826'; + return `${commentToken()} ${message}\n${translation.source}`; + + function commentToken() { + // This is future-proofed a bit, but don't read too much in this... + switch (translation.language) { + case 'python': + case 'ruby': + return '#'; + case 'csharp': + case 'java': + case 'go': + default: + return '//'; + } + } +} + +function transliterateType( + type: Type, + rosetta: Rosetta, + language: TargetLanguage, + workingDirectory: string, +): void { + transliterateDocs(type.docs); + switch (type.kind) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore 7029 + case TypeKind.Class: + transliterateDocs(type?.initializer?.docs); + + // fallthrough + case TypeKind.Interface: + for (const method of type.methods ?? []) { + transliterateDocs(method.docs); + for (const parameter of method.parameters ?? []) { + transliterateDocs(parameter.docs); + } + } + for (const property of type.properties ?? []) { + transliterateDocs(property.docs); + } + break; + + case TypeKind.Enum: + for (const member of type.members) { + transliterateDocs(member.docs); + } + break; + + default: + throw new Error(`Unsupported type kind: ${(type as any).kind}`); + } + + function transliterateDocs(docs: Docs | undefined) { + if (docs?.example) { + const snippet = fixturize( + typeScriptSnippetFromSource( + docs.example, + 'example', + true /* strict */, + { [SnippetParameters.$PROJECT_DIRECTORY]: workingDirectory }, + ), + ); + const translation = rosetta.translateSnippet(snippet, language); + if (translation != null) { + docs.example = prefixDisclaimer(translation); + } + } + } +} diff --git a/packages/jsii-rosetta/lib/index.ts b/packages/jsii-rosetta/lib/index.ts index 428e2f272f..f26bb05f93 100644 --- a/packages/jsii-rosetta/lib/index.ts +++ b/packages/jsii-rosetta/lib/index.ts @@ -1,5 +1,6 @@ export * from './translate'; export { renderTree } from './o-tree'; +export { TargetLanguage } from './languages/target-language'; export { CSharpVisitor } from './languages/csharp'; export { JavaVisitor } from './languages/java'; export { PythonVisitor } from './languages/python'; diff --git a/packages/jsii-rosetta/lib/languages/csharp.ts b/packages/jsii-rosetta/lib/languages/csharp.ts index 88e1bf8baf..4b00a61ee2 100644 --- a/packages/jsii-rosetta/lib/languages/csharp.ts +++ b/packages/jsii-rosetta/lib/languages/csharp.ts @@ -23,6 +23,7 @@ import { } from '../typescript/types'; import { flat, partition, setExtend } from '../util'; import { DefaultVisitor } from './default'; +import { TargetLanguage } from './target-language'; interface CSharpLanguageContext { /** @@ -74,7 +75,7 @@ interface CSharpLanguageContext { type CSharpRenderer = AstRenderer; export class CSharpVisitor extends DefaultVisitor { - public readonly language = 'csharp'; + public readonly language = TargetLanguage.CSHARP; public readonly defaultContext = { propertyOrMethod: false, diff --git a/packages/jsii-rosetta/lib/languages/index.ts b/packages/jsii-rosetta/lib/languages/index.ts index 5e6759529f..8bfa9c34cd 100644 --- a/packages/jsii-rosetta/lib/languages/index.ts +++ b/packages/jsii-rosetta/lib/languages/index.ts @@ -2,8 +2,10 @@ import { AstHandler } from '../renderer'; import { CSharpVisitor } from './csharp'; import { JavaVisitor } from './java'; import { PythonVisitor } from './python'; +import { TargetLanguage } from './target-language'; + +export { TargetLanguage }; -export type TargetLanguage = 'python' | 'csharp' | 'java'; export type VisitorFactory = () => AstHandler; export const TARGET_LANGUAGES: { [key in TargetLanguage]: VisitorFactory } = { diff --git a/packages/jsii-rosetta/lib/languages/java.ts b/packages/jsii-rosetta/lib/languages/java.ts index ea0bef5328..2ada081a38 100644 --- a/packages/jsii-rosetta/lib/languages/java.ts +++ b/packages/jsii-rosetta/lib/languages/java.ts @@ -2,6 +2,7 @@ import * as ts from 'typescript'; import { isStructType } from '../jsii/jsii-utils'; import { jsiiTargetParam } from '../jsii/packages'; +import { TargetLanguage } from '../languages/target-language'; import { OTree, NO_SYNTAX } from '../o-tree'; import { AstRenderer } from '../renderer'; import { @@ -102,7 +103,7 @@ interface InsideTypeDeclaration { type JavaRenderer = AstRenderer; export class JavaVisitor extends DefaultVisitor { - public readonly language = 'java'; + public readonly language = TargetLanguage.JAVA; public readonly defaultContext = {}; public mergeContext( diff --git a/packages/jsii-rosetta/lib/languages/python.ts b/packages/jsii-rosetta/lib/languages/python.ts index f058af97e5..9e6749f406 100644 --- a/packages/jsii-rosetta/lib/languages/python.ts +++ b/packages/jsii-rosetta/lib/languages/python.ts @@ -7,6 +7,7 @@ import { structPropertyAcceptsUndefined, } from '../jsii/jsii-utils'; import { jsiiTargetParam } from '../jsii/packages'; +import { TargetLanguage } from '../languages/target-language'; import { NO_SYNTAX, OTree, renderTree } from '../o-tree'; import { AstRenderer, nimpl, CommentSyntax } from '../renderer'; import { @@ -86,7 +87,7 @@ export interface PythonVisitorOptions { } export class PythonVisitor extends DefaultVisitor { - public readonly language = 'python'; + public readonly language = TargetLanguage.PYTHON; public readonly defaultContext = {}; public constructor(private readonly options: PythonVisitorOptions = {}) { diff --git a/packages/jsii-rosetta/lib/languages/target-language.ts b/packages/jsii-rosetta/lib/languages/target-language.ts new file mode 100644 index 0000000000..29c30e963d --- /dev/null +++ b/packages/jsii-rosetta/lib/languages/target-language.ts @@ -0,0 +1,5 @@ +export enum TargetLanguage { + PYTHON = 'python', + CSHARP = 'csharp', + JAVA = 'java', +} diff --git a/packages/jsii-rosetta/lib/rosetta.ts b/packages/jsii-rosetta/lib/rosetta.ts index ffb9385505..838a331ec7 100644 --- a/packages/jsii-rosetta/lib/rosetta.ts +++ b/packages/jsii-rosetta/lib/rosetta.ts @@ -9,7 +9,11 @@ import { transformMarkdown } from './markdown/markdown'; import { MarkdownRenderer } from './markdown/markdown-renderer'; import { ReplaceTypeScriptTransform } from './markdown/replace-typescript-transform'; import { CodeBlock } from './markdown/types'; -import { TypeScriptSnippet } from './snippet'; +import { + SnippetParameters, + TypeScriptSnippet, + updateParameters, +} from './snippet'; import { DEFAULT_TABLET_NAME, LanguageTablet, @@ -24,14 +28,19 @@ export interface RosettaOptions { * * @default false */ - liveConversion?: boolean; + readonly liveConversion?: boolean; /** * Target languages to use for live conversion * * @default All languages */ - targetLanguages?: TargetLanguage[]; + readonly targetLanguages?: readonly TargetLanguage[]; + + /** + * Whether to include compiler diagnostics in the compilation results. + */ + readonly includeCompilerDiagnostics?: boolean; } /** @@ -49,10 +58,14 @@ export interface RosettaOptions { export class Rosetta { private readonly loadedTablets: LanguageTablet[] = []; private readonly liveTablet = new LanguageTablet(); - private readonly extractedSnippets: Record = {}; - private readonly translator = new Translator(false); + private readonly extractedSnippets = new Map(); + private readonly translator: Translator; - public constructor(private readonly options: RosettaOptions = {}) {} + public constructor(private readonly options: RosettaOptions = {}) { + this.translator = new Translator( + options.includeCompilerDiagnostics ?? false, + ); + } /** * Diagnostics encountered while doing live translation @@ -104,7 +117,7 @@ export class Rosetta { for (const tsnip of allTypeScriptSnippets([ { assembly, directory: assemblyDir }, ])) { - this.extractedSnippets[tsnip.visibleSource] = tsnip; + this.extractedSnippets.set(tsnip.visibleSource, tsnip); } } } @@ -136,7 +149,7 @@ export class Rosetta { } // See if we're going to live-convert it with full source information - const extracted = this.extractedSnippets[source.visibleSource]; + const extracted = this.extractedSnippets.get(source.visibleSource); if (extracted !== undefined) { const snippet = this.translator.translate( extracted, @@ -159,12 +172,18 @@ export class Rosetta { targetLang: TargetLanguage, strict: boolean, translationToCodeBlock: (x: Translation) => CodeBlock = id, + compileDirectory = process.cwd(), ): string { return transformMarkdown( markdown, new MarkdownRenderer(), new ReplaceTypeScriptTransform('markdown', strict, (tsSnip) => { - const translated = this.translateSnippet(tsSnip, targetLang); + const translated = this.translateSnippet( + updateParameters(tsSnip, { + [SnippetParameters.$COMPILATION_DIRECTORY]: compileDirectory, + }), + targetLang, + ); if (!translated) { return undefined; } diff --git a/packages/jsii-rosetta/lib/snippet.ts b/packages/jsii-rosetta/lib/snippet.ts index 7c447d2f4b..ad23682d72 100644 --- a/packages/jsii-rosetta/lib/snippet.ts +++ b/packages/jsii-rosetta/lib/snippet.ts @@ -57,7 +57,11 @@ export function updateParameters( ): TypeScriptSnippet { return { ...snippet, - parameters: Object.assign({}, snippet.parameters ?? {}, params), + parameters: Object.assign( + Object.create(null), + snippet.parameters ?? {}, + params, + ), }; } diff --git a/packages/jsii-rosetta/lib/tablets/tablets.ts b/packages/jsii-rosetta/lib/tablets/tablets.ts index 248c367b29..6321b4a189 100644 --- a/packages/jsii-rosetta/lib/tablets/tablets.ts +++ b/packages/jsii-rosetta/lib/tablets/tablets.ts @@ -51,7 +51,7 @@ export class LanguageTablet { if (!obj.toolVersion || !obj.snippets) { throw new Error(`File '${filename}' does not seem to be a Tablet file`); } - if (obj.toolVersion !== TOOL_VERSION) { + if (obj.toolVersion !== TOOL_VERSION && TOOL_VERSION !== '0.0.0') { throw new Error( `Tablet file '${filename}' has been created with version '${obj.toolVersion}', cannot read with current version '${TOOL_VERSION}'`, ); diff --git a/packages/jsii-rosetta/lib/translate.ts b/packages/jsii-rosetta/lib/translate.ts index 20142ba337..9d917f6823 100644 --- a/packages/jsii-rosetta/lib/translate.ts +++ b/packages/jsii-rosetta/lib/translate.ts @@ -51,7 +51,7 @@ export class Translator { public translate( snip: TypeScriptSnippet, - languages = Object.keys(TARGET_LANGUAGES) as TargetLanguage[], + languages: readonly TargetLanguage[] = Object.values(TargetLanguage), ) { logging.debug( `Translating ${snippetKey(snip)} ${inspect(snip.parameters ?? {})}`, @@ -134,7 +134,8 @@ export class SnippetTranslator { const source = completeSource(snippet); const fakeCurrentDirectory = - snippet.parameters?.[SnippetParameters.$COMPILATION_DIRECTORY]; + snippet.parameters?.[SnippetParameters.$COMPILATION_DIRECTORY] ?? + snippet.parameters?.[SnippetParameters.$PROJECT_DIRECTORY]; this.compilation = compiler.compileInMemory( snippet.where, source, diff --git a/packages/jsii-rosetta/test/commands/transliterate.test.ts b/packages/jsii-rosetta/test/commands/transliterate.test.ts new file mode 100644 index 0000000000..ecb85ef88e --- /dev/null +++ b/packages/jsii-rosetta/test/commands/transliterate.test.ts @@ -0,0 +1,917 @@ +import { SPEC_FILE_NAME } from '@jsii/spec'; +import * as fs from 'fs-extra'; +import * as jsii from 'jsii'; +import * as os from 'os'; +import * as path from 'path'; + +import { transliterateAssembly } from '../../lib/commands/transliterate'; +import { TargetLanguage } from '../../lib/languages/target-language'; + +jest.setTimeout(60_000); + +test('single assembly, all languages', () => + withTemporaryDirectory(async (tmpDir) => { + // GIVEN + const compilationResult = await jsii.compileJsiiForTest({ + 'README.md': ` +# README +\`\`\`ts +const object: IInterface = new ClassName('this', 1337, { foo: 'bar' }); +object.property = EnumType.OPTION_A; +object.methodCall(); + +ClassName.staticMethod(EnumType.OPTION_B); +\`\`\` +`, + 'index.ts': ` +/** + * @example new ClassName('this', 1337, { property: EnumType.OPTION_B }); + */ +export enum EnumType { + /** + * @example new ClassName('this', 1337, { property: EnumType.OPTION_A }); + */ + OPTION_A = 1, + + /** + * @example new ClassName('this', 1337, { property: EnumType.OPTION_B }); + */ + OPTION_B = 2, +} + +export interface IInterface { + /** + * A property value. + * + * @example + * iface.property = EnumType.OPTION_B; + */ + property: EnumType; + + /** + * An instance method call. + * + * @example + * iface.methodCall(); + */ + methodCall(): void; +} + +export interface ClassNameProps { + readonly property?: EnumType; + readonly foo?: string; +} + +export class ClassName implements IInterface { + /** + * A static method. It can be invoked easily. + * + * @example ClassName.staticMethod(); + */ + public static staticMethod(_enm?: EnumType): void { + // ... + } + + public property: EnumType; + + /** + * Create a new instance of ClassName. + * + * @example new ClassName('this', 1337, { property: EnumType.OPTION_B }); + */ + public constructor(_this: string, _elite: number, props: ClassNameProps) { + this.property = props.property ?? EnumType.OPTION_A; + } + + public methodCall(): void { + // ... + } +}`, + }); + fs.writeJsonSync( + path.join(tmpDir, SPEC_FILE_NAME), + compilationResult.assembly, + { + spaces: 2, + }, + ); + for (const [file, content] of Object.entries(compilationResult.files)) { + fs.writeFileSync(path.resolve(tmpDir, file), content, 'utf-8'); + } + fs.mkdirSync(path.resolve(tmpDir, 'rosetta')); + fs.writeFileSync( + path.resolve(tmpDir, 'rosetta', 'default.ts-fixture'), + `import { EnumType, IInterface, ClassName } from '.';\ndeclare const iface: IInterface\n/// here`, + 'utf-8', + ); + + // WHEN + await expect( + transliterateAssembly([tmpDir], Object.values(TargetLanguage), { + strict: true, + }), + ).resolves.not.toThrow(); + + // THEN + expect( + fs.readJsonSync(path.join(tmpDir, `${SPEC_FILE_NAME}.csharp`)), + ).toMatchInlineSnapshot( + { + fingerprint: expect.any(String), + jsiiVersion: expect.any(String), + }, + ` + Object { + "author": Object { + "name": "John Doe", + "roles": Array [ + "author", + ], + }, + "description": "testpkg", + "fingerprint": Any, + "homepage": "https://github.com/aws/jsii.git", + "jsiiVersion": Any, + "license": "Apache-2.0", + "metadata": Object { + "jsii": Object { + "pacmak": Object { + "hasDefaultInterfaces": true, + }, + }, + }, + "name": "testpkg", + "readme": Object { + "markdown": "# README + + \`\`\`csharp + // Example automatically generated. See https://github.com/aws/jsii/issues/826 + IInterface object = new ClassName(\\"this\\", 1337, new ClassNameProps { Foo = \\"bar\\" }); + object.Property = EnumType.OPTION_A; + object.MethodCall(); + + ClassName.StaticMethod(EnumType.OPTION_B); + \`\`\`", + }, + "repository": Object { + "type": "git", + "url": "https://github.com/aws/jsii.git", + }, + "schema": "jsii/0.10.0", + "targets": Object { + "js": Object { + "npm": "testpkg", + }, + }, + "types": Object { + "testpkg.ClassName": Object { + "assembly": "testpkg", + "fqn": "testpkg.ClassName", + "initializer": Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_B });", + "summary": "Create a new instance of ClassName.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 57, + }, + "parameters": Array [ + Object { + "name": "_this", + "type": Object { + "primitive": "string", + }, + }, + Object { + "name": "_elite", + "type": Object { + "primitive": "number", + }, + }, + Object { + "name": "props", + "type": Object { + "fqn": "testpkg.ClassNameProps", + }, + }, + ], + }, + "interfaces": Array [ + "testpkg.IInterface", + ], + "kind": "class", + "locationInModule": Object { + "filename": "index.ts", + "line": 40, + }, + "methods": Array [ + Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName.StaticMethod();", + "remarks": "It can be invoked easily.", + "summary": "A static method.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 46, + }, + "name": "staticMethod", + "parameters": Array [ + Object { + "name": "_enm", + "optional": true, + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + "static": true, + }, + Object { + "docs": Object { + "summary": "An instance method call.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 61, + }, + "name": "methodCall", + "overrides": "testpkg.IInterface", + }, + ], + "name": "ClassName", + "properties": Array [ + Object { + "docs": Object { + "summary": "A property value.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 50, + }, + "name": "property", + "overrides": "testpkg.IInterface", + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + "testpkg.ClassNameProps": Object { + "assembly": "testpkg", + "datatype": true, + "fqn": "testpkg.ClassNameProps", + "kind": "interface", + "locationInModule": Object { + "filename": "index.ts", + "line": 35, + }, + "name": "ClassNameProps", + "properties": Array [ + Object { + "abstract": true, + "immutable": true, + "locationInModule": Object { + "filename": "index.ts", + "line": 37, + }, + "name": "foo", + "optional": true, + "type": Object { + "primitive": "string", + }, + }, + Object { + "abstract": true, + "immutable": true, + "locationInModule": Object { + "filename": "index.ts", + "line": 36, + }, + "name": "property", + "optional": true, + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + "testpkg.EnumType": Object { + "assembly": "testpkg", + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_B });", + }, + "fqn": "testpkg.EnumType", + "kind": "enum", + "locationInModule": Object { + "filename": "index.ts", + "line": 5, + }, + "members": Array [ + Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_A });", + }, + "name": "OPTION_A", + }, + Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps { Property = EnumType.OPTION_B });", + }, + "name": "OPTION_B", + }, + ], + "name": "EnumType", + }, + "testpkg.IInterface": Object { + "assembly": "testpkg", + "fqn": "testpkg.IInterface", + "kind": "interface", + "locationInModule": Object { + "filename": "index.ts", + "line": 17, + }, + "methods": Array [ + Object { + "abstract": true, + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + iface.MethodCall();", + "summary": "An instance method call.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 32, + }, + "name": "methodCall", + }, + ], + "name": "IInterface", + "properties": Array [ + Object { + "abstract": true, + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + iface.Property = EnumType.OPTION_B;", + "summary": "A property value.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 24, + }, + "name": "property", + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + }, + "version": "0.0.1", + } + `, + ); + expect( + fs.readJsonSync(path.join(tmpDir, `${SPEC_FILE_NAME}.java`)), + ).toMatchInlineSnapshot( + { + fingerprint: expect.any(String), + jsiiVersion: expect.any(String), + }, + ` + Object { + "author": Object { + "name": "John Doe", + "roles": Array [ + "author", + ], + }, + "description": "testpkg", + "fingerprint": Any, + "homepage": "https://github.com/aws/jsii.git", + "jsiiVersion": Any, + "license": "Apache-2.0", + "metadata": Object { + "jsii": Object { + "pacmak": Object { + "hasDefaultInterfaces": true, + }, + }, + }, + "name": "testpkg", + "readme": Object { + "markdown": "# README + + \`\`\`java + // Example automatically generated. See https://github.com/aws/jsii/issues/826 + IInterface object = new ClassName(\\"this\\", 1337, new ClassNameProps().foo(\\"bar\\")); + object.getProperty() = EnumType.getOPTION_A(); + object.methodCall(); + + ClassName.staticMethod(EnumType.getOPTION_B()); + \`\`\`", + }, + "repository": Object { + "type": "git", + "url": "https://github.com/aws/jsii.git", + }, + "schema": "jsii/0.10.0", + "targets": Object { + "js": Object { + "npm": "testpkg", + }, + }, + "types": Object { + "testpkg.ClassName": Object { + "assembly": "testpkg", + "fqn": "testpkg.ClassName", + "initializer": Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_B()));", + "summary": "Create a new instance of ClassName.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 57, + }, + "parameters": Array [ + Object { + "name": "_this", + "type": Object { + "primitive": "string", + }, + }, + Object { + "name": "_elite", + "type": Object { + "primitive": "number", + }, + }, + Object { + "name": "props", + "type": Object { + "fqn": "testpkg.ClassNameProps", + }, + }, + ], + }, + "interfaces": Array [ + "testpkg.IInterface", + ], + "kind": "class", + "locationInModule": Object { + "filename": "index.ts", + "line": 40, + }, + "methods": Array [ + Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName.staticMethod();", + "remarks": "It can be invoked easily.", + "summary": "A static method.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 46, + }, + "name": "staticMethod", + "parameters": Array [ + Object { + "name": "_enm", + "optional": true, + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + "static": true, + }, + Object { + "docs": Object { + "summary": "An instance method call.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 61, + }, + "name": "methodCall", + "overrides": "testpkg.IInterface", + }, + ], + "name": "ClassName", + "properties": Array [ + Object { + "docs": Object { + "summary": "A property value.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 50, + }, + "name": "property", + "overrides": "testpkg.IInterface", + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + "testpkg.ClassNameProps": Object { + "assembly": "testpkg", + "datatype": true, + "fqn": "testpkg.ClassNameProps", + "kind": "interface", + "locationInModule": Object { + "filename": "index.ts", + "line": 35, + }, + "name": "ClassNameProps", + "properties": Array [ + Object { + "abstract": true, + "immutable": true, + "locationInModule": Object { + "filename": "index.ts", + "line": 37, + }, + "name": "foo", + "optional": true, + "type": Object { + "primitive": "string", + }, + }, + Object { + "abstract": true, + "immutable": true, + "locationInModule": Object { + "filename": "index.ts", + "line": 36, + }, + "name": "property", + "optional": true, + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + "testpkg.EnumType": Object { + "assembly": "testpkg", + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_B()));", + }, + "fqn": "testpkg.EnumType", + "kind": "enum", + "locationInModule": Object { + "filename": "index.ts", + "line": 5, + }, + "members": Array [ + Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_A()));", + }, + "name": "OPTION_A", + }, + Object { + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + new ClassName(\\"this\\", 1337, new ClassNameProps().property(EnumType.getOPTION_B()));", + }, + "name": "OPTION_B", + }, + ], + "name": "EnumType", + }, + "testpkg.IInterface": Object { + "assembly": "testpkg", + "fqn": "testpkg.IInterface", + "kind": "interface", + "locationInModule": Object { + "filename": "index.ts", + "line": 17, + }, + "methods": Array [ + Object { + "abstract": true, + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + iface.methodCall();", + "summary": "An instance method call.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 32, + }, + "name": "methodCall", + }, + ], + "name": "IInterface", + "properties": Array [ + Object { + "abstract": true, + "docs": Object { + "example": "// Example automatically generated. See https://github.com/aws/jsii/issues/826 + iface.getProperty() = EnumType.getOPTION_B();", + "summary": "A property value.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 24, + }, + "name": "property", + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + }, + "version": "0.0.1", + } + `, + ); + expect( + fs.readJsonSync(path.join(tmpDir, `${SPEC_FILE_NAME}.python`)), + ).toMatchInlineSnapshot( + { + fingerprint: expect.any(String), + jsiiVersion: expect.any(String), + }, + ` + Object { + "author": Object { + "name": "John Doe", + "roles": Array [ + "author", + ], + }, + "description": "testpkg", + "fingerprint": Any, + "homepage": "https://github.com/aws/jsii.git", + "jsiiVersion": Any, + "license": "Apache-2.0", + "metadata": Object { + "jsii": Object { + "pacmak": Object { + "hasDefaultInterfaces": true, + }, + }, + }, + "name": "testpkg", + "readme": Object { + "markdown": "# README + + \`\`\`python + # Example automatically generated. See https://github.com/aws/jsii/issues/826 + object = ClassName(\\"this\\", 1337, foo=\\"bar\\") + object.property = EnumType.OPTION_A + object.method_call() + + ClassName.static_method(EnumType.OPTION_B) + \`\`\`", + }, + "repository": Object { + "type": "git", + "url": "https://github.com/aws/jsii.git", + }, + "schema": "jsii/0.10.0", + "targets": Object { + "js": Object { + "npm": "testpkg", + }, + }, + "types": Object { + "testpkg.ClassName": Object { + "assembly": "testpkg", + "fqn": "testpkg.ClassName", + "initializer": Object { + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName(\\"this\\", 1337, property=EnumType.OPTION_B)", + "summary": "Create a new instance of ClassName.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 57, + }, + "parameters": Array [ + Object { + "name": "_this", + "type": Object { + "primitive": "string", + }, + }, + Object { + "name": "_elite", + "type": Object { + "primitive": "number", + }, + }, + Object { + "name": "props", + "type": Object { + "fqn": "testpkg.ClassNameProps", + }, + }, + ], + }, + "interfaces": Array [ + "testpkg.IInterface", + ], + "kind": "class", + "locationInModule": Object { + "filename": "index.ts", + "line": 40, + }, + "methods": Array [ + Object { + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName.static_method()", + "remarks": "It can be invoked easily.", + "summary": "A static method.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 46, + }, + "name": "staticMethod", + "parameters": Array [ + Object { + "name": "_enm", + "optional": true, + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + "static": true, + }, + Object { + "docs": Object { + "summary": "An instance method call.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 61, + }, + "name": "methodCall", + "overrides": "testpkg.IInterface", + }, + ], + "name": "ClassName", + "properties": Array [ + Object { + "docs": Object { + "summary": "A property value.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 50, + }, + "name": "property", + "overrides": "testpkg.IInterface", + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + "testpkg.ClassNameProps": Object { + "assembly": "testpkg", + "datatype": true, + "fqn": "testpkg.ClassNameProps", + "kind": "interface", + "locationInModule": Object { + "filename": "index.ts", + "line": 35, + }, + "name": "ClassNameProps", + "properties": Array [ + Object { + "abstract": true, + "immutable": true, + "locationInModule": Object { + "filename": "index.ts", + "line": 37, + }, + "name": "foo", + "optional": true, + "type": Object { + "primitive": "string", + }, + }, + Object { + "abstract": true, + "immutable": true, + "locationInModule": Object { + "filename": "index.ts", + "line": 36, + }, + "name": "property", + "optional": true, + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + "testpkg.EnumType": Object { + "assembly": "testpkg", + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName(\\"this\\", 1337, property=EnumType.OPTION_B)", + }, + "fqn": "testpkg.EnumType", + "kind": "enum", + "locationInModule": Object { + "filename": "index.ts", + "line": 5, + }, + "members": Array [ + Object { + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName(\\"this\\", 1337, property=EnumType.OPTION_A)", + }, + "name": "OPTION_A", + }, + Object { + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + ClassName(\\"this\\", 1337, property=EnumType.OPTION_B)", + }, + "name": "OPTION_B", + }, + ], + "name": "EnumType", + }, + "testpkg.IInterface": Object { + "assembly": "testpkg", + "fqn": "testpkg.IInterface", + "kind": "interface", + "locationInModule": Object { + "filename": "index.ts", + "line": 17, + }, + "methods": Array [ + Object { + "abstract": true, + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + iface.method_call()", + "summary": "An instance method call.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 32, + }, + "name": "methodCall", + }, + ], + "name": "IInterface", + "properties": Array [ + Object { + "abstract": true, + "docs": Object { + "example": "# Example automatically generated. See https://github.com/aws/jsii/issues/826 + iface.property = EnumType.OPTION_B", + "summary": "A property value.", + }, + "locationInModule": Object { + "filename": "index.ts", + "line": 24, + }, + "name": "property", + "type": Object { + "fqn": "testpkg.EnumType", + }, + }, + ], + }, + }, + "version": "0.0.1", + } + `, + ); + })); + +async function withTemporaryDirectory( + callback: (dir: string) => Promise, +): Promise { + const tmpdir = fs.mkdtempSync( + path.join(os.tmpdir(), path.basename(__filename)), + ); + return callback(tmpdir).finally(() => fs.removeSync(tmpdir)); +} diff --git a/packages/jsii-rosetta/test/rosetta.test.ts b/packages/jsii-rosetta/test/rosetta.test.ts index c9bcb7e920..dadcc54130 100644 --- a/packages/jsii-rosetta/test/rosetta.test.ts +++ b/packages/jsii-rosetta/test/rosetta.test.ts @@ -19,11 +19,14 @@ test('Rosetta object can do live translation', () => { // GIVEN const rosetta = new Rosetta({ liveConversion: true, - targetLanguages: ['python'], + targetLanguages: [TargetLanguage.PYTHON], }); // WHEN - const translated = rosetta.translateSnippet(SAMPLE_CODE, 'python'); + const translated = rosetta.translateSnippet( + SAMPLE_CODE, + TargetLanguage.PYTHON, + ); // THEN expect(translated).toMatchObject({ @@ -47,7 +50,10 @@ test('Can use preloaded tablet', () => { rosetta.addTablet(tablet); // WHEN - const translated = rosetta.translateSnippet(SAMPLE_CODE, 'python'); + const translated = rosetta.translateSnippet( + SAMPLE_CODE, + TargetLanguage.PYTHON, + ); // THEN expect(translated).toMatchObject({ @@ -60,11 +66,14 @@ test('Rosetta object can do live translation', () => { // GIVEN const rosetta = new Rosetta({ liveConversion: true, - targetLanguages: ['python'], + targetLanguages: [TargetLanguage.PYTHON], }); // WHEN - const translated = rosetta.translateSnippet(SAMPLE_CODE, 'python'); + const translated = rosetta.translateSnippet( + SAMPLE_CODE, + TargetLanguage.PYTHON, + ); // THEN expect(translated).toMatchObject({ @@ -77,7 +86,7 @@ test('Rosetta object can do translation and annotation of snippets in MarkDown', // GIVEN const rosetta = new Rosetta({ liveConversion: true, - targetLanguages: ['python'], + targetLanguages: [TargetLanguage.PYTHON], }); // WHEN @@ -91,7 +100,7 @@ test('Rosetta object can do translation and annotation of snippets in MarkDown', '```', 'That was it, thank you for your attention.', ].join('\n'), - 'python', + TargetLanguage.PYTHON, false, (trans) => { return { @@ -142,7 +151,10 @@ describe('with mocked filesystem', () => { // WHEN const rosetta = new Rosetta(); await rosetta.loadTabletFromFile('/test.tablet'); - const translated = rosetta.translateSnippet(SAMPLE_CODE, 'python'); + const translated = rosetta.translateSnippet( + SAMPLE_CODE, + TargetLanguage.PYTHON, + ); // THEN expect(translated).toMatchObject({ @@ -158,7 +170,10 @@ describe('with mocked filesystem', () => { // WHEN const rosetta = new Rosetta(); await rosetta.addAssembly(fakeAssembly({}), '/'); - const translated = rosetta.translateSnippet(SAMPLE_CODE, 'python'); + const translated = rosetta.translateSnippet( + SAMPLE_CODE, + TargetLanguage.PYTHON, + ); // THEN expect(translated).toMatchObject({