hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
282 lines • 12 kB
JavaScript
import { HardhatError, assertHardhatInvariant, } from "@nomicfoundation/hardhat-errors";
import { isCi } from "@nomicfoundation/hardhat-utils/ci";
import { kebabToCamelCase } from "@nomicfoundation/hardhat-utils/string";
import { ArgumentType, } from "../../types/arguments.js";
import { parseArgumentValue } from "../core/arguments.js";
import { isArgumentRequired } from "../core/tasks/utils.js";
export async function parseBuiltinGlobalOptions(cliArguments, usedCliArguments) {
let configPath;
let showStackTraces = isCi();
let help = false;
let version = false;
let init = false;
// TODO: Use parseGlobalOptions(BUILTIN_GLOBAL_OPTIONS_DEFINITIONS, ...) instead
for (let i = 0; i < cliArguments.length; i++) {
const arg = cliArguments[i];
if (arg === "--init") {
usedCliArguments[i] = true;
init = true;
continue;
}
if (arg === "--config") {
usedCliArguments[i] = true;
if (configPath !== undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.DUPLICATED_NAME, {
name: "--config",
});
}
if (usedCliArguments[i + 1] === undefined ||
usedCliArguments[i + 1] === true) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.MISSING_CONFIG_FILE);
}
configPath = cliArguments[i + 1];
i++;
usedCliArguments[i] = true;
continue;
}
if (arg === "--show-stack-traces") {
usedCliArguments[i] = true;
showStackTraces = true;
continue;
}
if (arg === "--help" || arg === "-h") {
usedCliArguments[i] = true;
help = true;
continue;
}
if (arg === "--version") {
usedCliArguments[i] = true;
version = true;
continue;
}
}
if (init && configPath !== undefined) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.CANNOT_COMBINE_INIT_AND_CONFIG_PATH);
}
return { init, configPath, showStackTraces, help, version };
}
export async function parseGlobalOptions(globalOptionDefinitions, cliArguments, usedCliArguments) {
const globalOptions = {};
const optionDefinitions = new Map([...globalOptionDefinitions].map(([key, value]) => [key, value.option]));
parseOptions(cliArguments, usedCliArguments, optionDefinitions, globalOptions, true);
return globalOptions;
}
/**
* Parses the task from the cli args.
*
* @returns The task, or an array with the unrecognized task id.
* If no task id is provided, an empty array is returned.
*/
export function parseTask(cliArguments, usedCliArguments, tasksMap) {
const taskOrId = getTaskFromCliArguments(cliArguments, usedCliArguments, tasksMap);
return taskOrId;
}
function getTaskFromCliArguments(cliArguments, usedCliArguments, tasksMap) {
const taskId = [];
let task;
for (let i = 0; i < cliArguments.length; i++) {
if (usedCliArguments[i]) {
continue;
}
const arg = cliArguments[i];
if (arg.startsWith("-")) {
/* A standalone '--' is ok because it is used to separate CLI tool arguments
* from task arguments, ensuring the tool passes subsequent options directly
* to the task. Everything after "--" should be considered as a positional
* argument. */
if (arg === "--" || task !== undefined) {
break;
}
/* At this point in the code, the global options have already been parsed, so
* the remaining options starting with '--' are task options. Hence, if no task
* is defined, it means that the option is not assigned to any task, and it's
* an error. */
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.UNRECOGNIZED_OPTION, {
option: arg,
});
}
if (task === undefined) {
task = tasksMap.get(arg);
if (task === undefined) {
return [arg]; // No task found
}
}
else {
const subtask = task.subtasks.get(arg);
if (subtask === undefined) {
break;
}
task = subtask;
}
usedCliArguments[i] = true;
taskId.push(arg);
}
if (task === undefined) {
return taskId;
}
return task;
}
export function parseTaskArguments(cliArguments, usedCliArguments, task) {
const taskArguments = {};
// Parse options
parseOptions(cliArguments, usedCliArguments, task.options, taskArguments);
parsePositionalAndVariadicArguments(cliArguments, usedCliArguments, task, taskArguments);
const unusedIndex = usedCliArguments.indexOf(false);
if (unusedIndex !== -1) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.UNUSED_ARGUMENT, {
value: cliArguments[unusedIndex],
});
}
return taskArguments;
}
/**
* Parses the raw arguments from the command line, returning an array of
* arguments. If an argument starts with "--" and contains "=" (i.e. "--option=123")
* it is split into two separate arguments: the option name and the option value.
*/
export function parseRawArguments(rawArguments) {
return rawArguments.flatMap((arg) => {
if (arg.startsWith("--") && arg.includes("=")) {
const index = arg.indexOf("=");
const optionName = arg.substring(0, index);
const optionValue = arg.substring(index + 1);
return [optionName, optionValue];
}
return arg;
});
}
function parseOptions(cliArguments, usedCliArguments, optionDefinitions, providedArguments, ignoreUnknownOption = false) {
const optionDefinitionsByShortName = new Map();
for (const optionDefinition of optionDefinitions.values()) {
if (optionDefinition.shortName !== undefined) {
optionDefinitionsByShortName.set(optionDefinition.shortName, optionDefinition);
}
}
for (let i = 0; i < cliArguments.length; i++) {
if (usedCliArguments[i]) {
continue;
}
if (cliArguments[i] === "--") {
/* A standalone '--' is ok because it is used to separate CLI tool arguments
* from task arguments, ensuring the tool passes subsequent options directly
* to the task. Everything after "--" should be considered as a positional
* argument. */
break;
}
const arg = cliArguments[i];
let optionDefinition;
const providedByName = arg.startsWith("--");
const providedByShortName = !providedByName && arg.startsWith("-");
if (providedByName) {
const name = kebabToCamelCase(arg.substring(2));
optionDefinition = optionDefinitions.get(name);
}
else if (providedByShortName) {
const shortName = arg[1];
// Check if the short name is valid
if (Array.from(arg.substring(1)).some((c) => c !== shortName)) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.CANNOT_GROUP_OPTIONS, {
option: arg,
});
}
optionDefinition = optionDefinitionsByShortName.get(shortName);
}
else {
continue;
}
if (optionDefinition === undefined) {
if (ignoreUnknownOption === true) {
continue;
}
// Only throw an error when the argument is not a global option, because
// it might be a option related to a task
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.UNRECOGNIZED_OPTION, {
option: arg,
});
}
if (optionDefinition.hidden === true) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.NO_HIDDEN_OPTION_CLI, {
option: arg,
});
}
const optionName = optionDefinition.name;
// Check if the short name is valid again now that we know its type
// E.g. --flag --flag
const optionAlreadyProvided = providedArguments[optionName] !== undefined;
// E.g. -ff
const shortOptionGroupedAndRepeated = providedByShortName && arg.length > 2;
const isLevelOption = optionDefinition.type === ArgumentType.LEVEL;
if (optionAlreadyProvided ||
(shortOptionGroupedAndRepeated && !isLevelOption)) {
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.CANNOT_REPEAT_OPTIONS, {
option: arg,
type: optionDefinition.type,
});
}
usedCliArguments[i] = true;
if (optionDefinition.type === ArgumentType.FLAG) {
providedArguments[optionName] = true;
continue;
}
else if (optionDefinition.type === ArgumentType.LEVEL &&
providedByShortName) {
providedArguments[optionName] = arg.length - 1;
continue;
}
else if (usedCliArguments[i + 1] !== undefined &&
usedCliArguments[i + 1] === false) {
i++;
providedArguments[optionName] = parseArgumentValue(cliArguments[i], optionDefinition.type, optionName);
usedCliArguments[i] = true;
continue;
}
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.MISSING_VALUE_FOR_ARGUMENT, {
argument: arg,
});
}
}
function parsePositionalAndVariadicArguments(cliArguments, usedCliArguments, task, providedArguments) {
let argIndex = 0;
for (let i = 0; i < cliArguments.length; i++) {
if (usedCliArguments[i] === true) {
continue;
}
if (cliArguments[i] === "--") {
/* A standalone '--' is ok because it is used to separate CLI tool arguments
* from task arguments, ensuring the tool passes subsequent options directly
* to the task. Everything after "--" should be considered as a positional
* argument. */
usedCliArguments[i] = true;
continue;
}
const argumentDefinition = task.positionalArguments[argIndex];
if (argumentDefinition === undefined) {
break;
}
usedCliArguments[i] = true;
const formattedValue = parseArgumentValue(cliArguments[i], argumentDefinition.type, argumentDefinition.name);
if (argumentDefinition.isVariadic === false) {
providedArguments[argumentDefinition.name] = formattedValue;
argIndex++;
continue;
}
// Handle variadic arguments. No longer increment "argIndex" because there can
// only be one variadic argument, and it will consume all remaining arguments.
providedArguments[argumentDefinition.name] =
providedArguments[argumentDefinition.name] ?? [];
const variadicTaskArg = providedArguments[argumentDefinition.name];
assertHardhatInvariant(Array.isArray(variadicTaskArg), "Variadic argument values should be an array");
variadicTaskArg.push(formattedValue);
}
// Check if all the required arguments have been used
validateRequiredArguments(task.positionalArguments, providedArguments);
}
function validateRequiredArguments(argumentDefinitions, taskArguments) {
const missingRequiredArgument = argumentDefinitions.find(({ defaultValue, name, type }) => isArgumentRequired(type, defaultValue) &&
taskArguments[name] === undefined);
if (missingRequiredArgument === undefined) {
return;
}
throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.MISSING_VALUE_FOR_ARGUMENT, { argument: missingRequiredArgument.name });
}
//# sourceMappingURL=parser.js.map