import * as child_process from 'child_process' ; import * as path from 'path' ; import { URI } from 'vscode-uri' ; import { Connection , Diagnostic , DiagnosticSeverity , Position , Range , } from 'vscode-languageserver' ; import { TextDocument } from 'vscode-languageserver-textdocument' ; import { WorkspaceFolderContext } from './workspaceManager' ; import { CommandRunner } from '../utils/commandRunner' ; /**
 * Acts as an interface to ansible-playbook command.
 */ export class AnsiblePlaybook { private useProgressTracker = false ; param connection param context constructor ( private connection , private context ) { this . useProgressTracker = ! ! context . clientCapabilities . window ?. workDoneProgress ; } /**
     * Acts as an interface to ansible-playbook <file> --syntax-check command and a cache of its output.
     * ansible syntax-check may provide diagnostics for more than just the file for which
     * it was triggered, and this is reflected in the implementation.
     *
     * Perform ansible syntax-check for the given document.
     */ public async doValidate ( textDocument ) { const docPath = URI . parse ( textDocument . uri ) . path ; let diagnostics = new Map ( ) ; const progressTracker = this . useProgressTracker ? await this . connection . window . createWorkDoneProgress ( ) : { begin : ( ) => { } , done : ( ) => { } , } ; const workingDirectory = URI . parse ( this . context . workspaceFolder . uri ) . path ; const mountPaths = new Set ( [ workingDirectory , path . dirname ( docPath ) ] ) ; const settings = await this . context . documentSettings . get ( textDocument . uri ) ; progressTracker . begin ( 'ansible syntax-check' , undefined , 'Processing files...' ) ; const commandRunner = new CommandRunner ( this . connection , this . context , settings ) ; try { await commandRunner . runCommand ( 'ansible-playbook' , `${ docPath } --syntax-check` , workingDirectory , mountPaths ) ; } catch ( error ) { if ( error instanceof Error ) { const execError = error as child_process . ExecException & { stdout : string ; stderr : string ; } ; const ansibleSyntaxCheckRegex = /The error appears to be in '(?<filename>.*)': line (?<line>\d+), column (?<column>\d+)/ ; const filteredErrorMessage = ansibleSyntaxCheckRegex . exec ( execError . stderr ) ; diagnostics = filteredErrorMessage ? this . processReport ( execError . message , filteredErrorMessage . groups . filename , parseInt ( filteredErrorMessage . groups . line ) , parseInt ( filteredErrorMessage . groups . column ) ) : this . processReport ( execError . message , docPath , 1 , 1 ) ; if ( execError . stderr ) { this . connection . console . info ( `[ansible syntax-check] ${ execError . stderr }` ) ; } } else { this . connection . console . error ( `Exception in AnsibleSyntaxCheck service: ${ JSON . stringify ( error ) }` ) ; } } progressTracker . done ( ) ; return diagnostics ; } private processReport ( result , fileName , line , column ) { const diagnostics = new Map ( ) ; if ( ! result ) { this . connection . console . warn ( 'Standard output from ansible syntax-check is suspiciously empty.' ) ; return diagnostics ; } const start = { line : line - 1 , character : column - 1 , } ; const end = { line : line - 1 , character : Number . MAX_SAFE_INTEGER , } ; const range = { start , end , } ; const severity = DiagnosticSeverity . Error ; const locationUri = `file://${ fileName }` ; const fileDiagnostics = diagnostics . get ( locationUri ) || [ ] ; fileDiagnostics . push ( { message : result , range , severity , source : 'Ansible' , } ) ; diagnostics . set ( locationUri , fileDiagnostics ) ; return diagnostics ; } }