Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: fromXxx() methods can be called with implicit or explicit parameters #309

Merged
merged 5 commits into from
Sep 26, 2022

Conversation

otaviomacedo
Copy link
Contributor

Static factory methods with a “from” prefix allow users to import unowned constructs. Awslint enforces the rules that the first two arguments are the scope and id, followed by the parameters specific to the method.

With this change, users can call methods of this category, by either omitting the scope and id:

"Call": {
  "aws-cdk-lib.aws_s3.Bucket.fromBucketName": "read-bucket"
}

or by passing them explicitly using a new CDK-specific intrinsic function, CDK::Args, along with a new CDK-specific pseudo-parameter, CDK::Scope:

"Call": {
  "aws-cdk-lib.aws_s3.Bucket.fromBucketName": {
    "CDK::Args": [{ "Ref": "CDK::Scope" }, "ReadBucket", "read-bucket"]
  }
}

): MethodCall {
const candidateFQN = assertFullyQualifiedStaticMethodCall(call);
const candidateClass = typeSystem.findClass(candidateFQN);
const methods = enumLikeClassMethods(candidateClass);
const methods = staticNonVoidMethods(candidateClass);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because methods like Bucket.fromBucketName() return an IBucket and thus get filtered out in enumLikeClassMethods().

function prepareParameters(
x: ArrayLiteral,
callable: reflect.Callable,
logicalId?: string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place where the logical id is used. Every other function in this PR that got logicalId added to its signature only passes it down in order to reach this point. I'm not very happy with this situation, but I haven't found a good way to solve it.

One option I considered was to not receive the logical id here and, instead, inject a new pseudo-parameter (something like CDK::ImplicitId) in the second argument. Then, at the point where the lazy resource is resolved, this pseudo-parameter would be replaced with the id. But this would obscure the logic by creating a tight coupling between two functions that know nothing about each other.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure which way is better. But I do share your feelings about it being passed down all the way.

Re: pseudo-parameter. I don't think it's too bad to do this. Also it would not have to be a pseudo-parameter, but could be an AST type lazy-logical-id. We already have lazy evaluated resources, so why not have a lazy id?

Another option would be in introduce a context to type resolution. We will likely need something like it for #246 anyway. Basically a generic means of keeping track of where in the AST we are, what logicalID, what property, what the parent type is, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree with introducing context to type resolution. We should probably address that in its own change. For now, I've created a new intrinsic that lazily resolves the id. By default, it throws the error with the information we have. When it makes sense (static method calls on lazy resources) we replace that implementation with one that actually returns the id.

if (isBehavioralInterface(typeRef.type)) {
return ResolvableExpressionType.BEHAVIORAL_INTERFACE;
}
if (isSerializableInterface(typeRef.type)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is bug fix. Many behavioral interfaces are being classified as structs because of the order of evaluation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an explicit test case for this? If not maybe a comment would be useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

});

expect(() => new TypedTemplate(template, { typeSystem })).toThrow(
/failed because the id could not be inferred/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we could try and generate an id even for cases like this. For example, the id could be the logical id of the resource + the property name (BucketencryptionKey in this case). But this would be a complex change in itself (and I'm not even sure this can be done for all cases).

This is a one way door decision. Allowing implicit calls later would not be a breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: Does the explicit version with CDK:Args work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Added a new test for this scenario.

Comment on lines 295 to 297
if (logicalId === 'CDK::Scope') {
return this.context.stack;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this works. The other Pseudo Parameters are added to the context as evaluation references. Did you try to do that? I'd expect that to work without adding the implemenation detail to resolveReferences.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's better. Thanks!

Comment on lines 43 to 47
if (!/^[A-Za-z0-9]+$/.test(logicalId)) {
throw new Error(
`Resource logical IDs must be alphanumeric. Got: '${logicalId}'.`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess not. CFN will already have done this validation.

function prepareParameters(
x: ArrayLiteral,
callable: reflect.Callable,
logicalId?: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure which way is better. But I do share your feelings about it being passed down all the way.

Re: pseudo-parameter. I don't think it's too bad to do this. Also it would not have to be a pseudo-parameter, but could be an AST type lazy-logical-id. We already have lazy evaluated resources, so why not have a lazy id?

Another option would be in introduce a context to type resolution. We will likely need something like it for #246 anyway. Basically a generic means of keeping track of where in the AST we are, what logicalID, what property, what the parent type is, etc.

if (isBehavioralInterface(typeRef.type)) {
return ResolvableExpressionType.BEHAVIORAL_INTERFACE;
}
if (isSerializableInterface(typeRef.type)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an explicit test case for this? If not maybe a comment would be useful.

});

expect(() => new TypedTemplate(template, { typeSystem })).toThrow(
/failed because the id could not be inferred/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: Does the explicit version with CDK:Args work here?

Copy link
Contributor

@mrgrain mrgrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See inline comments/questions.

Copy link
Contributor

@mrgrain mrgrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it! 😍

@mergify mergify bot merged commit a1a2785 into main Sep 26, 2022
@mergify mergify bot deleted the otaviom/fromXxx branch September 26, 2022 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants