Split out lint code from edit.js
This commit is contained in:
parent
dfa71f699e
commit
07dce1006d
|
@ -9,6 +9,7 @@
|
|||
<script src="content/apply.js"></script>
|
||||
<link rel="stylesheet" href="edit/edit.css">
|
||||
<script src="edit/edit.js"></script>
|
||||
<script src="edit/lint.js"></script>
|
||||
|
||||
<script src="vendor/codemirror/lib/codemirror.js"></script>
|
||||
<link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css">
|
||||
|
@ -33,14 +34,15 @@
|
|||
|
||||
<script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="vendor/codemirror/addon/lint/lint.css" />
|
||||
<script src="vendor/codemirror/addon/lint/lint.js"></script>
|
||||
<script src="vendor-overwrites/codemirror/addon/lint/linter.js"></script>
|
||||
<!-- CSSLint -->
|
||||
<script src="vendor/csslint/csslint-worker.js"></script>
|
||||
<!-- stylelint -->
|
||||
<script src="vendor-overwrites/stylelint/stylelint-bundle.min.js"></script>
|
||||
<script src="vendor-overwrites/codemirror/addon/lint/stylelint-config.js"></script>
|
||||
<!-- linter -->
|
||||
<link rel="stylesheet" href="vendor/codemirror/addon/lint/lint.css" />
|
||||
<script src="vendor/codemirror/addon/lint/lint.js"></script>
|
||||
<script src="vendor-overwrites/codemirror/addon/lint/linter.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="vendor/codemirror/addon/hint/show-hint.css" />
|
||||
<script src="vendor/codemirror/addon/hint/show-hint.js"></script>
|
||||
|
|
182
edit/edit.js
182
edit/edit.js
|
@ -1,5 +1,5 @@
|
|||
/* eslint brace-style: 0, operator-linebreak: 0 */
|
||||
/* global CodeMirror exports parserlib CSSLint */
|
||||
/* global CodeMirror exports css_beautify parserlib CSSLint initLintHooks setLinter updateLintReport renderLintReport */
|
||||
'use strict';
|
||||
|
||||
let styleId = null;
|
||||
|
@ -168,7 +168,7 @@ function initCodeMirror() {
|
|||
matchBrackets: true,
|
||||
highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true},
|
||||
hintOptions: {},
|
||||
lint: {getAnnotations: CodeMirror.lint.css, delay: prefs.get('editor.lintDelay')},
|
||||
lint: setLinter('csslint'),
|
||||
lintReportDelay: prefs.get('editor.lintReportDelay'),
|
||||
styleActiveLine: true,
|
||||
theme: 'default',
|
||||
|
@ -350,11 +350,15 @@ function acmeEventListener(event) {
|
|||
}
|
||||
break;
|
||||
case 'linter':
|
||||
if (value !== null && (editors.lastActive || editors[0])) {
|
||||
if (value !== null && editors.length) {
|
||||
if (prefs.get(el.id) !== value) {
|
||||
prefs.set(el.id, value || 'csslint');
|
||||
}
|
||||
CodeMirror.signal(editors.lastActive || editors[0], "change");
|
||||
editors.forEach(cm => {
|
||||
console.log('set linter to', value);
|
||||
cm.setOption('lint', setLinter(value || 'csslint'));
|
||||
});
|
||||
// CodeMirror.signal(editors.lastActive || editors[0], "change");
|
||||
// save();
|
||||
}
|
||||
break;
|
||||
|
@ -1058,143 +1062,6 @@ function getEditorInSight(nearbyElement) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateLintReport(cm, delay) {
|
||||
if (delay === 0) {
|
||||
// immediately show pending csslint/stylelint messages in onbeforeunload and save
|
||||
update(cm);
|
||||
return;
|
||||
}
|
||||
if (delay > 0) {
|
||||
setTimeout(cm => { cm.performLint(); update(cm); }, delay, cm);
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-var
|
||||
var state = cm.state.lint;
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
// user is editing right now: postpone updating the report for the new issues (default: 500ms lint + 4500ms)
|
||||
// or update it as soon as possible (default: 500ms lint + 100ms) in case an existing issue was just fixed
|
||||
clearTimeout(state.reportTimeout);
|
||||
state.reportTimeout = setTimeout(update, state.options.delay + 100, cm);
|
||||
state.postponeNewIssues = delay === undefined || delay === null;
|
||||
|
||||
function update(cm) {
|
||||
const scope = cm ? [cm] : editors;
|
||||
let changed = false;
|
||||
let fixedOldIssues = false;
|
||||
scope.forEach(cm => {
|
||||
const linter = prefs.get('editor.linter');
|
||||
const scopedState = cm.state.lint || {};
|
||||
const oldMarkers = scopedState.markedLast || {};
|
||||
const newMarkers = {};
|
||||
const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '<tbody>' +
|
||||
scopedState.marked.map(mark => {
|
||||
const info = mark.__annotation;
|
||||
const isActiveLine = info.from.line === cm.getCursor().line;
|
||||
const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch);
|
||||
// stylelint rule added in parentheses at the end
|
||||
const rule = linter === 'stylelint' ?
|
||||
info.message.substring(info.message.lastIndexOf('('), info.message.length) :
|
||||
/ at line \d.+$/;
|
||||
// csslint
|
||||
let message = escapeHtml(info.message.replace(rule, ''));
|
||||
if (isActiveLine || oldMarkers[pos] === message) {
|
||||
delete oldMarkers[pos];
|
||||
}
|
||||
newMarkers[pos] = message;
|
||||
return `<tr class="${info.severity}">
|
||||
<td role="severity" class="CodeMirror-lint-marker-${info.severity}"
|
||||
${linter === 'stylelint' ? 'title="Rule: ' + rule + '"' : ''}>
|
||||
${info.severity}
|
||||
</td>
|
||||
<td role="line">${info.from.line + 1}</td>
|
||||
<td role="sep">:</td>
|
||||
<td role="col">${info.from.ch + 1}</td>
|
||||
<td role="message" title="${message}">${message}</td>
|
||||
</tr>`
|
||||
}).join('') + '</tbody>';
|
||||
scopedState.markedLast = newMarkers;
|
||||
fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0;
|
||||
if (scopedState.html !== html) {
|
||||
scopedState.html = html;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
clearTimeout(state ? state.renderTimeout : undefined);
|
||||
if (!state || !state.postponeNewIssues || fixedOldIssues) {
|
||||
renderLintReport(true);
|
||||
} else {
|
||||
state.renderTimeout = setTimeout(() => {
|
||||
renderLintReport(true);
|
||||
}, CodeMirror.defaults.lintReportDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
function escapeHtml(html) {
|
||||
const chars = {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/'};
|
||||
return html.replace(/[&<>"'/]/g, char => chars[char]);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLintReport(someBlockChanged) {
|
||||
const container = document.getElementById('lint');
|
||||
const content = container.children[1];
|
||||
const label = t('sectionCode');
|
||||
const newContent = content.cloneNode(false);
|
||||
let issueCount = 0;
|
||||
editors.forEach((cm, index) => {
|
||||
if (cm.state.lint && cm.state.lint.html) {
|
||||
const html = '<caption>' + label + ' ' + (index + 1) + '</caption>' + cm.state.lint.html;
|
||||
const newBlock = newContent.appendChild(tHTML(html, 'table'));
|
||||
|
||||
newBlock.cm = cm;
|
||||
issueCount += newBlock.rows.length;
|
||||
|
||||
const block = content.children[newContent.children.length - 1];
|
||||
const blockChanged = !block || cm !== block.cm || html !== block.innerHTML;
|
||||
someBlockChanged |= blockChanged;
|
||||
cm.state.lint.reportDisplayed = blockChanged;
|
||||
}
|
||||
});
|
||||
if (someBlockChanged || newContent.children.length !== content.children.length) {
|
||||
document.getElementById('issue-count').textContent = issueCount;
|
||||
container.replaceChild(newContent, content);
|
||||
container.style.display = newContent.children.length ? 'block' : 'none';
|
||||
resizeLintReport(null, newContent);
|
||||
}
|
||||
}
|
||||
|
||||
function resizeLintReport(event, content) {
|
||||
content = content || document.getElementById('lint').children[1];
|
||||
if (content.children.length) {
|
||||
const bounds = content.getBoundingClientRect();
|
||||
const newMaxHeight = bounds.bottom <= innerHeight ? '' : (innerHeight - bounds.top) + 'px';
|
||||
if (newMaxHeight !== content.style.maxHeight) {
|
||||
content.style.maxHeight = newMaxHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gotoLintIssue(event) {
|
||||
const issue = event.target.closest('tr');
|
||||
if (!issue) {
|
||||
return;
|
||||
}
|
||||
const block = issue.closest('table');
|
||||
makeSectionVisible(block.cm);
|
||||
block.cm.focus();
|
||||
block.cm.setSelection({
|
||||
line: parseInt(issue.querySelector('td[role="line"]').textContent) - 1,
|
||||
ch: parseInt(issue.querySelector('td[role="col"]').textContent) - 1
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLintReport() {
|
||||
document.getElementById('lint').classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
function beautify(event) {
|
||||
const script = document.head.appendChild(document.createElement('script'));
|
||||
script.src = 'vendor-overwrites/beautify/beautify-css-mod.js';
|
||||
|
@ -1384,14 +1251,7 @@ function initHooks() {
|
|||
document.getElementById('sections-help').addEventListener('click', showSectionHelp, false);
|
||||
document.getElementById('keyMap-help').addEventListener('click', showKeyMapHelp, false);
|
||||
document.getElementById('cancel-button').addEventListener('click', goBackToManage);
|
||||
document.getElementById('lint-help').addEventListener('click', showLintHelp);
|
||||
document.getElementById('lint').addEventListener('click', gotoLintIssue);
|
||||
window.addEventListener('resize', resizeLintReport);
|
||||
|
||||
// touch devices don't have onHover events so the element we'll be toggled via clicking (touching)
|
||||
if ('ontouchstart' in document.body) {
|
||||
document.querySelector('#lint h2').addEventListener('click', toggleLintReport);
|
||||
}
|
||||
initLintHooks();
|
||||
|
||||
if (!FIREFOX) {
|
||||
$$([
|
||||
|
@ -1878,29 +1738,6 @@ function showKeyMapHelp() {
|
|||
}
|
||||
}
|
||||
|
||||
function showLintHelp() {
|
||||
let list = '<ul class="rules">';
|
||||
let content = '';
|
||||
if (prefs.get('editor.linter') === 'csslint') {
|
||||
content = t('issuesHelp', '<a href="https://github.com/CSSLint/csslint" target="_blank">CSSLint</a>') + list +
|
||||
CSSLint.getRules().map(rule =>
|
||||
'<li><b>' + rule.name + '</b><br>' + rule.desc + '</li>'
|
||||
).join('');
|
||||
} else {
|
||||
let rules = [];
|
||||
const url = 'https://stylelint.io/user-guide/rules/';
|
||||
content = t('issuesHelp', `<a href="${url}" target="_blank">stylelint</a>`) + list;
|
||||
$$('#lint td[role="severity"]').forEach(el => {
|
||||
const rule = el.title.replace('Rule: (', '').replace(/[()]/g, '').trim();
|
||||
if (!rules.includes(rule)) {
|
||||
content += `<li><a target="_blank" href="${url}${rule}/">${rule}</a></li>`;
|
||||
rules.push(rule);
|
||||
}
|
||||
});
|
||||
}
|
||||
return showHelp(t('issues'), content + '</ul>');
|
||||
}
|
||||
|
||||
function showRegExpTester(event, section = getSectionForChild(this)) {
|
||||
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
|
||||
const OWN_ICON = chrome.runtime.getManifest().icons['16'];
|
||||
|
@ -2123,6 +1960,7 @@ function onRuntimeMessage(request) {
|
|||
break;
|
||||
case 'styleDeleted':
|
||||
if (styleId && styleId === request.id) {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
window.onbeforeunload = () => {};
|
||||
window.close();
|
||||
break;
|
||||
|
|
180
edit/lint.js
Normal file
180
edit/lint.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
/* global CodeMirror CSSLint editors makeSectionVisible showHelp */
|
||||
'use strict';
|
||||
|
||||
function initLintHooks() {
|
||||
document.getElementById('lint-help').addEventListener('click', showLintHelp);
|
||||
document.getElementById('lint').addEventListener('click', gotoLintIssue);
|
||||
window.addEventListener('resize', resizeLintReport);
|
||||
|
||||
// touch devices don't have onHover events so the element we'll be toggled via clicking (touching)
|
||||
if ('ontouchstart' in document.body) {
|
||||
document.querySelector('#lint h2').addEventListener('click', toggleLintReport);
|
||||
}
|
||||
}
|
||||
|
||||
function setLinter(name) {
|
||||
return {
|
||||
getAnnotations: CodeMirror.lint[name],
|
||||
delay: prefs.get('editor.lintDelay')
|
||||
};
|
||||
}
|
||||
|
||||
function updateLintReport(cm, delay) {
|
||||
if (delay === 0) {
|
||||
// immediately show pending csslint/stylelint messages in onbeforeunload and save
|
||||
update(cm);
|
||||
return;
|
||||
}
|
||||
if (delay > 0) {
|
||||
setTimeout(cm => { cm.performLint(); update(cm); }, delay, cm);
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-var
|
||||
var state = cm.state.lint;
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
// user is editing right now: postpone updating the report for the new issues (default: 500ms lint + 4500ms)
|
||||
// or update it as soon as possible (default: 500ms lint + 100ms) in case an existing issue was just fixed
|
||||
clearTimeout(state.reportTimeout);
|
||||
state.reportTimeout = setTimeout(update, state.options.delay + 100, cm);
|
||||
state.postponeNewIssues = delay === undefined || delay === null;
|
||||
|
||||
function update(cm) {
|
||||
const scope = cm ? [cm] : editors;
|
||||
let changed = false;
|
||||
let fixedOldIssues = false;
|
||||
scope.forEach(cm => {
|
||||
const linter = prefs.get('editor.linter');
|
||||
const scopedState = cm.state.lint || {};
|
||||
const oldMarkers = scopedState.markedLast || {};
|
||||
const newMarkers = {};
|
||||
const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '<tbody>' +
|
||||
scopedState.marked.map(mark => {
|
||||
const info = mark.__annotation;
|
||||
const isActiveLine = info.from.line === cm.getCursor().line;
|
||||
const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch);
|
||||
// stylelint rule added in parentheses at the end
|
||||
const rule = linter === 'stylelint' ?
|
||||
info.message.substring(info.message.lastIndexOf('('), info.message.length) :
|
||||
/ at line \d.+$/;
|
||||
// csslint
|
||||
const message = escapeHtml(info.message.replace(rule, ''));
|
||||
if (isActiveLine || oldMarkers[pos] === message) {
|
||||
delete oldMarkers[pos];
|
||||
}
|
||||
newMarkers[pos] = message;
|
||||
return `<tr class="${info.severity}">
|
||||
<td role="severity" class="CodeMirror-lint-marker-${info.severity}"
|
||||
${linter === 'stylelint' ? 'title="Rule: ' + rule + '"' : ''}>
|
||||
${info.severity}
|
||||
</td>
|
||||
<td role="line">${info.from.line + 1}</td>
|
||||
<td role="sep">:</td>
|
||||
<td role="col">${info.from.ch + 1}</td>
|
||||
<td role="message" title="${message}">${message}</td>
|
||||
</tr>`;
|
||||
}).join('') + '</tbody>';
|
||||
scopedState.markedLast = newMarkers;
|
||||
fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0;
|
||||
if (scopedState.html !== html) {
|
||||
scopedState.html = html;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
clearTimeout(state ? state.renderTimeout : undefined);
|
||||
if (!state || !state.postponeNewIssues || fixedOldIssues) {
|
||||
renderLintReport(true);
|
||||
} else {
|
||||
state.renderTimeout = setTimeout(() => {
|
||||
renderLintReport(true);
|
||||
}, CodeMirror.defaults.lintReportDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
function escapeHtml(html) {
|
||||
const chars = {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/'};
|
||||
return html.replace(/[&<>"'/]/g, char => chars[char]);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLintReport(someBlockChanged) {
|
||||
const container = document.getElementById('lint');
|
||||
const content = container.children[1];
|
||||
const label = t('sectionCode');
|
||||
const newContent = content.cloneNode(false);
|
||||
let issueCount = 0;
|
||||
editors.forEach((cm, index) => {
|
||||
if (cm.state.lint && cm.state.lint.html) {
|
||||
const html = '<caption>' + label + ' ' + (index + 1) + '</caption>' + cm.state.lint.html;
|
||||
const newBlock = newContent.appendChild(tHTML(html, 'table'));
|
||||
|
||||
newBlock.cm = cm;
|
||||
issueCount += newBlock.rows.length;
|
||||
|
||||
const block = content.children[newContent.children.length - 1];
|
||||
const blockChanged = !block || cm !== block.cm || html !== block.innerHTML;
|
||||
someBlockChanged |= blockChanged;
|
||||
cm.state.lint.reportDisplayed = blockChanged;
|
||||
}
|
||||
});
|
||||
if (someBlockChanged || newContent.children.length !== content.children.length) {
|
||||
document.getElementById('issue-count').textContent = issueCount;
|
||||
container.replaceChild(newContent, content);
|
||||
container.style.display = newContent.children.length ? 'block' : 'none';
|
||||
resizeLintReport(null, newContent);
|
||||
}
|
||||
}
|
||||
|
||||
function resizeLintReport(event, content) {
|
||||
content = content || document.getElementById('lint').children[1];
|
||||
if (content.children.length) {
|
||||
const bounds = content.getBoundingClientRect();
|
||||
const newMaxHeight = bounds.bottom <= innerHeight ? '' : (innerHeight - bounds.top) + 'px';
|
||||
if (newMaxHeight !== content.style.maxHeight) {
|
||||
content.style.maxHeight = newMaxHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gotoLintIssue(event) {
|
||||
const issue = event.target.closest('tr');
|
||||
if (!issue) {
|
||||
return;
|
||||
}
|
||||
const block = issue.closest('table');
|
||||
makeSectionVisible(block.cm);
|
||||
block.cm.focus();
|
||||
block.cm.setSelection({
|
||||
line: parseInt(issue.querySelector('td[role="line"]').textContent) - 1,
|
||||
ch: parseInt(issue.querySelector('td[role="col"]').textContent) - 1
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLintReport() {
|
||||
document.getElementById('lint').classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
function showLintHelp() {
|
||||
let list = '<ul class="rules">';
|
||||
let header = '';
|
||||
if (prefs.get('editor.linter') === 'csslint') {
|
||||
header = t('issuesHelp', '<a href="https://github.com/CSSLint/csslint" target="_blank">CSSLint</a>');
|
||||
list += CSSLint.getRules().map(rule =>
|
||||
'<li><b>' + rule.name + '</b><br>' + rule.desc + '</li>'
|
||||
).join('');
|
||||
} else {
|
||||
const rules = [];
|
||||
const url = 'https://stylelint.io/user-guide/rules/';
|
||||
header = t('issuesHelp', `<a href="${url}" target="_blank">stylelint</a>`);
|
||||
$$('#lint td[role="severity"]').forEach(el => {
|
||||
const rule = el.title.replace('Rule: (', '').replace(/[()]/g, '').trim();
|
||||
if (!rules.includes(rule)) {
|
||||
list += `<li><a target="_blank" href="${url}${rule}/">${rule}</a></li>`;
|
||||
rules.push(rule);
|
||||
}
|
||||
});
|
||||
}
|
||||
return showHelp(t('issues'), header + list + '</ul>');
|
||||
}
|
|
@ -15,10 +15,9 @@
|
|||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerHelper("lint", "css", function(text) {
|
||||
CodeMirror.registerHelper("lint", "csslint", function(text) {
|
||||
let found = [];
|
||||
const linter = prefs.get('editor.linter');
|
||||
if (linter === 'csslint' && window.CSSLint) {
|
||||
if (window.CSSLint) {
|
||||
|
||||
/* STYLISH: hack start (part 1) */
|
||||
var rules = CSSLint.getRules();
|
||||
|
@ -59,31 +58,35 @@ CodeMirror.registerHelper("lint", "css", function(text) {
|
|||
severity : message.type
|
||||
});
|
||||
}
|
||||
} else if (linter === 'stylelint') {
|
||||
const stylelint = require('stylelint').lint;
|
||||
if (stylelint) {
|
||||
return stylelint({
|
||||
code: text,
|
||||
// stylelintConfig stored in stylelint-config.js & loaded by edit.html
|
||||
config: stylelintConfig
|
||||
}).then(output => {
|
||||
const warnings = output.results.length ? output.results[0].warnings : [],
|
||||
len = warnings.length;
|
||||
let i, warning;
|
||||
if (len) {
|
||||
for (i = 0; i < len; i++) {
|
||||
warning = warnings[i];
|
||||
found.push({
|
||||
from: CodeMirror.Pos(warning.line - 1, warning.column - 1),
|
||||
to: CodeMirror.Pos(warning.line - 1, warning.column),
|
||||
message: warning.text,
|
||||
severity : warning.severity
|
||||
});
|
||||
}
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("lint", "stylelint", function(text) {
|
||||
let found = [];
|
||||
const stylelint = require('stylelint').lint;
|
||||
if (stylelint) {
|
||||
return stylelint({
|
||||
code: text,
|
||||
// stylelintConfig stored in stylelint-config.js & loaded by edit.html
|
||||
config: stylelintConfig
|
||||
}).then(output => {
|
||||
const warnings = output.results.length ? output.results[0].warnings : [],
|
||||
len = warnings.length;
|
||||
let i, warning;
|
||||
if (len) {
|
||||
for (i = 0; i < len; i++) {
|
||||
warning = warnings[i];
|
||||
found.push({
|
||||
from: CodeMirror.Pos(warning.line - 1, warning.column - 1),
|
||||
to: CodeMirror.Pos(warning.line - 1, warning.column),
|
||||
message: warning.text,
|
||||
severity : warning.severity
|
||||
});
|
||||
}
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user