Drop lint.js
This commit is contained in:
parent
eb76861fad
commit
bd29c07090
|
@ -1,41 +0,0 @@
|
||||||
/* global importScripts parserlib CSSLint parseMozFormat */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const CSSLINT_PATH = '/vendor-overwrites/csslint/';
|
|
||||||
importScripts(CSSLINT_PATH + 'parserlib.js');
|
|
||||||
|
|
||||||
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
|
|
||||||
|
|
||||||
self.onmessage = ({data}) => {
|
|
||||||
|
|
||||||
const {action = 'run'} = data;
|
|
||||||
|
|
||||||
if (action === 'parse') {
|
|
||||||
if (!self.parseMozFormat) self.importScripts('/js/moz-parser.js');
|
|
||||||
self.postMessage(parseMozFormat(data));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self.CSSLint) self.importScripts(CSSLINT_PATH + 'csslint.js');
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'getAllRuleIds':
|
|
||||||
// the functions are non-tranferable and we need only an id
|
|
||||||
self.postMessage(CSSLint.getRules().map(rule => rule.id));
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 'getAllRuleInfos':
|
|
||||||
// the functions are non-tranferable
|
|
||||||
self.postMessage(CSSLint.getRules().map(rule => JSON.parse(JSON.stringify(rule))));
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 'run': {
|
|
||||||
const {code, config} = data;
|
|
||||||
const results = CSSLint.verify(code, config).messages
|
|
||||||
//.filter(m => !m.message.includes('/*[[') && !m.message.includes(']]*/'))
|
|
||||||
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
|
|
||||||
self.postMessage(results);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
global CodeMirror parserlib loadScript
|
global CodeMirror loadScript
|
||||||
global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter
|
|
||||||
global createSourceEditor
|
global createSourceEditor
|
||||||
global closeCurrentTab regExpTester messageBox
|
global closeCurrentTab regExpTester messageBox
|
||||||
global setupCodeMirror
|
global setupCodeMirror
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/* global CodeMirror linterConfig */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
CodeMirror.registerHelper('lint', 'csslint', invokeHelper);
|
|
||||||
CodeMirror.registerHelper('lint', 'stylelint', invokeHelper);
|
|
||||||
|
|
||||||
const COOKS = {
|
|
||||||
csslint: 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),
|
|
||||||
|
|
||||||
stylelint({results}, cm) {
|
|
||||||
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)'));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function invokeHelper(code, options, cm) {
|
|
||||||
const config = linterConfig.getCurrent();
|
|
||||||
const cook = COOKS[linterConfig.getName()];
|
|
||||||
return linterConfig.invokeWorker({code, config})
|
|
||||||
.then(data => cook(data, cm));
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,50 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSSLint Config values
|
|
||||||
* 0 = disabled; 1 = warning; 2 = error
|
|
||||||
*/
|
|
||||||
window.linterConfig.defaults.csslint = {
|
|
||||||
// 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
|
|
||||||
};
|
|
|
@ -1,170 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window.linterConfig.defaults.stylelint = (defaultSeverity => ({
|
|
||||||
// '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, defaultSeverity],
|
|
||||||
'block-no-empty': [true, defaultSeverity],
|
|
||||||
'color-no-invalid-hex': [true, defaultSeverity],
|
|
||||||
'declaration-block-no-duplicate-properties': [true, {
|
|
||||||
'ignore': ['consecutive-duplicates-with-different-values'],
|
|
||||||
'severity': 'warning'
|
|
||||||
}],
|
|
||||||
'declaration-block-no-shorthand-property-overrides': [true, defaultSeverity],
|
|
||||||
'font-family-no-duplicate-names': [true, defaultSeverity],
|
|
||||||
'function-calc-no-unspaced-operator': [true, defaultSeverity],
|
|
||||||
'function-linear-gradient-no-nonstandard-direction': [true, defaultSeverity],
|
|
||||||
'keyframe-declaration-no-important': [true, defaultSeverity],
|
|
||||||
'media-feature-name-no-unknown': [true, defaultSeverity],
|
|
||||||
/* recommended true */
|
|
||||||
'no-empty-source': false,
|
|
||||||
'no-extra-semicolons': [true, defaultSeverity],
|
|
||||||
'no-invalid-double-slash-comments': [true, defaultSeverity],
|
|
||||||
'property-no-unknown': [true, defaultSeverity],
|
|
||||||
'selector-pseudo-class-no-unknown': [true, defaultSeverity],
|
|
||||||
'selector-pseudo-element-no-unknown': [true, defaultSeverity],
|
|
||||||
'selector-type-no-unknown': false, // for scss/less/stylus-lang
|
|
||||||
'string-no-newline': [true, defaultSeverity],
|
|
||||||
'unit-no-unknown': [true, defaultSeverity],
|
|
||||||
|
|
||||||
// ** non-essential rules
|
|
||||||
'comment-no-empty': false,
|
|
||||||
'declaration-block-no-redundant-longhand-properties': false,
|
|
||||||
'shorthand-property-no-redundant-values': false,
|
|
||||||
|
|
||||||
// ** stylistic rules **
|
|
||||||
/*
|
|
||||||
'at-rule-empty-line-before': [
|
|
||||||
'always',
|
|
||||||
{
|
|
||||||
'except': [
|
|
||||||
'blockless-after-same-name-blockless',
|
|
||||||
'first-nested'
|
|
||||||
],
|
|
||||||
'ignore': [
|
|
||||||
'after-comment'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'at-rule-name-case': 'lower',
|
|
||||||
'at-rule-name-space-after': 'always-single-line',
|
|
||||||
'at-rule-semicolon-newline-after': 'always',
|
|
||||||
'block-closing-brace-empty-line-before': 'never',
|
|
||||||
'block-closing-brace-newline-after': 'always',
|
|
||||||
'block-closing-brace-newline-before': 'always-multi-line',
|
|
||||||
'block-closing-brace-space-before': 'always-single-line',
|
|
||||||
'block-opening-brace-newline-after': 'always-multi-line',
|
|
||||||
'block-opening-brace-space-after': 'always-single-line',
|
|
||||||
'block-opening-brace-space-before': 'always',
|
|
||||||
'color-hex-case': 'lower',
|
|
||||||
'color-hex-length': 'short',
|
|
||||||
'comment-empty-line-before': [
|
|
||||||
'always',
|
|
||||||
{
|
|
||||||
'except': [
|
|
||||||
'first-nested'
|
|
||||||
],
|
|
||||||
'ignore': [
|
|
||||||
'stylelint-commands'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'comment-whitespace-inside': 'always',
|
|
||||||
'custom-property-empty-line-before': [
|
|
||||||
'always',
|
|
||||||
{
|
|
||||||
'except': [
|
|
||||||
'after-custom-property',
|
|
||||||
'first-nested'
|
|
||||||
],
|
|
||||||
'ignore': [
|
|
||||||
'after-comment',
|
|
||||||
'inside-single-line-block'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'declaration-bang-space-after': 'never',
|
|
||||||
'declaration-bang-space-before': 'always',
|
|
||||||
'declaration-block-semicolon-newline-after': 'always-multi-line',
|
|
||||||
'declaration-block-semicolon-space-after': 'always-single-line',
|
|
||||||
'declaration-block-semicolon-space-before': 'never',
|
|
||||||
'declaration-block-single-line-max-declarations': 1,
|
|
||||||
'declaration-block-trailing-semicolon': 'always',
|
|
||||||
'declaration-colon-newline-after': 'always-multi-line',
|
|
||||||
'declaration-colon-space-after': 'always-single-line',
|
|
||||||
'declaration-colon-space-before': 'never',
|
|
||||||
'declaration-empty-line-before': [
|
|
||||||
'always',
|
|
||||||
{
|
|
||||||
'except': [
|
|
||||||
'after-declaration',
|
|
||||||
'first-nested'
|
|
||||||
],
|
|
||||||
'ignore': [
|
|
||||||
'after-comment',
|
|
||||||
'inside-single-line-block'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'function-comma-newline-after': 'always-multi-line',
|
|
||||||
'function-comma-space-after': 'always-single-line',
|
|
||||||
'function-comma-space-before': 'never',
|
|
||||||
'function-max-empty-lines': 0,
|
|
||||||
'function-name-case': 'lower',
|
|
||||||
'function-parentheses-newline-inside': 'always-multi-line',
|
|
||||||
'function-parentheses-space-inside': 'never-single-line',
|
|
||||||
'function-whitespace-after': 'always',
|
|
||||||
'indentation': 2,
|
|
||||||
'length-zero-no-unit': true,
|
|
||||||
'max-empty-lines': 1,
|
|
||||||
'media-feature-colon-space-after': 'always',
|
|
||||||
'media-feature-colon-space-before': 'never',
|
|
||||||
'media-feature-name-case': 'lower',
|
|
||||||
'media-feature-parentheses-space-inside': 'never',
|
|
||||||
'media-feature-range-operator-space-after': 'always',
|
|
||||||
'media-feature-range-operator-space-before': 'always',
|
|
||||||
'media-query-list-comma-newline-after': 'always-multi-line',
|
|
||||||
'media-query-list-comma-space-after': 'always-single-line',
|
|
||||||
'media-query-list-comma-space-before': 'never',
|
|
||||||
'no-eol-whitespace': true,
|
|
||||||
'no-missing-end-of-source-newline': true,
|
|
||||||
'number-leading-zero': 'always',
|
|
||||||
'number-no-trailing-zeros': true,
|
|
||||||
'property-case': 'lower',
|
|
||||||
'rule-empty-line-before': [
|
|
||||||
'always-multi-line',
|
|
||||||
{
|
|
||||||
'except': [
|
|
||||||
'first-nested'
|
|
||||||
],
|
|
||||||
'ignore': [
|
|
||||||
'after-comment'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'selector-attribute-brackets-space-inside': 'never',
|
|
||||||
'selector-attribute-operator-space-after': 'never',
|
|
||||||
'selector-attribute-operator-space-before': 'never',
|
|
||||||
'selector-combinator-space-after': 'always',
|
|
||||||
'selector-combinator-space-before': 'always',
|
|
||||||
'selector-descendant-combinator-no-non-space': true,
|
|
||||||
'selector-list-comma-newline-after': 'always',
|
|
||||||
'selector-list-comma-space-before': 'never',
|
|
||||||
'selector-max-empty-lines': 0,
|
|
||||||
'selector-pseudo-class-case': 'lower',
|
|
||||||
'selector-pseudo-class-parentheses-space-inside': 'never',
|
|
||||||
'selector-pseudo-element-case': 'lower',
|
|
||||||
'selector-pseudo-element-colon-notation': 'double',
|
|
||||||
'selector-type-case': 'lower',
|
|
||||||
'unit-case': 'lower',
|
|
||||||
'value-list-comma-newline-after': 'always-multi-line',
|
|
||||||
'value-list-comma-space-after': 'always-single-line',
|
|
||||||
'value-list-comma-space-before': 'never',
|
|
||||||
'value-list-max-empty-lines': 0
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}))({severity: 'warning'});
|
|
558
edit/lint.js
558
edit/lint.js
|
@ -1,558 +0,0 @@
|
||||||
/* global CodeMirror messageBox */
|
|
||||||
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */
|
|
||||||
/* global loadScript require CSSLint stylelint */
|
|
||||||
/* global clipString */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
onDOMready().then(loadLinterAssets);
|
|
||||||
|
|
||||||
// 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',
|
|
||||||
},
|
|
||||||
worker: {
|
|
||||||
csslint: {path: '/edit/csslint-loader.js'},
|
|
||||||
stylelint: {path: '/edit/stylelint-loader.js'},
|
|
||||||
},
|
|
||||||
allRuleIds: {
|
|
||||||
csslint: null,
|
|
||||||
stylelint: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
getName() {
|
|
||||||
// some dirty hacks to override editor.linter getting from prefs
|
|
||||||
const linter = prefs.get('editor.linter');
|
|
||||||
const mode = linter && editors[0] && editors[0].doc.mode;
|
|
||||||
return mode && ((mode.name || mode) !== 'css' || mode.helperType) ? 'stylelint' : linter;
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrent(linter = linterConfig.getName()) {
|
|
||||||
return this.fallbackToDefaults(this[linter] || {});
|
|
||||||
},
|
|
||||||
|
|
||||||
getForCodeMirror(linter = linterConfig.getName()) {
|
|
||||||
return CodeMirror.lint && CodeMirror.lint[linter] ? {
|
|
||||||
getAnnotations: CodeMirror.lint[linter],
|
|
||||||
delay: prefs.get('editor.lintDelay'),
|
|
||||||
onUpdateLinting(annotationsNotSorted, annotations, cm) {
|
|
||||||
updateLintReport(cm);
|
|
||||||
},
|
|
||||||
} : false;
|
|
||||||
},
|
|
||||||
|
|
||||||
fallbackToDefaults(config, linter = linterConfig.getName()) {
|
|
||||||
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 Object.assign({}, this.defaults[linter] || {}, config);
|
|
||||||
} else {
|
|
||||||
return deepCopy(this.defaults[linter] || {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setLinter(linter = linterConfig.getName()) {
|
|
||||||
linter = linter.toLowerCase();
|
|
||||||
linter = linter === 'csslint' || linter === 'stylelint' ? linter : '';
|
|
||||||
if (linterConfig.getName() !== linter) {
|
|
||||||
prefs.set('editor.linter', linter);
|
|
||||||
}
|
|
||||||
return linter;
|
|
||||||
},
|
|
||||||
|
|
||||||
invokeWorker(message) {
|
|
||||||
const worker = linterConfig.worker[message.linter || linterConfig.getName()];
|
|
||||||
if (!worker.queue) {
|
|
||||||
worker.queue = [];
|
|
||||||
worker.instance.onmessage = ({data}) => {
|
|
||||||
worker.queue.shift().resolve(data);
|
|
||||||
if (worker.queue.length) {
|
|
||||||
worker.instance.postMessage(worker.queue[0].message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new Promise(resolve => {
|
|
||||||
worker.queue.push({message, resolve});
|
|
||||||
if (worker.queue.length === 1) {
|
|
||||||
worker.instance.postMessage(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getAllRuleIds(linter = linterConfig.getName()) {
|
|
||||||
return Promise.resolve(
|
|
||||||
this.allRuleIds[linter] ||
|
|
||||||
this.invokeWorker({linter, action: 'getAllRuleIds'})
|
|
||||||
.then(ids => (this.allRuleIds[linter] = ids.sort()))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
findInvalidRules(config, linter = linterConfig.getName()) {
|
|
||||||
return this.getAllRuleIds(linter).then(allRuleIds => {
|
|
||||||
const allRuleIdsSet = new Set(allRuleIds);
|
|
||||||
const rules = linter === 'stylelint' ? config.rules : config;
|
|
||||||
return Object.keys(rules).filter(rule => !allRuleIdsSet.has(rule));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stringify(config = this.getCurrent()) {
|
|
||||||
if (linterConfig.getName() === '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 = linterConfig.getName();
|
|
||||||
this[linter] = config;
|
|
||||||
chromeSync.setLZValue(this.storageName[linter], config);
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
|
|
||||||
loadAll() {
|
|
||||||
return 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(updateLinter);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// this is an event listener so it can't refer to self via 'this'
|
|
||||||
openOnClick(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
setupLinterPopup(linterConfig.stringify());
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
if (!this.init.pending) this.init.pending = this.loadAll();
|
|
||||||
return this.init.pending;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function initLint() {
|
|
||||||
$('#lint-help').addEventListener('click', showLintHelp);
|
|
||||||
$('#lint').addEventListener('click', gotoLintIssue);
|
|
||||||
$('#linter-settings').addEventListener('click', linterConfig.openOnClick);
|
|
||||||
|
|
||||||
updateLinter();
|
|
||||||
linterConfig.watchStorage();
|
|
||||||
prefs.subscribe(['editor.linter'], updateLinter);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLinter({immediately, linter = linterConfig.getName()} = {}) {
|
|
||||||
if (!immediately) {
|
|
||||||
debounce(updateLinter, 0, {immediately: true, linter});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const GUTTERS_CLASS = 'CodeMirror-lint-markers';
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
linterConfig.init(),
|
|
||||||
loadLinterAssets(linter)
|
|
||||||
]).then(updateEditors);
|
|
||||||
$('#linter-settings').style.display = !linter ? 'none' : 'inline-block';
|
|
||||||
$('#lint').classList.add('hidden');
|
|
||||||
|
|
||||||
function updateEditors() {
|
|
||||||
CodeMirror.defaults.lint = linterConfig.getForCodeMirror(linter);
|
|
||||||
const guttersOption = prepareGuttersOption();
|
|
||||||
editors.forEach(cm => {
|
|
||||||
if (cm.options.lint !== CodeMirror.defaults.lint) {
|
|
||||||
cm.setOption('lint', CodeMirror.defaults.lint);
|
|
||||||
}
|
|
||||||
if (guttersOption) {
|
|
||||||
cm.setOption('guttersOption', guttersOption);
|
|
||||||
updateGutters(cm, guttersOption);
|
|
||||||
cm.refresh();
|
|
||||||
}
|
|
||||||
setTimeout(updateLintReport, 0, cm);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareGuttersOption() {
|
|
||||||
const gutters = CodeMirror.defaults.gutters;
|
|
||||||
const needRefresh = Boolean(linter) !== gutters.includes(GUTTERS_CLASS);
|
|
||||||
if (needRefresh) {
|
|
||||||
if (linter) {
|
|
||||||
gutters.push(GUTTERS_CLASS);
|
|
||||||
} else {
|
|
||||||
gutters.splice(gutters.indexOf(GUTTERS_CLASS), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return needRefresh && gutters;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateGutters(cm, guttersOption) {
|
|
||||||
cm.options.gutters = guttersOption;
|
|
||||||
const el = $('.' + GUTTERS_CLASS, cm.display.gutters);
|
|
||||||
if (linter && !el) {
|
|
||||||
cm.display.gutters.appendChild($create('.CodeMirror-gutter ' + GUTTERS_CLASS));
|
|
||||||
} else if (!linter && el) {
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLintReport(cm, delay) {
|
|
||||||
const state = cm && cm.state && cm.state.lint || {};
|
|
||||||
if (delay === 0) {
|
|
||||||
// immediately show pending csslint/stylelint messages in onbeforeunload and save
|
|
||||||
clearTimeout(state.lintTimeout);
|
|
||||||
updateLintReportInternal(cm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (delay > 0) {
|
|
||||||
clearTimeout(state.lintTimeout);
|
|
||||||
state.lintTimeout = setTimeout(cm => {
|
|
||||||
if (cm.performLint) {
|
|
||||||
cm.performLint();
|
|
||||||
updateLintReportInternal(cm);
|
|
||||||
}
|
|
||||||
}, delay, cm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (state.options) {
|
|
||||||
clearTimeout(state.reportTimeout);
|
|
||||||
const delay = cm && cm.state.renderLintReportNow ? 0 : state.options.delay + 100;
|
|
||||||
state.reportTimeout = setTimeout(updateLintReportInternal, delay, cm, {
|
|
||||||
postponeNewIssues: delay === undefined || delay === null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
|
|
||||||
const {changed, fixedSome} = (scope ? [scope] : editors).reduce(process, {});
|
|
||||||
if (changed) {
|
|
||||||
const renderNow = editors.last.state.renderLintReportNow =
|
|
||||||
!postponeNewIssues || fixedSome || editors.last.state.renderLintReportNow;
|
|
||||||
debounce(renderLintReport, renderNow ? 0 : CodeMirror.defaults.lintReportDelay, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function process(result, cm) {
|
|
||||||
const lintState = cm.state.lint || {};
|
|
||||||
const oldMarkers = lintState.stylusMarkers || new Map();
|
|
||||||
const newMarkers = lintState.stylusMarkers = new Map();
|
|
||||||
const oldText = (lintState.body || {}).textContentCached || '';
|
|
||||||
const activeLine = cm.getCursor().line;
|
|
||||||
const body = !(lintState.marked || {}).length ? {} :
|
|
||||||
$create('tbody', lintState.marked.map(mark => {
|
|
||||||
const info = mark.__annotation;
|
|
||||||
const {line, ch} = info.from;
|
|
||||||
const isActiveLine = line === activeLine;
|
|
||||||
const pos = isActiveLine ? 'cursor' : (line + ',' + ch);
|
|
||||||
const title = clipString(info.message, 1000) + `\n(${info.rule})`;
|
|
||||||
const message = clipString(info.message, 100);
|
|
||||||
if (isActiveLine || oldMarkers[pos] === message) {
|
|
||||||
oldMarkers.delete(pos);
|
|
||||||
}
|
|
||||||
newMarkers.set(pos, message);
|
|
||||||
return $create(`tr.${info.severity}`, [
|
|
||||||
$create('td', {attributes: {role: 'severity'}, dataset: {rule: info.rule}},
|
|
||||||
$create('.CodeMirror-lint-marker-' + info.severity, info.severity)),
|
|
||||||
$create('td', {attributes: {role: 'line'}}, line + 1),
|
|
||||||
$create('td', {attributes: {role: 'sep'}}, ':'),
|
|
||||||
$create('td', {attributes: {role: 'col'}}, ch + 1),
|
|
||||||
$create('td', {attributes: {role: 'message'}, title}, message),
|
|
||||||
]);
|
|
||||||
}));
|
|
||||||
body.textContentCached = body.textContent || '';
|
|
||||||
lintState.body = body.textContentCached && body;
|
|
||||||
result.changed |= oldText !== body.textContentCached;
|
|
||||||
result.fixedSome |= lintState.reportDisplayed && oldMarkers.size;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLintReport(someBlockChanged) {
|
|
||||||
const container = $('#lint');
|
|
||||||
const content = container.children[1];
|
|
||||||
const label = t('sectionCode');
|
|
||||||
const newContent = content.cloneNode(false);
|
|
||||||
let issueCount = 0;
|
|
||||||
editors.forEach((cm, index) => {
|
|
||||||
cm.state.renderLintReportNow = false;
|
|
||||||
const lintState = cm.state.lint || {};
|
|
||||||
const body = lintState.body;
|
|
||||||
if (!body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newBlock = $create('table', {cm}, [
|
|
||||||
$create('caption', label + ' ' + (index + 1)),
|
|
||||||
body,
|
|
||||||
]);
|
|
||||||
newContent.appendChild(newBlock);
|
|
||||||
issueCount += newBlock.rows.length;
|
|
||||||
|
|
||||||
const block = content.children[newContent.children.length - 1];
|
|
||||||
const blockChanged =
|
|
||||||
!block ||
|
|
||||||
block.cm !== cm ||
|
|
||||||
body.textContentCached !== block.textContentCached;
|
|
||||||
someBlockChanged |= blockChanged;
|
|
||||||
lintState.reportDisplayed = blockChanged;
|
|
||||||
});
|
|
||||||
if (someBlockChanged || newContent.children.length !== content.children.length) {
|
|
||||||
$('#issue-count').textContent = issueCount;
|
|
||||||
container.replaceChild(newContent, content);
|
|
||||||
container.classList.toggle('hidden', !newContent.children.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function gotoLintIssue(event) {
|
|
||||||
const issue = event.target.closest('tr');
|
|
||||||
if (!issue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const block = issue.closest('table');
|
|
||||||
makeSectionVisible(block.cm);
|
|
||||||
block.cm.focus();
|
|
||||||
block.cm.setSelection({
|
|
||||||
line: parseInt($('td[role="line"]', issue).textContent) - 1,
|
|
||||||
ch: parseInt($('td[role="col"]', issue).textContent) - 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLintHelp() {
|
|
||||||
const linter = linterConfig.getName();
|
|
||||||
const baseUrl = linter === 'stylelint'
|
|
||||||
? 'https://stylelint.io/user-guide/rules/'
|
|
||||||
// some CSSLint rules do not have a url
|
|
||||||
: 'https://github.com/CSSLint/csslint/issues/535';
|
|
||||||
let headerLink, template, csslintRules;
|
|
||||||
if (linter === 'csslint') {
|
|
||||||
headerLink = $createLink('https://github.com/CSSLint/csslint/wiki/Rules', 'CSSLint');
|
|
||||||
template = ruleID => {
|
|
||||||
const rule = csslintRules.find(rule => rule.id === ruleID);
|
|
||||||
return rule &&
|
|
||||||
$create('li', [
|
|
||||||
$create('b', $createLink(rule.url || baseUrl, rule.name)),
|
|
||||||
$create('br'),
|
|
||||||
rule.desc,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
headerLink = $createLink(baseUrl, 'stylelint');
|
|
||||||
template = rule =>
|
|
||||||
$create('li',
|
|
||||||
rule === 'CssSyntaxError' ? rule : $createLink(baseUrl + rule, rule));
|
|
||||||
}
|
|
||||||
const header = t('linterIssuesHelp', '\x01').split('\x01');
|
|
||||||
const activeRules = new Set($$('#lint td[role="severity"]').map(el => el.dataset.rule));
|
|
||||||
Promise.resolve(linter !== 'csslint' || linterConfig.invokeWorker({action: 'getAllRuleInfos'}))
|
|
||||||
.then(data => {
|
|
||||||
csslintRules = data;
|
|
||||||
showHelp(t('linterIssues'),
|
|
||||||
$create([
|
|
||||||
header[0], headerLink, header[1],
|
|
||||||
$create('ul.rules', [...activeRules.values()].map(template)),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLinterErrorMessage(title, contents, popup) {
|
|
||||||
messageBox({
|
|
||||||
title,
|
|
||||||
contents,
|
|
||||||
className: 'danger center lint-config',
|
|
||||||
buttons: [t('confirmOK')],
|
|
||||||
}).then(() => popup && popup.codebox && popup.codebox.focus());
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupLinterPopup(config) {
|
|
||||||
const linter = linterConfig.getName();
|
|
||||||
const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
|
|
||||||
const defaultConfig = linterConfig.stringify(linterConfig.defaults[linter] || {});
|
|
||||||
const title = t('linterConfigPopupTitle', linterTitle);
|
|
||||||
const popup = showCodeMirrorPopup(title, null, {
|
|
||||||
lint: false,
|
|
||||||
extraKeys: {'Ctrl-Enter': save},
|
|
||||||
hintOptions: {hint},
|
|
||||||
});
|
|
||||||
$('.contents', popup).appendChild(makeFooter());
|
|
||||||
|
|
||||||
let cm = popup.codebox;
|
|
||||||
cm.focus();
|
|
||||||
cm.setValue(config);
|
|
||||||
cm.clearHistory();
|
|
||||||
cm.markClean();
|
|
||||||
cm.on('changes', updateButtonState);
|
|
||||||
updateButtonState();
|
|
||||||
|
|
||||||
cm.rerouteHotkeys(false);
|
|
||||||
window.addEventListener('closeHelp', function _() {
|
|
||||||
window.removeEventListener('closeHelp', _);
|
|
||||||
cm.rerouteHotkeys(true);
|
|
||||||
cm = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
loadScript([
|
|
||||||
'/vendor/codemirror/mode/javascript/javascript.js',
|
|
||||||
'/vendor/codemirror/addon/lint/json-lint.js',
|
|
||||||
'/vendor/jsonlint/jsonlint.js'
|
|
||||||
]).then(() => {
|
|
||||||
cm.setOption('mode', 'application/json');
|
|
||||||
cm.setOption('lint', 'json');
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeFooter() {
|
|
||||||
return $create('div', [
|
|
||||||
$create('p', [
|
|
||||||
$createLink(
|
|
||||||
linter === 'stylelint'
|
|
||||||
? 'https://stylelint.io/user-guide/rules/'
|
|
||||||
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
|
|
||||||
t('linterRulesLink')),
|
|
||||||
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '',
|
|
||||||
]),
|
|
||||||
$create('.buttons', [
|
|
||||||
$create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')),
|
|
||||||
$create('button.cancel', {onclick: cancel}, t('confirmClose')),
|
|
||||||
$create('button.reset', {onclick: reset, title: t('linterResetMessage')}, t('genericResetLabel')),
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function save(event) {
|
|
||||||
if (event instanceof Event) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
const json = tryJSONparse(cm.getValue());
|
|
||||||
if (!json) {
|
|
||||||
showLinterErrorMessage(linter, t('linterJSONError'), popup);
|
|
||||||
cm.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
linterConfig.findInvalidRules(json, linter).then(invalid => {
|
|
||||||
if (invalid.length) {
|
|
||||||
showLinterErrorMessage(linter, [
|
|
||||||
t('linterInvalidConfigError'),
|
|
||||||
$create('ul', invalid.map(name => $create('li', name))),
|
|
||||||
], popup);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
linterConfig.setLinter(linter);
|
|
||||||
linterConfig.save(json);
|
|
||||||
cm.markClean();
|
|
||||||
cm.focus();
|
|
||||||
updateButtonState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
linterConfig.setLinter(linter);
|
|
||||||
cm.setValue(defaultConfig);
|
|
||||||
cm.focus();
|
|
||||||
updateButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
$('.dismiss').dispatchEvent(new Event('click'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateButtonState() {
|
|
||||||
$('.save', popup).disabled = cm.isClean();
|
|
||||||
$('.reset', popup).disabled = cm.getValue() === defaultConfig;
|
|
||||||
$('.cancel', popup).textContent = t(cm.isClean() ? 'confirmClose' : 'confirmCancel');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hint(cm) {
|
|
||||||
return Promise.all([
|
|
||||||
linterConfig.getAllRuleIds(linter),
|
|
||||||
linter !== 'stylelint' || hint.allOptions ||
|
|
||||||
linterConfig.invokeWorker({action: 'getAllRuleOptions', linter})
|
|
||||||
.then(options => (hint.allOptions = options)),
|
|
||||||
])
|
|
||||||
.then(([ruleIds, options]) => {
|
|
||||||
const cursor = cm.getCursor();
|
|
||||||
const {start, end, string, type, state: {lexical}} = cm.getTokenAt(cursor);
|
|
||||||
const {line, ch} = cursor;
|
|
||||||
|
|
||||||
const quoted = string.startsWith('"');
|
|
||||||
const leftPart = string.slice(quoted ? 1 : 0, ch - start).trim();
|
|
||||||
const depth = getLexicalDepth(lexical);
|
|
||||||
|
|
||||||
const search = cm.getSearchCursor(/"([-\w]+)"/, {line, ch: start - 1});
|
|
||||||
let [, prevWord] = search.find(true) || [];
|
|
||||||
let words = [];
|
|
||||||
|
|
||||||
if (depth === 1 && linter === 'stylelint') {
|
|
||||||
words = quoted ? ['rules'] : [];
|
|
||||||
} else if ((depth === 1 || depth === 2) && type && type.includes('property')) {
|
|
||||||
words = ruleIds;
|
|
||||||
} else if (depth === 2 || depth === 3 && lexical.type === ']') {
|
|
||||||
words = !quoted ? ['true', 'false', 'null'] :
|
|
||||||
ruleIds.includes(prevWord) && (options[prevWord] || [])[0] || [];
|
|
||||||
} else if (depth === 4 && prevWord === 'severity') {
|
|
||||||
words = ['error', 'warning'];
|
|
||||||
} else if (depth === 4) {
|
|
||||||
words = ['ignore', 'ignoreAtRules', 'except', 'severity'];
|
|
||||||
} else if (depth === 5 && lexical.type === ']' && quoted) {
|
|
||||||
while (prevWord && !ruleIds.includes(prevWord)) {
|
|
||||||
prevWord = (search.find(true) || [])[1];
|
|
||||||
}
|
|
||||||
words = (options[prevWord] || []).slice(-1)[0] || ruleIds;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
list: words.filter(word => word.startsWith(leftPart)),
|
|
||||||
from: {line, ch: start + (quoted ? 1 : 0)},
|
|
||||||
to: {line, ch: string.endsWith('"') ? end - 1 : end},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLexicalDepth(lexicalState) {
|
|
||||||
let depth = 0;
|
|
||||||
while ((lexicalState = lexicalState.prev)) {
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadLinterAssets(name = linterConfig.getName()) {
|
|
||||||
const worker = linterConfig.worker[name];
|
|
||||||
if (!name || !worker) return Promise.resolve();
|
|
||||||
const scripts = [];
|
|
||||||
if (!worker.instance) {
|
|
||||||
worker.instance = new Worker(worker.path);
|
|
||||||
scripts.push(`/edit/lint-defaults-${name}.js`);
|
|
||||||
}
|
|
||||||
if (!CodeMirror.lint) {
|
|
||||||
scripts.push(
|
|
||||||
'/vendor/codemirror/addon/lint/lint.css',
|
|
||||||
'/vendor/codemirror/addon/lint/lint.js',
|
|
||||||
'/edit/lint-codemirror-helper.js');
|
|
||||||
}
|
|
||||||
return scripts.length ? loadScript(scripts) : Promise.resolve();
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ global CodeMirror
|
||||||
global editors propertyToCss CssToProperty
|
global editors propertyToCss CssToProperty
|
||||||
global onChange indicateCodeChange initHooks setCleanGlobal
|
global onChange indicateCodeChange initHooks setCleanGlobal
|
||||||
global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete
|
global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete
|
||||||
global setCleanItem updateTitle updateLintReportIfEnabled renderLintReport
|
global setCleanItem updateTitle
|
||||||
global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection
|
global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection
|
||||||
global clipString
|
global clipString
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/* global require importScripts */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
importScripts('/vendor/stylelint-bundle/stylelint-bundle.min.js');
|
|
||||||
|
|
||||||
const stylelint = require('stylelint');
|
|
||||||
|
|
||||||
self.onmessage = ({data: {action = 'run', code, config}}) => {
|
|
||||||
switch (action) {
|
|
||||||
case 'getAllRuleIds':
|
|
||||||
// the functions are non-tranferable
|
|
||||||
self.postMessage(Object.keys(stylelint.rules));
|
|
||||||
return;
|
|
||||||
case 'getAllRuleOptions':
|
|
||||||
self.postMessage(getAllRuleOptions());
|
|
||||||
return;
|
|
||||||
case 'run':
|
|
||||||
stylelint.lint({code, config}).then(results =>
|
|
||||||
self.postMessage(results));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getAllRuleOptions() {
|
|
||||||
const options = {};
|
|
||||||
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
|
|
||||||
const rxString = /"([-\w\s]{3,}?)"/g;
|
|
||||||
for (const id of Object.keys(stylelint.rules)) {
|
|
||||||
const ruleCode = String(stylelint.rules[id]);
|
|
||||||
const sets = [];
|
|
||||||
let m, mStr;
|
|
||||||
while ((m = rxPossible.exec(ruleCode))) {
|
|
||||||
const possible = m[1];
|
|
||||||
const set = [];
|
|
||||||
while ((mStr = rxString.exec(possible))) {
|
|
||||||
const s = mStr[1];
|
|
||||||
if (s.includes(' ')) {
|
|
||||||
set.push(...s.split(/\s+/));
|
|
||||||
} else {
|
|
||||||
set.push(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (possible.includes('ignoreAtRules')) {
|
|
||||||
set.push('ignoreAtRules');
|
|
||||||
}
|
|
||||||
if (possible.includes('ignoreShorthands')) {
|
|
||||||
set.push('ignoreShorthands');
|
|
||||||
}
|
|
||||||
if (set.length) {
|
|
||||||
sets.push(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sets.length) {
|
|
||||||
options[id] = sets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user