override fun function()

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)
  }