diff --git a/messages/run-command.md b/messages/run-command.md index c6d70a01a..c817851b7 100644 --- a/messages/run-command.md +++ b/messages/run-command.md @@ -146,6 +146,22 @@ To output the results to multiple files, specify this flag multiple times. For e If you specify a file within a folder, such as `--output-file ./out/results.json`, the folder must already exist, or you get an error. If the file already exists, it's overwritten without prompting. +# flags.include-fixes.summary + +Include fix data for violations when available. + +# flags.include-fixes.description + +When enabled, the output includes fix information for violations that have auto-fixes available. Each fix contains a code location and the replacement code. This flag may increase analysis time because engines must perform additional processing to compute fixes. + +# flags.include-suggestions.summary + +Include suggestion data for violations when available. + +# flags.include-suggestions.description + +When enabled, the output includes suggestion information for violations that have suggestions available. Each suggestion contains a code location and a message describing the suggested change. + # flags.no-suppressions.summary Disable processing of inline suppression markers. diff --git a/src/commands/code-analyzer/run.ts b/src/commands/code-analyzer/run.ts index 94365291a..100f5e8a9 100644 --- a/src/commands/code-analyzer/run.ts +++ b/src/commands/code-analyzer/run.ts @@ -71,6 +71,17 @@ export default class RunCommand extends SfCommand implements Displayable { char: 'c', exists: true }), + // === Flags pertaining to fixes and suggestions === + 'include-fixes': Flags.boolean({ + summary: getMessage(BundleName.RunCommand, 'flags.include-fixes.summary'), + description: getMessage(BundleName.RunCommand, 'flags.include-fixes.description'), + default: false + }), + 'include-suggestions': Flags.boolean({ + summary: getMessage(BundleName.RunCommand, 'flags.include-suggestions.summary'), + description: getMessage(BundleName.RunCommand, 'flags.include-suggestions.description'), + default: false + }), 'no-suppressions': Flags.boolean({ summary: getMessage(BundleName.RunCommand, 'flags.no-suppressions.summary'), description: getMessage(BundleName.RunCommand, 'flags.no-suppressions.description'), @@ -91,6 +102,8 @@ export default class RunCommand extends SfCommand implements Displayable { 'severity-threshold': parsedFlags['severity-threshold'] === undefined ? undefined : convertThresholdToEnum(parsedFlags['severity-threshold'].toLowerCase()), 'target': parsedFlags['target'], + 'include-fixes': parsedFlags['include-fixes'], + 'include-suggestions': parsedFlags['include-suggestions'], 'no-suppressions': parsedFlags['no-suppressions'] }; await action.execute(runInput); diff --git a/src/lib/actions/RunAction.ts b/src/lib/actions/RunAction.ts index d5db9a456..5e2cd90e2 100644 --- a/src/lib/actions/RunAction.ts +++ b/src/lib/actions/RunAction.ts @@ -40,6 +40,8 @@ export type RunInput = { 'severity-threshold'?: SeverityLevel; target?: string[]; workspace: string[]; + 'include-fixes'?: boolean; + 'include-suggestions'?: boolean; 'no-suppressions'?: boolean; } @@ -81,7 +83,11 @@ export class RunAction { // that's when progress events can start being emitted. this.dependencies.progressListeners.forEach(listener => listener.listen(core)); const ruleSelection: RuleSelection = await core.selectRules(input['rule-selector'], {workspace}); - const runOptions: RunOptions = {workspace}; + const runOptions: RunOptions = { + workspace, + includeFixes: input['include-fixes'], + includeSuggestions: input['include-suggestions'] + }; const results: RunResults = await core.run(ruleSelection, runOptions); this.emitEngineTelemetry(ruleSelection, results, enginePlugins.flatMap(p => p.getAvailableEngineNames())); // After Core is done running, the listeners need to be told to stop, since some of them have persistent UI elements diff --git a/test/lib/actions/RunAction.test.ts b/test/lib/actions/RunAction.test.ts index aa5591c3b..09a5d8de1 100644 --- a/test/lib/actions/RunAction.test.ts +++ b/test/lib/actions/RunAction.test.ts @@ -407,6 +407,77 @@ describe('RunAction tests', () => { expect(spyTelemetryEmitter.getCapturedTelemetry()[3].data.violationCount).toEqual(0); }); }) + + describe('include-fixes and include-suggestions flags', () => { + it.each([ + {case: 'neither flag set', includeFixes: undefined, includeSuggestions: undefined}, + {case: 'include-fixes=false, include-suggestions=false', includeFixes: false, includeSuggestions: false}, + ])('When $case, both are passed as-is to RunOptions', async ({includeFixes, includeSuggestions}) => { + const input: RunInput = { + 'rule-selector': ['all'], + 'workspace': ['.'], + 'output-file': [], + 'include-fixes': includeFixes, + 'include-suggestions': includeSuggestions + }; + + + await action.execute(input); + const runOptions = engine1.runRulesCallHistory[0].runOptions; + expect(runOptions.includeFixes).toEqual(includeFixes); + expect(runOptions.includeSuggestions).toEqual(includeSuggestions); + }); + + it('When include-fixes=true, it is forwarded to RunOptions', async () => { + + const input: RunInput = { + 'rule-selector': ['all'], + 'workspace': ['.'], + 'output-file': [], + 'include-fixes': true, + 'include-suggestions': false + }; + + await action.execute(input); + + const runOptions = engine1.runRulesCallHistory[0].runOptions; + expect(runOptions.includeFixes).toBe(true); + expect(runOptions.includeSuggestions).toBe(false); + }); + + it('When include-suggestions=true, it is forwarded to RunOptions', async () => { + + const input: RunInput = { + 'rule-selector': ['all'], + 'workspace': ['.'], + 'output-file': [], + 'include-fixes': false, + 'include-suggestions': true + }; + + await action.execute(input); + + const runOptions = engine1.runRulesCallHistory[0].runOptions; + expect(runOptions.includeFixes).toBe(false); + expect(runOptions.includeSuggestions).toBe(true); + }); + + it('When both include-fixes=true and include-suggestions=true, both are forwarded to RunOptions', async () => { + const input: RunInput = { + 'rule-selector': ['all'], + 'workspace': ['.'], + 'output-file': [], + 'include-fixes': true, + 'include-suggestions': true + }; + + await action.execute(input); + + const runOptions = engine1.runRulesCallHistory[0].runOptions; + expect(runOptions.includeFixes).toBe(true); + expect(runOptions.includeSuggestions).toBe(true); + }); + }); }); // TODO: Whenever we decide to document the custom_engine_plugin_modules flag in our configuration file, then we'll want