diff --git a/edit/csslint-loader.js b/edit/csslint-loader.js deleted file mode 100644 index dfe7811f..00000000 --- a/edit/csslint-loader.js +++ /dev/null @@ -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; - } - } -}; diff --git a/edit/edit.js b/edit/edit.js index 73c7594a..4eda2255 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1,6 +1,5 @@ /* -global CodeMirror parserlib loadScript -global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter +global CodeMirror loadScript global createSourceEditor global closeCurrentTab regExpTester messageBox global setupCodeMirror diff --git a/edit/lint-codemirror-helper.js b/edit/lint-codemirror-helper.js deleted file mode 100644 index 90555533..00000000 --- a/edit/lint-codemirror-helper.js +++ /dev/null @@ -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)); - } -})(); diff --git a/edit/lint-defaults-csslint.js b/edit/lint-defaults-csslint.js deleted file mode 100644 index 465a8215..00000000 --- a/edit/lint-defaults-csslint.js +++ /dev/null @@ -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 -}; diff --git a/edit/lint-defaults-stylelint.js b/edit/lint-defaults-stylelint.js deleted file mode 100644 index b75bf6f4..00000000 --- a/edit/lint-defaults-stylelint.js +++ /dev/null @@ -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'}); diff --git a/edit/lint.js b/edit/lint.js deleted file mode 100644 index a2c8d71f..00000000 --- a/edit/lint.js +++ /dev/null @@ -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(); -} diff --git a/edit/sections.js b/edit/sections.js index 1a2cb5ac..63a8a12a 100644 --- a/edit/sections.js +++ b/edit/sections.js @@ -3,7 +3,7 @@ global CodeMirror global editors propertyToCss CssToProperty global onChange indicateCodeChange initHooks setCleanGlobal global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete -global setCleanItem updateTitle updateLintReportIfEnabled renderLintReport +global setCleanItem updateTitle global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection global clipString */ diff --git a/edit/stylelint-loader.js b/edit/stylelint-loader.js deleted file mode 100644 index d7ac7c9a..00000000 --- a/edit/stylelint-loader.js +++ /dev/null @@ -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; -}