Skip to content

Commit f395242

Browse files
authored
feat: add property shorthand analysis (#556)
Mostly to get that export of `shorthand_properties` to use in the stylelint-plugin and projectwallace.com
1 parent b4e1a6b commit f395242

9 files changed

Lines changed: 163 additions & 15 deletions

File tree

package-lock.json

Lines changed: 13 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
"./values": {
3333
"types": "./dist/values/index.d.ts",
3434
"default": "./dist/values/index.js"
35+
},
36+
"./properties": {
37+
"types": "./dist/properties/index.d.ts",
38+
"default": "./dist/properties/index.js"
3539
}
3640
},
3741
"engines": {

src/index.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,13 @@ test('handles empty input gracefully', () => {
497497
ratio: 0,
498498
},
499499
},
500+
shorthands: {
501+
total: 0,
502+
totalUnique: 0,
503+
unique: {},
504+
uniquenessRatio: 0,
505+
ratio: 0,
506+
},
500507
browserhacks: {
501508
total: 0,
502509
totalUnique: 0,

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { AggregateCollection } from './aggregate-collection.js'
4141
import { endsWith, unquote } from './string-utils.js'
4242
import { getEmbedType } from './stylesheet/stylesheet.js'
4343
import { isIe9Hack } from './values/browserhacks.js'
44-
import { basename, SPACING_RESET_PROPERTIES, border_radius_properties } from './properties/property-utils.js'
44+
import { basename, SPACING_RESET_PROPERTIES, border_radius_properties, shorthand_properties } from './properties/property-utils.js'
4545

4646
export type Specificity = [number, number, number]
4747

@@ -170,6 +170,7 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
170170
let propertyHacks = new Collection(useLocations)
171171
let propertyVendorPrefixes = new Collection(useLocations)
172172
let customProperties = new Collection(useLocations)
173+
let shorthands = new Collection(useLocations)
173174
let propertyComplexities = new AggregateCollection()
174175

175176
// Values
@@ -501,6 +502,10 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
501502
} else {
502503
propertyComplexities.push(1)
503504
}
505+
506+
if (shorthand_properties.has(normalizedProperty)) {
507+
shorthands.p(property, propertyLoc)
508+
}
504509
//#endregion PROPERTIES
505510

506511
//#region VALUES
@@ -992,6 +997,9 @@ function analyzeInternal<T extends boolean>(css: string, options: Options, useLo
992997
ratio: ratio(importantCustomProperties.size(), customProperties.size()),
993998
}),
994999
}),
1000+
shorthands: assign(shorthands.c(), {
1001+
ratio: ratio(shorthands.size(), properties.size()),
1002+
}),
9951003
browserhacks: assign(propertyHacks.c(), {
9961004
ratio: ratio(propertyHacks.size(), properties.size()),
9971005
}),

src/properties/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export {
2+
shorthand_properties,
3+
SPACING_RESET_PROPERTIES as spacing_reset_properties,
4+
border_radius_properties,
5+
basename,
6+
isHack as is_property_hack
7+
} from './property-utils'

src/properties/properties.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,68 @@ describe('vendor prefixes', () => {
8484
})
8585
})
8686

