Source: add-x-ms-enum-value-names.js

/**
 * @copyright Copyright 2019 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 * @module "openapi-transformers/add-x-ms-enum-value-names.js"
 */

import assert from 'node:assert';

import dotnetCase from '@kevinoid/dotnet-identifier-case';
import OpenApiTransformerBase from 'openapi-transformer-base';

assert.ok(/\p{L}/u.test('X'), 'Unicode property escapes are supported.');

function firstToLower(word) {
  if (!word) {
    return word;
  }

  // Lower-case 2-letter acronym together (as Autorest does)
  if (word.length === 2 && word === word.toUpperCase()) {
    return word.toLowerCase();
  }

  return word.charAt(0).toLowerCase() + word.slice(1);
}

function firstToUpper(word) {
  if (!word) {
    return word;
  }

  return word.charAt(0).toUpperCase() + word.slice(1);
}

/** Returns the enum member name which would be used by Autorest.
 * https://github.com/Azure/autorest.common/blob/54c21af/src/CodeNamer.cs#L178
 *
 * @private
 */
function autorestEnumMemberName(value) {
  const clean = value.replaceAll(/[^\p{L}\p{N}_-]/gu, '');
  const words = clean.split(/[_-]/);

  if (clean.charAt(0) === '_') {
    // eslint-disable-next-line prefer-template
    return '_' + words
      .map((word, i) => (i > 0 ? firstToUpper(word) : firstToLower(word)))
      .join('');
  }

  return words
    .map(firstToUpper)
    .join('');
}

function addXMsEnumValueNamesToSchema(schema) {
  if (!schema.enum || !schema['x-ms-enum']) {
    // Schema won't generate an enum
    return schema;
  }

  const xMsEnum = schema['x-ms-enum'];
  const xMsEnumValues = xMsEnum.values;
  if (xMsEnumValues
    && xMsEnumValues.every((v) => v.name !== null && v.name !== undefined)) {
    // Names already specified for all values
    return schema;
  }

  const values =
    Array.isArray(xMsEnumValues) ? xMsEnumValues.map((v) => v.value)
      : schema.enum;
  const stringValues = values.map(String);
  const autorestNames = stringValues.map(autorestEnumMemberName);
  const microsoftNames = stringValues.map(dotnetCase);

  let anyNameChanged = false;
  let newXMsEnumValues;
  if (Array.isArray(xMsEnumValues)) {
    newXMsEnumValues = xMsEnumValues.map((xMsEnumValue, i) => {
      if (xMsEnumValue.name !== null && xMsEnumValue.name !== undefined) {
        return xMsEnumValue;
      }

      const autorestName = autorestNames[i];
      const microsoftName = microsoftNames[i];
      if (autorestName === microsoftName) {
        return xMsEnumValue;
      }

      anyNameChanged = true;
      return {
        ...xMsEnumValue,
        name: microsoftName,
      };
    });
  } else {
    newXMsEnumValues = schema.enum.map((value, i) => {
      const autorestName = autorestNames[i];
      const microsoftName = microsoftNames[i];
      const newXMsEnumValue = { value };
      if (autorestName !== microsoftName) {
        anyNameChanged = true;
        newXMsEnumValue.name = microsoftName;
      }
      return newXMsEnumValue;
    });
  }

  if (!anyNameChanged) {
    return schema;
  }

  return {
    ...schema,
    'x-ms-enum': {
      ...xMsEnum,
      values: newXMsEnumValues,
    },
  };
}

/**
 * Transformer to add x-ms-enum.values.name to enums where the name generated by
 * Autorest differs from the Microsoft C# Capitalization Rules for Identifiers.
 *
 * https://github.com/Azure/autorest/issues/3328
 */
export default class AddXMsEnumValueNamesTransformer
  extends OpenApiTransformerBase {
  transformSchema(schema) {
    return addXMsEnumValueNamesToSchema(
      super.transformSchema(schema),
    );
  }

  transformParameter(parameter) {
    return addXMsEnumValueNamesToSchema(
      super.transformParameter(parameter),
    );
  }
}