| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871 |
- /*
- Modded by tophf <github.com/tophf>
- ========== Original disclaimer:
- Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the 'Software'), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- /* global parserlib */
- 'use strict';
- //#region Reporter
- class Reporter {
- /**
- * An instance of Report is used to report results of the
- * verification back to the main API.
- * @class Reporter
- * @constructor
- * @param {String[]} lines - The text lines of the source.
- * @param {Object} ruleset - The set of rules to work with, including if
- * they are errors or warnings.
- * @param {Object} allow - explicitly allowed lines
- * @param {[][]} ignore - list of line ranges to be ignored
- */
- constructor(lines, ruleset, allow, ignore) {
- this.messages = [];
- this.stats = [];
- this.lines = lines;
- this.ruleset = ruleset;
- this.allow = allow || {};
- this.ignore = ignore || [];
- }
- error(message, {line = 1, col = 1}, rule = {}) {
- this.messages.push({
- type: 'error',
- evidence: this.lines[line - 1],
- line, col,
- message,
- rule,
- });
- }
- report(message, {line = 1, col = 1}, rule) {
- if (line in this.allow && rule.id in this.allow[line] ||
- this.ignore.some(range => range[0] <= line && line <= range[1])) {
- return;
- }
- this.messages.push({
- type: this.ruleset[rule.id] === 2 ? 'error' : 'warning',
- evidence: this.lines[line - 1],
- line, col,
- message,
- rule,
- });
- }
- info(message, {line = 1, col = 1}, rule) {
- this.messages.push({
- type: 'info',
- evidence: this.lines[line - 1],
- line, col,
- message,
- rule,
- });
- }
- rollupError(message, rule) {
- this.messages.push({
- type: 'error',
- rollup: true,
- message,
- rule,
- });
- }
- rollupWarn(message, rule) {
- this.messages.push({
- type: 'warning',
- rollup: true,
- message,
- rule,
- });
- }
- stat(name, value) {
- this.stats[name] = value;
- }
- }
- //#endregion
- //#region CSSLint
- //eslint-disable-next-line no-var
- var CSSLint = (() => {
- const RX_EMBEDDED = /\/\*\s*csslint\s+((?:[^*]|\*(?!\/))+?)\*\//ig;
- const EBMEDDED_RULE_VALUE_MAP = {
- // error
- 'true': 2,
- '2': 2,
- // warning
- '': 1,
- '1': 1,
- // ignore
- 'false': 0,
- '0': 0,
- };
- const rules = Object.create(null);
- // previous CSSLint overrides are used to decide whether the parserlib's cache should be reset
- let prevOverrides;
- return Object.assign(new parserlib.util.EventTarget(), {
- /**
- * This Proxy allows for direct property assignment of individual rules
- * so that "Go to symbol" command can be used in IDE to find a rule by id
- * as well as reduce the indentation thanks to the use of array literals.
- */
- addRule: new Proxy(rules, {
- set(_, id, [rule, init]) {
- rules[id] = rule;
- rule.id = id;
- rule.init = init;
- return true;
- },
- }),
- rules,
- getRuleList() {
- return Object.values(rules)
- .sort((a, b) => a.id < b.id ? -1 : a.id > b.id);
- },
- getRuleSet() {
- const ruleset = {};
- // by default, everything is a warning
- for (const id in rules) ruleset[id] = 1;
- return ruleset;
- },
- /**
- * Starts the verification process for the given CSS text.
- * @param {String} text The CSS text to verify.
- * @param {Object} ruleset (Optional) List of rules to apply. If null, then
- * all rules are used. If a rule has a value of 1 then it's a warning,
- * a value of 2 means it's an error.
- * @return {Object} Results of the verification.
- */
- verify(text, ruleset) {
- if (!ruleset) ruleset = this.getRuleSet();
- const allow = {};
- const ignore = [];
- RX_EMBEDDED.lastIndex =
- text.lastIndexOf('/*',
- text.indexOf('csslint',
- text.indexOf('/*') + 1 || text.length) + 1);
- if (RX_EMBEDDED.lastIndex >= 0) {
- ruleset = Object.assign({}, ruleset);
- applyEmbeddedOverrides(text, ruleset, allow, ignore);
- }
- const parser = new parserlib.css.Parser({
- starHack: true,
- ieFilters: true,
- underscoreHack: true,
- strict: false,
- });
- const reporter = new Reporter([], ruleset, allow, ignore);
- // always report parsing errors as errors
- ruleset.errors = 2;
- for (const [id, mode] of Object.entries(ruleset)) {
- const rule = mode && rules[id];
- if (rule) rule.init(rule, parser, reporter);
- }
- // TODO: when ruleset is unchanged we can try to invalidate only line ranges in 'allow' and 'ignore'
- const newOvr = [ruleset, allow, ignore];
- const reuseCache = !prevOverrides || JSON.stringify(prevOverrides) === JSON.stringify(newOvr);
- prevOverrides = newOvr;
- try {
- parser.parse(text, {reuseCache});
- } catch (ex) {
- reporter.error('Fatal error, cannot continue!\n' + ex.stack, ex, {});
- }
- const report = {
- messages: reporter.messages,
- stats: reporter.stats,
- ruleset: reporter.ruleset,
- allow: reporter.allow,
- ignore: reporter.ignore,
- };
- // sort by line numbers, rollups at the bottom
- report.messages.sort((a, b) =>
- a.rollup && !b.rollup ? 1 :
- !a.rollup && b.rollup ? -1 :
- a.line - b.line);
- parserlib.cache.feedback(report);
- return report;
- },
- });
- // Example 1:
- /* csslint ignore:start */
- /*
- the chunk of code where errors won't be reported
- the chunk's start is hardwired to the line of the opening comment
- the chunk's end is hardwired to the line of the closing comment
- */
- /* csslint ignore:end */
- // Example 2:
- // allow rule violations on the current line:
- // foo: bar; /* csslint allow:rulename1,rulename2,... */
- /* csslint allow:rulename1,rulename2,... */ // foo: bar;
- // Example 3:
- /* csslint rulename1 */
- /* csslint rulename2:N */
- /* csslint rulename3:N, rulename4:N */
- /* entire code is affected;
- * comments futher down the code extend/override previous comments of this kind
- * values for N (without the backquotes):
- `2` or `true` means "error"
- `1` or omitted means "warning" (when omitting, the colon can be omitted too)
- `0` or `false` means "ignore"
- */
- function applyEmbeddedOverrides(text, ruleset, allow, ignore) {
- let ignoreStart = null;
- let ignoreEnd = null;
- let lineno = 0;
- let eol = -1;
- let m;
- while ((m = RX_EMBEDDED.exec(text))) {
- // account for the lines between the previous and current match
- while (eol <= m.index) {
- eol = text.indexOf('\n', eol + 1);
- if (eol < 0) eol = text.length;
- lineno++;
- }
- const ovr = m[1].toLowerCase();
- const cmd = ovr.split(':', 1)[0];
- const i = cmd.length + 1;
- switch (cmd.trim()) {
- case 'allow': {
- const allowRuleset = {};
- let num = 0;
- ovr.slice(i).split(',').forEach(allowRule => {
- allowRuleset[allowRule.trim()] = true;
- num++;
- });
- if (num) allow[lineno] = allowRuleset;
- break;
- }
- case 'ignore':
- if (ovr.includes('start')) {
- ignoreStart = ignoreStart || lineno;
- break;
- }
- if (ovr.includes('end')) {
- ignoreEnd = lineno;
- if (ignoreStart && ignoreEnd) {
- ignore.push([ignoreStart, ignoreEnd]);
- ignoreStart = ignoreEnd = null;
- }
- }
- break;
- default:
- ovr.slice(i).split(',').forEach(rule => {
- const pair = rule.split(':');
- const property = pair[0] || '';
- const value = pair[1] || '';
- const mapped = EBMEDDED_RULE_VALUE_MAP[value.trim()];
- ruleset[property.trim()] = mapped === undefined ? 1 : mapped;
- });
- }
- }
- // Close remaining ignore block, if any
- if (ignoreStart) {
- ignore.push([ignoreStart, lineno]);
- }
- }
- })();
- //#endregion
- //#region Util
- CSSLint.Util = {
- /** Gets the lower-cased text without vendor prefix */
- getPropName(prop) {
- return prop._propName ||
- (prop._propName = prop.text.replace(parserlib.util.rxVendorPrefix, '').toLowerCase());
- },
- registerRuleEvents(parser, {start, property, end}) {
- for (const e of [
- 'fontface',
- 'keyframerule',
- 'page',
- 'pagemargin',
- 'rule',
- 'viewport',
- ]) {
- if (start) parser.addListener('start' + e, start);
- if (end) parser.addListener('end' + e, end);
- }
- if (property) parser.addListener('property', property);
- },
- registerShorthandEvents(parser, {property, end}) {
- const {shorthands, shorthandsFor} = CSSLint.Util;
- let props, inRule;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- inRule = true;
- props = null;
- },
- property(event) {
- if (!inRule) return;
- const name = CSSLint.Util.getPropName(event.property);
- 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);
- }
- },
- end(event) {
- inRule = false;
- if (end && props) {
- end(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
- //#region Rules
- CSSLint.addRule['adjoining-classes'] = [{
- name: 'Disallow adjoining classes',
- desc: "Don't use adjoining classes.",
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes',
- browsers: 'IE6',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const selector of event.selectors) {
- for (const part of selector.parts) {
- if (part.type === parser.SELECTOR_PART_TYPE) {
- let classCount = 0;
- for (const modifier of part.modifiers) {
- classCount += modifier.type === 'class';
- if (classCount > 1) {
- reporter.report('Adjoining classes: ' + selector.text, part, rule);
- }
- }
- }
- }
- }
- });
- }];
- CSSLint.addRule['box-model'] = [{
- name: 'Beware of broken box size',
- desc: "Don't use width or height when using padding or border.",
- url: 'https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const sizeProps = {
- width: ['border', 'border-left', 'border-right', 'padding', 'padding-left', 'padding-right'],
- height: ['border', 'border-bottom', 'border-top', 'padding', 'padding-bottom', 'padding-top'],
- };
- let properties = {};
- let boxSizing = false;
- let inRule;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- inRule = true;
- properties = {};
- boxSizing = false;
- },
- property(event) {
- if (!inRule) return;
- const name = CSSLint.Util.getPropName(event.property);
- if (sizeProps.width.includes(name) || sizeProps.height.includes(name)) {
- if (!/^0+\D*$/.test(event.value) &&
- (name !== 'border' || !/^none$/i.test(event.value))) {
- properties[name] = {
- line: event.property.line,
- col: event.property.col,
- value: event.value,
- };
- }
- } else if (/^(width|height)/i.test(name) &&
- /^(length|percentage)/.test(event.value.parts[0].type)) {
- properties[name] = 1;
- } else if (name === 'box-sizing') {
- boxSizing = true;
- }
- },
- end() {
- inRule = false;
- if (boxSizing) return;
- for (const size in sizeProps) {
- if (!properties[size]) continue;
- for (const prop of sizeProps[size]) {
- if (prop !== 'padding' || !properties[prop]) continue;
- const {value: {parts}, line, col} = properties[prop];
- if (parts.length !== 2 || Number(parts[0].value) !== 0) {
- reporter.report(
- `Using ${size} with ${prop} can sometimes make elements larger than you expect.`,
- {line, col}, rule);
- }
- }
- }
- },
- });
- }];
- CSSLint.addRule['box-sizing'] = [{
- name: 'Disallow use of box-sizing',
- desc: "'box-sizing' isn't supported in IE6-7.",
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing',
- browsers: 'IE6, IE7',
- tags: ['Compatibility'],
- }, (rule, parser, reporter) => {
- parser.addListener('property', event => {
- if (CSSLint.Util.getPropName(event.property) === 'box-sizing') {
- reporter.report(rule.desc, event, rule);
- }
- });
- }];
- CSSLint.addRule['bulletproof-font-face'] = [{
- name: 'Use the bulletproof @font-face syntax',
- desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE " +
- 'http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax',
- url: 'https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
- let firstSrc = true;
- let ruleFailed = false;
- let pos;
- // Mark the start of a @font-face declaration so we only test properties inside it
- parser.addListener('startfontface', () => {
- parser.addListener('property', property);
- });
- function property(event) {
- if (CSSLint.Util.getPropName(event.property) !== 'src') return;
- const value = event.value.toString();
- pos = event;
- const matched = regex.test(value);
- if (firstSrc && !matched) {
- ruleFailed = true;
- firstSrc = false;
- } else if (!firstSrc && matched) {
- ruleFailed = false;
- }
- }
- // Back to normal rules that we don't need to test
- parser.addListener('endfontface', () => {
- parser.removeListener('property', property);
- if (!ruleFailed) return;
- reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.",
- pos, rule);
- });
- }];
- CSSLint.addRule['compatible-vendor-prefixes'] = [{
- name: 'Require compatible vendor prefixes',
- desc: 'Include all compatible vendor prefixes to reach a wider range of users.',
- url: 'https://github.com/CSSLint/csslint/wiki/Require-compatible-vendor-prefixes',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
- const compatiblePrefixes = {
- 'animation': 'webkit',
- 'animation-delay': 'webkit',
- 'animation-direction': 'webkit',
- 'animation-duration': 'webkit',
- 'animation-fill-mode': 'webkit',
- 'animation-iteration-count': 'webkit',
- 'animation-name': 'webkit',
- 'animation-play-state': 'webkit',
- 'animation-timing-function': 'webkit',
- 'appearance': 'webkit moz',
- 'border-end': 'webkit moz',
- 'border-end-color': 'webkit moz',
- 'border-end-style': 'webkit moz',
- 'border-end-width': 'webkit moz',
- 'border-image': 'webkit moz o',
- 'border-radius': 'webkit',
- 'border-start': 'webkit moz',
- 'border-start-color': 'webkit moz',
- 'border-start-style': 'webkit moz',
- 'border-start-width': 'webkit moz',
- 'box-align': 'webkit moz',
- 'box-direction': 'webkit moz',
- 'box-flex': 'webkit moz',
- 'box-lines': 'webkit',
- 'box-ordinal-group': 'webkit moz',
- 'box-orient': 'webkit moz',
- 'box-pack': 'webkit moz',
- 'box-sizing': '',
- 'box-shadow': '',
- 'column-count': 'webkit moz ms',
- 'column-gap': 'webkit moz ms',
- 'column-rule': 'webkit moz ms',
- 'column-rule-color': 'webkit moz ms',
- 'column-rule-style': 'webkit moz ms',
- 'column-rule-width': 'webkit moz ms',
- 'column-width': 'webkit moz ms',
- 'flex': 'webkit ms',
- 'flex-basis': 'webkit',
- 'flex-direction': 'webkit ms',
- 'flex-flow': 'webkit',
- 'flex-grow': 'webkit',
- 'flex-shrink': 'webkit',
- 'hyphens': 'epub moz',
- 'line-break': 'webkit ms',
- 'margin-end': 'webkit moz',
- 'margin-start': 'webkit moz',
- 'marquee-speed': 'webkit wap',
- 'marquee-style': 'webkit wap',
- 'padding-end': 'webkit moz',
- 'padding-start': 'webkit moz',
- 'tab-size': 'moz o',
- 'text-size-adjust': 'webkit ms',
- 'transform': 'webkit ms',
- 'transform-origin': 'webkit ms',
- 'transition': '',
- 'transition-delay': '',
- 'transition-duration': '',
- 'transition-property': '',
- 'transition-timing-function': '',
- 'user-modify': 'webkit moz',
- 'user-select': 'webkit moz ms',
- 'word-break': 'epub ms',
- 'writing-mode': 'epub ms',
- };
- const applyTo = [];
- let properties = [];
- let inKeyFrame = false;
- let started = 0;
- for (const prop in compatiblePrefixes) {
- const variations = compatiblePrefixes[prop].split(' ').map(s => `-${s}-${prop}`);
- compatiblePrefixes[prop] = variations;
- applyTo.push(...variations);
- }
- parser.addListener('startrule', () => {
- started++;
- properties = [];
- });
- parser.addListener('startkeyframes', event => {
- started++;
- inKeyFrame = event.prefix || true;
- if (inKeyFrame && typeof inKeyFrame === 'string') {
- inKeyFrame = '-' + inKeyFrame + '-';
- }
- });
- parser.addListener('endkeyframes', () => {
- started--;
- inKeyFrame = false;
- });
- parser.addListener('property', event => {
- if (!started) return;
- const name = event.property.text;
- if (inKeyFrame &&
- typeof inKeyFrame === 'string' &&
- name.startsWith(inKeyFrame) ||
- !applyTo.includes(name)) {
- return;
- }
- properties.push(event.property);
- });
- parser.addListener('endrule', () => {
- started = false;
- if (!properties.length) return;
- const groups = {};
- for (const name of properties) {
- for (const prop in compatiblePrefixes) {
- const variations = compatiblePrefixes[prop];
- if (!variations.includes(name.text)) {
- continue;
- }
- if (!groups[prop]) {
- groups[prop] = {
- full: variations.slice(0),
- actual: [],
- actualNodes: [],
- };
- }
- if (!groups[prop].actual.includes(name.text)) {
- groups[prop].actual.push(name.text);
- groups[prop].actualNodes.push(name);
- }
- }
- }
- for (const prop in groups) {
- const value = groups[prop];
- const actual = value.actual;
- const len = actual.length;
- if (value.full.length <= len) continue;
- for (const item of value.full) {
- if (!actual.includes(item)) {
- const spec = len === 1 ? actual[0] : len === 2 ? actual.join(' and ') : actual.join(', ');
- reporter.report(
- `'${item}' is compatible with ${spec} and should be included as well.`,
- value.actualNodes[0], rule);
- }
- }
- }
- });
- }];
- CSSLint.addRule['display-property-grouping'] = [{
- name: 'Require properties appropriate for display',
- desc: "Certain properties shouldn't be used with certain display property values.",
- url: 'https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const propertiesToCheck = {
- 'display': 1,
- 'float': 'none',
- 'height': 1,
- 'width': 1,
- 'margin': 1,
- 'margin-left': 1,
- 'margin-right': 1,
- 'margin-bottom': 1,
- 'margin-top': 1,
- 'padding': 1,
- 'padding-left': 1,
- 'padding-right': 1,
- 'padding-bottom': 1,
- 'padding-top': 1,
- 'vertical-align': 1,
- };
- let properties;
- let inRule;
- const reportProperty = (name, display, msg) => {
- const prop = properties[name];
- if (prop && propertiesToCheck[name] !== prop.value.toLowerCase()) {
- reporter.report(msg || `'${name}' can't be used with display: ${display}.`, prop, rule);
- }
- };
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- inRule = true;
- properties = {};
- },
- property(event) {
- if (!inRule) return;
- const name = CSSLint.Util.getPropName(event.property);
- if (name in propertiesToCheck) {
- properties[name] = {
- value: event.value.text,
- line: event.property.line,
- col: event.property.col,
- };
- }
- },
- end() {
- inRule = false;
- const display = properties.display && properties.display.value;
- if (!display) return;
- switch (display.toLowerCase()) {
- case 'inline':
- ['height', 'width', 'margin', 'margin-top', 'margin-bottom']
- .forEach(p => reportProperty(p, display));
- reportProperty('float', display,
- "'display:inline' has no effect on floated elements " +
- '(but may be used to fix the IE6 double-margin bug).');
- break;
- case 'block':
- // vertical-align should not be used with block
- reportProperty('vertical-align', display);
- break;
- case 'inline-block':
- // float should not be used with inline-block
- reportProperty('float', display);
- break;
- default:
- // margin, float should not be used with table
- if (/^table-/i.test(display)) {
- ['margin', 'margin-left', 'margin-right', 'margin-top', 'margin-bottom', 'float']
- .forEach(p => reportProperty(p, display));
- }
- }
- },
- });
- }];
- CSSLint.addRule['duplicate-background-images'] = [{
- name: 'Disallow duplicate background images',
- desc: 'Every background-image should be unique. Use a common class for e.g. sprites.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const stack = {};
- parser.addListener('property', event => {
- if (!/^-(webkit|moz|ms|o)-background(-image)$/i.test(event.property.text)) {
- return;
- }
- for (const part of event.value.parts) {
- if (part.type !== 'uri') continue;
- const uri = stack[part.uri];
- if (!uri) {
- stack[part.uri] = event;
- } else {
- reporter.report(
- `Background image '${part.uri}' was used multiple times, ` +
- `first declared at line ${uri.line}, col ${uri.col}.`,
- event, rule);
- }
- }
- });
- }];
- CSSLint.addRule['duplicate-properties'] = [{
- name: 'Disallow duplicate properties',
- desc: 'Duplicate properties must appear one after the other. ' +
- 'Exact duplicates are always reported.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let props, lastName, inRule;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- inRule = true;
- props = {};
- },
- property(event) {
- if (!inRule) return;
- const property = event.property;
- const name = property.text.toLowerCase();
- const last = props[name];
- const dupValue = last === event.value.text;
- if (last && (lastName !== name || dupValue)) {
- reporter.report(`${dupValue ? 'Duplicate' : 'Ungrouped duplicate'} '${property}'.`,
- event, rule);
- }
- props[name] = event.value.text;
- lastName = name;
- },
- end() {
- inRule = false;
- },
- });
- }];
- CSSLint.addRule['empty-rules'] = [{
- name: 'Disallow empty rules',
- desc: 'Rules without any properties specified should be removed.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let count = 0;
- parser.addListener('startrule', () => (count = 0));
- parser.addListener('property', () => count++);
- parser.addListener('endrule', event => {
- if (!count) reporter.report('Empty rule.', event.selectors[0], rule);
- });
- }];
- CSSLint.addRule['errors'] = [{
- name: 'Parsing Errors',
- desc: 'This rule looks for recoverable syntax errors.',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('error', e => reporter.error(e.message, e, rule));
- }];
- CSSLint.addRule['fallback-colors'] = [{
- name: 'Require fallback colors',
- desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
- url: 'https://github.com/CSSLint/csslint/wiki/Require-fallback-colors',
- browsers: 'IE6,IE7,IE8',
- }, (rule, parser, reporter) => {
- const propertiesToCheck = new Set([
- 'color',
- 'background',
- 'border-color',
- 'border-top-color',
- 'border-right-color',
- 'border-bottom-color',
- 'border-left-color',
- 'border',
- 'border-top',
- 'border-right',
- 'border-bottom',
- 'border-left',
- 'background-color',
- ]);
- let lastProperty;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- lastProperty = null;
- },
- property(event) {
- const name = CSSLint.Util.getPropName(event.property);
- if (!propertiesToCheck.has(name)) {
- lastProperty = event;
- return;
- }
- let colorType = '';
- for (const part of event.value.parts) {
- if (part.type !== 'color') {
- continue;
- }
- if (!('alpha' in part || 'hue' in part)) {
- event.colorType = 'compat';
- continue;
- }
- if (/([^)]+)\(/.test(part)) {
- colorType = RegExp.$1.toUpperCase();
- }
- if (!lastProperty ||
- lastProperty.colorType !== 'compat' ||
- CSSLint.Util.getPropName(lastProperty.property) !== name) {
- reporter.report(`Fallback ${name} (hex or RGB) should precede ${colorType} ${name}.`,
- event, rule);
- }
- }
- lastProperty = event;
- },
- });
- }];
- CSSLint.addRule['floats'] = [{
- name: 'Disallow too many floats',
- desc: 'This rule tests if the float property too many times',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let count = 0;
- parser.addListener('property', ({property, value}) => {
- count +=
- CSSLint.Util.getPropName(property) === 'float' &&
- value.text.toLowerCase() !== 'none';
- });
- parser.addListener('endstylesheet', () => {
- reporter.stat('floats', count);
- if (count >= 10) {
- reporter.rollupWarn(
- `Too many floats (${count}), you're probably using them for layout. ` +
- 'Consider using a grid system instead.', rule);
- }
- });
- }];
- CSSLint.addRule['font-faces'] = [{
- name: "Don't use too many web fonts",
- desc: 'Too many different web fonts in the same stylesheet.',
- url: 'https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let count = 0;
- parser.addListener('startfontface', () => count++);
- parser.addListener('endstylesheet', () => {
- if (count > 5) {
- reporter.rollupWarn(`Too many @font-face declarations (${count}).`, rule);
- }
- });
- }];
- CSSLint.addRule['font-sizes'] = [{
- name: 'Disallow too many font sizes',
- desc: 'Checks the number of font-size declarations.',
- url: 'https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let count = 0;
- parser.addListener('property', event => {
- count += CSSLint.Util.getPropName(event.property) === 'font-size';
- });
- parser.addListener('endstylesheet', () => {
- reporter.stat('font-sizes', count);
- if (count >= 10) {
- reporter.rollupWarn(`Too many font-size declarations (${count}), abstraction needed.`, rule);
- }
- });
- }];
- 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.',
- url: 'https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let gradients;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- gradients = {
- moz: 0,
- webkit: 0,
- oldWebkit: 0,
- o: 0,
- };
- },
- property(event) {
- if (/-(moz|o|webkit)(?:-(?:linear|radial))-gradient/i.test(event.value)) {
- gradients[RegExp.$1] = 1;
- } else if (/-webkit-gradient/i.test(event.value)) {
- gradients.oldWebkit = 1;
- }
- },
- end(event) {
- const missing = [];
- if (!gradients.moz) missing.push('Firefox 3.6+');
- if (!gradients.webkit) missing.push('Webkit (Safari 5+, Chrome)');
- if (!gradients.oldWebkit) missing.push('Old Webkit (Safari 4+, Chrome)');
- if (!gradients.o) missing.push('Opera 11.1+');
- if (missing.length && missing.length < 4) {
- reporter.report(`Missing vendor-prefixed CSS gradients for ${missing.join(', ')}.`,
- event.selectors[0], rule);
- }
- },
- });
- }];
- CSSLint.addRule['ids'] = [{
- name: 'Disallow IDs in selectors',
- desc: 'Selectors should not contain IDs.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const sel of event.selectors) {
- const cnt =
- sel.parts.reduce((sum = 0, {type, modifiers}) =>
- type === parser.SELECTOR_PART_TYPE
- ? modifiers.reduce((sum, mod) => sum + (mod.type === 'id'), 0)
- : sum, 0);
- if (cnt) {
- reporter.report(`Id in selector${cnt > 1 ? '!'.repeat(cnt) : '.'}`, sel, rule);
- }
- }
- });
- }];
- CSSLint.addRule['import-ie-limit'] = [{
- name: '@import limit on IE6-IE9',
- desc: 'IE6-9 supports up to 31 @import per stylesheet',
- browsers: 'IE6, IE7, IE8, IE9',
- }, (rule, parser, reporter) => {
- const MAX_IMPORT_COUNT = 31;
- let count = 0;
- parser.addListener('startpage', () => (count = 0));
- parser.addListener('import', () => count++);
- parser.addListener('endstylesheet', () => {
- if (count > MAX_IMPORT_COUNT) {
- reporter.rollupError(
- `Too many @import rules (${count}). IE6-9 supports up to 31 import per stylesheet.`,
- rule);
- }
- });
- }];
- CSSLint.addRule['import'] = [{
- name: 'Disallow @import',
- desc: "Don't use @import, use <link> instead.",
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-%40import',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('import', e => {
- reporter.report('@import prevents parallel downloads, use <link> instead.', e, rule);
- });
- }];
- CSSLint.addRule['important'] = [{
- name: 'Disallow !important',
- desc: 'Be careful when using !important declaration',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-%21important',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let count = 0;
- parser.addListener('property', event => {
- if (event.important) {
- count++;
- reporter.report('!important.', event, rule);
- }
- });
- parser.addListener('endstylesheet', () => {
- reporter.stat('important', count);
- if (count >= 10) {
- reporter.rollupWarn(
- `Too many !important declarations (${count}), ` +
- 'try to use less than 10 to avoid specificity issues.', rule);
- }
- });
- }];
- CSSLint.addRule['known-properties'] = [{
- name: 'Require use of known properties',
- desc: 'Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.',
- url: 'https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('property', event => {
- const inv = event.invalid;
- if (inv) reporter.report(inv.message, inv, rule);
- });
- }];
- CSSLint.addRule['known-pseudos'] = [{
- 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',
- }, (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, // also allows ":"
- 'cue': 2,
- 'cue-region': 2,
- '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 + Func,
- 'disabled': 1,
- 'drop': 1,
- 'empty': 1,
- 'enabled': 1,
- 'first': 1,
- 'first-child': 1,
- 'first-of-type': 1,
- 'focus': 1,
- 'focus-visible': 1,
- 'focus-within': 1,
- 'fullscreen': 1,
- 'future': 1,
- 'has': 1 + Func,
- 'host': 1 + FuncToo,
- 'host-context': 1 + Func,
- 'hover': 1,
- 'in-range': 1,
- 'indeterminate': 1,
- 'invalid': 1,
- 'is': 1 + Func,
- 'lang': 1 + Func,
- 'last-child': 1,
- 'last-of-type': 1,
- 'left': 1,
- 'link': 1,
- 'local-link': 1,
- '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,
- 'past': 1,
- 'paused': 1,
- 'picture-in-picture': 1,
- 'placeholder-shown': 1,
- 'playing': 1,
- 'read-only': 1,
- 'read-write': 1,
- 'required': 1,
- 'right': 1,
- 'root': 1,
- 'scope': 1,
- 'state': 1 + Func,
- 'target': 1,
- 'target-within': 1,
- 'user-invalid': 1,
- 'valid': 1,
- 'visited': 1,
- '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,
- '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}) => {
- for (const {modifiers} of parts || []) {
- if (!modifiers) continue;
- for (const mod of modifiers) {
- 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.length > 1
- ? !(def & 2) && 'Must use : 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} ${text.slice(0, all.length)}`, mod, rule);
- }
- } else if (mod.args) {
- mod.args.forEach(checkSelector);
- }
- }
- }
- };
- parser.addListener('startrule', e => e.selectors.forEach(checkSelector));
- parser.addListener('supportsSelector', e => checkSelector(e.selector));
- }];
- CSSLint.addRule['order-alphabetical'] = [{
- name: 'Alphabetical order',
- desc: 'Assure properties are in alphabetical order',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let last, failed;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- last = '';
- failed = false;
- },
- property(event) {
- if (!failed) {
- const name = CSSLint.Util.getPropName(event.property);
- if (name < last) {
- reporter.report(`Non-alphabetical order: '${name}'.`, event, rule);
- failed = true;
- }
- last = name;
- }
- },
- });
- }];
- CSSLint.addRule['outline-none'] = [{
- name: 'Disallow outline: none',
- desc: 'Use of outline: none or outline: 0 should be limited to :focus rules.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone',
- browsers: 'All',
- tags: ['Accessibility'],
- }, (rule, parser, reporter) => {
- let lastRule;
- CSSLint.Util.registerRuleEvents(parser, {
- start(event) {
- lastRule = !event.selectors ? null : {
- line: event.line,
- col: event.col,
- selectors: event.selectors,
- propCount: 0,
- outline: false,
- };
- },
- property(event) {
- if (!lastRule) return;
- lastRule.propCount++;
- if (CSSLint.Util.getPropName(event.property) === 'outline' && /^(none|0)$/i.test(event.value)) {
- lastRule.outline = true;
- }
- },
- end() {
- const {outline, selectors, propCount} = lastRule || {};
- lastRule = null;
- if (!outline) return;
- if (!/:focus/i.test(selectors)) {
- reporter.report('Outlines should only be modified using :focus.', lastRule, rule);
- } else if (propCount === 1) {
- reporter.report("Outlines shouldn't be hidden unless other visual changes are made.",
- lastRule, rule);
- }
- },
- });
- }];
- CSSLint.addRule['overqualified-elements'] = [{
- name: 'Disallow overqualified elements',
- desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const classes = {};
- const report = (part, mod) => {
- reporter.report(`'${part}' is overqualified, just use '${mod}' without element name.`,
- part, rule);
- };
- parser.addListener('startrule', event => {
- for (const selector of event.selectors) {
- for (const part of selector.parts) {
- if (part.type !== parser.SELECTOR_PART_TYPE) continue;
- for (const mod of part.modifiers) {
- if (part.elementName && mod.type === 'id') {
- report(part, mod);
- } else if (mod.type === 'class') {
- (classes[mod] || (classes[mod] = []))
- .push({modifier: mod, part});
- }
- }
- }
- }
- });
- // one use means that this is overqualified
- parser.addListener('endstylesheet', () => {
- for (const prop of Object.values(classes)) {
- const {part, modifier} = prop[0];
- if (part.elementName && prop.length === 1) {
- report(part, modifier);
- }
- }
- });
- }];
- CSSLint.addRule['qualified-headings'] = [{
- name: 'Disallow qualified headings',
- desc: 'Headings should not be qualified (namespaced).',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const selector of event.selectors) {
- let first = true;
- for (const part of selector.parts) {
- const name = part.elementName;
- if (!first &&
- name &&
- part.type === parser.SELECTOR_PART_TYPE &&
- /h[1-6]/.test(name.toString())) {
- reporter.report(`Heading '${name}' should not be qualified.`, part, rule);
- }
- first = false;
- }
- }
- });
- }];
- CSSLint.addRule['regex-selectors'] = [{
- name: 'Disallow selectors that look like regexs',
- desc: 'Selectors that look like regular expressions are slow and should be avoided.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const selector of event.selectors) {
- for (const part of selector.parts) {
- if (part.type === parser.SELECTOR_PART_TYPE) {
- for (const mod of part.modifiers) {
- if (mod.type === 'attribute' && /([~|^$*]=)/.test(mod)) {
- reporter.report(`Slow attribute selector ${RegExp.$1}.`, mod, rule);
- }
- }
- }
- }
- }
- });
- }];
- CSSLint.addRule['rules-count'] = [{
- name: 'Rules Count',
- desc: 'Track how many rules there are.',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let count = 0;
- parser.addListener('startrule', () => count++);
- parser.addListener('endstylesheet', () => reporter.stat('rule-count', count));
- }];
- CSSLint.addRule['selector-max'] = [{
- name: 'Error when past the 4095 selector limit for IE',
- desc: 'Will error when selector count is > 4095.',
- browsers: 'IE',
- }, (rule, parser, reporter, limit = 4095) => {
- let count = 0;
- parser.addListener('startrule', event => {
- count += event.selectors.length;
- });
- parser.addListener('endstylesheet', () => {
- if (count > limit) {
- reporter.report(count + ' selectors found. ' +
- 'Internet Explorer supports a maximum of 4095 selectors per stylesheet. ' +
- 'Consider refactoring.', {}, rule);
- }
- });
- }];
- CSSLint.addRule['selector-max-approaching'] = [{
- name: 'Warn when approaching the 4095 selector limit for IE',
- desc: 'Will warn when selector count is >= 3800 selectors.',
- browsers: 'IE',
- }, (rule, parser, reporter) => {
- CSSLint.rules['selector-max'].init(rule, parser, reporter, Number(rule.desc.match(/\d+/)[0]));
- }];
- CSSLint.addRule['selector-newline'] = [{
- name: 'Disallow new-line characters in selectors',
- desc: 'New-line characters in selectors are usually a forgotten comma and not a descendant combinator.',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const {parts} of event.selectors) {
- for (let i = 0, p, pn; i < parts.length - 1 && (p = parts[i]); i++) {
- if (p.type === 'descendant' && (pn = parts[i + 1]).line > p.line) {
- reporter.report('Line break in selector (forgot a comma?)', pn, rule);
- }
- }
- }
- });
- }];
- CSSLint.addRule['shorthand'] = [{
- name: 'Require shorthand properties',
- desc: 'Use shorthand properties where possible.',
- url: 'https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const {shorthands} = CSSLint.Util;
- CSSLint.Util.registerShorthandEvents(parser, {
- end(event, props) {
- for (const [sh, events] of Object.entries(props)) {
- const names = Object.keys(events);
- if (names.length === shorthands[sh].length) {
- const msg = `'${sh}' shorthand can replace '${names.join("' + '")}'`;
- names.forEach(n => reporter.report(msg, events[n], rule));
- }
- }
- },
- });
- }];
- CSSLint.addRule['shorthand-overrides'] = [{
- 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',
- }, (rule, parser, reporter) => {
- CSSLint.Util.registerShorthandEvents(parser, {
- property(event, props, name) {
- const ovr = props[name];
- if (ovr) {
- delete props[name];
- reporter.report(`'${event.property}' overrides '${Object.keys(ovr).join("', '")}' above.`,
- event, rule);
- }
- },
- });
- }];
- CSSLint.addRule['simple-not'] = [{
- name: 'Require use of simple selectors inside :not()',
- desc: 'A complex selector inside :not() is only supported by CSS4-compliant browsers.',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', e => {
- for (const sel of e.selectors) {
- if (!/:not\(/i.test(sel.text)) continue;
- for (const part of sel.parts) {
- if (!part.modifiers) continue;
- for (const mod of part.modifiers) {
- if (mod.type !== 'not') continue;
- const {args} = mod;
- const {parts} = args[0];
- if (args.length > 1 ||
- parts.length !== 1 ||
- parts[0].modifiers.length + (parts[0].elementName ? 1 : 0) > 1 ||
- /^:not\(/i.test(parts[0])) {
- reporter.report('Complex selector inside :not().', args[0], rule);
- }
- }
- }
- }
- });
- }];
- CSSLint.addRule['star-property-hack'] = [{
- name: 'Disallow properties with a star prefix',
- desc: 'Checks for the star property hack (targets IE6/7)',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-star-hack',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('property', ({property}) => {
- if (property.hack === '*') {
- reporter.report('IE star prefix.', property, rule);
- }
- });
- }];
- CSSLint.addRule['text-indent'] = [{
- name: 'Disallow negative text-indent',
- desc: 'Checks for text indent less than -99px',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- let textIndent, isLtr;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- textIndent = false;
- isLtr = false;
- },
- property(event) {
- const name = CSSLint.Util.getPropName(event.property);
- const value = event.value;
- if (name === 'text-indent' && value.parts[0].value < -99) {
- textIndent = event.property;
- } else if (name === 'direction' && /^ltr$/i.test(value)) {
- isLtr = true;
- }
- },
- end() {
- if (textIndent && !isLtr) {
- reporter.report(
- "Negative 'text-indent' doesn't work well with RTL. " +
- "If you use 'text-indent' for image replacement, " +
- "explicitly set 'direction' for that item to 'ltr'.",
- textIndent, rule);
- }
- },
- });
- }];
- CSSLint.addRule['underscore-property-hack'] = [{
- name: 'Disallow properties with an underscore prefix',
- desc: 'Checks for the underscore property hack (targets IE6)',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('property', ({property}) => {
- if (property.hack === '_') {
- reporter.report('IE underscore prefix.', property, rule);
- }
- });
- }];
- CSSLint.addRule['unique-headings'] = [{
- name: 'Headings should only be defined once',
- desc: 'Headings should be defined only once.',
- url: 'https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const headings = new Array(6).fill(0);
- parser.addListener('startrule', event => {
- for (const {parts} of event.selectors) {
- const p = parts[parts.length - 1];
- if (/h([1-6])/i.test(p.elementName) &&
- !p.modifiers.some(mod => mod.type === 'pseudo') &&
- ++headings[RegExp.$1 - 1] > 1) {
- reporter.report(`Heading ${p.elementName} has already been defined.`, p, rule);
- }
- }
- });
- parser.addListener('endstylesheet', () => {
- const stats = headings
- .filter(h => h > 1)
- .map((h, i) => `${h} H${i + 1}s`);
- if (stats.length) {
- reporter.rollupWarn(stats.join(', '), rule);
- }
- });
- }];
- CSSLint.addRule['universal-selector'] = [{
- name: 'Disallow universal selector',
- desc: 'The universal selector (*) is known to be slow.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const {parts} of event.selectors) {
- const part = parts[parts.length - 1];
- if (part.elementName === '*') {
- reporter.report(rule.desc, part, rule);
- }
- }
- });
- }];
- CSSLint.addRule['unqualified-attributes'] = [{
- name: 'Disallow unqualified attribute selectors',
- desc: 'Unqualified attribute selectors are known to be slow.',
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('startrule', event => {
- for (const {parts} of event.selectors) {
- const part = parts[parts.length - 1];
- if (part.type === parser.SELECTOR_PART_TYPE &&
- !part.modifiers.some(mod => mod.type === 'class' || mod.type === 'id')) {
- const isUnqualified = !part.elementName || part.elementName === '*';
- for (const mod of part.modifiers) {
- if (mod.type === 'attribute' && isUnqualified) {
- reporter.report(rule.desc, part, rule);
- }
- }
- }
- }
- });
- }];
- CSSLint.addRule['vendor-prefix'] = [{
- name: 'Require standard property with vendor prefix',
- desc: 'When using a vendor-prefixed property, make sure to include the standard one.',
- url: 'https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- const propertiesToCheck = {
- '-webkit-border-radius': 'border-radius',
- '-webkit-border-top-left-radius': 'border-top-left-radius',
- '-webkit-border-top-right-radius': 'border-top-right-radius',
- '-webkit-border-bottom-left-radius': 'border-bottom-left-radius',
- '-webkit-border-bottom-right-radius': 'border-bottom-right-radius',
- '-o-border-radius': 'border-radius',
- '-o-border-top-left-radius': 'border-top-left-radius',
- '-o-border-top-right-radius': 'border-top-right-radius',
- '-o-border-bottom-left-radius': 'border-bottom-left-radius',
- '-o-border-bottom-right-radius': 'border-bottom-right-radius',
- '-moz-border-radius': 'border-radius',
- '-moz-border-radius-topleft': 'border-top-left-radius',
- '-moz-border-radius-topright': 'border-top-right-radius',
- '-moz-border-radius-bottomleft': 'border-bottom-left-radius',
- '-moz-border-radius-bottomright': 'border-bottom-right-radius',
- '-moz-column-count': 'column-count',
- '-webkit-column-count': 'column-count',
- '-moz-column-gap': 'column-gap',
- '-webkit-column-gap': 'column-gap',
- '-moz-column-rule': 'column-rule',
- '-webkit-column-rule': 'column-rule',
- '-moz-column-rule-style': 'column-rule-style',
- '-webkit-column-rule-style': 'column-rule-style',
- '-moz-column-rule-color': 'column-rule-color',
- '-webkit-column-rule-color': 'column-rule-color',
- '-moz-column-rule-width': 'column-rule-width',
- '-webkit-column-rule-width': 'column-rule-width',
- '-moz-column-width': 'column-width',
- '-webkit-column-width': 'column-width',
- '-webkit-column-span': 'column-span',
- '-webkit-columns': 'columns',
- '-moz-box-shadow': 'box-shadow',
- '-webkit-box-shadow': 'box-shadow',
- '-moz-transform': 'transform',
- '-webkit-transform': 'transform',
- '-o-transform': 'transform',
- '-ms-transform': 'transform',
- '-moz-transform-origin': 'transform-origin',
- '-webkit-transform-origin': 'transform-origin',
- '-o-transform-origin': 'transform-origin',
- '-ms-transform-origin': 'transform-origin',
- '-moz-box-sizing': 'box-sizing',
- '-webkit-box-sizing': 'box-sizing',
- };
- let properties, num, inRule;
- CSSLint.Util.registerRuleEvents(parser, {
- start() {
- inRule = true;
- properties = {};
- num = 1;
- },
- property(event) {
- if (!inRule) return;
- const name = CSSLint.Util.getPropName(event.property);
- let prop = properties[name];
- if (!prop) prop = properties[name] = [];
- prop.push({
- name: event.property,
- value: event.value,
- pos: num++,
- });
- },
- end() {
- inRule = false;
- const needsStandard = [];
- for (const prop in properties) {
- if (prop in propertiesToCheck) {
- needsStandard.push({
- actual: prop,
- needed: propertiesToCheck[prop],
- });
- }
- }
- for (const {needed, actual} of needsStandard) {
- const unit = properties[actual][0].name;
- if (!properties[needed]) {
- reporter.report(`Missing standard property '${needed}' to go along with '${actual}'.`,
- unit, rule);
- } else if (properties[needed][0].pos < properties[actual][0].pos) {
- reporter.report(
- `Standard property '${needed}' should come after vendor-prefixed property '${actual}'.`,
- unit, rule);
- }
- }
- },
- });
- }];
- CSSLint.addRule['warnings'] = [{
- name: 'Parsing warnings',
- desc: 'This rule looks for parser warnings.',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('warning', e => reporter.report(e.message, e, rule));
- }];
- CSSLint.addRule['zero-units'] = [{
- name: 'Disallow units for 0 values',
- desc: "You don't need to specify units when a value is 0.",
- url: 'https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values',
- browsers: 'All',
- }, (rule, parser, reporter) => {
- parser.addListener('property', event => {
- for (const p of event.value.parts) {
- if (p.value === 0 && (p.units || p.type === 'percentage') && p.type !== 'time') {
- reporter.report("'0' value with redundant units.", p, rule);
- }
- }
- });
- }];
- //#endregion
|