docs: function replaceRequireAndDefine(code, amdPackages, amdModules) {
    // Parse the code as an AST
    const ast = esprima.parseScript(code, {
      range: true
    });
  
    // Split the code into an array for easier substitutions
    const buffer = code.split('');
  
    // Walk thru the tree, find and replace our targets
    eswalk(ast, function(node) {
      if (!node) {
        return;
      }
  
      switch (node.type) {
        case 'CallExpression':
  
          if (!amdPackages || !amdModules) {
            // If not provided then we don't need to track them
            break;
          }
  
          // Collect the AMD modules
          // Looking for something like define(<name>, [<module1>, <module2>, ...], <function>)
          // This is the way ember defines a module
          if (node.callee.name === 'define') {
  
            if (node.arguments.length < 2 || node.arguments[1].type !== 'ArrayExpression' || !node.arguments[1].elements) {
              return;
            }
  
            node.arguments[1].elements.forEach(function(element) {
              if (element.type !== 'Literal') {
                return;
              }
  
              const isAMD = amdPackages.some(function(amdPackage) {
                if (typeof element.value !== 'string') {
                  return false;
                }
                return element.value.indexOf(amdPackage + '/') === 0 || element.value === amdPackage;
              });
  
              if (!isAMD) {
                return;
              }
  
              amdModules.add(element.value);
  
            });
  
            return;
          }
  
          // Dealing with ember-auto-import eval
          if (node.callee.name === 'eval' && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') {
            const evalCode = node.arguments[0].value;
            const evalCodeAfter = replaceRequireAndDefine(evalCode, amdPackages, amdModules);
            if (evalCode !== evalCodeAfter) {
              write(buffer, "eval(" + JSON.stringify(evalCodeAfter) + ");", node.range);
            }
          }
  
          return;
  
        case 'Identifier':
          {
            // We are dealing with code, make sure the node.name is not inherited from object 
            if (!identifiers.hasOwnProperty(node.name)) {
              return;
            }
  
            const identifier = identifiers[node.name];
            if (!identifier) {
              return;
            }
  
            write(buffer, identifier, node.range);
          }
          return;
  
        case 'Literal':
          {
            // We are dealing with code, make sure the node.name is not inherited from object 
            if (!literals.hasOwnProperty(node.value)) {
              return;
            }
  
            const literal = literals[node.value];
            if (!literal) {
              return;
            }
  
            write(buffer, literal, node.range);
          }
          return;
      }
    });
  
    // Return the new code
    return buffer.join('');
  }