csslint: allow globals like @import inside sections

This commit is contained in:
tophf 2021-04-28 17:33:27 +03:00
parent be4fd17113
commit d736a00bc1
3 changed files with 50 additions and 32 deletions

View File

@ -131,6 +131,7 @@ linterMan.DEFAULTS = {
'duplicate-properties': 1,
'empty-rules': 1,
'errors': 1,
'globals-in-document': 1,
'known-properties': 1,
'selector-newline': 1,
'shorthand-overrides': 1,

View File

@ -1003,6 +1003,26 @@ CSSLint.addRule['font-sizes'] = [{
});
}];
CSSLint.addRule['globals-in-document'] = [{
name: 'Warn about global @ rules inside @-moz-document',
desc: 'Warn about @import, @charset, @namespace inside @-moz-document',
browsers: 'All',
}, (rule, parser, reporter) => {
let level = 0;
let index = 0;
parser.addListener('startdocument', () => level++);
parser.addListener('enddocument', () => level-- * index++);
const check = event => {
if (level && index) {
reporter.report(`A nested @${event.type} is valid only if this @-moz-document section ` +
'is the first one matched for any given URL.', event, rule);
}
};
parser.addListener('import', check);
parser.addListener('charset', check);
parser.addListener('namespace', check);
}];
CSSLint.addRule['gradients'] = [{
name: 'Require all gradient definitions',
desc: 'When using a vendor-prefixed gradient, make sure to use them all.',

View File

@ -3439,31 +3439,16 @@ self.parserlib = (() => {
_stylesheet() {
const stream = this._tokenStream;
this.fire('startstylesheet');
this._skipCruft();
for (const [type, fn, max = Infinity] of [
[Tokens.CHARSET_SYM, this._charset, 1],
[Tokens.IMPORT_SYM, this._import],
[Tokens.NAMESPACE_SYM, this._namespace],
]) {
for (let i = 0; i++ < max && stream.peek() === type;) {
fn.call(this, stream.get(true));
this._skipCruft();
}
}
this._sheetGlobals();
const {topDocOnly} = this.options;
const allowedActions = topDocOnly ? Parser.ACTIONS.topDoc : Parser.ACTIONS.stylesheet;
for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) {
try {
let action = allowedActions.get(tt);
const action = allowedActions.get(tt);
if (action) {
action.call(this, token);
continue;
}
action = Parser.ACTIONS.stylesheetMisplaced.get(tt);
if (action) {
action.call(this, token, true);
throw new SyntaxError(Tokens[tt].text + ' not allowed here.', token);
}
if (topDocOnly) {
stream.readDeclValue({stopOn: '{}'});
if (stream._reader.peek() === '{') {
@ -3489,25 +3474,40 @@ self.parserlib = (() => {
this.fire('endstylesheet');
}
_charset(start, misplaced) {
_sheetGlobals() {
const stream = this._tokenStream;
this._skipCruft();
for (const [type, fn, max = Infinity] of [
[Tokens.CHARSET_SYM, this._charset, 1],
[Tokens.IMPORT_SYM, this._import],
[Tokens.NAMESPACE_SYM, this._namespace],
]) {
for (let i = 0; i++ < max && stream.peek() === type;) {
fn.call(this, stream.get(true));
this._skipCruft();
}
}
}
_charset(start) {
const stream = this._tokenStream;
const charset = stream.mustMatch(Tokens.STRING).value;
stream.mustMatch(Tokens.SEMICOLON);
if (!misplaced) this.fire({type: 'charset', charset}, start);
this.fire({type: 'charset', charset}, start);
}
_import(start, misplaced) {
_import(start) {
const stream = this._tokenStream;
const token = stream.mustMatch(TT.stringUri);
const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
this._ws();
const media = this._mediaQueryList();
stream.mustMatch(Tokens.SEMICOLON);
if (!misplaced) this.fire({type: 'import', media, uri}, start);
this.fire({type: 'import', media, uri}, start);
this._ws();
}
_namespace(start, misplaced) {
_namespace(start) {
const stream = this._tokenStream;
this._ws();
const prefix = stream.match(Tokens.IDENT).value;
@ -3515,16 +3515,16 @@ self.parserlib = (() => {
const token = stream.mustMatch(TT.stringUri);
const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
stream.mustMatch(Tokens.SEMICOLON);
if (!misplaced) this.fire({type: 'namespace', prefix, uri}, start);
this.fire({type: 'namespace', prefix, uri}, start);
this._ws();
}
_supports(start, misplaced) {
_supports(start) {
const stream = this._tokenStream;
this._ws();
this._supportsCondition();
stream.mustMatch(Tokens.LBRACE);
if (!misplaced) this.fire('startsupports', start);
this.fire('startsupports', start);
this._ws();
for (;; stream.skipComment()) {
const action = Parser.ACTIONS.supports.get(stream.peek());
@ -3535,7 +3535,7 @@ self.parserlib = (() => {
}
}
stream.mustMatch(Tokens.RBRACE);
if (!misplaced) this.fire('endsupports');
this.fire('endsupports');
this._ws();
}
@ -3747,6 +3747,9 @@ self.parserlib = (() => {
if (this.options.topDocOnly) {
stream.readDeclValue({stopOn: '}'});
} else {
/* We allow @import and such inside document sections because the final generated CSS for
* a given page may be valid e.g. if this section is the first one that matched the URL */
this._sheetGlobals();
this._ws();
let action;
do action = Parser.ACTIONS.document.get(stream.peek());
@ -4528,12 +4531,6 @@ self.parserlib = (() => {
[Tokens.S, Parser.prototype._ws],
]),
stylesheetMisplaced: new Map([
[Tokens.CHARSET_SYM, Parser.prototype._charset],
[Tokens.IMPORT_SYM, Parser.prototype._import],
[Tokens.NAMESPACE_SYM, Parser.prototype._namespace],
]),
topDoc: new Map([
symDocument,
symUnknown,