diff --git a/vendor/magento/module-offline-shipping/Model/SalesRule/Calculator.php b/vendor/magento/module-offline-shipping/Model/SalesRule/Calculator.php
index dfc22896d25..ee37b1a7c79 100644
--- a/vendor/magento/module-offline-shipping/Model/SalesRule/Calculator.php
+++ b/vendor/magento/module-offline-shipping/Model/SalesRule/Calculator.php
@@ -4,14 +4,12 @@
  * See COPYING.txt for license details.
  */
 
-/**
- * Shopping Cart Rule data model
- *
- * @author      Magento Core Team <core@magentocommerce.com>
- */
 namespace Magento\OfflineShipping\Model\SalesRule;
 
+use Magento\Quote\Model\Quote\Address;
+use Magento\Quote\Model\Quote\Item\AbstractItem;
 use Magento\SalesRule\Model\Validator;
+use \Magento\SalesRule\Model\Rule as SalesRule;
 
 /**
  * @api
@@ -24,38 +22,110 @@ class Calculator extends Validator
      * This process not affect information about applied rules, coupon code etc.
      * This information will be added during discount amounts processing
      *
-     * @param   \Magento\Quote\Model\Quote\Item\AbstractItem $item
+     * @param AbstractItem $item
+     *
      * @return  \Magento\OfflineShipping\Model\SalesRule\Calculator
+     *
+     * @throws \Zend_Db_Select_Exception
      */
-    public function processFreeShipping(\Magento\Quote\Model\Quote\Item\AbstractItem $item)
+    public function processFreeShipping(AbstractItem $item)
     {
         $address = $item->getAddress();
-        $item->setFreeShipping(false);
+        $this->resetFreeShipping($item);
 
+        /* @var $rule SalesRule */
         foreach ($this->getRules($address) as $rule) {
-            /* @var $rule \Magento\SalesRule\Model\Rule */
-            if (!$this->validatorUtility->canProcessRule($rule, $address)) {
+            if (!$this->canApplyRuleToItem($rule, $address, $item)) {
                 continue;
             }
 
-            if (!$rule->getActions()->validate($item)) {
-                continue;
-            }
+            $this->applyFreeShippingRule($rule, $address, $item);
 
-            switch ($rule->getSimpleFreeShipping()) {
-                case Rule::FREE_SHIPPING_ITEM:
-                    $item->setFreeShipping($rule->getDiscountQty() ? $rule->getDiscountQty() : true);
-                    $item->setFreeShippingMethod($item->getAddress()->getShippingMethod());
-                    break;
-
-                case Rule::FREE_SHIPPING_ADDRESS:
-                    $address->setFreeShipping(true);
-                    break;
-            }
             if ($rule->getStopRulesProcessing()) {
                 break;
             }
         }
+
         return $this;
     }
+
+    /**
+     * Validates rule for item
+     *
+     * @param SalesRule $rule
+     * @param Address $address
+     * @param AbstractItem $item
+     *
+     * @return bool
+     */
+    private function canApplyRuleToItem(SalesRule $rule, Address $address, AbstractItem $item): bool
+    {
+        if (!$this->validatorUtility->canProcessRule($rule, $address)) {
+            return false;
+        }
+
+        return (bool) $rule->getActions()->validate($item);
+    }
+
+    /**
+     * Apply free shipping rule for cart item
+     *
+     * @param SalesRule $rule
+     * @param Address $address
+     * @param AbstractItem $item
+     *
+     * @return void
+     */
+    private function applyFreeShippingRule(SalesRule $rule, Address $address, AbstractItem $item): void
+    {
+        $type = (int) $rule->getSimpleFreeShipping();
+
+        if ($type === Rule::FREE_SHIPPING_ITEM) {
+            $this->applyItemFreeShipping($rule, $item);
+            return;
+        }
+
+        if ($type === Rule::FREE_SHIPPING_ADDRESS) {
+            $address->setFreeShipping(true);
+        }
+    }
+
+    /**
+     * Free shipping can be applied to parent or child items
+     *
+     * @param SalesRule $rule
+     * @param AbstractItem $item
+     *
+     * @return void
+     */
+    private function applyItemFreeShipping(SalesRule $rule, AbstractItem $item): void
+    {
+        $method = $item->getAddress()->getShippingMethod();
+        $item->setFreeShipping($rule->getDiscountQty() ? $rule->getDiscountQty() : true);
+        $item->setFreeShippingMethod($method);
+
+        if ($item->getHasChildren() && $item->isShipSeparately()) {
+            foreach ($item->getChildren() as $child) {
+                $child->setFreeShipping($rule->getDiscountQty() ? $rule->getDiscountQty() : true);
+                $child->setFreeShippingMethod($method);
+            }
+        }
+    }
+
+    /**
+     * Reset free shipping for item
+     *
+     * @param AbstractItem $item
+     *
+     * @return void
+     */
+    private function resetFreeShipping(AbstractItem $item): void
+    {
+        $item->setFreeShipping(false);
+        if ($item->getHasChildren() && $item->isShipSeparately()) {
+            foreach ($item->getChildren() as $child) {
+                $child->setFreeShipping(false);
+            }
+        }
+    }
 }
