import * as _ from "lodash" ; import { performance } from "perf_hooks" ; import * as vscode from "vscode" ; import { getRequestDelay , lruCache , MovingAverage } from "./debouncing" ; import { mavenExplorerProvider } from "./explorer/mavenExplorerProvider" ; import { Dependency } from "./explorer/model/Dependency" ; import { MavenProject } from "./explorer/model/MavenProject" ; import { Settings } from "./Settings" ; import { UserError } from "./utils/errorUtils" ; import { ElementNode , getNodesByTag , XmlTagName } from "./utils/lexerUtils" ; export const MAVEN_DEPENDENCY_CONFLICT = "Maven dependency conflict" ; class DiagnosticProvider { private _collection : vscode . DiagnosticCollection ; public map : Map < vscode . Diagnostic , Dependency > = new Map ( ) ; public initialize ( context ) { const dependencyCollection = vscode . languages . createDiagnosticCollection ( "Dependency" ) ; this . _collection = dependencyCollection ; context . subscriptions . push ( this . _collection ) ; context . subscriptions . push ( vscode . workspace . onDidChangeTextDocument ( async ( e ) => { if ( e . document . fileName . endsWith ( "pom.xml" ) ) { await this . debouncedRefresh ( e . document . uri ) ; } } ) ) ; } private updateNodeForDocumentTimeout : NodeJS . Timer ; private async debouncedRefresh ( uri ) { if ( this . updateNodeForDocumentTimeout ) { clearTimeout ( this . updateNodeForDocumentTimeout ) ; } const timeout = getRequestDelay ( uri ) ; this . updateNodeForDocumentTimeout = setTimeout ( async ( ) => { const startTime = performance . now ( ) ; await this . refreshDiagnostics ( uri ) ; const executionTime = performance . now ( ) - startTime ; const movingAverage = lruCache . get ( uri ) || new MovingAverage ( ) ; movingAverage . update ( executionTime ) ; lruCache . set ( uri , movingAverage ) ; } , timeout ) ; } public async refreshDiagnostics ( uri ) { const diagnostics = [ ] ; if ( Settings . enableConflictDiagnostics ( ) === false ) { this . _collection . set ( uri , diagnostics ) ; return ; } const project = mavenExplorerProvider . getMavenProject ( uri . fsPath ) ; if ( project === undefined ) { throw new Error ( "Failed to get maven project." ) ; } const conflictNodes = project . conflictNodes ; for ( const node of conflictNodes ) { const diagnostic = await this . createDiagnostics ( node ) ; diagnostics . push ( diagnostic ) ; this . map . set ( diagnostic , node ) ; } this . _collection . set ( uri , diagnostics ) ; } public async createDiagnostics ( node ) { const root = node . root ; const range = await this . findConflictRange ( root . projectPomPath , root . groupId , root . artifactId ) ; const message = `Dependency conflict in ${ root . artifactId }: ${ node . groupId }:${ node . artifactId }:${ node . version } conflict with ${ node . omittedStatus ?. effectiveVersion }` ; const diagnostic = new vscode . Diagnostic ( range , message , vscode . DiagnosticSeverity . Warning ) ; diagnostic . code = MAVEN_DEPENDENCY_CONFLICT ; return diagnostic ; } public async findConflictRange ( filePath , gid , aid ) { const pomDocument = await vscode . window . showTextDocument ( vscode . Uri . file ( filePath ) , { preserveFocus : true } ) ; const projectNodes = getNodesByTag ( pomDocument . document . getText ( ) , XmlTagName . Project ) ; if ( projectNodes === undefined || projectNodes . length !== 1 ) { throw new UserError ( "Only support POM file with single <project> node." ) ; } const projectNode = projectNodes [ 0 ] ; const dependenciesNode = projectNode . children ?. find ( node => node . tag === XmlTagName . Dependencies ) ; const dependencyNode = dependenciesNode ?. children ?. find ( node => node . children ?. find ( id => id . tag === XmlTagName . GroupId && id . text === gid ) !== undefined && node . children ?. find ( id => id . tag === XmlTagName . ArtifactId && id . text === aid ) !== undefined ) ; if ( dependencyNode === undefined ) { throw new UserError ( "Failed to find dependency." ) ; } const aidItem = dependencyNode . children ?. find ( node => node . tag === XmlTagName . ArtifactId ) ; if ( aidItem === undefined || aidItem . contentStart === undefined || aidItem . contentEnd === undefined ) { throw new UserError ( "Failed to find dependency." ) ; } const currentDocument = await vscode . workspace . openTextDocument ( filePath ) ; return new vscode . Range ( currentDocument . positionAt ( aidItem . contentStart ) , currentDocument . positionAt ( aidItem . contentEnd ) ) ; } } export const diagnosticProvider = new DiagnosticProvider ( ) ;