Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/fixCookbookService.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import { Diagnostic } from '../../../../vscodeTypes';56import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';7import { createServiceIdentifier } from '../../../../util/common/services';8import { pythonRuffCookbooks } from './pythonCookbookData';910export const IFixCookbookService = createServiceIdentifier<IFixCookbookService>('IFixCookbookService');1112export interface IFixCookbookService {13readonly _serviceBrand: undefined;14getCookbook(language: string, diagnostic: Diagnostic): Cookbook;15}1617export enum ContextLocation {18ParentCallDefinition,19DefinitionAtLocation20}2122export type ManualSuggestedFix = {23title: string;24/** The copilot query is equal to title + message. */25message: string;26/** If true, replace the diagnostic provider's message with this message. */27replaceMessage?: string;28/** If true, display definition */29additionalContext?: ContextLocation;30};3132export class FixCookbookService implements IFixCookbookService {33readonly _serviceBrand: undefined;34constructor(35@ITelemetryService private readonly telemetryService: ITelemetryService36) {37// Always enable the Ruff cookbook by default38errorPrompts.Ruff = pythonRuffCookbooks;39}4041getCookbook(language: string, diagnostic: Diagnostic): Cookbook {42const code = typeof diagnostic.code === 'object' ? diagnostic.code.value : diagnostic.code;43const fixes = this._getManualSuggestedFixes(language, diagnostic.source as Provider | undefined, code);44return {45fixes,46messageReplacement() {47for (const fix of fixes) {48if (fix.replaceMessage !== undefined) {49return fix.replaceMessage;50}51}52return undefined;53},54additionalContext() {55const definitions: ContextLocation[] = [];56for (const fix of fixes) {57if (fix.additionalContext !== undefined) {58definitions.push(fix.additionalContext);59}60}61return definitions;62}63};64}6566private _getManualSuggestedFixes(languageId: string, provider: Provider | undefined, diagnostic: string | number | undefined): ManualSuggestedFix[] {67if (!provider || diagnostic === undefined) {68return [];69}7071const providerPrompts = errorPrompts[provider];72if (!providerPrompts) {73return [];74}7576const diagnosticPrompts = providerPrompts[diagnostic];77if (!diagnosticPrompts) {78return [];79}8081// send telemetry82/* __GDPR__83"cookbook.accessed" : {84"owner": "luabud",85"comment": "Reports when a cookbook entry is accessed for a diagnostic.",86"languageID": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The current file language." },87"diagnosticCode": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The diagnostic code accessed in the cookbook." },88"provider": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The tool which is the diagnostic provider." }89}90*/91this.telemetryService.sendMSFTTelemetryEvent('cookbook.accessed', {92languageId,93diagnosticCode: diagnostic.toString(),94provider95});9697// Ensure result is always an array of ManualSuggestedFix98const prompts = Array.isArray(diagnosticPrompts) ? diagnosticPrompts : [diagnosticPrompts];99return prompts.map(prompt => typeof prompt === 'string' ? { title: prompt, message: '' } : prompt);100}101102103}104105export interface Cookbook {106messageReplacement(): string | undefined;107additionalContext(): ContextLocation[];108readonly fixes: readonly ManualSuggestedFix[];109}110111112/** add diagnostic providers here as needed */113type Provider = 'eslint' | 'ts' | 'pylint' | 'Pylint' | 'Ruff';114type Prompt = string | ManualSuggestedFix | (string | ManualSuggestedFix)[];115type CookbookInternal = Record<Provider, Record<string, Prompt>>;116117118const errorPrompts: CookbookInternal = {119'Ruff': {}, // default to empty during experimentation120121'pylint': {122'C0301': [123'Split into many short lines to make sure each line is less than 20 tokens; split into many more lines than you normally would. Make sure to do the following: You must split all long strings, comments, and dictionary arguments and lists into shorter lines.',124]125},126'Pylint': {127'C0301:line-too-long': [128'Split into many short lines to make sure each line is less than 20 tokens; split into many more lines than you normally would. Make sure to do the following: You must split all long strings, comments, and dictionary arguments and lists into shorter lines.',129]130},131'ts': {1322345: { title: 'Use this declaration and other usages as examples.', message: '', additionalContext: ContextLocation.ParentCallDefinition },1332554: { title: 'Use this declaration and other usages as examples.', message: '', additionalContext: ContextLocation.ParentCallDefinition },134},135'eslint': {136'class-methods-use-this': [137'Make the method static.',138'Move the method outside of the class.',139'Rewrite the method to use properties of the class.',140],141'consistent-this': [142'Use the alias for this required by eslint consistent-this rule.',143'Use this directly instead of an alias.',144],145'constructor-super': {146title: 'Add missing super call and pass through new arguments.',147message: 'The code is missing a super call in the constructor. Copy base class parameters to this constructor and pass them to super.',148replaceMessage: 'Missing super call in constructor',149},150'func-names': [151'Give the function expression the same name as the variable it is assigned to.',152'Convert the function to an arrow function.',153],154'func-style': [155{156title: 'Convert the function declaration to an expression.',157message: 'The function expression should be assigned to a variable with the name of the original function declaration.',158},159],160'max-lines-per-function': [161'Split into multiple functions.',162],163'max-nested-callbacks': [164'Rewrite to avoid at least one callback.',165],166'max-params': [167{168title: 'Rewrite the signature to use an object parameter.',169message: 'Preserve all the parameters of the original signature.'170}171],172'max-statements': [173'Split into multiple functions.',174],175'no-case-declarations': [176'Surround the case block with braces.',177'Move the declaration outside the case block.',178],179'no-dupe-else-if': [180'Fix the duplicate condition to be different from the first.',181'Remove the duplicate condition',182],183'no-duplicate-case': [184{185title: 'Change the duplicate condition to be different.',186message: 'Do not delete the duplicate case, just fix it',187replaceMessage: 'Duplicated condition.'188},189'Remove the duplicate condition',190],191'no-duplicate-imports': 'Merge the duplicated import lines.',192'no-fallthrough': [193{194title: 'Rewrite to avoid fallthrough.',195message: 'Use the return value of the following cases and copy it to the preceding fallthrough case.',196replaceMessage: 'Fallthrough case in switch statement.'197},198'Add a // fallthrough comment.',199'Add a break statement.',200],201'no-inner-declarations': [202'Move the inner declaration to the top of its containing function.',203'Move the inner declaration to the bottom of its containing function.',204'Change the inner function declaration to an expression.',205],206'no-multi-assign': 'Assign each variable in separate statements.',207'no-negated-condition': 'Invert the branches of the conditional.',208'no-new': [209'Convert the class to functions.',210'Assign the resulting object to a variable.',211],212'no-sequences': [213{ title: 'Wrap the whole comma sequence in parentheses.', message: '', replaceMessage: 'Unnecessary comma sequence' },214{ title: 'Rewrite, preserving the original behavior.', message: 'The last element of the comma sequence is the one returned.' },215{ title: 'Delete the non-final elements of the sequence.', message: 'They are unused unless it is for side effects.' },216],217'no-sparse-arrays': [218'Remove duplicated commas.',219'Add default values for the missing elements.',220],221'require-await': [222'Removed the unused async keyword.',223{224title: 'Rewrite the function to use await.',225message: 'The code should change to call asynchronous functions where appropriate.'226},227],228'sort-keys': {229title: 'Sort the properties of the entire object literal.',230message: '',231replaceMessage: 'Unsorted keys in object literal.',232}233}234};235236237