/* global linter editor clipString createLinterHelpDialog $ $create */
'use strict';
Object.assign(linter, (() => {
const tables = new Map();
const helpDialog = createLinterHelpDialog(getIssues);
document.addEventListener('DOMContentLoaded', () => {
$('#lint-help').addEventListener('click', helpDialog.show);
}, {once: true});
linter.onLintingUpdated((annotationsNotSorted, annotations, cm) => {
let table = tables.get(cm);
if (!table) {
table = createTable(cm);
tables.set(cm, table);
const container = $('.lint-report-container');
const nextSibling = findNextSibling(tables, cm);
container.insertBefore(table.element, nextSibling && tables.get(nextSibling).element);
}
table.updateCaption();
table.updateAnnotations(annotations);
updateCount();
});
linter.onUnhook(cm => {
const table = tables.get(cm);
if (table) {
table.element.remove();
tables.delete(cm);
return {refreshReport};
function updateCount() {
const issueCount = Array.from(tables.values())
.reduce((sum, table) => sum + table.trs.length, 0);
$('#lint').classList.toggle('hidden-unless-compact', issueCount === 0);
$('#issue-count').textContent = issueCount;
function getIssues() {
const issues = new Set();
for (const table of tables.values()) {
for (const tr of table.trs) {
issues.add(tr.getAnnotation());
return issues;
function findNextSibling(tables, cm) {
const editors = editor.getEditors();
let i = editors.indexOf(cm) + 1;
while (i < editors.length) {
if (tables.has(editors[i])) {
return editors[i];
i++;
function refreshReport() {
function createTable(cm) {
const caption = $create('caption');
const tbody = $create('tbody');
const table = $create('table', [caption, tbody]);
const trs = [];
return {
element: table,
trs,
updateAnnotations,
updateCaption,
};
function updateCaption() {
caption.textContent = editor.getEditorTitle(cm);
function updateAnnotations(lines) {
let i = 0;
for (const anno of getAnnotations()) {
let tr;
if (i < trs.length) {
tr = trs[i];
} else {
tr = createTr();
trs.push(tr);
tbody.append(tr.element);
tr.update(anno);
if (i === 0) {
trs.length = 0;
tbody.textContent = '';
while (trs.length > i) {
trs.pop().element.remove();
table.classList.toggle('empty', trs.length === 0);
function *getAnnotations() {
for (const line of lines.filter(Boolean)) {
yield *line;
function createTr() {
let anno;
const severityIcon = $create('div');
const severity = $create('td', {attributes: {role: 'severity'}}, severityIcon);
const line = $create('td', {attributes: {role: 'line'}});
const col = $create('td', {attributes: {role: 'col'}});
const message = $create('td', {attributes: {role: 'message'}});
const trElement = $create('tr', {
onclick: () => gotoLintIssue(cm, anno),
}, [
severity,
line,
$create('td', {attributes: {role: 'sep'}}, ':'),
col,
message,
]);
element: trElement,
update,
getAnnotation: () => anno,
function update(_anno) {
anno = _anno;
trElement.className = anno.severity;
severity.dataset.rule = anno.rule;
severityIcon.className = `CodeMirror-lint-marker CodeMirror-lint-marker-${anno.severity}`;
severityIcon.textContent = anno.severity;
line.textContent = anno.from.line + 1;
col.textContent = anno.from.ch + 1;
message.title = clipString(anno.message, 1000) +
(anno.rule ? `\n(${anno.rule})` : '');
message.textContent = clipString(anno.message, 100).replace(/ at line.*/, '');
function gotoLintIssue(cm, anno) {
editor.scrollToEditor(cm);
cm.focus();
cm.jumpToPos(anno.from);
})());