Edit book: Switch to a new library (stylehint) for find problems in CSS as the old library was no longer maintained.

This commit is contained in:
Kovid Goyal 2023-01-05 11:28:43 +05:30
parent 036bbdb7ff
commit c7a61b2942
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 154 additions and 10987 deletions

File diff suppressed because it is too large Load Diff

17
resources/stylelint-bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

88
resources/stylelint.js Normal file
View File

@ -0,0 +1,88 @@
/* vim:fileencoding=utf-8
*
* Copyright (C) 2023 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPLv3 license
*/
/*jshint esversion: 6 */
(function() {
"use strict";
window.stylelint_results = [];
window.check_css = function(src) {
stylelint.lint({
code: src,
config: {
rules: {
'annotation-no-unknown': true,
'at-rule-no-unknown': true,
'block-no-empty': true,
'color-no-invalid-hex': true,
'comment-no-empty': true,
'custom-property-no-missing-var-function': true,
'declaration-block-no-duplicate-custom-properties': true,
'declaration-block-no-duplicate-properties': [
true,
{
ignore: ['consecutive-duplicates-with-different-values'],
},
],
'declaration-block-no-shorthand-property-overrides': true,
'font-family-no-duplicate-names': true,
'font-family-no-missing-generic-family-keyword': true,
'function-calc-no-unspaced-operator': true,
'function-linear-gradient-no-nonstandard-direction': true,
'function-no-unknown': true,
'keyframe-block-no-duplicate-selectors': true,
'keyframe-declaration-no-important': true,
'media-feature-name-no-unknown': true,
'named-grid-areas-no-invalid': true,
'no-descending-specificity': true,
'no-duplicate-at-import-rules': true,
'no-duplicate-selectors': true,
'no-empty-source': true,
'no-extra-semicolons': true,
'no-invalid-double-slash-comments': true,
'no-invalid-position-at-import-rule': true,
'no-irregular-whitespace': true,
'property-no-unknown': true,
'selector-pseudo-class-no-unknown': true,
'selector-pseudo-element-no-unknown': true,
'selector-type-no-unknown': [
true,
{
ignore: ['custom-elements'],
},
],
'string-no-newline': true,
'unit-no-unknown': true,
},
},
formatter: (results, returnValue) => {
var r = results[0];
r._postcssResult = undefined;
r.source = undefined;
return JSON.stringify({results:r, rule_metadata: returnValue.ruleMetadata});
},
})
.then((results) => {
window.stylelint_results.push({type: 'results', 'results':results});
document.title = 'checked:' + window.performance.now();
})
.catch((err) => {
window.stylelint_results.push({type: 'error', 'error':'' + err});
console.error(err.stack);
document.title = 'checked:' + window.performance.now();
}) ;
};
window.get_css_results = function(src) {
var ans = window.stylelint_results;
window.stylelint_results = [];
return ans;
};
document.title = 'ready:' + window.performance.now();
})();

View File

@ -20,7 +20,7 @@ __all__ = [
'upload_user_manual', 'upload_demo', 'reupload',
'stage1', 'stage2', 'stage3', 'stage4', 'stage5', 'publish', 'publish_betas',
'linux', 'linux64', 'linuxarm64', 'win', 'win64', 'osx', 'build_dep',
'export_packages', 'hyphenation', 'liberation_fonts', 'csslint'
'export_packages', 'hyphenation', 'liberation_fonts', 'stylelint'
]
from setup.installers import Linux, Win, OSX, Linux64, LinuxArm64, Win64, ExtDev, BuildDep, ExportPackages
@ -39,7 +39,7 @@ iso639 = ISO639()
iso3166 = ISO3166()
from setup.csslint import CSSLint
csslint = CSSLint()
stylelint = CSSLint()
from setup.build import Build
build = Build()

View File

@ -9,12 +9,9 @@ from setup import Command
class CSSLint(Command):
# We can't use the released copy since it has not had a release in years and
# there are several critical bug fixes we need
description = 'Update the bundled copy of csslint'
NAME = 'csslint.js'
DOWNLOAD_URL = 'https://github.com/CSSLint/csslint.git'
description = 'Update the bundled copy of stylelint'
NAME = 'stylelint-bundle.min.js'
DOWNLOAD_URL = 'https://github.com/openstyles/stylelint-bundle.git'
@property
def vendored_file(self):
@ -25,8 +22,9 @@ class CSSLint(Command):
with self.temp_dir() as dl_src:
subprocess.check_call(['git', 'clone', '--depth=1', self.DOWNLOAD_URL], cwd=dl_src)
src = self.j(dl_src, 'csslint')
src = self.j(dl_src, 'stylelint-bundle')
subprocess.check_call(['npm', 'install'], cwd=src)
subprocess.check_call(['npm', 'run', 'build'], cwd=src)
shutil.copyfile(self.j(src, 'dist', self.NAME), self.vendored_file)
def clean(self):

View File

