Repository: oclif/core
Pull request #598 (2023-01-23 16:09:31)

feat: specify flag name in parse error message (#589)
* feat: specify flag name in parse errors

* fix: impl. for specifying flag name in parse errors


Patch:
diff --git a/src/parser/errors.ts b/src/parser/errors.ts
index b0c952f..5be4539 100644
--- a/src/parser/errors.ts
+++ b/src/parser/errors.ts
@@ -4,7 +4,7 @@ import Deps from './deps'
 import * as Help from './help'
 import * as List from './list'
 import * as chalk from 'chalk'
-import {ParserArg, CLIParseErrorOptions, OptionFlag, Flag} from '../interfaces'
+import {ParserArg, CLIParseErrorOptions, OptionFlag, Flag, BooleanFlag} from '../interfaces'
 import {uniq} from '../config/util'
 
 export {CLIError} from '../errors'
@@ -94,6 +94,13 @@ export class FlagInvalidOptionError extends CLIParseError {
   }
 }
 
+export class FailedFlagParseError extends CLIParseError {
+  constructor(flag: BooleanFlag<any> | OptionFlag<any>, errMsg: string) {
+    const message = `Parsing --${flag.name} \n\t${errMsg}`
+    super({parse: {}, message})
+  }
+}
+
 export class ArgInvalidOptionError extends CLIParseError {
   constructor(arg: ParserArg<any>, input: string) {
     const message = `Expected ${input} to be one of: ${arg.options!.join(', ')}`
diff --git a/src/parser/parse.ts b/src/parser/parse.ts
index 0400d64..ad7be39 100644
--- a/src/parser/parse.ts
+++ b/src/parser/parse.ts
@@ -192,13 +192,14 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
         }
 
         // eslint-disable-next-line no-await-in-loop
-        flags[token.flag] = await flag.parse(flags[token.flag], this.context, flag)
+        flags[token.flag] = await this._parseFlag(flags[token.flag], flag)
       } else {
         const input = token.input
         this._validateOptions(flag, input)
 
         // eslint-disable-next-line no-await-in-loop
-        const value = flag.parse ? await flag.parse(input, this.context, flag) : input
+        const value = await this._parseFlag(input, flag)
+
         if (flag.multiple) {
           flags[token.flag] = flags[token.flag] || []
           flags[token.flag].push(...(Array.isArray(value) ? value : [value]))
@@ -237,6 +238,18 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
     return flags
   }
 
+  private async _parseFlag(input: any, flag: BooleanFlag<T> | OptionFlag<T>) {
+    try {
+      if (flag.type === 'boolean') {
+        return await flag.parse(input, this.context, flag)
+      }
+
+      return flag.parse ? await flag.parse(input, this.context, flag) : input
+    } catch (error: any) {
+      throw new m.errors.FailedFlagParseError(flag, error.message)
+    }
+  }
+
   private _validateOptions(flag: OptionFlag<any>, input: string) {
     if (flag.options && !flag.options.includes(input))
       throw new m.errors.FlagInvalidOptionError(flag, input)


Test patch:
diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts
index 9179c6d..b1bf7d2 100644
--- a/test/parser/parse.test.ts
+++ b/test/parser/parse.test.ts
@@ -411,7 +411,7 @@ See more help with --help`)
           message = error.message
         }
 
-        expect(message).to.equal('Expected an integer but received: 3.14')
+        expect(message).to.equal('Parsing --int \n\tExpected an integer but received: 3.14\nSee more help with --help')
       })
 
       it('does not parse fractions', async () => {
@@ -424,7 +424,7 @@ See more help with --help`)
           message = error.message
         }
 
-        expect(message).to.equal('Expected an integer but received: 3/4')
+        expect(message).to.equal('Parsing --int \n\tExpected an integer but received: 3/4\nSee more help with --help')
       })
 
       it('does not parse strings', async () => {
@@ -437,7 +437,7 @@ See more help with --help`)
           message = error.message
         }
 
-        expect(message).to.equal('Expected an integer but received: s10')
+        expect(message).to.equal('Parsing --int \n\tExpected an integer but received: s10\nSee more help with --help')
       })
 
       describe('min/max', () => {
@@ -476,7 +476,7 @@ See more help with --help`)
             message = error.message
           }
 
-          expect(message).to.equal('Expected an integer greater than or equal to 10 but received: 9')
+          expect(message).to.equal('Parsing --int \n\tExpected an integer greater than or equal to 10 but received: 9\nSee more help with --help')
         })
         it('max fail gt', async () => {
           let message = ''
@@ -488,7 +488,7 @@ See more help with --help`)
             message = error.message
           }
 
-          expect(message).to.equal('Expected an integer less than or equal to 20 but received: 21')
+          expect(message).to.equal('Parsing --int \n\tExpected an integer less than or equal to 20 but received: 21\nSee more help with --help')
         })
       })
     })
@@ -514,7 +514,7 @@ See more help with --help`)
         } catch (error_) {
           const error = error_ as Error
           expect(error.message).to.equal(
-            customParseException)
+            `Parsing --int \n\t${customParseException}\nSee more help with --help`)
         }
       })
     })
@@ -860,7 +860,7 @@ See more help with --help`)
         message = error.message
       }
 
-      expect(message).to.equal('Expected a valid url but received: example')
+      expect(message).to.equal('Parsing --foo \n\tExpected a valid url but received: example\nSee more help with --help')
     })
   })
 
@@ -1251,7 +1251,7 @@ See more help with --help`)
         } catch (error_) {
           const error = error_ as Error
           expect(error.message).to.equal(
-            `No directory found at ${testDir}`,
+            `Parsing --dir \n\tNo directory found at ${testDir}\nSee more help with --help`,
           )
         }
       })
@@ -1266,7 +1266,7 @@ See more help with --help`)
         } catch (error_) {
           const error = error_ as Error
           expect(error.message).to.equal(
-            `${testDir} exists but is not a directory`)
+            `Parsing --dir \n\t${testDir} exists but is not a directory\nSee more help with --help`)
         }
       })
       describe('custom parse functions', () => {
@@ -1291,7 +1291,7 @@ See more help with --help`)
           } catch (error_) {
             const error = error_ as Error
             expect(error.message).to.equal(
-              customParseException)
+              `Parsing --dir \n\t${customParseException}\nSee more help with --help`)
           }
         })
       })
@@ -1330,7 +1330,7 @@ See more help with --help`)
           throw new Error(`Should have thrown an error ${JSON.stringify(out)}`)
         } catch (error_) {
           const error = error_ as Error
-          expect(error.message).to.equal(`No file found at ${testFile}`)
+          expect(error.message).to.equal(`Parsing --file \n\tNo file found at ${testFile}\nSee more help with --help`)
         }
       })
       it('fails when file exists but is not a file', async () => {
@@ -1343,7 +1343,7 @@ See more help with --help`)
           throw new Error(`Should have thrown an error ${JSON.stringify(out)}`)
         } catch (error_) {
           const error = error_ as Error
-          expect(error.message).to.equal(`${testFile} exists but is not a file`)
+          expect(error.message).to.equal(`Parsing --file \n\t${testFile} exists but is not a file\nSee more help with --help`)
         }
       })
       describe('custom parse functions', () => {
@@ -1368,7 +1368,7 @@ See more help with --help`)
           } catch (error_) {
             const error = error_ as Error
             expect(error.message).to.equal(
-              customParseException)
+              `Parsing --dir \n\t${customParseException}\nSee more help with --help`)
           }
         })
       })