Repository: oclif/core (ts)

Issue:
Unnecessary warnings when command flags are marked as deprecated
**Is your feature request related to a problem? Please describe.**
I noticed that there was an [update](https://github.com/oclif/core/pull/511/) with version 1.18.0 to show that commands or flags are deprecated. I attempted to use this new feature with certain flags that are part of my application, but ran into what feels like a UX issue. With this update, no matter what flags are actually used, warnings about all of the deprecated flags of a command will be printed.

For example, [this test](https://github.com/oclif/core/pull/511/files#diff-e63382cfea41aa63be2c32caf2d9f0a840fa54e40a9d4898a9c3f0b4c6f7919fR236) shows that the flag `name` is deprecated. However, even though the command is run with no flags, a warning about the deprecated command is expected to be printed.

This situation could produce a lot of spam and be confusing for the user, especially if there are many deprecated flags in the command class none of which the user included in the actual command.

**Describe the solution you'd like**
The logic for deprecated flags should only print a warning for the deprecated command(s) that a user actually used. If I used two out of four deprecated flags, I'd expect two warnings. If I didn't use any deprecated commands, I'd expect no warnings.


Pull request:
feat: make flag deprecation warnings less noisy
Fixes #525 

Only show flag deprecation warning if the deprecated flag was provided by the user.

Patch:
diff --git a/src/command.ts b/src/command.ts
index 45c0c44..7efd940 100644
--- a/src/command.ts
+++ b/src/command.ts
@@ -246,20 +246,23 @@ export default abstract class Command {
     const g: any = global
     g['http-call'] = g['http-call'] || {}
     g['http-call']!.userAgent = this.config.userAgent
-    this.checkForDeprecations()
+    this.warnIfCommandDeprecated()
   }
 
-  protected checkForDeprecations() {
+  protected warnIfFlagDeprecated(flags: Record<string, unknown>) {
+    for (const flag of Object.keys(flags)) {
+      const deprecated = this.ctor.flags[flag]?.deprecated
+      if (deprecated) {
+        this.warn(formatFlagDeprecationWarning(flag, deprecated))
+      }
+    }
+  }
+
+  protected warnIfCommandDeprecated(): void {
     if (this.ctor.state === 'deprecated') {
       const cmdName = toConfiguredId(this.ctor.id, this.config)
       this.warn(formatCommandDeprecationWarning(cmdName, this.ctor.deprecationOptions))
     }
-
-    for (const [flag, opts] of Object.entries(this.ctor.flags ?? {})) {
-      if (opts.deprecated) {
-        this.warn(formatFlagDeprecationWarning(flag, opts.deprecated))
-      }
-    }
   }
 
   protected async parse<F extends Interfaces.FlagOutput, G extends Interfaces.FlagOutput, A extends { [name: string]: any }>(options?: Interfaces.Input<F, G>, argv = this.argv): Promise<Interfaces.ParserOutput<F, G, A>> {
@@ -268,7 +271,10 @@ export default abstract class Command {
     // the spread operator doesn't work with getters so we have to manually add it here
     opts.flags = options?.flags
     opts.args = options?.args
-    return Parser.parse(argv, opts)
+    const results = await Parser.parse<F, G, A>(argv, opts)
+    this.warnIfFlagDeprecated(results.flags ?? {})
+
+    return results
   }
 
   protected async catch(err: Interfaces.CommandError): Promise<any> {


Test patch:
diff --git a/test/command/command.test.ts b/test/command/command.test.ts
index 72e8693..e8b7629 100644
--- a/test/command/command.test.ts
+++ b/test/command/command.test.ts
@@ -246,18 +246,47 @@ describe('command', () => {
               version: '2.0.0',
             },
           }),
+          force: Flags.boolean(),
         }
 
         async run() {
+          await this.parse(CMD)
           this.log('running command')
         }
       }
-      await CMD.run([])
+      await CMD.run(['--name', 'astro'])
     })
     .do(ctx => expect(ctx.stderr).to.include('Warning: The "name" flag has been deprecated'))
     .it('shows warning for deprecated flags')
   })
 
+  describe('deprecated flags that are not provided', () => {
+    fancy
+    .stdout()
+    .stderr()
+    .do(async () => {
+      class CMD extends Command {
+        static flags = {
+          name: Flags.string({
+            deprecated: {
+              to: '--full-name',
+              version: '2.0.0',
+            },
+          }),
+          force: Flags.boolean(),
+        }
+
+        async run() {
+          await this.parse(CMD)
+          this.log('running command')
+        }
+      }
+      await CMD.run(['--force'])
+    })
+    .do(ctx => expect(ctx.stderr).to.not.include('Warning: The "name" flag has been deprecated'))
+    .it('does not show warning for deprecated flags if they are not provided')
+  })
+
   describe('deprecated state', () => {
     fancy
     .stdout()
@@ -273,7 +302,7 @@ describe('command', () => {
       await CMD.run([])
     })
     .do(ctx => expect(ctx.stderr).to.include('Warning: The "my:command" command has been deprecated'))
-    .it('shows warning for deprecated flags')
+    .it('shows warning for deprecated command')
   })
 
   describe('deprecated state with options', () => {
@@ -300,7 +329,7 @@ describe('command', () => {
       expect(ctx.stderr).to.include('in version 2.0.0')
       expect(ctx.stderr).to.include('Use "my:other:command" instead')
     })
-    .it('shows warning for deprecated flags')
+    .it('shows warning for deprecated command with custom options')
   })
 
   describe('stdout err', () => {