mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
036bbdb7ff
commit
c7a61b2942
10898
resources/csslint.js
10898
resources/csslint.js
File diff suppressed because it is too large
Load Diff
17
resources/stylelint-bundle.min.js
vendored
Normal file
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
88
resources/stylelint.js
Normal 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();
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ __all__ = [
|
|||||||
'upload_user_manual', 'upload_demo', 'reupload',
|
'upload_user_manual', 'upload_demo', 'reupload',
|
||||||
'stage1', 'stage2', 'stage3', 'stage4', 'stage5', 'publish', 'publish_betas',
|
'stage1', 'stage2', 'stage3', 'stage4', 'stage5', 'publish', 'publish_betas',
|
||||||
'linux', 'linux64', 'linuxarm64', 'win', 'win64', 'osx', 'build_dep',
|
'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
|
from setup.installers import Linux, Win, OSX, Linux64, LinuxArm64, Win64, ExtDev, BuildDep, ExportPackages
|
||||||
@ -39,7 +39,7 @@ iso639 = ISO639()
|
|||||||
iso3166 = ISO3166()
|
iso3166 = ISO3166()
|
||||||
|
|
||||||
from setup.csslint import CSSLint
|
from setup.csslint import CSSLint
|
||||||
csslint = CSSLint()
|
stylelint = CSSLint()
|
||||||
|
|
||||||
from setup.build import Build
|
from setup.build import Build
|
||||||
build = Build()
|
build = Build()
|
||||||
|
@ -9,12 +9,9 @@ from setup import Command
|
|||||||
|
|
||||||
|
|
||||||
class CSSLint(Command):
|
class CSSLint(Command):
|
||||||
# We can't use the released copy since it has not had a release in years and
|
description = 'Update the bundled copy of stylelint'
|
||||||
# there are several critical bug fixes we need
|
NAME = 'stylelint-bundle.min.js'
|
||||||
|
DOWNLOAD_URL = 'https://github.com/openstyles/stylelint-bundle.git'
|
||||||
description = 'Update the bundled copy of csslint'
|
|
||||||
NAME = 'csslint.js'
|
|
||||||
DOWNLOAD_URL = 'https://github.com/CSSLint/csslint.git'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vendored_file(self):
|
def vendored_file(self):
|
||||||
@ -25,8 +22,9 @@ class CSSLint(Command):
|
|||||||
|
|
||||||
with self.temp_dir() as dl_src:
|
with self.temp_dir() as dl_src:
|
||||||
subprocess.check_call(['git', 'clone', '--depth=1', self.DOWNLOAD_URL], cwd=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', 'install'], cwd=src)
|
||||||
|
subprocess.check_call(['npm', 'run', 'build'], cwd=src)
|
||||||
shutil.copyfile(self.j(src, 'dist', self.NAME), self.vendored_file)
|
shutil.copyfile(self.j(src, 'dist', self.NAME), self.vendored_file)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -41,74 +41,36 @@ def as_int_or_none(x):
|
|||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
def message_to_error(message, name, line_offset=0):
|
def message_to_error(message, name, line_offset, rule_metadata):
|
||||||
rule = message.get('rule', {})
|
rule_id = message.get('rule')
|
||||||
rule_id = rule.get('id') or ''
|
if rule_id == 'CssSyntaxError':
|
||||||
cls = CSSWarning
|
cls = CSSParseError
|
||||||
if message.get('type') == 'error':
|
else:
|
||||||
cls = CSSParseError if rule.get('name') == 'Parsing Errors' else CSSError
|
cls = CSSError if message.get('severity') == 'error' else CSSWarning
|
||||||
title = message.get('message') or _('Unknown error')
|
title = message.get('text') or _('Unknown error')
|
||||||
|
title = title.rpartition('(')[0].strip()
|
||||||
line = as_int_or_none(message.get('line'))
|
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:
|
if col is not None:
|
||||||
col -= 1
|
col -= 1
|
||||||
if line is not None:
|
if line is not None:
|
||||||
line += line_offset
|
line += line_offset
|
||||||
ans = cls(title, name, line, col)
|
ans = cls(title, name, line, col)
|
||||||
ans.HELP = rule.get('desc') or ''
|
ans.HELP = message.get('text') or ''
|
||||||
ans.css_rule_id = rule_id
|
ans.css_rule_id = rule_id
|
||||||
if ans.HELP and 'url' in rule:
|
m = rule_metadata.get(rule_id)
|
||||||
ans.HELP += ' ' + _('See <a href="{}">detailed description</a>.').format(rule['url'])
|
if m and 'url' in m:
|
||||||
|
ans.HELP += '. ' + _('See <a href="{}">detailed description</a>.').format(m['url'])
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def csslint_js():
|
def stylelint_js():
|
||||||
ans = getattr(csslint_js, 'ans', None)
|
ans = getattr(stylelint_js, 'ans', None)
|
||||||
if ans is None:
|
if ans is None:
|
||||||
ans = csslint_js.ans = P('csslint.js', data=True, allow_user_override=False).decode('utf-8') + '''
|
ans = stylelint_js.ans = (
|
||||||
|
('stylelint-bundle.min.js', P('stylelint-bundle.min.js', data=True, allow_user_override=False).decode('utf-8')),
|
||||||
window.check_css = function(src) {
|
('stylelint.js', P('stylelint.js', data=True, allow_user_override=False).decode('utf-8')),
|
||||||
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';
|
|
||||||
'''
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@ -117,11 +79,12 @@ def create_profile():
|
|||||||
if ans is None:
|
if ans is None:
|
||||||
ans = create_profile.ans = QWebEngineProfile(QApplication.instance())
|
ans = create_profile.ans = QWebEngineProfile(QApplication.instance())
|
||||||
setup_profile(ans)
|
setup_profile(ans)
|
||||||
s = QWebEngineScript()
|
for (name, code) in stylelint_js():
|
||||||
s.setName('csslint.js')
|
s = QWebEngineScript()
|
||||||
s.setSourceCode(csslint_js())
|
s.setName(name)
|
||||||
s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
|
s.setSourceCode(code)
|
||||||
ans.scripts().insert(s)
|
s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
|
||||||
|
ans.scripts().insert(s)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@ -134,22 +97,23 @@ class Worker(QWebEnginePage):
|
|||||||
QWebEnginePage.__init__(self, create_profile(), QApplication.instance())
|
QWebEnginePage.__init__(self, create_profile(), QApplication.instance())
|
||||||
self.titleChanged.connect(self.title_changed)
|
self.titleChanged.connect(self.title_changed)
|
||||||
secure_webengine(self.settings())
|
secure_webengine(self.settings())
|
||||||
self.console_messages = []
|
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.working = False
|
self.working = False
|
||||||
self.pending = None
|
self.pending = None
|
||||||
self.setHtml('')
|
self.setHtml('')
|
||||||
|
|
||||||
def title_changed(self, new_title):
|
def title_changed(self, new_title):
|
||||||
|
new_title = new_title.partition(':')[0]
|
||||||
if new_title == 'ready':
|
if new_title == 'ready':
|
||||||
self.ready = True
|
self.ready = True
|
||||||
if self.pending is not None:
|
if self.pending is not None:
|
||||||
self.check_css(self.pending)
|
self.check_css(self.pending)
|
||||||
self.pending = None
|
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):
|
def javaScriptConsoleMessage(self, level, msg, lineno, source_id):
|
||||||
msg = f'{source_id}:{lineno}:{msg}'
|
msg = f'{source_id}:{lineno}:{msg}'
|
||||||
self.console_messages.append(msg)
|
|
||||||
try:
|
try:
|
||||||
print(msg)
|
print(msg)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -157,9 +121,8 @@ class Worker(QWebEnginePage):
|
|||||||
|
|
||||||
def check_css(self, src):
|
def check_css(self, src):
|
||||||
self.working = True
|
self.working = True
|
||||||
self.console_messages = []
|
|
||||||
self.runJavaScript(
|
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):
|
def check_css_when_ready(self, src):
|
||||||
if self.ready:
|
if self.ready:
|
||||||
@ -168,9 +131,10 @@ class Worker(QWebEnginePage):
|
|||||||
self.working = True
|
self.working = True
|
||||||
self.pending = src
|
self.pending = src
|
||||||
|
|
||||||
def check_done(self, result):
|
def check_done(self, results):
|
||||||
self.working = False
|
self.working = False
|
||||||
self.work_done.emit(self, result)
|
for result in results:
|
||||||
|
self.work_done.emit(self, result)
|
||||||
|
|
||||||
|
|
||||||
class Pool:
|
class Pool:
|
||||||
@ -208,11 +172,9 @@ class Pool:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def work_done(self, worker, result):
|
def work_done(self, worker, result):
|
||||||
if not isinstance(result, dict):
|
|
||||||
result = worker.console_messages
|
|
||||||
self.results[worker.result_idx] = result
|
self.results[worker.result_idx] = result
|
||||||
self.assign_work()
|
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
|
self.working = False
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
@ -245,16 +207,16 @@ def check_css(jobs):
|
|||||||
return errors
|
return errors
|
||||||
results = pool.check_css([j.css for j in jobs])
|
results = pool.check_css([j.css for j in jobs])
|
||||||
for job, result in zip(jobs, results):
|
for job, result in zip(jobs, results):
|
||||||
if isinstance(result, dict):
|
if result['type'] == 'error':
|
||||||
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:
|
|
||||||
errors.append(CSSParseError(_('Failed to process CSS in {name} with errors: {errors}').format(
|
errors.append(CSSParseError(_('Failed to process CSS in {name} with errors: {errors}').format(
|
||||||
name=job.name, errors='\n'.join(result)), job.name))
|
name=job.name, errors=result['error']), job.name))
|
||||||
else:
|
continue
|
||||||
errors.append(CSSParseError(_('Failed to process CSS in {name}').format(name=job.name), job.name))
|
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
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user