import * as fse from "fs-extra" ; import * as semver from "semver" ; import * as vscode from "vscode" ; import { mavenExplorerProvider } from "../explorer/mavenExplorerProvider" ; import { Dependency } from "../explorer/model/Dependency" ; import { MavenProject } from "../explorer/model/MavenProject" ; import { constructDependenciesNode , constructDependencyManagementNode , constructDependencyNode , getIndentation } from "../utils/editUtils" ; import { UserError } from "../utils/errorUtils" ; import { ElementNode , getNodesByTag , XmlTagName } from "../utils/lexerUtils" ; import { getVersions } from "../utils/requestUtils" ; export async function setDependencyVersionHandler ( selectedItem ? ) { let pomPath ; let effectiveVersion ; if ( selectedItem && selectedItem . pomPath ) { pomPath = selectedItem . pomPath ; effectiveVersion = selectedItem . effectiveVersion ; } else if ( selectedItem && selectedItem instanceof Dependency ) { pomPath = selectedItem . projectPomPath ; effectiveVersion = selectedItem . omittedStatus ?. effectiveVersion ?? selectedItem . version ; } else { throw new UserError ( "No dependency node specified." ) ; } if ( ! await fse . pathExists ( pomPath ) ) { throw new UserError ( "Specified POM file does not exist on file system." ) ; } const gid = selectedItem . groupId ; const aid = selectedItem . artifactId ; const versions = getAllVersionsInTree ( pomPath , gid , aid ) ; const OPTION_SEARCH_MAVEN_CENTRAL = "Search Maven Central Repository..." ; versions . push ( OPTION_SEARCH_MAVEN_CENTRAL ) ; let selectedVersion = await vscode . window . showQuickPick ( versions . map ( version => ( { value : version , label : version !== OPTION_SEARCH_MAVEN_CENTRAL ? `$(package) ${ version }` : version , description : version === effectiveVersion ? "effective" : undefined } ) ) , { placeHolder : `Select a version for ${ gid }:${ aid }...` , ignoreFocusOut : true } ) . then ( version => version ? version . value : undefined ) ; if ( selectedVersion === undefined ) { return ; } if ( selectedVersion === OPTION_SEARCH_MAVEN_CENTRAL ) { const selectedVersionFromMavenCentral = await vscode . window . showQuickPick < vscode . QuickPickItem & { value : string ; } > ( getVersions ( gid , aid ) . then ( artifacts => artifacts . map ( artifact => ( { value : artifact . v , label : `$(package) ${ artifact . v }` , description : artifact . v === effectiveVersion ? "effective" : undefined } ) ) ) , { placeHolder : `Select a version for ${ gid }:${ aid }...` , ignoreFocusOut : true } ) . then ( artifact => artifact ? artifact . value : undefined ) ; if ( selectedVersionFromMavenCentral === undefined ) { return ; } selectedVersion = selectedVersionFromMavenCentral ; } if ( selectedVersion !== effectiveVersion ) { await setDependencyVersion ( pomPath , gid , aid , selectedVersion ) ; } } async function setDependencyVersion ( pomPath , gid , aid , version ) { const pomDocument = await vscode . window . showTextDocument ( vscode . Uri . file ( pomPath ) , { 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 dependencyManagementNode = projectNode . children ?. find ( node => node . tag === XmlTagName . DependencyManagement ) ; const deleteNode = 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 ( dependencyManagementNode !== undefined ) { await insertDependencyManagement ( pomPath , dependencyManagementNode , deleteNode , gid , aid , version ) ; } else { await insertDependencyManagement ( pomPath , projectNode , deleteNode , gid , aid , version ) ; } } async function insertDependencyManagement ( pomPath , targetNode , deleteNode , gid , aid , version ) { if ( targetNode . contentStart === undefined || targetNode . contentEnd === undefined ) { throw new UserError ( "Invalid target XML node to insert dependency management." ) ; } const currentDocument = await vscode . workspace . openTextDocument ( pomPath ) ; const textEditor = await vscode . window . showTextDocument ( currentDocument ) ; const baseIndent = getIndentation ( currentDocument , targetNode . contentEnd ) ; const options = textEditor . options ; const indent = options . insertSpaces && typeof options . tabSize === "number" ? " " . repeat ( options . tabSize ) : "\t" ; const eol = currentDocument . eol === vscode . EndOfLine . LF ? "\n" : "\r\n" ; let insertPosition ; let targetText ; let dependencyNodeInManagement ; const dependenciesNode = targetNode ?. children ?. find ( node => node . tag === XmlTagName . Dependencies ) ; if ( targetNode . tag === XmlTagName . DependencyManagement && dependenciesNode !== undefined ) { if ( dependenciesNode . contentStart === undefined || dependenciesNode . contentEnd === undefined ) { throw new UserError ( "Invalid target XML node to insert dependency management." ) ; } insertPosition = currentDocument . positionAt ( dependenciesNode . contentStart ) ; dependencyNodeInManagement = dependenciesNode . children ?. find ( node => node . children ?. find ( id => id . tag === XmlTagName . GroupId && id . text === gid ) && node . children ?. find ( id => id . tag === XmlTagName . ArtifactId && id . text === aid ) ) ; const newIndent = `${ baseIndent }${ indent }` ; targetText = constructDependencyNode ( { gid , aid , version , baseIndent : newIndent , indent , eol } ) ; } else if ( targetNode . tag === XmlTagName . DependencyManagement && dependenciesNode === undefined ) { insertPosition = currentDocument . positionAt ( targetNode . contentStart ) ; targetText = constructDependenciesNode ( { gid , aid , version , baseIndent , indent , eol } ) ; } else if ( targetNode . tag === XmlTagName . Project ) { insertPosition = currentDocument . positionAt ( targetNode . contentEnd ) ; targetText = constructDependencyManagementNode ( { gid , aid , version , baseIndent , indent , eol } ) ; } else { return ; } const edit = new vscode . WorkspaceEdit ( ) ; if ( deleteNode ) { const versionItem = deleteNode . children ?. find ( node => node . tag === XmlTagName . Version ) ; if ( versionItem ) { if ( versionItem . contentStart === undefined || versionItem . contentEnd === undefined ) { throw new UserError ( "Invalid target XML node to delete." ) ; } const versionString = "<version>" ; const start = versionItem . contentStart - versionString . length ; const end = versionItem . contentEnd + versionString . length + 1 ; const range = new vscode . Range ( currentDocument . positionAt ( start ) , currentDocument . positionAt ( end ) ) ; edit . delete ( currentDocument . uri , range ) ; } } if ( dependencyNodeInManagement ) { if ( dependencyNodeInManagement . contentStart === undefined || dependencyNodeInManagement . contentEnd === undefined ) { throw new UserError ( "Invalid target XML node to delete." ) ; } const dependencyString = "<dependency>" ; const start = dependencyNodeInManagement . contentStart - dependencyString . length ; const end = dependencyNodeInManagement . contentEnd + dependencyString . length + 1 ; const range = new vscode . Range ( currentDocument . positionAt ( start ) , currentDocument . positionAt ( end ) ) ; edit . delete ( currentDocument . uri , range ) ; } edit . insert ( currentDocument . uri , insertPosition , targetText ) ; await vscode . workspace . applyEdit ( edit ) ; const endingPosition = currentDocument . positionAt ( currentDocument . offsetAt ( insertPosition ) + targetText . length ) ; textEditor . revealRange ( new vscode . Range ( insertPosition , endingPosition ) ) ; } function getAllVersionsInTree ( pomPath , gid , aid ) { const project = mavenExplorerProvider . getMavenProject ( pomPath ) ; if ( project === undefined ) { throw new Error ( "Failed to get maven projects." ) ; } const fullText = project . fullText ; const re = new RegExp ( `${ gid }:${ aid }:[\\w.-]+` , "gm" ) ; const artifacts = fullText . match ( re ) ; let versions = [ ] ; if ( artifacts !== null ) { artifacts . forEach ( a => { versions . push ( a . slice ( gid . length + aid . length + 2 ) ) ; } ) ; } function compare ( v1 , v2 ) { const s1 = semver . coerce ( v1 ) ; const s2 = semver . coerce ( v2 ) ; const version1 = s1 === null ? v1 : s1 ; const version2 = s2 === null ? v2 : s2 ; return semver . rcompare ( version1 , version2 , true ) ; } versions = Array . from ( new Set ( versions ) ) . sort ( compare ) ; return versions ; }