diff --git a/utils/doclint/generateDotnetApi.js b/utils/doclint/generateDotnetApi.js index 333f9f9139..634f96b9ee 100644 --- a/utils/doclint/generateDotnetApi.js +++ b/utils/doclint/generateDotnetApi.js @@ -200,7 +200,7 @@ const customTypeNames = new Map([ out.push(`\t${escapedName},`); }); }, enumsDir)); - + if (process.argv[3] !== "--skip-format") { // run the formatting tool for .net, to ensure the files are prepped execSync(`dotnet format -f "${typesDir}" --include-generated --fix-whitespace`); @@ -294,16 +294,16 @@ function renderMember(member, parent, out) { let propertyOrigin = member.name; if (member.type.expression === '[string]|[float]') propertyOrigin = `${member.name}String`; - if(!member.clazz) + if (!member.clazz) output(`[JsonPropertyName("${propertyOrigin}")]`) if (parent && member && member.name === 'children') { // this is a special hack for Accessibility console.warn(`children property found in ${parent.name}, assuming array.`); type = `IEnumerable<${parent.name}>`; } - if(!type.endsWith('?') && !member.required && nullableTypes.includes(type)) + if (!type.endsWith('?') && !member.required && nullableTypes.includes(type)) type = `${type}?`; - if(member.clazz) + if (member.clazz) output(`public ${type} ${name} { get; }`); else output(`public ${type} ${name} { get; set; }`); @@ -484,19 +484,28 @@ function renderMethod(member, parent, output, name) { // render args let args = []; + let explodedArgs = []; + let argTypeMap = new Map([]); /** * * @param {string} innerArgType * @param {string} innerArgName * @param {Documentation.Member} argument + * @param {boolean} isExploded */ - const pushArg = (innerArgType, innerArgName, argument) => { + const pushArg = (innerArgType, innerArgName, argument, isExploded = false) => { let isNullable = nullableTypes.includes(innerArgType); - const requiredPrefix = argument.required ? "" : isNullable ? "?" : ""; - const requiredSuffix = argument.required ? "" : " = default"; - args.push(`${innerArgType}${requiredPrefix} ${innerArgName}${requiredSuffix}`); + const requiredPrefix = (argument.required || isExploded) ? "" : isNullable ? "?" : ""; + const requiredSuffix = (argument.required || isExploded) ? "" : " = default"; + var push = `${innerArgType}${requiredPrefix} ${innerArgName}${requiredSuffix}`; + if (isExploded) + explodedArgs.push(push) + else + args.push(push); + argTypeMap.set(push, innerArgName); }; + let parseArg = (/** @type {Documentation.Member} */ arg) => { if (arg.name === "options") { arg.type.properties.forEach(parseArg); @@ -539,11 +548,13 @@ function renderMethod(member, parent, output, name) { let argDocumentation = XmlDoc.renderTextOnly(arg.spec, maxDocumentationColumnWidth); for (const newArg of translatedArguments) { - const sanitizedArgName = newArg.match(/(?<=^[\s"']*)(\w+)/g, '')[0] || newArg; + let nonGenericType = newArg.replace(/\IEnumerable<(.*)\>/g, (m, v) => 'Enumerable' + v[0].toUpperCase() + v.substring(1)) + const sanitizedArgName = nonGenericType.match(/(?<=^[\s"']*)(\w+)/g, '')[0] || nonGenericType; const newArgName = `${argName}${sanitizedArgName[0].toUpperCase() + sanitizedArgName.substring(1)}`; - pushArg(newArg, newArgName, arg); + pushArg(newArg, newArgName, arg, true); // push the exploded arg addParamsDoc(newArgName, argDocumentation); } + args.push(arg.required ? 'EXPLODED_ARG' : 'OPTIONAL_EXPLODED_ARG'); return; } @@ -561,8 +572,7 @@ function renderMethod(member, parent, output, name) { .sort((a, b) => b.alias === 'options' ? -1 : 0) //move options to the back to the arguments list .forEach(parseArg); - output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth)); - paramDocs.forEach((val, ind) => { + let printArgDoc = function (val, ind) { if (val && val.length === 1) output(`/// ${val}`); else { @@ -570,8 +580,56 @@ function renderMethod(member, parent, output, name) { output(val.map(l => `/// ${l}`)); output(`/// `); } - }); - output(`${type} ${name}(${args.join(', ')});`); + } + + let getArgType = function (argType) { + var type = argTypeMap.get(argType); + return type; + } + + if (!explodedArgs.length) { + output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth)); + paramDocs.forEach((val, ind) => printArgDoc(val, ind)); + output(`${type} ${name}(${args.join(', ')});`); + } else { + let containsOptionalExplodedArgs = false; + explodedArgs.forEach((explodedArg, argIndex) => { + output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth)); + let overloadedArgs = []; + for (var i = 0; i < args.length; i++) { + let arg = args[i]; + if (arg === 'EXPLODED_ARG' || arg === 'OPTIONAL_EXPLODED_ARG') { + containsOptionalExplodedArgs = arg === 'OPTIONAL_EXPLODED_ARG'; + let argType = getArgType(explodedArg); + printArgDoc(paramDocs.get(argType), argType); + overloadedArgs.push(explodedArg); + } else { + let argType = getArgType(arg); + printArgDoc(paramDocs.get(argType), argType); + overloadedArgs.push(arg); + } + } + output(`${type} ${name}(${overloadedArgs.join(', ')});`); + if (argIndex < explodedArgs.length - 1) + output(``); // output a special blank line + }); + + // If the exploded union arguments are optional, we also output a special + // signature, to help prevent compilation errors with ambigious overloads. + // That particular overload only contains the required arguments, or rather + // contains all the arguments *except* the exploded ones. + if (containsOptionalExplodedArgs) { + var filteredArgs = args.filter(x => x !== 'OPTIONAL_EXPLODED_ARG'); + output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth)); + filteredArgs.forEach((arg) => { + if (arg === 'EXPLODED_ARG') + throw new Error(`Unsupported required union arg combined an optional union inside ${member.name}`); + let argType = getArgType(arg); + printArgDoc(paramDocs.get(argType), argType); + }); + output(`${type} ${name}(${filteredArgs.join(', ')});`); + } + } } /** @@ -652,9 +710,7 @@ function translateType(type, parent, generateNameCallback = t => t.name) { } else if (type.union.length == 2 && type.union[1].name === 'Array' && type.union[1].templates[0].name === type.union[0].name) return `IEnumerable<${type.union[0].name}>`; // an example of this is [string]|[Array]<[string]> else if (type.union[0].name === 'path') - // we don't support path, but we know it's usually an object on the other end, and we expect - // the dotnet folks to use [NameOfTheObject].LoadFromPath(); method which we can provide separately - return translateType(type.union[1], parent, generateNameCallback); + return null; else if (type.expression === '[float]|"raf"') return `Polling`; // hardcoded because there's no other way to denote this