diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 09106dd0..9b9d8e79 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -346,7 +346,7 @@
"description": "Label for the CSS linter issues block on the style edit page"
},
"issuesHelp": {
- "message": "The issues found by $link$ with these rules enabled:",
+ "message": "The issues found by $link$ rules:",
"description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
@@ -510,21 +510,30 @@
"message": "Remove section",
"description": "Label for the button to remove a section"
},
- "setStylelintLink": {
- "message": "Get a full list of rules",
- "description": "Stylelint rules label before link"
+ "setLinterLink": {
+ "message": "See a full list of rules",
+ "description": "Stylelint or CSSLint rules label added before a link"
},
- "setStylelintRules": {
- "message": "Set stylelint rules",
- "description": "Stylelint popup header"
+ "setLinterRulesTitle": {
+ "message": "Set rules for $linter$",
+ "description": "Stylelint or CSSLint popup header",
+ "placeholders": {
+ "linter": {
+ "content": "$1"
+ }
+ }
},
- "resetStylelintRules": {
+ "resetLinterRules": {
"message": "Reset",
- "description": "Reset stylelint rules"
+ "description": "Reset Stylelint or CSSLint rules"
},
- "setStylelintError": {
+ "setLinterError": {
"message": "Invalid JSON format",
- "description": "Stylelint invalid JSON message"
+ "description": "Setting linter rules with invalid JSON message"
+ },
+ "showCSSLintSettings": {
+ "message": "(Set rules: 0 = disabled; 1 = warning; 2 = error)",
+ "description": "CSSLint rule settings values"
},
"shortcuts": {
"message": "Shortcuts",
diff --git a/edit.html b/edit.html
index 9d3c26ff..26d9753a 100644
--- a/edit.html
+++ b/edit.html
@@ -190,7 +190,7 @@
-
diff --git a/edit/csslint-ruleset.js b/edit/csslint-ruleset.js
new file mode 100644
index 00000000..c285fb4e
--- /dev/null
+++ b/edit/csslint-ruleset.js
@@ -0,0 +1,49 @@
+'use strict';
+
+/**
+ * CSSLint Ruleset values
+ * 0 = disabled; 1 = warning; 2 = error
+ */
+window.csslintDefaultRuleset = {
+ // Default warnings
+ 'display-property-grouping': 1,
+ 'duplicate-properties': 1,
+ 'empty-rules': 1,
+ 'errors': 1,
+ 'known-properties': 1,
+
+ // Default disabled
+ 'adjoining-classes': 0,
+ 'box-model': 0,
+ 'box-sizing': 0,
+ 'bulletproof-font-face': 0,
+ 'compatible-vendor-prefixes': 0,
+ 'duplicate-background-images': 0,
+ 'fallback-colors': 0,
+ 'floats': 0,
+ 'font-faces': 0,
+ 'font-sizes': 0,
+ 'gradients': 0,
+ 'ids': 0,
+ 'import': 0,
+ 'import-ie-limit': 0,
+ 'important': 0,
+ 'order-alphabetical': 0,
+ 'outline-none': 0,
+ 'overqualified-elements': 0,
+ 'qualified-headings': 0,
+ 'regex-selectors': 0,
+ 'rules-count': 0,
+ 'selector-max': 0,
+ 'selector-max-approaching': 0,
+ 'selector-newline': 0,
+ 'shorthand': 0,
+ 'star-property-hack': 0,
+ 'text-indent': 0,
+ 'underscore-property-hack': 0,
+ 'unique-headings': 0,
+ 'universal-selector': 0,
+ 'unqualified-attributes': 0,
+ 'vendor-prefix': 0,
+ 'zero-units': 0
+};
diff --git a/edit/lint.js b/edit/lint.js
index 9d7b4a34..d27dcb78 100644
--- a/edit/lint.js
+++ b/edit/lint.js
@@ -1,18 +1,20 @@
/* global CodeMirror CSSLint editors makeSectionVisible showHelp showCodeMirrorPopup */
-/* global stylelintDefaultConfig onDOMscripted injectCSS require */
+/* global stylelintDefaultConfig csslintDefaultRuleset onDOMscripted injectCSS require */
'use strict';
function initLint() {
$('#lint-help').addEventListener('click', showLintHelp);
$('#lint').addEventListener('click', gotoLintIssue);
window.addEventListener('resize', resizeLintReport);
- $('#stylelint-settings').addEventListener('click', openStylelintSettings);
+ $('#linter-settings').addEventListener('click', openStylelintSettings);
// touch devices don't have onHover events so the element we'll be toggled via clicking (touching)
if ('ontouchstart' in document.body) {
$('#lint h2').addEventListener('click', toggleLintReport);
}
+ // initialize storage of rules
BG.chromeLocal.getValue('editorStylelintRules').then(rules => setStylelintRules(rules));
+ BG.chromeLocal.getValue('editorCSSLintRules').then(ruleset => setCSSLintRules(ruleset));
}
function setStylelintRules(rules = []) {
@@ -23,6 +25,14 @@ function setStylelintRules(rules = []) {
return rules;
}
+function setCSSLintRules(ruleset = []) {
+ if (Object.keys(ruleset).length === 0 && typeof csslintDefaultRuleset !== 'undefined') {
+ ruleset = Object.assign({}, csslintDefaultRuleset);
+ }
+ BG.chromeLocal.setValue('editorCSSLintRules', ruleset);
+ return ruleset;
+}
+
function getLinterConfigForCodeMirror(name) {
return CodeMirror.lint && CodeMirror.lint[name] ? {
getAnnotations: CodeMirror.lint[name],
@@ -30,9 +40,9 @@ function getLinterConfigForCodeMirror(name) {
} : false;
}
-function updateLinter(name) {
+function updateLinter(linter) {
function updateEditors() {
- const options = getLinterConfigForCodeMirror(name);
+ const options = getLinterConfigForCodeMirror(linter);
CodeMirror.defaults.lint = options === 'null' ? false : options;
editors.forEach(cm => {
// set lint to "null" to disable
@@ -42,15 +52,12 @@ function updateLinter(name) {
updateLintReport(cm, 200);
});
}
- if (prefs.get('editor.linter') !== name) {
- prefs.set('editor.linter', name);
- }
// load scripts
- loadSelectedLinter(name).then(() => {
+ loadSelectedLinter(linter).then(() => {
updateEditors();
});
- $('#stylelint-settings').style.display = name === 'stylelint' ?
- 'inline-block' : 'none';
+ $('#linter-settings').style.display = linter === 'null' ?
+ 'none' : 'inline-block';
}
function updateLintReport(cm, delay) {
@@ -82,7 +89,6 @@ function updateLintReport(cm, delay) {
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 = {};
@@ -92,12 +98,9 @@ function updateLintReport(cm, delay) {
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; extract it out for the stylelint info popup
- const stylelintRule = linter === 'stylelint' ? ` data-rule ="${
- info.message
- .substring(info.message.lastIndexOf('('), info.message.length)
- .replace(/[()]/g, '')}"`
- : '';
- // csslint
+ const lintRuleName = info.message
+ .substring(info.message.lastIndexOf('('), info.message.length)
+ .replace(/[()]/g, '');
const title = escapeHtml(info.message);
const message = title.length > 100 ? title.substr(0, 100) + '...' : title;
if (isActiveLine || oldMarkers[pos] === message) {
@@ -105,7 +108,7 @@ function updateLintReport(cm, delay) {
}
newMarkers[pos] = message;
return `
-
+ |
${info.severity}
|
${info.from.line + 1} |
@@ -200,39 +203,60 @@ function toggleLintReport() {
}
function showLintHelp() {
+ const CSSLintRules = CSSLint.getRules();
+ const findCSSLintRule = id => CSSLintRules.find(rule => rule.id === id);
+ const makeLink = (url, txt) => `${txt}`;
+ const linter = prefs.get('editor.linter');
+ const url = linter === 'stylelint'
+ ? 'https://stylelint.io/user-guide/rules/'
+ // some CSSLint rules do not have a url
+ : 'https://github.com/CSSLint/csslint/issues/535';
+ const rules = [];
+ let template;
let list = '';
let header = '';
- if (prefs.get('editor.linter') === 'csslint') {
- header = t('issuesHelp', 'CSSLint');
- list += CSSLint.getRules().map(rule =>
- `- ${rule.name}
${rule.desc} `
- ).join('');
+ if (linter === 'csslint') {
+ header = t('issuesHelp', makeLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint'));
+ template = ruleID => {
+ const rule = findCSSLintRule(ruleID);
+ return rule ? `- ${makeLink(rule.url || url, rule.name)}
${rule.desc} ` : '';
+ };
} else {
- const rules = [];
- const url = 'https://stylelint.io/user-guide/rules/';
- header = t('issuesHelp', `stylelint`);
- // to-do: change this to a generator
- $$('#lint td[role="severity"]').forEach(el => {
- const rule = el.dataset.rule;
- if (!rules.includes(rule)) {
- list += `- ${rule}
`;
- rules.push(rule);
- }
- });
+ header = t('issuesHelp', makeLink(url, 'stylelint'));
+ template = rule => `- ${makeLink(url + rule, rule)}
`;
}
+ // to-do: change this to a generator
+ $$('#lint td[role="severity"]').forEach(el => {
+ const rule = el.dataset.rule;
+ if (!rules.includes(rule)) {
+ list += template(rule);
+ rules.push(rule);
+ }
+ });
return showHelp(t('issues'), header + list + '
');
}
-function setupStylelintSettingsEvents(popup) {
+function checkLinter(linter = prefs.get('editor.linter')) {
+ linter = linter.toLowerCase();
+ if (prefs.get('editor.linter') !== linter) {
+ prefs.set('editor.linter', linter);
+ }
+ return linter;
+}
+
+function setupLinterSettingsEvents(popup) {
$('.save', popup).addEventListener('click', event => {
event.preventDefault();
+ const linter = checkLinter(event.target.dataset.linter);
const json = tryJSONparse(popup.codebox.getValue());
if (json && json.rules) {
- setStylelintRules(json.rules);
// it is possible to have stylelint rules popup open & switch to csslint
- if (prefs.get('editor.linter') === 'stylelint') {
- updateLinter('stylelint');
+ if (linter === 'stylelint') {
+ setStylelintRules(json.rules);
+ } else {
+ setCSSLintRules(json.rules);
}
+ updateLinter(linter);
} else {
$('#help-popup .error').classList.add('show');
clearTimeout($('#help-popup .contents').timer);
@@ -247,27 +271,42 @@ function setupStylelintSettingsEvents(popup) {
});
$('.reset', popup).addEventListener('click', event => {
event.preventDefault();
- setStylelintRules();
- popup.codebox.setValue(JSON.stringify({rules: stylelintDefaultConfig.rules}, null, 2));
- if (prefs.get('editor.linter') === 'stylelint') {
- updateLinter('stylelint');
+ const linter = checkLinter(event.target.dataset.linter);
+ let rules;
+ if (linter === 'stylelint') {
+ setStylelintRules();
+ rules = {rules: stylelintDefaultConfig.rules};
+ } else {
+ setCSSLintRules();
+ rules = {rules: csslintDefaultRuleset};
}
+ popup.codebox.setValue(JSON.stringify(rules, null, 2));
+ updateLinter(linter);
});
}
function openStylelintSettings() {
- BG.chromeLocal.getValue('editorStylelintRules').then(rules => {
+ const linter = prefs.get('editor.linter');
+ BG.chromeLocal.getValue(
+ linter === 'stylelint'
+ ? 'editorStylelintRules'
+ : 'editorCSSLintRules'
+ ).then(rules => {
if (rules.length === 0) {
- rules = setStylelintRules(rules);
+ rules = linter === 'stylelint'
+ ? setStylelintRules(rules)
+ : setCSSLintRules(rules);
}
const rulesString = JSON.stringify({rules: rules}, null, 2);
- setupStylelintPopup(rulesString);
+ setupLinterPopup(rulesString);
});
}
-function setupStylelintPopup(rules) {
+function setupLinterPopup(rules) {
+ const linter = prefs.get('editor.linter');
+ const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
function makeButton(className, text) {
- return $element({tag: 'button', className, type: 'button', textContent: t(text)});
+ return $element({tag: 'button', className, type: 'button', textContent: t(text), dataset: {linter}});
}
function makeLink(url, textContent) {
return $element({tag: 'a', target: '_blank', href: url, textContent});
@@ -276,21 +315,27 @@ function setupStylelintPopup(rules) {
cm.setOption('mode', 'application/json');
cm.setOption('lint', 'json');
}
- const popup = showCodeMirrorPopup(t('setStylelintRules'), $element({
+ const popup = showCodeMirrorPopup(t('setLinterRulesTitle', linterTitle), $element({
appendChild: [
$element({
tag: 'p',
appendChild: [
- t('setStylelintLink') + ' ',
- makeLink('https://stylelint.io/demo/', 'Stylelint')
+ t('setLinterLink') + ' ',
+ makeLink(
+ linter === 'stylelint'
+ ? 'https://stylelint.io/demo/'
+ : 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
+ linterTitle
+ ),
+ linter === 'csslint' ? ' ' + t('showCSSLintSettings') : ''
]
}),
makeButton('save', 'styleSaveLabel'),
- makeButton('reset', 'resetStylelintRules'),
+ makeButton('reset', 'resetLinterRules'),
$element({
tag: 'span',
className: 'error',
- textContent: t('setStylelintError')
+ textContent: t('setLinterError')
})
]
}));
@@ -304,7 +349,7 @@ function setupStylelintPopup(rules) {
popup.codebox.focus();
popup.codebox.setValue(rules);
onDOMscripted(loadJSON).then(() => setJSONMode(popup.codebox));
- setupStylelintSettingsEvents(popup);
+ setupLinterSettingsEvents(popup);
}
function loadSelectedLinter(name) {
@@ -319,7 +364,10 @@ function loadSelectedLinter(name) {
);
}
if (name === 'csslint' && !window.CSSLint) {
- scripts.push('vendor-overwrites/csslint/csslint-worker.js');
+ scripts.push(
+ 'edit/csslint-ruleset.js',
+ 'vendor-overwrites/csslint/csslint-worker.js'
+ );
} else if (name === 'stylelint' && !window.stylelint) {
scripts.push(
'vendor-overwrites/stylelint/stylelint-bundle.min.js',
diff --git a/vendor-overwrites/codemirror/addon/lint/css-lint.js b/vendor-overwrites/codemirror/addon/lint/css-lint.js
index 9adec55e..cc43f35e 100644
--- a/vendor-overwrites/codemirror/addon/lint/css-lint.js
+++ b/vendor-overwrites/codemirror/addon/lint/css-lint.js
@@ -4,7 +4,7 @@
// Depends on csslint.js from https://github.com/stubbornella/csslint
/* global CodeMirror require define */
-/* global CSSLint stylelint stylelintDefaultConfig */
+/* global CSSLint stylelint stylelintDefaultConfig csslintDefaultRuleset */
'use strict';
(mod => {
@@ -21,28 +21,21 @@
})(CodeMirror => {
CodeMirror.registerHelper('lint', 'csslint', text => {
const found = [];
- if (window.CSSLint) {
- /* STYLUS: hack start (part 1) */
- const rules = CSSLint.getRules();
- const allowedRules = [
- 'display-property-grouping',
- 'duplicate-properties',
- 'empty-rules',
- 'errors',
- 'known-properties'
- ];
- CSSLint.clearRules();
- rules.forEach(rule => {
- if (allowedRules.indexOf(rule.id) >= 0) {
- CSSLint.addRule(rule);
- }
- });
- /* STYLUS: hack end */
-
- const results = CSSLint.verify(text);
+ if (!window.CSSLint) {
+ return found;
+ }
+ /* STYLUS: hack start (part 1) */
+ return BG.chromeLocal.getValue('editorCSSLintRules').then((ruleset = csslintDefaultRuleset) => {
+ // csslintDefaultRuleset stored in csslint-ruleset.js & loaded by edit/lint.js
+ if (Object.keys(ruleset).length === 0) {
+ ruleset = Object.assign({}, csslintDefaultRuleset);
+ }
+ const results = CSSLint.verify(text, ruleset);
const messages = results.messages;
const hslRegex = /hsla?\(\s*(-?\d+)%?\s*,\s*(-?\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%(\s*,\s*(-?\d+|-?\d*.\d+))?\s*\)/;
let message = null;
+ /* STYLUS: hack end */
+
for (let i = 0; i < messages.length; i++) {
message = messages[i];
@@ -59,20 +52,21 @@
continue;
}
}
- /* STYLUS: hack end */
const startLine = message.line - 1;
const endLine = message.line - 1;
const startCol = message.col - 1;
const endCol = message.col;
+ /* STYLUS: hack end */
+
found.push({
from: CodeMirror.Pos(startLine, startCol),
to: CodeMirror.Pos(endLine, endCol),
- message: message.message,
+ message: message.message + ` (${message.rule.id})`,
severity : message.type
});
}
- }
- return found;
+ return found;
+ });
});
CodeMirror.registerHelper('lint', 'stylelint', text => {
@@ -80,7 +74,7 @@
window.stylelint = require('stylelint').lint;
if (window.stylelint) {
return BG.chromeLocal.getValue('editorStylelintRules').then((rules = stylelintDefaultConfig.rules) => {
- // stylelintDefaultConfig stored in stylelint-config.js & loaded by edit.html
+ // stylelintDefaultConfig stored in stylelint-config.js & loaded by edit/lint.js
if (Object.keys(rules).length === 0) {
rules = stylelintDefaultConfig.rules;
}