UNPKG

2.92 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const _ = require('lodash');
6const hasBlock = require('../../utils/hasBlock');
7const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
8const optionsMatches = require('../../utils/optionsMatches');
9const parser = require('postcss-selector-parser');
10const report = require('../../utils/report');
11const ruleMessages = require('../../utils/ruleMessages');
12const validateOptions = require('../../utils/validateOptions');
13
14const ruleName = 'max-nesting-depth';
15
16const messages = ruleMessages(ruleName, {
17 expected: (depth) => `Expected nesting depth to be no more than ${depth}`,
18});
19
20function rule(max, options) {
21 const isIgnoreAtRule = (node) =>
22 node.type === 'atrule' && optionsMatches(options, 'ignoreAtRules', node.name);
23
24 return (root, result) => {
25 validateOptions(
26 result,
27 ruleName,
28 {
29 actual: max,
30 possible: [_.isNumber],
31 },
32 {
33 optional: true,
34 actual: options,
35 possible: {
36 ignore: ['blockless-at-rules', 'pseudo-classes'],
37 ignoreAtRules: [_.isString, _.isRegExp],
38 },
39 },
40 );
41
42 root.walkRules(checkStatement);
43 root.walkAtRules(checkStatement);
44
45 function checkStatement(statement) {
46 if (isIgnoreAtRule(statement)) {
47 return;
48 }
49
50 if (!hasBlock(statement)) {
51 return;
52 }
53
54 if (statement.selector && !isStandardSyntaxRule(statement)) {
55 return;
56 }
57
58 const depth = nestingDepth(statement);
59
60 if (depth > max) {
61 report({
62 ruleName,
63 result,
64 node: statement,
65 message: messages.expected(max),
66 });
67 }
68 }
69 };
70
71 function nestingDepth(node, level = 0) {
72 const parent = node.parent;
73
74 if (isIgnoreAtRule(parent)) {
75 return 0;
76 }
77
78 // The nesting depth level's computation has finished
79 // when this function, recursively called, receives
80 // a node that is not nested -- a direct child of the
81 // root node
82 if (parent.type === 'root' || (parent.type === 'atrule' && parent.parent.type === 'root')) {
83 return level;
84 }
85
86 function containsPseudoClassesOnly(selector) {
87 const normalized = parser().processSync(selector, { lossless: false });
88 const selectors = normalized.split(',');
89
90 return selectors.every((selector) => selector.startsWith('&:') && selector[2] !== ':');
91 }
92
93 if (
94 (optionsMatches(options, 'ignore', 'blockless-at-rules') &&
95 node.type === 'atrule' &&
96 node.every((child) => child.type !== 'decl')) ||
97 (optionsMatches(options, 'ignore', 'pseudo-classes') &&
98 node.type === 'rule' &&
99 containsPseudoClassesOnly(node.selector))
100 ) {
101 return nestingDepth(parent, level);
102 }
103
104 // Unless any of the conditions above apply, we want to
105 // add 1 to the nesting depth level and then check the parent,
106 // continuing to add and move up the hierarchy
107 // until we hit the root node
108 return nestingDepth(parent, level + 1);
109 }
110}
111
112rule.ruleName = ruleName;
113rule.messages = messages;
114module.exports = rule;