blob: 63db013e2c94f3d4415659eb4ff1d77a525776c2 [file] [log] [blame]
/**
* @license
* Copyright (C) 2020 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.
*/
import {CommentRange} from '../../../types/common';
import {FILE, LineNumber} from './gr-diff-line';
import {Side} from '../../../constants/constants';
import {DiffInfo} from '../../../types/diff';
// If any line of the diff is more than the character limit, then disable
// syntax highlighting for the entire file.
export const SYNTAX_MAX_LINE_LENGTH = 500;
/**
* Compare two ranges. Either argument may be falsy, but will only return
* true if both are falsy or if neither are falsy and have the same position
* values.
*/
export function rangesEqual(a?: CommentRange, b?: CommentRange): boolean {
if (!a && !b) {
return true;
}
if (!a || !b) {
return false;
}
return (
a.start_line === b.start_line &&
a.start_character === b.start_character &&
a.end_line === b.end_line &&
a.end_character === b.end_character
);
}
export function isLongCommentRange(range: CommentRange): boolean {
return range.end_line - range.start_line > 10;
}
export function getLineNumberByChild(node?: Node) {
return getLineNumber(getLineElByChild(node));
}
export function lineNumberToNumber(lineNumber?: LineNumber | null): number {
if (!lineNumber) return 0;
if (lineNumber === 'LOST') return 0;
if (lineNumber === 'FILE') return 0;
return lineNumber;
}
export function getLineElByChild(node?: Node): HTMLElement | null {
while (node) {
if (node instanceof Element) {
if (node.classList.contains('lineNum')) {
return node as HTMLElement;
}
if (node.classList.contains('section')) {
return null;
}
}
node = node.previousSibling ?? node.parentElement ?? undefined;
}
return null;
}
export function getSideByLineEl(lineEl: Element) {
return lineEl.classList.contains(Side.RIGHT) ? Side.RIGHT : Side.LEFT;
}
export function getLineNumber(lineEl?: Element | null): LineNumber | null {
if (!lineEl) return null;
const lineNumberStr = lineEl.getAttribute('data-value');
if (!lineNumberStr) return null;
if (lineNumberStr === FILE) return FILE;
if (lineNumberStr === 'LOST') return 'LOST';
const lineNumber = Number(lineNumberStr);
return Number.isInteger(lineNumber) ? lineNumber : null;
}
export function getLine(threadEl: HTMLElement): LineNumber {
const lineAtt = threadEl.getAttribute('line-num');
if (lineAtt === 'LOST') return lineAtt;
if (!lineAtt || lineAtt === 'FILE') return FILE;
const line = Number(lineAtt);
if (isNaN(line)) throw new Error(`cannot parse line number: ${lineAtt}`);
if (line < 1) throw new Error(`line number smaller than 1: ${line}`);
return line;
}
export function getSide(threadEl: HTMLElement): Side | undefined {
// TODO(dhruvsri): Remove check for comment-side once all users of gr-diff
// start setting diff-side
const sideAtt =
threadEl.getAttribute('diff-side') || threadEl.getAttribute('comment-side');
if (!sideAtt) {
console.warn('comment thread without side');
return undefined;
}
if (sideAtt !== Side.LEFT && sideAtt !== Side.RIGHT)
throw Error(`unexpected value for side: ${sideAtt}`);
return sideAtt as Side;
}
export function getRange(threadEl: HTMLElement): CommentRange | undefined {
const rangeAtt = threadEl.getAttribute('range');
if (!rangeAtt) return undefined;
const range = JSON.parse(rangeAtt) as CommentRange;
if (!range.start_line) throw new Error(`invalid range: ${rangeAtt}`);
return range;
}
// TODO: This type should be exposed to gr-diff clients in a separate type file.
// For Gerrit these are instances of GrCommentThread, but other gr-diff users
// have different HTML elements in use for comment threads.
// TODO: Also document the required HTML attributes that thread elements must
// have, e.g. 'diff-side', 'range', 'line-num'.
export interface GrDiffThreadElement extends HTMLElement {
rootId: string;
}
const VISIBLE_TEXT_NODE_TYPES = [Node.TEXT_NODE, Node.ELEMENT_NODE];
export function getPreviousContentNodes(node?: Node | null) {
const sibs = [];
while (node) {
const {parentNode, previousSibling} = node;
const topContentLevel =
parentNode &&
(parentNode as HTMLElement).classList.contains('contentText');
let previousEl: Node | undefined | null;
if (previousSibling) {
previousEl = previousSibling;
} else if (!topContentLevel) {
previousEl = parentNode?.previousSibling;
}
if (previousEl && VISIBLE_TEXT_NODE_TYPES.includes(previousEl.nodeType)) {
sibs.push(previousEl);
}
node = previousEl;
}
return sibs;
}
export function isThreadEl(node: Node): node is GrDiffThreadElement {
return (
node.nodeType === Node.ELEMENT_NODE &&
(node as Element).classList.contains('comment-thread')
);
}
/**
* @return whether any of the lines in diff are longer
* than SYNTAX_MAX_LINE_LENGTH.
*/
export function anyLineTooLong(diff?: DiffInfo) {
if (!diff) return false;
return diff.content.some(section => {
const lines = section.ab
? section.ab
: (section.a || []).concat(section.b || []);
return lines.some(line => line.length >= SYNTAX_MAX_LINE_LENGTH);
});
}