diff --git a/package-lock.json b/package-lock.json index 53c69b2..6473ca5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@bramus/specificity": "^2.4.1", - "css-tree": "^2.3.1" + "css-tree": "^3.1.0" }, "devDependencies": { "@codecov/vite-plugin": "^1.9.0", @@ -147,25 +147,6 @@ "specificity": "bin/cli.js" } }, - "node_modules/@bramus/specificity/node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/@bramus/specificity/node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "license": "CC0-1.0" - }, "node_modules/@codecov/bundler-plugin-core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.9.0.tgz", @@ -1963,11 +1944,12 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -2458,9 +2440,10 @@ } }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" }, "node_modules/minimatch": { "version": "9.0.5", @@ -3461,22 +3444,6 @@ "integrity": "sha512-cI7AmySy3FGIC59YRusPWnscNr2/M60HKTvE2h63EMGZPdB1LLT2G7OE3XB8tajjX7hVBR0YXUVvTEr4JHtLsg==", "requires": { "css-tree": "^3.0.0" - }, - "dependencies": { - "css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "requires": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - } - }, - "mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==" - } } }, "@codecov/bundler-plugin-core": { @@ -4572,11 +4539,11 @@ } }, "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "requires": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, @@ -4909,9 +4876,9 @@ } }, "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==" }, "minimatch": { "version": "9.0.5", diff --git a/package.json b/package.json index a0d77d2..7f588b9 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ ], "dependencies": { "@bramus/specificity": "^2.4.1", - "css-tree": "^2.3.1" + "css-tree": "^3.1.0" }, "devDependencies": { "@codecov/vite-plugin": "^1.9.0", @@ -54,8 +54,5 @@ "uvu": "^0.5.6", "vite": "^6.3.4", "vite-plugin-dts": "^4.5.0" - }, - "mangle": { - "regex": "^_[^_]" } } \ No newline at end of file diff --git a/src/atrules/atrules.js b/src/atrules/atrules.js index 8ed71a0..45d0e87 100644 --- a/src/atrules/atrules.js +++ b/src/atrules/atrules.js @@ -3,7 +3,6 @@ import walk from 'css-tree/walker' import { Identifier, MediaQuery, - MediaFeature, Declaration, } from '../css-tree-node-types.js' @@ -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 diff --git a/src/atrules/atrules.test.js b/src/atrules/atrules.test.js index 4fc5480..d2ff907 100644 --- a/src/atrules/atrules.test.js +++ b/src/atrules/atrules.test.js @@ -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; @@ -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, @@ -173,7 +180,7 @@ AtRules('finds @layer', () => { "overrides": 1, "": 2, }, - uniquenessRatio: 26 / 48 + uniquenessRatio: 28 / 50 } assert.equal(actual, expected) @@ -389,13 +396,18 @@ 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, @@ -403,12 +415,44 @@ AtRules('finds @imports', () => { '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', () => { diff --git a/src/index.js b/src/index.js index 7b4438b..ba3e56c 100644 --- a/src/index.js +++ b/src/index.js @@ -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 @@ -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 @@ -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