diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html new file mode 100644 index 0000000000..56a6fb9b1a --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js new file mode 100644 index 0000000000..5e7cc26932 --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js @@ -0,0 +1,129 @@ +/** + * @license + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + /** @enum {string} */ + Gerrit.CoverageType = { + /** + * start_character and end_character of the range will be ignored for this + * type. + */ + COVERED: 'COVERED', + /** + * start_character and end_character of the range will be ignored for this + * type. + */ + NOT_COVERED: 'NOT_COVERED', + PARTIALLY_COVERED: 'PARTIALLY_COVERED', + /** + * You don't have to use this. If there is no coverage information for a + * range, then it implicitly means NOT_INSTRUMENTED. start_character and + * end_character of the range will be ignored for this type. + */ + NOT_INSTRUMENTED: 'NOT_INSTRUMENTED', + }; + + /** + * @typedef {{ + * side: string, + * type: Gerrit.CoverageType, + * code_range: Gerrit.Range, + * }} + */ + Gerrit.CoverageRange; + + Polymer({ + is: 'gr-coverage-layer', + + properties: { + /** + * Must be sorted by code_range.start_line. + * Must only contain ranges that match the side. + * + * @type {!Array} + */ + coverageRanges: Array, + side: String, + + /** + * We keep track of the line number from the previous annotate() call, + * and also of the index of the coverage range that had matched. + * annotate() calls are coming in with increasing line numbers and + * coverage ranges are sorted by line number. So this is a very simple + * and efficient way for finding the coverage range that matches a given + * line number. + */ + _lineNumber: { + type: Number, + value: 0, + }, + _index: { + type: Number, + value: 0, + }, + }, + + /** + * Layer method to add annotations to a line. + * + * @param {!HTMLElement} el Not used for this layer. + * @param {!HTMLElement} lineNumberEl The element with the line number. + * @param {!Object} line Not used for this layer. + */ + annotate(el, lineNumberEl, line) { + if (!lineNumberEl || !lineNumberEl.classList.contains(this.side)) { + return; + } + const elementLineNumber = parseInt( + lineNumberEl.getAttribute('data-value'), 10); + if (!elementLineNumber || elementLineNumber < 1) return; + + // If the line number is smaller than before, then we have to reset our + // algorithm and start searching the coverage ranges from the beginning. + // That happens for example when you expand diff sections. + if (elementLineNumber < this._lineNumber) { + this._index = 0; + } + this._lineNumber = elementLineNumber; + + // We simply loop through all the coverage ranges until we find one that + // matches the line number. + while (this._index < this.coverageRanges.length) { + const coverageRange = this.coverageRanges[this._index]; + + // If the line number has moved past the current coverage range, then + // try the next coverage range. + if (this._lineNumber > coverageRange.code_range.end_line) { + this._index++; + continue; + } + + // If the line number has not reached the next coverage range (and the + // range before also did not match), then this line has not been + // instrumented. Nothing to do for this line. + if (this._lineNumber < coverageRange.code_range.start_line) { + return; + } + + // The line number is within the current coverage range. Style it! + lineNumberEl.classList.add(coverageRange.type); + return; + } + }, + }); +})(); diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html new file mode 100644 index 0000000000..edd88a24e1 --- /dev/null +++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html @@ -0,0 +1,138 @@ + + + + +gr-coverage-layer + + + + + + + + + + + + + + + diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html index b5f21b6b91..7bfec005db 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html @@ -16,6 +16,7 @@ limitations under the License. --> + @@ -31,6 +32,14 @@ limitations under the License. + + @@ -108,6 +117,19 @@ limitations under the License. type: Array, value: () => [], }, + /** @type {!Array} */ + coverageRanges: { + type: Array, + value: () => [], + }, + _leftCoverageRanges: { + type: Array, + computed: '_computeLeftCoverageRanges(coverageRanges)', + }, + _rightCoverageRanges: { + type: Array, + computed: '_computeRightCoverageRanges(coverageRanges)', + }, /** * The promise last returned from `render()` while the asynchronous * rendering is running - `null` otherwise. Provides a `cancel()` @@ -125,6 +147,14 @@ limitations under the License. '_groupsChanged(_groups.splices)', ], + _computeLeftCoverageRanges(coverageRanges) { + return coverageRanges.filter(range => range && range.side === 'left'); + }, + + _computeRightCoverageRanges(coverageRanges) { + return coverageRanges.filter(range => range && range.side === 'right'); + }, + render(keyLocations, prefs) { // Setting up annotation layers must happen after plugins are // installed, and |render| satisfies the requirement, however, @@ -184,6 +214,8 @@ limitations under the License. this._createIntralineLayer(), this._createTabIndicatorLayer(), this.$.rangeLayer, + this.$.coverageLayerLeft, + this.$.coverageLayerRight, ]; // Get layers from plugins (if any). diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html index 61e8603ed0..8a117af73b 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html @@ -270,6 +270,15 @@ limitations under the License. .newlineWarning.hidden { display: none; } + .lineNum.COVERED { + background-color: #E0F2F1; + } + .lineNum.NOT_COVERED { + background-color: #FFD1A4; + } + .lineNum.PARTIALLY_COVERED { + background: linear-gradient(to right bottom, #FFD1A4 0%, #FFD1A4 50%, #E0F2F1 50%, #E0F2F1 100%); + }
@@ -289,6 +298,7 @@ limitations under the License. [], }, + /** @type {!Array} */ + coverageRanges: { + type: Array, + value: () => [], + }, lineWrapping: { type: Boolean, value: false,