function populateSymbolLayer(layer, data, opt, nested) {
  // get swap action on top level symbol
  if (!nested) {
    let swapAction = getSymbolSwapAction(layer, data)
    if (swapAction) {
      let symbolName = swapAction.params[0]
      let symbolToSwapWith = Layers.findSymbolMasterWithName(symbolName)
      if (symbolToSwapWith) {
        // convert to JS wrapped object and swap symbol master
        sketch.fromNative(layer).master = symbolToSwapWith
      }
    }
  }

  let overrides = null
  let symbolMaster = null

  // get overrides and symbol master
  // layer might be a symbol master if populating target symbol override
  if (Layers.isSymbolMaster(layer)) {
    overrides = NSMutableDictionary.alloc().init()
    symbolMaster = layer
  } else {
    // get overrides
    overrides = NSMutableDictionary.dictionaryWithDictionary(
      Layers.getSymbolOverrides(layer) || NSDictionary.alloc().init()
    )

    // get master for symbol instance
    symbolMaster = layer.symbolMaster()
  }

  // set root overrides in option to pass down in recursive calls
  if (!nested) {
    opt.rootOverrides = overrides
  }
  opt.rootOverrides = opt.rootOverrides || NSMutableDictionary.alloc().init()

  // populate text layers
  let textLayers = Layers.findLayersInLayer('*', false, Layers.TEXT, symbolMaster, false, null)
  textLayers.forEach(textLayer => {
    populateTextLayer(textLayer, data, {
      trimText: opt.trimText,
      insertEllipsis: opt.insertEllipsis,
      defaultSubstitute: opt.defaultSubstitute,
      overrides: overrides,
      rootOverrides: opt.rootOverrides
    })
  })

  // populate images
  let imageLayers = Layers.findLayersInLayer('{*}', false, Layers.SHAPE, symbolMaster, false, null)
  imageLayers = imageLayers.concat(
    Layers.findLayersInLayer('{*}', false, Layers.BITMAP, symbolMaster, false, null)
  )
  imageLayers.forEach(imageLayer => {
    populateImageLayer(imageLayer, data, {
      rootDir: opt.rootDir,
      overrides: overrides
    })
  })

  // populate symbols
  let symbolLayers = Layers.findLayersInLayer('*', false, Layers.SYMBOL, symbolMaster, false, null)
  symbolLayers.forEach(symbolLayer => {
    // get swap action on symbol and the symbol to swap with
    let swapAction = getSymbolSwapAction(symbolLayer, data)
    let symbolToSwapWith = null
    if (swapAction) {
      let symbolName = swapAction.params[0]
      symbolToSwapWith =
        symbolName === undefined ? 'None' : Layers.findSymbolMasterWithName(symbolName)
    }

    // swap nested symbol
    // swap action always takes priority
    if (symbolToSwapWith) {
      // get symbol id
      let idOfSymbolToSwapWith = symbolToSwapWith === 'None' ? '' : symbolToSwapWith.symbolID()

      // prepare nested root overrides
      let nestedRootOverrides = opt.rootOverrides.valueForKey(symbolLayer.objectID())
      if (!nestedRootOverrides) {
        nestedRootOverrides = NSMutableDictionary.alloc().init()
      }
      let nestedOpt = Object.assign({}, opt)
      nestedOpt.rootOverrides = nestedRootOverrides

      // get nested overrides
      let nestedOverrides =
        symbolToSwapWith !== 'None'
          ? populateSymbolLayer(symbolToSwapWith, data, nestedOpt, true)
          : nestedRootOverrides

      nestedOverrides.setValue_forKey(idOfSymbolToSwapWith, 'symbolID')
      overrides.setValue_forKey(nestedOverrides, symbolLayer.objectID())
    } else {
      // resolve nested symbol override
      if (
        opt.rootOverrides.valueForKey(symbolLayer.objectID()) &&
        opt.rootOverrides.valueForKey(symbolLayer.objectID()).valueForKey('symbolID')
      ) {
        // get overridden symbol ID
        let symbolID = String(
          opt.rootOverrides.valueForKey(symbolLayer.objectID()).valueForKey('symbolID')
        )

        // hide symbol
        if (!symbolID || !symbolID.length) {
          // get existing nested overrides
          let existingNestedOverrides = overrides.valueForKey(symbolLayer.objectID())
          if (!existingNestedOverrides) {
            existingNestedOverrides = NSDictionary.alloc().init()
          }
          let nestedOverrides = NSMutableDictionary.dictionaryWithDictionary(
            existingNestedOverrides
          )

          // set empty symbol override
          // no need to keep populating recursively
          nestedOverrides.setValue_forKey('', 'symbolID')
          overrides.setValue_forKey(nestedOverrides, symbolLayer.objectID())
        } else {
          let overriddenSymbolLayer = Layers.findSymbolMasterWithId(symbolID)

          // prepare nested root overrides
          let nestedRootOverrides = opt.rootOverrides.valueForKey(symbolLayer.objectID())
          if (!nestedRootOverrides) {
            nestedRootOverrides = NSMutableDictionary.alloc().init()
          }
          let nestedOpt = Object.assign({}, opt)
          nestedOpt.rootOverrides = nestedRootOverrides

          // get nested overrides
          let nestedOverrides = populateSymbolLayer(overriddenSymbolLayer, data, nestedOpt, true)
          nestedOverrides.setValue_forKey(symbolID, 'symbolID')

          // keep overrides if not overwritten
          Object.keys(nestedRootOverrides).forEach(key => {
            if (!nestedOverrides.objectForKey(key)) {
              nestedOverrides.setObject_forKey(nestedRootOverrides.objectForKey(key), key)
            }
          })

          overrides.setValue_forKey(nestedOverrides, symbolLayer.objectID())
        }
      }

      // nested symbol is not overridden
      else {
        // prepare nested root overrides
        let nestedRootOverrides = opt.rootOverrides.valueForKey(symbolLayer.objectID())
        if (!nestedRootOverrides) {
          nestedRootOverrides = NSMutableDictionary.alloc().init()
        }
        let nestedOpt = Object.assign({}, opt)
        nestedOpt.rootOverrides = nestedRootOverrides

        // get nested overrides
        let nestedOverrides = populateSymbolLayer(symbolLayer, data, nestedOpt, true)

        // keep overrides if not overwritten
        Object.keys(nestedRootOverrides).forEach(key => {
          if (!nestedOverrides.objectForKey(key)) {
            nestedOverrides.setObject_forKey(nestedRootOverrides.objectForKey(key), key)
          }
        })

        overrides.setValue_forKey(nestedOverrides, symbolLayer.objectID())
      }
    }
  })

  // set new overrides
  if (!nested) Layers.setSymbolOverrides(layer, overrides)

  // return overrides
  return overrides
}
Populates a symbol instance layer.

@param {MSSymbolInstance} layer
@param {Object} data
@param {Object} opt
@param {Boolean} nested

opt: {
  rootDir: {String},
  trimText: {Boolean},
  insertEllipsis: {Boolean},
  defaultSubstitute: {String}
}