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 = ''); } -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; }