Skip to content

Commit

Permalink
Add custom Emotion css eslint plugin for non-logical properties
Browse files Browse the repository at this point in the history
  • Loading branch information
cee-chen committed Aug 12, 2022
1 parent 653594b commit 438b861
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintplugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ exports.rules = {
'href-with-rel': require('./scripts/eslint-plugin/rel'),
'require-license-header': require('./scripts/eslint-plugin/require_license_header'),
'forward-ref': require('./scripts/eslint-plugin/forward_ref_display_name'),
'css-logical-properties': require('./scripts/eslint-plugin/css_logical_properties'),
};
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ module.exports = {
'local/i18n': 'error',
'local/href-with-rel': 'error',
'local/forward-ref': 'error',
'local/css-logical-properties': 'error',
'local/require-license-header': [
'warn',
{
Expand Down
63 changes: 63 additions & 0 deletions scripts/eslint-plugin/css_logical_properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Enforce using CSS logical properties in our Emotion CSS',
},
},
create: function (context) {
return {
TaggedTemplateExpression(node) {
if (node.tag?.name !== 'css') return; // We only want to check Emotion css`` template literals

const lintNonLogicalProperty = (property) => {
context.report({
node,
message: `Prefer the CSS logical property for ${property} - @see src/global_styling/functions/logicals.ts`,
});
};

const templateContents = node.quasi?.quasis || [];
templateContents.forEach((cssNode) => {
const stringLiteral = cssNode?.value?.raw;
if (!stringLiteral) return;

const propertiesToFlag = {
// sizing - catches min/max as well
width: 'width:',
height: /(?<!line-)height:/,
// positioning - catches padding/margin/border sides as well
top: 'top:',
right: 'right:',
bottom: 'bottom:',
left: 'left:',
// border side specific properties
'border sides': /border-(top|right|bottom|left)-(color|style|width):/,
// text-align
'text-align left': 'text-align: left',
'text-align right': 'text-align: right',
// overflow
'overflow-x': 'overflow-x:',
'overflow-y': 'overflow-y:',
};
const properties = Object.values(propertiesToFlag);
const propertyNames = Object.keys(propertiesToFlag);

for (let i = 0; i < properties.length; i++) {
const property = properties[i];

if (typeof property === 'string') {
if (stringLiteral.includes(property)) {
return lintNonLogicalProperty(propertyNames[i]);
}
} else {
if (stringLiteral.match(property)) {
return lintNonLogicalProperty(propertyNames[i]);
}
}
}
});
},
};
},
};
70 changes: 70 additions & 0 deletions scripts/eslint-plugin/css_logical_properties.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import rule from './css_logical_properties.js';
const RuleTester = require('eslint').RuleTester;

const ruleTester = new RuleTester({
parser: require.resolve('babel-eslint'),
});

const valid = [
`css\`
inline-size: 50px;
inline-start-end: 10px;
\``,
'`width: 50px; left: 10px;`', // This is not in css``, so it's fine
'css`line-height: 20px`', // Make sure we don't incorrectly catch line-height
];

const getErrorMessage = (property) =>
`Prefer the CSS logical property for ${property} - @see src/global_styling/functions/logicals.ts`;

const invalid = [
{
code: 'css`height: 50px;`',
errors: [{ message: getErrorMessage('height') }],
},
{
code: 'css`max-height: 50px;`',
errors: [{ message: getErrorMessage('height') }],
},
{
code: 'css`width: 50px;`',
errors: [{ message: getErrorMessage('width') }],
},
{
code: 'css`min-width: 50px;`',
errors: [{ message: getErrorMessage('width') }],
},
{
code: 'css`top: 0;`',
errors: [{ message: getErrorMessage('top') }],
},
{
code: 'css`padding-right: 0;`',
errors: [{ message: getErrorMessage('right') }],
},
{
code: 'css`margin-bottom: 0;`',
errors: [{ message: getErrorMessage('bottom') }],
},
{
code: 'css`border-left: 1px solid green;`',
errors: [{ message: getErrorMessage('left') }],
},
{
code: 'css`border-left-color: red;`',
errors: [{ message: getErrorMessage('border sides') }],
},
{
code: 'css`text-align: left;`',
errors: [{ message: getErrorMessage('text-align left') }],
},
{
code: 'css`overflow-y: hidden;`',
errors: [{ message: getErrorMessage('overflow-y') }],
},
];

ruleTester.run('css_logical_properties', rule, {
valid,
invalid,
});

0 comments on commit 438b861

Please sign in to comment.