use CSSLint in a web werkker

This commit is contained in:
tophf 2017-11-27 17:02:23 +03:00
parent f3cf6e1856
commit 99512da9da
4 changed files with 75 additions and 87 deletions

View File

@ -40,6 +40,8 @@ body {
box-shadow: 0 0 3rem -1.2rem black;
box-sizing: border-box;
z-index: 10;
display: flex;
flex-direction: column;
}
#header h1 {
margin-top: 0;
@ -468,9 +470,9 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
}
/************ lint ************/
#lint > div {
#lint {
overflow-y: auto;
}
overflow-x: hidden;}
#lint table {
font-size: 100%;
border-spacing: 0;
@ -505,6 +507,7 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
}
#lint td[role="message"] {
text-align: left;
white-space: nowrap;
}
#message-box.center.lint-config #message-box-contents {
text-align: left;

View File

@ -1,38 +1,20 @@
/* global CodeMirror CSSLint parserlib stylelint linterConfig */
'use strict';
CodeMirror.registerHelper('lint', 'csslint', code => {
if (!CSSLint.suppressUsoVarError) {
CSSLint.suppressUsoVarError = true;
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
const isUsoVar = ({value}) => value.startsWith('/*[[') && value.endsWith(']]*/');
CSSLint.addRule({
id: 'uso-vars',
init(parser, reporter) {
parser.addListener('error', function ({message, line, col}) {
if (!isUsoVar(this._tokenStream._token)) {
const {_lt, _ltIndex: i} = this._tokenStream;
if (i < 2 || !_lt.slice(0, i - 1).reverse().some(isUsoVar)) {
reporter.error(message, line, col);
}
}
});
},
});
}
const rules = deepCopy(linterConfig.getCurrent('csslint'));
Object.defineProperty(rules, 'errors', {get: () => 0, set: () => 0});
rules['uso-vars'] = 1;
return CSSLint.verify(code, rules).messages
.map(({line, col, message, rule, type}) => line && {
message,
from: {line: line - 1, ch: col - 1},
to: {line: line - 1, ch: col},
rule: rule.id,
severity: type
})
.filter(Boolean);
});
CodeMirror.registerHelper('lint', 'csslint', code => new Promise(resolve => {
CSSLint.onmessage = ({data}) => {
resolve(
data.map(({line, col, message, rule, type}) => line && {
message,
from: {line: line - 1, ch: col - 1},
to: {line: line - 1, ch: col},
rule: rule.id,
severity: type
}).filter(Boolean));
};
const config = deepCopy(linterConfig.getCurrent('csslint'));
CSSLint.postMessage({action: 'verify', code, config});
}));
CodeMirror.registerHelper('lint', 'stylelint', code =>
stylelint.lint({

View File

@ -38,6 +38,9 @@ var linterConfig = {
return CodeMirror.lint && CodeMirror.lint[linter] ? {
getAnnotations: CodeMirror.lint[linter],
delay: prefs.get('editor.lintDelay'),
onUpdateLinting(annotationsNotSorted, annotations, cm) {
updateLintReport(cm, 0);
},
} : false;
},
@ -64,12 +67,18 @@ var linterConfig = {
findInvalidRules(config, linter = linterConfig.getDefault()) {
const rules = linter === 'stylelint' ? config.rules : config;
const allRules = new Set(
linter === 'stylelint'
? Object.keys(stylelint.rules)
: CSSLint.getRules().map(rule => rule.id)
);
return Object.keys(rules).filter(rule => !allRules.has(rule));
return new Promise(resolve => {
if (linter === 'stylelint') {
resolve(Object.keys(stylelint.rules));
} else {
CSSLint.onmessage = ({data}) =>
resolve(data.map(rule => rule.id));
CSSLint.postMessage({action: 'getRules'});
}
}).then(allRules => {
allRules = new Set(allRules);
return Object.keys(rules).filter(rule => !allRules.has(rule));
});
},
stringify(config = this.getCurrent()) {
@ -140,7 +149,6 @@ function initLint() {
$('#lint-help').addEventListener('click', showLintHelp);
$('#lint').addEventListener('click', gotoLintIssue);
$('#linter-settings').addEventListener('click', linterConfig.openOnClick);
window.addEventListener('resize', resizeLintReport);
updateLinter();
linterConfig.watchStorage();
@ -332,22 +340,6 @@ function renderLintReport(someBlockChanged) {
$('#issue-count').textContent = issueCount;
container.replaceChild(newContent, content);
container.classList.toggle('hidden', !newContent.children.length);
resizeLintReport();
}
}
function resizeLintReport() {
// subtracted value to prevent scrollbar
const magicBuffer = 20;
const content = $('#lint table');
if (content) {
const bounds = content.getBoundingClientRect();
const newMaxHeight = bounds.bottom <= window.innerHeight ? '' :
// subtract out a bit of padding or the vertical scrollbar extends beyond the viewport
(window.innerHeight - bounds.top - magicBuffer) + 'px';
if (newMaxHeight !== content.style.maxHeight) {
content.parentNode.style.maxHeight = newMaxHeight;
}
}
}
@ -421,7 +413,11 @@ function setupLinterSettingsEvents(popup) {
const linter = linterConfig.setLinter(event.target.dataset.linter);
const json = tryJSONparse(popup.codebox.getValue());
if (json) {
const invalid = linterConfig.findInvalidRules(json, linter);
showLinterErrorMessage(linter, t('linterJSONError'));
popup.codebox.focus();
return;
}
linterConfig.findInvalidRules(json, linter).then(invalid => {
if (invalid.length) {
showLinterErrorMessage(linter, [
t('linterInvalidConfigError'),
@ -436,10 +432,8 @@ function setupLinterSettingsEvents(popup) {
linterConfig.save(json);
linterConfig.showSavedMessage();
popup.codebox.markClean();
} else {
showLinterErrorMessage(linter, t('linterJSONError'));
}
popup.codebox.focus();
popup.codebox.focus();
});
});
$('.reset', popup).addEventListener('click', event => {
event.preventDefault();
@ -524,8 +518,8 @@ function loadLinterAssets(name = linterConfig.getDefault()) {
function loadLibrary() {
if (name === 'csslint' && !window.CSSLint) {
window.CSSLint = new Worker('/vendor-overwrites/csslint/csslint-worker.js');
return loadScript([
'/vendor-overwrites/csslint/csslint-worker.js',
'/edit/lint-defaults-csslint.js'
]);
}

View File

@ -10931,31 +10931,40 @@ CSSLint.addFormatter({
}
});
/*
* Web worker for CSSLint
*/
if (!CSSLint.suppressUsoVarError) {
CSSLint.suppressUsoVarError = true;
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
const isUsoVar = ({value}) => value.startsWith('/*[[') && value.endsWith(']]*/');
CSSLint.addRule({
id: 'uso-vars',
init(parser, reporter) {
parser.addListener('error', function ({message, line, col}) {
if (!isUsoVar(this._tokenStream._token)) {
const {_lt, _ltIndex: i} = this._tokenStream;
if (i < 2 || !_lt.slice(0, i - 1).reverse().some(isUsoVar)) {
reporter.error(message, line, col);
}
}
});
},
});
}
/* global self, JSON */
self.onmessage = ({data: {action, code, config}}) => {
switch (action) {
// message indicates to start linting
self.onmessage = function(event) {
"use strict";
var data = event.data,
message,
text,
ruleset,
results;
case 'getRules':
self.postMessage(CSSLint.getRules());
return;
try {
message = JSON.parse(data);
text = message.text;
ruleset = message.ruleset;
} catch (ex) {
text = data;
}
results = CSSLint.verify(text, ruleset);
// Not all browsers support structured clone, so JSON stringify results
self.postMessage(JSON.stringify(results));
case 'verify':
Object.defineProperty(config, 'errors', {get: () => 0, set: () => 0});
config['uso-vars'] = 1;
self.postMessage(CSSLint.verify(code, config).messages.map(m => {
// the functions are non-tranferable and we need only an id
m.rule = {id: m.rule.id};
return m;
}));
return;
}
};