import * as vscode from "vscode" ; import { templateKeys } from '../../../common' ; import * as Json from "../../language/json/JSON" ; import { Span } from "../../language/Span" ; import { assertNever } from "../../util/assertNever" ; import { IParameterDefinition } from "../parameters/IParameterDefinition" ; import { DeploymentTemplateDoc } from "./DeploymentTemplateDoc" ; import { TemplateSectionType } from "./TemplateSectionType" ; import { UserFunctionDefinition } from './UserFunctionDefinition' ; import { UserFunctionNamespaceDefinition } from './UserFunctionNamespaceDefinition' ; import { IVariableDefinition } from './VariableDefinition' ; type CommentsMap = Map < number , Span > ; export class SortQuickPickItem implements vscode . QuickPickItem { public label : string ; public value : TemplateSectionType ; public description : string ; constructor ( label , value , description ) { this . label = label ; this . value = value ; this . description = description ; } } export function getQuickPickItems ( ) { let items = [ ] ; items . push ( new SortQuickPickItem ( "Functions" , TemplateSectionType . Functions , "Sort function namespaces and functions" ) ) ; items . push ( new SortQuickPickItem ( "Outputs" , TemplateSectionType . Outputs , "Sort outputs" ) ) ; items . push ( new SortQuickPickItem ( "Parameters" , TemplateSectionType . Parameters , "Sort parameters for the template" ) ) ; items . push ( new SortQuickPickItem ( "Resources" , TemplateSectionType . Resources , "Sort resources based on the name including first level of child resources" ) ) ; items . push ( new SortQuickPickItem ( "Variables" , TemplateSectionType . Variables , "Sort variables" ) ) ; items . push ( new SortQuickPickItem ( "Top level" , TemplateSectionType . TopLevel , "Sort top level items based on recommended order (parameters, functions, variables, resources, outputs)" ) ) ; return items ; } export async function sortTemplate ( template , sectionType , textEditor ) { if ( ! template ) { return ; } switch ( sectionType ) { case TemplateSectionType . Functions : await showSortingResultMessage ( ( ) => sortFunctions ( template , textEditor ) , "Functions" ) ; break ; case TemplateSectionType . Outputs : await showSortingResultMessage ( ( ) => sortOutputs ( template , textEditor ) , "Outputs" ) ; break ; case TemplateSectionType . Parameters : await showSortingResultMessage ( ( ) => sortParameters ( template , textEditor ) , "Parameters" ) ; break ; case TemplateSectionType . Resources : await showSortingResultMessage ( ( ) => sortResources ( template , textEditor ) , "Resources" ) ; break ; case TemplateSectionType . Variables : await showSortingResultMessage ( ( ) => sortVariables ( template , textEditor ) , "Variables" ) ; break ; case TemplateSectionType . TopLevel : await showSortingResultMessage ( ( ) => sortTopLevel ( template , textEditor ) , "Top-level items" ) ; break ; default : assertNever ( sectionType ) ; } } param sortAction param part returns async function showSortingResultMessage ( sortAction , part ) { let didSorting = await sortAction ( ) ; let message = didSorting ? `"${ part }" section was sorted` : `Nothing in "${ part . toLowerCase ( ) }" needed sorting` ; vscode . window . showInformationMessage ( message ) ; } param template param textEditor returns async function sortOutputs ( template , textEditor ) { let rootValue = template . topLevelValue ; if ( ! rootValue ) { return false ; } let outputs = Json . asObjectValue ( rootValue . getPropertyValue ( templateKeys . outputs ) ) ; if ( outputs ) { return await sortGeneric < Json . Property > ( outputs . properties , x => x . nameValue . quotedValue , x => x . span , template , textEditor ) ; } return false ; } param template param textEditor returns async function sortResources ( template , textEditor ) { let rootValue = template . topLevelValue ; if ( ! rootValue ) { return false ; } let resources = Json . asArrayValue ( rootValue . getPropertyValue ( templateKeys . resources ) ) ; if ( resources ) { let didSort1 = await sortGenericDeep < Json . Value , Json . Value > ( resources . elements , getResourcesFromResource , getNameFromResource , x => x . span , template , textEditor ) ; let didSort2 = await sortGeneric < Json . Value > ( resources . elements , getNameFromResource , x => x . span , template , textEditor ) ; return didSort1 || didSort2 ; } return false ; } param template param textEditor returns async function sortTopLevel ( template , textEditor ) { let rootValue = template . topLevelValue ; if ( ! rootValue ) { return false ; } return await sortGeneric < Json . Property > ( rootValue . properties , x => getTopLevelOrder ( x . nameValue . quotedValue ) , x => x . span , template , textEditor ) ; } param template param textEditor returns async function sortVariables ( template , textEditor ) { return await sortGeneric < IVariableDefinition > ( template . topLevelScope . variableDefinitions , x => x . nameValue . quotedValue , x => x . span , template , textEditor ) ; } param template param textEditor returns async function sortParameters ( template , textEditor ) { return await sortGeneric < IParameterDefinition > ( template . topLevelScope . parameterDefinitionsSource . parameterDefinitions , x => x . nameValue . quotedValue , x => x . fullSpan , template , textEditor ) ; } param template param textEditor returns async function sortFunctions ( template , textEditor ) { let didSort1 = await sortGenericDeep < UserFunctionNamespaceDefinition , UserFunctionDefinition > ( template . topLevelScope . namespaceDefinitions , x => x . members , x => x . nameValue . quotedValue , x => x . span , template , textEditor ) ; let didSort2 = await sortGeneric < UserFunctionNamespaceDefinition > ( template . topLevelScope . namespaceDefinitions , x => x . nameValue . quotedValue , x => x . span , template , textEditor ) ; return didSort1 || didSort2 ; } function createCommentsMap ( tokens , commentTokens , lastSpan ) { let commentsMap = new Map < number , Span > ( ) ; let currentCommentsSpan ; let allTokens = tokens . concat ( commentTokens ) ; allTokens . sort ( ( a , b ) => a . span . startIndex - b . span . startIndex ) ; allTokens . forEach ( ( token , index ) => { if ( token . type === Json . TokenType . Comment ) { currentCommentsSpan = ! currentCommentsSpan ? token . span : token . span . union ( currentCommentsSpan ) ; } else { if ( currentCommentsSpan ) { commentsMap . set ( token . span . startIndex , currentCommentsSpan ) ; currentCommentsSpan = undefined ; } } if ( token . span . startIndex > lastSpan . endIndex ) { return commentsMap ; } } ) ; return commentsMap ; } function expandSpanToPrecedingComments ( span , comments ) { let startIndex = span . startIndex ; let commentSpan = comments . get ( startIndex ) ; if ( ! commentSpan ) { return span ; } return commentSpan . union ( span ) ; } function getTopLevelOrder ( key ) { if ( ! key ) { return "9" ; } switch ( key . toLocaleLowerCase ( ) . replace ( /\"/gi , "" ) ) { case "$schema" : return "0" ; case "contentversion" : return "1" ; case "apiprofile" : return "2" ; case "parameters" : return "3" ; case "functions" : return "4" ; case "variables" : return "5" ; case "resources" : return "6" ; case "outputs" : return "7" ; default : return "9" ; } } async function sortGeneric < T > ( list , sortSelector , spanSelector , template , textEditor ) { if ( list . length < 2 ) { return false ; } let document = textEditor . document ; let lastSpan = spanSelector ( list [ list . length - 1 ] ) ; let comments = createCommentsMap ( template . jsonParseResult . tokens , template . jsonParseResult . commentTokens , lastSpan ) ; let selectionWithComments = getSelection ( expandSpanToPrecedingComments ( spanSelector ( list [ 0 ] ) , comments ) , expandSpanToPrecedingComments ( lastSpan , comments ) , document ) ; let indentedTexts = getIndentTexts < T > ( list , x => expandSpanToPrecedingComments ( spanSelector ( x ) , comments ) , document ) ; let orderBefore = list . map ( sortSelector ) ; let sorted = list . sort ( ( a , b ) => sortSelector ( a ) . localeCompare ( sortSelector ( b ) ) ) ; let orderAfter = list . map ( sortSelector ) ; if ( arraysEqual < string > ( orderBefore , orderAfter ) ) { return false ; } let sortedTexts = sorted . map ( value => getText ( expandSpanToPrecedingComments ( spanSelector ( value ) , comments ) , document ) ) ; let joined = joinTexts ( sortedTexts , indentedTexts ) ; await textEditor . edit ( x => x . replace ( selectionWithComments , joined ) ) ; return true ; } async function sortGenericDeep < T , TChild > ( list , childSelector , sortSelector , spanSelector , template , textEditor ) { let didSorting = false ; for ( let item of list ) { let children = childSelector ( item ) ; if ( children ) { if ( await sortGeneric ( children , sortSelector , spanSelector , template , textEditor ) ) { didSorting = true ; } } } return didSorting ; } function getNameFromResource ( value ) { let objectValue = Json . asObjectValue ( value ) ; if ( ! objectValue ) { return "" ; } let nameProperty = objectValue . getPropertyValue ( "name" ) ; if ( ! nameProperty ) { return "" ; } let name = nameProperty . toShortFriendlyString ( ) ; return name ; } function getResourcesFromResource ( value ) { let objectValue = Json . asObjectValue ( value ) ; if ( ! objectValue ) { return undefined ; } let resources = Json . asArrayValue ( objectValue . getPropertyValue ( "resources" ) ) ; if ( ! resources ) { return undefined ; } return resources . elements ; } function joinTexts ( texts , intendentTexts ) { let output = "" ; for ( let index = 0 ; index < texts . length - 1 ; index ++ ) { output = `${ output }${ texts [ index ] }${ intendentTexts [ index ] }` ; } output = `${ output }${ texts [ texts . length - 1 ] }` ; return output ; } function getIndentTexts < T > ( list , spanSelector , document ) { let indentTexts = [ ] ; for ( let index = 0 ; index < list . length - 1 ; index ++ ) { let span1 = spanSelector ( list [ index ] ) ; let span2 = spanSelector ( list [ index + 1 ] ) ; let intendentText = document . getText ( new vscode . Range ( document . positionAt ( span1 . afterEndIndex ) , document . positionAt ( span2 . startIndex ) ) ) ; indentTexts . push ( intendentText ) ; } return indentTexts ; } function getRange ( span , document ) { return new vscode . Range ( document . positionAt ( span . startIndex ) , document . positionAt ( span . afterEndIndex ) ) ; } function getText ( span , document ) { let range = getRange ( span , document ) ; let text = document . getText ( range ) ; return text ; } function getSelection ( start , end , document ) { let startIndex = start . startIndex ; let endIndex = end . afterEndIndex ; let selection = new vscode . Selection ( document . positionAt ( startIndex ) , document . positionAt ( endIndex ) ) ; return selection ; } function arraysEqual < T > ( a , b ) { if ( a === b ) { return true ; } if ( ! a || ! b ) { return false ; } if ( a . length !== b . length ) { return false ; } for ( let i = 0 ; i < a . length ; ++ i ) { if ( a [ i ] !== b [ i ] ) { return false ; } } return true ; }