8000 refactor: reuse the AST extractor by francoismassart · Pull Request #419 · francoismassart/eslint-plugin-tailwindcss · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
cleaning
  • Loading branch information
francoismassart committed Aug 21, 2025
commit c4d3d4a9e733f1c9e46f7cc0df23fb69937b6179
12 changes: 6 additions & 6 deletions src/utils/parser/node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
getTemplateElementAffixes,
getVAttributeName,
} from "./node";
import { jsxAttribute, vAttribute } from "./test-helpers";
import { _jsxAttribute, _vAttribute } from "./test-helpers";

test("getClassnamesFromValue", () => {
expect(getClassnamesFromValue(`flex grow`)).toStrictEqual({
Expand Down Expand Up @@ -49,7 +49,7 @@ test("getTemplateElementAffixes", () => {
});

test("getTagNameFromTaggedTemplateExpression", () => {
const attribute = jsxAttribute("<h1 className={tw`flex`}>html</h1>");
const attribute = _jsxAttribute("<h1 className={tw`flex`}>html</h1>");
if (
attribute.value?.type === TSESTree.AST_NODE_TYPES.JSXExpressionContainer &&
attribute.value.expression.type ===
Expand All @@ -68,11 +68,11 @@ test("getTagNameFromTaggedTemplateExpression", () => {
test("getJSXAttributeName", () => {
[
// JSXLiteral
[jsxAttribute(`<h1 class="m-0 flex">jsx</h1>`), "class"],
[_jsxAttribute(`<h1 class="m-0 flex">jsx</h1>`), "class"],
// JSXExpressionContainer
[jsxAttribute(`<h1 className={'block'}>jsx</h1>`), "className"],
[_jsxAttribute(`<h1 className={'block'}>jsx</h1>`), "className"],
// JSXNamespacedName
[jsxAttribute(`<h1 ns:className={'block'}>jsx</h1>`), "ns:className"],
[_jsxAttribute(`<h1 ns:className={'block'}>jsx</h1>`), "ns:className"],
].map(([input, expected]) => {
// @ts-expect-error Argument of type 'string | JSXAttribute' is not assignable to parameter of type 'JSXAttribute'.
expect(getJSXAttributeName(input)).toBe(expected);
Expand All @@ -88,7 +88,7 @@ test("getVAttributeName", () => {
// VDirectiveKey
[`<h1 :short="{'block': true}">jsx</h1>`, "short"],
].map(([templateCode, expected]) => {
const input = vAttribute(`<template>${templateCode}</template>`);
const input = _vAttribute(`<template>${templateCode}</template>`);
// @ts-expect-error Argument of type 'string | VAttribute' is not assignable to parameter of type 'VAttribute'.
expect(getVAttributeName(input)).toBe(expected);
});
Expand Down
16 changes: 8 additions & 8 deletions src/utils/parser/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const withJSX = {
ecmaFeatures: { jsx: true },
};

export const getFirstHTMLOpeningElement = (code: string) => {
const getFirstHTMLOpeningElement = (code: string) => {
const program = AngularParser.parse(code, { filePath: "node.spec.ts" });
const node = program.templateNodes.at(0);
if (node === undefined) {
Expand All @@ -21,15 +21,15 @@ export const getFirstHTMLOpeningElement = (code: string) => {
return node;
};

export const getHTMLAttribute = (node: GenericElement): TextAttribute => {
const getHTMLAttribute = (node: GenericElement): TextAttribute => {
const htmlAttribute = node.attributes.at(0);
if (htmlAttribute === undefined) {
throw new Error("No HTMLAttribute found");
}
return htmlAttribute;
};

export const getFirstJSXOpeningElement = (code: string) => {
const getFirstJSXOpeningElement = (code: string) => {
const program = Parser.parse(code, withJSX);
const body = program.body.at(0);
if (body === undefined) {
Expand All @@ -44,7 +44,7 @@ export const getFirstJSXOpeningElement = (code: string) => {
return body.expression.openingElement;
};

export const getJSXAttribute = (node: TSESTree.JSXOpeningElement) => {
const getJSXAttribute = (node: TSESTree.JSXOpeningElement) => {
const jsxAttribute = node.attributes.at(0);
if (jsxAttribute === undefined) throw new Error("No JSXAttribute found");
if (jsxAttribute.type === TSESTree.AST_NODE_TYPES.JSXSpreadAttribute)
Expand All @@ -71,22 +71,22 @@ const getVAttribute = (node: VStartTag) => {
return vAttribute;
};

export const htmlAttribute = (code: string) => {
export const _htmlAttribute = (code: string) => {
const element = getFirstHTMLOpeningElement(code);
return getHTMLAttribute(element);
};

export const jsxAttribute = (code: string) => {
export const _jsxAttribute = (code: string) => {
const jsxElement = getFirstJSXOpeningElement(code);
return getJSXAttribute(jsxElement);
};

export const vAttribute = (code: string) => {
export const _vAttribute = (code: string) => {
const vElement = getFirstVOpeningElement(code);
return getVAttribute(vElement);
};

export const callExpression = (code: string) => {
export const _callExpression = (code: string) => {
const program = Parser.parse(code, withJSX);
const body = program.body.at(0);
if (body === undefined) {
Expand Down
46 changes: 23 additions & 23 deletions src/utils/parser/visitors-validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { expect, test } from "vitest";

import { parsePluginSettings, PluginSettings } from "../parse-plugin-settings";
import {
callExpression,
htmlAttribute,
jsxAttribute,
vAttribute,
_callExpression,
_htmlAttribute,
_jsxAttribute,
_vAttribute,
} from "./test-helpers";
import {
isLiteralAttributeValue,
Expand Down Expand Up @@ -43,7 +43,7 @@ test("isValidJSXAttribute", () => {
[`<p custom={'value'}>p</p>`, customAttributeSettings],
];
valid.map(([input, settings]) => {
const node = jsxAttribute(input);
const node = _jsxAttribute(input);
expect(isValidJSXAttribute(node, settings)).toBe(true);
});
});
Expand All @@ -60,7 +60,7 @@ test("isValidTextAttribute", () => {
[`<p custom="value">p</p>`, customAttributeSettings],
];
valid.map(([input, settings]) => {
const node = htmlAttribute(input);
const node = _htmlAttribute(input);
expect(isValidTextAttribute(node, settings)).toBe(true);
});
// Invalid expressions
Expand All @@ -74,7 +74,7 @@ test("isValidTextAttribute", () => {
[`<p className="value">p</p>`, noAttributeSettings],
];
invalid.map(([input, settings]) => {
const node = htmlAttribute(input);
const node = _htmlAttribute(input);
expect(isValidTextAttribute(node, settings)).toBe(false);
});
});
Expand All @@ -90,7 +90,7 @@ test("isValidVAttribute", () => {
[`<p :custom="value">p</p>`, customAttributeSettings],
];
valid.map(([input, settings]) => {
const node = vAttribute(`<template>${input}</template>`);
const node = _vAttribute(`<template>${input}</template>`);
// @ts-expect-error Argument of type 'VAttribute | VDirective' is not assignable to parameter of type 'VAttribute'.
expect(isValidVAttribute(node, settings)).toBe(true);
});
Expand All @@ -106,7 +106,7 @@ test("isValidVAttribute", () => {
[`<p className="value">p</p>`, noAttributeSettings],
];
invalid.map(([input, settings]) => {
const node = vAttribute(`<template>${input}</template>`);
const node = _vAttribute(`<template>${input}</template>`);
// @ts-expect-error Argument of type 'VAttribute | VDirective' is not assignable to parameter of type 'VAttribute'.
expect(isValidVAttribute(node, settings)).toBe(false);
});
Expand All @@ -117,23 +117,23 @@ test("isValidCallExpression", () => {
// Valid expressions
const valid: Array<TestConfig> = [
// Defaults
[callExpression(`ctl('flex')`), defaultSettings],
[callExpression(`obj.ctl('flex')`), defaultSettings],
[_callExpression(`ctl('flex')`), defaultSettings],
[_callExpression(`obj.ctl('flex')`), defaultSettings],
// Custom
[callExpression(`custom('flex')`), customFunctionSettings],
[_callExpression(`custom('flex')`), customFunctionSettings],
];
valid.map(([input, settings]) => {
expect(isValidCallExpression(input, settings)).toBe(true);
});
// Invalid expressions
const invalid: Array<TestConfig> = [
// Defaults
[callExpression(`nope('flex')`), defaultSettings],
[callExpression(`ctl.member('flex')`), defaultSettings],
[_callExpression(`nope('flex')`), defaultSettings],
[_callExpression(`ctl.member('flex')`), defaultSettings],
// Custom
[callExpression(`ctl('flex')`), customFunctionSettings],
[_callExpression(`ctl('flex')`), customFunctionSettings],
// No function
[callExpression(`ctl('flex')`), noFunctionSettings],
[_callExpression(`ctl('flex')`), noFunctionSettings],
];
invalid.map(([input, settings]) => {
expect(isValidCallExpression(input, settings)).toBe(false);
Expand All @@ -144,20 +144,20 @@ test("isLiteralAttributeValue", () => {
// Valid expressions
[
// Normal case: "TextAttribute" via AngularParser (HTML)
htmlAttribute(`<h1 class="flex">normal</h1>`),
_htmlAttribute(`<h1 class="flex">normal</h1>`),
// Normal case (JSX)
jsxAttribute(`<h1 className="flex">normal jsx</h1>`),
_jsxAttribute(`<h1 className="flex">normal jsx</h1>`),
].map((input) => {
expect(isLiteralAttributeValue(input)).toBe(true);
});
// Invalid expressions
[
// No value case via AngularParser (HTML)
htmlAttribute(`<h1 hidden>no value</h1>`),
_htmlAttribute(`<h1 hidden>no value</h1>`),
// No value case (JSX)
jsxAttribute(`<h1 hidden>hidden jsx</h1>`),
_jsxAttribute(`<h1 hidden>hidden jsx</h1>`),
// CallExpression (JSX)
jsxAttribute(`<h1 class={ctl('flex')}>ctl</h1>`),
_jsxAttribute(`<h1 class={ctl('flex')}>ctl</h1>`),
].map((input) => {
expect(isLiteralAttributeValue(input)).toBe(false);
});
Expand All @@ -171,7 +171,7 @@ test("isValidExpressionAttributeValue", () => {
"<p class={tw`flex`}>p</p>",
"<p class={ctl('flex')}>p</p>",
].map((input) => {
const validExpression = jsxAttribute(input);
const validExpression = _jsxAttribute(input);
expect(isValidExpressionAttributeValue(validExpression)).toBe(true);
});
// Invalid expressions
Expand All @@ -183,7 +183,7 @@ test("isValidExpressionAttributeValue", () => {
// Empty expression
`<p class={}>p</p>`,
].map((input) => {
const invalidExpression = jsxAttribute(input);
const invalidExpression = _jsxAttribute(input);
expect(isValidExpressionAttributeValue(invalidExpression)).toBe(false);
});
});
20 changes: 2 additions & 18 deletions src/utils/rule.ts
133E
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export const createScriptVisitors = <TRuleContext, TOptions>(
literals: Array<AtomicNode>
) => void
): RuleListener => {
const callExpression = {
return {
/**
* In JSX + inside <script> section of Vue SFC…
* @example
Expand All @@ -254,9 +254,7 @@ export const createScriptVisitors = <TRuleContext, TOptions>(
);
lintLiterals(context, settings, literals);
},
};

const jsxAttribute = {
/**
* Only the JSXAttributes
* @example
Expand All @@ -274,9 +272,7 @@ export const createScriptVisitors = <TRuleContext, TOptions>(
);
lintLiterals(context, settings, literals);
},
};

const taggedTemplateExpression = {
/**
* In JSX + inside <script> section of Vue SFC…
* @example
Expand All @@ -300,9 +296,7 @@ export const createScriptVisitors = <TRuleContext, TOptions>(
);
lintLiterals(context, settings, literals);
},
};

const textAttribute = {
/**
* Useful for regular HTML (non JSX)
* @example
Expand All @@ -315,13 +309,6 @@ export const createScriptVisitors = <TRuleContext, TOptions>(
lintLiterals(context, settings, literals);
},
};

return {
...callExpression,
...jsxAttribute,
...taggedTemplateExpression,
...textAttribute,
};
};

/**
Expand All @@ -344,7 +331,7 @@ export const createTemplateVisitors = <TRuleContext, TOptions>(
literals: Array<AtomicNode>
) => void
): RuleListener => {
const vAttribute = {
return {
/**
* Visitor for VAttribute within Vue SFC's `<template>`
* @example
Expand Down Expand Up @@ -407,7 +394,4 @@ export const createTemplateVisitors = <TRuleContext, TOptions>(
lintLiterals(context, settings, literals);
},
};
return {
...vAttribute,
};
};