csslint: add 'shorthand-overrides' rule
This commit is contained in:
parent
cb85fe9392
commit
57233db546
|
@ -133,6 +133,7 @@ linterMan.DEFAULTS = {
|
||||||
'errors': 1,
|
'errors': 1,
|
||||||
'known-properties': 1,
|
'known-properties': 1,
|
||||||
'selector-newline': 1,
|
'selector-newline': 1,
|
||||||
|
'shorthand-overrides': 1,
|
||||||
'simple-not': 1,
|
'simple-not': 1,
|
||||||
'warnings': 1,
|
'warnings': 1,
|
||||||
// disabled
|
// disabled
|
||||||
|
|
|
@ -344,6 +344,102 @@ CSSLint.Util = {
|
||||||
}
|
}
|
||||||
if (property) parser.addListener('property', property);
|
if (property) parser.addListener('property', property);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
registerShorthandEvents(parser, {property, endRule}) {
|
||||||
|
const {shorthands, shorthandsFor} = CSSLint.Util;
|
||||||
|
let props, inRule;
|
||||||
|
parser.addListener('startrule', onStartRule);
|
||||||
|
parser.addListener('startfontface', onStartRule);
|
||||||
|
parser.addListener('property', onProperty);
|
||||||
|
parser.addListener('endrule', onEndRule);
|
||||||
|
parser.addListener('endfontface', onEndRule);
|
||||||
|
function onStartRule() {
|
||||||
|
inRule = true;
|
||||||
|
props = null;
|
||||||
|
}
|
||||||
|
function onProperty(event) {
|
||||||
|
if (!inRule) return;
|
||||||
|
const name = event.property.text.toLowerCase();
|
||||||
|
const sh = shorthandsFor[name];
|
||||||
|
if (sh) {
|
||||||
|
if (!props) props = {};
|
||||||
|
(props[sh] || (props[sh] = {}))[name] = event;
|
||||||
|
} else if (property && props && name in shorthands) {
|
||||||
|
property(event, props, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onEndRule(event) {
|
||||||
|
inRule = false;
|
||||||
|
if (endRule && props) {
|
||||||
|
endRule(event, props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get shorthands() {
|
||||||
|
const WSC = 'width|style|color';
|
||||||
|
const TBLR = 'top|bottom|left|right';
|
||||||
|
const shorthands = Object.create(null);
|
||||||
|
const shorthandsFor = Object.create(null);
|
||||||
|
for (const [sh, pattern, ...args] of [
|
||||||
|
['animation', '%-1',
|
||||||
|
'name|duration|timing-function|delay|iteration-count|direction|fill-mode|play-state'],
|
||||||
|
['background', '%-1', 'image|size|position|repeat|origin|clip|attachment|color'],
|
||||||
|
['border', '%-1-2', TBLR, WSC],
|
||||||
|
['border-top', '%-1', WSC],
|
||||||
|
['border-left', '%-1', WSC],
|
||||||
|
['border-right', '%-1', WSC],
|
||||||
|
['border-bottom', '%-1', WSC],
|
||||||
|
['border-block-end', '%-1', WSC],
|
||||||
|
['border-block-start', '%-1', WSC],
|
||||||
|
['border-image', '%-1', 'source|slice|width|outset|repeat'],
|
||||||
|
['border-inline-end', '%-1', WSC],
|
||||||
|
['border-inline-start', '%-1', WSC],
|
||||||
|
['border-radius', 'border-1-2-radius', 'top|bottom', 'left|right'],
|
||||||
|
['border-color', 'border-1-color', TBLR],
|
||||||
|
['border-style', 'border-1-style', TBLR],
|
||||||
|
['border-width', 'border-1-width', TBLR],
|
||||||
|
['column-rule', '%-1', WSC],
|
||||||
|
['columns', 'column-1', 'width|count'],
|
||||||
|
['flex', '%-1', 'grow|shrink|basis'],
|
||||||
|
['flex-flow', 'flex-1', 'direction|wrap'],
|
||||||
|
['font', '%-style|%-variant|%-weight|%-stretch|%-size|%-family|line-height'],
|
||||||
|
['grid', '%-1',
|
||||||
|
'template-rows|template-columns|template-areas|' +
|
||||||
|
'auto-rows|auto-columns|auto-flow|column-gap|row-gap'],
|
||||||
|
['grid-area', 'grid-1-2', 'row|column', 'start|end'],
|
||||||
|
['grid-column', '%-1', 'start|end'],
|
||||||
|
['grid-gap', 'grid-1-gap', 'row|column'],
|
||||||
|
['grid-row', '%-1', 'start|end'],
|
||||||
|
['grid-template', '%-1', 'columns|rows|areas'],
|
||||||
|
['list-style', 'list-1', 'type|position|image'],
|
||||||
|
['margin', '%-1', TBLR],
|
||||||
|
['mask', '%-1', 'image|mode|position|size|repeat|origin|clip|composite'],
|
||||||
|
['outline', '%-1', WSC],
|
||||||
|
['padding', '%-1', TBLR],
|
||||||
|
['text-decoration', '%-1', 'color|style|line'],
|
||||||
|
['text-emphasis', '%-1', 'style|color'],
|
||||||
|
['transition', '%-1', 'delay|duration|property|timing-function'],
|
||||||
|
]) {
|
||||||
|
let res = pattern.replace(/%/g, sh);
|
||||||
|
args.forEach((arg, i) => {
|
||||||
|
res = arg.replace(/[^|]+/g, res.replace(new RegExp(`${i + 1}`, 'g'), '$$&'));
|
||||||
|
});
|
||||||
|
(shorthands[sh] = res.split('|')).forEach(r => {
|
||||||
|
shorthandsFor[r] = sh;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Object.defineProperties(CSSLint.Util, {
|
||||||
|
shorthands: {value: shorthands},
|
||||||
|
shorthandsFor: {value: shorthandsFor},
|
||||||
|
});
|
||||||
|
return shorthands;
|
||||||
|
},
|
||||||
|
|
||||||
|
get shorthandsFor() {
|
||||||
|
return CSSLint.Util.shorthandsFor ||
|
||||||
|
CSSLint.Util.shorthands && CSSLint.Util.shorthandsFor;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
@ -1428,50 +1524,39 @@ CSSLint.addRule({
|
||||||
browsers: 'All',
|
browsers: 'All',
|
||||||
|
|
||||||
init(parser, reporter) {
|
init(parser, reporter) {
|
||||||
const propertiesToCheck = {};
|
const {shorthands} = CSSLint.Util;
|
||||||
const mapping = {
|
CSSLint.Util.registerShorthandEvents(parser, {
|
||||||
margin: ['margin-top', 'margin-bottom', 'margin-left', 'margin-right'],
|
endRule: (event, props) => {
|
||||||
padding: ['padding-top', 'padding-bottom', 'padding-left', 'padding-right'],
|
for (const [sh, events] of Object.entries(props)) {
|
||||||
};
|
const names = Object.keys(events);
|
||||||
let properties;
|
if (names.length === shorthands[sh].length) {
|
||||||
let started = 0;
|
const msg = `'${sh}' shorthand can replace '${names.join("' + '")}'`;
|
||||||
|
names.forEach(n => reporter.report(msg, events[n].line, events[n].col, this));
|
||||||
for (const short in mapping) {
|
|
||||||
for (const full of mapping[short]) {
|
|
||||||
propertiesToCheck[full] = short;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const startRule = () => {
|
CSSLint.addRule({
|
||||||
started = 1;
|
id: 'shorthand-overrides',
|
||||||
properties = {};
|
name: 'Avoid shorthands that override individual properties',
|
||||||
};
|
desc: 'Avoid shorthands like `background: foo` that follow individual properties ' +
|
||||||
|
'like `background-image: bar` thus overriding them',
|
||||||
|
browsers: 'All',
|
||||||
|
|
||||||
const property = event => {
|
init(parser, reporter) {
|
||||||
if (!started) return;
|
CSSLint.Util.registerShorthandEvents(parser, {
|
||||||
const name = event.property.toString().toLowerCase();
|
property: (event, props, name) => {
|
||||||
if (name in propertiesToCheck) {
|
const ovr = props[name];
|
||||||
properties[name] = 1;
|
if (ovr) {
|
||||||
}
|
delete props[name];
|
||||||
};
|
reporter.report(`'${event.property}' overrides '${Object.keys(ovr).join("', '")}' above.`,
|
||||||
|
|
||||||
const endRule = event => {
|
|
||||||
started = 0;
|
|
||||||
for (const short in mapping) {
|
|
||||||
const fullList = mapping[short];
|
|
||||||
const total = fullList.reduce((sum = 0, name) => sum + (properties[name] ? 1 : 0));
|
|
||||||
if (total === fullList.length) {
|
|
||||||
reporter.report(`The properties ${fullList.join(', ')} can be replaced by ${short}.`,
|
|
||||||
event.line, event.col, this);
|
event.line, event.col, this);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
parser.addListener('startrule', startRule);
|
|
||||||
parser.addListener('startfontface', startRule);
|
|
||||||
parser.addListener('property', property);
|
|
||||||
parser.addListener('endrule', endRule);
|
|
||||||
parser.addListener('endfontface', endRule);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user