123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- "use strict";
- const path = require("path");
- // Based on https://github.com/webpack/webpack/blob/master/lib/cli.js
- // Please do not modify it
- /** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */
- /**
- * @typedef {Object} Problem
- * @property {ProblemType} type
- * @property {string} path
- * @property {string} argument
- * @property {any=} value
- * @property {number=} index
- * @property {string=} expected
- */
- /**
- * @typedef {Object} LocalProblem
- * @property {ProblemType} type
- * @property {string} path
- * @property {string=} expected
- */
- /**
- * @typedef {Object} ArgumentConfig
- * @property {string} description
- * @property {string} path
- * @property {boolean} multiple
- * @property {"enum"|"string"|"path"|"number"|"boolean"|"RegExp"|"reset"} type
- * @property {any[]=} values
- */
- /**
- * @typedef {Object} Argument
- * @property {string} description
- * @property {"string"|"number"|"boolean"} simpleType
- * @property {boolean} multiple
- * @property {ArgumentConfig[]} configs
- */
- const cliAddedItems = new WeakMap();
- /**
- * @param {any} config configuration
- * @param {string} schemaPath path in the config
- * @param {number | undefined} index index of value when multiple values are provided, otherwise undefined
- * @returns {{ problem?: LocalProblem, object?: any, property?: string | number, value?: any }} problem or object with property and value
- */
- const getObjectAndProperty = (config, schemaPath, index = 0) => {
- if (!schemaPath) {
- return { value: config };
- }
- const parts = schemaPath.split(".");
- const property = parts.pop();
- let current = config;
- let i = 0;
- for (const part of parts) {
- const isArray = part.endsWith("[]");
- const name = isArray ? part.slice(0, -2) : part;
- let value = current[name];
- if (isArray) {
- // eslint-disable-next-line no-undefined
- if (value === undefined) {
- value = {};
- current[name] = [...Array.from({ length: index }), value];
- cliAddedItems.set(current[name], index + 1);
- } else if (!Array.isArray(value)) {
- return {
- problem: {
- type: "unexpected-non-array-in-path",
- path: parts.slice(0, i).join("."),
- },
- };
- } else {
- let addedItems = cliAddedItems.get(value) || 0;
- while (addedItems <= index) {
- // eslint-disable-next-line no-undefined
- value.push(undefined);
- // eslint-disable-next-line no-plusplus
- addedItems++;
- }
- cliAddedItems.set(value, addedItems);
- const x = value.length - addedItems + index;
- // eslint-disable-next-line no-undefined
- if (value[x] === undefined) {
- value[x] = {};
- } else if (value[x] === null || typeof value[x] !== "object") {
- return {
- problem: {
- type: "unexpected-non-object-in-path",
- path: parts.slice(0, i).join("."),
- },
- };
- }
- value = value[x];
- }
- // eslint-disable-next-line no-undefined
- } else if (value === undefined) {
- // eslint-disable-next-line no-multi-assign
- value = current[name] = {};
- } else if (value === null || typeof value !== "object") {
- return {
- problem: {
- type: "unexpected-non-object-in-path",
- path: parts.slice(0, i).join("."),
- },
- };
- }
- current = value;
- // eslint-disable-next-line no-plusplus
- i++;
- }
- const value = current[/** @type {string} */ (property)];
- if (/** @type {string} */ (property).endsWith("[]")) {
- const name = /** @type {string} */ (property).slice(0, -2);
- // eslint-disable-next-line no-shadow
- const value = current[name];
- // eslint-disable-next-line no-undefined
- if (value === undefined) {
- // eslint-disable-next-line no-undefined
- current[name] = [...Array.from({ length: index }), undefined];
- cliAddedItems.set(current[name], index + 1);
- // eslint-disable-next-line no-undefined
- return { object: current[name], property: index, value: undefined };
- } else if (!Array.isArray(value)) {
- // eslint-disable-next-line no-undefined
- current[name] = [value, ...Array.from({ length: index }), undefined];
- cliAddedItems.set(current[name], index + 1);
- // eslint-disable-next-line no-undefined
- return { object: current[name], property: index + 1, value: undefined };
- }
- let addedItems = cliAddedItems.get(value) || 0;
- while (addedItems <= index) {
- // eslint-disable-next-line no-undefined
- value.push(undefined);
- // eslint-disable-next-line no-plusplus
- addedItems++;
- }
- cliAddedItems.set(value, addedItems);
- const x = value.length - addedItems + index;
- // eslint-disable-next-line no-undefined
- if (value[x] === undefined) {
- value[x] = {};
- } else if (value[x] === null || typeof value[x] !== "object") {
- return {
- problem: {
- type: "unexpected-non-object-in-path",
- path: schemaPath,
- },
- };
- }
- return {
- object: value,
- property: x,
- value: value[x],
- };
- }
- return { object: current, property, value };
- };
- /**
- * @param {ArgumentConfig} argConfig processing instructions
- * @param {any} value the value
- * @returns {any | undefined} parsed value
- */
- const parseValueForArgumentConfig = (argConfig, value) => {
- // eslint-disable-next-line default-case
- switch (argConfig.type) {
- case "string":
- if (typeof value === "string") {
- return value;
- }
- break;
- case "path":
- if (typeof value === "string") {
- return path.resolve(value);
- }
- break;
- case "number":
- if (typeof value === "number") {
- return value;
- }
- if (typeof value === "string" && /^[+-]?\d*(\.\d*)[eE]\d+$/) {
- const n = +value;
- if (!isNaN(n)) return n;
- }
- break;
- case "boolean":
- if (typeof value === "boolean") {
- return value;
- }
- if (value === "true") {
- return true;
- }
- if (value === "false") {
- return false;
- }
- break;
- case "RegExp":
- if (value instanceof RegExp) {
- return value;
- }
- if (typeof value === "string") {
- // cspell:word yugi
- const match = /^\/(.*)\/([yugi]*)$/.exec(value);
- if (match && !/[^\\]\//.test(match[1])) {
- return new RegExp(match[1], match[2]);
- }
- }
- break;
- case "enum":
- if (/** @type {any[]} */ (argConfig.values).includes(value)) {
- return value;
- }
- for (const item of /** @type {any[]} */ (argConfig.values)) {
- if (`${item}` === value) return item;
- }
- break;
- case "reset":
- if (value === true) {
- return [];
- }
- break;
- }
- };
- /**
- * @param {ArgumentConfig} argConfig processing instructions
- * @returns {string | undefined} expected message
- */
- const getExpectedValue = (argConfig) => {
- switch (argConfig.type) {
- default:
- return argConfig.type;
- case "boolean":
- return "true | false";
- case "RegExp":
- return "regular expression (example: /ab?c*/)";
- case "enum":
- return /** @type {any[]} */ (argConfig.values)
- .map((v) => `${v}`)
- .join(" | ");
- case "reset":
- return "true (will reset the previous value to an empty array)";
- }
- };
- /**
- * @param {any} config configuration
- * @param {string} schemaPath path in the config
- * @param {any} value parsed value
- * @param {number | undefined} index index of value when multiple values are provided, otherwise undefined
- * @returns {LocalProblem | null} problem or null for success
- */
- const setValue = (config, schemaPath, value, index) => {
- const { problem, object, property } = getObjectAndProperty(
- config,
- schemaPath,
- index
- );
- if (problem) {
- return problem;
- }
- object[/** @type {string} */ (property)] = value;
- return null;
- };
- /**
- * @param {ArgumentConfig} argConfig processing instructions
- * @param {any} config configuration
- * @param {any} value the value
- * @param {number | undefined} index the index if multiple values provided
- * @returns {LocalProblem | null} a problem if any
- */
- const processArgumentConfig = (argConfig, config, value, index) => {
- // eslint-disable-next-line no-undefined
- if (index !== undefined && !argConfig.multiple) {
- return {
- type: "multiple-values-unexpected",
- path: argConfig.path,
- };
- }
- const parsed = parseValueForArgumentConfig(argConfig, value);
- // eslint-disable-next-line no-undefined
- if (parsed === undefined) {
- return {
- type: "invalid-value",
- path: argConfig.path,
- expected: getExpectedValue(argConfig),
- };
- }
- const problem = setValue(config, argConfig.path, parsed, index);
- if (problem) {
- return problem;
- }
- return null;
- };
- /**
- * @param {Record<string, Argument>} args object of arguments
- * @param {any} config configuration
- * @param {Record<string, string | number | boolean | RegExp | (string | number | boolean | RegExp)[]>} values object with values
- * @returns {Problem[] | null} problems or null for success
- */
- const processArguments = (args, config, values) => {
- /**
- * @type {Problem[]}
- */
- const problems = [];
- for (const key of Object.keys(values)) {
- const arg = args[key];
- if (!arg) {
- problems.push({
- type: "unknown-argument",
- path: "",
- argument: key,
- });
- // eslint-disable-next-line no-continue
- continue;
- }
- /**
- * @param {any} value
- * @param {number | undefined} i
- */
- const processValue = (value, i) => {
- const currentProblems = [];
- for (const argConfig of arg.configs) {
- const problem = processArgumentConfig(argConfig, config, value, i);
- if (!problem) {
- return;
- }
- currentProblems.push({
- ...problem,
- argument: key,
- value,
- index: i,
- });
- }
- problems.push(...currentProblems);
- };
- const value = values[key];
- if (Array.isArray(value)) {
- for (let i = 0; i < value.length; i++) {
- processValue(value[i], i);
- }
- } else {
- // eslint-disable-next-line no-undefined
- processValue(value, undefined);
- }
- }
- if (problems.length === 0) {
- return null;
- }
- return problems;
- };
- module.exports = processArguments;
|