import { resolve , sep , relative , dirname } from "path" ; import { Diagnostic , Range } from "vscode-languageserver-types" ; import { TextDocument } from "vscode-languageserver" ; import { readJsonSync , readFileSync } from "fs-extra" ; import { expect } from "chai" ; import { sortBy , forEachRight , map , cloneDeep , forEach } from "lodash" ; import { generateModel } from "@ui5-language-assistant/test-utils" ; import { UI5SemanticModel } from "@ui5-language-assistant/semantic-model-types" ; import { generate } from "@ui5-language-assistant/semantic-model" ; import { getXMLViewDiagnostics } from "../../../src/xml-view-diagnostics" ; export const INPUT_FILE_NAME = "input.xml" ; export const OUTPUT_LSP_RESPONSE_FILE_NAME = "output-lsp-response.json" ; export type LSPDiagnosticOptions = { flexEnabled : boolean ; } ; export async function snapshotTestLSPDiagnostic ( testDir , options ) { const pkgJsonPath = require . resolve ( "@ui5-language-assistant/language-server/package.json" ) ; const languageServerDir = dirname ( pkgJsonPath ) ; const rootDir = resolve ( languageServerDir , ".." , ".." ) ; const originalXMLSnippet = readInputXMLSnippet ( testDir , false ) ; const diagnosticsSnapshots = readSnapshotDiagnosticsLSPResponse ( testDir ) ; const snapshotRanges = map ( diagnosticsSnapshots , ( _ ) => _ . range ) ; const snapshotXMLWithMarkedRanges = computeXMLWithMarkedRanges ( testDir , snapshotRanges ) ; expect ( originalXMLSnippet , `The XML input snippet range markers don't match the snapshot response ranges. Did you forget to run "yarn update-snapshots"?
     Input xml is at: ${ relative ( rootDir , getInputXMLSnippetPath ( testDir ) ) }
     Snapshot response is at: ${ relative ( rootDir , getSnapshotDiagnosticsLSPResponsePath ( testDir ) ) }` ) . to . equal ( snapshotXMLWithMarkedRanges ) ; const newlyComputedDiagnostics = await computeNewDiagnosticLSPResponse ( testDir , options ) ; expect ( newlyComputedDiagnostics , `Snapshot Mismatch in LSP Diagnostics response. 
     Snapshot is at: ${ relative ( rootDir , getSnapshotDiagnosticsLSPResponsePath ( testDir ) ) }` ) . to . deep . equal ( diagnosticsSnapshots ) ; const newlyCommutedRanges = map ( newlyComputedDiagnostics , ( _ ) => _ . range ) ; const newlyComputedXMLWithMarkedRanges = computeXMLWithMarkedRanges ( testDir , newlyCommutedRanges ) ; expect ( originalXMLSnippet , `The XML input snippet range markers are incorrect. 
     Snapshot is at: ${ relative ( rootDir , getInputXMLSnippetPath ( testDir ) ) }` ) . to . equal ( newlyComputedXMLWithMarkedRanges ) ; } export function getSnapshotDiagnosticsLSPResponsePath ( sourcesTestDir ) { const snapshotResponsePath = resolve ( sourcesTestDir , OUTPUT_LSP_RESPONSE_FILE_NAME ) ; return snapshotResponsePath ; } export function readSnapshotDiagnosticsLSPResponse ( sourcesTestDir ) { const lspResponsePath = getSnapshotDiagnosticsLSPResponsePath ( sourcesTestDir ) ; const expectedDiagnostics = readJsonSync ( lspResponsePath ) ; return expectedDiagnostics ; } const ui5ModelPromise = generateModel ( { version : "1.71.14" , modelGenerator : generate , } ) ; let ui5Model = undefined ; export async function computeNewDiagnosticLSPResponse ( testDir , options ) { ui5Model = await ui5ModelPromise ; const xmlTextSnippet = readInputXMLSnippet ( testDir ) ; const xmlTextDoc = TextDocument . create ( `file://${ getInputXMLSnippetPath ( testDir ) }` , "xml" , 0 , xmlTextSnippet ) ; const actualDiagnostics = getXMLViewDiagnostics ( { document : xmlTextDoc , ui5Model , flexEnabled : options ? options . flexEnabled : false , } ) ; const diagnosticsForAssertions = cleanupLSPResponseForAssertions ( actualDiagnostics ) ; return diagnosticsForAssertions ; } export function computeXMLWithMarkedRanges ( testDir , ranges ) { const xmlTextSnippet = readInputXMLSnippet ( testDir ) ; const xmlTextDoc = TextDocument . create ( `file://${ getInputXMLSnippetPath ( testDir ) }` , "xml" , 0 , xmlTextSnippet ) ; const issueRangesByOffsetAscending = sortBy ( ranges , ( _ ) => xmlTextDoc . offsetAt ( _ . start ) ) ; let xmlSnippetWithRangeMarkers = xmlTextSnippet ; forEachRight ( issueRangesByOffsetAscending , ( _ ) => { const startOffset = xmlTextDoc . offsetAt ( _ . start ) ; const endOffset = xmlTextDoc . offsetAt ( _ . end ) ; const prefix = xmlSnippetWithRangeMarkers . substring ( 0 , startOffset ) ; const toBeMarked = xmlSnippetWithRangeMarkers . substring ( startOffset , endOffset ) ; const suffix = xmlSnippetWithRangeMarkers . substring ( endOffset ) ; xmlSnippetWithRangeMarkers = prefix + "🢂" + toBeMarked + "🢀" + suffix ; } ) ; return xmlSnippetWithRangeMarkers ; } export function getInputXMLSnippetPath ( sourcesTestDir ) { const inputXMLSnippetPath = resolve ( sourcesTestDir , INPUT_FILE_NAME ) ; return inputXMLSnippetPath ; } function readInputXMLSnippet ( testDir , removeRangeMarkers = true ) { const inputPath = getInputXMLSnippetPath ( testDir ) ; const xmlOriginalContent = readFileSync ( inputPath ) . toString ( "utf-8" ) ; if ( removeRangeMarkers ) { const xmlTextSnippetWithoutMarkers = stripRangeMarkers ( xmlOriginalContent ) ; return xmlTextSnippetWithoutMarkers ; } return xmlOriginalContent ; } export function toSourcesTestDir ( libTestDir ) { return libTestDir . replace ( sep + "lib" , "" ) ; } function stripRangeMarkers ( text ) { return text . replace ( /[🢂🢀]/gu , "" ) ; } function cleanupLSPResponseForAssertions ( diagnostics ) { const clonedDiagnostics = cloneDeep ( diagnostics ) ; forEach ( clonedDiagnostics , ( _ ) => { forEach ( _ . relatedInformation , ( info ) => { const uriSuffixMatch = /.+(snapshots.*)/ . exec ( info . location . uri ) ; if ( uriSuffixMatch === null ) { throw Error ( "Failure computing a relatedInformation URI for snapshots!" ) ; } const uriSuffixWithForwardSlash = uriSuffixMatch [ 1 ] . replace ( /\\/g , "/" ) ; info . location . uri = uriSuffixWithForwardSlash ; } ) ; return _ ; } ) ; return clonedDiagnostics ; }