Skip to content

Csstree3 #460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 17 additions & 50 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,13 @@
],
"dependencies": {
"@bramus/specificity": "^2.4.1",
"css-tree": "^2.3.1"
"css-tree": "^3.1.0"
},
"devDependencies": {
"@codecov/vite-plugin": "^1.9.0",
"c8": "^10.1.3",
"uvu": "^0.5.6",
"vite": "^6.3.4",
"vite-plugin-dts": "^4.5.0"
},
"mangle": {
"regex": "^_[^_]"
}
}
12 changes: 3 additions & 9 deletions src/atrules/atrules.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import walk from 'css-tree/walker'
import {
Identifier,
MediaQuery,
MediaFeature,
Declaration,
} from '../css-tree-node-types.js'

Expand Down Expand Up @@ -54,21 +53,16 @@ export function isMediaBrowserhack(prelude) {
let returnValue = false

walk(prelude, function (node) {
let children = node.children
let name = node.name
let value = node.value

if (node.type === MediaQuery
&& children.size === 1
&& children.first.type === Identifier
) {
let n = children.first.name
if (node.type === MediaQuery && node.mediaType !== null) {
// Note: CSSTree adds a trailing space to \\9
if (startsWith('\\0', n) || endsWith('\\9 ', n)) {
if (startsWith('\\0', node.mediaType) || endsWith('\\9 ', node.mediaType)) {
returnValue = true
return this.break
}
} else if (node.type === MediaFeature) {
} else if (node.type === 'Feature' && node.kind === 'media') {
if (value && value.unit && value.unit === '\\0') {
returnValue = true
return this.break
Expand Down
60 changes: 52 additions & 8 deletions src/atrules/atrules.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ AtRules('finds @layer', () => {
// Fixture is pretty much a straight copy from all code examples from
// https://css-tricks.com/css-cascade-layers/
const fixture = `
@import url('test.css') layer;
@import url('test.css') layer();
@import url('test.css') layer(test);
@import url('test.css') layer(test.abc);

/* establish a layer order up-front, from lowest to highest priority */
@layer reset, defaults, patterns, components, utilities, overrides;

Expand Down Expand Up @@ -143,9 +148,11 @@ AtRules('finds @layer', () => {
`
const actual = analyze(fixture).atrules.layer
const expected = {
total: 48,
totalUnique: 26,
total: 50,
totalUnique: 28,
unique: {
"test": 1,
"test.abc": 1,
"defaults": 5,
"layer-1": 1,
"layer-2": 1,
Expand Down Expand Up @@ -173,7 +180,7 @@ AtRules('finds @layer', () => {
"overrides": 1,
"<anonymous>": 2,
},
uniquenessRatio: 26 / 48
uniquenessRatio: 28 / 50
}

assert.equal(actual, expected)
Expand Down Expand Up @@ -389,26 +396,63 @@ AtRules('finds @imports', () => {

@import url('remedy.css') layer(reset.remedy);

@import 'test.css' supports((display: grid));
@import 'test.css' supports(not (display: grid));
@import 'test.css' supports(selector(a:has(b)));
/*@import "test.css" supports((selector(h2 > p) and (font-tech(color-COLRv1))));*/

/* @import without prelude */
@import;
`
const actual = analyze(fixture).atrules.import
const actual = analyze(fixture).atrules
const expected = {
total: 7,
totalUnique: 7,
total: 10,
totalUnique: 10,
unique: {
'"https://example.com/without-url"': 1,
'url("https://example.com/with-url")': 1,
'url("https://example.com/with-media-query") screen and (min-width: 33em)': 1,
'url("https://example.com/with-multiple-media-queries") screen, projection': 1,
'url(\'example.css\') layer(named-layer)': 1,
'url(\'../example.css\') layer': 1,
'url(\'remedy.css\') layer(reset.remedy)': 1,
"url('remedy.css') layer(reset.remedy)": 1,
"'test.css' supports((display: grid))": 1,
"'test.css' supports(not (display: grid))": 1,
"'test.css' supports(selector(a:has(b)))": 1,
},
uniquenessRatio: 1,
}

assert.equal(actual, expected)
assert.equal(actual.import, expected)

const expected_supports = {
total: 3,
totalUnique: 3,
unique: {
"(display: grid)": 1,
"not (display: grid)": 1,
"selector(a:has(b))": 1,
},
uniquenessRatio: 1,
browserhacks: {
total: 0,
totalUnique: 0,
unique: {},
uniquenessRatio: 0,
},
}
assert.equal(actual.supports, expected_supports)

const expected_layers = {
total: 2,
totalUnique: 2,
unique: {
"named-layer": 1,
'reset.remedy': 1,
},
uniquenessRatio: 1,
}
assert.equal(actual.layer, expected_layers)
})

AtRules('finds @charsets', () => {
Expand Down
18 changes: 14 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ export function analyze(css, options = {}) {
}
keyframes.p(name, loc)
} else if (atRuleName === 'import') {
walk(node, function (prelude_node) {
if (prelude_node.type === 'Condition' && prelude_node.kind === 'supports') {
let prelude = stringifyNode(prelude_node)

supports.p(prelude, prelude_node.loc)
return this.break
}
})
imports.p(preludeStr, loc)
// TODO: analyze complexity of media queries, layers and supports in @import
// see https://github.com/projectwallace/css-analyzer/issues/326
Expand All @@ -256,10 +264,6 @@ export function analyze(css, options = {}) {
} else if (atRuleName === 'container') {
containers.p(preludeStr, loc)
// TODO: calculate complexity of container 'declaration'
} else if (atRuleName === 'layer') {
preludeStr
.split(',')
.forEach(name => layers.p(name.trim(), loc))
} else if (atRuleName === 'property') {
registeredProperties.p(preludeStr, loc)
// TODO: add complexity for descriptors
Expand All @@ -273,6 +277,12 @@ export function analyze(css, options = {}) {
atRuleComplexities.push(complexity)
break
}
case 'Layer': {
if (node.name !== null) {
layers.p(node.name, node.loc)
}
break
}
case Rule: {
let prelude = node.prelude
let block = node.block
Expand Down