docs: function processServices(swagger, models, options) {
    var param, name, i, j;
    var services = {};
    var minParamsForContainer = options.minParamsForContainer || 2;
    var sortParams = options.sortParams || 'desc';
    for (var url in swagger.paths) {
      var path = swagger.paths[url];
  	  var methodParameters = path.parameters;
      for (var method in path || {}) {
        var def = path[method];
        if (!def || method == 'parameters') {
          continue;
        }
        var tags = def.tags || [];
        var tag = tagName(tags.length == 0 ? null : tags[0], options);
        var descriptor = services[tag];
        if (descriptor == null) {
          var serviceClass = toClassName(tag);
          descriptor = {
            serviceName: tag,
            serviceClass: serviceClass + 'Service',
            serviceFile: toFileName(serviceClass) + '.service',
            operationIds: new Set(),
            serviceOperations: [],
          };
          services[tag] = descriptor;
        }
  
        var id = operationId(
          def.operationId,
          method,
          url,
          descriptor.operationIds
        );
  
        var parameters = def.parameters || [];
  
        if (methodParameters) {
          parameters = parameters.concat(methodParameters);
        }
  
        var paramsClass = null;
        var paramsClassComments = null;
        if (parameters.length >= minParamsForContainer) {
          paramsClass = id.charAt(0).toUpperCase() + id.substr(1) + 'Params';
          paramsClassComments = toComments('Parameters for ' + id, 1);
        }
  
        var operationParameters = [];
        for (var p = 0; p < parameters.length; p++) {
          param = parameters[p];
          if (param.$ref) {
            param = resolveRef(swagger, param.$ref);
          }
          var paramType;
          if (param.schema) {
            paramType = propertyType(param.schema);
          } else {
            paramType = propertyType(param);
          }
          var paramTypeNoNull = removeBrackets(paramType, true);
          var paramVar = toIdentifier(param.name);
          var paramDescriptor = {
            paramName: param.name,
            paramIn: param.in,
            paramVar: paramVar,
            paramFullVar: (paramsClass == null ? '' : 'params.') + paramVar,
            paramRequired: param.required === true || param.in === 'path',
            paramIsQuery: param.in === 'query',
            paramIsPath: param.in === 'path',
            paramIsHeader: param.in === 'header',
            paramIsBody: param.in === 'body',
            paramIsFormData: param.in === 'formData',
            paramIsArray: param.type === 'array',
            paramToJson: param.in === 'formData' && paramTypeNoNull !== 'Blob' &&
              paramTypeNoNull !== 'string',
            paramDescription: param.description,
            paramComments: toComments(param.description, 2),
            paramType: paramType,
            paramCollectionFormat: param.collectionFormat,
          };
          operationParameters.push(paramDescriptor);
        }
        operationParameters.sort((a, b) => {
          if (a.paramRequired && !b.paramRequired) return -1;
          if (!a.paramRequired && b.paramRequired) return 1;
          switch (sortParams) {
            case 'asc':
              return a.paramName > b.paramName ? 1 :
                a.paramName < b.paramName ? -1 : 0;
            case 'desc':
              return a.paramName > b.paramName ? -1 :
                a.paramName < b.paramName ? 1 : 0;
            default:
              return 0;
          }
        });
        if (operationParameters.length > 0) {
          operationParameters[operationParameters.length - 1].paramIsLast = true;
        }
        var operationResponses = processResponses(swagger, def, path, models);
        var resultType = operationResponses.resultType;
        var isMultipart = false;
        for (i = 0; i < operationParameters.length; i++) {
          if (operationParameters[i].paramIsFormData) {
            isMultipart = true;
            break;
          }
        }
        var docString = (def.description || '').trim();
        if (paramsClass == null) {
          for (i = 0; i < operationParameters.length; i++) {
            param = operationParameters[i];
            docString +=
              '\n@param ' + param.paramName + ' ' + param.paramDescription;
          }
        } else {
          docString +=
            '\n@param params The `' +
            descriptor.serviceClass +
            '.' +
            paramsClass +
            '` containing the following parameters:\n';
          for (i = 0; i < operationParameters.length; i++) {
            param = operationParameters[i];
            docString += '\n- `' + param.paramName + '`: ';
            var lines = (param.paramDescription || '').trim().split('\n');
            for (var l = 0; l < lines.length; l++) {
              var line = lines[l];
              if (line === '') {
                docString += '\n';
              } else {
                docString += (l == 0 ? '' : '  ') + line + '\n';
              }
            }
          }
        }
        if (operationResponses.resultDescription) {
          docString += '\n@return ' + operationResponses.resultDescription;
        }
        function getOperationName(string) {
          if (options.camelCase) return string.charAt(0).toLowerCase() + string.slice(1);
          else return string;
        }
        var operation = {
          operationName: getOperationName(id),
          operationParamsClass: paramsClass,
          operationParamsClassComments: paramsClassComments,
          operationMethod: method.toLocaleUpperCase(),
          operationPath: url,
          operationPathExpression:
            toPathExpression(operationParameters, paramsClass, url),
          operationResultType: resultType,
          operationHttpResponseType: '__StrictHttpResponse<' + resultType + '>',
          operationComments: toComments(docString, 1),
          operationParameters: operationParameters,
          operationResponses: operationResponses,
        };
        var modelResult = models[removeBrackets(resultType)];
        var actualType = resultType;
        if (modelResult && modelResult.modelIsSimple) {
          actualType = modelResult.modelSimpleType;
        }
        operation.operationIsMultipart = isMultipart;
        operation.operationIsVoid = actualType === 'void';
        operation.operationIsString = actualType === 'string';
        operation.operationIsNumber = actualType === 'number';
        operation.operationIsOther =
          !['void', 'number', 'boolean'].includes(actualType);
        operation.operationIsBoolean = actualType === 'boolean';
        operation.operationIsEnum = modelResult && modelResult.modelIsEnum;
        operation.operationIsObject = modelResult && modelResult.modelIsObject;
        operation.operationIsPrimitiveArray =
          !modelResult && (resultType.toString().includes('Array<') ||
            resultType.toString().includes('[]'));
        operation.operationIsFile = actualType === 'Blob';
        operation.operationResponseType =
          operation.operationIsFile ? 'blob' :
          operation.operationIsVoid ||
          operation.operationIsString ||
          operation.operationIsNumber ||
          operation.operationIsBoolean ||
          operation.operationIsEnum ?
            'text' : 'json';
        operation.operationIsUnknown = !(
          operation.operationIsVoid ||
          operation.operationIsString ||
          operation.operationIsNumber ||
          operation.operationIsBoolean ||
          operation.operationIsEnum ||
          operation.operationIsObject ||
          operation.operationIsFile ||
          operation.operationIsPrimitiveArray
        );
        descriptor.serviceOperations.push(operation);
      }
    }
  
    // Read the comments of each tag to use for service comments
    if (swagger.tags && swagger.tags.length > 0) {
      for (i = 0; i < swagger.tags.length; i++) {
        const tag = swagger.tags[i];
        const name = tagName(tag.name);
        const service = services[name];
        if (service && tag.description) {
          service.serviceComments = toComments(tag.description);
        }
      }
    }
  
    // Resolve the models used by each service
    for (name in services) {
      var service = services[name];
      var dependencies = new DependenciesResolver(models);
      var errorDependencies = new DependenciesResolver(models);
      for (i = 0; i < service.serviceOperations.length; i++) {
        var op = service.serviceOperations[i];
        for (var code in op.operationResponses) {
          var status = Number(code);
          var actualDeps = (status < 200 || status >= 300)
            ? errorDependencies : dependencies;
          var response = op.operationResponses[code];
          if (response.type) {
            var type = response.type;
            if (type && type.allTypes) {
              // This is an inline object. Append all types
              type.allTypes.forEach(t => actualDeps.add(t));
            } else {
              actualDeps.add(type);
            }
          }
        }
        for (j = 0; j < op.operationParameters.length; j++) {
          param = op.operationParameters[j];
          dependencies.add(param.paramType);
        }
      }
      service.serviceDependencies = dependencies.get();
      service.serviceErrorDependencies = errorDependencies.get();
    }
  
    return services;
  }