diff --git a/packages/@aws-cdk/custom-resource-handlers/package.json b/packages/@aws-cdk/custom-resource-handlers/package.json index b09c7931ebab8..203ce043be1c5 100644 --- a/packages/@aws-cdk/custom-resource-handlers/package.json +++ b/packages/@aws-cdk/custom-resource-handlers/package.json @@ -48,7 +48,7 @@ "@types/jest": "^29.5.14", "aws-sdk-client-mock": "4.1.0", "aws-sdk-client-mock-jest": "4.1.0", - "@cdklabs/typewriter": "^0.0.12", + "@cdklabs/typewriter": "^0.0.14", "jest": "^29.7.0", "sinon": "^9.2.4", "nock": "^13.5.6", diff --git a/packages/@aws-cdk/mixins-preview/package.json b/packages/@aws-cdk/mixins-preview/package.json index 1fa31a50715e5..20c6d4e455f78 100644 --- a/packages/@aws-cdk/mixins-preview/package.json +++ b/packages/@aws-cdk/mixins-preview/package.json @@ -659,7 +659,7 @@ "@aws-cdk/service-spec-types": "^0.0.193", "@aws-cdk/spec2cdk": "0.0.0", "@cdklabs/tskb": "^0.0.4", - "@cdklabs/typewriter": "^0.0.12", + "@cdklabs/typewriter": "^0.0.14", "@types/jest": "^29.5.14", "aws-cdk-lib": "0.0.0", "constructs": "^10.0.0", diff --git a/packages/@aws-cdk/mixins-preview/scripts/spec2logs/builder.ts b/packages/@aws-cdk/mixins-preview/scripts/spec2logs/builder.ts index 0f108b150cfb2..06823a8897725 100644 --- a/packages/@aws-cdk/mixins-preview/scripts/spec2logs/builder.ts +++ b/packages/@aws-cdk/mixins-preview/scripts/spec2logs/builder.ts @@ -1,10 +1,10 @@ import type { Resource, Service, SpecDatabase } from '@aws-cdk/service-spec-types'; import { naming, util } from '@aws-cdk/spec2cdk'; -import { CDK_CORE, CONSTRUCTS } from '@aws-cdk/spec2cdk/lib/cdk/cdk'; +import { CDK_CORE, CDK_INTERFACES, CONSTRUCTS } from '@aws-cdk/spec2cdk/lib/cdk/cdk'; import type { Method } from '@cdklabs/typewriter'; import { Module, ExternalModule, ClassType, Stability, Type, expr, stmt, ThingSymbol, $this, CallableProxy, NewExpression, $E } from '@cdklabs/typewriter'; -import { CDK_AWS_LOGS, MIXINS_LOGS_DELIVERY, REF_INTERFACES } from './helpers'; -import type { ServiceSubmoduleProps, SelectiveImport, LocatedModule } from '@aws-cdk/spec2cdk/lib/cdk/service-submodule'; +import { CDK_AWS_LOGS, MIXINS_LOGS_DELIVERY } from './helpers'; +import type { ServiceSubmoduleProps, LocatedModule } from '@aws-cdk/spec2cdk/lib/cdk/service-submodule'; import { BaseServiceSubmodule, relativeImportPath } from '@aws-cdk/spec2cdk/lib/cdk/service-submodule'; import type { AddServiceProps, LibraryBuilderProps } from '@aws-cdk/spec2cdk/lib/cdk/library-builder'; import { LibraryBuilder } from '@aws-cdk/spec2cdk/lib/cdk/library-builder'; @@ -48,8 +48,6 @@ export class LogsDeliveryBuilder extends LibraryBuilder(); public readonly mixin: LogsMixin; private readonly helpers: LogsHelper[] = []; @@ -157,7 +154,7 @@ class LogsHelper extends ClassType { const paramS3 = toS3.addParameter({ name: 'bucket', - type: REF_INTERFACES.IBucketRef, + type: CDK_INTERFACES.IBucketRef, }); const permissions = this.resource.vendedLogs!.permissionsVersion === 'V2' ? MIXINS_LOGS_DELIVERY.S3LogsDeliveryPermissionsVersion.V2 : MIXINS_LOGS_DELIVERY.S3LogsDeliveryPermissionsVersion.V1; @@ -179,7 +176,7 @@ class LogsHelper extends ClassType { const paramCWL = toCWL.addParameter({ name: 'logGroup', - type: REF_INTERFACES.ILogGroupRef, + type: CDK_INTERFACES.ILogGroupRef, }); toCWL.addBody(stmt.block( @@ -199,7 +196,7 @@ class LogsHelper extends ClassType { const paramFH = toFH.addParameter({ name: 'deliveryStream', - type: REF_INTERFACES.IDeliveryStreamRef, + type: CDK_INTERFACES.IDeliveryStreamRef, }); toFH.addBody(stmt.block( @@ -238,7 +235,6 @@ class LogsHelper extends ClassType { } class LogsMixin extends ClassType { - public readonly imports = new Array(); private readonly resourceType: Type; constructor( diff --git a/packages/@aws-cdk/mixins-preview/scripts/spec2logs/helpers.ts b/packages/@aws-cdk/mixins-preview/scripts/spec2logs/helpers.ts index 8161f8dbd2a05..0c12c14f2a57a 100644 --- a/packages/@aws-cdk/mixins-preview/scripts/spec2logs/helpers.ts +++ b/packages/@aws-cdk/mixins-preview/scripts/spec2logs/helpers.ts @@ -9,16 +9,9 @@ class MixinsLogsDelivery extends ExternalModule { public readonly S3LogsDeliveryPermissionsVersion = $T(Type.fromName(this, 'S3LogsDeliveryPermissionsVersion')); } -class CdkRefInterfaces extends ExternalModule { - public readonly IBucketRef = Type.fromName(this, 'aws_s3.IBucketRef'); - public readonly ILogGroupRef = Type.fromName(this, 'aws_logs.ILogGroupRef'); - public readonly IDeliveryStreamRef = Type.fromName(this, 'aws_kinesisfirehose.IDeliveryStreamRef'); -} - class CdkAwsLogs extends ExternalModule { public readonly CfnDeliverySource = Type.fromName(this, 'CfnDeliverySource'); } export const MIXINS_LOGS_DELIVERY = new MixinsLogsDelivery('@aws-cdk/mixins-preview/services/aws-logs'); -export const REF_INTERFACES = new CdkRefInterfaces('aws-cdk-lib/interfaces'); export const CDK_AWS_LOGS = new CdkAwsLogs('aws-cdk-lib/aws-logs'); diff --git a/packages/@aws-cdk/mixins-preview/scripts/spec2mixins/builder.ts b/packages/@aws-cdk/mixins-preview/scripts/spec2mixins/builder.ts index 8f9c07bf03755..3c706a8bdda53 100644 --- a/packages/@aws-cdk/mixins-preview/scripts/spec2mixins/builder.ts +++ b/packages/@aws-cdk/mixins-preview/scripts/spec2mixins/builder.ts @@ -9,7 +9,7 @@ import { ExternalModule, Module, ClassType, Stability, StructType, Type, expr, s import { MIXINS_COMMON, MIXINS_CORE, MIXINS_UTILS } from './helpers'; import type { AddServiceProps, LibraryBuilderProps } from '@aws-cdk/spec2cdk/lib/cdk/library-builder'; import { LibraryBuilder } from '@aws-cdk/spec2cdk/lib/cdk/library-builder'; -import type { LocatedModule, SelectiveImport, ServiceSubmoduleProps } from '@aws-cdk/spec2cdk/lib/cdk/service-submodule'; +import type { LocatedModule, ServiceSubmoduleProps } from '@aws-cdk/spec2cdk/lib/cdk/service-submodule'; import { BaseServiceSubmodule, relativeImportPath } from '@aws-cdk/spec2cdk/lib/cdk/service-submodule'; class MixinsServiceModule extends BaseServiceSubmodule { @@ -47,8 +47,6 @@ export class MixinsBuilder extends LibraryBuilder { submodule.registerResource(resource.cloudFormationType, l1PropsMixin); l1PropsMixin.build(); - - submodule.registerSelectiveImports(...l1PropsMixin.imports); } private createMixinsModule(submodule: MixinsServiceModule, service: Service): LocatedModule { @@ -86,7 +84,6 @@ class L1PropsMixin extends ClassType { private readonly decider: ResourceDecider; private readonly relationshipDecider: RelationshipDecider; private readonly converter: TypeConverter; - public readonly imports = new Array(); constructor( scope: Module, @@ -126,14 +123,17 @@ class L1PropsMixin extends ClassType { }, }); - this.relationshipDecider = new RelationshipDecider(this.resource, db, false); + this.relationshipDecider = new RelationshipDecider(this.resource, db, { + enableRelationships: true, + enableNestedRelationships: true, + refsImportLocation: 'aws-cdk-lib/interfaces' + }); this.converter = TypeConverter.forMixin({ db: db, resource: this.resource, resourceClass: this, relationshipDecider: this.relationshipDecider, }); - this.imports = this.relationshipDecider.imports; this.decider = new ResourceDecider(this.resource, this.converter, this.relationshipDecider); } diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/aws-cdk-lib.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/aws-cdk-lib.ts index ffb9bf74adf5b..027e1a7e9b8fe 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/aws-cdk-lib.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/aws-cdk-lib.ts @@ -175,16 +175,16 @@ export class AwsCdkLibBuilder extends LibraryBuilder const resourceClass = new ResourceClass(resourceModule, this.db, resource, { suffix: props?.nameSuffix, deprecated: props?.deprecated, - interfacesModule: submodule.interfaces ? { - module: submodule.interfaces?.module, + importPaths: this.resolveImportPaths(submodule.resourcesMod.filePath), + interfacesModule: { + module: submodule.interfaces.module, importLocation: relativeImportPath(submodule.resourcesMod, submodule.interfaces), - } : undefined, + }, }); resourceClass.build(); submodule.registerResource(resource.cloudFormationType, resourceClass); - submodule.registerSelectiveImports(...resourceClass.imports); submodule.augmentations?.module.augmentResource(resource, resourceClass); } @@ -259,18 +259,8 @@ export class AwsCdkLibBuilder extends LibraryBuilder grantModule.build(Object.fromEntries(submodule.resources), props?.nameSuffix); } - // Apply selective imports only to resources module - for (const selectiveImport of submodule.imports) { - const sourceModule = new Module(selectiveImport.moduleName); - sourceModule.importSelective( - submodule.resourcesMod.module, - selectiveImport.types.map((t) => `${t.originalType} as ${t.aliasedType}`), - { fromLocation: relativeImportPath(submodule.resourcesMod, sourceModule.name) }, - ); - } - // Add an import for the interfaces file to the entry point file (make sure not to do it twice) - if (!submodule.interfaces?.module.isEmpty() && this.interfacesEntry && submodule.didCreateInterfaceModule) { + if (!submodule.interfaces.module.isEmpty() && this.interfacesEntry && submodule.didCreateInterfaceModule) { const exportName = submoduleSymbolFromName(submodule.service.name); const importLocation = relativeImportPath(this.interfacesEntry, submodule.interfaces); @@ -284,7 +274,8 @@ export class AwsCdkLibBuilder extends LibraryBuilder if (!this.inCdkLib) { return { core: 'aws-cdk-lib/core', - interfacesEnvironmentAware: 'aws-cdk-lib/interfaces', + interfaces: 'aws-cdk-lib/interfaces', + interfacesEnvironmentAware: 'aws-cdk-lib/interfaces/environment-aware', coreHelpers: 'aws-cdk-lib/core/lib/helpers-internal', coreErrors: 'aws-cdk-lib/core/lib/errors', cloudwatch: 'aws-cdk-lib/aws-cloudwatch', @@ -294,6 +285,7 @@ export class AwsCdkLibBuilder extends LibraryBuilder return { core: relativeImportPath(sourceModule, 'core/lib'), + interfaces: relativeImportPath(sourceModule, 'interfaces'), interfacesEnvironmentAware: relativeImportPath(sourceModule, 'interfaces/environment-aware'), coreHelpers: relativeImportPath(sourceModule, 'core/lib/helpers-internal'), coreErrors: relativeImportPath(sourceModule, 'core/lib/errors'), @@ -321,6 +313,11 @@ export interface ImportPaths { */ readonly core: string; + /** + * The import name used import the `interfaces` module + */ + readonly interfaces: string; + /** * The import name used import a specific interface from the `interfaces` module * diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts index b6c64f49f11d1..70c9fbaa7d3ea 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts @@ -85,6 +85,10 @@ export class CdkCore extends ExternalModule { export class Interfaces extends ExternalModule { public readonly IEnvironmentAware = Type.fromName(this, 'IEnvironmentAware'); + + public readonly IBucketRef = Type.fromName(this, 'aws_s3.IBucketRef'); + public readonly ILogGroupRef = Type.fromName(this, 'aws_logs.ILogGroupRef'); + public readonly IDeliveryStreamRef = Type.fromName(this, 'aws_kinesisfirehose.IDeliveryStreamRef'); } export class CdkInternalHelpers extends ExternalModule { @@ -121,6 +125,7 @@ export class CdkCloudWatch extends ExternalModule { public readonly MetricOptions = Type.fromName(this, 'MetricOptions'); } +export const CDK_INTERFACES = new Interfaces('aws-cdk-lib/interfaces'); export const CDK_INTERFACES_ENVIRONMENT_AWARE = new Interfaces('aws-cdk-lib/interfaces/environment-aware'); export const CDK_CORE = new CdkCore('aws-cdk-lib/core'); export const CDK_CLOUDWATCH = new CdkCloudWatch('aws-cdk-lib/aws-cloudwatch'); diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/library-builder.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/library-builder.ts index 0b7670d20dc66..3e1bf06ac93aa 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/library-builder.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/library-builder.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { SpecDatabase, Resource, Service } from '@aws-cdk/service-spec-types'; import { Module } from '@cdklabs/typewriter'; import { IWriter, substituteFilePattern } from '../util'; -import { BaseServiceSubmodule, LocatedModule, relativeImportPath } from './service-submodule'; +import { BaseServiceSubmodule, LocatedModule } from './service-submodule'; export interface AddServiceProps { /** @@ -142,16 +142,8 @@ export abstract class LibraryBuilder `${t.originalType} as ${t.aliasedType}`), { - fromLocation: relativeImportPath(mod.filePath, sourceModule.name), - }); - } - } + protected postprocessSubmodule(_submodule: ServiceSubmodule, _props?: AddServiceProps): void { + // does nothing, this is a hook for implementations } private obtainServiceSubmodule(service: Service, targetSubmodule?: string, grantsConfig?: string): ServiceSubmodule { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts index d14f3a35ed913..8a66477afe6c4 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts @@ -1,23 +1,9 @@ import { Property, RelationshipRef, Resource, RichProperty, SpecDatabase } from '@aws-cdk/service-spec-types'; +import { Module, SelectiveModuleImport, Type } from '@cdklabs/typewriter'; import * as naming from '../naming'; -import { namespaceFromResource, referenceInterfaceName, referenceInterfaceAttributeName, referencePropertyName, typeAliasPrefixFromResource } from '../naming'; +import { CDK_INTERFACES } from './cdk'; import { ResourceReference } from './reference-props'; import { log } from '../util'; -import { SelectiveImport } from './service-submodule'; - -/** - * We currently disable the relationship on the properties of types because they would create a backwards incompatible change - * by broadening the output type as types are used both in input and output. This represents: - * Relationship counts: - * Resource-level (non-nested): 598 - * Type-level (nested): 483 <- these are disabled by this flag - * Total: 1081 - * Properties with relationships: - * Resource-level (non-nested): 493 - * Type-level (nested): 358 - * Total: 851 - */ -export const GENERATE_RELATIONSHIPS_ON_TYPES = false; /** * Represents a cross-service property relationship that enables references @@ -25,45 +11,57 @@ export const GENERATE_RELATIONSHIPS_ON_TYPES = false; */ export interface Relationship { /** The TypeScript interface type that provides the reference (e.g. "IRoleRef") */ - readonly referenceType: string; + readonly refType: Type; + /** Human friendly name of the reference type for error generation (e.g. "iam.IRoleRef") */ + readonly refTypeDisplayName: string; /** The property name on the reference interface that holds the reference object (e.g. "roleRef") */ - readonly referenceName: string; + readonly refPropStructName: string; /** The property to extract from the reference object (e.g. "roleArn") */ - readonly propName: string; - /** Human friendly name of the reference type for error generation (e.g. "iam.IRoleRef") */ - readonly typeDisplayName: string; + readonly refPropName: string; +} + +export interface RelationshipDeciderProps { + /** + * Render relationships + */ + readonly enableRelationships: boolean; + + /** + * We currently disable the relationship on the properties of types because they would create a backwards incompatible change + * by broadening the output type as types are used both in input and output. This represents: + * Relationship counts: + * Resource-level (non-nested): 598 + * Type-level (nested): 483 <- these are disabled by this flag + * Total: 1081 + * Properties with relationships: + * Resource-level (non-nested): 493 + * Type-level (nested): 358 + * Total: 851 + */ + readonly enableNestedRelationships: boolean; + + /** + * The location to import reference interfaces from. + */ + readonly refsImportLocation: string; } /** * Extracts resource relationship information from the database for cross-service property references. */ export class RelationshipDecider { - private readonly namespace: string; - public readonly imports = new Array(); + public readonly enableRelationships: boolean; + public readonly enableNestedRelationships: boolean; + private readonly refsImportLocation: string; - constructor(readonly resource: Resource, private readonly db: SpecDatabase, private readonly enableRelationships = true) { - this.namespace = namespaceFromResource(resource); - } - - private registerRequiredImport({ namespace, originalType, aliasedType }: { - namespace: string; - originalType: string; - aliasedType: string; - }) { - const moduleName = naming.modulePartsFromNamespace(namespace).moduleName; - const moduleImport = this.imports.find(i => i.moduleName === moduleName); - if (!moduleImport) { - this.imports.push({ - moduleName, - types: [{ originalType, aliasedType }], - }); - } else { - if (!moduleImport.types.find(t => - t.originalType === originalType && t.aliasedType === aliasedType, - )) { - moduleImport.types.push({ originalType, aliasedType }); - } - } + constructor( + readonly resource: Resource, + private readonly db: SpecDatabase, + props: RelationshipDeciderProps, + ) { + this.enableRelationships = props.enableRelationships ?? true; + this.enableNestedRelationships = props.enableNestedRelationships ?? false; + this.refsImportLocation = props.refsImportLocation; } /** @@ -77,7 +75,7 @@ export class RelationshipDecider { } const targetResource = this.db.lookup('resource', 'cloudFormationType', 'equals', relationship.cloudFormationType).only(); const refProps = new ResourceReference(targetResource).referenceProps; - const expectedPropName = referencePropertyName(relationship.propertyName, targetResource.name); + const expectedPropName = naming.referencePropertyName(relationship.propertyName, targetResource.name); const prop = refProps.find(p => p.declaration.name === expectedPropName); if (!prop) { log.debug( @@ -90,7 +88,7 @@ export class RelationshipDecider { return targetResource; } - public parseRelationship(sourcePropName: string, relationships?: RelationshipRef[]) { + public parseRelationship(targetModule: Module, sourcePropName: string, relationships?: RelationshipRef[]) { const parsedRelationships: Relationship[] = []; if (!relationships) { return parsedRelationships; @@ -100,22 +98,25 @@ export class RelationshipDecider { if (!targetResource) { continue; } + + // import the ref module + const refsAlias = naming.interfaceModuleImportName(targetResource); + const selectiveImport = new SelectiveModuleImport(CDK_INTERFACES, this.refsImportLocation, [ + [naming.submoduleSymbolFromResource(targetResource), refsAlias], + ]); + targetModule.addImport(selectiveImport); + // Ignore the suffix part because it's an edge case that happens only for one module - const interfaceName = referenceInterfaceName(targetResource.name); - const refPropStructName = referenceInterfaceAttributeName(targetResource.name); - - const targetNamespace = namespaceFromResource(targetResource); - let aliasedTypeName = undefined; - if (this.namespace !== targetNamespace) { - // If this is not in our namespace we need to alias the import type - aliasedTypeName = `${typeAliasPrefixFromResource(targetResource)}${interfaceName}`; - this.registerRequiredImport({ namespace: targetNamespace, originalType: interfaceName, aliasedType: aliasedTypeName }); - } + const interfaceName = naming.referenceInterfaceName(targetResource.name); + const referenceType = Type.fromName(targetModule, `${refsAlias}.${interfaceName}`); + const referenceTypeDisplayName = `${naming.typeAliasPrefixFromResource(targetResource).toLowerCase()}.${interfaceName}`; + const refPropStructName = naming.referenceInterfaceAttributeName(targetResource.name); + parsedRelationships.push({ - referenceType: aliasedTypeName ?? interfaceName, - referenceName: refPropStructName, - propName: referencePropertyName(relationship.propertyName, targetResource.name), - typeDisplayName: `${typeAliasPrefixFromResource(targetResource).toLowerCase()}.${interfaceName}`, + refType: referenceType, + refTypeDisplayName: referenceTypeDisplayName, + refPropStructName: refPropStructName, + refPropName: naming.referencePropertyName(relationship.propertyName, targetResource.name), }); } return parsedRelationships; @@ -146,7 +147,7 @@ export class RelationshipDecider { * Checks if a given property needs a flattening function or not */ public needsFlatteningFunction(propName: string, prop: Property, visited = new Set()): boolean { - if (!GENERATE_RELATIONSHIPS_ON_TYPES) { + if (!this.enableNestedRelationships) { return false; } if (this.hasValidRelationships(propName, prop.relationshipRefs)) { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts index 83e1cc462d9cc..f941270ffb377 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts @@ -1,7 +1,7 @@ import { DefinitionReference, Property } from '@aws-cdk/service-spec-types'; import { expr, Expression, Module, Type } from '@cdklabs/typewriter'; import { CDK_CORE } from './cdk'; -import { RelationshipDecider, Relationship, GENERATE_RELATIONSHIPS_ON_TYPES } from './relationship-decider'; +import { RelationshipDecider, Relationship } from './relationship-decider'; import { NON_RESOLVABLE_PROPERTY_NAMES } from './tagging'; import { TypeConverter } from './type-converter'; import { flattenFunctionNameFromType, propertyNameFromCloudFormation } from '../naming'; @@ -29,7 +29,7 @@ export class ResolverBuilder { ) {} public buildResolver(prop: Property, cfnName: string, isTypeProp = false): ResolverResult { - const shouldGenerateRelationships = isTypeProp ? GENERATE_RELATIONSHIPS_ON_TYPES : true; + const shouldGenerateRelationships = isTypeProp ? this.relationshipDecider.enableNestedRelationships : true; const name = propertyNameFromCloudFormation(cfnName); const baseType = this.converter.typeFromProperty(prop); @@ -39,7 +39,7 @@ export class ResolverBuilder { const resolvableType = cfnName in NON_RESOLVABLE_PROPERTY_NAMES ? baseType : this.converter.makeTypeResolvable(baseType); if (shouldGenerateRelationships) { - const relationships = this.relationshipDecider.parseRelationship(name, prop.relationshipRefs); + const relationships = this.relationshipDecider.parseRelationship(this.module, name, prop.relationshipRefs); if (relationships.length > 0) { return this.buildRelationshipResolver({ relationships, baseType, name, resolvableType }); } @@ -72,13 +72,13 @@ export class ResolverBuilder { if (!(baseType === Type.STRING || baseType.arrayOfType === Type.STRING)) { throw Error('Trying to map to a non string property'); } - const newTypes = relationships.map(t => Type.fromName(this.module, t.referenceType)); + const newTypes = relationships.map(t => t.refType); const propType = resolvableType.arrayOfType ? Type.arrayOf(Type.distinctUnionOf(resolvableType.arrayOfType, ...newTypes)) : Type.distinctUnionOf(resolvableType, ...newTypes); const typeDisplayNames = [ - ...relationships.map(r => r.typeDisplayName), + ...relationships.map(r => r.refTypeDisplayName), resolvableType.arrayOfType?.toString() ?? resolvableType.toString(), ].join(' | '); @@ -86,12 +86,12 @@ export class ResolverBuilder { // For single value T | string : (props.xx as IxxxRef)?.xxxRef?.xxxArn ?? cdk.ensureStringOrUndefined(props.xxx, "xxx", "iam.IxxxRef | string"); // For array []: (props.xx?.forEach((item: T | string, i: number, arr: Array) => { arr[i] = (item as T)?.xxxRef?.xx ?? cdk.ensureStringOrUndefined(item, "xxx", "lambda.T | string"); }), props.xxx as Array); // Ensures that arn properties always appear first in the chain as they are more general - const arnRels = relationships.filter(r => r.propName.toLowerCase().endsWith('arn')); - const otherRels = relationships.filter(r => !r.propName.toLowerCase().endsWith('arn')); + const arnRels = relationships.filter(r => r.refPropName.toLowerCase().endsWith('arn')); + const otherRels = relationships.filter(r => !r.refPropName.toLowerCase().endsWith('arn')); const buildChain = (itemName: string) => [ ...[...arnRels, ...otherRels] - .map(r => `(${itemName} as ${r.referenceType})?.${r.referenceName}?.${r.propName}`), + .map(r => `(${itemName} as ${r.refType})?.${r.refPropStructName}?.${r.refPropName}`), `cdk.ensureStringOrUndefined(${itemName}, "${name}", "${typeDisplayNames}")`, ].join(' ?? '); const resolver = (_: Expression) => { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts index b0a6152ec40e3..1ec15114f1932 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts @@ -32,6 +32,7 @@ import { $this, } from '@cdklabs/typewriter'; import { extractVariablesFromArnFormat, findNonIdentifierArnProperty } from './arn'; +import { ImportPaths } from './aws-cdk-lib'; import { CDK_CORE, CDK_INTERFACES_ENVIRONMENT_AWARE, CONSTRUCTS } from './cdk'; import { CloudFormationMapping } from './cloudformation-mapping'; import { ResourceDecider } from './resource-decider'; @@ -51,7 +52,6 @@ import { } from '../naming'; import { isDefined, splitDocumentation, maybeDeprecated } from '../util'; import { RelationshipDecider } from './relationship-decider'; -import { SelectiveImport } from './service-submodule'; export interface ITypeHost { typeFromSpecType(type: PropertyType): Type; @@ -63,7 +63,8 @@ export interface Referenceable { } export interface ResourceClassProps { - readonly interfacesModule?: { + readonly importPaths: ImportPaths; + readonly interfacesModule: { readonly module: Module; readonly importLocation: string; }; @@ -77,14 +78,13 @@ export class ResourceClass extends ClassType implements Referenceable { private readonly relationshipDecider: RelationshipDecider; private readonly converter: TypeConverter; private readonly module: Module; - public readonly imports = new Array(); public ref: ReferenceInterfaceTypes; constructor( scope: IScope, private readonly db: SpecDatabase, private readonly resource: Resource, - private readonly props: ResourceClassProps = {}, + private readonly props: ResourceClassProps, ) { // A mutable array we pass to super() const implements_: Type[] = [CDK_CORE.IInspectable]; @@ -107,15 +107,17 @@ export class ResourceClass extends ClassType implements Referenceable { this.module = Module.of(this); - this.relationshipDecider = new RelationshipDecider(this.resource, db); + this.relationshipDecider = new RelationshipDecider(this.resource, db, { + enableRelationships: true, + enableNestedRelationships: false, + refsImportLocation: this.props.importPaths.interfaces, + }); this.converter = TypeConverter.forResource({ db: db, resource: this.resource, resourceClass: this, relationshipDecider: this.relationshipDecider, }); - - this.imports = this.relationshipDecider.imports; this.decider = new ResourceDecider(this.resource, this.converter, this.relationshipDecider); this.propsType = new StructType(this.scope, { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/service-submodule.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/service-submodule.ts index 37bc2d67add27..a606f1e5438b7 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/service-submodule.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/service-submodule.ts @@ -24,23 +24,6 @@ export function relativeImportPath(source: LocatedModule | string, target: return ret.startsWith('.') ? ret : './' + ret; } -/** - * Represents a selective import statement for cross-module type references. - * Used to import specific types from other CDK modules when relationships - * are between different modules. - */ -export interface SelectiveImport { - /** The module name to import from */ - readonly moduleName: string; - /** Array of types that need to be imported */ - readonly types: { - /** The original type name in the source module */ - originalType: string; - /** The aliased name to avoid naming conflicts */ - aliasedType: string; - }[]; -} - /** * Create a service submodule. */ @@ -77,15 +60,10 @@ export class BaseServiceSubmodule { */ public readonly resources: Map = new Map(); - public get imports(): SelectiveImport[] { - return this.selectiveImports.sort((a, b) => a.moduleName.localeCompare(b.moduleName)); - } - public get locatedModules(): LocatedModule[] { return this.modules; } - private selectiveImports: SelectiveImport[] = []; private modules: LocatedModule[] = []; public constructor(props: ServiceSubmoduleProps) { @@ -100,29 +78,4 @@ export class BaseServiceSubmodule { public registerResource(cfnResourceName: string, type: TypeDeclaration) { this.resources.set(cfnResourceName, type); } - - public registerSelectiveImports(...selectiveImports: SelectiveImport[]) { - for (const theImport of selectiveImports) { - const existingModuleImport = this.findSelectiveImport(theImport); - if (!existingModuleImport) { - this.selectiveImports.push(theImport); - continue; - } - - // We need to avoid importing the same reference multiple times - for (const type of theImport.types) { - if (!existingModuleImport.types.find((t) => - t.originalType === type.originalType && t.aliasedType === type.aliasedType, - )) { - existingModuleImport.types.push(type); - } - } - } - } - - private findSelectiveImport(selectiveImport: SelectiveImport): SelectiveImport | undefined { - return this.selectiveImports.find( - (imp) => imp.moduleName === selectiveImport.moduleName, - ); - } } diff --git a/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts b/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts index 5ba483c5851f4..6cd66159ec7d0 100644 --- a/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts +++ b/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts @@ -62,6 +62,14 @@ export function interfaceNameFromResource(res: Resource, suffix?: string) { return `I${classNameFromResource(res, suffix)}`; } +/** + * resource to alias for interface imports + * `AWS::S3::Bucket` -> `s3Refs` + */ +export function interfaceModuleImportName(res: Resource) { + return camelcase(`${modulePartsFromResource(res).moduleBaseName}Refs`); +} + export function namespaceFromResource(res: Resource) { return res.cloudFormationType.split('::').slice(0, 2).join('::'); } @@ -145,6 +153,13 @@ export function modulePartsFromNamespace(namespace: string) { }; } +/** + * resource to module name parts (`AWS::S3::Bucket` -> ['aws-s3', 'AWS', 'S3']) + */ +export function modulePartsFromResource(res: Resource) { + return modulePartsFromNamespace(namespaceFromResource(res)); +} + /** * Submodule identifier from name (`aws-s3` -> `aws_s3`) */ @@ -152,6 +167,13 @@ export function submoduleSymbolFromName(name: string) { return name.replace(/-/g, '_'); } +/** + * Submodule identifier from name (`AWS::S3::Bucket` -> `aws_s3`) + */ +export function submoduleSymbolFromResource(res: Resource) { + return modulePartsFromResource(res).moduleName.replace(/-/g, '_'); +} + /** * Get the namespace name from the event name */ diff --git a/tools/@aws-cdk/spec2cdk/package.json b/tools/@aws-cdk/spec2cdk/package.json index 2ab23f4bcad89..444fcb1212cdb 100644 --- a/tools/@aws-cdk/spec2cdk/package.json +++ b/tools/@aws-cdk/spec2cdk/package.json @@ -35,7 +35,7 @@ "@aws-cdk/service-spec-importers": "^0.0.99", "@aws-cdk/service-spec-types": "^0.0.193", "@cdklabs/tskb": "^0.0.4", - "@cdklabs/typewriter": "^0.0.12", + "@cdklabs/typewriter": "^0.0.14", "camelcase": "^6", "fs-extra": "^9", "p-limit": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index 9007f43c55c02..d3a3fe46ee2fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2938,10 +2938,10 @@ resolved "https://registry.npmjs.org/@cdklabs/tskb/-/tskb-0.0.4.tgz#6d7af32427ef947dddcb6f1f1d76d75924e33323" integrity sha512-NFx1X0l7p5DyHtLLEyNeh1hPN4UN9hTkZkzFs/Mp+kFk7dpdINGmGVpCfRDjJ2DrcNSNENUmT+w8g73TvmBbTw== -"@cdklabs/typewriter@^0.0.12": - version "0.0.12" - resolved "https://registry.npmjs.org/@cdklabs/typewriter/-/typewriter-0.0.12.tgz#dd6348ff5284535c0134b5e43d5bb349aaf5c1db" - integrity sha512-xjiGYgMvCGwl5HFaauOjlvgBVXHtYdx8TFgn0T0E6ka95wnMhsKWvGolp8KFsK9sQ9vUr11B205DwUqquKBqLQ== +"@cdklabs/typewriter@^0.0.14": + version "0.0.14" + resolved "https://registry.npmjs.org/@cdklabs/typewriter/-/typewriter-0.0.14.tgz#57f10a3acb36c1e3604a8c5233e381579939d03e" + integrity sha512-Om+W7PT4OOwdgkxXGm6Xyk3sj3aGWcAys3qMs9tWivENKfluD2WvL4J7+ZhmIcNdJ9oqE8X/9kR+ne92QdsJLA== "@colors/colors@1.6.0", "@colors/colors@^1.6.0": version "1.6.0"