worker for stylelint; hints in linter config popup
This commit is contained in:
parent
c2d68612ec
commit
493c1a65c0
|
@ -215,6 +215,10 @@
|
||||||
"message": "Use default",
|
"message": "Use default",
|
||||||
"description": "'Set to default' button in a confirm dialog"
|
"description": "'Set to default' button in a confirm dialog"
|
||||||
},
|
},
|
||||||
|
"confirmDiscardChanges": {
|
||||||
|
"message": "Discard the changes?",
|
||||||
|
"description": "Generic label or title displayed when trying to close something (not a style) with unsaved changes"
|
||||||
|
},
|
||||||
"confirmSave": {
|
"confirmSave": {
|
||||||
"message": "Save",
|
"message": "Save",
|
||||||
"description": "'Save' button in a confirm dialog"
|
"description": "'Save' button in a confirm dialog"
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
.CodeMirror-hints {
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
.CodeMirror-hint:hover {
|
.CodeMirror-hint:hover {
|
||||||
color: white;
|
color: white;
|
||||||
background: #08f;
|
background: #08f;
|
||||||
|
|
29
edit/edit.js
29
edit/edit.js
|
@ -1718,6 +1718,7 @@ function fromMozillaFormat() {
|
||||||
popup.codebox.focus();
|
popup.codebox.focus();
|
||||||
popup.codebox.on('changes', cm => {
|
popup.codebox.on('changes', cm => {
|
||||||
popup.classList.toggle('ready', !cm.isBlank());
|
popup.classList.toggle('ready', !cm.isBlank());
|
||||||
|
cm.markClean();
|
||||||
});
|
});
|
||||||
// overwrite default extraKeys as those are inapplicable in popup context
|
// overwrite default extraKeys as those are inapplicable in popup context
|
||||||
popup.codebox.options.extraKeys = {
|
popup.codebox.options.extraKeys = {
|
||||||
|
@ -1885,15 +1886,18 @@ function showKeyMapHelp() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showHelp(title, body) {
|
function showHelp(title = '', body) {
|
||||||
const div = $('#help-popup');
|
const div = $('#help-popup');
|
||||||
div.classList.remove('big');
|
div.classList.remove('big');
|
||||||
$('.contents', div).textContent = '';
|
const contents = $('.contents', div);
|
||||||
$('.contents', div).appendChild(typeof body === 'string' ? tHTML(body) : body);
|
contents.textContent = '';
|
||||||
|
if (body) {
|
||||||
|
contents.appendChild(typeof body === 'string' ? tHTML(body) : body);
|
||||||
|
}
|
||||||
$('.title', div).textContent = title;
|
$('.title', div).textContent = title;
|
||||||
|
|
||||||
if (getComputedStyle(div).display === 'none') {
|
if (getComputedStyle(div).display === 'none') {
|
||||||
document.addEventListener('keydown', closeHelp);
|
window.addEventListener('keydown', closeHelp, true);
|
||||||
// avoid chaining on multiple showHelp() calls
|
// avoid chaining on multiple showHelp() calls
|
||||||
$('.dismiss', div).onclick = closeHelp;
|
$('.dismiss', div).onclick = closeHelp;
|
||||||
}
|
}
|
||||||
|
@ -1902,16 +1906,19 @@ function showHelp(title, body) {
|
||||||
return div;
|
return div;
|
||||||
|
|
||||||
function closeHelp(e) {
|
function closeHelp(e) {
|
||||||
if (
|
if (!e || e.type === 'click' ||
|
||||||
!e ||
|
(e.which === 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey &&
|
||||||
e.type === 'click' ||
|
!$('.CodeMirror-hints, #message-box') && !(document.activeElement instanceof HTMLInputElement))) {
|
||||||
((e.keyCode || e.which) === 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey)
|
if (e && div.codebox && !div.codebox.options.readOnly && !div.codebox.isClean()) {
|
||||||
) {
|
messageBox.confirm(t('confirmDiscardChanges')).then(ok => ok && closeHelp());
|
||||||
|
return;
|
||||||
|
}
|
||||||
div.style.display = '';
|
div.style.display = '';
|
||||||
const contents = $('.contents');
|
|
||||||
contents.textContent = '';
|
contents.textContent = '';
|
||||||
clearTimeout(contents.timer);
|
clearTimeout(contents.timer);
|
||||||
document.removeEventListener('keydown', closeHelp);
|
window.removeEventListener('keydown', closeHelp, true);
|
||||||
|
window.dispatchEvent(new Event('closeHelp'));
|
||||||
|
(editors.lastActive || editors[0]).focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,26 @@
|
||||||
/* global CodeMirror CSSLint parserlib stylelint linterConfig */
|
/* global CodeMirror linterConfig */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
CodeMirror.registerHelper('lint', 'csslint', code => new Promise(resolve => {
|
CodeMirror.registerHelper('lint', 'csslint', code =>
|
||||||
CSSLint.onmessage = ({data}) => {
|
linterConfig.invokeWorker({code, config: linterConfig.getCurrent()}).then(results =>
|
||||||
resolve(
|
results.map(({line, col: ch, message, rule, type: severity}) => line && {
|
||||||
data.map(({line, col, message, rule, type}) => line && {
|
|
||||||
message,
|
message,
|
||||||
from: {line: line - 1, ch: col - 1},
|
from: {line: line - 1, ch: ch - 1},
|
||||||
to: {line: line - 1, ch: col},
|
to: {line: line - 1, ch},
|
||||||
rule: rule.id,
|
rule: rule.id,
|
||||||
severity: type
|
severity,
|
||||||
}).filter(Boolean));
|
}).filter(Boolean)));
|
||||||
};
|
|
||||||
const config = deepCopy(linterConfig.getCurrent('csslint'));
|
|
||||||
CSSLint.postMessage({action: 'verify', code, config});
|
|
||||||
}));
|
|
||||||
|
|
||||||
CodeMirror.registerHelper('lint', 'stylelint', code =>
|
CodeMirror.registerHelper('lint', 'stylelint', code =>
|
||||||
stylelint.lint({
|
linterConfig.invokeWorker({code, config: linterConfig.getCurrent()}).then(({results}) =>
|
||||||
code,
|
!results[0] && [] ||
|
||||||
config: deepCopy(linterConfig.getCurrent('stylelint')),
|
results[0].warnings.map(({line, column:ch, text, severity}) => ({
|
||||||
}).then(({results}) => {
|
from: {line: line - 1, ch: ch - 1},
|
||||||
if (!results[0]) {
|
to: {line: line - 1, ch},
|
||||||
return [];
|
message: text
|
||||||
}
|
|
||||||
return results[0].warnings.map(warning => ({
|
|
||||||
from: CodeMirror.Pos(warning.line - 1, warning.column - 1),
|
|
||||||
to: CodeMirror.Pos(warning.line - 1, warning.column),
|
|
||||||
message: warning.text
|
|
||||||
.replace('Unexpected ', '')
|
.replace('Unexpected ', '')
|
||||||
.replace(/^./, firstLetter => firstLetter.toUpperCase())
|
.replace(/^./, firstLetter => firstLetter.toUpperCase())
|
||||||
.replace(/\s*\([^(]+\)$/, ''), // strip the rule,
|
.replace(/\s*\([^(]+\)$/, ''), // strip the rule,
|
||||||
rule: warning.text.replace(/^.*?\s*\(([^(]+)\)$/, '$1'),
|
rule: text.replace(/^.*?\s*\(([^(]+)\)$/, '$1'),
|
||||||
severity : warning.severity
|
severity,
|
||||||
}));
|
}))));
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
336
edit/lint.js
336
edit/lint.js
|
@ -1,5 +1,5 @@
|
||||||
/* global CodeMirror messageBox */
|
/* global CodeMirror messageBox */
|
||||||
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */
|
/* global editors makeSectionVisible showCodeMirrorPopup showHelp hotkeyRerouter */
|
||||||
/* global loadScript require CSSLint stylelint */
|
/* global loadScript require CSSLint stylelint */
|
||||||
/* global makeLink */
|
/* global makeLink */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@ -20,8 +20,16 @@ var linterConfig = {
|
||||||
csslint: 'editorCSSLintConfig',
|
csslint: 'editorCSSLintConfig',
|
||||||
stylelint: 'editorStylelintConfig',
|
stylelint: 'editorStylelintConfig',
|
||||||
},
|
},
|
||||||
|
worker: {
|
||||||
|
csslint: {path: '/vendor-overwrites/csslint/csslint-worker.js'},
|
||||||
|
stylelint: {path: '/vendor-overwrites/stylelint/stylelint-bundle.min.js'},
|
||||||
|
},
|
||||||
|
allRuleIds: {
|
||||||
|
csslint: null,
|
||||||
|
stylelint: null,
|
||||||
|
},
|
||||||
|
|
||||||
getDefault() {
|
getName() {
|
||||||
// some dirty hacks to override editor.linter getting from prefs
|
// some dirty hacks to override editor.linter getting from prefs
|
||||||
const linter = prefs.get('editor.linter');
|
const linter = prefs.get('editor.linter');
|
||||||
if (linter && editors[0] && editors[0].getOption('mode') !== 'css') {
|
if (linter && editors[0] && editors[0].getOption('mode') !== 'css') {
|
||||||
|
@ -30,11 +38,11 @@ var linterConfig = {
|
||||||
return linter;
|
return linter;
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrent(linter = linterConfig.getDefault()) {
|
getCurrent(linter = linterConfig.getName()) {
|
||||||
return this.fallbackToDefaults(this[linter] || {});
|
return this.fallbackToDefaults(this[linter] || {});
|
||||||
},
|
},
|
||||||
|
|
||||||
getForCodeMirror(linter = linterConfig.getDefault()) {
|
getForCodeMirror(linter = linterConfig.getName()) {
|
||||||
return CodeMirror.lint && CodeMirror.lint[linter] ? {
|
return CodeMirror.lint && CodeMirror.lint[linter] ? {
|
||||||
getAnnotations: CodeMirror.lint[linter],
|
getAnnotations: CodeMirror.lint[linter],
|
||||||
delay: prefs.get('editor.lintDelay'),
|
delay: prefs.get('editor.lintDelay'),
|
||||||
|
@ -44,7 +52,7 @@ var linterConfig = {
|
||||||
} : false;
|
} : false;
|
||||||
},
|
},
|
||||||
|
|
||||||
fallbackToDefaults(config, linter = linterConfig.getDefault()) {
|
fallbackToDefaults(config, linter = linterConfig.getName()) {
|
||||||
if (config && Object.keys(config).length) {
|
if (config && Object.keys(config).length) {
|
||||||
if (linter === 'stylelint') {
|
if (linter === 'stylelint') {
|
||||||
// always use default syntax because we don't expose it in config UI
|
// always use default syntax because we don't expose it in config UI
|
||||||
|
@ -56,33 +64,52 @@ var linterConfig = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setLinter(linter = linterConfig.getDefault()) {
|
setLinter(linter = linterConfig.getName()) {
|
||||||
linter = linter.toLowerCase();
|
linter = linter.toLowerCase();
|
||||||
linter = linter === 'csslint' || linter === 'stylelint' ? linter : '';
|
linter = linter === 'csslint' || linter === 'stylelint' ? linter : '';
|
||||||
if (linterConfig.getDefault() !== linter) {
|
if (linterConfig.getName() !== linter) {
|
||||||
prefs.set('editor.linter', linter);
|
prefs.set('editor.linter', linter);
|
||||||
}
|
}
|
||||||
return linter;
|
return linter;
|
||||||
},
|
},
|
||||||
|
|
||||||
findInvalidRules(config, linter = linterConfig.getDefault()) {
|
invokeWorker(message) {
|
||||||
const rules = linter === 'stylelint' ? config.rules : config;
|
const worker = linterConfig.worker[message.linter || linterConfig.getName()];
|
||||||
return new Promise(resolve => {
|
if (!worker.queue) {
|
||||||
if (linter === 'stylelint') {
|
worker.queue = [];
|
||||||
resolve(Object.keys(stylelint.rules));
|
worker.instance.onmessage = ({data}) => {
|
||||||
} else {
|
worker.queue.shift().resolve(data);
|
||||||
CSSLint.onmessage = ({data}) =>
|
if (worker.queue.length) {
|
||||||
resolve(data.map(rule => rule.id));
|
worker.instance.postMessage(worker.queue[0].message);
|
||||||
CSSLint.postMessage({action: 'getRules'});
|
|
||||||
}
|
}
|
||||||
}).then(allRules => {
|
};
|
||||||
allRules = new Set(allRules);
|
}
|
||||||
return Object.keys(rules).filter(rule => !allRules.has(rule));
|
return new Promise(resolve => {
|
||||||
|
worker.queue.push({message, resolve});
|
||||||
|
if (worker.queue.length === 1) {
|
||||||
|
worker.instance.postMessage(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAllRuleIds(linter = linterConfig.getName()) {
|
||||||
|
return Promise.resolve(
|
||||||
|
this.allRuleIds[linter] ||
|
||||||
|
this.invokeWorker({linter, action: 'getAllRuleIds'})
|
||||||
|
.then(ids => (this.allRuleIds[linter] = ids.sort()))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
findInvalidRules(config, linter = linterConfig.getName()) {
|
||||||
|
return this.getAllRuleIds(linter).then(allRuleIds => {
|
||||||
|
const allRuleIdsSet = new Set(allRuleIds);
|
||||||
|
const rules = linter === 'stylelint' ? config.rules : config;
|
||||||
|
return Object.keys(rules).filter(rule => !allRuleIdsSet.has(rule));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
stringify(config = this.getCurrent()) {
|
stringify(config = this.getCurrent()) {
|
||||||
if (linterConfig.getDefault() === 'stylelint') {
|
if (linterConfig.getName() === 'stylelint') {
|
||||||
config.syntax = undefined;
|
config.syntax = undefined;
|
||||||
}
|
}
|
||||||
return JSON.stringify(config, null, 2)
|
return JSON.stringify(config, null, 2)
|
||||||
|
@ -91,7 +118,7 @@ var linterConfig = {
|
||||||
|
|
||||||
save(config) {
|
save(config) {
|
||||||
config = this.fallbackToDefaults(config);
|
config = this.fallbackToDefaults(config);
|
||||||
const linter = linterConfig.getDefault();
|
const linter = linterConfig.getName();
|
||||||
this[linter] = config;
|
this[linter] = config;
|
||||||
BG.chromeSync.setLZValue(this.storageName[linter], config);
|
BG.chromeSync.setLZValue(this.storageName[linter], config);
|
||||||
return config;
|
return config;
|
||||||
|
@ -155,7 +182,7 @@ function initLint() {
|
||||||
prefs.subscribe(['editor.linter'], updateLinter);
|
prefs.subscribe(['editor.linter'], updateLinter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLinter({immediately, linter = linterConfig.getDefault()} = {}) {
|
function updateLinter({immediately, linter = linterConfig.getName()} = {}) {
|
||||||
if (!immediately) {
|
if (!immediately) {
|
||||||
debounce(updateLinter, 0, {immediately: true, linter});
|
debounce(updateLinter, 0, {immediately: true, linter});
|
||||||
return;
|
return;
|
||||||
|
@ -358,17 +385,16 @@ function gotoLintIssue(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLintHelp() {
|
function showLintHelp() {
|
||||||
const linter = linterConfig.getDefault();
|
const linter = linterConfig.getName();
|
||||||
const baseUrl = linter === 'stylelint'
|
const baseUrl = linter === 'stylelint'
|
||||||
? 'https://stylelint.io/user-guide/rules/'
|
? 'https://stylelint.io/user-guide/rules/'
|
||||||
// some CSSLint rules do not have a url
|
// some CSSLint rules do not have a url
|
||||||
: 'https://github.com/CSSLint/csslint/issues/535';
|
: 'https://github.com/CSSLint/csslint/issues/535';
|
||||||
let headerLink, template;
|
let headerLink, template;
|
||||||
if (linter === 'csslint') {
|
if (linter === 'csslint') {
|
||||||
const CSSLintRules = CSSLint.getRules();
|
|
||||||
headerLink = makeLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint');
|
headerLink = makeLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint');
|
||||||
template = ruleID => {
|
template = ruleID => {
|
||||||
const rule = CSSLintRules.find(rule => rule.id === ruleID);
|
const rule = linterConfig.allRuleIds.csslint.find(rule => rule.id === ruleID);
|
||||||
return rule &&
|
return rule &&
|
||||||
$element({tag: 'li', appendChild: [
|
$element({tag: 'li', appendChild: [
|
||||||
$element({tag: 'b', appendChild: makeLink(rule.url || baseUrl, rule.name)}),
|
$element({tag: 'b', appendChild: makeLink(rule.url || baseUrl, rule.name)}),
|
||||||
|
@ -398,91 +424,79 @@ function showLintHelp() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLinterErrorMessage(title, contents) {
|
function showLinterErrorMessage(title, contents, popup) {
|
||||||
messageBox({
|
messageBox({
|
||||||
title,
|
title,
|
||||||
contents,
|
contents,
|
||||||
className: 'danger center lint-config',
|
className: 'danger center lint-config',
|
||||||
buttons: [t('confirmOK')],
|
buttons: [t('confirmOK')],
|
||||||
});
|
}).then(() => popup && popup.codebox.focus());
|
||||||
}
|
|
||||||
|
|
||||||
function setupLinterSettingsEvents(popup) {
|
|
||||||
$('.save', popup).addEventListener('click', event => {
|
|
||||||
event.preventDefault();
|
|
||||||
const linter = linterConfig.setLinter(event.target.dataset.linter);
|
|
||||||
const json = tryJSONparse(popup.codebox.getValue());
|
|
||||||
if (json) {
|
|
||||||
showLinterErrorMessage(linter, t('linterJSONError'));
|
|
||||||
popup.codebox.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
linterConfig.findInvalidRules(json, linter).then(invalid => {
|
|
||||||
if (invalid.length) {
|
|
||||||
showLinterErrorMessage(linter, [
|
|
||||||
t('linterInvalidConfigError'),
|
|
||||||
$element({
|
|
||||||
tag: 'ul',
|
|
||||||
appendChild: invalid.map(name =>
|
|
||||||
$element({tag: 'li', textContent: name})),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
linterConfig.save(json);
|
|
||||||
linterConfig.showSavedMessage();
|
|
||||||
popup.codebox.markClean();
|
|
||||||
popup.codebox.focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$('.reset', popup).addEventListener('click', event => {
|
|
||||||
event.preventDefault();
|
|
||||||
const linter = linterConfig.setLinter(event.target.dataset.linter);
|
|
||||||
popup.codebox.setValue(linterConfig.stringify(linterConfig.defaults[linter] || {}));
|
|
||||||
popup.codebox.focus();
|
|
||||||
});
|
|
||||||
$('.cancel', popup).addEventListener('click', event => {
|
|
||||||
event.preventDefault();
|
|
||||||
$('.dismiss').dispatchEvent(new Event('click'));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupLinterPopup(config) {
|
function setupLinterPopup(config) {
|
||||||
const linter = linterConfig.getDefault();
|
const linter = linterConfig.getName();
|
||||||
const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
|
const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
|
||||||
|
const defaultConfig = linterConfig.stringify(linterConfig.defaults[linter] || {});
|
||||||
|
const title = t('linterConfigPopupTitle', linterTitle);
|
||||||
|
const popup = showCodeMirrorPopup(title, null, {
|
||||||
|
lint: false,
|
||||||
|
extraKeys: {'Ctrl-Enter': save},
|
||||||
|
hintOptions: {hint},
|
||||||
|
});
|
||||||
|
$('.contents', popup).appendChild(makeFooter());
|
||||||
|
|
||||||
function makeButton(className, text, options = {}) {
|
const cm = popup.codebox;
|
||||||
return $element(Object.assign(options, {
|
cm.focus();
|
||||||
tag: 'button',
|
cm.setValue(config);
|
||||||
|
cm.clearHistory();
|
||||||
|
cm.markClean();
|
||||||
|
cm.on('changes', updateButtonState);
|
||||||
|
updateButtonState();
|
||||||
|
|
||||||
|
hotkeyRerouter.setState(false);
|
||||||
|
window.addEventListener('closeHelp', function _() {
|
||||||
|
window.removeEventListener('closeHelp', _);
|
||||||
|
hotkeyRerouter.setState(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadScript([
|
||||||
|
'/vendor/codemirror/mode/javascript/javascript.js',
|
||||||
|
'/vendor/codemirror/addon/lint/json-lint.js',
|
||||||
|
'/vendor/jsonlint/jsonlint.js'
|
||||||
|
]).then(() => {
|
||||||
|
cm.setOption('mode', 'application/json');
|
||||||
|
cm.setOption('lint', 'json');
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeFooter() {
|
||||||
|
const makeButton = (className, onclick, text, options = {}) =>
|
||||||
|
$element(Object.assign(options, {
|
||||||
className,
|
className,
|
||||||
|
onclick,
|
||||||
|
tag: 'button',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
textContent: t(text),
|
textContent: t(text),
|
||||||
dataset: {linter}
|
|
||||||
}));
|
}));
|
||||||
}
|
return $element({
|
||||||
function makeLink(url, textContent) {
|
|
||||||
return $element({tag: 'a', target: '_blank', href: url, textContent});
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = t('linterConfigPopupTitle', linterTitle);
|
|
||||||
const contents = $element({
|
|
||||||
appendChild: [
|
appendChild: [
|
||||||
$element({
|
$element({
|
||||||
tag: 'p',
|
tag: 'p',
|
||||||
appendChild: [
|
appendChild: [
|
||||||
t('linterRulesLink') + ' ',
|
t('linterRulesLink') + ' ',
|
||||||
makeLink(
|
$element({
|
||||||
linter === 'stylelint'
|
tag: 'a',
|
||||||
|
target: '_blank',
|
||||||
|
href: linter === 'stylelint'
|
||||||
? 'https://stylelint.io/user-guide/rules/'
|
? 'https://stylelint.io/user-guide/rules/'
|
||||||
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
|
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
|
||||||
linterTitle
|
textContent: linterTitle
|
||||||
),
|
}),
|
||||||
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : ''
|
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : ''
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
makeButton('save', 'styleSaveLabel', {disabled: true}),
|
makeButton('save', save, 'styleSaveLabel', {title: 'Ctrl-Enter'}),
|
||||||
makeButton('cancel', 'confirmCancel'),
|
makeButton('cancel', cancel, 'confirmClose'),
|
||||||
makeButton('reset', 'genericResetLabel', {title: t('linterResetMessage')}),
|
makeButton('reset', reset, 'genericResetLabel', {title: t('linterResetMessage')}),
|
||||||
$element({
|
$element({
|
||||||
tag: 'span',
|
tag: 'span',
|
||||||
className: 'saved-message',
|
className: 'saved-message',
|
||||||
|
@ -490,58 +504,122 @@ function setupLinterPopup(config) {
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const popup = showCodeMirrorPopup(title, contents, {lint: false});
|
|
||||||
contents.parentNode.appendChild(contents);
|
|
||||||
popup.codebox.focus();
|
|
||||||
popup.codebox.setValue(config);
|
|
||||||
popup.codebox.clearHistory();
|
|
||||||
popup.codebox.markClean();
|
|
||||||
popup.codebox.on('change', cm => {
|
|
||||||
$('.save', popup).disabled = cm.isClean();
|
|
||||||
});
|
|
||||||
setupLinterSettingsEvents(popup);
|
|
||||||
loadScript([
|
|
||||||
'/vendor/codemirror/mode/javascript/javascript.js',
|
|
||||||
'/vendor/codemirror/addon/lint/json-lint.js',
|
|
||||||
'/vendor/jsonlint/jsonlint.js'
|
|
||||||
]).then(() => {
|
|
||||||
popup.codebox.setOption('mode', 'application/json');
|
|
||||||
popup.codebox.setOption('lint', 'json');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadLinterAssets(name = linterConfig.getDefault()) {
|
function save(event) {
|
||||||
if (!name) {
|
if (event instanceof Event) {
|
||||||
return Promise.resolve();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
return loadLibrary().then(loadAddon);
|
const json = tryJSONparse(cm.getValue());
|
||||||
|
if (!json) {
|
||||||
function loadLibrary() {
|
showLinterErrorMessage(linter, t('linterJSONError'), popup);
|
||||||
if (name === 'csslint' && !window.CSSLint) {
|
cm.focus();
|
||||||
window.CSSLint = new Worker('/vendor-overwrites/csslint/csslint-worker.js');
|
|
||||||
return loadScript([
|
|
||||||
'/edit/lint-defaults-csslint.js'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if (name === 'stylelint' && !window.stylelint) {
|
linterConfig.findInvalidRules(json, linter).then(invalid => {
|
||||||
return loadScript([
|
if (invalid.length) {
|
||||||
'/vendor-overwrites/stylelint/stylelint-bundle.min.js',
|
showLinterErrorMessage(linter, [
|
||||||
'/edit/lint-defaults-stylelint.js'
|
t('linterInvalidConfigError'),
|
||||||
]).then(() => (window.stylelint = require('stylelint')));
|
$element({tag: 'ul', appendChild: invalid.map(name =>
|
||||||
}
|
$element({tag: 'li', textContent: name})),
|
||||||
return Promise.resolve();
|
}),
|
||||||
}
|
], popup);
|
||||||
|
|
||||||
function loadAddon() {
|
|
||||||
if (CodeMirror.lint) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return loadScript([
|
linterConfig.setLinter(linter);
|
||||||
|
linterConfig.save(json);
|
||||||
|
linterConfig.showSavedMessage();
|
||||||
|
cm.markClean();
|
||||||
|
cm.focus();
|
||||||
|
updateButtonState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (linterConfig.getName() !== linter) {
|
||||||
|
linterConfig.setLinter(linter);
|
||||||
|
}
|
||||||
|
cm.setValue(defaultConfig);
|
||||||
|
cm.focus();
|
||||||
|
updateButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$('.dismiss').dispatchEvent(new Event('click'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtonState() {
|
||||||
|
$('.save', popup).disabled = cm.isClean();
|
||||||
|
$('.reset', popup).disabled = cm.getValue() === defaultConfig;
|
||||||
|
$('.cancel', popup).textContent = t(cm.isClean() ? 'confirmClose' : 'confirmCancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hint(cm) {
|
||||||
|
return Promise.all([
|
||||||
|
linterConfig.getAllRuleIds(linter),
|
||||||
|
linter !== 'stylelint' || hint.allOptions ||
|
||||||
|
linterConfig.invokeWorker({action: 'getAllRuleOptions', linter})
|
||||||
|
.then(options => (hint.allOptions = options)),
|
||||||
|
])
|
||||||
|
.then(([ruleIds, options]) => {
|
||||||
|
const cursor = cm.getCursor();
|
||||||
|
const {start, end, string, type, state: {lexical}} = cm.getTokenAt(cursor);
|
||||||
|
const {line, ch} = cursor;
|
||||||
|
|
||||||
|
const quoted = string.startsWith('"');
|
||||||
|
const leftPart = string.slice(quoted ? 1 : 0, ch - start).trim();
|
||||||
|
const depth = getLexicalDepth(lexical);
|
||||||
|
|
||||||
|
const search = cm.getSearchCursor(/"([-\w]+)"/, {line, ch: start - 1});
|
||||||
|
let [, prevWord] = search.find(true) || [];
|
||||||
|
let words = [];
|
||||||
|
|
||||||
|
if (depth === 1 && linter === 'stylelint') {
|
||||||
|
words = quoted ? ['rules'] : [];
|
||||||
|
} else if ((depth === 1 || depth === 2) && type && type.includes('property')) {
|
||||||
|
words = ruleIds;
|
||||||
|
} else if (depth === 2 || depth === 3 && lexical.type === ']') {
|
||||||
|
words = !quoted ? ['true', 'false', 'null'] :
|
||||||
|
ruleIds.includes(prevWord) && (options[prevWord] || [])[0] || [];
|
||||||
|
} else if (depth === 4 && prevWord === 'severity') {
|
||||||
|
words = ['error', 'warning'];
|
||||||
|
} else if (depth === 4) {
|
||||||
|
words = ['ignore', 'ignoreAtRules', 'except', 'severity'];
|
||||||
|
} else if (depth === 5 && lexical.type === ']' && quoted) {
|
||||||
|
while (prevWord && !ruleIds.includes(prevWord)) {
|
||||||
|
prevWord = (search.find(true) || [])[1];
|
||||||
|
}
|
||||||
|
words = (options[prevWord] || []).slice(-1)[0] || ruleIds;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: words.filter(word => word.startsWith(leftPart)),
|
||||||
|
from: {line, ch: start + (quoted ? 1 : 0)},
|
||||||
|
to: {line, ch: string.endsWith('"') ? end - 1 : end},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLexicalDepth(lexicalState) {
|
||||||
|
let depth = 0;
|
||||||
|
while ((lexicalState = lexicalState.prev)) {
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLinterAssets(name = linterConfig.getName()) {
|
||||||
|
const worker = linterConfig.worker[name];
|
||||||
|
return !name || !worker || worker.instance ? Promise.resolve() :
|
||||||
|
loadScript((worker.instance ? [] : [
|
||||||
|
(worker.instance = new Worker(worker.path)),
|
||||||
|
`/edit/lint-defaults-${name}.js`,
|
||||||
|
]).concat(CodeMirror.lint ? [] : [
|
||||||
'/vendor/codemirror/addon/lint/lint.css',
|
'/vendor/codemirror/addon/lint/lint.css',
|
||||||
'/msgbox/msgbox.css',
|
'/msgbox/msgbox.css',
|
||||||
'/vendor/codemirror/addon/lint/lint.js',
|
'/vendor/codemirror/addon/lint/lint.js',
|
||||||
'/edit/lint-codemirror-helper.js',
|
'/edit/lint-codemirror-helper.js',
|
||||||
'/msgbox/msgbox.js'
|
'/msgbox/msgbox.js'
|
||||||
]);
|
]));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ function createSourceEditor(style) {
|
||||||
update();
|
update();
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
linterEl.value = linterConfig.getDefault();
|
linterEl.value = linterConfig.getName();
|
||||||
|
|
||||||
const cssLintOption = linterEl.querySelector('[value="csslint"]');
|
const cssLintOption = linterEl.querySelector('[value="csslint"]');
|
||||||
if (cm.getOption('mode') !== 'css') {
|
if (cm.getOption('mode') !== 'css') {
|
||||||
|
|
|
@ -15,6 +15,7 @@ function messageBox({
|
||||||
if (onshow) {
|
if (onshow) {
|
||||||
onshow(messageBox.element);
|
onshow(messageBox.element);
|
||||||
}
|
}
|
||||||
|
messageBox.element.focus();
|
||||||
return new Promise(_resolve => {
|
return new Promise(_resolve => {
|
||||||
messageBox.resolve = _resolve;
|
messageBox.resolve = _resolve;
|
||||||
});
|
});
|
||||||
|
|
|
@ -10950,14 +10950,15 @@ if (!CSSLint.suppressUsoVarError) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.onmessage = ({data: {action, code, config}}) => {
|
self.onmessage = ({data: {action = 'run', code, config}}) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|
||||||
case 'getRules':
|
case 'getAllRuleIds':
|
||||||
self.postMessage(CSSLint.getRules());
|
// the functions are non-tranferable and we need only an id
|
||||||
|
self.postMessage(CSSLint.getRules().map(rule => rule.id));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'verify':
|
case 'run':
|
||||||
Object.defineProperty(config, 'errors', {get: () => 0, set: () => 0});
|
Object.defineProperty(config, 'errors', {get: () => 0, set: () => 0});
|
||||||
config['uso-vars'] = 1;
|
config['uso-vars'] = 1;
|
||||||
self.postMessage(CSSLint.verify(code, config).messages.map(m => {
|
self.postMessage(CSSLint.verify(code, config).messages.map(m => {
|
||||||
|
|
|
@ -1673,3 +1673,59 @@ N,R,y-N,"inline"])):(K.lastIndex=G+1,K.test(E),y=0===K.lastIndex?E.length-1:K.la
|
||||||
Object.keys(f).forEach(function(a){d[a]=f[a]});return d}if(a&&f)return k(a)(f);if("function"!==typeof a)throw new TypeError("need wrapper function");Object.keys(a).forEach(function(c){d[c]=a[c]});return d}l.exports=k},{}],611:[function(a,l,g){var k=a("fs"),h=a("path"),f=a("mkdirp");l.exports=function(a,c,b){var d=h.dirname(a);k.exists(d,function(g){g?k.writeFile(a,c,b):f(d,function(d){if(d)return b(d);k.writeFile(a,c,b)})})};l.exports.sync=function(a,c){var b=h.dirname(a);k.existsSync(b)||f.sync(b);
|
Object.keys(f).forEach(function(a){d[a]=f[a]});return d}if(a&&f)return k(a)(f);if("function"!==typeof a)throw new TypeError("need wrapper function");Object.keys(a).forEach(function(c){d[c]=a[c]});return d}l.exports=k},{}],611:[function(a,l,g){var k=a("fs"),h=a("path"),f=a("mkdirp");l.exports=function(a,c,b){var d=h.dirname(a);k.exists(d,function(g){g?k.writeFile(a,c,b):f(d,function(d){if(d)return b(d);k.writeFile(a,c,b)})})};l.exports.sync=function(a,c){var b=h.dirname(a);k.existsSync(b)||f.sync(b);
|
||||||
k.writeFileSync(a,c)};l.exports.stream=function(a){var c=h.dirname(a);k.existsSync(c)||f.sync(c);return k.createWriteStream(a)}},{fs:1,mkdirp:173,path:14}],stylelint:[function(a,l,g){g=a("./utils/checkAgainstRule");var k=a("./createPlugin"),h=a("./createStylelint"),f=a("./formatters"),d=a("./postcssPlugin"),c=a("./utils/report"),b=a("./utils/ruleMessages"),p=a("./rules"),m=a("./standalone");a=a("./utils/validateOptions");d.utils={report:c,ruleMessages:b,validateOptions:a,checkAgainstRule:g};d.lint=
|
k.writeFileSync(a,c)};l.exports.stream=function(a){var c=h.dirname(a);k.existsSync(c)||f.sync(c);return k.createWriteStream(a)}},{fs:1,mkdirp:173,path:14}],stylelint:[function(a,l,g){g=a("./utils/checkAgainstRule");var k=a("./createPlugin"),h=a("./createStylelint"),f=a("./formatters"),d=a("./postcssPlugin"),c=a("./utils/report"),b=a("./utils/ruleMessages"),p=a("./rules"),m=a("./standalone");a=a("./utils/validateOptions");d.utils={report:c,ruleMessages:b,validateOptions:a,checkAgainstRule:g};d.lint=
|
||||||
m;d.rules=p;d.formatters=f;d.createPlugin=k;d.createLinter=h;l.exports=d},{"./createPlugin":334,"./createStylelint":335,"./formatters":338,"./postcssPlugin":346,"./rules":429,"./standalone":520,"./utils/checkAgainstRule":529,"./utils/report":592,"./utils/ruleMessages":593,"./utils/validateOptions":595}]},{},[]);
|
m;d.rules=p;d.formatters=f;d.createPlugin=k;d.createLinter=h;l.exports=d},{"./createPlugin":334,"./createStylelint":335,"./formatters":338,"./postcssPlugin":346,"./rules":429,"./standalone":520,"./utils/checkAgainstRule":529,"./utils/report":592,"./utils/ruleMessages":593,"./utils/validateOptions":595}]},{},[]);
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const stylelint = require('stylelint');
|
||||||
|
|
||||||
|
self.onmessage = ({data: {action = 'run', code, config}}) => {
|
||||||
|
switch (action) {
|
||||||
|
case 'getAllRuleIds':
|
||||||
|
// the functions are non-tranferable
|
||||||
|
self.postMessage(Object.keys(stylelint.rules));
|
||||||
|
return;
|
||||||
|
case 'getAllRuleOptions':
|
||||||
|
self.postMessage(getAllRuleOptions());
|
||||||
|
return;
|
||||||
|
case 'run':
|
||||||
|
stylelint.lint({code, config}).then(results =>
|
||||||
|
self.postMessage(results));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAllRuleOptions() {
|
||||||
|
const options = {};
|
||||||
|
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
|
||||||
|
const rxString = /"([-\w\s]{3,}?)"/g;
|
||||||
|
for (const id of Object.keys(stylelint.rules)) {
|
||||||
|
const ruleCode = String(stylelint.rules[id]);
|
||||||
|
const sets = [];
|
||||||
|
let m, mStr;
|
||||||
|
while ((m = rxPossible.exec(ruleCode))) {
|
||||||
|
const possible = m[1];
|
||||||
|
const set = [];
|
||||||
|
while ((mStr = rxString.exec(possible))) {
|
||||||
|
const s = mStr[1];
|
||||||
|
if (s.includes(' ')) {
|
||||||
|
set.push(...s.split(/\s+/));
|
||||||
|
} else {
|
||||||
|
set.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (possible.includes('ignoreAtRules')) {
|
||||||
|
set.push('ignoreAtRules');
|
||||||
|
}
|
||||||
|
if (possible.includes('ignoreShorthands')) {
|
||||||
|
set.push('ignoreShorthands');
|
||||||
|
}
|
||||||
|
if (set.length) {
|
||||||
|
sets.push(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sets.length) {
|
||||||
|
options[id] = sets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user