Skip to content

Commit b62a981

Browse files
authored
Merge pull request #5414 from NomicFoundation/resolve-global-args
resolveGlobalArguments implementation & testing
2 parents 38b2dc1 + 665a755 commit b62a981

File tree

7 files changed

+390
-12
lines changed

7 files changed

+390
-12
lines changed

v-next/core/src/internal/global-options.ts

+64-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { ParameterTypeToValueType } from "../types/common.js";
1+
import type {
2+
ParameterTypeToValueType,
3+
ParameterValue,
4+
} from "../types/common.js";
25
import type {
36
GlobalOptions,
47
GlobalOption,
@@ -7,21 +10,24 @@ import type {
710
import type { HardhatPlugin } from "../types/plugins.js";
811

912
import { HardhatError } from "@nomicfoundation/hardhat-errors";
13+
import { camelToSnakeCase } from "@nomicfoundation/hardhat-utils/string";
1014

1115
import { ParameterType } from "../types/common.js";
1216

1317
import {
1418
RESERVED_PARAMETER_NAMES,
1519
isParameterValueValid,
1620
isValidParamNameCasing,
21+
parseParameterValue,
1722
} from "./parameters.js";
1823

1924
/**
20-
* Builds a map of the global options, validating them.
25+
* Builds a map of the global option definitions by going through all the
26+
* plugins and validating the global options they define.
2127
*
2228
* Note: this function can be used before initializing the HRE, so the plugins
2329
* shouldn't be consider validated. Hence, we should validate the global
24-
* parameters.
30+
* options.
2531
*/
2632
export function buildGlobalOptionsMap(
2733
resolvedPlugins: HardhatPlugin[],
@@ -60,6 +66,10 @@ export function buildGlobalOptionsMap(
6066
return globalOptionsMap;
6167
}
6268

69+
/**
70+
* Builds a global option definition, validating the name, type, and default
71+
* value.
72+
*/
6373
export function buildGlobalOptionDefinition<T extends ParameterType>({
6474
name,
6575
description,
@@ -104,13 +114,59 @@ export function buildGlobalOptionDefinition<T extends ParameterType>({
104114
};
105115
}
106116

117+
/**
118+
* Resolves global options by merging user-provided options with environment
119+
* variables, adhering to predefined global option definitions. This function
120+
* ensures that only options specified in the globalOptionsMap are considered.
121+
* Each option is validated against its definition in the map, with
122+
* user-provided options taking precedence over environment variables. If an
123+
* option is not provided by the user or set as an environment variable, its
124+
* default value (as specified in the globalOptionsMap) is used.
125+
*
126+
* @param userProvidedGlobalOptions The options explicitly provided by the
127+
* user. These take precedence over equivalent environment variables.
128+
* @param globalOptionsMap A map defining valid global options, their default
129+
* values, and expected types. This map is used to validate and parse the options.
130+
* @returns {GlobalOptions} An object containing the resolved global options,
131+
* with each option adhering to its definition in the globalOptionsMap.
132+
* @throws {HardhatError} with descriptor
133+
* {@link HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE} if a user-provided
134+
* option has an invalid value for its type.
135+
*/
107136
export function resolveGlobalOptions(
108137
userProvidedGlobalOptions: Partial<GlobalOptions>,
109-
_globalOptionsMap: GlobalOptionsMap,
138+
globalOptionsMap: GlobalOptionsMap,
110139
): GlobalOptions {
111-
// TODO: Validate the userProvidedGlobalOptions and get the remaining ones
112-
// from env variables
140+
const globalOptions: GlobalOptions = {};
141+
// iterate over the definitions to parse and validate the arguments
142+
for (const [name, { option }] of globalOptionsMap) {
143+
let value =
144+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
145+
-- GlobalOptions is empty for user extension, so we need to cast it to
146+
assign the value. */
147+
(userProvidedGlobalOptions as Record<string, string | undefined>)[name];
148+
149+
let parsedValue: ParameterValue;
150+
// if the value is provided in the user options, it's already parsed
151+
// and it takes precedence over env vars
152+
if (value !== undefined) {
153+
parsedValue = value;
154+
} else {
155+
value = process.env[`HARDHAT_${camelToSnakeCase(name).toUpperCase()}`];
156+
if (value !== undefined) {
157+
// if the value is provided via an env var, it needs to be parsed
158+
parsedValue = parseParameterValue(value, option.parameterType, name);
159+
} else {
160+
// if the value is not provided by the user or env var, use the default
161+
parsedValue = option.defaultValue;
162+
}
163+
}
164+
165+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
166+
-- GlobalOptions is empty for user extension, so we need to cast it to
167+
assign the value. */
168+
(globalOptions as Record<string, ParameterValue>)[name] = parsedValue;
169+
}
113170

114-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO
115-
return userProvidedGlobalOptions as GlobalOptions;
171+
return globalOptions;
116172
}

v-next/core/src/internal/parameters.ts

+100
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import type { ParameterValue } from "../types/common.js";
2+
3+
import { HardhatError } from "@nomicfoundation/hardhat-errors";
4+
15
import { ParameterType } from "../types/common.js";
26

37
/**
@@ -54,3 +58,99 @@ const parameterTypeValidators: Record<
5458
[ParameterType.FLOAT]: (value): value is number => typeof value === "number",
5559
[ParameterType.FILE]: (value): value is string => typeof value === "string",
5660
};
61+
62+
/**
63+
* Parses a parameter value from a string to the corresponding type.
64+
*/
65+
// TODO: this code is duplicated in v-next/hardhat/src/internal/cli/main.ts
66+
// we should move it to a shared place and add tests
67+
export function parseParameterValue(
68+
value: string,
69+
type: ParameterType,
70+
name: string,
71+
): ParameterValue {
72+
switch (type) {
73+
case ParameterType.STRING:
74+
case ParameterType.FILE:
75+
return value;
76+
case ParameterType.INT:
77+
return validateAndParseInt(name, value);
78+
case ParameterType.FLOAT:
79+
return validateAndParseFloat(name, value);
80+
case ParameterType.BIGINT:
81+
return validateAndParseBigInt(name, value);
82+
case ParameterType.BOOLEAN:
83+
return validateAndParseBoolean(name, value);
84+
}
85+
}
86+
87+
function validateAndParseInt(name: string, value: string): number {
88+
const decimalPattern = /^\d+(?:[eE]\d+)?$/;
89+
const hexPattern = /^0[xX][\dABCDEabcde]+$/;
90+
91+
if (!decimalPattern.test(value) && !hexPattern.test(value)) {
92+
throw new HardhatError(
93+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
94+
{
95+
value,
96+
name,
97+
type: ParameterType.INT,
98+
},
99+
);
100+
}
101+
102+
return Number(value);
103+
}
104+
105+
function validateAndParseFloat(name: string, value: string): number {
106+
const decimalPattern = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]\d+)?$/;
107+
const hexPattern = /^0[xX][\dABCDEabcde]+$/;
108+
109+
if (!decimalPattern.test(value) && !hexPattern.test(value)) {
110+
throw new HardhatError(
111+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
112+
{
113+
value,
114+
name,
115+
type: ParameterType.FLOAT,
116+
},
117+
);
118+
}
119+
120+
return Number(value);
121+
}
122+
123+
function validateAndParseBigInt(name: string, value: string): bigint {
124+
const decimalPattern = /^\d+(?:n)?$/;
125+
const hexPattern = /^0[xX][\dABCDEabcde]+$/;
126+
127+
if (!decimalPattern.test(value) && !hexPattern.test(value)) {
128+
throw new HardhatError(
129+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
130+
{
131+
value,
132+
name,
133+
type: ParameterType.BIGINT,
134+
},
135+
);
136+
}
137+
138+
return BigInt(value.replace("n", ""));
139+
}
140+
141+
function validateAndParseBoolean(name: string, value: string): boolean {
142+
const normalizedValue = value.toLowerCase();
143+
144+
if (normalizedValue !== "true" && normalizedValue !== "false") {
145+
throw new HardhatError(
146+
HardhatError.ERRORS.ARGUMENTS.INVALID_VALUE_FOR_TYPE,
147+
{
148+
value,
149+
name,
150+
type: ParameterType.BOOLEAN,
151+
},
152+
);
153+
}
154+
155+
return normalizedValue === "true";
156+
}

0 commit comments

Comments
 (0)