UNPKG

2.97 kBJavaScriptView Raw
1'use strict';
2
3const eachDeclarationBlock = require('../../utils/eachDeclarationBlock');
4const isCustomProperty = require('../../utils/isCustomProperty');
5const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty');
6const optionsMatches = require('../../utils/optionsMatches');
7const report = require('../../utils/report');
8const ruleMessages = require('../../utils/ruleMessages');
9const validateOptions = require('../../utils/validateOptions');
10const { isString } = require('../../utils/validateTypes');
11
12const ruleName = 'declaration-block-no-duplicate-properties';
13
14const messages = ruleMessages(ruleName, {
15 rejected: (property) => `Unexpected duplicate "${property}"`,
16});
17
18/** @type {import('stylelint').Rule} */
19const rule = (primary, secondaryOptions) => {
20 return (root, result) => {
21 const validOptions = validateOptions(
22 result,
23 ruleName,
24 { actual: primary },
25 {
26 actual: secondaryOptions,
27 possible: {
28 ignore: ['consecutive-duplicates', 'consecutive-duplicates-with-different-values'],
29 ignoreProperties: [isString],
30 },
31 optional: true,
32 },
33 );
34
35 if (!validOptions) {
36 return;
37 }
38
39 eachDeclarationBlock(root, (eachDecl) => {
40 /** @type {string[]} */
41 const decls = [];
42 /** @type {string[]} */
43 const values = [];
44
45 eachDecl((decl) => {
46 const prop = decl.prop;
47 const value = decl.value;
48
49 if (!isStandardSyntaxProperty(prop)) {
50 return;
51 }
52
53 if (isCustomProperty(prop)) {
54 return;
55 }
56
57 // Return early if the property is to be ignored
58 if (optionsMatches(secondaryOptions, 'ignoreProperties', prop)) {
59 return;
60 }
61
62 // Ignore the src property as commonly duplicated in at-fontface
63 if (prop.toLowerCase() === 'src') {
64 return;
65 }
66
67 const indexDuplicate = decls.indexOf(prop.toLowerCase());
68
69 if (indexDuplicate !== -1) {
70 if (
71 optionsMatches(
72 secondaryOptions,
73 'ignore',
74 'consecutive-duplicates-with-different-values',
75 )
76 ) {
77 // if duplicates are not consecutive
78 if (indexDuplicate !== decls.length - 1) {
79 report({
80 message: messages.rejected(prop),
81 node: decl,
82 result,
83 ruleName,
84 });
85
86 return;
87 }
88
89 // if values of consecutive duplicates are equal
90 if (value === values[indexDuplicate]) {
91 report({
92 message: messages.rejected(value),
93 node: decl,
94 result,
95 ruleName,
96 });
97
98 return;
99 }
100
101 return;
102 }
103
104 if (
105 optionsMatches(secondaryOptions, 'ignore', 'consecutive-duplicates') &&
106 indexDuplicate === decls.length - 1
107 ) {
108 return;
109 }
110
111 report({
112 message: messages.rejected(prop),
113 node: decl,
114 result,
115 ruleName,
116 });
117 }
118
119 decls.push(prop.toLowerCase());
120 values.push(value.toLowerCase());
121 });
122 });
123 };
124};
125
126rule.ruleName = ruleName;
127rule.messages = messages;
128module.exports = rule;