diff --git a/.changeset/popular-beans-wonder.md b/.changeset/popular-beans-wonder.md new file mode 100644 index 00000000000..65c9d801b78 --- /dev/null +++ b/.changeset/popular-beans-wonder.md @@ -0,0 +1,5 @@ +--- +'@firebase/api-documenter': minor +--- + +Add an option to sort functions by first param. (--sort-functions) diff --git a/repo-scripts/api-documenter/src/cli/MarkdownAction.ts b/repo-scripts/api-documenter/src/cli/MarkdownAction.ts index b5d4156c8c9..903f41ebf49 100644 --- a/repo-scripts/api-documenter/src/cli/MarkdownAction.ts +++ b/repo-scripts/api-documenter/src/cli/MarkdownAction.ts @@ -21,8 +21,10 @@ import { ApiDocumenterCommandLine } from './ApiDocumenterCommandLine'; import { BaseAction } from './BaseAction'; import { MarkdownDocumenter } from '../documenters/MarkdownDocumenter'; +import { CommandLineStringParameter } from '@rushstack/ts-command-line'; export class MarkdownAction extends BaseAction { + private _sortFunctions!: CommandLineStringParameter; public constructor(parser: ApiDocumenterCommandLine) { super({ actionName: 'markdown', @@ -33,10 +35,24 @@ export class MarkdownAction extends BaseAction { }); } + protected onDefineParameters(): void { + super.onDefineParameters(); + + this._sortFunctions = this.defineStringParameter({ + parameterLongName: '--sort-functions', + argumentName: 'PRIORITY_PARAMS', + description: + `Sorts functions tables and listings by first parameter. ` + + `Provide comma-separated strings for preferred params to be ` + + `ordered first. Alphabetical otherwise.` + }); + } + protected async onExecute(): Promise { // override const { apiModel, outputFolder, addFileNameSuffix, projectName } = this.buildApiModel(); + const sortFunctions: string = this._sortFunctions.value || ''; if (!projectName) { throw new Error('No project name provided. Use --project.'); @@ -47,7 +63,8 @@ export class MarkdownAction extends BaseAction { documenterConfig: undefined, outputFolder, addFileNameSuffix, - projectName + projectName, + sortFunctions }); markdownDocumenter.generateFiles(); } diff --git a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts index f8215d0a19a..2be5cb87143 100644 --- a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts +++ b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts @@ -93,6 +93,7 @@ export interface IMarkdownDocumenterOptions { outputFolder: string; addFileNameSuffix: boolean; projectName: string; + sortFunctions: string; } /** @@ -108,6 +109,7 @@ export class MarkdownDocumenter { private readonly _pluginLoader: PluginLoader; private readonly _addFileNameSuffix: boolean; private readonly _projectName: string; + private readonly _sortFunctions: string; public constructor(options: IMarkdownDocumenterOptions) { this._apiModel = options.apiModel; @@ -115,6 +117,7 @@ export class MarkdownDocumenter { this._outputFolder = options.outputFolder; this._addFileNameSuffix = options.addFileNameSuffix; this._projectName = options.projectName; + this._sortFunctions = options.sortFunctions; this._tsdocConfiguration = CustomDocNodes.configuration; this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); @@ -834,11 +837,13 @@ page_type: reference headerTitles: ['Enumeration', 'Description'] }); - const functionsTable: DocTable = new DocTable({ + const finalFunctionsTable: DocTable = new DocTable({ configuration, headerTitles: ['Function', 'Description'] }); + const functionsRowGroup: Record = {}; + const interfacesTable: DocTable = new DocTable({ configuration, headerTitles: ['Interface', 'Description'] @@ -859,7 +864,8 @@ page_type: reference headerTitles: ['Type Alias', 'Description'] }); - const functionsDefinitions: DocNode[] = []; + const functionsDefinitionsGroup: Record = {}; + const finalFunctionsDefinitions: DocNode[] = []; const variablesDefinitions: DocNode[] = []; const typeAliasDefinitions: DocNode[] = []; const enumsDefinitions: DocNode[] = []; @@ -899,10 +905,29 @@ page_type: reference break; case ApiItemKind.Function: - functionsTable.addRow(row); - functionsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); + /** + * If this option is set, group functions by first param. + * Organize using a map where the key is the first param. + */ + if (this._sortFunctions) { + const firstParam = (apiMember as ApiParameterListMixin) + .parameters[0] || { name: '' }; + if (!functionsRowGroup[firstParam.name]) { + functionsRowGroup[firstParam.name] = []; + } + if (!functionsDefinitionsGroup[firstParam.name]) { + functionsDefinitionsGroup[firstParam.name] = []; + } + functionsRowGroup[firstParam.name].push(row); + functionsDefinitionsGroup[firstParam.name].push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } else { + finalFunctionsTable.addRow(row); + finalFunctionsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } break; case ApiItemKind.TypeAlias: @@ -921,9 +946,59 @@ page_type: reference } } - if (functionsTable.rows.length > 0) { + /** + * Sort the functions groups by first param. If priority params were + * provided to --sort-functions, will put them first in the order + * given. + */ + if (this._sortFunctions) { + let priorityParams: string[] = []; + if (this._sortFunctions.includes(',')) { + priorityParams = this._sortFunctions.split(','); + } else { + priorityParams = [this._sortFunctions]; + } + const sortedFunctionsFirstParamKeys = Object.keys(functionsRowGroup).sort( + (a, b) => { + if (priorityParams.includes(a) && priorityParams.includes(b)) { + return priorityParams.indexOf(a) - priorityParams.indexOf(b); + } else if (priorityParams.includes(a)) { + return -1; + } else if (priorityParams.includes(b)) { + return 1; + } + return a.localeCompare(b); + } + ); + + for (const paramKey of sortedFunctionsFirstParamKeys) { + // Header for each group of functions grouped by first param. + // Doesn't make sense if there's only one group. + const headerText = paramKey ? `function(${paramKey}...)` : 'function()'; + const formattedHeaderText = `${headerText}`; + if (sortedFunctionsFirstParamKeys.length > 1) { + finalFunctionsTable.addRow( + new DocTableRow({ configuration }, [ + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: formattedHeaderText }) + ]) + ]) + ]) + ); + } + for (const functionsRow of functionsRowGroup[paramKey]) { + finalFunctionsTable.addRow(functionsRow); + } + for (const functionDefinition of functionsDefinitionsGroup[paramKey]) { + finalFunctionsDefinitions.push(functionDefinition); + } + } + } + + if (finalFunctionsTable.rows.length > 0) { output.push(new DocHeading({ configuration, title: 'Functions' })); - output.push(functionsTable); + output.push(finalFunctionsTable); } if (classesTable.rows.length > 0) { @@ -956,8 +1031,8 @@ page_type: reference output.push(typeAliasesTable); } - if (functionsDefinitions.length > 0) { - output.push(...functionsDefinitions); + if (finalFunctionsDefinitions.length > 0) { + output.push(...finalFunctionsDefinitions); } if (variablesDefinitions.length > 0) { diff --git a/scripts/docgen/docgen.ts b/scripts/docgen/docgen.ts index 5df01925338..c2f39b24469 100644 --- a/scripts/docgen/docgen.ts +++ b/scripts/docgen/docgen.ts @@ -39,6 +39,24 @@ https://github.com/firebase/firebase-js-sdk const tmpDir = `${projectRoot}/temp`; const EXCLUDED_PACKAGES = ['app-compat', 'util', 'rules-unit-testing']; +/** + * When ordering functions, will prioritize these first params at + * the top, in order. + */ +const PREFERRED_PARAMS = [ + 'app', + 'analyticsInstance', + 'appCheckInstance', + 'db', + 'firestore', + 'functionsInstance', + 'installations', + 'messaging', + 'performance', + 'remoteConfig', + 'storage' +]; + yargs .command( '$0', @@ -181,7 +199,9 @@ async function generateDocs( '--output', outputFolder, '--project', - 'js' + 'js', + '--sort-functions', + PREFERRED_PARAMS.join(',') ], { stdio: 'inherit' } );