diff --git a/src/__fixtures__/bol-com-20231008.json b/src/__fixtures__/bol-com-20231008.json index f0bc75c..864539a 100644 --- a/src/__fixtures__/bol-com-20231008.json +++ b/src/__fixtures__/bol-com-20231008.json @@ -56587,6 +56587,18 @@ "median": 1, "range": 1, "sum": 10452 + }, + "keywords": { + "total": 930, + "totalUnique": 5, + "unique": { + "none": 574, + "inherit": 89, + "auto": 231, + "initial": 33, + "unset": 3 + }, + "uniquenessRatio": 0.005376344086021506 } } } \ No newline at end of file diff --git a/src/__fixtures__/bootstrap-5.3.2.json b/src/__fixtures__/bootstrap-5.3.2.json index 0997c05..dea7123 100644 --- a/src/__fixtures__/bootstrap-5.3.2.json +++ b/src/__fixtures__/bootstrap-5.3.2.json @@ -30656,6 +30656,17 @@ "median": 1, "range": 1, "sum": 5643 + }, + "keywords": { + "total": 538, + "totalUnique": 4, + "unique": { + "inherit": 34, + "none": 208, + "auto": 292, + "initial": 4 + }, + "uniquenessRatio": 0.007434944237918215 } } } \ No newline at end of file diff --git a/src/__fixtures__/cnn-20231008.json b/src/__fixtures__/cnn-20231008.json index 4302e5f..9af879a 100644 --- a/src/__fixtures__/cnn-20231008.json +++ b/src/__fixtures__/cnn-20231008.json @@ -33714,6 +33714,18 @@ "median": 1, "range": 1, "sum": 5964 + }, + "keywords": { + "total": 663, + "totalUnique": 5, + "unique": { + "auto": 116, + "none": 429, + "inherit": 43, + "initial": 13, + "unset": 62 + }, + "uniquenessRatio": 0.007541478129713424 } } } \ No newline at end of file diff --git a/src/__fixtures__/css-tricks-20231008.json b/src/__fixtures__/css-tricks-20231008.json index a1b09d3..fad962a 100644 --- a/src/__fixtures__/css-tricks-20231008.json +++ b/src/__fixtures__/css-tricks-20231008.json @@ -14876,6 +14876,16 @@ "median": 1, "range": 1, "sum": 2701 + }, + "keywords": { + "total": 182, + "totalUnique": 3, + "unique": { + "none": 113, + "auto": 59, + "inherit": 10 + }, + "uniquenessRatio": 0.016483516483516484 } } } \ No newline at end of file diff --git a/src/__fixtures__/gazelle-20231008.json b/src/__fixtures__/gazelle-20231008.json index 27bef14..315b30b 100644 --- a/src/__fixtures__/gazelle-20231008.json +++ b/src/__fixtures__/gazelle-20231008.json @@ -90344,6 +90344,18 @@ "median": 1, "range": 1, "sum": 18098 + }, + "keywords": { + "total": 1196, + "totalUnique": 5, + "unique": { + "none": 767, + "auto": 347, + "inherit": 40, + "initial": 36, + "unset": 6 + }, + "uniquenessRatio": 0.004180602006688963 } } } \ No newline at end of file diff --git a/src/__fixtures__/github-20231008.json b/src/__fixtures__/github-20231008.json index e9f16ac..9a4dc98 100644 --- a/src/__fixtures__/github-20231008.json +++ b/src/__fixtures__/github-20231008.json @@ -102120,6 +102120,19 @@ "median": 1, "range": 1, "sum": 26314 + }, + "keywords": { + "total": 1518, + "totalUnique": 6, + "unique": { + "none": 961, + "inherit": 150, + "auto": 364, + "initial": 19, + "revert": 3, + "unset": 21 + }, + "uniquenessRatio": 0.003952569169960474 } } } \ No newline at end of file diff --git a/src/__fixtures__/indiatimes-20231008.json b/src/__fixtures__/indiatimes-20231008.json index 40e1caf..b0afc9c 100644 --- a/src/__fixtures__/indiatimes-20231008.json +++ b/src/__fixtures__/indiatimes-20231008.json @@ -58267,6 +58267,17 @@ "median": 1, "range": 1, "sum": 6161 + }, + "keywords": { + "total": 266, + "totalUnique": 4, + "unique": { + "none": 95, + "auto": 135, + "inherit": 35, + "unset": 1 + }, + "uniquenessRatio": 0.015037593984962405 } } } \ No newline at end of file diff --git a/src/__fixtures__/smashing-magazine-20231008.json b/src/__fixtures__/smashing-magazine-20231008.json index 227e0f7..e848fd4 100644 --- a/src/__fixtures__/smashing-magazine-20231008.json +++ b/src/__fixtures__/smashing-magazine-20231008.json @@ -51551,6 +51551,18 @@ "median": 1, "range": 1, "sum": 11962 + }, + "keywords": { + "total": 980, + "totalUnique": 5, + "unique": { + "inherit": 80, + "auto": 281, + "none": 588, + "initial": 27, + "unset": 4 + }, + "uniquenessRatio": 0.00510204081632653 } } } \ No newline at end of file diff --git a/src/__fixtures__/trello-20231008.json b/src/__fixtures__/trello-20231008.json index 9be60c0..65df953 100644 --- a/src/__fixtures__/trello-20231008.json +++ b/src/__fixtures__/trello-20231008.json @@ -11656,6 +11656,16 @@ "median": 1, "range": 1, "sum": 3825 + }, + "keywords": { + "total": 300, + "totalUnique": 3, + "unique": { + "inherit": 19, + "none": 141, + "auto": 140 + }, + "uniquenessRatio": 0.01 } } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 95103ec..dca15fa 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import { isSupportsBrowserhack, isMediaBrowserhack } from './atrules/atrules.js' import { getCombinators, getComplexity, isAccessibility, isPrefixed } from './selectors/utils.js' import { colorFunctions, colorKeywords, namedColors, systemColors } from './values/colors.js' import { destructure, isSystemFont } from './values/destructure-font-shorthand.js' -import { isValueKeyword } from './values/values.js' +import { isValueKeyword, keywords } from './values/values.js' import { analyzeAnimation } from './values/animations.js' import { isValuePrefixed } from './values/vendor-prefix.js' import { ContextCollection } from './context-collection.js' @@ -175,6 +175,7 @@ export function analyze(css, options = {}) { let colorFormats = new Collection(useLocations) let units = new ContextCollection(useLocations) let gradients = new Collection(useLocations) + let valueKeywords = new Collection(useLocations) walk(ast, function (node) { switch (node.type) { @@ -412,6 +413,7 @@ export function analyze(css, options = {}) { case Value: { if (isValueKeyword(node)) { valueComplexities.push(1) + valueKeywords.p(stringifyNode(node), node.loc) break } @@ -450,7 +452,11 @@ export function analyze(css, options = {}) { } else if (isProperty('font', property)) { if (isSystemFont(node)) return - let { font_size, line_height, font_family } = destructure(node, stringifyNode) + let { font_size, line_height, font_family } = destructure(node, stringifyNode, function (item) { + if (item.type === 'keyword') { + valueKeywords.p(item.value, loc) + } + }) if (font_family) { fontFamilies.p(font_family, loc) @@ -481,6 +487,8 @@ export function analyze(css, options = {}) { timingFunctions.p(stringifyNode(item.value), loc) } else if (item.type === 'duration') { durations.p(stringifyNode(item.value), loc) + } else if (item.type === 'keyword') { + valueKeywords.p(stringifyNode(item.value), loc) } }) break @@ -533,6 +541,10 @@ export function analyze(css, options = {}) { return this.skip } case Identifier: { + if (keywords.has(nodeName)) { + valueKeywords.p(nodeName, loc) + } + // Bail out if it can't be a color name // 20 === 'lightgoldenrodyellow'.length // 3 === 'red'.length @@ -870,6 +882,7 @@ export function analyze(css, options = {}) { browserhacks: valueBrowserhacks.c(), units: units.count(), complexity: valueComplexity, + keywords: valueKeywords.c(), }, __meta__: { parseTime: startAnalysis - startParse, diff --git a/src/index.test.js b/src/index.test.js index bf07e02..8df6f7b 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -460,7 +460,13 @@ Api("handles empty input gracefully", () => { median: 0, range: 0, sum: 0, - } + }, + keywords: { + total: 0, + totalUnique: 0, + unique: {}, + uniquenessRatio: 0, + }, }, } diff --git a/src/smoke.test.js b/src/smoke.test.js index 09a70e0..1856167 100644 --- a/src/smoke.test.js +++ b/src/smoke.test.js @@ -30,7 +30,6 @@ Object.entries({ const expected = JSON.parse(json) // writeFileSync(`./src/__fixtures__/${fileName}.json`, JSON.stringify(actual, null, 2)) - Smoke(`${name} - Stylesheet`, () => assert.equal(actual.stylesheet, expected.stylesheet)) Smoke(`${name} - Atrules`, () => assert.equal(actual.atrules, expected.atrules)) Smoke(`${name} - Rules`, () => assert.equal(actual.rules, expected.rules)) diff --git a/src/values/animations.js b/src/values/animations.js index a9f0529..22bf328 100644 --- a/src/values/animations.js +++ b/src/values/animations.js @@ -1,4 +1,5 @@ import { KeywordSet } from "../keyword-set.js" +import { keywords } from "./values.js" import { Operator, Dimension, @@ -38,11 +39,18 @@ export function analyzeAnimation(children, cb) { type: 'duration', value: child, }) - } else if (type === Identifier && TIMING_KEYWORDS.has(name)) { - cb({ - type: 'fn', - value: child, - }) + } else if (type === Identifier) { + if (TIMING_KEYWORDS.has(name)) { + cb({ + type: 'fn', + value: child, + }) + } else if (keywords.has(name)) { + cb({ + type: 'keyword', + value: child, + }) + } } else if (type === Func && TIMING_FUNCTION_VALUES.has(name)) { cb({ type: 'fn', diff --git a/src/values/destructure-font-shorthand.js b/src/values/destructure-font-shorthand.js index cf59c22..d4ea4b5 100644 --- a/src/values/destructure-font-shorthand.js +++ b/src/values/destructure-font-shorthand.js @@ -1,4 +1,5 @@ import { KeywordSet } from '../keyword-set.js' +import { keywords } from './values.js' import { Identifier, Nr, Operator } from '../css-tree-node-types.js' const SYSTEM_FONTS = new KeywordSet([ @@ -38,7 +39,7 @@ export function isSystemFont(node) { * @param {import('css-tree').Value} value * @param {*} stringifyNode */ -export function destructure(value, stringifyNode) { +export function destructure(value, stringifyNode, cb) { let font_family = Array.from({ length: 2 }) let font_size let line_height @@ -47,6 +48,13 @@ export function destructure(value, stringifyNode) { let prev = item.prev ? item.prev.data : undefined let next = item.next ? item.next.data : undefined + if (node.type === Identifier && keywords.has(node.name)) { + cb({ + type: 'keyword', + value: node.name, + }) + } + // any node that comes before the '/' is the font-size if (next && next.type === Operator && next.value.charCodeAt(0) === SLASH) { font_size = stringifyNode(node) diff --git a/src/values/keywords.test.js b/src/values/keywords.test.js new file mode 100644 index 0000000..a0a8109 --- /dev/null +++ b/src/values/keywords.test.js @@ -0,0 +1,84 @@ +import { suite } from 'uvu'; +import * as assert from 'uvu/assert'; +import { analyze } from '../index.js' + +const test = suite('Global Keywords') + +test('finds global keywords', () => { + const fixture = ` + test { + /* Global values */ + line-height: inherit; + line-height: initial; + line-height: revert; + line-height: revert-layer; + line-height: unset; + + font: inherit; + font: initial; + font: revert; + font: revert-layer; + font: unset; + } + ` + const actual = analyze(fixture).values.keywords + const expected = { + total: 10, + totalUnique: 5, + unique: { + inherit: 2, + initial: 2, + revert: 2, + 'revert-layer': 2, + unset: 2 + }, + uniquenessRatio: 5 / 10 + } + + assert.equal(actual, expected) +}) + +test('finds global keywords in shorthands', () => { + let fixture = ` + a { + animation: myAnimation 2s infinite inherit; + margin: 0 auto; + font: italic bold 16px/1.5 Arial, sans-serif inherit; + } + ` + let actual = analyze(fixture).values.keywords + let expected = { + total: 3, + totalUnique: 2, + unique: { + inherit: 2, + auto: 1 + }, + uniquenessRatio: 2 / 3 + } + + assert.equal(actual, expected) +}) + +test('finds global keywords in multi-values', () => { + let fixture = ` + a { + background-size: auto, 50%; + animation: myAnimation1 2s infinite inherit, myAnimation2 2s infinite inherit; + } + ` + let actual = analyze(fixture).values.keywords + let expected = { + total: 3, + totalUnique: 2, + unique: { + auto: 1, + inherit: 2, + }, + uniquenessRatio: 2 / 3 + } + + assert.equal(actual, expected) +}) + +test.run() diff --git a/src/values/values.js b/src/values/values.js index ba9e1f5..813fabb 100644 --- a/src/values/values.js +++ b/src/values/values.js @@ -1,14 +1,14 @@ import { KeywordSet } from "../keyword-set.js" import { Identifier } from "../css-tree-node-types.js" -const keywords = new KeywordSet([ +export const keywords = new KeywordSet([ 'auto', + 'none', // for `text-shadow`, `box-shadow` and `background` 'inherit', 'initial', 'unset', 'revert', 'revert-layer', - 'none', // for `text-shadow`, `box-shadow` and `background` ]) /**