87+
describe('shorthands', () => {
88+
test('counts shorthand properties', () => {
89+
const fixture = `
90+
a {
91+
margin: 0;
92+
padding: 0;
93+
color: red;
94+
font: bold 1em/1.5 sans-serif;
95+
}
96+
`
97+
const actual = analyze(fixture).properties.shorthands
98+
99+
expect(actual.total).toEqual(3)
100+
expect(actual.totalUnique).toEqual(3)
101+
expect(actual.unique).toEqual({ margin: 1, padding: 1, font: 1 })
102+
expect(actual.ratio).toEqual(3 / 4)
103+
})
104+
105+
test('counts duplicate shorthands', () => {
106+
const fixture = `
107+
a { margin: 0; }
108+
b { margin: 10px; }
109+
`
110+
const actual = analyze(fixture).properties.shorthands
111+
112+
expect(actual.total).toEqual(2)
113+
expect(actual.totalUnique).toEqual(1)
114+
expect(actual.unique).toEqual({ margin: 2 })
115+
expect(actual.uniquenessRatio).toEqual(1 / 2)
116+
})
117+
118+
test('non-shorthand properties are not counted', () => {
119+
const fixture = `a { color: red; font-size: 1em; }`
120+
const actual = analyze(fixture).properties.shorthands
121+
122+
expect(actual.total).toEqual(0)
123+
expect(actual.totalUnique).toEqual(0)
124+
expect(actual.unique).toEqual({})
125+
expect(actual.ratio).toEqual(0)
126+
})
127+
128+
test('preserves original casing of shorthand property name', () => {
129+
const fixture = `a { MARGIN: 0; }`
130+
const actual = analyze(fixture).properties.shorthands
131+
132+
expect(actual.total).toEqual(1)
133+
expect(actual.unique).toEqual({ MARGIN: 1 })
134+
})
135+
136+
test('counts shorthands inside at-rules', () => {
137+
const fixture = `
138+
@media screen {
139+
a { padding: 0; }
140+
}
141+
`
142+
const actual = analyze(fixture).properties.shorthands
143+
144+
expect(actual.total).toEqual(1)
145+
expect(actual.unique).toEqual({ padding: 1 })
146+
})
147+
})
148+
87149
describe('browserhacks', () => {
88150
test('counts browser hacks', () => {
89151
const fixture = `

src/properties/property-utils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,56 @@
11
import { KeywordSet } from '../keyword-set.js'
22
import { is_custom, is_vendor_prefixed } from '@projectwallace/css-parser'
33

4+
export const shorthand_properties = new Set([
5+
'all',
6+
'animation',
7+
'background',
8+
'border',
9+
'border-block-end',
10+
'border-block-start',
11+
'border-bottom',
12+
'border-color',
13+
'border-image',
14+
'border-inline-end',
15+
'border-inline-start',
16+
'border-left',
17+
'border-radius',
18+
'border-right',
19+
'border-style',
20+
'border-top',
21+
'border-width',
22+
'column-rule',
23+
'columns',
24+
'contain-intrinsic-size',
25+
'flex',
26+
'flex-flow',
27+
'font',
28+
'gap',
29+
'grid',
30+
'grid-area',
31+
'grid-column',
32+
'grid-row',
33+
'grid-template',
34+
'inset',
35+
'list-style',
36+
'margin',
37+
'mask',
38+
'offset',
39+
'outline',
40+
'overflow',
41+
'padding',
42+
'place-content',
43+
'place-items',
44+
'place-self',
45+
'scroll-margin',
46+
'scroll-padding',
47+
'scroll-timeline',
48+
'text-decoration',
49+
'text-emphasis',
50+
'transition',
51+
'vertical-align',
52+
])
53+
454
export const SPACING_RESET_PROPERTIES = new Set([
555
'margin',
656
'margin-block',

src/vendor-prefix.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { is_vendor_prefixed } from '@projectwallace/css-parser'
22

3-
/** Kept for backwards compatibility */
3+
/**
4+
* Kept for backwards compatibility
5+
* @deprecated
6+
* */
47
export function hasVendorPrefix(keyword: string): boolean {
58
return is_vendor_prefixed(keyword)
69
}

tsdown.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { defineConfig } from 'tsdown'
22
import { codecovRollupPlugin } from '@codecov/rollup-plugin'
33

44
export default defineConfig({
5-
entry: ['src/index.ts', 'src/selectors/index.ts', 'src/atrules/index.ts', 'src/values/index.ts'],
5+
entry: [
6+
'src/index.ts',
7+
'src/selectors/index.ts',
8+
'src/atrules/index.ts',
9+
'src/values/index.ts',
10+
'src/properties/index.ts'
11+
],
612
platform: 'neutral',
713
publint: true,
814
plugins: [

0 commit comments

Comments
 (0)