'use strict' ; import { Diagnostic , DiagnosticSeverity , DiagnosticCollection , Uri , window , Disposable , workspace } from 'vscode' ; import { SAuthentication } from '../util/Authentication' ; import { PddlWorkspace } from 'pddl-workspace' ; import { PlanInfo } from 'pddl-workspace' ; import { ProblemInfo } from 'pddl-workspace' ; import { DomainInfo } from 'pddl-workspace' ; import { FileInfo , FileStatus , stripComments } from 'pddl-workspace' ; import { Validator } from './validator' ; import { ValidatorService } from './ValidatorService' ; import { ValidatorExecutable } from './ValidatorExecutable' ; import { PDDLParserSettings } from '../util/Settings' ; import { PddlConfiguration , PDDL_PARSER , VALIDATION_PATH , CONF_PDDL } from '../configuration/configuration' ; import { PlanValidator } from './PlanValidator' ; import { HappeningsValidator } from './HappeningsValidator' ; import { HappeningsInfo } from 'pddl-workspace' ; import { NoDomainAssociated , getDomainFileForProblem } from '../workspace/workspaceUtils' ; import { isHttp , toUri } from '../utils' ; import { CodePddlWorkspace } from '../workspace/CodePddlWorkspace' ; import { toDiagnosticsFromParsingProblems } from './validatorUtils' ; /**
 * Listens to updates to PDDL files and performs detailed parsing and syntactical analysis and report problems as `Diagnostics`.
 */ export class Diagnostics extends Disposable { timeout : NodeJS . Timer | undefined ; validator : Validator | undefined ; pddlParserSettings : PDDLParserSettings ; private defaultTimerDelayInSeconds = 3 ; constructor ( private readonly codePddlWorkspace , private readonly diagnosticCollection , private readonly pddlConfiguration , private readonly planValidator , private readonly happeningsValidator ) { super ( ( ) => { console . log ( 'Diagnostics disposed' ) ; } ) ; this . pddlParserSettings = pddlConfiguration . getParserSettings ( ) ; this . codePddlWorkspace . pddlWorkspace . on ( PddlWorkspace . UPDATED , ( fileInfo ) => { this . clearDiagnostics ( fileInfo . fileUri ) ; this . scheduleValidation ( ) ; } ) ; this . codePddlWorkspace . pddlWorkspace . on ( PddlWorkspace . REMOVING , ( doc ) => this . clearDiagnostics ( doc . fileUri ) ) ; workspace . onDidChangeConfiguration ( e => { if ( e . affectsConfiguration ( PDDL_PARSER ) || e . affectsConfiguration ( CONF_PDDL + '.' + VALIDATION_PATH ) ) { this . handleConfigurationChange ( ) ; } } ) ; } scheduleValidation ( ) { this . cancelScheduledValidation ( ) ; const timerDelayInSeconds = this . pddlParserSettings . delayInSecondsBeforeParsing || this . defaultTimerDelayInSeconds ; this . timeout = setTimeout ( ( ) => this . validateAllDirty ( ) , timerDelayInSeconds * 1000 ) ; } cancelScheduledValidation ( ) { if ( this . timeout ) { clearTimeout ( this . timeout ) ; } } validateAllDirty ( ) { const dirtyUnknowns = this . codePddlWorkspace . pddlWorkspace . getAllFilesIf ( fileInfo => fileInfo . isUnknownPddl ( ) && fileInfo . getStatus ( ) === FileStatus . Parsed ) ; dirtyUnknowns . forEach ( file => this . validateUnknownFile ( file ) ) ; const dirtyDomains = this . codePddlWorkspace . pddlWorkspace . getAllFilesIf ( fileInfo => fileInfo . isDomain ( ) && fileInfo . getStatus ( ) === FileStatus . Parsed ) ; if ( dirtyDomains . length > 0 ) { const firstDirtyDomain = dirtyDomains [ 0 ] as DomainInfo ; const scheduleFurtherValidation = dirtyDomains . length > 1 ; this . validatePddlDocument ( firstDirtyDomain , scheduleFurtherValidation ) ; } const dirtyProblems = this . codePddlWorkspace . pddlWorkspace . getAllFilesIf ( fileInfo => fileInfo . isProblem ( ) && fileInfo . getStatus ( ) === FileStatus . Parsed ) ; if ( dirtyProblems . length > 0 ) { const firstDirtyProblem = dirtyProblems [ 0 ] as ProblemInfo ; const scheduleFurtherValidation = dirtyProblems . length > 1 ; this . validatePddlDocument ( firstDirtyProblem , scheduleFurtherValidation ) ; } const dirtyPlans = this . codePddlWorkspace . pddlWorkspace . getAllFilesIf ( fileInfo => fileInfo . isPlan ( ) && fileInfo . getStatus ( ) === FileStatus . Parsed ) ; if ( dirtyPlans . length > 0 ) { const firstDirtyPlan = dirtyPlans [ 0 ] as PlanInfo ; const scheduleFurtherValidation = dirtyPlans . length > 1 ; this . validatePlan ( firstDirtyPlan , scheduleFurtherValidation ) ; } const dirtyHappenings = this . codePddlWorkspace . pddlWorkspace . getAllFilesIf ( fileInfo => fileInfo . isHappenings ( ) && fileInfo . getStatus ( ) === FileStatus . Parsed ) ; if ( dirtyHappenings . length > 0 ) { const firstDirtyHappenings = dirtyHappenings [ 0 ] as HappeningsInfo ; const scheduleFurtherValidation = dirtyHappenings . length > 1 ; this . validateHappenings ( firstDirtyHappenings , scheduleFurtherValidation ) ; } } handleConfigurationChange ( ) { this . pddlParserSettings = this . pddlConfiguration . getParserSettings ( ) ; this . revalidateAll ( ) ; } revalidateAll ( ) { this . codePddlWorkspace . pddlWorkspace . folders . forEach ( folder => { folder . files . forEach ( f => { if ( f . getStatus ( ) !== FileStatus . Dirty ) { f . setStatus ( FileStatus . Parsed ) ; } } ) ; } ) ; this . cancelScheduledValidation ( ) ; this . validateAllDirty ( ) ; } validatePddlDocumentByUri ( fileUri , scheduleFurtherValidation ) { const fileInfo = this . codePddlWorkspace . pddlWorkspace . getFileInfo ( fileUri ) ; if ( ! fileInfo ) { throw new Error ( `File not found in the PDDL workspace: ${ fileUri }` ) ; } this . validatePddlDocument ( fileInfo , scheduleFurtherValidation ) ; } validatePlan ( planInfo , scheduleFurtherValidation ) { if ( planInfo === null || planInfo === undefined ) { return ; } if ( ! this . planValidator . testConfiguration ( ) ) { return ; } planInfo . setStatus ( FileStatus . Validating ) ; console . log ( `Validating ${ planInfo . name } plan.` ) ; this . planValidator . validatePlanAndReportDiagnostics ( planInfo , false , ( diagnostics ) => { this . sendDiagnostics ( diagnostics ) ; planInfo . setStatus ( FileStatus . Validated ) ; if ( scheduleFurtherValidation ) { this . scheduleValidation ( ) ; } } , ( err ) => { window . showErrorMessage ( "Error during plan validation: " + err ) ; console . warn ( "Error during plan validation: " + err ) ; } ) ; } validateHappenings ( happeningsInfo , scheduleFurtherValidation ) { if ( happeningsInfo === null || happeningsInfo === undefined ) { return ; } happeningsInfo . setStatus ( FileStatus . Validating ) ; console . log ( `Validating ${ happeningsInfo . name } plan.` ) ; this . happeningsValidator . validateAndReportDiagnostics ( happeningsInfo , false , ( diagnostics ) => { this . sendDiagnostics ( diagnostics ) ; happeningsInfo . setStatus ( FileStatus . Validated ) ; if ( scheduleFurtherValidation ) { this . scheduleValidation ( ) ; } } , ( err ) => { window . showErrorMessage ( err ) ; console . warn ( err ) ; } ) ; } validatePddlDocument ( fileInfo , scheduleFurtherValidation ) { if ( fileInfo === null || fileInfo === undefined ) { console . log ( 'File not found in the workspace.' ) ; return ; } if ( fileInfo . getParsingProblems ( ) . length > 0 ) { const parsingProblems = new Map < string , Diagnostic [ ] > ( ) ; parsingProblems . set ( fileInfo . fileUri . toString ( ) , toDiagnosticsFromParsingProblems ( fileInfo . getParsingProblems ( ) ) ) ; this . sendDiagnostics ( parsingProblems ) ; return ; } if ( fileInfo . isDomain ( ) ) { const domainInfo = fileInfo as DomainInfo ; const problemFiles = this . codePddlWorkspace . pddlWorkspace . getProblemFiles ( domainInfo ) ; this . validateDomainAndProblems ( domainInfo , problemFiles , scheduleFurtherValidation ) ; } else if ( fileInfo . isProblem ( ) ) { const problemInfo = fileInfo as ProblemInfo ; const domainFile = this . getDomainFileFor ( problemInfo ) ; if ( domainFile ) { this . validateDomainAndProblems ( domainFile , [ problemInfo ] , scheduleFurtherValidation ) ; } } else { } } validateDomainAndProblems ( domainInfo , problemFiles , scheduleFurtherValidation ) { if ( ! this . pddlConfiguration . getParserPath ( ) ) { const showNever = true ; this . pddlConfiguration . suggestNewParserConfiguration ( showNever ) ; return ; } domainInfo . setStatus ( FileStatus . Validating ) ; problemFiles . forEach ( p => p . setStatus ( FileStatus . Validating ) ) ; const validator = this . createValidator ( this . pddlConfiguration . getParserPath ( ) ! ) ; if ( ! validator ) { return ; } validator . validate ( domainInfo , problemFiles , ( diagnostics ) => { this . sendDiagnostics ( diagnostics ) ; if ( scheduleFurtherValidation ) { this . scheduleValidation ( ) ; } } , ( err ) => { window . showErrorMessage ( err ) ; console . warn ( err ) ; const showNever = false ; this . pddlConfiguration . suggestNewParserConfiguration ( showNever ) ; } ) ; } createValidator ( newParserPath ) { if ( ! this . validator || this . validator . path !== newParserPath || ( this . validator instanceof ValidatorExecutable ) && ( this . validator . syntax !== this . pddlParserSettings . executableOptions || this . validator . customPattern !== this . pddlParserSettings . problemPattern ) ) { if ( isHttp ( newParserPath ) ) { const authentication = new SAuthentication ( this . pddlParserSettings . serviceAuthenticationUrl , this . pddlParserSettings . serviceAuthenticationRequestEncoded , this . pddlParserSettings . serviceAuthenticationClientId , this . pddlParserSettings . serviceAuthenticationCallbackPort , this . pddlParserSettings . serviceAuthenticationTimeoutInMs , this . pddlParserSettings . serviceAuthenticationTokensvcUrl , this . pddlParserSettings . serviceAuthenticationTokensvcApiKey , this . pddlParserSettings . serviceAuthenticationTokensvcAccessPath , this . pddlParserSettings . serviceAuthenticationTokensvcValidatePath , this . pddlParserSettings . serviceAuthenticationTokensvcCodePath , this . pddlParserSettings . serviceAuthenticationTokensvcRefreshPath , this . pddlParserSettings . serviceAuthenticationTokensvcSvctkPath , this . pddlParserSettings . serviceAuthenticationRefreshToken , this . pddlParserSettings . serviceAuthenticationAccessToken , this . pddlParserSettings . serviceAuthenticationSToken ) ; return this . validator = new ValidatorService ( newParserPath , this . pddlParserSettings . serviceAuthenticationEnabled , authentication ) ; } else { return this . validator = new ValidatorExecutable ( newParserPath , this . pddlParserSettings . executableOptions , this . pddlParserSettings . problemPattern ) ; } } else { return this . validator ; } } sendDiagnostics ( diagnostics ) { diagnostics . forEach ( ( diagnostics , fileUri ) => this . diagnosticCollection . set ( Uri . parse ( fileUri ) , diagnostics ) ) ; } getDomainFileFor ( problemFile ) { try { return getDomainFileForProblem ( problemFile , this . codePddlWorkspace ) ; } catch ( err ) { if ( err instanceof NoDomainAssociated ) { this . sendDiagnosticInfo ( toUri ( problemFile . fileUri ) , err . message , NoDomainAssociated . DIAGNOSTIC_CODE ) ; problemFile . setStatus ( FileStatus . Validated ) ; return undefined ; } throw err ; } } sendDiagnosticInfo ( fileUri , message , code ? ) { this . sendDiagnostic ( fileUri , message , DiagnosticSeverity . Information , code ) ; } sendDiagnostic ( fileUri , message , severity , code ? ) { const diagnostic = new Diagnostic ( Validator . createLineRange ( 0 ) , message , severity ) ; if ( code !== undefined && code !== null ) { diagnostic . code = code ; } this . diagnosticCollection . set ( fileUri , [ diagnostic ] ) ; } clearDiagnostics ( fileUri ) { this . diagnosticCollection . delete ( fileUri ) ; } validateUnknownFile ( fileInfo ) { fileInfo . setStatus ( FileStatus . Validating ) ; const firstLine = stripComments ( fileInfo . getText ( ) ) . replace ( /^\s+/g , '' ) . split ( '\n' ) [ 0 ] ; this . sendDiagnostic ( fileInfo . fileUri , `Cannot recognize whether this is a domain or problem: ${ firstLine }` , DiagnosticSeverity . Error , 'CONTENT_NOT_RECOGNIZED' ) ; fileInfo . setStatus ( FileStatus . Validated ) ; } }