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, 'duplicate-properties': 1,
'empty-rules': 1, 'empty-rules': 1,
'errors': 1, 'errors': 1,
'globals-in-document': 1,
'known-properties': 1, 'known-properties': 1,
'selector-newline': 1, 'selector-newline': 1,
'shorthand-overrides': 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'] = [{ CSSLint.addRule['gradients'] = [{
name: 'Require all gradient definitions', name: 'Require all gradient definitions',
desc: 'When using a vendor-prefixed gradient, make sure to use them all.', desc: 'When using a vendor-prefixed gradient, make sure to use them all.',

View File

@ -3439,31 +3439,16 @@ self.parserlib = (() => {
_stylesheet() { _stylesheet() {
const stream = this._tokenStream; const stream = this._tokenStream;
this.fire('startstylesheet'); this.fire('startstylesheet');
this._skipCruft(); this._sheetGlobals();
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();
}
}
const {topDocOnly} = this.options; const {topDocOnly} = this.options;
const allowedActions = topDocOnly ? Parser.ACTIONS.topDoc : Parser.ACTIONS.stylesheet; const allowedActions = topDocOnly ? Parser.ACTIONS.topDoc : Parser.ACTIONS.stylesheet;
for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) { for (let tt, token; (tt = (token = stream.get(true)).type); this._skipCruft()) {
try { try {
let action = allowedActions.get(tt); const action = allowedActions.get(tt);
if (action) { if (action) {
action.call(this, token); action.call(this, token);
continue; 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) { if (topDocOnly) {
stream.readDeclValue({stopOn: '{}'}); stream.readDeclValue({stopOn: '{}'});
if (stream._reader.peek() === '{') { if (stream._reader.peek() === '{') {
@ -3489,25 +3474,40 @@ self.parserlib = (() => {
this.fire('endstylesheet'); 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 stream = this._tokenStream;
const charset = stream.mustMatch(Tokens.STRING).value; const charset = stream.mustMatch(Tokens.STRING).value;
stream.mustMatch(Tokens.SEMICOLON); 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 stream = this._tokenStream;
const token = stream.mustMatch(TT.stringUri); const token = stream.mustMatch(TT.stringUri);
const uri = token.uri || token.value.replace(/^["']|["']$/g, ''); const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
this._ws(); this._ws();
const media = this._mediaQueryList(); const media = this._mediaQueryList();
stream.mustMatch(Tokens.SEMICOLON); stream.mustMatch(Tokens.SEMICOLON);
if (!misplaced) this.fire({type: 'import', media, uri}, start); this.fire({type: 'import', media, uri}, start);
this._ws(); this._ws();
} }
_namespace(start, misplaced) { _namespace(start) {
const stream = this._tokenStream; const stream = this._tokenStream;
this._ws(); this._ws();
const prefix = stream.match(Tokens.IDENT).value; const prefix = stream.match(Tokens.IDENT).value;
@ -3515,16 +3515,16 @@ self.parserlib = (() => {
const token = stream.mustMatch(TT.stringUri); const token = stream.mustMatch(TT.stringUri);
const uri = token.uri || token.value.replace(/^["']|["']$/g, ''); const uri = token.uri || token.value.replace(/^["']|["']$/g, '');
stream.mustMatch(Tokens.SEMICOLON); stream.mustMatch(Tokens.SEMICOLON);
if (!misplaced) this.fire({type: 'namespace', prefix, uri}, start); this.fire({type: 'namespace', prefix, uri}, start);
this._ws(); this._ws();
} }
_supports(start, misplaced) { _supports(start) {
const stream = this._tokenStream; const stream = this._tokenStream;
this._ws(); this._ws();
this._supportsCondition(); this._supportsCondition();
stream.mustMatch(Tokens.LBRACE); stream.mustMatch(Tokens.LBRACE);
if (!misplaced) this.fire('startsupports', start); this.fire('startsupports', start);
this._ws(); this._ws();
for (;; stream.skipComment()) { for (;; stream.skipComment()) {
const action = Parser.ACTIONS.supports.get(stream.peek()); const action = Parser.ACTIONS.supports.get(stream.peek());
@ -3535,7 +3535,7 @@ self.parserlib = (() => {
} }
} }
stream.mustMatch(Tokens.RBRACE); stream.mustMatch(Tokens.RBRACE);
if (!misplaced) this.fire('endsupports'); this.fire('endsupports');
this._ws(); this._ws();
} }
@ -3747,6 +3747,9 @@ self.parserlib = (() => {
if (this.options.topDocOnly) { if (this.options.topDocOnly) {
stream.readDeclValue({stopOn: '}'}); stream.readDeclValue({stopOn: '}'});
} else { } 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(); this._ws();
let action; let action;
do action = Parser.ACTIONS.document.get(stream.peek()); do action = Parser.ACTIONS.document.get(stream.peek());
@ -4528,12 +4531,6 @@ self.parserlib = (() => {
[Tokens.S, Parser.prototype._ws], [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([ topDoc: new Map([
symDocument, symDocument,
symUnknown, symUnknown,