From 9946f3c781c4d1557689d1a4cfb719feace05bdd Mon Sep 17 00:00:00 2001 From: tophf Date: Mon, 28 Aug 2017 08:22:19 +0300 Subject: [PATCH] regroup some of lint* data and code * all lint-related js files are prefixed by lint- * config-related stuff is grouped in linterConfig * CM helper is rewritten and moved in /edit now that CSSLint supports these features * chromeSync methods that apply LZString got LZ in their names * empty string is used for 'disabled' in linter selector --- background/storage.js | 11 +- edit.html | 2 +- edit/edit.js | 7 +- edit/lint-codemirror-helper.js | 31 ++ ...int-config.js => lint-defaults-csslint.js} | 2 +- ...t-config.js => lint-defaults-stylelint.js} | 2 +- edit/lint.js | 279 ++++++++++-------- js/prefs.js | 2 +- .../codemirror/addon/lint/css-lint.js | 114 ------- 9 files changed, 199 insertions(+), 251 deletions(-) create mode 100644 edit/lint-codemirror-helper.js rename edit/{csslint-config.js => lint-defaults-csslint.js} (96%) rename edit/{stylelint-config.js => lint-defaults-stylelint.js} (98%) delete mode 100644 vendor-overwrites/codemirror/addon/lint/css-lint.js diff --git a/background/storage.js b/background/storage.js index 66a0bfd6..7d055f1a 100644 --- a/background/storage.js +++ b/background/storage.js @@ -46,7 +46,7 @@ var chromeLocal = { var chromeSync = { get(options) { return new Promise(resolve => { - chrome.storage.sync.get(options, data => resolve(data)); + chrome.storage.sync.get(options, resolve); }); }, set(data) { @@ -54,10 +54,15 @@ var chromeSync = { chrome.storage.sync.set(data, () => resolve(data)); }); }, - getValue(key) { + getLZValue(key) { return chromeSync.get(key).then(data => tryJSONparse(LZString.decompressFromUTF16(data[key]))); }, - setValue(key, value) { + getLZValues(keys) { + return chromeSync.get(keys).then(data => + Object.assign({}, ...keys.map(key => + ({[key]: tryJSONparse(LZString.decompressFromUTF16(data[key]))})))); + }, + setLZValue(key, value) { return chromeSync.set({[key]: LZString.compressToUTF16(JSON.stringify(value))}); } }; diff --git a/edit.html b/edit.html index 234725eb..3368a28b 100644 --- a/edit.html +++ b/edit.html @@ -187,7 +187,7 @@ diff --git a/edit/edit.js b/edit/edit.js index 6558f8d2..71820ffb 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1,7 +1,7 @@ /* eslint brace-style: 0, operator-linebreak: 0 */ /* global CodeMirror parserlib */ /* global exports css_beautify onDOMscripted */ -/* global CSSLint initLint getLinterConfigForCodeMirror updateLintReport renderLintReport updateLinter */ +/* global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter */ 'use strict'; let styleId = null; @@ -161,8 +161,7 @@ function initCodeMirror() { const CM = CodeMirror; const isWindowsOS = navigator.appVersion.indexOf('Windows') > 0; // lint.js is not loaded initially - const hasLinter = typeof getLinterConfigForCodeMirror !== 'undefined' ? - getLinterConfigForCodeMirror(prefs.get('editor.linter')) : false; + const hasLinter = window.linterConfig ? linterConfig.getForCodeMirror() : false; // CodeMirror miserably fails on keyMap='' so let's ensure it's not if (!prefs.get('editor.keyMap')) { @@ -1955,7 +1954,7 @@ function showCodeMirrorPopup(title, html, options) { foldGutter: true, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], matchBrackets: true, - lint: getLinterConfigForCodeMirror(prefs.get('editor.linter')), + lint: linterConfig.getForCodeMirror(), styleActiveLine: true, theme: prefs.get('editor.theme'), keyMap: prefs.get('editor.keyMap') diff --git a/edit/lint-codemirror-helper.js b/edit/lint-codemirror-helper.js new file mode 100644 index 00000000..d7ed510b --- /dev/null +++ b/edit/lint-codemirror-helper.js @@ -0,0 +1,31 @@ +/* global CodeMirror CSSLint stylelint linterConfig */ +'use strict'; + +CodeMirror.registerHelper('lint', 'csslint', code => + CSSLint.verify(code, linterConfig.getCurrent('csslint')) + .messages.map(message => ({ + from: CodeMirror.Pos(message.line - 1, message.col - 1), + to: CodeMirror.Pos(message.line - 1, message.col), + message: message.message + ` (${message.rule.id})`, + severity : message.type + })) +); + +CodeMirror.registerHelper('lint', 'stylelint', code => + stylelint.lint({ + code, + config: linterConfig.getCurrent('stylelint'), + }).then(({results}) => { + if (!results[0]) { + return []; + } + 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(/^./, firstLetter => firstLetter.toUpperCase()), + severity : warning.severity + })); + }) +); diff --git a/edit/csslint-config.js b/edit/lint-defaults-csslint.js similarity index 96% rename from edit/csslint-config.js rename to edit/lint-defaults-csslint.js index c48e6e8e..104f767e 100644 --- a/edit/csslint-config.js +++ b/edit/lint-defaults-csslint.js @@ -4,7 +4,7 @@ * CSSLint Config values * 0 = disabled; 1 = warning; 2 = error */ -window.csslintDefaultConfig = { +window.linterConfig.defaults.csslint = { // Default warnings 'display-property-grouping': 1, 'duplicate-properties': 1, diff --git a/edit/stylelint-config.js b/edit/lint-defaults-stylelint.js similarity index 98% rename from edit/stylelint-config.js rename to edit/lint-defaults-stylelint.js index 5dc1106c..0b12052c 100644 --- a/edit/stylelint-config.js +++ b/edit/lint-defaults-stylelint.js @@ -1,6 +1,6 @@ 'use strict'; -window.stylelintDefaultConfig = (defaultSeverity => ({ +window.linterConfig.defaults.stylelint = (defaultSeverity => ({ // 'sugarss' is a indent-based syntax like Sass or Stylus // ref: https://github.com/postcss/postcss#syntaxes syntax: 'sugarss', diff --git a/edit/lint.js b/edit/lint.js index d3aaf8db..c3dbf56c 100644 --- a/edit/lint.js +++ b/edit/lint.js @@ -1,51 +1,141 @@ /* global CodeMirror messageBox */ /* global editors makeSectionVisible showCodeMirrorPopup showHelp */ -/* global stylelintDefaultConfig csslintDefaultConfig onDOMscripted injectCSS require */ +/* global onDOMscripted injectCSS require CSSLint stylelint */ 'use strict'; +// eslint-disable-next-line no-var +var linterConfig = { + csslint: {}, + stylelint: {}, + defaults: { + // set in lint-defaults-csslint.js + csslint: {}, + // set in lint-defaults-stylelint.js + stylelint: {}, + }, + storageName: { + csslint: 'editorCSSLintConfig', + stylelint: 'editorStylelintConfig', + }, + + getCurrent(linter = prefs.get('editor.linter')) { + return this.fallbackToDefaults(this[linter] || {}); + }, + + getForCodeMirror(linter = prefs.get('editor.linter')) { + return CodeMirror.lint && CodeMirror.lint[linter] ? { + getAnnotations: CodeMirror.lint[linter], + delay: prefs.get('editor.lintDelay'), + } : false; + }, + + fallbackToDefaults(config, linter = prefs.get('editor.linter')) { + if (config && Object.keys(config).length) { + if (linter === 'stylelint') { + // always use default syntax because we don't expose it in config UI + config.syntax = this.defaults.stylelint.syntax; + } + return config; + } else { + return deepCopy(this.defaults[linter] || {}); + } + }, + + setLinter(linter = prefs.get('editor.linter')) { + linter = linter.toLowerCase(); + linter = linter === 'csslint' || linter === 'stylelint' ? linter : ''; + if (prefs.get('editor.linter') !== linter) { + prefs.set('editor.linter', linter); + } + return linter; + }, + + findInvalidRules(config, linter = prefs.get('editor.linter')) { + 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)); + }, + + stringify(config = this.getCurrent()) { + if (prefs.get('editor.linter') === 'stylelint') { + config.syntax = undefined; + } + return JSON.stringify(config, null, 2) + .replace(/,\n\s+\{\n\s+("severity":\s"\w+")\n\s+\}/g, ', {$1}'); + }, + + save(config) { + config = this.fallbackToDefaults(config); + const linter = prefs.get('editor.linter'); + this[linter] = config; + BG.chromeSync.setLZValue(this.storageName[linter], config); + return config; + }, + + loadAll() { + return BG.chromeSync.getLZValues([ + 'editorCSSLintConfig', + 'editorStylelintConfig', + ]).then(data => { + this.csslint = this.fallbackToDefaults(data.editorCSSLintConfig, 'csslint'); + this.stylelint = this.fallbackToDefaults(data.editorStylelintConfig, 'stylelint'); + }); + }, + + watchStorage() { + chrome.storage.onChanged.addListener((changes, area) => { + if (area === 'sync') { + for (const name of ['editorCSSLintConfig', 'editorStylelintConfig']) { + if (name in changes && changes[name].newValue !== changes[name].oldValue) { + this.loadAll().then(() => debounce(updateLinter)); + break; + } + } + } + }); + }, + + // this is an event listener so it can't refer to self via 'this' + openOnClick() { + setupLinterPopup(linterConfig.stringify()); + }, + + showSavedMessage() { + $('#help-popup .saved-message').classList.add('show'); + clearTimeout($('#help-popup .contents').timer); + $('#help-popup .contents').timer = setTimeout(() => { + // popup may be closed at this point + const msg = $('#help-popup .saved-message'); + if (msg) { + msg.classList.remove('show'); + } + }, 2000); + }, +}; + function initLint() { $('#lint-help').addEventListener('click', showLintHelp); $('#lint').addEventListener('click', gotoLintIssue); + $('#linter-settings').addEventListener('click', linterConfig.openOnClick); window.addEventListener('resize', resizeLintReport); - $('#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 linter config - BG.chromeSync.getValue('editorStylelintConfig').then(config => setStylelintConfig(config)); - BG.chromeSync.getValue('editorCSSLintConfig').then(config => setCSSLintConfig(config)); + + linterConfig.loadAll(); + linterConfig.watchStorage(); } -function setStylelintConfig(config) { - // can't use default parameters, because config may be null - if (Object.keys(config || []).length === 0 && typeof stylelintDefaultConfig !== 'undefined') { - config = deepCopy(stylelintDefaultConfig.rules); - } - BG.chromeSync.setValue('editorStylelintConfig', config); - return config; -} - -function setCSSLintConfig(config) { - if (Object.keys(config || []).length === 0 && typeof csslintDefaultConfig !== 'undefined') { - config = Object.assign({}, csslintDefaultConfig); - } - BG.chromeSync.setValue('editorCSSLintConfig', config); - return config; -} - -function getLinterConfigForCodeMirror(name) { - return CodeMirror.lint && CodeMirror.lint[name] ? { - getAnnotations: CodeMirror.lint[name], - delay: prefs.get('editor.lintDelay') - } : false; -} - -function updateLinter(linter) { +function updateLinter(linter = prefs.get('editor.linter')) { function updateEditors() { - const options = getLinterConfigForCodeMirror(linter); - CodeMirror.defaults.lint = options === 'null' ? false : options; + const options = linterConfig.getForCodeMirror(linter); + CodeMirror.defaults.lint = options; editors.forEach(cm => { // set lint to "null" to disable cm.setOption('lint', options); @@ -58,7 +148,7 @@ function updateLinter(linter) { loadSelectedLinter(linter).then(() => { updateEditors(); }); - $('#linter-settings').style.display = linter === 'null' ? 'none' : 'inline-block'; + $('#linter-settings').style.display = !linter ? 'none' : 'inline-block'; } function updateLintReport(cm, delay) { @@ -215,7 +305,7 @@ function showLintHelp() { let list = '