jsii-rosetta 1.74.0 → 1.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/jest.config.mjs +1 -0
  2. package/lib/languages/csharp.js +13 -13
  3. package/lib/languages/default.d.ts +2 -1
  4. package/lib/languages/default.js +3 -2
  5. package/lib/languages/go.d.ts +3 -2
  6. package/lib/languages/go.js +61 -18
  7. package/lib/languages/java.d.ts +2 -1
  8. package/lib/languages/java.js +18 -4
  9. package/lib/languages/python.d.ts +3 -2
  10. package/lib/languages/python.js +8 -4
  11. package/lib/languages/record-references.d.ts +3 -2
  12. package/lib/languages/record-references.js +3 -3
  13. package/lib/languages/target-language.d.ts +26 -1
  14. package/lib/languages/target-language.js +39 -1
  15. package/lib/languages/visualize.d.ts +2 -0
  16. package/lib/languages/visualize.js +2 -0
  17. package/lib/renderer.d.ts +5 -2
  18. package/lib/renderer.js +109 -145
  19. package/lib/submodule-reference.d.ts +13 -0
  20. package/lib/submodule-reference.js +144 -0
  21. package/lib/translate.d.ts +1 -0
  22. package/lib/translate.js +14 -4
  23. package/lib/typescript/imports.d.ts +12 -1
  24. package/lib/typescript/imports.js +96 -26
  25. package/package.json +6 -5
  26. package/test/commands/transliterate.test.js +1 -1
  27. package/test/jsii-imports.test.js +3 -3
  28. package/test/translations/calls/shorthand_property.cs +2 -2
  29. package/test/translations/calls/will_type_deep_structs_directly_if_type_info_is_available.go +4 -4
  30. package/test/translations/expressions/await.cs +1 -1
  31. package/test/translations/expressions/backtick_string_w_o_substitutions.cs +1 -1
  32. package/test/translations/expressions/computed_key.cs +1 -1
  33. package/test/translations/expressions/string_interpolation.cs +2 -2
  34. package/test/translations/expressions/string_literal.cs +2 -2
  35. package/test/translations/expressions/string_literal.go +9 -1
  36. package/test/translations/expressions/string_literal.java +1 -1
  37. package/test/translations/expressions/string_literal.py +1 -1
  38. package/test/translations/expressions/struct_assignment.cs +1 -1
  39. package/test/translations/imports/selective_import.java +3 -3
  40. package/test/translations/imports/submodule-import.cs +15 -0
  41. package/test/translations/imports/submodule-import.go +23 -0
  42. package/test/translations/imports/submodule-import.java +17 -0
  43. package/test/translations/imports/submodule-import.py +15 -0
  44. package/test/translations/statements/statements_and_newlines.cs +4 -4
  45. package/test/translations/structs/infer_struct_from_union.go +3 -3
  46. package/test/translations/structs/optional_known_struct.go +1 -1
  47. package/test/translations/structs/struct_starting_with_i.go +1 -1
  48. package/test/translations/structs/var_new_class_known_struct.cs +1 -1
  49. package/test/translations/structs/var_new_class_known_struct.go +1 -1
package/jest.config.mjs CHANGED
@@ -4,4 +4,5 @@ import { default as defaultConfig, overriddenConfig } from '../../jest.config.mj
4
4
  export default overriddenConfig({
5
5
  setupFiles: [createRequire(import.meta.url).resolve('./jestsetup.js')],
6
6
  testTimeout: process.env.CI === 'true' ? 30_000 : defaultConfig.testTimeout,
7
+ watchPathIgnorePatterns: ['(\\.d)?\\.tsx?$'],
7
8
  });
@@ -63,7 +63,7 @@ class CSharpVisitor extends default_1.DefaultVisitor {
63
63
  const guessedNamespace = guessDotnetNamespace(importStatement.packageName);
64
64
  const namespace = (0, util_1.fmap)(importStatement.moduleSymbol, findDotnetName) ?? guessedNamespace;
65
65
  if (importStatement.imports.import === 'full') {
66
- this.dropPropertyAccesses.add(importStatement.imports.alias);
66
+ this.dropPropertyAccesses.add(importStatement.imports.sourceName);
67
67
  this.alreadyImportedNamespaces.add(namespace);
68
68
  return new o_tree_1.OTree([`using ${namespace};`], [], { canBreakLine: true });
69
69
  }
@@ -366,27 +366,27 @@ class CSharpVisitor extends default_1.DefaultVisitor {
366
366
  return new o_tree_1.OTree(['(', this.renderTypeNode(node.type, false, context), ')', context.convert(node.expression)]);
367
367
  }
368
368
  variableDeclaration(node, renderer) {
369
- let fallback = 'var';
370
- if (node.type) {
371
- fallback = node.type.getText();
372
- }
373
- const type = (node.type && renderer.typeOfType(node.type)) ||
369
+ let typeOrVar = 'var';
370
+ const fallback = node.type?.getText() ?? 'var';
371
+ const type = (node.type && renderer.typeOfType(node.type)) ??
374
372
  (node.initializer && renderer.typeOfExpression(node.initializer));
375
- let renderedType = this.renderType(node, type, false, fallback, renderer);
376
- if (renderedType === 'object') {
377
- renderedType = 'var';
373
+ const varType = this.renderType(node, type, false, fallback, renderer);
374
+ // If there is an initializer, and the value isn't "IDictionary<...", we always use var, as this is the
375
+ // recommendation from Roslyn.
376
+ if (varType !== 'object' && (varType.startsWith('IDictionary<') || node.initializer == null)) {
377
+ typeOrVar = varType;
378
378
  }
379
379
  if (!node.initializer) {
380
- return new o_tree_1.OTree([renderedType, ' ', renderer.convert(node.name), ';'], []);
380
+ return new o_tree_1.OTree([typeOrVar, ' ', renderer.convert(node.name), ';']);
381
381
  }
382
382
  return new o_tree_1.OTree([
383
- renderedType,
383
+ typeOrVar,
384
384
  ' ',
385
385
  renderer.convert(node.name),
386
386
  ' = ',
387
387
  renderer.updateContext({ preferObjectLiteralAsStruct: false }).convert(node.initializer),
388
388
  ';',
389
- ], [], { canBreakLine: true });
389
+ ], undefined, { canBreakLine: true });
390
390
  }
