Skip to content

Commit

Permalink
case-sensitivity fixes, additional tests (#318)
Browse files Browse the repository at this point in the history
- Added tests for line-heights
- Removed obsolete isValueKeyword check
- Added lots of CSS global keywords to value tests
- Fixed case sensitivity check for animation-timing-functions (`StEpS`,
`cubic-bEzIeR`)
- Added some JSDoc examples to confusing functions
  • Loading branch information
bartveneman authored Apr 13, 2023
1 parent b7f2fa5 commit a62c7c6
Showing 17 changed files with 245 additions and 63 deletions.
11 changes: 11 additions & 0 deletions src/declarations/declarations.test.js
Original file line number Diff line number Diff line change
@@ -4,6 +4,17 @@ import { analyze } from '../index.js'

const Declarations = suite('Declarations')

Declarations('handles empty values', () => {
let css = `
thing {
height:;
width: ;
}
`

assert.not.throws(() => analyze(css))
})

Declarations('should be counted', () => {
const fixture = `
rule {
12 changes: 5 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { calculate } from '@bramus/specificity/core'
import { isSupportsBrowserhack, isMediaBrowserhack } from './atrules/atrules.js'
import { getComplexity, isAccessibility, compareSpecificity } from './selectors/utils.js'
import { colorFunctions, colorKeywords, namedColors, systemColors } from './values/colors.js'
import { destructure, isFontKeyword } from './values/destructure-font-shorthand.js'
import { destructure, isSystemFont } from './values/destructure-font-shorthand.js'
import { isValueKeyword } from './values/values.js'
import { analyzeAnimation } from './values/animations.js'
import { isAstVendorPrefixed } from './values/vendor-prefix.js'
@@ -343,12 +343,10 @@ function analyze(css) {
// Process properties first that don't have colors,
// so we can avoid further walking them;
if (isProperty('z-index', property)) {
if (!isValueKeyword(node)) {
zindex.push(stringifyNode(node))
}
zindex.push(stringifyNode(node))
return this.skip
} else if (isProperty('font', property)) {
if (isFontKeyword(node)) return
if (isSystemFont(node)) return

let { font_size, line_height, font_family } = destructure(node, stringifyNode)

@@ -364,12 +362,12 @@ function analyze(css) {

break
} else if (isProperty('font-size', property)) {
if (!isFontKeyword(node)) {
if (!isSystemFont(node)) {
fontSizes.push(stringifyNode(node))
}
break
} else if (isProperty('font-family', property)) {
if (!isFontKeyword(node)) {
if (!isSystemFont(node)) {
fontFamilies.push(stringifyNode(node))
}
break
14 changes: 14 additions & 0 deletions src/properties/property-utils.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,20 @@ export function isCustom(property) {
return property.charCodeAt(0) === 45 && property.charCodeAt(1) === 45
}

/**
* A check to verify that a propery is `basename` or a prefixed
* version of that, but never a custom property that accidentally
* ends with the same substring.
*
* @example
* isProperty('animation', 'animation') // true
* isProperty('animation', '-webkit-animation') // true
* isProperty('animation', '--my-animation') // false
*
* @param {string} basename
* @param {string} property
* @returns {boolean} True if `property` equals `basename` without prefix
*/
export function isProperty(basename, property) {
if (isCustom(property)) return false
return endsWith(basename, property)
1 change: 0 additions & 1 deletion src/properties/property-utils.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { suite } from 'uvu'
import * as assert from 'uvu/assert'
import { analyze } from '../index.js'
import { isHack, isCustom, isProperty } from './property-utils.js'

const PropertyUtils = suite('Property Utils')
41 changes: 26 additions & 15 deletions src/string-utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Case-insensitive compare two character codes
* @param {string} charA
* @param {string} charB
* @param {string} referenceCode
* @param {string} testCode
* @see https://github.com/csstree/csstree/blob/41f276e8862d8223eeaa01a3d113ab70bb13d2d9/lib/tokenizer/utils.js#L22
*/
function compareChar(referenceCode, testCode) {
@@ -15,16 +15,22 @@ function compareChar(referenceCode, testCode) {

/**
* Case-insensitive string-comparison
* @param {string} base
* @param {string} test
* @example
* strEquals('test', 'test') // true
* strEquals('test', 'TEST') // true
* strEquals('test', 'TesT') // true
* strEquals('test', 'derp') // false
*
* @param {string} base The string to check against
* @param {string} maybe The test string, possibly containing uppercased characters
* @returns {boolean} true if the two strings are the same, false otherwise
*/
export function strEquals(base, test) {
export function strEquals(base, maybe) {
let len = base.length;
if (len !== test.length) return false
if (len !== maybe.length) return false

for (let i = 0; i < len; i++) {
if (compareChar(base.charCodeAt(i), test.charCodeAt(i)) === false) {
if (compareChar(base.charCodeAt(i), maybe.charCodeAt(i)) === false) {
return false
}
}
@@ -34,20 +40,25 @@ export function strEquals(base, test) {

/**
* Case-insensitive testing whether a string ends with a given substring
*
* @example
* endsWith('test', 'my-test') // true
* endsWith('test', 'est') // false
*
* @param {string} base e.g. '-webkit-transform'
* @param {string} test e.g. 'transform'
* @param {string} maybe e.g. 'transform'
* @returns {boolean} true if `test` ends with `base`, false otherwise
*/
export function endsWith(base, test) {
let len = test.length
export function endsWith(base, maybe) {
let len = maybe.length
let offset = len - base.length

if (offset < 0) {
return false
}

for (let i = len - 1; i >= offset; i--) {
if (compareChar(base.charCodeAt(i - offset), test.charCodeAt(i)) === false) {
if (compareChar(base.charCodeAt(i - offset), maybe.charCodeAt(i)) === false) {
return false
}
}
@@ -58,15 +69,15 @@ export function endsWith(base, test) {
/**
* Case-insensitive testing whether a string starts with a given substring
* @param {string} base
* @param {string} test
* @param {string} maybe
* @returns {boolean} true if `test` starts with `base`, false otherwise
*/
export function startsWith(base, test) {
export function startsWith(base, maybe) {
let len = base.length
if (test.length < len) return false
if (maybe.length < len) return false

for (let i = 0; i < len; i++) {
if (compareChar(base.charCodeAt(i), test.charCodeAt(i)) === false) {
if (compareChar(base.charCodeAt(i), maybe.charCodeAt(i)) === false) {
return false
}
}
15 changes: 8 additions & 7 deletions src/values/animations.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KeywordSet } from "../keyword-set.js"

const timingKeywords = new KeywordSet([
const TIMING_KEYWORDS = new KeywordSet([
'linear',
'ease',
'ease-in',
@@ -10,6 +10,11 @@ const timingKeywords = new KeywordSet([
'step-end',
])

const TIMING_FUNCTION_VALUES = new KeywordSet([
'cubic-bezier',
'steps'
])

export function analyzeAnimation(children, stringifyNode) {
let durationFound = false
let durations = []
@@ -26,14 +31,10 @@ export function analyzeAnimation(children, stringifyNode) {
durationFound = true
return durations.push(stringifyNode(child))
}
if (type === 'Identifier' && timingKeywords.has(child.name)) {
if (type === 'Identifier' && TIMING_KEYWORDS.has(child.name)) {
return timingFunctions.push(stringifyNode(child))
}
if (type === 'Function'
&& (
child.name === 'cubic-bezier' || child.name === 'steps'
)
) {
if (type === 'Function' && TIMING_FUNCTION_VALUES.has(child.name)) {
return timingFunctions.push(stringifyNode(child))
}
})
9 changes: 6 additions & 3 deletions src/values/animations.test.js
Original file line number Diff line number Diff line change
@@ -94,19 +94,22 @@ Animations('finds shorthand timing functions', () => {
transition: all 4s cubic-bezier(0,1,0,1);
transition: all 5s linear 5000ms;
transition: all 6s Cubic-Bezier(0,1,0,1);
--my-animation: invalid;
--my-transition: invalid;
}
`
const actual = analyze(fixture).values.animations.timingFunctions
const expected = {
total: 4,
totalUnique: 2,
total: 5,
totalUnique: 3,
unique: {
'linear': 2,
'cubic-bezier(0,1,0,1)': 2,
'Cubic-Bezier(0,1,0,1)': 1,
},
uniquenessRatio: 2 / 4
uniquenessRatio: 3 / 5
}
assert.equal(actual, expected)
})
8 changes: 7 additions & 1 deletion src/values/box-shadows.test.js
Original file line number Diff line number Diff line change
@@ -64,8 +64,14 @@ BoxShadows('finds vendor prefixed values', () => {
BoxShadows('ignores keywords', () => {
const fixture = `
box-shadows-keyword {
box-shadow: initial;
box-shadow: none;
/* Global keywords */
box-shadow: initial;
box-shadow: inherit;
box-shadow: revert;
box-shadow: revert-layer;
box-shadow: unset;
}
`
const actual = analyze(fixture).values.boxShadows
2 changes: 2 additions & 0 deletions src/values/colors.test.js
Original file line number Diff line number Diff line change
@@ -892,6 +892,8 @@ Colors('ignores CSS keywords', () => {
color: unset;
color: initial;
color: transparent;
color: revert;
color: revert-layer;
background: none;
}
13 changes: 3 additions & 10 deletions src/values/destructure-font-shorthand.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { KeywordSet } from "../keyword-set.js"

const FONT_KEYWORDS = new KeywordSet([
// Global CSS keywords
'inherit',
'initial',
'unset',
'revert',

// System font keywords
const SYSTEM_FONTS = new KeywordSet([
'caption',
'icon',
'menu',
@@ -36,9 +29,9 @@ const SLASH = 47 // '/'.charCodeAt(0) === 47
const TYPE_OPERATOR = 'Operator'
const TYPE_IDENTIFIER = 'Identifier'

export function isFontKeyword(node) {
export function isSystemFont(node) {
let firstChild = node.children.first
return firstChild.type === TYPE_IDENTIFIER && FONT_KEYWORDS.has(firstChild.name)
return firstChild.type === TYPE_IDENTIFIER && SYSTEM_FONTS.has(firstChild.name)
}

export function destructure(value, stringifyNode) {
10 changes: 9 additions & 1 deletion src/values/font-families.test.js
Original file line number Diff line number Diff line change
@@ -99,10 +99,18 @@ FontFamilies('handles system fonts', () => {
FontFamilies('ignores keywords and global values', () => {
const fixture = `
test {
/* Global values */
font-family: inherit;
font-family: initial;
font-family: revert;
font-family: revert-layer;
font-family: unset;
font: inherit;
font: initial;
font: unset;
font: revert;
font: revert-layer;
font: unset;
}
`
const actual = analyze(fixture).values.fontFamilies
15 changes: 9 additions & 6 deletions src/values/font-sizes.test.js
Original file line number Diff line number Diff line change
@@ -97,15 +97,18 @@ FontSizes('handles system fonts', () => {
FontSizes('ignores keywords and global values', () => {
const fixture = `
test {
/* Global values */
font-size: inherit;
font-size: initial;
font-size: revert;
font-size: revert-layer;
font-size: unset;
font: inherit;
font: initial;
font: unset;
font: revert;
/*TODO:font-size: inherit;
font-size: initial;
font-size: unset;
font-size: revert;*/
font: revert-layer;
font: unset;
}
`
const actual = analyze(fixture).values.fontSizes
Loading

0 comments on commit a62c7c6

Please sign in to comment.