@autorest/go
Version:
AutoRest Go Generator
1,177 lines • 42.2 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as go from '../../../codemodel.go/src/index.js';
import * as naming from '../../../naming.go/src/naming.js';
import { CodegenError } from './errors.js';
// variable to be used to determine comment length when calling comment from @azure-tools
export const commentLength = 120;
export const plainDateFormat = 'time.DateOnly';
export const RFC3339Format = 'time.RFC3339Nano';
export const RFC1123Format = 'time.RFC1123';
export const plainTimeFormat = 'time.TimeOnly';
// matches both current format (DO NOT EDIT on same line as Code generated)
// and legacy autorest format (DO NOT EDIT on a separate line)
export const doNotEditRegex = /^\/\/ Code generated .*( DO NOT EDIT\.$|[\s\S]*?^\/\/ DO NOT EDIT\.$)/m;
const defaultHeaderText = `Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. See License.txt in the project root for license information.
Code generated by Microsoft (R) Go Code Generator.`;
let overrideHeaderText;
/**
* sets the specified content to use as the header text comment per file.
*
* @param headerText the custom header text to use
*/
export function setCustomHeaderText(headerText) {
overrideHeaderText = headerText;
}
/**
* returns the common source-file preamble (license comment, package name etc)
*
* @param pkg the package to use in the package declaration
* @param doNotEdit when false the 'DO NOT EDIT' clause is omitted
* @returns the source file preamble
*/
export function contentPreamble(pkg, doNotEdit = true) {
const headerText = comment(overrideHeaderText ?? defaultHeaderText, '// ');
let text = headerText;
if (doNotEdit) {
// ensure tools recognize the file as generated according to
// https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source
text = text.replace(/^\/\/ Code generated .*\.$/m, '$& DO NOT EDIT.');
if (!text.match(doNotEditRegex)) {
text += '\n// Code generated by @autorest/go. DO NOT EDIT.';
}
}
else {
// remove the blurb about the changes being lost
text = text.replace(/^\/\/ Changes may cause incorrect behavior and will be lost if the code is regenerated\.$/m, '');
}
text += `\n\npackage ${go.getPackageName(pkg)}\n\n`;
return text;
}
// used to sort strings in ascending order
export function sortAscending(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
/**
* returns the parameter's type definition with a possible '*' prefix
*
* @param scope the package into which the type definition is being emitted
* @param param the parameter for which to emit the type definition
* @returns the parameter type definition text
*/
export function formatParameterTypeName(scope, param) {
let typeName;
let required;
switch (param.kind) {
case 'armClientOptions':
typeName = go.getTypeDeclaration(param, scope);
required = false;
break;
case 'clientOptions':
typeName = go.getTypeDeclaration(param, scope);
required = false;
break;
case 'paramGroup':
typeName = go.getTypeDeclaration(param, scope);
required = param.required;
break;
default:
typeName = go.getTypeDeclaration(param.type, scope);
required = param.byValue;
}
return required ? typeName : `*${typeName}`;
}
// sorts parameters by their required state, ordering required before optional
export function sortParametersByRequired(a, b) {
let aRequired = false;
let bRequired = false;
switch (a.kind) {
case 'paramGroup':
aRequired = a.required;
break;
default:
aRequired = go.isRequiredParameter(a.style);
break;
}
switch (b.kind) {
case 'paramGroup':
bRequired = b.required;
break;
default:
bRequired = go.isRequiredParameter(b.style);
break;
}
if (aRequired === bRequired) {
return 0;
}
else if (aRequired && !bRequired) {
return -1;
}
return 1;
}
/**
* sort client parameters based on the API design guidelines.
* the params array is sorted in place and returned to the caller.
*
* @param params the client parameters to sort
* @param type the Go code model type
* @returns the provided array after it's been sorted
*/
export function sortClientParameters(params, type) {
// ARM will never have uriParams but it will likely have a scalar path
// param for the subscription ID, so we sort by that instead.
const sortBy = type === 'azure-arm' ? 'pathScalarParam' : 'uriParam';
params.sort((a, b) => {
if (a.kind === sortBy || (a.kind === 'credentialParam' && b.kind !== sortBy)) {
// sortBy always comes first, followed by credential (if applicable)
return -1;
}
return 0;
});
return params;
}
// returns the parameters for the internal request creator method.
// e.g. "i int, s string"
export function getCreateRequestParametersSig(method) {
const methodParams = getMethodParameters(method);
const params = new Array();
params.push('ctx context.Context');
for (const methodParam of methodParams) {
let paramName = naming.uncapitalize(methodParam.name);
// when creating the method sig for fooCreateRequest, if the options type is empty
// or only contains the ResumeToken param use _ for the param name to quiet the linter
if (methodParam.kind === 'paramGroup' && (methodParam.params.length === 0 || (methodParam.params.length === 1 && methodParam.params[0].kind === 'resumeTokenParam'))) {
paramName = '_';
}
params.push(`${paramName} ${formatParameterTypeName(method.receiver.type.pkg, methodParam)}`);
}
return params.join(', ');
}
/**
* returns the parameter names for an operation (excludes the param types).
* e.g. "i, s"
*
* @param method the method containing the params
* @param optionsParam optional custom param name for the method options param
* @returns the text for the parameters
*/
export function getCreateRequestParameters(method, optionsParam) {
// NOTE: keep in sync with getCreateRequestParametersSig
const methodParams = getMethodParameters(method);
const params = new Array();
params.push('ctx');
for (let i = 0; i < methodParams.length; ++i) {
const methodParam = methodParams[i];
// the options param is always the last one
if (optionsParam && i === methodParams.length - 1) {
params.push(optionsParam);
}
else {
params.push(naming.uncapitalize(methodParam.name));
}
}
return params.join(', ');
}
// returns the complete collection of method parameters
export function getMethodParameters(method, paramsFilter) {
const params = new Array();
const paramGroups = new Array();
let methodParams = method.parameters;
if (paramsFilter) {
methodParams = paramsFilter(methodParams);
}
for (const param of methodParams) {
if (param.location === 'client') {
// client params are passed via the receiver
// must check before param group as client params can be grouped
continue;
}
else if (param.group) {
// param groups will be added after individual params
if (!paramGroups.includes(param.group)) {
paramGroups.push(param.group);
}
}
else if (param.type.kind === 'literal') {
// don't generate a parameter for a constant
// NOTE: this check must come last as non-required optional constants
// in header/query params get dumped into the optional params group
continue;
}
else {
params.push(param);
}
}
// move global optional params to the end of the slice
params.sort(sortParametersByRequired);
// add any parameter groups. optional groups go last
paramGroups.sort((a, b) => {
if (a.required === b.required) {
return 0;
}
if (a.required && !b.required) {
return -1;
}
return 1;
});
// add the optional param group last if it's not already in the list.
if (method.kind !== 'nextPageMethod') {
if (!paramGroups.find((gp) => gp.groupName === method.optionalParamsGroup.groupName)) {
paramGroups.push(method.optionalParamsGroup);
}
}
const combined = new Array();
for (const param of params) {
combined.push(param);
}
for (const paramGroup of paramGroups) {
combined.push(paramGroup);
}
return combined;
}
// returns the fully-qualified parameter name. this is usually just the name
// but will include the client or optional param group name prefix as required.
export function getParamName(param) {
let paramName = param.name;
// must check paramGroup first as client params can also be grouped
if (param.group) {
paramName = `${naming.uncapitalize(param.group.name)}.${naming.capitalize(paramName)}`;
}
if (param.location === 'client') {
paramName = `client.${paramName}`;
}
// client parameters with default values aren't emitted as pointer-to-type
if (!go.isRequiredParameter(param.style) && !(param.location === 'client' && go.isClientSideDefault(param.style)) && !param.byValue) {
paramName = `*${paramName}`;
}
return paramName;
}
// converts the Go code model encoding type to the type name in the standard library
export function formatBytesEncoding(enc) {
if (enc === 'URL') {
return 'RawURL';
}
return 'Std';
}
export function formatParamValue(param, imports, indent) {
let paramName = getParamName(param);
switch (param.kind) {
case 'formBodyCollectionParam':
case 'headerCollectionParam':
case 'pathCollectionParam':
case 'queryCollectionParam': {
if (param.collectionFormat === 'multi') {
throw new CodegenError('InternalError', 'multi collection format should have been previously handled');
}
const separator = getDelimiterForCollectionFormat(param.collectionFormat);
const emitConvertOver = function (paramName, format) {
const encodedVar = `encoded${naming.capitalize(paramName)}`;
let content = 'strings.Join(func() []string {\n';
content += `${indent.push().get()}${encodedVar} := make([]string, len(${paramName}))\n`;
content += `${indent.get()}for i := 0; i < len(${paramName}); i++ {\n`;
content += `${indent.push().get()}${encodedVar}[i] = ${format}\n`;
content += `${indent.pop().get()}}\n`;
content += `${indent.get()}return ${encodedVar}\n`;
content += `${indent.pop().get()}}(), "${separator}")`;
return content;
};
switch (param.type.elementType.kind) {
case 'encodedBytes':
imports.add('encoding/base64');
imports.add('strings');
return emitConvertOver(param.name, `base64.${formatBytesEncoding(param.type.elementType.encoding)}Encoding.EncodeToString(${param.name}[i])`);
case 'string':
imports.add('strings');
return `strings.Join(${paramName}, "${separator}")`;
case 'time':
imports.add('strings');
imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/datetime');
return emitConvertOver(param.name, `datetime.${param.type.elementType.format}(${param.name}[i]).String()`);
default:
imports.add('fmt');
imports.add('strings');
return `strings.Join(strings.Fields(strings.Trim(fmt.Sprint(${paramName}), "[]")), "${separator}")`;
}
}
}
return formatValue(paramName, param.type, imports);
}
export function getDelimiterForCollectionFormat(cf) {
switch (cf) {
case 'csv':
return ',';
case 'pipes':
return '|';
case 'ssv':
return ' ';
case 'tsv':
return '\\t';
default:
throw new CodegenError('InternalError', `unhandled CollectionFormat ${cf}`);
}
}
export function formatValue(paramName, type, imports, defef) {
// callers don't have enough context to know if paramName needs to be
// deferenced so we track that here when specified. note that not all
// cases will require paramName to be dereferenced.
let star = '';
if (defef === true) {
star = '*';
}
switch (type.kind) {
case 'constant':
if (type.type === 'string') {
return `string(${star}${paramName})`;
}
imports.add('fmt');
return `fmt.Sprintf("%v", ${star}${paramName})`;
case 'encodedBytes':
// a base-64 encoded value in string format
imports.add('encoding/base64');
return `base64.${formatBytesEncoding(type.encoding)}Encoding.EncodeToString(${paramName})`;
case 'etag':
return `string(${star}${paramName})`;
case 'literal':
// cannot use formatLiteralValue() since all values are treated as strings
switch (type.type.kind) {
case 'constantDef':
return type.type.name;
default:
return `"${type.literal}"`;
}
case 'scalar':
switch (type.type) {
case 'bool':
imports.add('strconv');
return `strconv.FormatBool(${star}${paramName})`;
case 'float32':
imports.add('strconv');
return `strconv.FormatFloat(float64(${star}${paramName}), 'f', -1, 32)`;
case 'float64':
imports.add('strconv');
return `strconv.FormatFloat(${star}${paramName}, 'f', -1, 64)`;
case 'int32':
imports.add('strconv');
return `strconv.FormatInt(int64(${star}${paramName}), 10)`;
case 'int64':
imports.add('strconv');
return `strconv.FormatInt(${star}${paramName}, 10)`;
default:
throw new CodegenError('InternalError', `unhandled scalar type ${type.type}`);
}
case 'time':
imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/datetime');
return `datetime.${type.format}(${star}${paramName}).String()`;
default:
return `${star}${paramName}`;
}
}
// returns the clientDefaultValue of the specified param.
// this is usually the value in quotes (i.e. a string) however
// it could also be a constant.
export function formatLiteralValue(value, withCast) {
switch (value.type.kind) {
case 'constant':
if (value.literal?.kind === 'constantValue') {
return value.literal.name;
}
// extensible enum with a value that's not one of the pre-defined ones.
// emit it as a cast to the enum type, e.g. NetworkAPIVersion("2026-04-15-preview").
if (value.type.type === 'string') {
return `${value.type.name}("${value.literal}")`;
}
return `${value.type.name}(${value.literal})`;
case 'constantDef':
return value.type.name;
case 'encodedBytes':
return value.literal;
case 'scalar':
if (!withCast) {
return `${value.literal}`;
}
switch (value.type.type) {
case 'float32':
return `float32(${value.literal})`;
case 'float64':
return `float64(${value.literal})`;
case 'int32':
return `int32(${value.literal})`;
case 'int64':
return `int64(${value.literal})`;
default:
return value.literal;
}
case 'string':
if (value.literal[0] === '"') {
// string is already quoted
return value.literal;
}
return `"${value.literal}"`;
case 'time':
return `"${value.literal}"`;
}
}
// returns true if at least one of the responses has a schema
export function hasSchemaResponse(method) {
switch (method.returns.result?.kind) {
case 'anyResult':
case 'modelResult':
case 'monomorphicResult':
case 'polymorphicResult':
return true;
default:
return false;
}
}
// returns the name of the response field within the response envelope
export function getResultFieldName(method) {
const result = method.returns.result;
if (!result) {
throw new CodegenError('InternalError', `missing result for method ${method.name}`);
}
switch (result.kind) {
case 'anyResult':
case 'binaryResult':
case 'headAsBooleanResult':
case 'monomorphicResult':
return result.fieldName;
case 'modelResult':
return result.modelType.name;
case 'polymorphicResult':
return result.interface.name;
}
}
export function formatStatusCodes(statusCodes) {
const asHTTPStatus = new Array();
for (const rawCode of statusCodes) {
asHTTPStatus.push(formatStatusCode(rawCode));
}
return asHTTPStatus.join(', ');
}
export function formatStatusCode(statusCode) {
switch (statusCode) {
// 1xx
case 100:
return 'http.StatusContinue';
case 101:
return 'http.StatusSwitchingProtocols';
case 102:
return 'http.StatusProcessing';
case 103:
return 'http.StatusEarlyHints';
// 2xx
case 200:
return 'http.StatusOK';
case 201:
return 'http.StatusCreated';
case 202:
return 'http.StatusAccepted';
case 203:
return 'http.StatusNonAuthoritativeInfo';
case 204:
return 'http.StatusNoContent';
case 205:
return 'http.StatusResetContent';
case 206:
return 'http.StatusPartialContent';
case 207:
return 'http.StatusMultiStatus';
case 208:
return 'http.StatusAlreadyReported';
case 226:
return 'http.StatusIMUsed';
// 3xx
case 300:
return 'http.StatusMultipleChoices';
case 301:
return 'http.StatusMovedPermanently';
case 302:
return 'http.StatusFound';
case 303:
return 'http.StatusSeeOther';
case 304:
return 'http.StatusNotModified';
case 305:
return 'http.StatusUseProxy';
case 307:
return 'http.StatusTemporaryRedirect';
// 4xx
case 400:
return 'http.StatusBadRequest';
case 401:
return 'http.StatusUnauthorized';
case 402:
return 'http.StatusPaymentRequired';
case 403:
return 'http.StatusForbidden';
case 404:
return 'http.StatusNotFound';
case 405:
return 'http.StatusMethodNotAllowed';
case 406:
return 'http.StatusNotAcceptable';
case 407:
return 'http.StatusProxyAuthRequired';
case 408:
return 'http.StatusRequestTimeout';
case 409:
return 'http.StatusConflict';
case 410:
return 'http.StatusGone';
case 411:
return 'http.StatusLengthRequired';
case 412:
return 'http.StatusPreconditionFailed';
case 413:
return 'http.StatusRequestEntityTooLarge';
case 414:
return 'http.StatusRequestURITooLong';
case 415:
return 'http.StatusUnsupportedMediaType';
case 416:
return 'http.StatusRequestedRangeNotSatisfiable';
case 417:
return 'http.StatusExpectationFailed';
case 418:
return 'http.StatusTeapot';
case 421:
return 'http.StatusMisdirectedRequest';
case 422:
return 'http.StatusUnprocessableEntity';
case 423:
return 'http.StatusLocked';
case 424:
return 'http.StatusFailedDependency';
case 425:
return 'http.StatusTooEarly';
case 426:
return 'http.StatusUpgradeRequired';
case 428:
return 'http.StatusPreconditionRequired';
case 429:
return 'http.StatusTooManyRequests';
case 431:
return 'http.StatusRequestHeaderFieldsTooLarge';
case 451:
return 'http.StatusUnavailableForLegalReasons';
// 5xx
case 500:
return 'http.StatusInternalServerError';
case 501:
return 'http.StatusNotImplemented';
case 502:
return 'http.StatusBadGateway';
case 503:
return 'http.StatusServiceUnavailable';
case 504:
return 'http.StatusGatewayTimeout ';
case 505:
return 'http.StatusHTTPVersionNotSupported';
case 506:
return 'http.StatusVariantAlsoNegotiates';
case 507:
return 'http.StatusInsufficientStorage';
case 508:
return 'http.StatusLoopDetected';
case 510:
return 'http.StatusNotExtended';
case 511:
return 'http.StatusNetworkAuthenticationRequired';
default:
throw new CodegenError('InternalError', `unhandled status code ${statusCode}`);
}
}
export function formatCommentAsBulletItem(prefix, docs) {
// first create the comment block. note that it can be multi-line depending on length:
//
// some comment first line
// and it finishes here.
let description = formatDocCommentWithPrefix(prefix, docs);
if (description.length === 0) {
return '';
}
// transform the above to look like this:
//
// - some comment first line
// and it finishes here.
const chunks = description.split('\n');
for (let i = 0; i < chunks.length; ++i) {
if (i === 0) {
chunks[i] = chunks[i].replace('// ', '// - ');
}
else {
chunks[i] = chunks[i].replace('// ', '// ');
}
}
return chunks.join('\n');
}
// conditionally returns a doc comment on an entity that requires a prefix.
// e.g.:
// {Prefix} - {docs.summary}
//
// {docs.description}
export function formatDocCommentWithPrefix(prefix, docs) {
if (!docs.summary && !docs.description) {
return '';
}
let docComment = '';
if (docs.summary) {
docComment = `${comment(`${prefix} - ${docs.summary}`, '//', undefined, commentLength)}\n`;
}
if (docs.description) {
let description = docs.description;
if (docs.summary) {
docComment += '//\n';
}
else {
// only apply the prefix to the description if there was no summary
description = `${prefix} - ${description}`;
}
docComment += `${comment(`${description}`, '//', undefined, commentLength)}\n`;
}
return docComment;
}
// conditionally returns a doc comment
// {docs.summary}
//
// {docs.description}
export function formatDocComment(docs) {
if (!docs.summary && !docs.description) {
return '';
}
let docComment = '';
if (docs.summary) {
docComment = `${comment(docs.summary, '//', undefined, commentLength)}\n`;
}
if (docs.description) {
if (docs.summary) {
docComment += '//\n';
}
docComment += `${comment(docs.description, '//', undefined, commentLength)}\n`;
}
return docComment;
}
export function getBitSizeForNumber(intSize) {
switch (intSize) {
case 'int8':
return '8';
case 'int16':
return '16';
case 'int32':
case 'float32':
return '32';
case 'int64':
case 'float64':
return '64';
}
}
// returns the underlying map/slice element/value type
// if item isn't a map or slice, item is returned
export function recursiveUnwrapMapSlice(item) {
switch (item.kind) {
case 'map':
return recursiveUnwrapMapSlice(item.valueType);
case 'slice':
return recursiveUnwrapMapSlice(item.elementType);
default:
return item;
}
}
/**
* returns a * character when byValue is false
*
* @param byValue indicates if the type is passed by value
* @returns a * or the empty string
*/
export function star(byValue) {
return byValue ? '' : '*';
}
/**
* returns the zero-value expression for the specific parameter
*
* @param param the param for which to create a zero value
* @returns the zero-value expression
*/
export function zeroValue(param) {
// even though API version params typically have a client-side default which makes
// them optional, the azcore.ClientOptions.APIVersion field isn't pointer-to-type.
if (go.isRequiredParameter(param.style) || go.isAPIVersionParameter(param)) {
switch (param.type.kind) {
case 'string':
return `""`;
default:
throw new CodegenError('InternalError', `unhandled zero-value kind ${param.type.kind}`);
}
}
// optional params are pointer-to-type
return 'nil';
}
// used by getSerDeFormat to cache results
const serDeFormatCache = new Map();
// returns the wire format for the named model.
// at present this assumes the formats to be mutually exclusive.
export function getSerDeFormat(model, pkg) {
let serDeFormat = serDeFormatCache.get(model.name);
if (serDeFormat) {
return serDeFormat;
}
// for model-only builds we assume the format to be JSON
if (pkg.clients.length === 0) {
return 'JSON';
}
// recursively walks the fields in model, updating serDeFormatCache with the model name and specified format
const recursiveWalkModelFields = function (type, serDeFormat) {
type = recursiveUnwrapMapSlice(type);
switch (type.kind) {
case 'interface':
recursiveWalkModelFields(type.rootType, serDeFormat);
for (const possibleType of type.possibleTypes) {
recursiveWalkModelFields(possibleType, serDeFormat);
}
break;
case 'model':
case 'polymorphicModel':
if (serDeFormatCache.has(type.name)) {
// we've already processed this type, don't do it again
return;
}
serDeFormatCache.set(type.name, serDeFormat);
for (const field of type.fields) {
const fieldType = recursiveUnwrapMapSlice(field.type);
recursiveWalkModelFields(fieldType, serDeFormat);
}
break;
}
};
// walk the methods, indexing the model formats
for (const client of pkg.clients) {
for (const method of client.methods) {
for (const param of method.parameters) {
if (param.kind !== 'bodyParam' || (param.bodyFormat !== 'JSON' && param.bodyFormat !== 'XML')) {
continue;
}
recursiveWalkModelFields(param.type, param.bodyFormat);
}
const resultType = method.returns.result;
switch (resultType?.kind) {
case 'anyResult':
if (resultType.format === 'JSON' || resultType.format === 'XML') {
for (const type of Object.values(resultType.httpStatusCodeType)) {
recursiveWalkModelFields(type, resultType.format);
}
}
break;
case 'modelResult':
recursiveWalkModelFields(resultType.modelType, resultType.format);
break;
case 'monomorphicResult':
if (resultType.format === 'JSON' || resultType.format === 'XML') {
recursiveWalkModelFields(resultType.monomorphicType, resultType.format);
}
break;
case 'polymorphicResult':
recursiveWalkModelFields(resultType.interface, resultType.format);
break;
}
}
}
serDeFormat = serDeFormatCache.get(model.name);
if (!serDeFormat) {
// if we get here there are two possibilities
// - we have a bug in the above indexing
// - the model type is unreferenced by any operation
//
// while the former is possible, the latter has the potential to be real.
// regardless of the cause, we will just assume the format to be JSON.
serDeFormat = 'JSON';
}
return serDeFormat;
}
// return combined client parameters for all the clients
export function getAllClientParameters(pkg, target) {
const allClientParams = new Array();
for (const client of pkg.clients) {
if (client.instance?.kind === 'constructable') {
for (const ctor of client.instance.constructors) {
for (const ctorParam of ctor.parameters) {
if (go.isAPIVersionParameter(ctorParam)) {
continue;
}
else if (allClientParams.find((param) => param.name === ctorParam.name)) {
continue;
}
allClientParams.push(ctorParam);
}
}
}
}
return sortClientParameters(allClientParams, target);
}
// returns common client parameters for all the clients
export function getCommonClientParameters(pkg, target) {
const paramCount = new Map();
let numClients = 0; // track client count since we might skip some
for (const client of pkg.clients) {
// special cases: some ARM clients always don't contain any parameters (OperationsClient will be depracated in the future)
if (target === 'azure-arm' && client.name.match(/^OperationsClient$/)) {
continue;
}
++numClients;
if (client.instance?.kind === 'constructable') {
for (const ctor of client.instance.constructors) {
for (const ctorParam of ctor.parameters) {
if (go.isAPIVersionParameter(ctorParam)) {
continue;
}
let entry = paramCount.get(ctorParam.name);
if (!entry) {
entry = { uses: 0, param: ctorParam };
paramCount.set(ctorParam.name, entry);
}
++entry.uses;
}
}
}
}
// for each param, if its usage count is equal to the
// number of clients, then it's common to all clients
const commonClientParams = new Array();
for (const entry of paramCount.values()) {
if (entry.uses === numClients) {
commonClientParams.push(entry.param);
}
}
return sortClientParameters(commonClientParams, target);
}
/**
* enumerates method parameters and returns them based on kinds
*
* @param method the method containing the parameters to group
* @returns the groups of parameters
*/
export function getMethodParamGroups(method) {
let bodyParam;
const encodedQueryParams = new Array();
const formBodyParams = new Array();
const headerParams = new Array();
const multipartBodyParams = new Array();
const pathParams = new Array();
const partialBodyParams = new Array();
const unencodedQueryParams = new Array();
for (const param of method.parameters) {
switch (param.kind) {
case 'bodyParam':
bodyParam = param;
break;
case 'formBodyCollectionParam':
case 'formBodyScalarParam':
formBodyParams.push(param);
break;
case 'headerCollectionParam':
case 'headerMapParam':
case 'headerScalarParam':
headerParams.push(param);
break;
case 'multipartFormBodyParam':
multipartBodyParams.push(param);
break;
case 'partialBodyParam':
partialBodyParams.push(param);
break;
case 'pathCollectionParam':
case 'pathScalarParam':
pathParams.push(param);
break;
case 'queryCollectionParam':
case 'queryScalarParam':
if (param.isEncoded) {
encodedQueryParams.push(param);
}
else {
unencodedQueryParams.push(param);
}
break;
}
}
return {
bodyParam,
encodedQueryParams,
formBodyParams,
headerParams,
multipartBodyParams,
pathParams,
partialBodyParams,
unencodedQueryParams,
};
}
/** helper for managing indentation levels */
export class Indentation {
level;
constructor(level) {
if (level !== undefined) {
this.level = level;
}
else {
// default to one level of indentation
this.level = 1;
}
}
/**
* returns tabs for the current indentation level
*
* @returns a string with the current indentation level
*/
get() {
let indent = '';
for (let i = 0; i < this.level; ++i) {
indent += '\t';
}
return indent;
}
/**
* increments the indentation level
*
* @returns this indentation instance
*/
push() {
++this.level;
return this;
}
/**
* decrements the indentation level
*
* @returns this indentation instance
*/
pop() {
--this.level;
if (this.level < 0) {
throw new CodegenError('InternalError', 'indentation stack underflow');
}
return this;
}
}
/**
* constructs an if block (can expand to include else if as necessary)
*
* @param indent the current indentation helper in scope
* @param ifBlock the if block definition
* @param elseBlock optional else block definition
* @returns the text for the if block
*/
export function buildIfBlock(indent, ifBlock, elseBlock) {
let body = `if ${ifBlock.condition} {\n`;
body += ifBlock.body(indent.push());
body += `${indent.pop().get()}}`;
if (elseBlock) {
body += ' else {\n';
body += elseBlock.body(indent.push());
body += `${indent.pop().get()}}`;
}
return body;
}
/**
* constructs an "if err != nil { return something }" block
*
* @param indent the current indentation helper in scope
* @param errVar the name of the error variable used in the condition
* @param returns optional value besides the error to return from the control block
* @returns the text for the error check block
*/
export function buildErrCheck(indent, errVar, returns) {
let body = `if ${errVar} != nil {\n`;
body += `${indent.push().get()}return ${returns ? `${returns}, ` : ''}${errVar}\n`;
body += `${indent.pop().get()}}`;
return body;
}
/**
* splits a token credential scope into the audience and scope.
* e.g. "https://monitor.azure.com/.default" is split into
* - audience: https://monitor.azure.com
* - scope: /.default
* @param scope
*/
export function splitScope(scope) {
const scopeSplit = scope.lastIndexOf('/');
return {
audience: scope.substring(0, scopeSplit),
scope: scope.substring(scopeSplit),
};
}
/** returns true if the specified method is internal */
export function isMethodInternal(method) {
return !!method.name.match(/^[a-z]{1}/);
}
/**
* returns true if the provided client has no exported methods
*
* @param client the client containing the methods
* @returns true if all client methods are internal
*/
export function clientHasNoExportedMethods(client) {
for (const method of client.methods) {
if (!isMethodInternal(method)) {
return false;
}
}
return true;
}
/**
* provides access to the next link field, handling nested fields as required.
* e.g. var.Foo.Bar
*
* @param strategy the next link pageable strategy
* @returns the complete path to the next link
*/
export function buildNextLinkPath(strategy) {
return strategy.nextLinkPath.map((segment) => segment.name).join('.');
}
// the following was copied from @azure-tools/codegen as it's being deprecated
const ones = [
'',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
'sixteen',
'seventeen',
'eighteen',
'nineteen',
];
const tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
const magvalues = [10 ** 3, 10 ** 6, 10 ** 9, 10 ** 12, 10 ** 15, 10 ** 18, 10 ** 21, 10 ** 24, 10 ** 27];
const magnitude = ['thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'septillion', 'octillion'];
function* convert(num) {
if (!num) {
yield 'zero';
return;
}
if (num > 1e30) {
yield 'lots';
return;
}
if (num > 999) {
for (let i = magvalues.length; i > -1; i--) {
const c = magvalues[i];
if (num > c) {
yield* convert(Math.floor(num / c));
yield magnitude[i];
num = num % c;
}
}
}
if (num > 99) {
yield ones[Math.floor(num / 100)];
yield 'hundred';
num %= 100;
}
if (num > 19) {
yield tens[Math.floor(num / 10)];
num %= 10;
}
if (num) {
yield ones[num];
}
}
function deconstruct(identifier) {
if (Array.isArray(identifier)) {
return identifier.flatMap(deconstruct);
}
return `${identifier}`
.replace(/([a-z]+)([A-Z])/g, '$1 $2')
.replace(/(\d+)([a-z|A-Z]+)/g, '$1 $2')
.replace(/\b([A-Z]+)([A-Z])([a-z])/, '$1 $2$3')
.split(/[\W|_]+/)
.map((each) => each.toLowerCase());
}
function fixLeadingNumber(identifier) {
if (identifier.length > 0 && /^\d+/.exec(identifier[0])) {
return [...convert(parseInt(identifier[0])), ...identifier.slice(1)];
}
return identifier;
}
function isEqual(s1, s2) {
// when s2 is undefined and s1 is the string 'undefined', it returns 0, making this true.
// To prevent that, first we need to check if s2 is undefined.
return s2 !== undefined && !!s1 && !s1.localeCompare(s2, undefined, { sensitivity: 'base' });
}
function removeSequentialDuplicates(identifier) {
const ids = [...identifier].filter((each) => !!each);
for (let i = 0; i < ids.length; i++) {
while (isEqual(ids[i], ids[i - 1])) {
ids.splice(i, 1);
}
while (isEqual(ids[i], ids[i - 2]) && isEqual(ids[i + 1], ids[i - 1])) {
ids.splice(i, 2);
}
}
return ids;
}
export function camelCase(identifier) {
if (typeof identifier === 'string') {
return camelCase(fixLeadingNumber(deconstruct(identifier)));
}
switch (identifier.length) {
case 0:
return '';
case 1:
return naming.uncapitalize(identifier[0]);
}
return `${naming.uncapitalize(identifier[0])}${pascalCase(identifier.slice(1))}`;
}
export function pascalCase(identifier, removeDuplicates = true) {
return identifier === undefined
? ''
: typeof identifier === 'string'
? pascalCase(fixLeadingNumber(deconstruct(identifier)), removeDuplicates)
: (removeDuplicates ? [...removeSequentialDuplicates(identifier)] : identifier).map((each) => naming.capitalize(each)).join('');
}
function indent(content, factor = 1) {
const i = '\t'.repeat(factor);
content = i + content.trim().replace(/\r\n/g, '\n');
return content.split(/\n/g).join(`\n${i}`);
}
const lineCommentPrefix = '//';
export function comment(content, prefix = lineCommentPrefix, factor = 0, maxLength = 120) {
const result = new Array();
let line = '';
prefix = indent(prefix, factor);
content = content.trim();
if (content) {
for (const word of content.replace(/\n+/g, ' » ').split(/\s+/g)) {
if (word === '»') {
result.push(line);
line = prefix;
continue;
}
if (maxLength < line.length) {
result.push(line);
line = '';
}
if (!line) {
line = prefix;
}
line += ` ${word}`;
}
if (line) {
result.push(line);
}
return result.join('\n');
}
return '';
}
// end ports from @azure-tools/codegen
//# sourceMappingURL=helpers.js.map