import { AxeCoreResults , AxeResult , FingerprintGenerator } from 'axe-result-converter' ; import { inject , injectable } from 'inversify' ; import { UrlInfo } from 'accessibility-insights-report' ; import { BaselineEvaluation , BaselineOptions , BaselineResult , CountsByRule } from './baseline-types' ; import { BaselineGenerator } from './baseline-generator' ; interface UrlComparison { fixedCount : number ; intersectingCount : number ; newUrls : Set < string > ; urlInfos : UrlInfo [ ] ; } const newComesFirst = - 1 ; const oldComesFirst = 1 ; @ injectable ( ) export class BaselineEngine { constructor ( @ inject ( BaselineGenerator ) private readonly baselineGenerator , @ inject ( FingerprintGenerator ) private readonly fingerprintGenerator ) { } public updateResultsInPlace ( axeResults , baselineOptions ) { const oldBaselineResults = baselineOptions . baselineContent ?. results ?? [ ] ; const newBaseline = this . baselineGenerator . generateBaseline ( axeResults . violations , baselineOptions . urlNormalizer ) ; const newBaselineResults = newBaseline . results ; const evaluation = { suggestedBaselineUpdate : baselineOptions . baselineContent ? null : newBaseline , fixedViolationsByRule : { } , newViolationsByRule : { } , totalFixedViolations : 0 , totalNewViolations : 0 , } ; if ( baselineOptions . baselineContent ) { evaluation . totalBaselineViolations = 0 ; } let oldResultIndex = 0 ; let newResultIndex = 0 ; while ( oldResultIndex < oldBaselineResults . length || newResultIndex < newBaselineResults . length ) { const oldBaselineResult = oldResultIndex < oldBaselineResults . length ? oldBaselineResults [ oldResultIndex ] : null ; const newBaselineResult = newResultIndex < newBaselineResults . length ? newBaselineResults [ newResultIndex ] : null ; const resultDetailComparison = this . compareResultDetails ( oldBaselineResult , newBaselineResult ) ; if ( resultDetailComparison < 0 ) { this . addFixedViolationsToEvaluation ( oldBaselineResult , evaluation ) ; oldResultIndex ++ ; } else if ( resultDetailComparison > 0 ) { this . addNewViolationsToEvaluation ( newBaselineResult , evaluation ) ; this . updateAxeResults ( axeResults , newBaselineResult ) ; newResultIndex ++ ; } else { const urlComparison = this . getUrlComparison ( oldBaselineResult . urls , newBaselineResult . urls ) ; if ( urlComparison . fixedCount ) { this . updateCountsByRule ( evaluation . fixedViolationsByRule , oldBaselineResult . rule , urlComparison . fixedCount ) ; evaluation . totalFixedViolations += urlComparison . fixedCount ; } if ( urlComparison . newUrls . size ) { this . updateCountsByRule ( evaluation . newViolationsByRule , oldBaselineResult . rule , urlComparison . newUrls . size ) ; evaluation . totalNewViolations += urlComparison . newUrls . size ; } evaluation . totalBaselineViolations += oldBaselineResult . urls . length ; this . updateAxeResults ( axeResults , newBaselineResult , urlComparison . newUrls ) ; oldResultIndex ++ ; newResultIndex ++ ; } } if ( evaluation . totalFixedViolations || evaluation . totalNewViolations || baselineOptions . baselineContent ?. metadata ?. fileFormatVersion !== newBaseline . metadata . fileFormatVersion ) { evaluation . suggestedBaselineUpdate = newBaseline ; } return evaluation ; } private updateAxeResults ( axeResults , baselineResult , newUrls ? ) { const fingerprint = this . fingerprintGenerator . getFingerprint ( { rule : baselineResult . rule , snippet : baselineResult . htmlSnippet , cssSelector : baselineResult . cssSelector , xpathSelector : baselineResult . xpathSelector , } ) ; const resultToUpdate = axeResults . violations . get ( fingerprint ) ; resultToUpdate . urlInfos = this . buildUrlInfos ( baselineResult , newUrls ) ; } private buildUrlInfos ( newBaselineResult , newUrls ? ) { const urlInfos = [ ] ; newBaselineResult . urls . map ( ( url ) => urlInfos . push ( this . buildUrlInfo ( url , newUrls ) ) ) ; return urlInfos ; } private buildUrlInfo ( url , newUrls ? ) { const isNew = ! newUrls || newUrls . has ( url ) ; return { url , baselineStatus : isNew ? 'new' : 'existing' , } ; } private addFixedViolationsToEvaluation ( fixedViolation , evaluation ) { this . updateCountsByRule ( evaluation . fixedViolationsByRule , fixedViolation . rule , fixedViolation . urls . length ) ; evaluation . totalFixedViolations += fixedViolation . urls . length ; evaluation . totalBaselineViolations += fixedViolation . urls . length ; } private addNewViolationsToEvaluation ( newViolation , evaluation ) { this . updateCountsByRule ( evaluation . newViolationsByRule , newViolation . rule , newViolation . urls . length ) ; evaluation . totalNewViolations += newViolation . urls . length ; } private compareResultDetails ( oldResult , newResult ) { if ( oldResult && newResult ) { return ( this . safelyCompareStrings ( oldResult . rule , newResult . rule ) || this . safelyCompareStrings ( oldResult . cssSelector , newResult . cssSelector ) || this . safelyCompareStrings ( oldResult . xpathSelector , newResult . xpathSelector ) || this . safelyCompareStrings ( oldResult . htmlSnippet , newResult . htmlSnippet ) ) ; } return oldResult ? newComesFirst : oldComesFirst ; } private safelyCompareStrings ( oldString , newString ) { if ( oldString && newString ) { return oldString . localeCompare ( newString ) ; } if ( ! oldString && ! newString ) { return 0 ; } return oldString ? newComesFirst : oldComesFirst ; } private updateCountsByRule ( countsByRule , rule , count ) { const oldCount = countsByRule [ rule ] || 0 ; countsByRule [ rule ] = oldCount + count ; } private getUrlComparison ( oldUrls , newUrls ) { const urlComparison = { fixedCount : 0 , intersectingCount : 0 , newUrls : new Set ( ) , urlInfos : [ ] , } ; const oldSet = new Set ( oldUrls ) ; newUrls . map ( ( url ) => { if ( oldSet . has ( url ) ) { urlComparison . intersectingCount ++ ; urlComparison . urlInfos . push ( { url , baselineStatus : 'existing' , } ) ; } else { urlComparison . newUrls . add ( url ) ; urlComparison . urlInfos . push ( { url , baselineStatus : 'new' , } ) ; } } ) ; urlComparison . fixedCount = oldSet . size - urlComparison . intersectingCount ; return urlComparison ; } }