From e2caf0a1096e5a60e62e18047fc66e376d7f37fb Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Thu, 21 Oct 2021 16:15:47 -0400 Subject: [PATCH 1/2] Audits: Add a report on unused or undefined custom properties. --- css-audit.config.js | 1 + src/__tests__/custom-properties.js | 54 ++++++++++++++++++++++++++ src/audits/custom-properties.js | 58 ++++++++++++++++++++++++++++ src/formats/html/_audit-default.twig | 6 ++- src/run.js | 3 ++ 5 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/custom-properties.js create mode 100644 src/audits/custom-properties.js 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..778194e --- /dev/null +++ b/src/__tests__/custom-properties.js @@ -0,0 +1,54 @@ +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 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..e67b778 --- /dev/null +++ b/src/audits/custom-properties.js @@ -0,0 +1,58 @@ +/** + * 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 ); + } + } else { + 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 = From 6d8bab3a5a5af0c6edd29f5e0fc30b50e97f0461 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Thu, 21 Oct 2021 16:50:56 -0400 Subject: [PATCH 2/2] Check for custom properties in the value of other custom properties --- src/__tests__/custom-properties.js | 13 +++++++++++++ src/audits/custom-properties.js | 27 ++++++++++++--------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/__tests__/custom-properties.js b/src/__tests__/custom-properties.js index 778194e..d16db2a 100644 --- a/src/__tests__/custom-properties.js +++ b/src/__tests__/custom-properties.js @@ -32,6 +32,19 @@ describe( 'Audit: Custom Properties', () => { 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 = [ { diff --git a/src/audits/custom-properties.js b/src/audits/custom-properties.js index e67b778..c78e881 100644 --- a/src/audits/custom-properties.js +++ b/src/audits/custom-properties.js @@ -15,22 +15,19 @@ module.exports = function ( files = [] ) { if ( ! definedProps.includes( prop ) ) { definedProps.push( prop ); } - } else { - 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 ) {} } + 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 ) {} } } ); } );