@codama/validators
Version:
Validator visitors for the Codama framework
250 lines (245 loc) • 9.13 kB
JavaScript
;
var nodes = require('@codama/nodes');
var visitorsCore = require('@codama/visitors-core');
var errors = require('@codama/errors');
// src/getValidationItemsVisitor.ts
// src/ValidationItem.ts
var LOG_LEVELS = ["debug", "trace", "info", "warn", "error"];
function validationItem(level, message, node, path) {
return {
level,
message,
node,
path: Array.isArray(path) ? path : path.getPath()
};
}
var getLevelIndex = (level) => LOG_LEVELS.indexOf(level);
// src/getValidationItemsVisitor.ts
function getValidationItemsVisitor() {
const linkables = new visitorsCore.LinkableDictionary();
const stack = new visitorsCore.NodeStack();
return visitorsCore.pipe(
visitorsCore.mergeVisitor(
() => [],
(_, items) => items.flat()
),
(v) => visitorsCore.recordLinkablesOnFirstVisitVisitor(v, linkables),
(v) => visitorsCore.recordNodeStackVisitor(v, stack),
(v) => visitorsCore.extendVisitor(v, {
visitAccount(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Account has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitDefinedType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Defined type has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitDefinedTypeLink(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Pointing to a defined type with no name.", node, stack));
} else if (!linkables.has(stack.getPath(node.kind))) {
items.push(
validationItem(
"error",
`Pointing to a missing defined type named "${node.name}"`,
node,
stack
)
);
}
return [...items, ...next(node)];
},
visitEnumEmptyVariantType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitEnumStructVariantType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitEnumTupleVariantType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitEnumType(node, { next }) {
const items = [];
if (node.variants.length === 0) {
items.push(validationItem("warn", "Enum has no variants.", node, stack));
}
node.variants.forEach((variant) => {
if (!variant.name) {
items.push(validationItem("error", "Enum variant has no name.", node, stack));
}
});
return [...items, ...next(node)];
},
visitError(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Error has no name.", node, stack));
}
if (typeof node.code !== "number") {
items.push(validationItem("error", "Error has no code.", node, stack));
}
if (!node.message) {
items.push(validationItem("warn", "Error has no message.", node, stack));
}
return [...items, ...next(node)];
},
visitInstruction(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Instruction has no name.", node, stack));
}
const accountNameHistogram = /* @__PURE__ */ new Map();
node.accounts.forEach((account) => {
if (!account.name) {
items.push(validationItem("error", "Instruction account has no name.", node, stack));
return;
}
const count = (accountNameHistogram.get(account.name) ?? 0) + 1;
accountNameHistogram.set(account.name, count);
if (count === 2) {
items.push(
validationItem(
"error",
`Account name "${account.name}" is not unique in instruction "${node.name}".`,
node,
stack
)
);
}
});
const cyclicCheckVisitor = visitorsCore.getResolvedInstructionInputsVisitor();
try {
visitorsCore.visit(node, cyclicCheckVisitor);
} catch (error) {
items.push(validationItem("error", error.message, node, stack));
}
const names = nodes.getAllInstructionArguments(node).map(({ name }) => nodes.camelCase(name));
const duplicates = names.filter((e, i, a) => a.indexOf(e) !== i);
const uniqueDuplicates = [...new Set(duplicates)];
const hasConflictingNames = uniqueDuplicates.length > 0;
if (hasConflictingNames) {
items.push(
validationItem(
"error",
`The names of the following instruction arguments are conflicting: [${uniqueDuplicates.join(", ")}].`,
node,
stack
)
);
}
nodes.getAllInstructionArguments(node).forEach((argument) => {
const { defaultValue } = argument;
if (nodes.isNode(defaultValue, "accountBumpValueNode")) {
const defaultAccount = node.accounts.find((account) => account.name === defaultValue.name);
if (defaultAccount && defaultAccount.isSigner !== false) {
items.push(
validationItem(
"error",
`Argument ${argument.name} cannot default to the bump attribute of the [${defaultValue.name}] account as it may be a Signer.`,
node,
stack
)
);
}
}
});
return [...items, ...next(node)];
},
visitProgram(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Program has no name.", node, stack));
}
if (!node.publicKey) {
items.push(validationItem("error", "Program has no public key.", node, stack));
}
if (!node.version) {
items.push(validationItem("warn", "Program has no version.", node, stack));
}
if (!node.origin) {
items.push(validationItem("info", "Program has no origin.", node, stack));
}
return [...items, ...next(node)];
},
visitStructFieldType(node, { next }) {
const items = [];
if (!node.name) {
items.push(validationItem("error", "Struct field has no name.", node, stack));
}
return [...items, ...next(node)];
},
visitStructType(node, { next }) {
const items = [];
const fieldNameHistogram = /* @__PURE__ */ new Map();
node.fields.forEach((field) => {
if (!field.name) return;
const count = (fieldNameHistogram.get(field.name) ?? 0) + 1;
fieldNameHistogram.set(field.name, count);
if (count === 2) {
items.push(
validationItem(
"error",
`Struct field name "${field.name}" is not unique.`,
field,
stack
)
);
}
});
return [...items, ...next(node)];
},
visitTupleType(node, { next }) {
const items = [];
if (node.items.length === 0) {
items.push(validationItem("warn", "Tuple has no items.", node, stack));
}
return [...items, ...next(node)];
}
})
);
}
function throwValidatorItemsVisitor(visitor, throwLevel = "error") {
return visitorsCore.mapVisitor(visitor, (validationItems) => {
const levelHistogram = [...validationItems].sort((a, b) => getLevelIndex(b.level) - getLevelIndex(a.level)).reduce(
(acc, item) => {
acc[item.level] = (acc[item.level] ?? 0) + 1;
return acc;
},
{}
);
const maxLevel = Object.keys(levelHistogram).map((level) => getLevelIndex(level)).sort((a, b) => b - a)[0];
if (maxLevel >= getLevelIndex(throwLevel)) {
const formattedHistogram = Object.keys(levelHistogram).map((level) => `${level}s: ${levelHistogram[level]}`).join(", ");
throw new errors.CodamaError(errors.CODAMA_ERROR__VISITORS__FAILED_TO_VALIDATE_NODE, {
formattedHistogram,
validationItems
});
}
});
}
exports.LOG_LEVELS = LOG_LEVELS;
exports.getLevelIndex = getLevelIndex;
exports.getValidationItemsVisitor = getValidationItemsVisitor;
exports.throwValidatorItemsVisitor = throwValidatorItemsVisitor;
exports.validationItem = validationItem;
//# sourceMappingURL=index.browser.cjs.map
//# sourceMappingURL=index.browser.cjs.map