core/converters/emitter.converter.ts
core/converters/emitter.converter.ts is a file in GovLab Quality. 135 lines of code and 34 definitions.
import type { ActiveConcept, ConfigValue, EmitDescriptor, EmitInput } from "#types/emitter.types";
import { isConfigObject } from "#core/predicates/config.predicate";
const isNonEmpty = function isNonEmpty(value: string | null | undefined): value is string {
return typeof value === "string" && value.length > 0;
};
export const hasValue = function hasValue(
concept: ActiveConcept,
): concept is ActiveConcept & { knob: string; value: ConfigValue } {
return concept.knob !== null && concept.value !== undefined;
};
const normalizeId = function normalizeId(value: string): string {
return value.toLowerCase().replaceAll("-", "").replaceAll("_", "").replaceAll(".", "");
};
export const ownerRuleId = function ownerRuleId(canonicalId: string, ruleIds: string[]): string {
const exact = ruleIds.find((id) => id === canonicalId);
if (typeof exact === "string") {
return exact;
}
const target = normalizeId(canonicalId);
const matches = ruleIds.filter((id) => normalizeId(id).includes(target)).sort((a, b) => a.length - b.length);
return matches[0] ?? ruleIds[0] ?? "";
};
const childObject = function childObject(current: ConfigValue | null): Record<string, ConfigValue> {
return isConfigObject(current) ? current : {};
};
const setPath = function setPath(tree: Record<string, ConfigValue>, dotted: string, value: ConfigValue): void {
const parts = dotted.split(".");
let node: Record<string, ConfigValue> = tree;
for (const key of parts.slice(0, -1)) {
node[key] = childObject(node[key] ?? null);
node = childObject(node[key] ?? null);
}
const leaf = parts.at(-1);
if (typeof leaf === "string") {
node[leaf] = value;
}
};
const centralPath = function centralPath(descriptor: EmitDescriptor, knob: string): string {
const section = descriptor.knobSection?.[knob] ?? descriptor.settingsContainer;
const cased = descriptor.knobCase === "kebab" ? knob.replaceAll("_", "-") : knob;
return isNonEmpty(section) ? `${section}.${cased}` : cased;
};
const collectCentral = function collectCentral(
descriptor: EmitDescriptor,
concepts: ActiveConcept[],
): { select: Set<string>; settings: [string, ConfigValue][] } {
const select = new Set(concepts.flatMap((concept) => concept.ruleIds));
const settings = concepts
.filter(hasValue)
.map((concept): [string, ConfigValue] => [centralPath(descriptor, concept.knob), concept.value]);
return { select, settings };
};
const centralContainers = function centralContainers(
descriptor: EmitDescriptor,
select: Set<string>,
ignore: string[],
): [string, ConfigValue][] {
const out: [string, ConfigValue][] = [];
if (isNonEmpty(descriptor.selectContainer) && select.size > 0) {
out.push([descriptor.selectContainer, [...select].sort((a, b) => a.localeCompare(b))]);
}
if (isNonEmpty(descriptor.ignoreContainer) && ignore.length > 0) {
out.push([descriptor.ignoreContainer, [...ignore]]);
}
return out;
};
const centralTree = function centralTree(input: EmitInput): ConfigValue {
const tree: Record<string, ConfigValue> = {};
const { select, settings } = collectCentral(input.descriptor, input.concepts);
const preamble = Object.entries(input.descriptor.preamble ?? {});
for (const [path, value] of [
...preamble,
...centralContainers(input.descriptor, select, input.ignore),
...settings,
]) {
setPath(tree, path, value);
}
return tree;
};
const perRuleBlock = function perRuleBlock(
descriptor: EmitDescriptor,
concept: ActiveConcept,
existing: ConfigValue,
): ConfigValue {
const block: Record<string, ConfigValue> = isConfigObject(existing) ? existing : {};
if (isNonEmpty(descriptor.enableKey)) {
block[descriptor.enableKey] = true;
}
if (hasValue(concept)) {
const key = isNonEmpty(descriptor.compoundSplit)
? (concept.knob.split(descriptor.compoundSplit)[0] ?? concept.knob)
: concept.knob;
block[key] = concept.value;
}
return block;
};
const perRuleTree = function perRuleTree(input: EmitInput): ConfigValue {
const tree: Record<string, ConfigValue> = {};
for (const concept of input.concepts) {
for (const ruleId of concept.ruleIds) {
tree[ruleId] = perRuleBlock(input.descriptor, concept, tree[ruleId] ?? null);
}
}
return tree;
};
const containerEntries = function containerEntries(
descriptor: EmitDescriptor,
concept: ActiveConcept,
): [string, ConfigValue][] {
if (hasValue(concept)) {
const owner = ownerRuleId(concept.canonicalId, concept.ruleIds);
const block = isNonEmpty(descriptor.argumentsKey)
? { [descriptor.argumentsKey]: [concept.value] }
: { [concept.knob]: concept.value };
return [[owner, block]];
}
return concept.ruleIds.map((ruleId): [string, ConfigValue] => [
ruleId,
isNonEmpty(descriptor.argumentsKey) ? {} : "enable",
]);
};
const rulesContainerTree = function rulesContainerTree(input: EmitInput): ConfigValue {
const container = Object.fromEntries(
input.concepts.flatMap((concept) => containerEntries(input.descriptor, concept)),
);
return { [input.descriptor.ruleContainer ?? "rules"]: container };
};
const IDIOM_TREES: Readonly<Record<EmitDescriptor["idiom"], (input: EmitInput) => ConfigValue>> = {
central: centralTree,
"per-rule": perRuleTree,
"rules-container": rulesContainerTree,
};
export const treeFor = function treeFor(input: EmitInput): ConfigValue {
return IDIOM_TREES[input.descriptor.idiom](input);
};