stylelint: Add `ignore: ["consecutive-duplicates-with-different-syntaxes"]` to `declaration-block-no-duplicate-properties`

What is the problem you’re trying to solve?

https://stylelint.io/user-guide/rules/declaration-block-no-duplicate-properties/#ignore-consecutive-duplicates-with-different-values

I would like to have errors on duplicate declarations with different values only if the different values have the same syntax.

A different syntax is most likely a fallback/progressive enhancement.

Error :

.foo {
  width: 100px;
  width: 150px;
}
.foo {
  width: calc(10px * 2);
  width: calc(20px * 2);
}

Ok :

.foo {
  width: 100px;
  width: 10ch;
}
.foo {
  width: calc(10px * 2);
  width: 2vi;
}

What solution would you like to see?

It is possible to analyse the value by tokenizing and checking if the token streams of both are the same.

When comparing the token streams the actual values must be ignored and only the type and unit must be considered.

If the token streams differ the syntax is not the same.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 21 (21 by maintainers)

Most upvoted comments

@jeddy3 I start implementing this issue.

I think the new option name better be consecutive-duplicates-with-different-syntax. Duplicated ignore, and there is already an option consecutive-duplicates-with-different-values.

@ybiquitous Do you prefer to discuss this further in this issue or in a new one?


In hindsight I should have taken the time to provide more examples to explain what I had in mind for this option.

I think it can be interpreted in two ways :

  • similar AST shape, but different values
  • similar syntax features, but different values

These are related, but not the same. You can have differing AST shapes while also using the same syntax features, and you can have the same AST shape while using different features.

I think that what exists know checks if the AST shape is similar, but doesn’t actually check for similar syntax features.

For example

.foo {
  width: calc(10px + 1px);
  width: calc(10px + calc(1px + 1px));
}

These have different AST shapes, but they use the same syntax features.


The vague idea I had when proposing this option was to do these steps :

  1. tokenize both values (tokenize, not parse)
  2. create Set’s for :
    • token types -> token_types
    • function names -> function_names
    • ident names -> ident_names
    • dimension units -> dimension_units
    • delims -> delims
  3. loop all tokens
    1. add the token type to token_types
    2. if the token is a function -> add the lowercase name to function_names
    3. if the token is an ident
      1. if it is a dashed ident (--foo) -> continue / skip
      2. else -> add the lowercase name to ident_names
    4. if the token is a dimension -> add the lowercase unit to dimension_units
    5. if the token is a delim -> add the delim value to delimns

I might have forgotten a token type, writing this from memory

If all sets have the same contents for both values then the syntax is similar.

The false positive is things like this :

.foo {
  background: linear-gradient(#fff 0%, grey 50%, grey 75%, black 100%);
  background: linear-gradient(#fff 0%, grey 50% 75%, black 100%);
}

These do not have the same syntax. One has a double position stop.

@ybiquitous Thanks for your reply!

For stylelint-config-recommended, something like:

position: fixed;
position: sticky; /* stylelint-disable-line declaration-block-no-duplicate-properties -- Fallback. */

Seems to tricky for being recommended. Potentially something for stylelint-config-standard, but even then personally seems too much of an interference.

If excluding keywords, the following case won’t be reported, but no problem? (min-content, max-content)

For now seems like the best option considering the potential issues as specified. We can then follow up with determining what keywords should be considered the same group. In which case min-content and max-content can be put in the same group, while fit-content and auto can be in different groups.

@carlosjeurissen width has an initial value of auto, so that fallback isn’t technically required. Was there a specific reason for setting it?

This is used to override width: 100px; which is set with a different className.

But two different keywords can be counted as two different syntaxes.

This seems like a good idea. When using initial instead of auto, this is also removed in favor of fit-content. initial for sure should be seen as a different category as fit-content.

I think changing to consecutive-duplicates-with-different-syntaxes is better because it can more strictly check fallback.

@kimulaco Thanks for the pull request. We will release this new option with the next version.

So, let me ask a question about our recommended config. The current config turns on the rule with ignore: ['consecutive-duplicates-with-different-values']. Should we change it to consecutive-duplicates-with-different-syntaxes? What do you think, guys?

'declaration-block-no-duplicate-properties': [
	true,
	{
		ignore: ['consecutive-duplicates-with-different-values'],
	},
],

https://github.com/stylelint/stylelint-config-recommended/blob/e6c852041a7c030c5763b3eca0deddc9fad852a9/index.js#L12-L17

I agree. consecutive-duplicates-with-different-syntaxes is better!

Good. I just updated the issue title again. 👍🏼

@ybiquitous Thanks. I agree. consecutive-duplicates-with-different-syntaxes is better!