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);
+ });
+ }
+ }
+})();