Skip to content

Commit

Permalink
Fix declaration-block-no-shorthand-property-overrides false negativ…
Browse files Browse the repository at this point in the history
…es for `font` and `border` (stylelint#7606)
  • Loading branch information
Mouvedia authored and emmacharp committed May 7, 2024
1 parent c57d98a commit ba11020
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-flies-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": minor
---

Fixed: `declaration-block-no-shorthand-property-overrides` false negatives for `font` and `border`
42 changes: 42 additions & 0 deletions lib/reference/properties.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,40 @@ const acceptCustomIdentsProperties = new Set([
'list-style-type',
]);

const shorthandToResetToInitialProperty = new Map([
[
'border',
new Set([
'border-image',
'border-image-outset',
'border-image-repeat',
'border-image-slice',
'border-image-source',
'border-image-width',
]),
],
[
/** @see https://www.w3.org/TR/css-fonts-4/#font-prop */
'font',
new Set([
// prettier-ignore
'font-feature-settings',
'font-kerning',
'font-language-override',
'font-optical-sizing',
'font-size-adjust',
'font-variant-alternates',
'font-variant-caps',
'font-variant-east-asian',
'font-variant-emoji',
'font-variant-ligatures',
'font-variant-numeric',
'font-variant-position',
'font-variation-settings',
]),
],
]);

/** @type {import('stylelint').LonghandSubPropertiesOfShorthandProperties} */
const longhandSubPropertiesOfShorthandProperties = new Map([
// Sort alphabetically
Expand Down Expand Up @@ -247,6 +281,13 @@ const longhandSubPropertiesOfShorthandProperties = new Map([
new Set([
// prettier-ignore
'font-style',
/**
* reset explicitly: normal | small-caps
* reset implicitly: all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps
* i.e. either way it will be reset
* @todo add font-variant shorthand to longhandSubPropertiesOfShorthandProperties
* {@link https://www.w3.org/TR/css-fonts-4/#font-variant-prop World Wide Web Consortium}
*/
'font-variant',
'font-weight',
'font-stretch',
Expand Down Expand Up @@ -573,3 +614,4 @@ exports.acceptCustomIdentsProperties = acceptCustomIdentsProperties;
exports.longhandSubPropertiesOfShorthandProperties = longhandSubPropertiesOfShorthandProperties;
exports.longhandTimeProperties = longhandTimeProperties;
exports.shorthandTimeProperties = shorthandTimeProperties;
exports.shorthandToResetToInitialProperty = shorthandToResetToInitialProperty;
41 changes: 41 additions & 0 deletions lib/reference/properties.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,40 @@ export const acceptCustomIdentsProperties = new Set([
'list-style-type',
]);

export const shorthandToResetToInitialProperty = new Map([
[
'border',
new Set([
'border-image',
'border-image-outset',
'border-image-repeat',
'border-image-slice',
'border-image-source',
'border-image-width',
]),
],
[
/** @see https://www.w3.org/TR/css-fonts-4/#font-prop */
'font',
new Set([
// prettier-ignore
'font-feature-settings',
'font-kerning',
'font-language-override',
'font-optical-sizing',
'font-size-adjust',
'font-variant-alternates',
'font-variant-caps',
'font-variant-east-asian',
'font-variant-emoji',
'font-variant-ligatures',
'font-variant-numeric',
'font-variant-position',
'font-variation-settings',
]),
],
]);

/** @type {import('stylelint').LonghandSubPropertiesOfShorthandProperties} */
export const longhandSubPropertiesOfShorthandProperties = new Map([
// Sort alphabetically
Expand Down Expand Up @@ -243,6 +277,13 @@ export const longhandSubPropertiesOfShorthandProperties = new Map([
new Set([
// prettier-ignore
'font-style',
/**
* reset explicitly: normal | small-caps
* reset implicitly: all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps
* i.e. either way it will be reset
* @todo add font-variant shorthand to longhandSubPropertiesOfShorthandProperties
* {@link https://www.w3.org/TR/css-fonts-4/#font-variant-prop World Wide Web Consortium}
*/
'font-variant',
'font-weight',
'font-stretch',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ testRule({
endLine: 1,
endColumn: 33,
},
{
code: 'a { border-image: url("foo.png"); border: 1px solid black; }',
message: messages.rejected('border', 'border-image'),
line: 1,
column: 35,
endLine: 1,
endColumn: 41,
},
{
code: 'a { border-image-source: url("foo.png"); border: 1px solid black; }',
message: messages.rejected('border', 'border-image-source'),
line: 1,
column: 42,
endLine: 1,
endColumn: 48,
},
{
code: 'a { pAdDiNg-lEfT: 10Px; pAdDiNg: 20Px; }',
message: messages.rejected('pAdDiNg', 'pAdDiNg-lEfT'),
Expand Down Expand Up @@ -161,6 +177,39 @@ testRule({
endLine: 1,
endColumn: 61,
},
{
code: 'a { font-variant: small-caps; font: sans-serif; }',
message: messages.rejected('font', 'font-variant'),
description: 'CSS2 explicit reset',
line: 1,
column: 31,
endLine: 1,
endColumn: 35,
},
{
code: 'a { font-variant: all-small-caps; font: sans-serif; }',
message: messages.rejected('font', 'font-variant'),
description: 'CSS3 implicit reset',
line: 1,
column: 35,
endLine: 1,
endColumn: 39,
},
{
code: 'a { font-size-adjust: 0.545; font: Verdana; }',
message: messages.rejected('font', 'font-size-adjust'),
line: 1,
column: 30,
endLine: 1,
endColumn: 34,
},
{
code: 'a { font-variant-caps: small-caps; font-variant: normal; }',
message: messages.rejected('font-variant', 'font-variant-caps'),
line: 1,
endLine: 1,
skip: true,
},
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
'use strict';

const eachDeclarationBlock = require('../../utils/eachDeclarationBlock.cjs');
const properties = require('../../reference/properties.cjs');
const report = require('../../utils/report.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const uniteSets = require('../../utils/uniteSets.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');
const vendor = require('../../utils/vendor.cjs');
const properties = require('../../reference/properties.cjs');

const ruleName = 'declaration-block-no-shorthand-property-overrides';

Expand Down Expand Up @@ -36,19 +37,18 @@ const rule = (primary) => {
const prop = decl.prop;
const unprefixedProp = vendor.unprefixed(prop).toLowerCase();
const prefix = vendor.prefix(prop).toLowerCase();

const overrideables = /** @type {Map<string, Set<string>>} */ (
const subProperties = /** @type {Map<string, Set<string>>} */ (
properties.longhandSubPropertiesOfShorthandProperties
).get(unprefixedProp);
const resettables = properties.shorthandToResetToInitialProperty.get(unprefixedProp);
const union = uniteSets(subProperties ?? [], resettables ?? []);

declarations.set(prop.toLowerCase(), prop);

if (!overrideables) {
return;
}
if (union.size === 0) return;

for (const longhandProp of overrideables) {
const declaration = declarations.get(prefix + longhandProp);
for (const property of union) {
const declaration = declarations.get(prefix + property);

if (!declaration) {
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import eachDeclarationBlock from '../../utils/eachDeclarationBlock.mjs';
import { longhandSubPropertiesOfShorthandProperties } from '../../reference/properties.mjs';
import report from '../../utils/report.mjs';
import ruleMessages from '../../utils/ruleMessages.mjs';
import uniteSets from '../../utils/uniteSets.mjs';
import validateOptions from '../../utils/validateOptions.mjs';
import vendor from '../../utils/vendor.mjs';

import {
longhandSubPropertiesOfShorthandProperties,
shorthandToResetToInitialProperty,
} from '../../reference/properties.mjs';

const ruleName = 'declaration-block-no-shorthand-property-overrides';

const messages = ruleMessages(ruleName, {
Expand Down Expand Up @@ -32,19 +37,18 @@ const rule = (primary) => {
const prop = decl.prop;
const unprefixedProp = vendor.unprefixed(prop).toLowerCase();
const prefix = vendor.prefix(prop).toLowerCase();

const overrideables = /** @type {Map<string, Set<string>>} */ (
const subProperties = /** @type {Map<string, Set<string>>} */ (
longhandSubPropertiesOfShorthandProperties
).get(unprefixedProp);
const resettables = shorthandToResetToInitialProperty.get(unprefixedProp);
const union = uniteSets(subProperties ?? [], resettables ?? []);

declarations.set(prop.toLowerCase(), prop);

if (!overrideables) {
return;
}
if (union.size === 0) return;

for (const longhandProp of overrideables) {
const declaration = declarations.get(prefix + longhandProp);
for (const property of union) {
const declaration = declarations.get(prefix + property);

if (!declaration) {
continue;
Expand Down
1 change: 1 addition & 0 deletions lib/utils/uniteSets.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Unite two or more sets
*
* @param {Iterable<string>[]} args
* @see {@link https://github.com/microsoft/TypeScript/issues/57228|GitHub}
*/
function uniteSets(...args) {
return new Set([...args].reduce((result, set) => [...result, ...set], []));
Expand Down
1 change: 1 addition & 0 deletions lib/utils/uniteSets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Unite two or more sets
*
* @param {Iterable<string>[]} args
* @see {@link https://github.com/microsoft/TypeScript/issues/57228|GitHub}
*/
export default function uniteSets(...args) {
return new Set([...args].reduce((result, set) => [...result, ...set], []));
Expand Down

0 comments on commit ba11020

Please sign in to comment.