Path: blob/main/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts
3520 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { makeArray } from './utils';67export type SpecMixin =8| Fig.Subcommand9| ((currentSpec: Fig.Subcommand, context: Fig.ShellContext) => Fig.Subcommand);1011type NamedObject = { name: Fig.SingleOrArray<string> };1213const concatArrays = <T>(a: T[] | undefined, b: T[] | undefined): T[] | undefined =>14a && b ? [...a, ...b] : a || b;1516const mergeNames = <T = string>(a: T | T[], b: T | T[]): T | T[] => [17...new Set(concatArrays(makeArray(a), makeArray(b))),18];1920const mergeArrays = <T>(a: T[] | undefined, b: T[] | undefined): T[] | undefined =>21a && b ? [...new Set(concatArrays(makeArray(a), makeArray(b)))] : a || b;2223const mergeArgs = (arg: Fig.Arg, partial: Fig.Arg): Fig.Arg => ({24...arg,25...partial,26suggestions: concatArrays<Fig.Suggestion | string>(arg.suggestions, partial.suggestions),27generators:28arg.generators && partial.generators29? concatArrays(makeArray(arg.generators), makeArray(partial.generators))30: arg.generators || partial.generators,31template:32arg.template && partial.template33? mergeNames<Fig.TemplateStrings>(arg.template, partial.template)34: arg.template || partial.template,35});3637const mergeArgArrays = (38args: Fig.SingleOrArray<Fig.Arg> | undefined,39partials: Fig.SingleOrArray<Fig.Arg> | undefined40): Fig.SingleOrArray<Fig.Arg> | undefined => {41if (!args || !partials) {42return args || partials;43}44const argArray = makeArray(args);45const partialArray = makeArray(partials);46const result = [];47for (let i = 0; i < Math.max(argArray.length, partialArray.length); i += 1) {48const arg = argArray[i];49const partial = partialArray[i];50if (arg !== undefined && partial !== undefined) {51result.push(mergeArgs(arg, partial));52} else if (partial !== undefined || arg !== undefined) {53result.push(arg || partial);54}55}56return result.length === 1 ? result[0] : result;57};5859const mergeOptions = (option: Fig.Option, partial: Fig.Option): Fig.Option => ({60...option,61...partial,62name: mergeNames(option.name, partial.name),63args: mergeArgArrays(option.args, partial.args),64exclusiveOn: mergeArrays(option.exclusiveOn, partial.exclusiveOn),65dependsOn: mergeArrays(option.dependsOn, partial.dependsOn),66});6768const mergeNamedObjectArrays = <T extends NamedObject>(69objects: T[] | undefined,70partials: T[] | undefined,71mergeItems: (a: T, b: T) => T72): T[] | undefined => {73if (!objects || !partials) {74return objects || partials;75}76const mergedObjects = objects ? [...objects] : [];7778const existingNameIndexMap: Record<string, number> = {};79for (let i = 0; i < objects.length; i += 1) {80makeArray(objects[i].name).forEach((name) => {81existingNameIndexMap[name] = i;82});83}8485for (let i = 0; i < partials.length; i += 1) {86const partial = partials[i];87if (!partial) {88throw new Error('Invalid object passed to merge');89}90const existingNames = makeArray(partial.name).filter((name) => name in existingNameIndexMap);91if (existingNames.length === 0) {92mergedObjects.push(partial);93} else {94const index = existingNameIndexMap[existingNames[0]];95if (existingNames.some((name) => existingNameIndexMap[name] !== index)) {96throw new Error('Names provided for option matched multiple existing options');97}98mergedObjects[index] = mergeItems(mergedObjects[index], partial);99}100}101return mergedObjects;102};103104function mergeOptionArrays(105options: Fig.Option[] | undefined,106partials: Fig.Option[] | undefined107): Fig.Option[] | undefined {108return mergeNamedObjectArrays(options, partials, mergeOptions);109}110111function mergeSubcommandArrays(112subcommands: Fig.Subcommand[] | undefined,113partials: Fig.Subcommand[] | undefined114): Fig.Subcommand[] | undefined {115return mergeNamedObjectArrays(subcommands, partials, mergeSubcommands);116}117118export function mergeSubcommands(119subcommand: Fig.Subcommand,120partial: Fig.Subcommand121): Fig.Subcommand {122return {123...subcommand,124...partial,125name: mergeNames(subcommand.name, partial.name),126args: mergeArgArrays(subcommand.args, partial.args),127additionalSuggestions: concatArrays<Fig.Suggestion | string>(128subcommand.additionalSuggestions,129partial.additionalSuggestions130),131subcommands: mergeSubcommandArrays(subcommand.subcommands, partial.subcommands),132options: mergeOptionArrays(subcommand.options, partial.options),133parserDirectives:134subcommand.parserDirectives && partial.parserDirectives135? { ...subcommand.parserDirectives, ...partial.parserDirectives }136: subcommand.parserDirectives || partial.parserDirectives,137};138}139140export const applyMixin = (141spec: Fig.Subcommand,142context: Fig.ShellContext,143mixin: SpecMixin144): Fig.Subcommand => {145if (typeof mixin === 'function') {146return mixin(spec, context);147}148const partial = mixin;149return mergeSubcommands(spec, partial);150};151152153