diff --git a/edit.html b/edit.html index 68de7a1e..8463a984 100644 --- a/edit.html +++ b/edit.html @@ -89,8 +89,8 @@ - - + + diff --git a/edit/linter-csslint.js b/edit/linter-csslint.js deleted file mode 100644 index bd606344..00000000 --- a/edit/linter-csslint.js +++ /dev/null @@ -1,89 +0,0 @@ -/* global linter editorWorker memoize */ -'use strict'; - -// eslint-disable-next-line no-var -var csslint = (() => { - const DEFAULT = { - // Default warnings - 'display-property-grouping': 1, - 'duplicate-properties': 1, - 'empty-rules': 1, - 'errors': 1, - 'warnings': 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 - }; - let config; - - const prepareConfig = memoize(() => { - chrome.storage.onChanged.addListener((changes, area) => { - if (area !== 'sync' || !changes.hasOwnProperty('editorCSSLintConfig')) { - return; - } - getNewValue().then(linter.refresh); - }); - return getNewValue(); - - function getNewValue() { - return chromeSync.getLZValue('editorCSSLintConfig') - .then(newConfig => { - config = Object.assign({}, DEFAULT, newConfig); - }); - } - }); - - linter.register((text, options, cm) => { - if (prefs.get('editor.linter') !== 'csslint' || cm.getOption('mode') !== 'css') { - return; - } - return prepareConfig() - .then(() => editorWorker.csslint(text, config)) - .then(results => - results - .map(({line, col: ch, message, rule, type: severity}) => line && { - message, - from: {line: line - 1, ch: ch - 1}, - to: {line: line - 1, ch}, - rule: rule.id, - severity, - }) - .filter(Boolean) - ); - }); - - return {DEFAULT}; -})(); diff --git a/edit/linter-stylelint.js b/edit/linter-defaults.js similarity index 67% rename from edit/linter-stylelint.js rename to edit/linter-defaults.js index 0692b35e..e02e5420 100644 --- a/edit/linter-stylelint.js +++ b/edit/linter-defaults.js @@ -1,39 +1,38 @@ -/* global linter editorWorker memoize */ 'use strict'; // eslint-disable-next-line no-var -var stylelint = (() => { - const DEFAULT_SEVERITY = {severity: 'warning'}; - const DEFAULT = { +var LINTER_DEFAULTS = (() => { + const SEVERITY = {severity: 'warning'}; + const STYLELINT = { // 'sugarss' is a indent-based syntax like Sass or Stylus // ref: https://github.com/postcss/postcss#syntaxes // syntax: 'sugarss', // ** recommended rules ** // ref: https://github.com/stylelint/stylelint-config-recommended/blob/master/index.js rules: { - 'at-rule-no-unknown': [true, DEFAULT_SEVERITY], - 'block-no-empty': [true, DEFAULT_SEVERITY], - 'color-no-invalid-hex': [true, DEFAULT_SEVERITY], + 'at-rule-no-unknown': [true, SEVERITY], + 'block-no-empty': [true, SEVERITY], + 'color-no-invalid-hex': [true, SEVERITY], 'declaration-block-no-duplicate-properties': [true, { 'ignore': ['consecutive-duplicates-with-different-values'], 'severity': 'warning' }], - 'declaration-block-no-shorthand-property-overrides': [true, DEFAULT_SEVERITY], - 'font-family-no-duplicate-names': [true, DEFAULT_SEVERITY], - 'function-calc-no-unspaced-operator': [true, DEFAULT_SEVERITY], - 'function-linear-gradient-no-nonstandard-direction': [true, DEFAULT_SEVERITY], - 'keyframe-declaration-no-important': [true, DEFAULT_SEVERITY], - 'media-feature-name-no-unknown': [true, DEFAULT_SEVERITY], + 'declaration-block-no-shorthand-property-overrides': [true, SEVERITY], + 'font-family-no-duplicate-names': [true, SEVERITY], + 'function-calc-no-unspaced-operator': [true, SEVERITY], + 'function-linear-gradient-no-nonstandard-direction': [true, SEVERITY], + 'keyframe-declaration-no-important': [true, SEVERITY], + 'media-feature-name-no-unknown': [true, SEVERITY], /* recommended true */ 'no-empty-source': false, - 'no-extra-semicolons': [true, DEFAULT_SEVERITY], - 'no-invalid-double-slash-comments': [true, DEFAULT_SEVERITY], - 'property-no-unknown': [true, DEFAULT_SEVERITY], - 'selector-pseudo-class-no-unknown': [true, DEFAULT_SEVERITY], - 'selector-pseudo-element-no-unknown': [true, DEFAULT_SEVERITY], + 'no-extra-semicolons': [true, SEVERITY], + 'no-invalid-double-slash-comments': [true, SEVERITY], + 'property-no-unknown': [true, SEVERITY], + 'selector-pseudo-class-no-unknown': [true, SEVERITY], + 'selector-pseudo-element-no-unknown': [true, SEVERITY], 'selector-type-no-unknown': false, // for scss/less/stylus-lang - 'string-no-newline': [true, DEFAULT_SEVERITY], - 'unit-no-unknown': [true, DEFAULT_SEVERITY], + 'string-no-newline': [true, SEVERITY], + 'unit-no-unknown': [true, SEVERITY], // ** non-essential rules 'comment-no-empty': false, @@ -172,59 +171,49 @@ var stylelint = (() => { */ } }; - let config; + const CSSLINT = { + // Default warnings + 'display-property-grouping': 1, + 'duplicate-properties': 1, + 'empty-rules': 1, + 'errors': 1, + 'warnings': 1, + 'known-properties': 1, - const prepareConfig = memoize(() => { - chrome.storage.onChanged.addListener((changes, area) => { - if (area !== 'sync' || !changes.hasOwnProperty('editorStylelintConfig')) { - return; - } - getNewValue().then(linter.run); - }); - return getNewValue(); - - function getNewValue() { - return chromeSync.getLZValue('editorStylelintConfig') - .then(newConfig => { - const output = {}; - output.rules = Object.assign({}, DEFAULT.rules, newConfig && newConfig.rules); - output.syntax = 'sugarss'; - config = output; - }); - } - }); - - linter.register((text, options, cm) => { - if ( - !prefs.get('editor.linter') || - cm.getOption('mode') === 'css' && prefs.get('editor.linter') !== 'stylelint' - ) { - return; - } - return prepareConfig() - .then(() => editorWorker.stylelint(text, config)) - .then(({results}) => { - if (!results[0]) { - return []; - } - const output = results[0].warnings.map(({line, column: ch, text, severity}) => - ({ - from: {line: line - 1, ch: ch - 1}, - to: {line: line - 1, ch}, - message: text - .replace('Unexpected ', '') - .replace(/^./, firstLetter => firstLetter.toUpperCase()) - .replace(/\s*\([^(]+\)$/, ''), // strip the rule, - rule: text.replace(/^.*?\s*\(([^(]+)\)$/, '$1'), - severity, - }) - ); - return cm.doc.mode.name !== 'stylus' ? - output : - output.filter(({message}) => - !message.includes('"@css"') || !message.includes('(at-rule-no-unknown)')); - }); - }); - - return {DEFAULT}; + // 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 + }; + return {STYLELINT, CSSLINT, SEVERITY}; })(); diff --git a/edit/linter-engines.js b/edit/linter-engines.js new file mode 100644 index 00000000..817972b8 --- /dev/null +++ b/edit/linter-engines.js @@ -0,0 +1,105 @@ +/* global LINTER_DEFAULTS linter editorWorker */ +'use strict'; + +(() => { + registerLinters({ + csslint: { + storageName: 'editorCSSLintConfig', + lint: csslint, + validMode: mode => mode === 'css', + getConfig: config => Object.assign({}, LINTER_DEFAULTS.CSSLINT, config) + }, + stylelint: { + storageName: 'editorStylelintConfig', + lint: stylelint, + validMode: () => true, + getConfig: config => ({ + syntax: 'sugarss', + rules: Object.assign({}, LINTER_DEFAULTS.STYLELINT.rules, config && config.rules) + }) + } + }); + + function stylelint(text, config, mode) { + return editorWorker.stylelint(text, config) + .then(({results}) => { + if (!results[0]) { + return []; + } + const output = results[0].warnings.map(({line, column: ch, text, severity}) => + ({ + from: {line: line - 1, ch: ch - 1}, + to: {line: line - 1, ch}, + message: text + .replace('Unexpected ', '') + .replace(/^./, firstLetter => firstLetter.toUpperCase()) + .replace(/\s*\([^(]+\)$/, ''), // strip the rule, + rule: text.replace(/^.*?\s*\(([^(]+)\)$/, '$1'), + severity, + }) + ); + return mode !== 'stylus' ? + output : + output.filter(({message}) => + !message.includes('"@css"') || !message.includes('(at-rule-no-unknown)')); + }); + } + + function csslint(text, config) { + return editorWorker.csslint(text, config) + .then(results => + results + .map(({line, col: ch, message, rule, type: severity}) => line && { + message, + from: {line: line - 1, ch: ch - 1}, + to: {line: line - 1, ch}, + rule: rule.id, + severity, + }) + .filter(Boolean) + ); + } + + function registerLinters(engines) { + const configs = new Map(); + + chrome.storage.onChanged.addListener((changes, area) => { + if (area !== 'sync' || !changes.hasOwnProperty('editorStylelintConfig')) { + return; + } + for (const [name, engine] of Object.entries(engines)) { + if (changes.hasOwnProperty(engine.storageName)) { + chromeSync.getLZValue(engine.storageName) + .then(config => { + configs.set(name, engine.getConfig(config)); + linter.run(); + }); + } + } + }); + + linter.register((text, options, cm) => { + const selectedLinter = prefs.get('editor.linter'); + if (!selectedLinter) { + return; + } + const mode = cm.getOption('mode'); + for (const [name, engine] of Object.entries(engines)) { + if (name === selectedLinter && engine.validMode(mode)) { + return getConfig(name).then(config => engine.lint(text, config, mode)); + } + } + }); + + function getConfig(name) { + if (configs.has(name)) { + return Promise.resolve(configs.get(name)); + } + return chromeSync.getLZValue(engines[name].storageName) + .then(config => { + configs.set(name, engines[name].getConfig(config)); + return configs.get(name); + }); + } + } +})();