-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from movableink/require-purgeable-class-names
Require purgeable class names
- Loading branch information
Showing
5 changed files
with
167 additions
and
4 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# `require-purgeable-class-names` | ||
|
||
This lint rules helps ensure that CSS classes are not concatenated dynamically at run-time. Doing so prevents the classes from being detected by [PurgeCSS](https://purgecss.com). Writing purgeable class names is important, as it allows for the automatic removal of unused classes provided by a utility library. This pattern has become especially popular due to the widespread adopting of Tailwind CSS. | ||
|
||
## Examples | ||
|
||
### Forbidden | ||
|
||
```hbs | ||
<div class="text-white bg-{{color}}"></div> | ||
``` | ||
|
||
### Allowed | ||
|
||
```hbs | ||
<div class="text-white {{if @error "bg-red" "bg-black"}}"></div> | ||
``` | ||
|
||
## When Not To Use This | ||
|
||
If it is impossible to know the full set of required class names in your application ahead of time, you'll want to disable this rule. |
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,61 @@ | ||
'use strict'; | ||
|
||
const { Rule } = require('ember-template-lint'); | ||
|
||
const DOES_NOT_END_IN_WHITESPACE_REGEX = /(\S)$/; | ||
|
||
const ERROR_MESSAGE = | ||
'CSS class names should not be created through concatentation, since they cannot be detected for purging. Dynamically select between full class names instead.'; | ||
|
||
function isClassAttribute(node) { | ||
return node.name === 'class' || node.name === '@className' || node.name === '@classNames'; | ||
} | ||
|
||
function isConcatStatement(node) { | ||
return node.type === 'ConcatStatement'; | ||
} | ||
|
||
function isInvalidMustachePrefix(classString) { | ||
return DOES_NOT_END_IN_WHITESPACE_REGEX.test(classString); | ||
} | ||
|
||
module.exports = class RequirePurgeableClassNames extends Rule { | ||
visitor() { | ||
return { | ||
AttrNode(node) { | ||
if (isClassAttribute(node) && isConcatStatement(node.value)) { | ||
for (const part of node.value.parts) { | ||
// Skip processing this "part" if we're not looking at a dynamic segment | ||
if (part.type !== 'MustacheStatement') { | ||
continue; | ||
} | ||
|
||
const currentPartIndex = node.value.parts.indexOf(part); | ||
const previousPart = node.value.parts[currentPartIndex - 1]; | ||
|
||
// This dynamic segment can't be part of a concatentated class if there is no previous "part" | ||
if (!previousPart) { | ||
continue; | ||
} | ||
|
||
// If the previous "text" part doesn't end with whitespace, then the mustache statement is being used | ||
// to dynamically concatenate a class name | ||
if (isInvalidMustachePrefix(previousPart.chars)) { | ||
const classList = previousPart.chars.split(/\s/); | ||
const lastClassString = classList[classList.length - 1]; | ||
|
||
this.log({ | ||
message: ERROR_MESSAGE, | ||
line: part.loc && part.loc.start.line, | ||
column: part.loc && part.loc.start.column - lastClassString.length, | ||
source: lastClassString + this.sourceForNode(part), | ||
}); | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
}; | ||
|
||
module.exports.ERROR_MESSAGE = ERROR_MESSAGE; |
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,75 @@ | ||
'use strict'; | ||
|
||
const generateRuleTests = require('../../helpers/rule-test-harness'); | ||
const { ERROR_MESSAGE } = require('../../../lib/rules/require-purgeable-class-names'); | ||
|
||
generateRuleTests({ | ||
name: 'require-purgeable-class-names', | ||
config: true, | ||
|
||
good: [ | ||
`<div class="bg-red" />`, | ||
`<div class="bg-red {{color}}" />`, | ||
`<div | ||
class=" | ||
bg- | ||
{{color}} | ||
" | ||
/>`, | ||
`<div some-other-attribute="bg-{{color}}" />`, | ||
`<MyComponent @className="bg-red" />`, | ||
], | ||
|
||
bad: [ | ||
{ | ||
template: `<div class="bg-{{color}}" />`, | ||
result: { | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 12, | ||
source: 'bg-{{color}}', | ||
}, | ||
}, | ||
{ | ||
template: `<div class="bg-{{color}} text-{{color}}" />`, | ||
results: [ | ||
{ | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 12, | ||
source: 'bg-{{color}}', | ||
}, | ||
{ | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 25, | ||
source: 'text-{{color}}', | ||
}, | ||
], | ||
}, | ||
{ | ||
template: ` | ||
<div | ||
class=" | ||
bg-{{color}} | ||
" | ||
/> | ||
`, | ||
result: { | ||
message: ERROR_MESSAGE, | ||
line: 4, | ||
column: 12, | ||
source: 'bg-{{color}}', | ||
}, | ||
}, | ||
{ | ||
template: `<MyComponent @className="bg-{{color}}" />`, | ||
result: { | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 25, | ||
source: 'bg-{{color}}', | ||
}, | ||
}, | ||
], | ||
}); |