-
Notifications
You must be signed in to change notification settings - Fork 833
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Emotion] Add eslint rule for CSS logical properties (#6125)
* Add custom Emotion css eslint plugin for non-logical properties * Convert logicals map to JSON - so that both script/ JS and src/ TS can use it * Fix datagrid docs TS JSON imports typing - throwing as a result of our new `resolveJsonModule: true` * Convert old static map to use imported logicals map - importing the map directly with a regex for newlines produces less false positives for non-logical properties * Remove inset property from map - it's already a new logical property and doesn't need to be converted * Re-add catch for text-align logical values + add separate message for values vs properties - this might get used more for other future properties * Initial conversion to using regex exec - switching to a exec allows us to get the start index of the match, so that we can more accurately return the node location and also use a fixer - (opinionated) using a single regex vs multiple loops also simplifes logic somewhat, even if the regex itself is annoying - use regex capture groups for readability * Add ability to --fix eslint rule - via the maps we've set up * Add logic for determining exact css property location - vs highlighting the entire css`` block - yay for even more gnarly regex utils * Convert global HTML height property * Typography * EuiScreenReaderOnly * EuiAccordion * EuiBeacon * EuiButton * EuiCallOut * EuiComment * EuiExpression * EuiImage - use euiBreakpoint mixin - instead of manual media query * EuiImage inset * Loading components note: loading_logo.styles already had so many static logical properties i just continued using them for in-file consistency * EuiPageTemplate * EuiProgress * EuiToast * EuiPanel * Fix misc src-doc usages * Fix scroll documentation usages + simplify snippets * [PR feedback] lint *all* template literals, not just css`` - since we have several Emotion utilities that pass back raw ``s instead of css`` * Fix src styles missing logical properties * Fix over-aggressive src-docs linting by removing template literals * Fix over-aggressive src-docs linting by disabling rule * Fix valid src-docs catches - although debatable how useful they are if we're moving away from Sass * Add dev documentation on how to disable the lint rule for specific properties
- Loading branch information
Constance
authored
Aug 22, 2022
1 parent
0b7a908
commit 4c285cd
Showing
56 changed files
with
549 additions
and
311 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
const logicals = require('../../src/global_styling/functions/logicals.json'); | ||
const logicalProperties = Object.keys(logicals); | ||
|
||
const logicalValues = { | ||
'text-align: left': 'text-align: start', | ||
'text-align: right': 'text-align: end', | ||
// TODO: Consider adding float, clear, & resize as well | ||
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties | ||
}; | ||
|
||
const logicalPropertiesRegex = logicalProperties.join('|'); | ||
const logicalValuesRegex = Object.keys(logicalValues).join('|'); | ||
// @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec | ||
// @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Backreferences | ||
const regex = new RegExp( | ||
`^(?<whitespace>[\\s]*)((?<property>${logicalPropertiesRegex}):)|(?<value>${logicalValuesRegex})`, | ||
'gm' | ||
); | ||
|
||
const logicalsFixMap = { ...logicals, ...logicalValues }; | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Enforce using CSS logical properties in our Emotion CSS', | ||
}, | ||
messages: { | ||
preferLogicalProperty: | ||
'Prefer the CSS logical property for {{ property }} - @see src/global_styling/functions/logicals.ts', | ||
preferLogicalValue: | ||
'Prefer the CSS logical value for {{ property }} - @see src/global_styling/functions/logicals.ts', | ||
}, | ||
fixable: 'code', | ||
// NOTE: To disable this lint rule for a single line/property within a css`` block | ||
// your code must use a comment inside a template literal, e.g.: | ||
// css` | ||
// color: red; | ||
// height: 40px; ${/* eslint-disable-line local/css-logical-properties */ ''} | ||
// ` | ||
}, | ||
create: function (context) { | ||
return { | ||
TemplateLiteral(node) { | ||
const templateContents = node.quasis || []; | ||
templateContents.forEach((quasi) => { | ||
const stringLiteral = quasi?.value?.raw; | ||
if (!stringLiteral) return; | ||
|
||
findOccurrences(regex, stringLiteral).forEach( | ||
({ match, lineNumber, column }) => { | ||
const property = match.groups.property || match.groups.value; | ||
const whitespace = match.groups.whitespace?.length || 0; | ||
|
||
const lineStart = quasi.loc.start.line + lineNumber; | ||
const columnStart = column + whitespace; | ||
|
||
context.report({ | ||
loc: { | ||
start: { | ||
line: lineStart, | ||
column: columnStart, | ||
}, | ||
end: { | ||
line: lineStart, | ||
column: columnStart + property.length, | ||
}, | ||
}, | ||
messageId: match.groups.value | ||
? 'preferLogicalValue' | ||
: 'preferLogicalProperty', | ||
data: { property }, | ||
fix: function (fixer) { | ||
const literalStart = quasi.range[0] + 1; // Account for backtick | ||
const indexStart = literalStart + match.index + whitespace; | ||
|
||
return fixer.replaceTextRange( | ||
[indexStart, indexStart + property.length], | ||
logicalsFixMap[property] | ||
); | ||
}, | ||
}); | ||
} | ||
); | ||
}); | ||
}, | ||
}; | ||
}, | ||
}; | ||
|
||
/** | ||
* Regex helpers for finding the location of a property | ||
* (vs highlighting the entire css`` node) | ||
* | ||
* credit to https://stackoverflow.com/a/61725880/4294462 | ||
*/ | ||
|
||
const lineNumberByIndex = (index, string) => { | ||
const re = /^[\S\s]/gm; | ||
let line = 0, | ||
match; | ||
let lastRowIndex = 0; | ||
while ((match = re.exec(string))) { | ||
if (match.index > index) break; | ||
lastRowIndex = match.index; | ||
line++; | ||
} | ||
return [Math.max(line - 1, 0), lastRowIndex]; | ||
}; | ||
|
||
const findOccurrences = (needle, haystack) => { | ||
let match; | ||
const result = []; | ||
while ((match = needle.exec(haystack))) { | ||
const pos = lineNumberByIndex(needle.lastIndex, haystack); | ||
result.push({ | ||
match, | ||
lineNumber: pos[0], | ||
column: needle.lastIndex - pos[1] - match[0].length, | ||
}); | ||
} | ||
return result; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
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; | ||
\``, | ||
// Make sure we don't incorrectly catch similar properties that do not have logical equivalents | ||
`\` | ||
line-height: 20px; | ||
border-width: 1px; | ||
scrollbar-width: 30px | ||
\``, | ||
]; | ||
|
||
const invalid = [ | ||
{ | ||
code: 'css`height: 50px;`', | ||
output: 'css`block-size: 50px;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: '`max-height: 50px;`', | ||
output: '`max-block-size: 50px;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`width: 50px;`', | ||
output: 'css`inline-size: 50px;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: '`min-width: 50px;`', | ||
output: '`min-inline-size: 50px;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`top: 0;`', | ||
output: 'css`inset-block-start: 0;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`padding-right: 0;`', | ||
output: 'css`padding-inline-end: 0;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`margin-bottom: 0;`', | ||
output: 'css`margin-block-end: 0;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`border-left: 1px solid green;`', | ||
output: 'css`border-inline-start: 1px solid green;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`border-left-color: red;`', | ||
output: 'css`border-inline-start-color: red;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
{ | ||
code: 'css`text-align: left;`', | ||
output: 'css`text-align: start;`', | ||
errors: [{ messageId: 'preferLogicalValue' }], | ||
}, | ||
{ | ||
code: 'css`overflow-y: hidden;`', | ||
output: 'css`overflow-block: hidden;`', | ||
errors: [{ messageId: 'preferLogicalProperty' }], | ||
}, | ||
// Test multiple errors | ||
{ | ||
code: `css\` | ||
content: 'ok'; | ||
text-align: right; | ||
bottom: 50px; | ||
\``, | ||
output: `css\` | ||
content: 'ok'; | ||
text-align: end; | ||
inset-block-end: 50px; | ||
\``, | ||
errors: [ | ||
{ messageId: 'preferLogicalValue' }, | ||
{ messageId: 'preferLogicalProperty' }, | ||
], | ||
}, | ||
]; | ||
|
||
ruleTester.run('css_logical_properties', rule, { | ||
valid, | ||
invalid, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.