@@ -5,30 +5,24 @@ import { CancellationToken, OutputChannel, Uri } from 'vscode';
55import '../../common/extensions' ;
66import { IInterpreterService } from '../../interpreter/contracts' ;
77import { IServiceContainer } from '../../ioc/types' ;
8- import { ILinterManager , LinterId } from '../../linters/types' ;
98import { EnvironmentType , PythonEnvironment } from '../../pythonEnvironments/info' ;
10- import { sendTelemetryEvent } from '../../telemetry' ;
11- import { EventName } from '../../telemetry/constants' ;
12- import { IApplicationShell , ICommandManager , IWorkspaceService } from '../application/types' ;
13- import { Commands , STANDARD_OUTPUT_CHANNEL } from '../constants' ;
14- import { LinterInstallationPromptVariants } from '../experiments/groups' ;
9+ import { IApplicationShell , IWorkspaceService } from '../application/types' ;
10+ import { STANDARD_OUTPUT_CHANNEL } from '../constants' ;
1511import { traceError , traceInfo } from '../logger' ;
1612import { IPlatformService } from '../platform/types' ;
1713import { IProcessServiceFactory , IPythonExecutionFactory } from '../process/types' ;
1814import { ITerminalServiceFactory } from '../terminal/types' ;
1915import {
2016 IConfigurationService ,
21- IExperimentService ,
2217 IInstaller ,
2318 InstallerResponse ,
2419 IOutputChannel ,
25- IPersistentStateFactory ,
2620 ProductInstallStatus ,
2721 ModuleNamePurpose ,
2822 Product ,
2923 ProductType ,
3024} from '../types' ;
31- import { Common , Installer , Linters } from '../utils/localize' ;
25+ import { Installer } from '../utils/localize' ;
3226import { isResource , noop } from '../utils/misc' ;
3327import { ProductNames } from './productNames' ;
3428import {
@@ -294,244 +288,6 @@ export class FormatterInstaller extends BaseInstaller {
294288 return InstallerResponse . Ignore ;
295289 }
296290}
297-
298- export class LinterInstaller extends BaseInstaller {
299- // This is a hack, really we should be handling this in a service that
300- // controls the prompts we show. The issue here was that if we show
301- // a prompt to install pylint and flake8, and user selects flake8
302- // we immediately show this prompt again saying install flake8, while the
303- // installation is on going.
304- private static promptSeen : boolean = false ;
305- private readonly experimentsManager : IExperimentService ;
306- private readonly linterManager : ILinterManager ;
307-
308- constructor ( protected serviceContainer : IServiceContainer , protected outputChannel : OutputChannel ) {
309- super ( serviceContainer , outputChannel ) ;
310- this . experimentsManager = serviceContainer . get < IExperimentService > ( IExperimentService ) ;
311- this . linterManager = serviceContainer . get < ILinterManager > ( ILinterManager ) ;
312- }
313-
314- public static reset ( ) {
315- // Read notes where this is defined.
316- LinterInstaller . promptSeen = false ;
317- }
318-
319- protected async promptToInstallImplementation (
320- product : Product ,
321- resource ?: Uri ,
322- cancel ?: CancellationToken ,
323- ) : Promise < InstallerResponse > {
324- // This is a hack, really we should be handling this in a service that
325- // controls the prompts we show. The issue here was that if we show
326- // a prompt to install pylint and flake8, and user selects flake8
327- // we immediately show this prompt again saying install flake8, while the
328- // installation is on going.
329- if ( LinterInstaller . promptSeen ) {
330- return InstallerResponse . Ignore ;
331- }
332-
333- LinterInstaller . promptSeen = true ;
334-
335- // Conditions to use experiment prompt:
336- // 1. There should be no linter set in any scope
337- // 2. The default linter should be pylint
338-
339- if ( ! this . isLinterSetInAnyScope ( ) && product === Product . pylint ) {
340- if ( await this . experimentsManager . inExperiment ( LinterInstallationPromptVariants . noPrompt ) ) {
341- // We won't show a prompt, so tell the extension to treat as though user
342- // ignored the prompt.
343- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT , undefined , {
344- prompt : 'noPrompt' ,
345- } ) ;
346-
347- const productName = ProductNames . get ( product ) ! ;
348- traceInfo ( `Linter ${ productName } is not installed.` ) ;
349-
350- return InstallerResponse . Ignore ;
351- } else if ( await this . experimentsManager . inExperiment ( LinterInstallationPromptVariants . pylintFirst ) ) {
352- return this . newPromptForInstallation ( true , resource , cancel ) ;
353- } else if ( await this . experimentsManager . inExperiment ( LinterInstallationPromptVariants . flake8First ) ) {
354- return this . newPromptForInstallation ( false , resource , cancel ) ;
355- }
356- }
357-
358- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT , undefined , {
359- prompt : 'old' ,
360- } ) ;
361- return this . oldPromptForInstallation ( product , resource , cancel ) ;
362- }
363-
364- /**
365- * For installers that want to avoid prompting the user over and over, they can make use of a
366- * persisted true/false value representing user responses to 'stop showing this prompt'. This method
367- * gets the persisted value given the installer-defined key.
368- *
369- * @param key Key to use to get a persisted response value, each installer must define this for themselves.
370- * @returns Boolean: The current state of the stored response key given.
371- */
372- protected getStoredResponse ( key : string ) : boolean {
373- const factory = this . serviceContainer . get < IPersistentStateFactory > ( IPersistentStateFactory ) ;
374- const state = factory . createGlobalPersistentState < boolean | undefined > ( key , undefined ) ;
375- return state . value === true ;
376- }
377-
378- private async newPromptForInstallation ( pylintFirst : boolean , resource ?: Uri , cancel ?: CancellationToken ) {
379- const productName = ProductNames . get ( Product . pylint ) ! ;
380-
381- // User has already set to ignore this prompt
382- const disableLinterInstallPromptKey = `${ productName } _DisableLinterInstallPrompt` ;
383- if ( this . getStoredResponse ( disableLinterInstallPromptKey ) === true ) {
384- return InstallerResponse . Ignore ;
385- }
386-
387- // Check if the linter settings has Pylint or flake8 pointing to executables.
388- // If the settings point to executables then we can't install. Defer to old Prompt.
389- if (
390- ! this . isExecutableAModule ( Product . pylint , resource ) ||
391- ! this . isExecutableAModule ( Product . flake8 , resource )
392- ) {
393- return this . oldPromptForInstallation ( Product . pylint , resource , cancel ) ;
394- }
395-
396- const installPylint = Linters . installPylint ( ) ;
397- const installFlake8 = Linters . installFlake8 ( ) ;
398- const doNotShowAgain = Common . doNotShowAgain ( ) ;
399-
400- const options = pylintFirst
401- ? [ installPylint , installFlake8 , doNotShowAgain ]
402- : [ installFlake8 , installPylint , doNotShowAgain ] ;
403- const message = Linters . installMessage ( ) ;
404- const prompt = pylintFirst ? 'pylintFirst' : 'flake8first' ;
405-
406- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT , undefined , {
407- prompt,
408- } ) ;
409-
410- const response = await this . appShell . showInformationMessage ( message , ...options ) ;
411-
412- if ( response === installPylint ) {
413- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT_ACTION , undefined , {
414- prompt,
415- action : 'installPylint' ,
416- } ) ;
417- return this . install ( Product . pylint , resource , cancel ) ;
418- } else if ( response === installFlake8 ) {
419- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT_ACTION , undefined , {
420- prompt,
421- action : 'installFlake8' ,
422- } ) ;
423- await this . linterManager . setActiveLintersAsync ( [ Product . flake8 ] , resource ) ;
424- return this . install ( Product . flake8 , resource , cancel ) ;
425- } else if ( response === doNotShowAgain ) {
426- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT_ACTION , undefined , {
427- prompt,
428- action : 'disablePrompt' ,
429- } ) ;
430- await this . setStoredResponse ( disableLinterInstallPromptKey , true ) ;
431- return InstallerResponse . Ignore ;
432- }
433-
434- sendTelemetryEvent ( EventName . LINTER_INSTALL_PROMPT_ACTION , undefined , {
435- prompt,
436- action : 'close' ,
437- } ) ;
438- return InstallerResponse . Ignore ;
439- }
440-
441- private async oldPromptForInstallation ( product : Product , resource ?: Uri , cancel ?: CancellationToken ) {
442- const isPylint = product === Product . pylint ;
443-
444- const productName = ProductNames . get ( product ) ! ;
445- const install = Common . install ( ) ;
446- const doNotShowAgain = Common . doNotShowAgain ( ) ;
447- const disableLinterInstallPromptKey = `${ productName } _DisableLinterInstallPrompt` ;
448- const selectLinter = Linters . selectLinter ( ) ;
449-
450- if ( isPylint && this . getStoredResponse ( disableLinterInstallPromptKey ) === true ) {
451- return InstallerResponse . Ignore ;
452- }
453-
454- const options = isPylint ? [ selectLinter , doNotShowAgain ] : [ selectLinter ] ;
455-
456- let message = `Linter ${ productName } is not installed.` ;
457- if ( this . isExecutableAModule ( product , resource ) ) {
458- options . splice ( 0 , 0 , install ) ;
459- } else {
460- const executable = this . getExecutableNameFromSettings ( product , resource ) ;
461- message = `Path to the ${ productName } linter is invalid (${ executable } )` ;
462- }
463- const response = await this . appShell . showErrorMessage ( message , ...options ) ;
464- if ( response === install ) {
465- sendTelemetryEvent ( EventName . LINTER_NOT_INSTALLED_PROMPT , undefined , {
466- tool : productName as LinterId ,
467- action : 'install' ,
468- } ) ;
469- return this . install ( product , resource , cancel ) ;
470- } else if ( response === doNotShowAgain ) {
471- await this . setStoredResponse ( disableLinterInstallPromptKey , true ) ;
472- sendTelemetryEvent ( EventName . LINTER_NOT_INSTALLED_PROMPT , undefined , {
473- tool : productName as LinterId ,
474- action : 'disablePrompt' ,
475- } ) ;
476- return InstallerResponse . Ignore ;
477- }
478-
479- if ( response === selectLinter ) {
480- sendTelemetryEvent ( EventName . LINTER_NOT_INSTALLED_PROMPT , undefined , { action : 'select' } ) ;
481- const commandManager = this . serviceContainer . get < ICommandManager > ( ICommandManager ) ;
482- await commandManager . executeCommand ( Commands . Set_Linter ) ;
483- }
484- return InstallerResponse . Ignore ;
485- }
486-
487- private isLinterSetInAnyScope ( ) {
488- const config = this . workspaceService . getConfiguration ( 'python' ) ;
489- if ( config ) {
490- const keys = [
491- 'linting.pylintEnabled' ,
492- 'linting.flake8Enabled' ,
493- 'linting.banditEnabled' ,
494- 'linting.mypyEnabled' ,
495- 'linting.pycodestyleEnabled' ,
496- 'linting.prospectorEnabled' ,
497- 'linting.pydocstyleEnabled' ,
498- 'linting.pylamaEnabled' ,
499- ] ;
500-
501- const values = keys . map ( ( key ) => {
502- const value = config . inspect < boolean > ( key ) ;
503- if ( value ) {
504- if ( value . globalValue || value . workspaceValue || value . workspaceFolderValue ) {
505- return 'linter set' ;
506- }
507- }
508- return 'no info' ;
509- } ) ;
510-
511- return values . includes ( 'linter set' ) ;
512- }
513-
514- return false ;
515- }
516-
517- /**
518- * For installers that want to avoid prompting the user over and over, they can make use of a
519- * persisted true/false value representing user responses to 'stop showing this prompt'. This
520- * method will set that persisted value given the installer-defined key.
521- *
522- * @param key Key to use to get a persisted response value, each installer must define this for themselves.
523- * @param value Boolean value to store for the user - if they choose to not be prompted again for instance.
524- * @returns Boolean: The current state of the stored response key given.
525- */
526- private async setStoredResponse ( key : string , value : boolean ) : Promise < void > {
527- const factory = this . serviceContainer . get < IPersistentStateFactory > ( IPersistentStateFactory ) ;
528- const state = factory . createGlobalPersistentState < boolean | undefined > ( key , undefined ) ;
529- if ( state && state . value !== value ) {
530- await state . updateValue ( value ) ;
531- }
532- }
533- }
534-
535291export class TestFrameworkInstaller extends BaseInstaller {
536292 protected async promptToInstallImplementation (
537293 product : Product ,
@@ -695,8 +451,6 @@ export class ProductInstaller implements IInstaller {
695451 switch ( productType ) {
696452 case ProductType . Formatter :
697453 return new FormatterInstaller ( this . serviceContainer , this . outputChannel ) ;
698- case ProductType . Linter :
699- return new LinterInstaller ( this . serviceContainer , this . outputChannel ) ;
700454 case ProductType . WorkspaceSymbols :
701455 return new CTagsInstaller ( this . serviceContainer , this . outputChannel ) ;
702456 case ProductType . TestFramework :
0 commit comments