in CloudFormation/src/main/kotlin/com/intellij/aws/cloudformation/CloudFormationInspections.kt [79:345]
override fun function(function: CfnFunctionNode) {
val arg0 = function.args.getOrNull(0)
val arg1 = function.args.getOrNull(1)
// TODO make it sealed when some time in the future
@Suppress("UNUSED_VARIABLE")
val _used_to_enforce_exhaustive_check: Unit = when (function.functionId) {
CloudFormationIntrinsicFunction.Ref -> {
if (function.args.size != 1 || arg0 !is CfnScalarValueNode) {
addProblem(function, message("reference.expects.one.string.argument"))
}
else {
val arg0WithoutVersionOrAlias = arg0.value.removeSuffix(".Version").removeSuffix(".Alias")
val resourceNodeWithoutVersionOrAlias = CloudFormationResolve.resolveResource(
parsed, arg0WithoutVersionOrAlias)
val resourceNodeParent = function.parentOfType<CfnResourceNode>(parsed)
val excluded = resourceNodeParent?.let { it.name?.value }?.let { listOf(it) } ?: emptyList()
when {
CloudFormationMetadataProvider.METADATA.predefinedParameters.contains(arg0.value) -> Unit
// Disgusting hack from serverless spec
// https://github.com/awslabs/serverless-application-model/blob/develop/versions/2016-10-31.md#referencing-lambda-version--alias-resources
resourceNodeWithoutVersionOrAlias != null && resourceNodeWithoutVersionOrAlias.isAwsServerlessFunctionWithAutoPublishAlias() &&
arg0WithoutVersionOrAlias != arg0.value -> {
addEntityReference(arg0, CloudFormationSection.ResourcesSingletonList, excludeFromCompletion = excluded,
referenceValue = arg0WithoutVersionOrAlias)
}
else -> {
addEntityReference(arg0, CloudFormationSection.ParametersAndResources, excludeFromCompletion = excluded)
}
}
}
Unit
}
CloudFormationIntrinsicFunction.Condition ->
if (function.args.size != 1 || arg0 !is CfnScalarValueNode) {
addProblem(function, message("condition.reference.expects.one.string.argument"))
}
else {
addEntityReference(arg0, CloudFormationSection.ConditionsSingletonList)
}
CloudFormationIntrinsicFunction.FnBase64 -> {
if (function.args.size != 1) {
addProblem(function, message("base64.reference.expects.1.argument"))
}
Unit
}
CloudFormationIntrinsicFunction.FnFindInMap -> {
if (function.args.size != 3) {
addProblem(function, message("findinmap.requires.3.arguments"))
}
else {
val mappingName = function.args[0]
val firstLevelKey = function.args[1]
val secondLevelKey = function.args[2]
if (mappingName is CfnScalarValueNode) {
addEntityReference(mappingName, CloudFormationSection.MappingsSingletonList)
val mapping = CloudFormationResolve.resolveMapping(parsed, mappingName.value)
if (mapping != null && firstLevelKey is CfnScalarValueNode) {
val firstLevelKeyPsiElement = parsed.getPsiElement(firstLevelKey)
addReference(CloudFormationMappingFirstLevelKeyReference(firstLevelKeyPsiElement, mappingName.value))
// TODO resolve possible values if first level key is an expression
if (secondLevelKey is CfnScalarValueNode) {
val secondLevelKeyPsiElement = parsed.getPsiElement(secondLevelKey)
addReference(CloudFormationMappingSecondLevelKeyReference(secondLevelKeyPsiElement, mappingName.value, firstLevelKey.value))
}
}
}
}
Unit
}
CloudFormationIntrinsicFunction.FnGetAtt -> {
val resourceName: String?
val attributeName: String?
if (function.args.size == 1 && arg0 is CfnScalarValueNode && function.name.value == CloudFormationIntrinsicFunction.FnGetAtt.shortForm) {
val dotIndex = arg0.value.indexOf('.')
if (dotIndex < 0) {
addProblem(function,
message("getattr.in.short.form.requires.argument.in.the.format.logicalnameofresource.attributename"))
resourceName = null
attributeName = null
}
else {
resourceName = arg0.value.substring(0, dotIndex)
attributeName = arg0.value.substring(dotIndex + 1)
}
}
else if (function.args.size == 2 && arg0 is CfnScalarValueNode) {
resourceName = arg0.value
attributeName = if (arg1 is CfnScalarValueNode) arg1.value else null
}
else {
addProblem(function,
message("getatt.requires.two.string.arguments.in.full.form.or.one.string.argument.in.short.form"))
resourceName = null
attributeName = null
}
if (resourceName != null) {
// TODO calculate exact text range and add it to ReferencesTest
val resourceNodeParent = function.parentOfType<CfnResourceNode>(parsed)
val excluded = resourceNodeParent?.let { it.name?.value }?.let { listOf(it) } ?: emptyList()
val resource = CloudFormationResolve.resolveResource(parsed, resourceName)
// From https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
// Role: ARN of an IAM role to use as this function's execution role. If omitted, a default role is created for this function.
// Here we handle this implicitly created role
val resourceNameWithoutRoleSuffix = resourceName.removeSuffix("Role")
val resourceWithoutRoleSuffix = CloudFormationResolve.resolveResource(parsed, resourceNameWithoutRoleSuffix)
if (resource == null && resourceWithoutRoleSuffix?.typeName == awsServerlessFunction.name) {
addEntityReference(arg0 as CfnScalarValueNode, CloudFormationSection.ResourcesSingletonList,
excludeFromCompletion = excluded, referenceValue = resourceNameWithoutRoleSuffix)
if (attributeName != "Arn") {
addProblem(
if (function.args.size == 1) arg0 else (arg1 ?: function),
message("implicit.iam.function.role.supports.only.arn.attribute"))
}
}
else {
addEntityReference(arg0 as CfnScalarValueNode, CloudFormationSection.ResourcesSingletonList, excludeFromCompletion = excluded,
referenceValue = resourceName)
if (attributeName != null) {
val typeName = resource?.typeName
if (typeName != null &&
!CloudFormationResourceType.isCustomResourceType(typeName) &&
!(CloudFormationResourceType.isCloudFormationStack(typeName) && attributeName.startsWith("Outputs.")) &&
!(CloudFormationResourceType.isServerlessApplication(typeName) && attributeName.startsWith("Outputs.")) &&
CloudFormationMetadataProvider.METADATA.findResourceType(typeName, parsed.root) != null) {
if (!resource.getAttributes(parsed.root).containsKey(attributeName)) {
addProblem(
if (function.args.size == 1) arg0 else (arg1 ?: function),
message("unknown.attribute.in.resource.type.0.1", typeName, attributeName))
}
}
}
}
}
Unit
}
CloudFormationIntrinsicFunction.FnGetAZs -> {
// TODO verify string against known regions
// TODO possibility for dataflow checks
if (function.args.size != 1) {
addProblem(function, message("getazs.expects.one.argument"))
}
Unit
}
CloudFormationIntrinsicFunction.FnCidr -> {
if (function.args.size != 2 && function.args.size != 3) {
addProblem(function, message("cidr.expects.two.or.three.arguments"))
}
Unit
}
CloudFormationIntrinsicFunction.FnImportValue -> {
if (function.args.size != 1) {
addProblem(function, message("importvalue.expects.one.argument"))
}
Unit
}
CloudFormationIntrinsicFunction.FnJoin -> {
if (function.args.size != 2) {
addProblem(function, message("join.expects.a.string.argument.and.an.array.argument"))
}
Unit
}
CloudFormationIntrinsicFunction.FnSplit -> {
if (function.args.size != 2) {
addProblem(function, message("split.expects.two.string.arguments"))
}
Unit
}
CloudFormationIntrinsicFunction.FnSelect -> {
if (function.args.size != 2) {
addProblem(function, message("select.expects.an.index.argument.and.an.array.argument"))
}
else if (arg0 is CfnScalarValueNode) {
try {
Integer.parseUnsignedInt(arg0.value)
}
catch (t: NumberFormatException) {
addProblem(function, message("select.index.should.be.a.valid.non.negative.number"))
}
}
Unit
}
CloudFormationIntrinsicFunction.FnSub -> {
// TODO Add references to substituted values in a string
// TODO Add references to the mapping
if (function.args.size != 1 && !(function.args.size == 2 && arg1 is CfnObjectValueNode)) {
addProblem(function, message("sub.expects.one.argument.plus.an.optional.value.map"))
}
Unit
}
// TODO Check context, valid only in boolean context
CloudFormationIntrinsicFunction.FnAnd, CloudFormationIntrinsicFunction.FnOr -> {
if (function.args.size < 2) {
addProblem(function, message("0.expects.at.least.2.arguments", function.functionId.shortForm))
}
Unit
}
CloudFormationIntrinsicFunction.FnEquals -> {
if (function.args.size != 2) {
addProblem(function, message("equals.expects.exactly.2.arguments"))
}
Unit
}
CloudFormationIntrinsicFunction.FnIf ->
if (function.args.size == 3) {
if (arg0 is CfnScalarValueNode) {
addEntityReference(arg0, CloudFormationSection.ConditionsSingletonList)
}
else {
addProblem(function, message("if.s.first.argument.should.be.a.condition.name"))
}
}
else {
addProblem(function, message("if.expects.exactly.3.arguments"))
}
CloudFormationIntrinsicFunction.FnNot -> {
if (function.args.size != 1) {
addProblem(function, message("not.expects.exactly.1.argument"))
}
Unit
}
}
super.function(function)
}