diff --git a/package-lock.json b/package-lock.json index a30ea10..790be83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@bramus/specificity": "^2.3.0", - "css-tree": "^2.3.1" + "css-tree": "git://github.com/csstree/csstree.git#26a64c716fd7913a4cfa6b735791e558737e1bf2" }, "devDependencies": { "microbundle": "^0.15.1", @@ -2295,10 +2295,11 @@ }, "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==", + "resolved": "git+ssh://git@github.com/csstree/csstree.git#26a64c716fd7913a4cfa6b735791e558737e1bf2", + "integrity": "sha512-H9DUqHJ6gSayg06fRmxkHSBhqmPsWDmyHeP9O/E2qTYRQyPLjNPcXsFXVt8PGjNUGsN3tI4mMWZnZLca4r9c3g==", + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.1.0", "source-map-js": "^1.0.1" }, "engines": { @@ -3612,9 +3613,9 @@ } }, "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.1.0", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.1.0.tgz", + "integrity": "sha512-dbAWH6A+2NGuVJlQFrTKHJc07Vqn5frnhyTOGz+7BsK7V2hHdoBcwoiyV3QVhLHYpM/zqe2OSUn5ZWbVXLBB8A==" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -6897,11 +6898,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": "git+ssh://git@github.com/csstree/csstree.git#26a64c716fd7913a4cfa6b735791e558737e1bf2", + "integrity": "sha512-H9DUqHJ6gSayg06fRmxkHSBhqmPsWDmyHeP9O/E2qTYRQyPLjNPcXsFXVt8PGjNUGsN3tI4mMWZnZLca4r9c3g==", + "from": "css-tree@git://github.com/csstree/csstree.git#26a64c716fd7913a4cfa6b735791e558737e1bf2", "requires": { - "mdn-data": "2.0.30", + "mdn-data": "2.1.0", "source-map-js": "^1.0.1" } }, @@ -7776,9 +7777,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.1.0", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.1.0.tgz", + "integrity": "sha512-dbAWH6A+2NGuVJlQFrTKHJc07Vqn5frnhyTOGz+7BsK7V2hHdoBcwoiyV3QVhLHYpM/zqe2OSUn5ZWbVXLBB8A==" }, "merge-stream": { "version": "2.0.0", diff --git a/package.json b/package.json index 9cddcdd..2b33732 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ ], "dependencies": { "@bramus/specificity": "^2.3.0", - "css-tree": "^2.3.1" + "css-tree": "git://github.com/csstree/csstree.git#26a64c716fd7913a4cfa6b735791e558737e1bf2" }, "devDependencies": { "microbundle": "^0.15.1", diff --git a/src/__fixtures__/bol-com-20231008.json b/src/__fixtures__/bol-com-20231008.json index 6655514..16657f0 100644 --- a/src/__fixtures__/bol-com-20231008.json +++ b/src/__fixtures__/bol-com-20231008.json @@ -199,7 +199,7 @@ "total": 5, "totalUnique": 3, "unique": { - "((-webkit-clip-path:polygon(0 50%, 100% 100%, 100%0)) or (clip-path:polygon(0 50%, 100% 100%, 100%0)))": 3, + "(-webkit-clip-path:polygon(0 50%, 100% 100%, 100%0)) or (clip-path:polygon(0 50%, 100% 100%, 100%0))": 3, "(-webkit-clip-path:polygon(0 50%, 100% 50%, 100%0))": 1, "(-webkit-clip-path:polygon(0 50%, 100% 20%, 100% 80%))": 1 }, diff --git a/src/__fixtures__/cnn-20231008.json b/src/__fixtures__/cnn-20231008.json index 68e5457..af4d11a 100644 --- a/src/__fixtures__/cnn-20231008.json +++ b/src/__fixtures__/cnn-20231008.json @@ -1,6 +1,6 @@ { "stylesheet": { - "sourceLinesOfCode": 10127, + "sourceLinesOfCode": 10132, "linesOfCode": 14690, "size": 821224, "comments": { @@ -6942,9 +6942,9 @@ } }, "selectors": { - "total": 4036, - "totalUnique": 2618, - "uniquenessRatio": 0.6486620416253717, + "total": 4041, + "totalUnique": 2619, + "uniquenessRatio": 0.6481069042316259, "specificity": { "min": [ 0, @@ -6962,9 +6962,9 @@ 1936 ], "mean": [ - 0.0004955401387512388, - 3.064420218037661, - 0.4796828543111992 + 0.0004949269982677555, + 3.0606285572878003, + 0.47908933432318734 ], "mode": [ 0, @@ -7462,6 +7462,11 @@ 4, 1 ], + [ + 0, + 0, + 0 + ], [ 0, 3, @@ -7562,6 +7567,11 @@ 5, 2 ], + [ + 0, + 0, + 0 + ], [ 0, 5, @@ -8037,6 +8047,11 @@ 6, 1 ], + [ + 0, + 0, + 0 + ], [ 0, 5, @@ -14492,6 +14507,11 @@ 6, 1 ], + [ + 0, + 0, + 0 + ], [ 0, 3, @@ -27147,6 +27167,11 @@ 6, 1 ], + [ + 0, + 0, + 0 + ], [ 0, 4, @@ -27158,7 +27183,7 @@ 1 ] ], - "total": 4036, + "total": 4041, "totalUnique": 27, "unique": { "0,0,1": 17, @@ -27170,6 +27195,7 @@ "0,3,1": 284, "0,4,0": 239, "0,4,1": 336, + "0,0,0": 6, "0,6,1": 113, "0,5,1": 373, "0,5,2": 114, @@ -27186,24 +27212,23 @@ "0,1,2": 8, "0,1,4": 1, "0,7,2": 4, - "0,0,0": 1, "0,8,0": 2 }, - "uniquenessRatio": 0.006689791873141724 + "uniquenessRatio": 0.0066815144766146995 }, "complexity": { "min": 1, "max": 18, - "mean": 5.616947472745292, + "mean": 5.612472160356347, "mode": 3, "median": 5, "range": 17, - "sum": 22670, - "total": 4036, + "sum": 22680, + "total": 4041, "totalUnique": 17, "unique": { "1": 552, - "2": 208, + "2": 213, "3": 727, "4": 414, "5": 346, @@ -27220,7 +27245,7 @@ "16": 8, "18": 4 }, - "uniquenessRatio": 0.00421209117938553, + "uniquenessRatio": 0.004206879485275922, "items": [ 1, 1, @@ -27319,6 +27344,7 @@ 4, 8, 8, + 2, 5, 5, 5, @@ -27339,6 +27365,7 @@ 8, 11, 11, + 2, 10, 11, 9, @@ -27434,6 +27461,7 @@ 10, 12, 12, + 2, 10, 7, 9, @@ -28725,6 +28753,7 @@ 10, 12, 12, + 2, 5, 5, 5, @@ -31256,6 +31285,7 @@ 1, 8, 13, + 2, 6, 11 ] @@ -31268,7 +31298,7 @@ "#taui-mvpdpicker": 1 }, "uniquenessRatio": 1, - "ratio": 0.0004955401387512388 + "ratio": 0.0004949269982677555 }, "accessibility": { "total": 4, @@ -31280,7 +31310,7 @@ ".kiln-edit-mode .kiln-overlay-form.byline button[aria-label='Do Magic']:hover": 1 }, "uniquenessRatio": 1, - "ratio": 0.0009910802775024777 + "ratio": 0.000989853996535511 }, "keyframes": { "total": 12, @@ -31304,7 +31334,7 @@ ".ad-feedback__container .ad-feedback__comment:-ms-input-placeholder": 1 }, "uniquenessRatio": 1, - "ratio": 0.0009910802775024777 + "ratio": 0.000989853996535511 }, "combinators": { "total": 5663, diff --git a/src/__fixtures__/gazelle-20231008.json b/src/__fixtures__/gazelle-20231008.json index ada1f35..ce296d2 100644 --- a/src/__fixtures__/gazelle-20231008.json +++ b/src/__fixtures__/gazelle-20231008.json @@ -190,7 +190,7 @@ "totalUnique": 3, "unique": { "(-webkit-overflow-scrolling: touch)": 1, - "(-webkit-appearance:none) and (not (stroke-color:transparent))": 1, + "(-webkit-appearance:none) and (not (stroke-color:transparent)": 1, "(-webkit-appearance:none)": 1 }, "uniquenessRatio": 1, @@ -198,7 +198,7 @@ "total": 2, "totalUnique": 2, "unique": { - "(-webkit-appearance:none) and (not (stroke-color:transparent))": 1, + "(-webkit-appearance:none) and (not (stroke-color:transparent)": 1, "(-webkit-appearance:none)": 1 }, "uniquenessRatio": 1 diff --git a/src/__fixtures__/github-20231008.json b/src/__fixtures__/github-20231008.json index eeba813..c3cbfc9 100644 --- a/src/__fixtures__/github-20231008.json +++ b/src/__fixtures__/github-20231008.json @@ -1,6 +1,6 @@ { "stylesheet": { - "sourceLinesOfCode": 33278, + "sourceLinesOfCode": 33281, "linesOfCode": 51852, "size": 1206900, "comments": { @@ -27243,9 +27243,9 @@ } }, "selectors": { - "total": 10736, - "totalUnique": 9591, - "uniquenessRatio": 0.893349478390462, + "total": 10739, + "totalUnique": 9594, + "uniquenessRatio": 0.893379271813018, "specificity": { "min": [ 0, @@ -27259,13 +27259,13 @@ ], "sum": [ 12, - 19836, + 19841, 2273 ], "mean": [ - 0.0011177347242921013, - 1.8476154992548435, - 0.21171758569299554 + 0.0011174224788155322, + 1.8475649501815812, + 0.21165844119564206 ], "mode": [ 0, @@ -42508,6 +42508,11 @@ 2, 0 ], + [ + 0, + 2, + 0 + ], [ 0, 2, @@ -46648,6 +46653,11 @@ 1, 2 ], + [ + 0, + 1, + 0 + ], [ 0, 2, @@ -60193,6 +60203,11 @@ 2, 0 ], + [ + 0, + 2, + 0 + ], [ 0, 3, @@ -80959,11 +80974,11 @@ 0 ] ], - "total": 10736, + "total": 10739, "totalUnique": 35, "unique": { - "0,1,0": 4261, - "0,2,0": 2345, + "0,1,0": 4262, + "0,2,0": 2347, "0,1,1": 631, "0,2,1": 568, "0,0,1": 115, @@ -80998,23 +81013,23 @@ "1,0,4": 1, "1,0,3": 1 }, - "uniquenessRatio": 0.0032600596125186287 + "uniquenessRatio": 0.0032591488965453023 }, "complexity": { "min": 1, "max": 17, - "mean": 2.779433681073025, + "mean": 2.7793090604339326, "mode": 1, "median": 2, "range": 16, - "sum": 29840, - "total": 10736, + "sum": 29847, + "total": 10739, "totalUnique": 16, "unique": { - "1": 4355, - "2": 1060, + "1": 4356, + "2": 1061, "3": 1966, - "4": 1317, + "4": 1318, "5": 1040, "6": 477, "7": 299, @@ -81028,7 +81043,7 @@ "15": 1, "17": 1 }, - "uniquenessRatio": 0.0014903129657228018, + "uniquenessRatio": 0.0014898966384207096, "items": [ 1, 1, @@ -84076,6 +84091,7 @@ 3, 4, 2, + 2, 5, 3, 1, @@ -84904,6 +84920,7 @@ 1, 4, 3, + 1, 4, 1, 2, @@ -87612,6 +87629,7 @@ 1, 4, 6, + 4, 2, 5, 6, @@ -91786,7 +91804,7 @@ "#user-content-toctitle h2": 1 }, "uniquenessRatio": 1, - "ratio": 0.0011177347242921013 + "ratio": 0.0011174224788155322 }, "accessibility": { "total": 331, @@ -92111,7 +92129,7 @@ ".repo-access-add-team[aria-selected=true] .team-description": 1 }, "uniquenessRatio": 0.9577039274924471, - "ratio": 0.030830849478390463 + "ratio": 0.030822236707328428 }, "keyframes": { "total": 188, @@ -92178,7 +92196,7 @@ ".faq-mktg summary::-webkit-details-marker": 1 }, "uniquenessRatio": 1, - "ratio": 0.0028874813710879285 + "ratio": 0.002886674736940125 }, "combinators": { "total": 6287, diff --git a/src/__fixtures__/smashing-magazine-20231008.json b/src/__fixtures__/smashing-magazine-20231008.json index c49b444..158c705 100644 --- a/src/__fixtures__/smashing-magazine-20231008.json +++ b/src/__fixtures__/smashing-magazine-20231008.json @@ -43027,21 +43027,21 @@ "complexity": { "min": 1, "max": 13, - "mean": 2.9806863252284876, + "mean": 2.9803414381789963, "mode": 1, "median": 3, "range": 12, - "sum": 17285, + "sum": 17283, "total": 5799, "totalUnique": 12, "unique": { "1": 2266, "2": 486, "3": 1340, - "4": 430, - "5": 539, - "6": 220, - "7": 239, + "4": 431, + "5": 538, + "6": 221, + "7": 238, "8": 97, "9": 62, "10": 37, @@ -46194,10 +46194,10 @@ 5, 5, 5, - 5, + 4, 5, 7, - 7, + 6, 5, 5, 7, diff --git a/src/atrules/atrules.js b/src/atrules/atrules.js index 61828a8..8bb48c7 100644 --- a/src/atrules/atrules.js +++ b/src/atrules/atrules.js @@ -47,18 +47,14 @@ export function isMediaBrowserhack(prelude) { let returnValue = false walk(prelude, function (node) { - if (node.type === 'MediaQuery' - && node.children.size === 1 - && node.children.first.type === 'Identifier' - ) { - node = node.children.first + if (node.type === 'MediaQuery' && node.mediaType !== null) { // Note: CSSTree adds a trailing space to \\9 - if (startsWith('\\0', node.name) || endsWith('\\9 ', node.name)) { + if (startsWith('\\0', node.mediaType) || endsWith('\\9 ', node.mediaType)) { returnValue = true return this.break } } - if (node.type === 'MediaFeature') { + if (node.type === 'Feature' && node.kind === 'media') { if (node.value !== null && node.value.unit === '\\0') { returnValue = true return this.break diff --git a/src/atrules/atrules.test.js b/src/atrules/atrules.test.js index a9e6f2a..7ae862b 100644 --- a/src/atrules/atrules.test.js +++ b/src/atrules/atrules.test.js @@ -8,6 +8,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; @@ -74,8 +79,8 @@ AtRules('finds @layer', () => { ` const actual = analyze(fixture).atrules.layer const expected = { - total: 46, - totalUnique: 25, + total: 48, + totalUnique: 27, unique: { "defaults": 5, "layer-1": 1, @@ -102,8 +107,10 @@ AtRules('finds @layer', () => { "layouts": 1, "structures": 1, "overrides": 1, + "test": 1, + "test.abc": 1, }, - uniquenessRatio: 25 / 46 + uniquenessRatio: 27 / 48 } assert.equal(actual, expected) @@ -241,11 +248,16 @@ AtRules('finds @imports', () => { @import url('../example.css') layer; @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))));*/ ` - 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, @@ -254,11 +266,45 @@ AtRules('finds @imports', () => { 'url(\'example.css\') layer(named-layer)': 1, 'url(\'../example.css\') layer': 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, + // '"test.css" supports((selector(h2 > p) and (font-tech(color-COLRv1))))': 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, + // "selector(h2 > p) and (font-tech(color-COLRv1))": 1, + }, + uniquenessRatio: 1, + browserhacks: { + total: 0, + totalUnique: 0, + unique: {}, + uniquenessRatio: 0, + } + } + assert.equal(actual.supports, expected_supports, 'Incorrect SupportsCondition matches') + + const expected_layers = { + total: 2, + totalUnique: 2, + unique: { + 'named-layer': 1, + 'reset.remedy': 1, + }, + uniquenessRatio: 1, + } + assert.equal(actual.layer, expected_layers, 'Incorrect Layer matches') }) AtRules('finds @charsets', () => { @@ -289,16 +335,24 @@ AtRules('finds @supports', () => { @media (min-width: 0) { @supports (-webkit-appearance: none) {} } + + /* Should not wrap in extra (), because CSSTree will see it as 2 Conditions */ + @supports not (stroke-color: transparent) {} + @supports (not (stroke-color: transparent)) {} ` const actual = analyze(fixture).atrules.supports + delete actual.browserhacks - assert.equal(actual.total, 4) - assert.equal(actual.totalUnique, 3) - assert.equal(actual.uniquenessRatio, 3 / 4) - assert.equal(actual.unique, { - '(filter: blur(5px))': 1, - '(display: table-cell) and (display: list-item)': 1, - '(-webkit-appearance: none)': 2, + assert.equal(actual, { + total: 6, + totalUnique: 4, + uniquenessRatio: 4 / 6, + unique: { + '(filter: blur(5px))': 1, + '(display: table-cell) and (display: list-item)': 1, + '(-webkit-appearance: none)': 2, + 'not (stroke-color: transparent)': 2, + } }) }) @@ -355,19 +409,22 @@ AtRules('finds @media', () => { } ` const actual = analyze(fixture).atrules.media + delete actual.browserhacks - assert.is(actual.total, 7) - assert.is(actual.totalUnique, 7) - assert.equal(actual.unique, { - 'screen': 1, - 'screen and (min-width: 33em)': 1, - '(min-width: 20px)': 1, - '(max-width: 200px)': 1, - 'screen or print': 1, - 'all and (transform-3d), (-webkit-transform-3d)': 1, - '(min-width: 0)': 1, + assert.equal(actual, { + total: 7, + totalUnique: 7, + uniquenessRatio: 1, + unique: { + 'screen': 1, + 'screen and (min-width: 33em)': 1, + '(min-width: 20px)': 1, + '(max-width: 200px)': 1, + 'screen or print': 1, + 'all and (transform-3d), (-webkit-transform-3d)': 1, + '(min-width: 0)': 1, + } }) - assert.is(actual.uniquenessRatio, 1) }) AtRules('finds @media browserhacks', () => { diff --git a/src/index.js b/src/index.js index a20a7ed..347dca8 100644 --- a/src/index.js +++ b/src/index.js @@ -181,16 +181,19 @@ export function analyze(css, options = {}) { if (atRuleName === 'media') { let prelude = stringifyNode(node.prelude) medias.push(prelude, node.prelude.loc) + if (isMediaBrowserhack(node.prelude)) { mediaBrowserhacks.push(prelude, node.prelude.loc) } break } if (atRuleName === 'supports') { - let prelude = stringifyNode(node.prelude) - supports.push(prelude, node.prelude.loc) - if (isSupportsBrowserhack(node.prelude)) { - supportsBrowserhacks.push(prelude, node.prelude.loc) + let prelude = node.prelude + let condition = stringifyNode(prelude) + supports.push(condition, prelude.loc) + + if (isSupportsBrowserhack(prelude)) { + supportsBrowserhacks.push(condition, prelude.loc) } break } @@ -203,6 +206,17 @@ export function analyze(css, options = {}) { break } if (atRuleName === 'import') { + walk(node, function (prelude_node) { + if (prelude_node.type === 'Condition' && prelude_node.kind === 'supports') { + let prelude = stringifyNode(prelude_node) + supports.push(prelude, prelude_node.loc) + + if (isSupportsBrowserhack(prelude_node)) { + supportsBrowserhacks.push(prelude, prelude_node.loc) + } + return this.break + } + }) imports.push(stringifyNode(node.prelude), node.prelude.loc) break } @@ -214,13 +228,6 @@ export function analyze(css, options = {}) { containers.push(stringifyNode(node.prelude), node.prelude.loc) break } - if (atRuleName === 'layer') { - let prelude = stringifyNode(node.prelude) - prelude - .split(',') - .forEach(name => layers.push(name.trim(), node.prelude.loc)) - break - } if (atRuleName === 'property') { let prelude = stringifyNode(node.prelude) registeredProperties.push(prelude, node.prelude.loc) @@ -228,6 +235,10 @@ export function analyze(css, options = {}) { } break } + case 'Layer': { + layers.push(node.name, node.loc) + break + } case 'Rule': { let numSelectors = node.prelude.children ? node.prelude.children.size : 0 let numDeclarations = node.block.children ? node.block.children.size : 0