99 clearAnimation ,
1010 clearAnnotations ,
1111} from "../common/animation" ;
12- import { lineSelector } from "./constants" ;
12+ import { oldLineSelector , newLineSelector } from "./constants" ;
1313import { colors } from "../common/constants" ;
1414import { print } from "src/utils" ;
1515import { getConsent , getPRReport } from "../common/fetchers" ;
@@ -82,19 +82,41 @@ async function execute() {
8282 updateContainer ( head , patch , change ) ;
8383
8484 globals . coverageReport = transformReport ( coverageReport . files ) ;
85- animateAndAnnotateLines ( lineSelector , annotateLine ) ;
85+
86+ annotateLines ( ) ;
8687}
8788
88- function createContainer ( ) {
89- const parent = document . getElementsByClassName ( "pr-review-tools" ) . item ( 0 ) ! ;
89+ function isNewExperience ( ) {
90+ const toolbar = document . querySelector (
91+ "section[class*=' PullRequestFilesToolbar-module__toolbar']"
92+ ) ;
93+ return ! ! toolbar ;
94+ }
9095
96+ function createContainer ( ) {
9197 const element = (
92- < div className = "codecov-flex float-left mr-4 " id = "coverage-report-data" >
93- < div className = "my -auto mr-6" > Loading coverage report...</ div >
98+ < div className = "ml-auto " id = "coverage-report-data" >
99+ < div className = "ml -auto mr-6" > Loading coverage report...</ div >
94100 </ div >
95101 ) ;
96102
97- parent . prepend ( element ) ;
103+ if ( ! isNewExperience ( ) ) {
104+ // Old experience
105+ const parent = document
106+ . getElementsByClassName ( "pr-review-tools" )
107+ . item ( 0 ) ?. parentElement ;
108+
109+ parent ?. insertBefore ( element , parent . lastElementChild ) ;
110+
111+ return ;
112+ }
113+
114+ // New experience code
115+ const parent = document . querySelector (
116+ "section[class*=' PullRequestFilesToolbar-module__toolbar']"
117+ ) ! ;
118+
119+ parent . insertBefore ( element , parent . lastChild ! ) ;
98120}
99121
100122function getMetadataFromURL ( ) : { [ key : string ] : string } | null {
@@ -111,7 +133,7 @@ const handleToggleClick: React.MouseEventHandler = (event) => {
111133 const button = event . target as HTMLElement ;
112134 const isInactive = button . getAttribute ( "data-inactive" ) ;
113135 if ( isInactive == "true" ) {
114- animateAndAnnotateLines ( lineSelector , annotateLine ) ;
136+ annotateLines ( ) ;
115137 button . removeAttribute ( "data-inactive" ) ;
116138 button . innerText = "Hide Coverage" ;
117139 } else {
@@ -172,7 +194,38 @@ function transformReport(filesReport: any) {
172194 return result ;
173195}
174196
175- function annotateLine ( line : HTMLElement ) {
197+ function annotateLines ( ) {
198+ if ( ! isNewExperience ( ) ) {
199+ // old selector/annotation logic
200+ animateAndAnnotateLines ( oldLineSelector , oldAnnotateLine ) ;
201+ } else {
202+ // new selector/annotation logic
203+ animateAndAnnotateLines ( newLineSelector , newAnnotateLine ) ;
204+ }
205+ }
206+
207+ function clearAnimationAndAnnotations ( ) {
208+ if ( ! isNewExperience ( ) ) {
209+ // old selector/annotation logic
210+ clearAnimation ( oldLineSelector , oldAnnotateLine ) ;
211+ clearAnnotations ( ( line : HTMLElement ) => ( line . style . boxShadow = "inherit" ) ) ;
212+ } else {
213+ // new selector/annotation logic
214+ clearAnimation ( newLineSelector , newAnnotateLine ) ;
215+ clearAnnotations ( ( line : HTMLElement ) => {
216+ if ( line . children . length < 3 ) {
217+ return ;
218+ }
219+ let child = line . lastElementChild as HTMLElement ;
220+ if ( child . style . boxShadow !== "inherit" ) {
221+ child . style . boxShadow = "inherit" ;
222+ return ;
223+ }
224+ } ) ;
225+ }
226+ }
227+
228+ function oldAnnotateLine ( line : HTMLElement ) {
176229 if ( line . getAttribute ( "data-split-side" ) === "left" ) {
177230 // split diff view: ignore deleted line
178231 return ;
@@ -203,9 +256,74 @@ function annotateLine(line: HTMLElement) {
203256 }
204257}
205258
206- function clearAnimationAndAnnotations ( ) {
207- clearAnimation ( lineSelector , annotateLine ) ;
208- clearAnnotations ( ( line : HTMLElement ) => ( line . style . boxShadow = "inherit" ) ) ;
259+ function newAnnotateLine ( line : HTMLElement ) {
260+ const secondChild = line . children [ 1 ] ;
261+ const thirdChild = line . children [ 2 ] ;
262+
263+ if ( ! secondChild || ! thirdChild ) {
264+ return ;
265+ }
266+
267+ // If the second child of the row is a line number cell (possibly empty), we're looking at a unified diff.
268+ const isUnifiedDiff = line
269+ . querySelectorAll ( "td[class*=' diff-line-number']" )
270+ . values ( )
271+ . toArray ( )
272+ . includes ( secondChild ) ;
273+
274+ // New line number cell is in cell 2 in a unified diff and cell 3 in a split diff.
275+ const newLineNumberCell = isUnifiedDiff ? secondChild : thirdChild ;
276+
277+ // We want to ignore deleted lines.
278+ // If the new line number cell does not contain a line number, then the line was deleted.
279+ if ( ! newLineNumberCell . textContent ) {
280+ return ;
281+ }
282+
283+ // This is not a deleted line, grab the line number and find coverage value.
284+ const lineNumber = newLineNumberCell . textContent ;
285+
286+ // Get the file name.
287+ // Up to the shared root of the file section then down to the file header
288+ //
289+ // For some reason the text content here contains three invisible bytes
290+ // adding up to one utf-8 character, which we need to remove.
291+ //
292+ // >> e = new TextEncoder()
293+ // >> e.encode(newLineNumberCell.textContent)
294+ // Uint8Array(59) [ 226, 128, 142, 97, 112, 112, 115, 47, 119, 111, … ]
295+ // >> e.encode("apps/worker/services/test_analytics/ta_process_flakes.py")
296+ // Uint8Array(56) [ 97, 112, 112, 115, 47, 119, 111, 114, 107, 101, … ]
297+ // >> e.encode(newLineNumberCell.textContent.slice(1))
298+ // Uint8Array(56) [ 97, 112, 112, 115, 47, 119, 111, 114, 107, 101, … ]
299+ //
300+ // Idk why these are here, but we can just remove them.
301+
302+ const fileNameContainer = line
303+ . closest ( "div[class^='Diff-module__diffTargetable']" )
304+ ?. querySelector ( "h3[class^='DiffFileHeader-module__file-name']" ) ;
305+ const fileName = fileNameContainer ?. textContent ?. slice ( 1 ) ;
306+ if ( ! fileName ) {
307+ return ;
308+ }
309+
310+ const status =
311+ globals . coverageReport ?. [ fileName ] ?. lines [ lineNumber ] ?. coverage [ "head" ] ;
312+ if ( status == null ) {
313+ return ;
314+ }
315+
316+ const lineContentCell = newLineNumberCell . nextSibling as HTMLElement ;
317+ const borderStylePrefix = "inset 2px 0 " ;
318+ if ( status === CoverageStatus . COVERED ) {
319+ lineContentCell . style . boxShadow = `${ borderStylePrefix } ${ colors . green } ` ;
320+ } else if ( status === CoverageStatus . UNCOVERED ) {
321+ lineContentCell . style . boxShadow = `${ borderStylePrefix } ${ colors . red } ` ;
322+ } else if ( status === CoverageStatus . PARTIAL ) {
323+ lineContentCell . style . boxShadow = `${ borderStylePrefix } ${ colors . yellow } ` ;
324+ } else {
325+ lineContentCell . style . boxShadow = "inherit" ;
326+ }
209327}
210328
211329await init ( ) ;
0 commit comments