import { CliCommandDefinition, CliCommandDefinitionOption, CliProgramDefinition } from '../types'; import { CliProgram } from './CliProgram'; /** * Validates values in a CliProgram to ensure they individually and collectively meet constraints * required by the Parser. */ export class ProgramValidator { public get errors(): string[] { return this._errors || []; } private _errors: string[] = []; private _cmdMap: Map = new Map(); constructor(private program: CliProgram, private internalOptions: CliCommandDefinitionOption[] = []) {} public validate = (): string[] => { this._errors = []; this.validateProgram(); this.validateCommands(); this.validateOptions(); return this._errors; }; private pushError = (msg: string) => { this._errors.push(msg); }; private validateCommand = (cmd: CliCommandDefinition) => { const argNames: { [name: string]: boolean } = {}; if (cmd.arguments) { // const isVariadic = defArgs.length > 0 && defArgs[defArgs.length-1].isVariadic let seenOptional = false; let seenVariadic = false; cmd.arguments.forEach(a => { if (argNames[a.name]) { this.pushError(`Duplicate Argument '${a.name}' for Command '${cmd.name}'`); } argNames[a.name] = true; if (seenVariadic) { this.pushError( `A variadic argument must be the last argument in a Command Definition ('${a.name}' for Command '${cmd.name}')` ); } if (!a.isOptional && seenOptional) { this.pushError( `Optional Argument '${a.name}' for Command '${cmd.name}' cannot follow required argument` ); } if (a.isVariadic && seenVariadic) { this.pushError(`Duplicate Variadic Argument '${a.name}' for Command '${cmd.name}'`); } if (a.isVariadic && seenOptional) { this.pushError( `Variadic Argument '${a.name}' for Command '${cmd.name}' cannot follow optional argument` ); } if (a.default && !a.isOptional) { this.pushError( `Argument '${a.name}' for Command '${cmd.name}' cannot have a default value for a required argument` ); } if (a.validate) { if (!(typeof a.validate === 'function' || a.validate instanceof RegExp)) { this.pushError( `Argument '${a.name}' for Command '${cmd.name}' validate property must be a function or Regular expression` ); } } if (a.transform) { if (typeof a.transform !== 'function') { this.pushError( `Argument '${a.name}' for Command '${cmd.name}' transformValue property must be a function` ); } } seenOptional = a.isOptional; seenVariadic = a.isVariadic; }); } }; private validateOptions = () => { // ignoring internal options to allow over-write (TBD: will that work in parser?) // Check for dupes, (in a serious over-kill way..) const globalMap: Map = new Map(); (this.program.options || []).forEach(o => { if (globalMap.has(o.name)) { this.pushError(`Duplicate Global Option '${o.name}'`); } }); this.program.commands.forEach(cmd => { const cmdMap: Map = new Map(); (cmd.options || []).forEach(o => { if (globalMap.has(o.name)) { this.pushError( `Option '${o.name}' for Command '${cmd.name}' conflicts with global option with same name` ); } else if (cmdMap.has(o.name)) { this.pushError(`Duplicate Option '${o.name}' for Command '${cmd.name}'`); } else { cmdMap.set(o.name, o); } }); }); }; private validateCommands = () => { // validate program command definitions this.program.commands.forEach(d => { if (this._cmdMap.has(d.name)) { this.pushError(`Duplicate Command Definition for ${d.name}`); } this._cmdMap.set(d.name, d); this.validateCommand(d); }); }; private validateProgram = () => { // validate program definition if (this.program.defaultCommandName) { if (!this.program.commands.find(c => c.name === this.program.defaultCommandName)) { this.pushError(`No Command Definition for DefaultCommand: ${this.program.defaultCommandName}`); } } }; }