function createExternAssignment(name, node, alwaysIncludeConstructor) {
  switch (node.type) {
    case 'ClassExpression': {
      // Example code: foo.bar = class bar2 extends foo.baz { /* ... */ };
      // Example node: {
      //   id: { name: 'bar' },   // or null
      //   superClass: { type: 'MemberExpression', ... },  // or null
      //   body: { body: [ ... ] },
      // }
      let classString = ' = class ';
      if (node.id) {
        classString += getIdentifierString(node.id) + ' ';
      }
      if (node.superClass) {
        classString += 'extends ' + getIdentifierString(node.superClass) + ' ';
      }
      classString += '{\n';
      for (const member of node.body.body) {
        const comment = getLeadingBlockComment(member);

        if (EXPORT_REGEX.test(comment)) {
          // This has an export annotation, so fall through and generate
          // externs.
        } else {
          // If there's no export annotation, we may make an exception for the
          // constructor in some situations.
          if (member.key.name == 'constructor' && alwaysIncludeConstructor) {
            // Fall through and generate externs.
          } else {
            // Skip extern generation.
            continue;
          }
        }

        assert.equal(
            member.type, 'MethodDefinition',
            'Unexpected exported member type in exported class!');

        classString += createExternMethod(member) + '\n';
      }
      classString += '}';
      return classString;
    }

    case 'ArrowFunctionExpression':
    case 'FunctionExpression': {
      // Example code: foo.square = function(x) { return x * x; };
      // Example node: { params: [ { type: 'Identifier', name: 'x' } ] }
      const params = getFunctionParameters(node);
      return ' = function(' + params.join(', ') + ') {}';
    }

    case 'ObjectExpression': {
      // Example code: foo.Bar = { 'ABC': 1, DEF: 2 };
      // Example node: {
      //   properties: [ {
      //     kind: 'init',
      //     key: { type: 'Literal', value: 'ABC' }
      //     value: { type: 'Literal', value: 1 }
      //   }, {
      //     kind: 'init',
      //     key: { type: 'Identifier', name: 'DEF' }
      //     value: { type: 'Literal', value: 2 }
      //   } ]
      // }
      const propertyStrings = node.properties.map((prop) => {
        assert.equal(prop.kind, 'init');
        assert(prop.key.type == 'Literal' || prop.key.type == 'Identifier');
        // Literal indicates a quoted name in the source, while Identifier is
        // an unquoted name.  In the case of Literal, key.raw gets us the
        // unquoted name, we end up with an unquoted name in both cases.
        const name = prop.key.type == 'Literal' ? prop.key.raw : prop.key.name;
        assert.equal(prop.value.type, 'Literal');
        return '  ' + name + ': ' + prop.value.raw;
      });
      return ' = {\n' + propertyStrings.join(',\n') + '\n}';
    }

    case 'Identifier':
      // Example code: /** @const {string} @export */ foo.version = VERSION;
      // Example extern: /** @const {string} */ foo.version;
      return '';

    case 'Literal':
      // Example code: /** @const {string} @export */ foo.version = 'v1.0.0';
      // Example extern: /** @const {string} */ foo.version;
      return '';

    case 'BinaryExpression':
      // Example code: /** @const {string} @export */ foo.version = 'a' + 'b';
      // Example extern: /** @const {string} */ foo.version;
      return '';

    default:
      assert.fail('Unexpected export type: ' + node.type);
      return '';  // Shouldn't be hit, but linter wants a return statement.
  }
}
@param {string} name The name of the thing we are assigning.
@param {ASTNode} node An assignment node from the abstract syntax tree.
@param {boolean} alwaysIncludeConstructor Include the constructor of a class
  expression, even if there is no export annotation.
@return {string} The assignment part of the extern string for this node.