diff --git a/js/csslint/csslint.js b/js/csslint/csslint.js index 1e852afb..012e66d7 100644 --- a/js/csslint/csslint.js +++ b/js/csslint/csslint.js @@ -1145,76 +1145,85 @@ CSSLint.addRule['known-properties'] = [{ }]; CSSLint.addRule['known-pseudos'] = [{ - name: 'Require use of known pseudo selectors except when vendor-prefixed', + name: 'Require use of known pseudo selectors', url: 'https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements', browsers: 'All', - _data: { - // 1 = requires ":" - // 2 = requires "::" - // 1+2 = allows both ":" and "::" - // 4 = requires "(" - // 8 = allows both "(" and bare name - 'active': 1, - 'after': 1 + 2, - 'any-link': 1, - 'autofill': 1, +}, (rule, parser, reporter) => { + // 1 = requires ":" + // 2 = requires "::" + const Func = 4; // must be :function() + const FuncToo = 8; // both :function() and :non-function + const WK = 0x10; + const Moz = 0x20; + const definitions = { + // elements + 'after': 1 + 2, // also allows ":" 'backdrop': 2, - 'before': 1 + 2, - 'blank': 1, - 'checked': 1, + 'before': 1 + 2, // also allows ":" 'cue': 2, 'cue-region': 2, - 'current': 1 + 8, + 'file-selector-button': 2, + 'first-letter': 1 + 2, // also allows ":" + 'first-line': 1 + 2, // also allows ":" + 'grammar-error': 2, + 'highlight': 2 + Func, + 'marker': 2, + 'part': 2 + Func, + 'placeholder': 2 + Moz, + 'selection': 2 + Moz, + 'slotted': 2 + Func, + 'spelling-error': 2, + 'target-text': 2, + // classes + 'active': 1, + 'any-link': 1 + Moz + WK, + 'autofill': 1 + WK, + 'blank': 1, + 'checked': 1, + 'current': 1 + FuncToo, 'default': 1, 'defined': 1, - 'dir': 1 + 4, + 'dir': 1 + Func, 'disabled': 1, 'drop': 1, 'empty': 1, 'enabled': 1, - 'file-selector-button': 2, 'first': 1, 'first-child': 1, - 'first-letter': 1 + 2, - 'first-line': 1 + 2, 'first-of-type': 1, 'focus': 1, 'focus-visible': 1, 'focus-within': 1, 'fullscreen': 1, 'future': 1, - 'grammar-error': 2, - 'has': 1 + 4, - 'host': 1 + 8, - 'host-context': 1 + 4, + 'has': 1 + Func, + 'host': 1 + FuncToo, + 'host-context': 1 + Func, 'hover': 1, 'in-range': 1, 'indeterminate': 1, 'invalid': 1, - 'is': 1 + 4, - 'lang': 1 + 4, + 'is': 1 + Func, + 'lang': 1 + Func, 'last-child': 1, 'last-of-type': 1, 'left': 1, 'link': 1, 'local-link': 1, - 'marker': 2, - 'not': 1 + 4, - 'nth-child': 1 + 4, - 'nth-col': 1 + 4, - 'nth-last-child': 1 + 4, - 'nth-last-col': 1 + 4, - 'nth-last-of-type': 1 + 4, - 'nth-of-type': 1 + 4, + 'not': 1 + Func, + 'nth-child': 1 + Func, + 'nth-col': 1 + Func, + 'nth-last-child': 1 + Func, + 'nth-last-col': 1 + Func, + 'nth-last-of-type': 1 + Func, + 'nth-of-type': 1 + Func, 'only-child': 1, 'only-of-type': 1, 'optional': 1, 'out-of-range': 1, - 'part': 2 + 4, 'past': 1, 'paused': 1, 'picture-in-picture': 1, - 'placeholder': 2, 'placeholder-shown': 1, 'playing': 1, 'read-only': 1, @@ -1223,47 +1232,129 @@ CSSLint.addRule['known-pseudos'] = [{ 'right': 1, 'root': 1, 'scope': 1, - 'selection': 2, - 'slotted': 2 + 4, - 'spelling-error': 2, - 'state': 1 + 4, + 'state': 1 + Func, 'target': 1, - 'target-text': 2, 'target-within': 1, 'user-invalid': 1, 'valid': 1, 'visited': 1, - 'where': 1 + 4, - // used with ::-webkit-scrollbar selectors + 'where': 1 + Func, + 'xr-overlay': 1, + // ::-webkit-scrollbar specific classes + 'corner-present': 1, 'decrement': 1, + 'double-button': 1, + 'end': 1, 'horizontal': 1, 'increment': 1, + 'no-button': 1, 'single-button': 1, + 'start': 1, 'vertical': 1, - }, -}, (rule, parser, reporter) => { - const definitions = rule._data; - const rxColons = /^:+/; - const rxPseudoVendorPrefix = /^(::?)-(webkit|moz|ms|o)-/i; + 'window-inactive': 1 + Moz, + }; + const definitionsPrefixed = { + 'any': 1 + Func + Moz + WK, + 'calendar-picker-indicator': 2 + WK, + 'clear-button': 2 + WK, + 'color-swatch': 2 + WK, + 'color-swatch-wrapper': 2 + WK, + 'date-and-time-value': 2 + WK, + 'datetime-edit': 2 + WK, + 'datetime-edit-ampm-field': 2 + WK, + 'datetime-edit-day-field': 2 + WK, + 'datetime-edit-fields-wrapper': 2 + WK, + 'datetime-edit-hour-field': 2 + WK, + 'datetime-edit-millisecond-field': 2 + WK, + 'datetime-edit-minute-field': 2 + WK, + 'datetime-edit-month-field': 2 + WK, + 'datetime-edit-second-field': 2 + WK, + 'datetime-edit-text': 2 + WK, + 'datetime-edit-week-field': 2 + WK, + 'datetime-edit-year-field': 2 + WK, + 'drag': 1 + WK, + 'drag-over': 1 + Moz, + 'file-upload-button': 2 + WK, + 'focus-inner': 2 + Moz, + 'focusring': 1 + Moz, + 'full-page-media': 1 + WK, + 'full-screen': 1 + Moz + WK, + 'full-screen-ancestor': 1 + Moz + WK, + 'inner-spin-button': 2 + WK, + 'input-placeholder': 1 + 2 + WK + Moz, + 'loading': 1 + Moz, + 'media-controls': 2 + WK, + 'media-controls-current-time-display': 2 + WK, + 'media-controls-enclosure': 2 + WK, + 'media-controls-fullscreen-button': 2 + WK, + 'media-controls-mute-button': 2 + WK, + 'media-controls-overlay-enclosure': 2 + WK, + 'media-controls-overlay-play-button': 2 + WK, + 'media-controls-panel': 2 + WK, + 'media-controls-play-button': 2 + WK, + 'media-controls-time-remaining-display': 2 + WK, + 'media-controls-timeline': 2 + WK, + 'media-controls-timeline-container': 2 + WK, + 'media-controls-toggle-closed-captions-button': 2 + WK, + 'media-controls-volume-slider': 2 + WK, + 'media-slider-container': 2 + WK, + 'media-slider-thumb': 2 + WK, + 'media-text-track-container': 2 + WK, + 'media-text-track-display': 2 + WK, + 'media-text-track-region': 2 + WK, + 'media-text-track-region-container': 2 + WK, + 'meter-bar': 2 + WK, + 'meter-even-less-good-value': 2 + WK, + 'meter-inner-element': 2 + WK, + 'meter-optimum-value': 2 + WK, + 'meter-suboptimum-value': 2 + WK, + 'progress-bar': 2 + WK, + 'progress-inner-element': 2 + WK, + 'progress-value': 2 + WK, + 'resizer': 2 + WK, + 'scrollbar': 2 + WK, + 'scrollbar-button': 2 + WK, + 'scrollbar-corner': 2 + WK, + 'scrollbar-thumb': 2 + WK, + 'scrollbar-track': 2 + WK, + 'scrollbar-track-piece': 2 + WK, + 'search-cancel-button': 2 + WK, + 'slider-container': 2 + WK, + 'slider-runnable-track': 2 + WK, + 'slider-thumb': 2 + WK, + 'textfield-decoration-container': 2 + WK, + }; + const rx = /^(:+)(?:-(\w+)-)?([^(]+)(\()?/i; + const allowsFunc = Func + FuncToo; + const allowsPrefix = WK + Moz; const {lower} = parserlib.util; const checkSelector = ({parts}) => { - let text; for (const {modifiers} of parts || []) { if (!modifiers) continue; for (const mod of modifiers) { - if (mod.type === 'pseudo' && !rxPseudoVendorPrefix.test(text = mod.text)) { - const i = text.indexOf('('); - const colons = text.match(rxColons)[0].length; - const def = definitions[lower(text.slice(colons, i < 0 ? 99 : i))]; + if (mod.type === 'pseudo') { + const {text} = mod; + const [all, colons, prefix, name, paren] = rx.exec(lower(text)) || 0; + const defPrefixed = definitionsPrefixed[name]; + const def = definitions[name] || defPrefixed; for (const err of !def ? ['Unknown pseudo'] : [ - colons > 1 + colons.length > 1 ? !(def & 2) && 'Must use : in' - : !(def & 1) && 'Must use :: in', - i < 0 - ? (def & 4) && 'Must use ( after' - : !(def & (4 + 8)) && 'Unexpected ( in', + : !(def & 1) && all !== ':-moz-placeholder' && 'Must use :: in', + paren + ? !(def & allowsFunc) && 'Unexpected ( in' + : (def & Func) && 'Must use ( after', + prefix ? + ( + !(def & allowsPrefix) || + prefix === 'webkit' && !(def & WK) || + prefix === 'moz' && !(def & Moz) + ) && 'Unexpected prefix in' + : defPrefixed && `Must use ${ + (def & WK) && (def & Moz) && '-webkit- or -moz-' || + (def & WK) && '-webkit-' || '-moz-'} prefix in`, ]) { - if (err) reporter.report(`${err} ${i < 0 ? text : text.slice(0, i + 1)}`, mod, rule); + if (err) reporter.report(`${err} ${text.slice(0, all.length)}`, mod, rule); } } else if (mod.args) { mod.args.forEach(checkSelector);