3 const _ = require("lodash");
4 const isCustomProperty = require("../../utils/isCustomProperty");
5 const isStandardSyntaxProperty = require("../../utils/isStandardSyntaxProperty");
6 const optionsMatches = require("../../utils/optionsMatches");
7 const report = require("../../utils/report");
8 const ruleMessages = require("../../utils/ruleMessages");
9 const validateOptions = require("../../utils/validateOptions");
11 const ruleName = "declaration-block-no-duplicate-properties";
13 const messages = ruleMessages(ruleName, {
14 rejected: property => `Unexpected duplicate "${property}"`
17 const rule = function(on, options) {
18 return (root, result) => {
19 const validOptions = validateOptions(
27 "consecutive-duplicates",
28 "consecutive-duplicates-with-different-values"
30 ignoreProperties: [_.isString]
39 // In order to accommodate nested blocks (postcss-nested),
40 // we need to run a shallow loop (instead of eachDecl() or eachRule(),
41 // which loop recursively) and allow each nested block to accumulate
42 // its own list of properties -- so that a property in a nested rule
43 // does not conflict with the same property in the parent rule
45 if (node.type === "rule" || node.type === "atrule") {
46 checkRulesInNode(node);
50 function checkRulesInNode(node) {
55 if (child.nodes && child.nodes.length) {
56 checkRulesInNode(child);
59 if (child.type !== "decl") {
63 const prop = child.prop;
64 const value = child.value;
66 if (!isStandardSyntaxProperty(prop)) {
69 if (isCustomProperty(prop)) {
73 // Return early if the property is to be ignored
74 if (optionsMatches(options, "ignoreProperties", prop)) {
78 // Ignore the src property as commonly duplicated in at-fontface
79 if (prop.toLowerCase() === "src") {
83 const indexDuplicate = decls.indexOf(prop.toLowerCase());
85 if (indexDuplicate !== -1) {
90 "consecutive-duplicates-with-different-values"
93 // if duplicates are not consecutive
94 if (indexDuplicate !== decls.length - 1) {
96 message: messages.rejected(prop),
103 // if values of consecutive duplicates are equal
104 if (value === values[indexDuplicate]) {
106 message: messages.rejected(value),
117 optionsMatches(options, "ignore", "consecutive-duplicates") &&
118 indexDuplicate === decls.length - 1
124 message: messages.rejected(prop),
131 decls.push(prop.toLowerCase());
132 values.push(value.toLowerCase());
138 rule.ruleName = ruleName;
139 rule.messages = messages;
140 module.exports = rule;