UNPKG

@autorest/go

Version:
1,177 lines 42.2 kB
/*--------------------------------------------------------------------------------------------- * 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