Skip to content

Commit

Permalink
HTML parser via @angular-eslint/template-parser (#106)
Browse files Browse the repository at this point in the history
* Testing with `@angular-eslint/template-parser`
* 3.2.1-beta.0
* FIX #73
* 3.2.2-beta.0
* Add `TextAttribute` visitor
* 3.3.1-beta.0
  • Loading branch information
francoismassart authored Jan 21, 2022
1 parent 3c14e78 commit 84d7394
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 42 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ If you enjoy my work you can:

## Latest changelog

- FIX: [Escaping special characters in the `prefix`](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/73)
- FIX: [Formating HTML files](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/85) is now possible using `@angular-eslint/template-parser`

- New feature: [crawling `ArrayExpression` elements and `ObjectExpression`](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/103), see [issue #99](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/99) (by [matt-tingen](https://github.com/matt-tingen) 🙏)
- New rule: [`no-arbitrary-value`](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/89) which forbid using arbitrary values in classnames
- New rule: [default for `cssFiles` option used by `no-custom-classname`](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/37)
Expand Down
25 changes: 17 additions & 8 deletions lib/rules/classnames-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,13 @@ module.exports = {
if (arg === null) {
originalClassNamesValue = astUtil.extractValueFromNode(node);
const range = astUtil.extractRangeFromNode(node);
start = range[0] + 1;
end = range[1] - 1;
if (node.type === 'TextAttribute') {
start = range[0];
end = range[1];
} else {
start = range[0] + 1;
end = range[1] - 1;
}
} else {
switch (arg.type) {
case 'TemplateLiteral':
Expand Down Expand Up @@ -341,13 +346,17 @@ module.exports = {
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------

const attributeVisitor = function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
sortNodeArgumentValue(node);
};

const scriptVisitor = {
JSXAttribute: function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
sortNodeArgumentValue(node);
},
JSXAttribute: attributeVisitor,
TextAttribute: attributeVisitor,
CallExpression: function (node) {
if (callees.findIndex((name) => node.callee.name === name) === -1) {
return;
Expand Down
15 changes: 9 additions & 6 deletions lib/rules/enforces-shorthand.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,16 @@ module.exports = {
// Public
//----------------------------------------------------------------------

const attributeVisitor = function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
parseForShorthandCandidates(node);
};

const scriptVisitor = {
JSXAttribute: function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
parseForShorthandCandidates(node);
},
JSXAttribute: attributeVisitor,
TextAttribute: attributeVisitor,
CallExpression: function (node) {
if (callees.findIndex((name) => node.callee.name === name) === -1) {
return;
Expand Down
15 changes: 9 additions & 6 deletions lib/rules/migration-from-tailwind-2.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,16 @@ module.exports = {
// Public
//----------------------------------------------------------------------

const attributeVisitor = function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
parseForObsoleteClassNames(node);
};

const scriptVisitor = {
JSXAttribute: function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
parseForObsoleteClassNames(node);
},
JSXAttribute: attributeVisitor,
TextAttribute: attributeVisitor,
CallExpression: function (node) {
if (callees.findIndex((name) => node.callee.name === name) === -1) {
return;
Expand Down
15 changes: 9 additions & 6 deletions lib/rules/no-arbitrary-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,16 @@ module.exports = {
// Public
//----------------------------------------------------------------------

const attributeVisitor = function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
parseForArbitraryValues(node);
};

const scriptVisitor = {
JSXAttribute: function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
parseForArbitraryValues(node);
},
JSXAttribute: attributeVisitor,
TextAttribute: attributeVisitor,
CallExpression: function (node) {
if (callees.findIndex((name) => node.callee.name === name) === -1) {
return;
Expand Down
15 changes: 9 additions & 6 deletions lib/rules/no-contradicting-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,16 @@ module.exports = {
// Public
//----------------------------------------------------------------------

const attributeVisitor = function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
astUtil.parseNodeRecursive(node, null, parseForContradictingClassNames, true);
};

const scriptVisitor = {
JSXAttribute: function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
astUtil.parseNodeRecursive(node, null, parseForContradictingClassNames, true);
},
JSXAttribute: attributeVisitor,
TextAttribute: attributeVisitor,
CallExpression: function (node) {
if (callees.findIndex((name) => node.callee.name === name) === -1) {
return;
Expand Down
15 changes: 9 additions & 6 deletions lib/rules/no-custom-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,16 @@ module.exports = {
// Public
//----------------------------------------------------------------------

const attributeVisitor = function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
astUtil.parseNodeRecursive(node, null, parseForCustomClassNames);
};

const scriptVisitor = {
JSXAttribute: function (node) {
if (!astUtil.isValidJSXAttribute(node)) {
return;
}
astUtil.parseNodeRecursive(node, null, parseForCustomClassNames);
},
JSXAttribute: attributeVisitor,
TextAttribute: attributeVisitor,
CallExpression: function (node) {
if (callees.findIndex((name) => node.callee.name === name) === -1) {
return;
Expand Down
26 changes: 25 additions & 1 deletion lib/util/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

'use strict';

// context.parserPath
// /.../eslint-plugin-tailwindcss/node_modules/espree/espree.js
// /.../eslint-plugin-tailwindcss/node_modules/@angular-eslint/template-parser/dist/index.js

const attrUtil = require('./attr');
const removeDuplicatesFromArray = require('./removeDuplicatesFromArray');

Expand All @@ -14,7 +18,18 @@ const removeDuplicatesFromArray = require('./removeDuplicatesFromArray');
* @returns {Boolean}
*/
function isClassAttribute(node) {
return node.name && /^class(Name)?$/.test(node.name.name);
if (!node.name) {
return false;
}
let name = '';
switch (node.type) {
case 'TextAttribute':
name = node.name;
break;
default:
name = node.name.name;
}
return /^class(Name)?$/.test(name);
}

/**
Expand Down Expand Up @@ -47,6 +62,9 @@ function isVueLiteralAttributeValue(node) {
* @returns {Boolean}
*/
function isLiteralAttributeValue(node) {
if (node.type === 'TextAttribute' && node.name === 'class' && typeof node.value === 'string') {
return true;
}
if (node.value) {
switch (node.value.type) {
case 'Literal':
Expand Down Expand Up @@ -97,6 +115,9 @@ function isValidVueAttribute(node) {
}

function extractRangeFromNode(node) {
if (node.type === 'TextAttribute' && node.name === 'class') {
return [node.valueSpan.fullStart.offset, node.valueSpan.end.offset];
}
switch (node.value.type) {
case 'JSXExpressionContainer':
return node.value.expression.range;
Expand All @@ -106,6 +127,9 @@ function extractRangeFromNode(node) {
}

function extractValueFromNode(node) {
if (node.type === 'TextAttribute' && node.name === 'class') {
return node.value;
}
switch (node.value.type) {
case 'JSXExpressionContainer':
return node.value.expression.value;
Expand Down
2 changes: 1 addition & 1 deletion lib/util/groupMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const length = require('./types/length');
* @returns {String} Escaped version
*/
function escapeSpecialChars(str) {
return str.replace(/[\-\.\/\(\)]/g, '\\$&');
return str.replace(/\W/g, '\\$&');
}

/**
Expand Down
18 changes: 17 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-tailwindcss",
"version": "3.3.0",
"version": "3.3.1-beta.0",
"description": "Rules enforcing best practices while using Tailwind CSS",
"keywords": [
"eslint",
Expand Down Expand Up @@ -29,6 +29,7 @@
"tailwindcss": "^3.0.7"
},
"devDependencies": {
"@angular-eslint/template-parser": "^13.0.1",
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/line-clamp": "^0.3.0",
Expand Down
41 changes: 41 additions & 0 deletions tests/lib/rules/classnames-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,5 +661,46 @@ ruleTester.run("classnames-order", rule, {
})`,
errors: errors,
},
{
code: `
<div class="flex-col cursor-pointer flex"></div>
<div class="m-0 lg:m-2 md:m-1"></div>`,
output: `
<div class="flex flex-col cursor-pointer"></div>
<div class="m-0 md:m-1 lg:m-2"></div>`,
errors: [...errors, ...errors],
parser: require.resolve("@angular-eslint/template-parser"),
},
{
code: `<div class="grid lg:grid-col-4 grid-cols-1 sm:grid-cols-2">:)</div>`,
output: `<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-col-4">:)</div>`,
errors: errors,
parser: require.resolve("@angular-eslint/template-parser"),
},
{
code: `
<div class="
flex-col
cursor-pointer
flex
">
:)
</div>`,
output: `
<div class="
flex
flex-col
cursor-pointer
">
:)
</div>`,
errors: errors,
parser: require.resolve("@angular-eslint/template-parser"),
options: [
{
groupByResponsive: false,
},
],
},
],
});
13 changes: 13 additions & 0 deletions tests/lib/rules/no-contradicting-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ ruleTester.run("no-contradicting-classname", rule, {
</div>
</div>`,
},
{
code: `
<div class="p-1 px-2 sm:px-3 sm:pt-0">Accepts shorthands</div>
<div class="m-1 mx-2 sm:mx-3">Accepts shorthands</div>`,
parser: require.resolve("@angular-eslint/template-parser"),
},
],

invalid: [
Expand Down Expand Up @@ -406,6 +412,13 @@ ruleTester.run("no-contradicting-classname", rule, {
options: config,
errors: generateErrors("aspect-none aspect-w-16"),
},
{
code: `
<div class="container w-1 w-2"></div>
<div class="block flex"></div>`,
errors: [...generateErrors("w-1 w-2"), ...generateErrors("block flex")],
parser: require.resolve("@angular-eslint/template-parser"),
},
// {
// code: `
// <div class="scale-75 transform-none">
Expand Down
30 changes: 30 additions & 0 deletions tests/lib/rules/no-custom-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,17 @@ ruleTester.run("no-custom-classname", rule, {
code: `
<div className="transform-none">Disabling transform</div>`,
},
{
code: `
<div className="p/r[e].f!-x_flex">Nasty prefix</div>`,
options: [
{
config: {
prefix: "p/r[e].f!-x_",
},
},
],
},
{
code: `
<div className="text-9xl text-ffffff/[24%] bg-000000">Issue #101</div>`,
Expand Down Expand Up @@ -570,6 +581,25 @@ ruleTester.run("no-custom-classname", rule, {
},
],
},
{
code: `
<div class="transform-none">Using</div>
<div class="flex flex-col">HTML</div>`,
parser: require.resolve("@angular-eslint/template-parser"),
},
{
code: `
<div class="p/r[e].f!-x_flex">Using HTML</div>
<div class="p/r[e].f!-x_block">With nasty prefix</div>`,
options: [
{
config: {
prefix: "p/r[e].f!-x_",
},
},
],
parser: require.resolve("@angular-eslint/template-parser"),
},
],

invalid: [
Expand Down

0 comments on commit 84d7394

Please sign in to comment.