391
391
  templateExpression(node, context) {
392
392
  // If this is a multi-line string literal, we need not quote much, as @"string" literals in C#
@@ -491,7 +491,7 @@ function findDotnetName(jsiiSymbol) {
491
491
  return modName;
492
492
  }
493
493
  }
494
- return `${recurse((0, jsii_utils_1.namespaceName)(fqn))}.${(0, jsii_utils_1.simpleName)(jsiiSymbol.fqn)}`;
494
+ return `${recurse((0, jsii_utils_1.namespaceName)(fqn))}.${ucFirst((0, jsii_utils_1.simpleName)(jsiiSymbol.fqn))}`;
495
495
  }
496
496
  }
497
497
  function guessDotnetNamespace(ref) {
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
2
2
  import { ObjectLiteralStruct } from '../jsii/jsii-types';
3
3
  import { OTree } from '../o-tree';
4
4
  import { AstRenderer, AstHandler, CommentSyntax } from '../renderer';
5
+ import { SubmoduleReference } from '../submodule-reference';
5
6
  import { ImportStatement } from '../typescript/imports';
6
7
  import { TargetLanguage } from '.';
7
8
  /**
@@ -28,7 +29,7 @@ export declare abstract class DefaultVisitor<C> implements AstHandler<C> {
28
29
  translateUnaryOperator(operator: ts.PrefixUnaryOperator): string;
29
30
  translateBinaryOperator(operator: string): string;
30
31
  ifStatement(node: ts.IfStatement, context: AstRenderer<C>): OTree;
31
- propertyAccessExpression(node: ts.PropertyAccessExpression, context: AstRenderer<C>): OTree;
32
+ propertyAccessExpression(node: ts.PropertyAccessExpression, context: AstRenderer<C>, _submoduleReference: SubmoduleReference | undefined): OTree;
32
33
  /**
33
34
  * Do some work on property accesses to translate common JavaScript-isms to language-specific idioms
34
35
  */
@@ -7,6 +7,7 @@ const jsii_utils_1 = require("../jsii/jsii-utils");
7
7
  const o_tree_1 = require("../o-tree");
8
8
  const renderer_1 = require("../renderer");
9
9
  const ast_utils_1 = require("../typescript/ast-utils");
10
+ const types_1 = require("../typescript/types");
10
11
  /**
11
12
  * A basic visitor that applies for most curly-braces-based languages
12
13
  */
@@ -76,7 +77,7 @@ class DefaultVisitor {
76
77
  ifStatement(node, context) {
77
78
  return this.notImplemented(node, context);
78
79
  }
79
- propertyAccessExpression(node, context) {
80
+ propertyAccessExpression(node, context, _submoduleReference) {
80
81
  return new o_tree_1.OTree([context.convert(node.expression), '.', context.convert(node.name)]);
81
82
  }
82
83
  /**
@@ -132,7 +133,7 @@ class DefaultVisitor {
132
133
  : ts.isShorthandPropertyAssignment(p)
133
134
  ? isExpressionOfFunctionType(context.typeChecker, p.name)
134
135
  : false);
135
- const inferredType = context.inferredTypeOfExpression(node);
136
+ const inferredType = (0, types_1.inferredTypeOfExpression)(context.typeChecker, node);
136
137
  if ((inferredType && (0, jsii_utils_1.isJsiiProtocolType)(context.typeChecker, inferredType)) || anyMembersFunctions) {
137
138
  context.report(node, `You cannot use an object literal to make an instance of an interface. Define a class instead.`);
138
139
  }
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
2
2
  import { ObjectLiteralStruct } from '../jsii/jsii-types';
3
3
  import { OTree } from '../o-tree';
4
4
  import { AstRenderer } from '../renderer';
5
+ import { SubmoduleReference } from '../submodule-reference';
5
6
  import { ImportStatement } from '../typescript/imports';
6
7
  import { DefaultVisitor } from './default';
7
8
  import { TargetLanguage } from './target-language';
@@ -70,14 +71,14 @@ export declare class GoVisitor extends DefaultVisitor<GoLanguageContext> {
70
71
  asExpression(node: ts.AsExpression, renderer: AstRenderer<GoLanguageContext>): OTree;
71
72
  parameterDeclaration(node: ts.ParameterDeclaration, renderer: GoRenderer): OTree;
72
73
  printStatement(args: ts.NodeArray<ts.Expression>, renderer: GoRenderer): OTree;
73
- propertyAccessExpression(node: ts.PropertyAccessExpression, renderer: GoRenderer): OTree;
74
+ propertyAccessExpression(node: ts.PropertyAccessExpression, renderer: GoRenderer, submoduleReference?: SubmoduleReference): OTree;
74
75
  methodSignature(node: ts.MethodSignature, renderer: AstRenderer<GoLanguageContext>): OTree;
75
76
  propertyDeclaration(node: ts.PropertyDeclaration, renderer: AstRenderer<GoLanguageContext>): OTree;
76
77
  propertySignature(node: ts.PropertySignature, renderer: GoRenderer): OTree;
77
78
  regularCallExpression(node: ts.CallExpression, renderer: GoRenderer): OTree;
78
79
  returnStatement(node: ts.ReturnStatement, renderer: AstRenderer<GoLanguageContext>): OTree;
79
80
  binaryExpression(node: ts.BinaryExpression, renderer: AstRenderer<GoLanguageContext>): OTree;
80
- stringLiteral(node: ts.StringLiteral, renderer: GoRenderer): OTree;
81
+ stringLiteral(node: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral, renderer: GoRenderer): OTree;
81
82
  numericLiteral(node: ts.NumericLiteral, renderer: GoRenderer): OTree;
82
83
  classDeclaration(node: ts.ClassDeclaration, renderer: AstRenderer<GoLanguageContext>): OTree;
83
84
  structInterfaceDeclaration(node: ts.InterfaceDeclaration, renderer: GoRenderer): OTree;
@@ -6,6 +6,7 @@ exports.GoVisitor = void 0;
6
6
  const assert_1 = require("assert");
7
7
  const ts = require("typescript");
8
8
  const jsii_types_1 = require("../jsii/jsii-types");
9
+ const jsii_utils_1 = require("../jsii/jsii-utils");
9
10
  const o_tree_1 = require("../o-tree");
10
11
  const ast_utils_1 = require("../typescript/ast-utils");
11
12
  const imports_1 = require("../typescript/imports");
@@ -133,6 +134,14 @@ class GoVisitor extends default_1.DefaultVisitor {
133
134
  classNamespace: renderer.updateContext({ isExported: false }).convert(expr.expression),
134
135
  };
135
136
  }
137
+ else if (ts.isPropertyAccessExpression(expr.expression) &&
138
+ renderer.submoduleReferences.has(expr.expression)) {
139
+ const submodule = renderer.submoduleReferences.get(expr.expression);
140
+ return {
141
+ className: ucFirst(expr.name.text),
142
+ classNamespace: renderer.updateContext({ isExported: false }).convert(submodule.lastNode),
143
+ };
144
+ }
136
145
  renderer.reportUnsupported(expr.expression, target_language_1.TargetLanguage.GO);
137
146
  return {
138
147
  className: ucFirst(expr.name.text),
@@ -174,14 +183,16 @@ class GoVisitor extends default_1.DefaultVisitor {
174
183
  ? JSON.stringify(node.name.text)
175
184
  : this.goName(node.name.text, renderer, renderer.typeChecker.getSymbolAtLocation(node.name))
176
185
  : renderer.convert(node.name);
177
- // Struct member values are always pointers...
178
186
  return new o_tree_1.OTree([
179
187
  key,
180
188
  ': ',
181
189
  renderer
182
190
  .updateContext({
183
- wrapPtr: renderer.currentContext.isStruct || renderer.currentContext.inMapLiteral,
191
+ // Reset isExported, as this was intended for the key name translation...
192
+ isExported: undefined,
193
+ // Struct member values are always pointers...
184
194
  isPtr: renderer.currentContext.isStruct,
195
+ wrapPtr: renderer.currentContext.isStruct || renderer.currentContext.inMapLiteral,
185
196
  })
186
197
  .convert(node.initializer),
187
198
  ], [], {
@@ -252,11 +263,12 @@ class GoVisitor extends default_1.DefaultVisitor {
252
263
  });
253
264
  }
254
265
  knownStructObjectLiteralExpression(node, structType, renderer) {
266
+ const isExported = structType.kind === 'struct';
255
267
  return new o_tree_1.OTree([
256
268
  '&',
257
- this.goName(structType.type.symbol.name, renderer.updateContext({ isPtr: false }), structType.type.symbol),
269
+ this.goName(structType.type.symbol.name, renderer.updateContext({ isExported, isPtr: false }), structType.type.symbol),
258
270
  '{',
259
- ], renderer.updateContext({ isStruct: true }).convertAll(node.properties), {
271
+ ], renderer.updateContext({ isExported, isStruct: true }).convertAll(node.properties), {
260
272
  suffix: '}',
261
273
  separator: ',',
262
274
  trailingSeparator: true,
@@ -295,29 +307,48 @@ class GoVisitor extends default_1.DefaultVisitor {
295
307
  const renderedArgs = this.argumentList(args, renderer);
296
308
  return new o_tree_1.OTree(['fmt.Println(', renderedArgs, ')']);
297
309
  }
298
- propertyAccessExpression(node, renderer) {
310
+ propertyAccessExpression(node, renderer, submoduleReference) {
311
+ if (submoduleReference != null) {
312
+ return new o_tree_1.OTree([
313
+ renderer
314
+ .updateContext({ isExported: false, isPtr: false, wrapPtr: false })
315
+ .convert(submoduleReference.lastNode),
316
+ ]);
317
+ }
299
318
  const expressionType = (0, types_1.typeOfExpression)(renderer.typeChecker, node.expression);
300
319
  const valueSymbol = renderer.typeChecker.getSymbolAtLocation(node.name);
301
- const isClassStaticMember = expressionType?.symbol?.valueDeclaration != null &&
320
+ const isStaticMember = valueSymbol?.valueDeclaration != null && (0, ast_utils_1.isStatic)(valueSymbol.valueDeclaration);
321
+ const isClassStaticPropertyAccess = isStaticMember &&
322
+ expressionType?.symbol?.valueDeclaration != null &&
302
323
  ts.isClassDeclaration(expressionType.symbol.valueDeclaration) &&
303
- valueSymbol?.valueDeclaration != null &&
304
- ts.isPropertyDeclaration(valueSymbol.valueDeclaration) &&
305
- (0, ast_utils_1.isStatic)(valueSymbol.valueDeclaration);
324
+ (ts.isPropertyDeclaration(valueSymbol.valueDeclaration) || ts.isAccessor(valueSymbol.valueDeclaration));
325
+ const isClassStaticMethodAccess = isStaticMember && !isClassStaticPropertyAccess && ts.isMethodDeclaration(valueSymbol.valueDeclaration);
306
326
  // When the expression has an unknown type (unresolved symbol), and has an upper-case first
307
327
  // letter, we assume it's a type name... In such cases, what comes after can be considered a
308
328
  // static member access. Note that the expression might be further qualified, so we check using
309
329
  // a regex that checks for the last "."-delimited segment if there's dots in there...
310
330
  const expressionLooksLikeTypeReference = expressionType.symbol == null &&
311
331
  /(?:\.|^)[A-Z][^.]*$/.exec(node.expression.getText(node.expression.getSourceFile())) != null;
312
- const isEnum = expressionType?.symbol?.valueDeclaration != null && ts.isEnumDeclaration(expressionType.symbol.valueDeclaration);
313
- const delimiter = isEnum || isClassStaticMember || expressionLooksLikeTypeReference ? '_' : '.';
332
+ // Whether the node is an enum member reference.
333
+ const isEnumMember = expressionType?.symbol?.valueDeclaration != null && ts.isEnumDeclaration(expressionType.symbol.valueDeclaration);
334
+ const jsiiSymbol = (0, jsii_utils_1.lookupJsiiSymbolFromNode)(renderer.typeChecker, node.name);
335
+ const isExportedTypeName = jsiiSymbol != null && jsiiSymbol.symbolType !== 'module';
336
+ const delimiter = isEnumMember || isClassStaticPropertyAccess || isClassStaticMethodAccess || expressionLooksLikeTypeReference
337
+ ? '_'
338
+ : '.';
314
339
  return new o_tree_1.OTree([
315
340
  renderer.convert(node.expression),
316
341
  delimiter,
317
342
  renderer
318
- .updateContext({ isExported: isClassStaticMember || expressionLooksLikeTypeReference || isEnum })
343
+ .updateContext({
344
+ isExported: isClassStaticPropertyAccess ||
345
+ isClassStaticMethodAccess ||
346
+ expressionLooksLikeTypeReference ||
347
+ isEnumMember ||
348
+ isExportedTypeName,
349
+ })
319
350
  .convert(node.name),
320
- ...(isClassStaticMember
351
+ ...(isClassStaticPropertyAccess
321
352
  ? ['()']
322
353
  : // If the parent's not a call-like expression, and it's an inferred static property access, we need to put call
323
354
  // parentheses at the end, as static properties are accessed via synthetic readers.
@@ -404,7 +435,11 @@ class GoVisitor extends default_1.DefaultVisitor {
404
435
  return wrapPtrExpression(renderer.typeChecker, node, output);
405
436
  }
406
437
  stringLiteral(node, renderer) {
407
- const text = JSON.stringify(node.text);
438
+ // Go supports backtick-delimited multi-line string literals, similar/same as JavaScript no-substitution templates.
439
+ // We only use this trick if the literal includes actual new line characters (otherwise it just looks weird in go).
440
+ const text = ts.isNoSubstitutionTemplateLiteral(node) && /[\n\r]/m.test(node.text)
441
+ ? node.getText(node.getSourceFile())
442
+ : JSON.stringify(node.text);
408
443
  return new o_tree_1.OTree([`${renderer.currentContext.wrapPtr ? jsiiStr(text) : text}`]);
409
444
  }
410
445
  numericLiteral(node, renderer) {
@@ -556,18 +591,26 @@ class GoVisitor extends default_1.DefaultVisitor {
556
591
  }
557
592
  importStatement(node, renderer) {
558
593
  const packageName = node.moduleSymbol?.sourceAssembly?.packageJson.jsii?.targets?.go?.packageName ??
559
- this.goName(node.packageName, renderer, undefined);
594
+ node.packageName
595
+ // Special case namespaced npm package names, so they are mangled the same way pacmak does.
596
+ .replace(/@([a-z0-9_-]+)\/([a-z0-9_-])/, '$1$2')
597
+ .split('/')
598
+ .map((txt) => this.goName(txt, renderer, undefined))
599
+ .filter((txt) => txt !== '')
600
+ .join('/');
560
601
  const moduleName = node.moduleSymbol?.sourceAssembly?.packageJson.jsii?.targets?.go?.moduleName
561
602
  ? `${node.moduleSymbol.sourceAssembly.packageJson.jsii.targets.go.moduleName}/${packageName}`
562
603
  : `github.com/aws-samples/dummy/${packageName}`;
563
604
  if (node.imports.import === 'full') {
564
- return new o_tree_1.OTree(['import ', this.goName(node.imports.alias, renderer, undefined), ' "', moduleName, '"'], undefined, { canBreakLine: true });
605
+ // We don't emit the alias if it matches the last path segment (conventionally this is the package name)
606
+ const maybeAlias = node.imports.alias ? `${this.goName(node.imports.alias, renderer, undefined)} ` : '';
607
+ return new o_tree_1.OTree([`import ${maybeAlias}${JSON.stringify(moduleName)}`], undefined, { canBreakLine: true });
565
608
  }
566
609
  if (node.imports.elements.length === 0) {
567
610
  // This is a blank import (for side-effects only)
568
- return new o_tree_1.OTree(['import _ "', moduleName, '"'], undefined, { canBreakLine: true });
611
+ return new o_tree_1.OTree([`import _ ${JSON.stringify(moduleName)}`], undefined, { canBreakLine: true });
569
612
  }
570
- return new o_tree_1.OTree(['import "', moduleName, '"'], undefined, { canBreakLine: true });
613
+ return new o_tree_1.OTree([`import ${JSON.stringify(moduleName)}`], undefined, { canBreakLine: true });
571
614
  }
572
615
  variableDeclaration(node, renderer) {
573
616
  if (!node.initializer) {
@@ -3,6 +3,7 @@ import { ObjectLiteralStruct } from '../jsii/jsii-types';
3
3
  import { TargetLanguage } from '../languages/target-language';
4
4
  import { OTree } from '../o-tree';
5
5
  import { AstRenderer } from '../renderer';
6
+ import { SubmoduleReference } from '../submodule-reference';
6
7
  import { ImportStatement } from '../typescript/imports';
7
8
  import { DefaultVisitor } from './default';
8
9
  interface JavaContext {
@@ -122,7 +123,7 @@ export declare class JavaVisitor extends DefaultVisitor<JavaContext> {
122
123
  knownStructObjectLiteralExpression(node: ts.ObjectLiteralExpression, structType: ObjectLiteralStruct, renderer: JavaRenderer): OTree;
123
124
  propertyAssignment(node: ts.PropertyAssignment, renderer: JavaRenderer): OTree;
124
125
  shorthandPropertyAssignment(node: ts.ShorthandPropertyAssignment, renderer: JavaRenderer): OTree;
125
- propertyAccessExpression(node: ts.PropertyAccessExpression, renderer: JavaRenderer): OTree;
126
+ propertyAccessExpression(node: ts.PropertyAccessExpression, renderer: JavaRenderer, submoduleRef: SubmoduleReference | undefined): OTree;
126
127
  stringLiteral(node: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral, renderer: JavaRenderer): OTree;
127
128
  identifier(node: ts.Identifier | ts.StringLiteral | ts.NoSubstitutionTemplateLiteral, renderer: JavaRenderer): OTree;
128
129
  private renderObjectLiteralAsBuilder;
@@ -29,13 +29,20 @@ class JavaVisitor extends default_1.DefaultVisitor {
29
29
  importStatement(importStatement) {
30
30
  const guessedNamespace = guessJavaNamespaceName(importStatement.packageName);
31
31
  if (importStatement.imports.import === 'full') {
32
- this.dropPropertyAccesses.add(importStatement.imports.alias);
32
+ this.dropPropertyAccesses.add(importStatement.imports.sourceName);
33
33
  const namespace = (0, util_1.fmap)(importStatement.moduleSymbol, findJavaName) ?? guessedNamespace;
34
34
  return new o_tree_1.OTree([`import ${namespace}.*;`], [], { canBreakLine: true });
35
35
  }
36
36
  const imports = importStatement.imports.elements.map((e) => {
37
37
  const fqn = (0, util_1.fmap)(e.importedSymbol, findJavaName) ?? `${guessedNamespace}.${e.sourceName}`;
38
- return e.importedSymbol?.symbolType === 'module' ? `import ${fqn}.*;` : `import ${fqn};`;
38
+ // If there is no imported symbol, we check if there is anything looking like a type name in
39
+ // the source name (that is, any segment that starts with an upper case letter), and if none
40
+ // is found, assume this refers to a namespace/module.
41
+ return (e.importedSymbol?.symbolType == null &&
42
+ !e.sourceName.split('.').some((segment) => /^[A-Z]/.test(segment))) ||
43
+ e.importedSymbol?.symbolType === 'module'
44
+ ? `import ${fqn}.*;`
45
+ : `import ${fqn};`;
39
46
  });
40
47
  const localNames = importStatement.imports.elements
41
48
  .filter((el) => el.importedSymbol?.symbolType === 'module')
@@ -345,8 +352,12 @@ class JavaVisitor extends default_1.DefaultVisitor {
345
352
  ? this.singlePropertyInJavaScriptObjectLiteralToJavaMap(node.name, node.name, renderer)
346
353
  : this.singlePropertyInJavaScriptObjectLiteralToFluentSetters(node.name, node.name, renderer);
347
354
  }
348
- propertyAccessExpression(node, renderer) {
355
+ propertyAccessExpression(node, renderer, submoduleRef) {
349
356
  const rightHandSide = renderer.convert(node.name);
357
+ // If a submodule access, then just render the name, we emitted a * import of the expression segment already.
358
+ if (submoduleRef != null) {
359
+ return rightHandSide;
360
+ }
350
361
  let parts;
351
362
  const leftHandSide = renderer.textOf(node.expression);
352
363
  // Suppress the LHS of the dot operator if it matches an alias for a module import.
@@ -572,7 +583,10 @@ function findJavaName(jsiiSymbol) {
572
583
  return modName;
573
584
  }
574
585
  }
575
- return `${recurse((0, jsii_utils_1.namespaceName)(fqn))}.${(0, jsii_utils_1.simpleName)(jsiiSymbol.fqn)}`;
586
+ const ns = (0, jsii_utils_1.namespaceName)(fqn);
587
+ const nsJavaName = recurse(ns);
588
+ const leaf = (0, jsii_utils_1.simpleName)(fqn);
589
+ return `${nsJavaName}.${leaf}`;
576
590
  }
577
591
  }
578
592
  function guessJavaNamespaceName(packageName) {
@@ -3,6 +3,7 @@ import { ObjectLiteralStruct } from '../jsii/jsii-types';
3
3
  import { TargetLanguage } from '../languages/target-language';
4
4
  import { OTree } from '../o-tree';
5
5
  import { AstRenderer, CommentSyntax } from '../renderer';
6
+ import { SubmoduleReference } from '../submodule-reference';
6
7
  import { ImportStatement } from '../typescript/imports';
7
8
  import { DefaultVisitor } from './default';
8
9
  interface StructVar {
@@ -74,7 +75,7 @@ export declare class PythonVisitor extends DefaultVisitor<PythonLanguageContext>
74
75
  * existing cached translations.
75
76
  */
76
77
  static readonly VERSION = "2";
77
- readonly language = TargetLanguage.PYTHON;
78
+ readonly language = TargetLanguage.VISUALIZE;
78
79
  readonly defaultContext: {};
79
80
  /**
80
81
  * Keep track of module imports we've seen, so that if we need to render a type we can pick from these modules
@@ -101,7 +102,7 @@ export declare class PythonVisitor extends DefaultVisitor<PythonLanguageContext>
101
102
  }): OTree;
102
103
  block(node: ts.Block, context: PythonVisitorContext): OTree;
103
104
  regularCallExpression(node: ts.CallExpression, context: PythonVisitorContext): OTree;
104
- propertyAccessExpression(node: ts.PropertyAccessExpression, context: PythonVisitorContext): OTree;
105
+ propertyAccessExpression(node: ts.PropertyAccessExpression, context: PythonVisitorContext, submoduleReference: SubmoduleReference | undefined): OTree;
105
106
  parameterDeclaration(node: ts.ParameterDeclaration, context: PythonVisitorContext): OTree;
106
107
  ifStatement(node: ts.IfStatement, context: PythonVisitorContext): OTree;
107
108
  unknownTypeObjectLiteralExpression(node: ts.ObjectLiteralExpression, context: PythonVisitorContext): OTree;
@@ -58,11 +58,12 @@ class PythonVisitor extends default_1.DefaultVisitor {
58
58
  importStatement(node, context) {
59
59
  if (node.imports.import === 'full') {
60
60
  const moduleName = (0, util_1.fmap)(node.moduleSymbol, findPythonName) ?? guessPythonPackageName(node.packageName);
61
+ const importName = node.imports.alias ?? node.imports.sourceName;
61
62
  this.addImport({
62
63
  importedFqn: node.moduleSymbol?.fqn ?? node.packageName,
63
- importName: node.imports.alias,
64
+ importName,
64
65
  });
65
- return new o_tree_1.OTree([`import ${moduleName} as ${mangleIdentifier(node.imports.alias)}`], [], {
66
+ return new o_tree_1.OTree([`import ${moduleName} as ${mangleIdentifier(importName)}`], [], {
66
67
  canBreakLine: true,
67
68
  });
68
69
  }
@@ -167,7 +168,7 @@ class PythonVisitor extends default_1.DefaultVisitor {
167
168
  ')',
168
169
  ], [], { canBreakLine: true });
169
170
  }
170
- propertyAccessExpression(node, context) {
171
+ propertyAccessExpression(node, context, submoduleReference) {
171
172
  const fullText = context.textOf(node);
172
173
  if (fullText in BUILTIN_FUNCTIONS) {
173
174
  return new o_tree_1.OTree([BUILTIN_FUNCTIONS[fullText]]);
@@ -178,7 +179,10 @@ class PythonVisitor extends default_1.DefaultVisitor {
178
179
  if (explodedParameter && context.textOf(node.expression) === explodedParameter.variableName) {
179
180
  return context.convert(node.name);
180
181
  }
181
- return super.propertyAccessExpression(node, context);
182
+ if (submoduleReference != null) {
183
+ return context.convert(submoduleReference.lastNode);
184
+ }
185
+ return super.propertyAccessExpression(node, context, submoduleReference);
182
186
  }
183
187
  parameterDeclaration(node, context) {
184
188
  const type = node.type && context.typeOfType(node.type);
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
2
2
  import { TargetLanguage } from '../languages/target-language';
3
3
  import { OTree } from '../o-tree';
4
4
  import { AstRenderer } from '../renderer';
5
+ import { SubmoduleReference } from '../submodule-reference';
5
6
  import { Spans } from '../typescript/visible-spans';
6
7
  import { DefaultVisitor } from './default';
7
8
  interface RecordReferencesContext {
@@ -13,7 +14,7 @@ declare type RecordReferencesRenderer = AstRenderer<RecordReferencesContext>;
13
14
  export declare class RecordReferencesVisitor extends DefaultVisitor<RecordReferencesContext> {
14
15
  private readonly visibleSpans;
15
16
  static readonly VERSION = "2";
16
- readonly language = TargetLanguage.PYTHON;
17
+ readonly language = TargetLanguage.VISUALIZE;
17
18
  readonly defaultContext: {};
18
19
  private readonly references;
19
20
  constructor(visibleSpans: Spans);
@@ -26,7 +27,7 @@ export declare class RecordReferencesVisitor extends DefaultVisitor<RecordRefere
26
27
  */
27
28
  variableDeclaration(node: ts.VariableDeclaration, renderer: RecordReferencesRenderer): OTree;
28
29
  newExpression(node: ts.NewExpression, context: RecordReferencesRenderer): OTree;
29
- propertyAccessExpression(node: ts.PropertyAccessExpression, context: RecordReferencesRenderer): OTree;
30
+ propertyAccessExpression(node: ts.PropertyAccessExpression, context: RecordReferencesRenderer, submoduleReference: SubmoduleReference | undefined): OTree;
30
31
  regularCallExpression(node: ts.CallExpression, context: RecordReferencesRenderer): OTree;
31
32
  objectLiteralExpression(node: ts.ObjectLiteralExpression, context: RecordReferencesRenderer): OTree;
32
33
  propertyAssignment(node: ts.PropertyAssignment, renderer: RecordReferencesRenderer): OTree;
@@ -12,7 +12,7 @@ class RecordReferencesVisitor extends default_1.DefaultVisitor {
12
12
  constructor(visibleSpans) {
13
13
  super();
14
14
  this.visibleSpans = visibleSpans;
15
- this.language = target_language_1.TargetLanguage.PYTHON; // Doesn't matter, but we need it to use the visitor infra :(
15
+ this.language = target_language_1.TargetLanguage.VISUALIZE;
16
16
  this.defaultContext = {};
17
17
  this.references = new Set();
18
18
  }
@@ -45,13 +45,13 @@ class RecordReferencesVisitor extends default_1.DefaultVisitor {
45
45
  }
46
46
  return super.newExpression(node, context);
47
47
  }
48
- propertyAccessExpression(node, context) {
48
+ propertyAccessExpression(node, context, submoduleReference) {
49
49
  if (this.visibleSpans.containsStartOfNode(node)) {
50
50
  // The property itself
51
51
  this.recordNode(node, context);
52
52
  // Not currently considering the return type as "referenced"
53
53
  }
54
- return super.propertyAccessExpression(node, context);
54
+ return super.propertyAccessExpression(node, context, submoduleReference);
55
55
  }
56
56
  regularCallExpression(node, context) {
57
57
  if (this.visibleSpans.containsStartOfNode(node)) {
@@ -1,12 +1,37 @@
1
1
  export declare enum TargetLanguage {
2
+ /** @internal an alias of PYTHON to make intent clear when language is irrelevant */
3
+ VISUALIZE = "python",
2
4
  PYTHON = "python",
3
5
  CSHARP = "csharp",
4
6
  JAVA = "java",
5
7
  GO = "go"
6
8
  }
7
- export declare function targetName(language: TargetLanguage.PYTHON): 'python';
9
+ export declare function targetName(language: TargetLanguage.PYTHON | TargetLanguage.VISUALIZE): 'python';
8
10
  export declare function targetName(language: TargetLanguage.CSHARP): 'dotnet';
9
11
  export declare function targetName(language: TargetLanguage.JAVA): 'java';
10
12
  export declare function targetName(language: TargetLanguage.GO): 'go';
11
13
  export declare function targetName(language: TargetLanguage): 'python' | 'dotnet' | 'java' | 'go';
14
+ /**
15
+ * Determines whether the supplied language supports transitive submodule
16
+ * access (similar to how TypeScript/Javascript allows to use a partially
17
+ * qualified name to access a namespace-nested value).
18
+ *
19
+ * If `true`, imports will mirror those found in the original TypeScript
20
+ * code, namespace-traversing property accesses will be rendered as such. This
21
+ * means the following snippet would be transformed "as-is":
22
+ * ```ts
23
+ * import * as cdk from 'aws-cdk-lib';
24
+ * new cdk.aws_s3.Bucket(this, 'Bucket');
25
+ * ```
26
+ *
27
+ * If `false` on the other hand, each used submodule will be imported
28
+ * separately and namespace-traversing property accesses will be replaced with
29
+ * references to the separately-imported submodule. This means the above
30
+ * snippet would be transformed as if it had been modifired to:
31
+ * ```ts
32
+ * import * as aws_s3 from 'aws-cdk-lib/aws-s3';
33
+ * new aws_s3.Bucket(this, 'Bucket');
34
+ * ```
35
+ */
36
+ export declare function supportsTransitiveSubmoduleAccess(language: TargetLanguage): boolean;
12
37
  //# sourceMappingURL=target-language.d.ts.map
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.targetName = exports.TargetLanguage = void 0;
3
+ exports.supportsTransitiveSubmoduleAccess = exports.targetName = exports.TargetLanguage = void 0;
4
4
  const assert = require("assert");
5
5
  var TargetLanguage;
6
6
  (function (TargetLanguage) {
7
+ /** @internal an alias of PYTHON to make intent clear when language is irrelevant */
8
+ TargetLanguage["VISUALIZE"] = "python";
7
9
  TargetLanguage["PYTHON"] = "python";
8
10
  TargetLanguage["CSHARP"] = "csharp";
9
11
  TargetLanguage["JAVA"] = "java";
@@ -20,6 +22,7 @@ function targetName(language) {
20
22
  // values of the TargetLanguage enum, but we add an assert here for clarity of intent.
21
23
  assert(VALID_TARGET_LANGUAGES.has(language), `Invalid/unexpected target language identifier: ${language}`);
22
24
  switch (language) {
25
+ case TargetLanguage.VISUALIZE:
23
26
  case TargetLanguage.PYTHON:
24
27
  return 'python';
25
28
  case TargetLanguage.CSHARP:
@@ -31,4 +34,39 @@ function targetName(language) {
31
34
  }
32
35
  }
33
36
  exports.targetName = targetName;
37
+ /**
38
+ * Determines whether the supplied language supports transitive submodule
39
+ * access (similar to how TypeScript/Javascript allows to use a partially
40
+ * qualified name to access a namespace-nested value).
41
+ *
42
+ * If `true`, imports will mirror those found in the original TypeScript
43
+ * code, namespace-traversing property accesses will be rendered as such. This
44
+ * means the following snippet would be transformed "as-is":
45
+ * ```ts
46
+ * import * as cdk from 'aws-cdk-lib';
47
+ * new cdk.aws_s3.Bucket(this, 'Bucket');
48
+ * ```
49
+ *
50
+ * If `false` on the other hand, each used submodule will be imported
51
+ * separately and namespace-traversing property accesses will be replaced with
52
+ * references to the separately-imported submodule. This means the above
53
+ * snippet would be transformed as if it had been modifired to:
54
+ * ```ts
55
+ * import * as aws_s3 from 'aws-cdk-lib/aws-s3';
56
+ * new aws_s3.Bucket(this, 'Bucket');
57
+ * ```
58
+ */
59
+ function supportsTransitiveSubmoduleAccess(language) {
60
+ switch (language) {
61
+ case TargetLanguage.PYTHON:
62
+ return true;
63
+ case TargetLanguage.CSHARP:
64
+ return true;
65
+ case TargetLanguage.JAVA:
66
+ return false;
67
+ case TargetLanguage.GO:
68
+ return false;
69
+ }
70
+ }
71
+ exports.supportsTransitiveSubmoduleAccess = supportsTransitiveSubmoduleAccess;
34
72
  //# sourceMappingURL=target-language.js.map
@@ -2,8 +2,10 @@ import * as ts from 'typescript';
2
2
  import { OTree } from '../o-tree';
3
3
  import { AstRenderer, AstHandler, CommentSyntax } from '../renderer';
4
4
  import { ImportStatement } from '../typescript/imports';
5
+ import { TargetLanguage } from './target-language';
5
6
  export declare class VisualizeAstVisitor implements AstHandler<void> {
6
7
  private readonly includeHandlerNames?;
8
+ readonly language = TargetLanguage.VISUALIZE;
7
9
  readonly defaultContext: void;
8
10
  constructor(includeHandlerNames?: boolean | undefined);
9
11
  mergeContext(_old: any, _update: any): any;
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VisualizeAstVisitor = void 0;
4
4
  const o_tree_1 = require("../o-tree");
5
5
  const renderer_1 = require("../renderer");
6
+ const target_language_1 = require("./target-language");
6
7
  class VisualizeAstVisitor {
7
8
  constructor(includeHandlerNames) {
8
9
  this.includeHandlerNames = includeHandlerNames;
10
+ this.language = target_language_1.TargetLanguage.VISUALIZE;
9
11
  this.defaultContext = undefined;
10
12
  }
11
13
  mergeContext(_old, _update) {
package/lib/renderer.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as ts from 'typescript';
2
2
  import { TargetLanguage } from './languages';
3
3
  import { OTree, UnknownSyntax, Span } from './o-tree';
4
+ import { SubmoduleReference, SubmoduleReferenceMap } from './submodule-reference';
4
5
  import { ImportStatement } from './typescript/imports';
5
6
  /**
6
7
  * Render a TypeScript AST to some other representation (encoded in OTrees)
@@ -15,9 +16,10 @@ export declare class AstRenderer<C> {
15
16
  readonly typeChecker: ts.TypeChecker;
16
17
  private readonly handler;
17
18
  private readonly options;
19
+ readonly submoduleReferences: SubmoduleReferenceMap;
18
20
  readonly diagnostics: ts.Diagnostic[];
19
21
  readonly currentContext: C;
20
- constructor(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, handler: AstHandler<C>, options?: AstRendererOptions);
22
+ constructor(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, handler: AstHandler<C>, options?: AstRendererOptions, submoduleReferences?: SubmoduleReferenceMap);
21
23
  /**
22
24
  * Merge the new context with the current context and create a new Converter from it
23
25
  */
@@ -95,6 +97,7 @@ export declare class AstRenderer<C> {
95
97
  * of AST node.
96
98
  */
97
99
  export interface AstHandler<C> {
100
+ readonly language: TargetLanguage;
98
101
  readonly defaultContext: C;
99
102
  readonly indentChar?: ' ' | '\t';
100
103
  mergeContext(old: C, update: Partial<C>): C;
@@ -110,7 +113,7 @@ export interface AstHandler<C> {
110
113
  returnStatement(node: ts.ReturnStatement, context: AstRenderer<C>): OTree;
111
114
  binaryExpression(node: ts.BinaryExpression, context: AstRenderer<C>): OTree;
112
115
  ifStatement(node: ts.IfStatement, context: AstRenderer<C>): OTree;
113
- propertyAccessExpression(node: ts.PropertyAccessExpression, context: AstRenderer<C>): OTree;
116
+ propertyAccessExpression(node: ts.PropertyAccessExpression, context: AstRenderer<C>, submoduleReference: SubmoduleReference | undefined): OTree;
114
117
  awaitExpression(node: ts.AwaitExpression, context: AstRenderer<C>): OTree;
115
118
  callExpression(node: ts.CallExpression, context: AstRenderer<C>): OTree;
116
119
  expressionStatement(node: ts.ExpressionStatement, context: AstRenderer<C>): OTree;