diff --git a/messages/run-command.md b/messages/run-command.md index 1117844b7..c84714250 100644 --- a/messages/run-command.md +++ b/messages/run-command.md @@ -142,6 +142,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. + # error.invalid-severity-threshold Expected --severity-threshold=%s to be one of: %s diff --git a/src/commands/code-analyzer/run.ts b/src/commands/code-analyzer/run.ts index ff3773332..17d2f08b2 100644 --- a/src/commands/code-analyzer/run.ts +++ b/src/commands/code-analyzer/run.ts @@ -70,7 +70,18 @@ export default class RunCommand extends SfCommand implements Displayable { description: getMessage(BundleName.RunCommand, 'flags.config-file.description'), 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 + }), }; public async run(): Promise { @@ -84,7 +95,9 @@ export default class RunCommand extends SfCommand implements Displayable { 'workspace': parsedFlags['workspace'], 'severity-threshold': parsedFlags['severity-threshold'] === undefined ? undefined : convertThresholdToEnum(parsedFlags['severity-threshold'].toLowerCase()), - 'target': parsedFlags['target'] + 'target': parsedFlags['target'], + 'include-fixes': parsedFlags['include-fixes'], + 'include-suggestions': parsedFlags['include-suggestions'] }; await action.execute(runInput); } diff --git a/src/lib/actions/RunAction.ts b/src/lib/actions/RunAction.ts index 510620c3d..2b02b988d 100644 --- a/src/lib/actions/RunAction.ts +++ b/src/lib/actions/RunAction.ts @@ -40,7 +40,8 @@ export type RunInput = { 'severity-threshold'?: SeverityLevel; target?: string[]; workspace: string[]; - + 'include-fixes'?: boolean; + 'include-suggestions'?: boolean; } export class RunAction { @@ -75,7 +76,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