diff --git a/css-audit.config.js b/css-audit.config.js index d6dd8d8..3de1d2c 100644 --- a/css-audit.config.js +++ b/css-audit.config.js @@ -2,6 +2,7 @@ module.exports = { format: 'html', filename: 'wp-admin', audits: [ + 'custom-props', 'colors', 'important', 'display-none', diff --git a/src/__tests__/custom-properties.js b/src/__tests__/custom-properties.js new file mode 100644 index 0000000..d16db2a --- /dev/null +++ b/src/__tests__/custom-properties.js @@ -0,0 +1,67 @@ +const audit = require( '../audits/custom-properties' ); + +function getResultValue( results, key ) { + const { value } = results.find( ( { id } ) => key === id ); + return value; +} + +describe( 'Audit: Custom Properties', () => { + it( 'should return no values when no custom properties', () => { + const files = [ + { + name: 'a.css', + content: `body { font-size: 1em; line-height: 1.6; color: red }`, + }, + ]; + const { results } = audit( files ); + expect( getResultValue( results, 'unused' ) ).toHaveLength( 0 ); + expect( getResultValue( results, 'undefined' ) ).toHaveLength( 0 ); + } ); + + it( 'should count the number of custom properties in a file', () => { + const files = [ + { + name: 'a.css', + content: `:root { --foo: red; --bar: blue; } + h1 { color: var(--foo); background: var(--baz); } + h2 { color: var(--baz); background: var(--foo); }`, + }, + ]; + const { results } = audit( files ); + expect( getResultValue( results, 'unused' ) ).toHaveLength( 1 ); + expect( getResultValue( results, 'undefined' ) ).toHaveLength( 1 ); + } ); + + it( 'should count custom properties used in other custom properties', () => { + const files = [ + { + name: 'a.css', + content: `:root { --foo: red; --bar: 1px solid var(--foo); } + h1 { border: var(--bar); }`, + }, + ]; + const { results } = audit( files ); + expect( getResultValue( results, 'unused' ) ).toHaveLength( 0 ); + expect( getResultValue( results, 'undefined' ) ).toHaveLength( 0 ); + } ); + + it( 'should count the number of custom properties across multiple files', () => { + const files = [ + { + name: 'props.css', + content: `:root { --foo: red; --bar: blue; }`, + }, + { + name: 'a.css', + content: `h1 { color: var(--foo); }`, + }, + { + name: 'b.css', + content: `h2 { color: rgb(var(--baz) / 0.5); background: var(--foo); }`, + }, + ]; + const { results } = audit( files ); + expect( getResultValue( results, 'unused' ) ).toHaveLength( 1 ); + expect( getResultValue( results, 'undefined' ) ).toHaveLength( 1 ); + } ); +} ); diff --git a/src/audits/custom-properties.js b/src/audits/custom-properties.js new file mode 100644 index 0000000..c78e881 --- /dev/null +++ b/src/audits/custom-properties.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +const { parse } = require( 'postcss' ); +const { parse: parseValue } = require( 'postcss-values-parser' ); + +module.exports = function ( files = [] ) { + const definedProps = []; + const usedProps = []; + files.forEach( ( { content, name } ) => { + const root = parse( content, { from: name } ); + root.walkDecls( ( { prop, value } ) => { + if ( prop ) { + if ( '--' === prop.slice( 0, 2 ) ) { + if ( ! definedProps.includes( prop ) ) { + definedProps.push( prop ); + } + } + try { + const valueRoot = parseValue( value, { + ignoreUnknownWords: true, + } ); + valueRoot.walkFuncs( ( node ) => { + if ( node.isVar ) { + if ( ! usedProps.includes( node.first.value ) ) { + usedProps.push( node.first.value ); + } + } + } ); + } catch ( error ) {} + } + } ); + } ); + + return { + audit: 'custom-props', + name: 'Custom Properties', + results: [ + { + id: 'unused', + label: 'Defined but unused custom properties', + value: definedProps + .filter( ( x ) => ! usedProps.includes( x ) ) + .map( ( prop ) => ( { name: prop } ) ), + }, + { + id: 'undefined', + label: 'Undefined custom properties', + value: usedProps + .filter( ( x ) => ! definedProps.includes( x ) ) + .map( ( prop ) => ( { name: prop } ) ), + }, + ], + }; +}; diff --git a/src/formats/html/_audit-default.twig b/src/formats/html/_audit-default.twig index b7acced..80a670d 100644 --- a/src/formats/html/_audit-default.twig +++ b/src/formats/html/_audit-default.twig @@ -5,7 +5,9 @@ {% for value in item.value %} {% if value.name %}
  • - {{value.count}} + {% if value.count %} + {{value.count}} + {% endif %} {{value.name}}
  • {% endif %} @@ -21,4 +23,4 @@ {% else %}

    {{item.label}}: {{item.value}}

    {% endif %} -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/src/run.js b/src/run.js index 0856c73..e2812cd 100644 --- a/src/run.js +++ b/src/run.js @@ -27,6 +27,9 @@ const runAudits = ( cssFiles ) => { if ( getArg( '--typography' ) ) { audits.push( require( './audits/typography' )( cssFiles ) ); } + if ( getArg( '--custom-props' ) ) { + audits.push( require( './audits/custom-properties' )( cssFiles ) ); + } const propertyValues = getArg( '--property-values' ); const isPropertyValuesArray =