@ -41,74 +41,36 @@ def as_int_or_none(x):
return x
def message_to_error(message, name, line_offset=0):
rule = message.get('rule', {})
rule_id = rule.get('id') or ''
cls = CSSWarning
if message.get('type') == 'error':
cls = CSSParseError if rule.get('name') == 'Parsing Errors' else CSSError
title = message.get('message') or _('Unknown error')
def message_to_error(message, name, line_offset, rule_metadata):
rule_id = message.get('rule')
if rule_id == 'CssSyntaxError':
cls = CSSParseError
else:
cls = CSSError if message.get('severity') == 'error' else CSSWarning
title = message.get('text') or _('Unknown error')
title = title.rpartition('(')[0].strip()
line = as_int_or_none(message.get('line'))
col = as_int_or_none(message.get('col'))
col = as_int_or_none(message.get('column'))
if col is not None:
col -= 1
if line is not None:
line += line_offset
ans = cls(title, name, line, col)
ans.HELP = rule.get('desc') or ''
ans.HELP = message.get('text') or ''
ans.css_rule_id = rule_id
if ans.HELP and 'url' in rule:
ans.HELP += ' ' + _('See <a href="{}">detailed description</a>.').format(rule['url'])
m = rule_metadata.get(rule_id)
if m and 'url' in m:
ans.HELP += '. ' + _('See <a href="{}">detailed description</a>.').format(m['url'])
return ans
def csslint_js():
ans = getattr(csslint_js, 'ans', None)
def stylelint_js():
ans = getattr(stylelint_js, 'ans', None)
if ans is None:
ans = csslint_js.ans = P('csslint.js', data=True, allow_user_override=False).decode('utf-8') + '''
window.check_css = function(src) {
var rules = CSSLint.getRules();
var ruleset = {};
var ignored_rules = {
'order-alphabetical': 1,
'font-sizes': 1,
'zero-units': 1,
'bulletproof-font-face': 1,
'import': 1,
'box-model': 1,
'adjoining-classes': 1,
'box-sizing': 1,
'compatible-vendor-prefixes': 1,
'text-indent': 1,
'unique-headings': 1,
'fallback-colors': 1,
'font-faces': 1,
'regex-selectors': 1,
'universal-selector': 1,
'unqualified-attributes': 1,
'overqualified-elements': 1,
'shorthand': 1,
'duplicate-background-images': 1,
'floats': 1,
'ids': 1,
'gradients': 1
};
var error_rules = {
'known-properties': 1,
'duplicate-properties': 1,
'vendor-prefix': 1
};
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (!ignored_rules[rule.id] && rule.browsers === "All") ruleset[rule.id] = error_rules[rule.id] ? 2 : 1;
}
var result = CSSLint.verify(src, ruleset);
return result;
}
document.title = 'ready';
'''
ans = stylelint_js.ans = (
('stylelint-bundle.min.js', P('stylelint-bundle.min.js', data=True, allow_user_override=False).decode('utf-8')),
('stylelint.js', P('stylelint.js', data=True, allow_user_override=False).decode('utf-8')),
)
return ans
@ -117,11 +79,12 @@ def create_profile():
if ans is None:
ans = create_profile.ans = QWebEngineProfile(QApplication.instance())
setup_profile(ans)
s = QWebEngineScript()
s.setName('csslint.js')
s.setSourceCode(csslint_js())
s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
ans.scripts().insert(s)
for (name, code) in stylelint_js():
s = QWebEngineScript()
s.setName(name)
s.setSourceCode(code)
s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
ans.scripts().insert(s)
return ans
@ -134,22 +97,23 @@ class Worker(QWebEnginePage):
QWebEnginePage.__init__(self, create_profile(), QApplication.instance())
self.titleChanged.connect(self.title_changed)
secure_webengine(self.settings())
self.console_messages = []
self.ready = False
self.working = False
self.pending = None
self.setHtml('')
def title_changed(self, new_title):
new_title = new_title.partition(':')[0]
if new_title == 'ready':
self.ready = True
if self.pending is not None:
self.check_css(self.pending)
self.pending = None
elif new_title == 'checked':
self.runJavaScript('window.get_css_results()', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done)
def javaScriptConsoleMessage(self, level, msg, lineno, source_id):
msg = f'{source_id}:{lineno}:{msg}'
self.console_messages.append(msg)
try:
print(msg)
except Exception:
@ -157,9 +121,8 @@ class Worker(QWebEnginePage):
def check_css(self, src):
self.working = True
self.console_messages = []
self.runJavaScript(
f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done)
f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld)
def check_css_when_ready(self, src):
if self.ready:
@ -168,9 +131,10 @@ class Worker(QWebEnginePage):
self.working = True
self.pending = src
def check_done(self, result):
def check_done(self, results):
self.working = False
self.work_done.emit(self, result)
for result in results:
self.work_done.emit(self, result)
class Pool:
@ -208,11 +172,9 @@ class Pool:
break
def work_done(self, worker, result):
if not isinstance(result, dict):
result = worker.console_messages
self.results[worker.result_idx] = result
self.assign_work()
if not self.pending and not [w for w in self.workers if w.working]:
if not self.pending and not any(w for w in self.workers if w.working):
self.working = False
def shutdown(self):
@ -245,16 +207,16 @@ def check_css(jobs):
return errors
results = pool.check_css([j.css for j in jobs])
for job, result in zip(jobs, results):
if isinstance(result, dict):
for msg in result['messages']:
err = message_to_error(msg, job.name, job.line_offset)
if err is not None:
errors.append(err)
elif isinstance(result, list) and result:
if result['type'] == 'error':
errors.append(CSSParseError(_('Failed to process CSS in {name} with errors: {errors}').format(
name=job.name, errors='\n'.join(result)), job.name))
else:
errors.append(CSSParseError(_('Failed to process CSS in {name}').format(name=job.name), job.name))
name=job.name, errors=result['error']), job.name))
continue
result = json.loads(result['results']['output'])
rule_metadata = result['rule_metadata']
for msg in result['results']['warnings']:
err = message_to_error(msg, job.name, job.line_offset, rule_metadata)
if err is not None:
errors.append(err)
return errors