validation.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import { argsert } from './argsert.js';
  2. import { assertNotStrictEqual, } from './typings/common-types.js';
  3. import { levenshtein as distance } from './utils/levenshtein.js';
  4. import { objFilter } from './utils/obj-filter.js';
  5. const specialKeys = ['$0', '--', '_'];
  6. export function validation(yargs, usage, shim) {
  7. const __ = shim.y18n.__;
  8. const __n = shim.y18n.__n;
  9. const self = {};
  10. self.nonOptionCount = function nonOptionCount(argv) {
  11. const demandedCommands = yargs.getDemandedCommands();
  12. const positionalCount = argv._.length + (argv['--'] ? argv['--'].length : 0);
  13. const _s = positionalCount - yargs.getInternalMethods().getContext().commands.length;
  14. if (demandedCommands._ &&
  15. (_s < demandedCommands._.min || _s > demandedCommands._.max)) {
  16. if (_s < demandedCommands._.min) {
  17. if (demandedCommands._.minMsg !== undefined) {
  18. usage.fail(demandedCommands._.minMsg
  19. ? demandedCommands._.minMsg
  20. .replace(/\$0/g, _s.toString())
  21. .replace(/\$1/, demandedCommands._.min.toString())
  22. : null);
  23. }
  24. else {
  25. usage.fail(__n('Not enough non-option arguments: got %s, need at least %s', 'Not enough non-option arguments: got %s, need at least %s', _s, _s.toString(), demandedCommands._.min.toString()));
  26. }
  27. }
  28. else if (_s > demandedCommands._.max) {
  29. if (demandedCommands._.maxMsg !== undefined) {
  30. usage.fail(demandedCommands._.maxMsg
  31. ? demandedCommands._.maxMsg
  32. .replace(/\$0/g, _s.toString())
  33. .replace(/\$1/, demandedCommands._.max.toString())
  34. : null);
  35. }
  36. else {
  37. usage.fail(__n('Too many non-option arguments: got %s, maximum of %s', 'Too many non-option arguments: got %s, maximum of %s', _s, _s.toString(), demandedCommands._.max.toString()));
  38. }
  39. }
  40. }
  41. };
  42. self.positionalCount = function positionalCount(required, observed) {
  43. if (observed < required) {
  44. usage.fail(__n('Not enough non-option arguments: got %s, need at least %s', 'Not enough non-option arguments: got %s, need at least %s', observed, observed + '', required + ''));
  45. }
  46. };
  47. self.requiredArguments = function requiredArguments(argv, demandedOptions) {
  48. let missing = null;
  49. for (const key of Object.keys(demandedOptions)) {
  50. if (!Object.prototype.hasOwnProperty.call(argv, key) ||
  51. typeof argv[key] === 'undefined') {
  52. missing = missing || {};
  53. missing[key] = demandedOptions[key];
  54. }
  55. }
  56. if (missing) {
  57. const customMsgs = [];
  58. for (const key of Object.keys(missing)) {
  59. const msg = missing[key];
  60. if (msg && customMsgs.indexOf(msg) < 0) {
  61. customMsgs.push(msg);
  62. }
  63. }
  64. const customMsg = customMsgs.length ? `\n${customMsgs.join('\n')}` : '';
  65. usage.fail(__n('Missing required argument: %s', 'Missing required arguments: %s', Object.keys(missing).length, Object.keys(missing).join(', ') + customMsg));
  66. }
  67. };
  68. self.unknownArguments = function unknownArguments(argv, aliases, positionalMap, isDefaultCommand, checkPositionals = true) {
  69. var _a;
  70. const commandKeys = yargs
  71. .getInternalMethods()
  72. .getCommandInstance()
  73. .getCommands();
  74. const unknown = [];
  75. const currentContext = yargs.getInternalMethods().getContext();
  76. Object.keys(argv).forEach(key => {
  77. if (!specialKeys.includes(key) &&
  78. !Object.prototype.hasOwnProperty.call(positionalMap, key) &&
  79. !Object.prototype.hasOwnProperty.call(yargs.getInternalMethods().getParseContext(), key) &&
  80. !self.isValidAndSomeAliasIsNotNew(key, aliases)) {
  81. unknown.push(key);
  82. }
  83. });
  84. if (checkPositionals &&
  85. (currentContext.commands.length > 0 ||
  86. commandKeys.length > 0 ||
  87. isDefaultCommand)) {
  88. argv._.slice(currentContext.commands.length).forEach(key => {
  89. if (!commandKeys.includes('' + key)) {
  90. unknown.push('' + key);
  91. }
  92. });
  93. }
  94. if (checkPositionals) {
  95. const demandedCommands = yargs.getDemandedCommands();
  96. const maxNonOptDemanded = ((_a = demandedCommands._) === null || _a === void 0 ? void 0 : _a.max) || 0;
  97. const expected = currentContext.commands.length + maxNonOptDemanded;
  98. if (expected < argv._.length) {
  99. argv._.slice(expected).forEach(key => {
  100. key = String(key);
  101. if (!currentContext.commands.includes(key) &&
  102. !unknown.includes(key)) {
  103. unknown.push(key);
  104. }
  105. });
  106. }
  107. }
  108. if (unknown.length) {
  109. usage.fail(__n('Unknown argument: %s', 'Unknown arguments: %s', unknown.length, unknown.map(s => (s.trim() ? s : `"${s}"`)).join(', ')));
  110. }
  111. };
  112. self.unknownCommands = function unknownCommands(argv) {
  113. const commandKeys = yargs
  114. .getInternalMethods()
  115. .getCommandInstance()
  116. .getCommands();
  117. const unknown = [];
  118. const currentContext = yargs.getInternalMethods().getContext();
  119. if (currentContext.commands.length > 0 || commandKeys.length > 0) {
  120. argv._.slice(currentContext.commands.length).forEach(key => {
  121. if (!commandKeys.includes('' + key)) {
  122. unknown.push('' + key);
  123. }
  124. });
  125. }
  126. if (unknown.length > 0) {
  127. usage.fail(__n('Unknown command: %s', 'Unknown commands: %s', unknown.length, unknown.join(', ')));
  128. return true;
  129. }
  130. else {
  131. return false;
  132. }
  133. };
  134. self.isValidAndSomeAliasIsNotNew = function isValidAndSomeAliasIsNotNew(key, aliases) {
  135. if (!Object.prototype.hasOwnProperty.call(aliases, key)) {
  136. return false;
  137. }
  138. const newAliases = yargs.parsed.newAliases;
  139. return [key, ...aliases[key]].some(a => !Object.prototype.hasOwnProperty.call(newAliases, a) || !newAliases[key]);
  140. };
  141. self.limitedChoices = function limitedChoices(argv) {
  142. const options = yargs.getOptions();
  143. const invalid = {};
  144. if (!Object.keys(options.choices).length)
  145. return;
  146. Object.keys(argv).forEach(key => {
  147. if (specialKeys.indexOf(key) === -1 &&
  148. Object.prototype.hasOwnProperty.call(options.choices, key)) {
  149. [].concat(argv[key]).forEach(value => {
  150. if (options.choices[key].indexOf(value) === -1 &&
  151. value !== undefined) {
  152. invalid[key] = (invalid[key] || []).concat(value);
  153. }
  154. });
  155. }
  156. });
  157. const invalidKeys = Object.keys(invalid);
  158. if (!invalidKeys.length)
  159. return;
  160. let msg = __('Invalid values:');
  161. invalidKeys.forEach(key => {
  162. msg += `\n ${__('Argument: %s, Given: %s, Choices: %s', key, usage.stringifiedValues(invalid[key]), usage.stringifiedValues(options.choices[key]))}`;
  163. });
  164. usage.fail(msg);
  165. };
  166. let implied = {};
  167. self.implies = function implies(key, value) {
  168. argsert('<string|object> [array|number|string]', [key, value], arguments.length);
  169. if (typeof key === 'object') {
  170. Object.keys(key).forEach(k => {
  171. self.implies(k, key[k]);
  172. });
  173. }
  174. else {
  175. yargs.global(key);
  176. if (!implied[key]) {
  177. implied[key] = [];
  178. }
  179. if (Array.isArray(value)) {
  180. value.forEach(i => self.implies(key, i));
  181. }
  182. else {
  183. assertNotStrictEqual(value, undefined, shim);
  184. implied[key].push(value);
  185. }
  186. }
  187. };
  188. self.getImplied = function getImplied() {
  189. return implied;
  190. };
  191. function keyExists(argv, val) {
  192. const num = Number(val);
  193. val = isNaN(num) ? val : num;
  194. if (typeof val === 'number') {
  195. val = argv._.length >= val;
  196. }
  197. else if (val.match(/^--no-.+/)) {
  198. val = val.match(/^--no-(.+)/)[1];
  199. val = !Object.prototype.hasOwnProperty.call(argv, val);
  200. }
  201. else {
  202. val = Object.prototype.hasOwnProperty.call(argv, val);
  203. }
  204. return val;
  205. }
  206. self.implications = function implications(argv) {
  207. const implyFail = [];
  208. Object.keys(implied).forEach(key => {
  209. const origKey = key;
  210. (implied[key] || []).forEach(value => {
  211. let key = origKey;
  212. const origValue = value;
  213. key = keyExists(argv, key);
  214. value = keyExists(argv, value);
  215. if (key && !value) {
  216. implyFail.push(` ${origKey} -> ${origValue}`);
  217. }
  218. });
  219. });
  220. if (implyFail.length) {
  221. let msg = `${__('Implications failed:')}\n`;
  222. implyFail.forEach(value => {
  223. msg += value;
  224. });
  225. usage.fail(msg);
  226. }
  227. };
  228. let conflicting = {};
  229. self.conflicts = function conflicts(key, value) {
  230. argsert('<string|object> [array|string]', [key, value], arguments.length);
  231. if (typeof key === 'object') {
  232. Object.keys(key).forEach(k => {
  233. self.conflicts(k, key[k]);
  234. });
  235. }
  236. else {
  237. yargs.global(key);
  238. if (!conflicting[key]) {
  239. conflicting[key] = [];
  240. }
  241. if (Array.isArray(value)) {
  242. value.forEach(i => self.conflicts(key, i));
  243. }
  244. else {
  245. conflicting[key].push(value);
  246. }
  247. }
  248. };
  249. self.getConflicting = () => conflicting;
  250. self.conflicting = function conflictingFn(argv) {
  251. Object.keys(argv).forEach(key => {
  252. if (conflicting[key]) {
  253. conflicting[key].forEach(value => {
  254. if (value && argv[key] !== undefined && argv[value] !== undefined) {
  255. usage.fail(__('Arguments %s and %s are mutually exclusive', key, value));
  256. }
  257. });
  258. }
  259. });
  260. if (yargs.getInternalMethods().getParserConfiguration()['strip-dashed']) {
  261. Object.keys(conflicting).forEach(key => {
  262. conflicting[key].forEach(value => {
  263. if (value &&
  264. argv[shim.Parser.camelCase(key)] !== undefined &&
  265. argv[shim.Parser.camelCase(value)] !== undefined) {
  266. usage.fail(__('Arguments %s and %s are mutually exclusive', key, value));
  267. }
  268. });
  269. });
  270. }
  271. };
  272. self.recommendCommands = function recommendCommands(cmd, potentialCommands) {
  273. const threshold = 3;
  274. potentialCommands = potentialCommands.sort((a, b) => b.length - a.length);
  275. let recommended = null;
  276. let bestDistance = Infinity;
  277. for (let i = 0, candidate; (candidate = potentialCommands[i]) !== undefined; i++) {
  278. const d = distance(cmd, candidate);
  279. if (d <= threshold && d < bestDistance) {
  280. bestDistance = d;
  281. recommended = candidate;
  282. }
  283. }
  284. if (recommended)
  285. usage.fail(__('Did you mean %s?', recommended));
  286. };
  287. self.reset = function reset(localLookup) {
  288. implied = objFilter(implied, k => !localLookup[k]);
  289. conflicting = objFilter(conflicting, k => !localLookup[k]);
  290. return self;
  291. };
  292. const frozens = [];
  293. self.freeze = function freeze() {
  294. frozens.push({
  295. implied,
  296. conflicting,
  297. });
  298. };
  299. self.unfreeze = function unfreeze() {
  300. const frozen = frozens.pop();
  301. assertNotStrictEqual(frozen, undefined, shim);
  302. ({ implied, conflicting } = frozen);
  303. };
  304. return self;
  305. }