@autorest/common
Version:
Autorest common utilities
203 lines • 9.23 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable no-prototype-builtins */
Object.defineProperty(exports, "__esModule", { value: true });
exports.strictMerge = strictMerge;
exports.resolveRValue = resolveRValue;
exports.mergeOverwriteOrAppend = mergeOverwriteOrAppend;
const yaml_1 = require("@azure-tools/yaml");
/**
* Merge a and b by adding new properties of b into a. It will fail if a and b have the same property and the value is different.
* @param a Object 1 to merge
* @param b Object 2 to merge
* @param path current path of the merge.
*/
function strictMerge(a, b, path = []) {
if (a === null || b === null) {
throw new Error(`Argument cannot be null ('${(0, yaml_1.Stringify)(path)}')`);
}
// trivial case
if (a === b || JSON.stringify(a) === JSON.stringify(b)) {
return a;
}
// mapping nodes
if (typeof a === "object" && typeof b === "object") {
if (a instanceof Array && b instanceof Array) {
if (a.length === 0) {
return b;
}
if (b.length === 0) {
return a;
}
// both sides gave a sequence, and they are not identical.
// this is currently not a good thing.
throw new Error(`'${(0, yaml_1.Stringify)(path)}' has two arrays that are incompatible (${(0, yaml_1.Stringify)(a)}, ${(0, yaml_1.Stringify)(b)}).`);
}
else {
// object nodes - iterate all members
const result = {};
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const key of keys) {
const subpath = path.concat(key);
// forward if only present in one of the nodes
if (a[key] === undefined) {
result[key] = b[key];
continue;
}
if (b[key] === undefined) {
result[key] = a[key];
continue;
}
// try merge objects otherwise
const aMember = a[key];
const bMember = b[key];
result[key] = strictMerge(aMember, bMember, subpath);
}
return result;
}
}
throw new Error(`'${(0, yaml_1.Stringify)(path)}' has incompatible values (${(0, yaml_1.Stringify)(a)}, ${(0, yaml_1.Stringify)(b)}).`);
}
// Note: I am not convinced this works precisely as it should
// but it works well enough for my needs right now
// I will revisit it later.
const macroRegEx = () => /\$\(([a-zA-Z0-9_-]*)\)/gi;
/**
* Resolve the expanded value by interpolating any
* @param value Value to interpolate.
* @param propertyName Name of the property.
* @param higherPriority Higher priority context to resolve the interpolation values.
* @param lowerPriority Lower priority context to resolve the interpolation values.
* @param jsAware
*/
function resolveRValue(value, propertyName, higherPriority, lowerPriority, jsAware = 0) {
if (value) {
// resolves the actual macro value.
const resolve = (macroExpression, macroKey) => {
// if the original set has it, use that.
if (higherPriority && higherPriority[macroKey]) {
return resolveRValue(higherPriority[macroKey], macroKey, lowerPriority, null, jsAware - 1);
}
if (lowerPriority) {
// check to see if the value is in the overrides set before the key itself.
const keys = Object.getOwnPropertyNames(lowerPriority);
const macroKeyLocation = keys.indexOf(macroKey);
if (macroKeyLocation > -1) {
if (macroKeyLocation < keys.indexOf(propertyName)) {
// the macroKey is in the overrides, and it precedes the propertyName itself
return resolveRValue(lowerPriority[macroKey], macroKey, higherPriority, lowerPriority, jsAware - 1);
}
}
}
// can't find the macro. maybe later.
return macroExpression;
};
// resolve the macro value for strings
if (typeof value === "string") {
const match = macroRegEx().exec(value.trim());
if (match) {
if (match[0] === match.input) {
// the target value should be the result without string twiddling
if (jsAware > 0) {
return JSON.stringify(resolve(match[0], match[1]));
}
return resolve(match[0], match[1]);
}
// it looks like we should do a string replace.
return value.replace(macroRegEx(), resolve);
}
}
// resolve macro values for array values
if (value instanceof Array) {
// since we're not naming the parameter,
// if there isn't a higher priority,
// we can fall back to a wide-lookup in lowerPriority.
return value.map((x) => resolveRValue(x, "", higherPriority || lowerPriority, null));
}
}
if (jsAware > 0) {
return JSON.stringify(value);
}
return value;
}
const defaultOptions = {
arrayMergeStrategy: "high-pri-first",
concatListPathFilter: () => false,
};
function mergeOverwriteOrAppend(higherPriority, lowerPriority, options = {}, path = []) {
var _a;
if (higherPriority === null || lowerPriority === null) {
return null;
}
const computedOptions = {
...defaultOptions,
...options,
interpolationContext: (_a = options.interpolationContext) !== null && _a !== void 0 ? _a : higherPriority,
};
// if (higherPriority === true && typeof lowerPriority.extensions) {
// console.log("Merge", higherPriority, lowerPriority);
// }
// Take care of the case where an option is enable via a flag `--az` and then nested config under it don't work(az.extensions)
if (higherPriority === true && typeof lowerPriority === "object") {
return lowerPriority;
}
// scalars/arrays involved
if (typeof higherPriority !== "object" ||
higherPriority instanceof Array ||
typeof lowerPriority !== "object" ||
lowerPriority instanceof Array) {
return mergeArray(higherPriority, lowerPriority, path, computedOptions);
}
// object nodes - iterate all members
const result = {};
const keys = getKeysInOrder(higherPriority, lowerPriority, computedOptions);
for (const key of keys) {
const subpath = path.concat(key);
// forward if only present in one of the nodes
if (higherPriority[key] === undefined) {
result[key] = resolveRValue(lowerPriority[key], key, computedOptions.interpolationContext, lowerPriority);
continue;
}
if (lowerPriority[key] === undefined) {
result[key] = resolveRValue(higherPriority[key], key, null, computedOptions.interpolationContext);
continue;
}
// try merge objects otherwise
const aMember = resolveRValue(higherPriority[key], key, lowerPriority, computedOptions.interpolationContext);
const bMember = resolveRValue(lowerPriority[key], key, computedOptions.interpolationContext, lowerPriority);
result[key] = mergeOverwriteOrAppend(aMember, bMember, { ...computedOptions, interpolationContext: computedOptions.interpolationContext[key] }, subpath);
}
return result;
}
/**
*
* @param higherPriority Higher priority object
* @param lowerPriority Lower priority object
* @param options Merge options.
* @returns List of unique keys used in both object in the order defined in the options.
*/
function getKeysInOrder(higherPriority, lowerPriority, options) {
const lowPriKeys = Object.getOwnPropertyNames(lowerPriority);
const highPriKeys = Object.getOwnPropertyNames(higherPriority);
return [
...new Set(options.arrayMergeStrategy === "low-pri-first" ? lowPriKeys.concat(highPriKeys) : highPriKeys.concat(lowPriKeys)),
];
}
function mergeArray(higherPriority, lowerPriority, path, { concatListPathFilter, arrayMergeStrategy }) {
if (!(higherPriority instanceof Array) && !(lowerPriority instanceof Array) && !concatListPathFilter(path)) {
return higherPriority;
}
const higherPriorityArray = higherPriority instanceof Array ? higherPriority : [higherPriority];
const lowerPriorityArray = lowerPriority instanceof Array ? lowerPriority : [lowerPriority];
if (arrayMergeStrategy === "high-pri-first") {
return [...new Set(higherPriorityArray.concat(lowerPriority))];
}
else {
return [...new Set(lowerPriorityArray.concat(higherPriority))];
}
}
//# sourceMappingURL=merging.js.map