diff --git a/.changeset/smart-lies-admire.md b/.changeset/smart-lies-admire.md new file mode 100644 index 0000000..8a5bb5a --- /dev/null +++ b/.changeset/smart-lies-admire.md @@ -0,0 +1,6 @@ +--- +"eslint-plugin-rtl-friendly": minor +--- + +Support TaggedTemplate ("tw\`...`") +- tw\`...\` is supported but tw\`... ${...}\` is not yet diff --git a/src/rules/no-phyisical-properties/rule.ts b/src/rules/no-phyisical-properties/rule.ts index 0b6684f..22eb105 100644 --- a/src/rules/no-phyisical-properties/rule.ts +++ b/src/rules/no-phyisical-properties/rule.ts @@ -3,7 +3,7 @@ import type { RuleContext, RuleModule, } from "@typescript-eslint/utils/ts-eslint"; -import { type Token, extractTokenFromNode } from "../../utils/ast.js"; +import { type Token, extractTokensFromNode } from "../../utils/ast.js"; import { parseForPhysicalClasses } from "../../utils/tailwind.js"; // const cache = new Map< @@ -58,7 +58,7 @@ export const noPhysicalProperties: RuleModule = { // return; // } - const tokens = extractTokenFromNode(node, "checker"); + const tokens = extractTokensFromNode(node, "checker"); tokens?.forEach((token) => { const classValue = token?.getValue(); if (!classValue) return; diff --git a/src/rules/no-phyisical-properties/test.ts b/src/rules/no-phyisical-properties/test.ts index 3d803ed..8c403a9 100644 --- a/src/rules/no-phyisical-properties/test.ts +++ b/src/rules/no-phyisical-properties/test.ts @@ -108,7 +108,11 @@ tester.run("no-physical-properties", noPhysicalProperties, { name: `{"..." + "..."}`, code: `
`, output: `
`, - errors: [{messageId: NO_PHYSICAL_CLASSESS}, {messageId: NO_PHYSICAL_CLASSESS}, {messageId: NO_PHYSICAL_CLASSESS}] + errors: [ + { messageId: NO_PHYSICAL_CLASSESS }, + { messageId: NO_PHYSICAL_CLASSESS }, + { messageId: NO_PHYSICAL_CLASSESS }, + ], }, { name: '{isCondition && "..."}', @@ -218,6 +222,28 @@ tester.run("no-physical-properties", noPhysicalProperties, { { messageId: NO_PHYSICAL_CLASSESS }, ], }, + { + name: "Identifier", + code: ` + const variable = "text-left"; +
+ `, + errors: [{ messageId: NO_PHYSICAL_CLASSESS }], + skip: true, + }, + { + name: "TaggedTemplate tw`...`", + code: "
", + output: "
", + errors: [{ messageId: NO_PHYSICAL_CLASSESS }], + }, + { + name: "TaggedTemplate tw`...` with ${...} inside (Not Supported)", + code: "
", // ${...} within TaggedTemplate + output: "
", + errors: [{ messageId: NO_PHYSICAL_CLASSESS }], + skip: true, + }, { name: "should report if physical margin properties are used and fix them", code: `
text
`, diff --git a/src/utils/ast.ts b/src/utils/ast.ts index 9bf1dad..41d75e4 100644 --- a/src/utils/ast.ts +++ b/src/utils/ast.ts @@ -11,7 +11,7 @@ export type Token = ( getRaw: () => string; }; -export function extractTokenFromNode( +export function extractTokensFromNode( node: TSESTree.JSXAttribute, runner: "checker" | "fixer" ): (Token | undefined | null)[] { @@ -29,7 +29,7 @@ export function extractTokenFromNode( if (!expression || expression?.type === "JSXEmptyExpression") return []; - return extractTokenFromExpression(expression, runner); + return extractTokensFromExpression(expression, runner); } if (value.type === "JSXElement" || value.type === "JSXSpreadChild") { @@ -40,20 +40,14 @@ export function extractTokenFromNode( return []; } -function extractTokenFromExpression( - exp: TSESTree.Expression, +type Exp = TSESTree.Expression | TSESTree.TemplateElement; + +function extractTokensFromExpression( + exp: Exp, runner: "checker" | "fixer" ): (Token | undefined)[] { - // We care about: - // -> Literal; - // -> TemplateLiteral; - // -> BinaryExpression - // -> CallExpression; - // -> ConditionalExpression; - // -> LogicalExpression; - - const rerun = (expression: TSESTree.Expression) => { - return extractTokenFromExpression(expression, runner); + const rerun = (expression: Exp) => { + return extractTokensFromExpression(expression, runner); }; // const isFixer = runner === "fixer"; @@ -76,6 +70,14 @@ function extractTokenFromExpression( ); } + if (is(exp, "TemplateElement")) { + return format( + exp, + exp.value.cooked, + `\`${exp.value.raw}\`` + ); + } + if (is(exp, "LogicalExpression")) { // isCondition && "..." return rerun(exp.right); @@ -103,8 +105,9 @@ function extractTokenFromExpression( if ( el.type === "AssignmentPattern" || el.type === "TSEmptyBodyFunctionExpression" - ) + ) { return []; + } return rerun(el); }); @@ -121,20 +124,21 @@ function extractTokenFromExpression( }); } - if (is(exp, "BinaryExpression")) { + if (is(exp, "BinaryExpression")) { const right = rerun(exp.right); if (exp.left.type === "PrivateIdentifier") return right; - return [...right, ...rerun(exp.left)]; + return [...right, ...rerun(exp.left)]; } - // if ( - // is(exp, "Identifier") || - // is(exp, "MemberExpression") || - // is(exp, "TaggedTemplateExpression") - // ) { - // // Will be implemented - // return []; - // } + if (is(exp, "TaggedTemplateExpression")) { + // tw`...` + return exp.quasi.quasis.flatMap((q) => rerun(q)); + } + + if (is(exp, "Identifier") || is(exp, "MemberExpression")) { + // We should follow the identifier and get the value + return []; + } // if ((unsupported as typeof exp.type[]).includes(exp.type)) { // if ( @@ -237,9 +241,9 @@ function callOrValue( return typeof func === "function" ? func(param!) : func; } -function is( - exp: TSESTree.Expression, +function is( + exp: Exp, type: `${T}` -): exp is Extract { +): exp is Extract { return exp.type === type; }