eslint-plugin-tailwind-variants 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +6 -4
  2. package/dist/src/index.d.ts +31 -0
  3. package/dist/src/index.d.ts.map +1 -0
  4. package/dist/src/rules/index.d.ts +7 -0
  5. package/dist/src/rules/index.d.ts.map +1 -0
  6. package/dist/src/rules/limited-inline-classes.d.ts +41 -0
  7. package/dist/src/rules/limited-inline-classes.d.ts.map +1 -0
  8. package/dist/src/rules/require-variants-call-styles-name.d.ts +19 -0
  9. package/dist/src/rules/require-variants-call-styles-name.d.ts.map +1 -0
  10. package/dist/src/rules/require-variants-suffix.d.ts +19 -0
  11. package/dist/src/rules/require-variants-suffix.d.ts.map +1 -0
  12. package/dist/src/rules/sort-custom-properties.d.ts +86 -0
  13. package/dist/src/rules/sort-custom-properties.d.ts.map +1 -0
  14. package/dist/src/utils/create-rule-visitors.d.ts +2 -0
  15. package/dist/src/utils/create-rule-visitors.d.ts.map +1 -0
  16. package/dist/src/utils/get-bind-class-expression.d.ts +3 -0
  17. package/dist/src/utils/get-bind-class-expression.d.ts.map +1 -0
  18. package/package.json +43 -37
  19. package/src/index.js +61 -0
  20. package/{dist → src}/rules/index.js +5 -5
  21. package/src/rules/limited-inline-classes.js +440 -0
  22. package/src/rules/limited-inline-classes.test.js +268 -0
  23. package/src/rules/require-variants-call-styles-name.js +195 -0
  24. package/src/rules/require-variants-call-styles-name.test.js +105 -0
  25. package/src/rules/require-variants-suffix.js +146 -0
  26. package/src/rules/require-variants-suffix.test.js +81 -0
  27. package/src/rules/sort-custom-properties.js +596 -0
  28. package/src/rules/sort-custom-properties.test.js +758 -0
  29. package/src/utils/create-rule-visitors.js +28 -0
  30. package/src/utils/get-bind-class-expression.js +36 -0
  31. package/dist/index.d.ts +0 -11
  32. package/dist/index.js +0 -43
  33. package/dist/rules/index.d.ts +0 -2
  34. package/dist/rules/limited-inline-classes.d.ts +0 -23
  35. package/dist/rules/limited-inline-classes.js +0 -198
  36. package/dist/rules/require-variants-call-styles-name.d.ts +0 -17
  37. package/dist/rules/require-variants-call-styles-name.js +0 -75
  38. package/dist/rules/require-variants-suffix.d.ts +0 -17
  39. package/dist/rules/require-variants-suffix.js +0 -66
  40. package/dist/rules/sort-custom-properties.d.ts +0 -37
  41. package/dist/rules/sort-custom-properties.js +0 -210
  42. package/dist/utils/create-rule-visitors.d.ts +0 -6
  43. package/dist/utils/create-rule-visitors.js +0 -18
  44. package/dist/utils/get-bind-class-expression.d.ts +0 -6
  45. package/dist/utils/get-bind-class-expression.js +0 -20
@@ -1,210 +0,0 @@
1
- import { ESLintUtils } from "@typescript-eslint/utils";
2
- const createRule = ESLintUtils.RuleCreator((name) => name);
3
- const BLOCK_SELECTOR = "Rule > Block, AtRule[name='theme'] > Block, AtRule[name='utility'] > Block";
4
- const defaultOrder = [
5
- "^--spacing-",
6
- "^--size-",
7
- "^--font-",
8
- "^--weight-",
9
- "^--leading-",
10
- "^--tracking-",
11
- "^--radius-",
12
- "^--shadow-",
13
- "^--animate-",
14
- "^--transition-",
15
- "^--color-",
16
- ];
17
- export const MESSAGE_IDS = {
18
- missingEmptyLineBetweenGroups: "missingEmptyLineBetweenGroups",
19
- patternTooLong: "patternTooLong",
20
- unsortedCustomProperties: "unsortedCustomProperties",
21
- };
22
- function isNodeWithOffset(node) {
23
- return (node.loc != null &&
24
- typeof node.loc.start.offset ===
25
- "number" &&
26
- typeof node.loc.end.offset === "number");
27
- }
28
- export const rule = createRule({
29
- name: "sort-custom-properties",
30
- meta: {
31
- docs: {
32
- description: "Enforce sorting of CSS custom properties based on RegEx patterns within declaration blocks.",
33
- },
34
- fixable: "code",
35
- messages: {
36
- missingEmptyLineBetweenGroups: "Expected empty line between different custom property prefix groups",
37
- patternTooLong: "The pattern '{{pattern}}' is too long and may cause performance issues",
38
- unsortedCustomProperties: "Custom properties should be sorted by the defined order: {{order}}",
39
- },
40
- schema: [
41
- {
42
- additionalProperties: false,
43
- properties: {
44
- emptyLineBetweenGroups: {
45
- default: false,
46
- description: "Add empty line between different prefix groups",
47
- type: "boolean",
48
- },
49
- order: {
50
- default: defaultOrder,
51
- description: "Array of RegEx patterns defining the sort order",
52
- items: {
53
- type: "string",
54
- },
55
- type: "array",
56
- },
57
- },
58
- type: "object",
59
- },
60
- ],
61
- type: "layout",
62
- },
63
- defaultOptions: [
64
- {
65
- emptyLineBetweenGroups: false,
66
- order: defaultOrder,
67
- },
68
- ],
69
- create: (context) => {
70
- const options = context.options[0] || {};
71
- const order = options.order || defaultOrder;
72
- const emptyLineBetweenGroups = options.emptyLineBetweenGroups || false;
73
- const { sourceCode } = context;
74
- const compiledOrder = order.map((pattern) => {
75
- if (pattern.length > 100) {
76
- context.report({
77
- data: { pattern },
78
- loc: { column: 1, line: 1 },
79
- messageId: MESSAGE_IDS.patternTooLong,
80
- });
81
- return /(?!)/; // Matches nothing
82
- }
83
- try {
84
- return new RegExp(pattern);
85
- }
86
- catch {
87
- // Fallback: escape special chars and treat as a prefix match
88
- return new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
89
- }
90
- });
91
- const getMatchingOrderIndex = (propName) => {
92
- for (let i = 0; i < compiledOrder.length; i++) {
93
- if (compiledOrder[i].test(propName)) {
94
- return i;
95
- }
96
- }
97
- return compiledOrder.length; // Unmatched properties go to the end
98
- };
99
- const blockStack = [];
100
- return {
101
- [`${BLOCK_SELECTOR}:exit`]() {
102
- const currentBlockProperties = blockStack.pop();
103
- if (!currentBlockProperties || currentBlockProperties.length < 2) {
104
- return;
105
- }
106
- let isSorted = true;
107
- let needsEmptyLines = false;
108
- for (let i = 1; i < currentBlockProperties.length; i++) {
109
- const prev = currentBlockProperties[i - 1];
110
- const curr = currentBlockProperties[i];
111
- if (prev.orderIndex > curr.orderIndex) {
112
- isSorted = false;
113
- break;
114
- }
115
- else if (prev.orderIndex === curr.orderIndex &&
116
- prev.property > curr.property) {
117
- isSorted = false;
118
- break;
119
- }
120
- if (emptyLineBetweenGroups && prev.orderIndex !== curr.orderIndex) {
121
- const prevNode = prev.node;
122
- const currNode = curr.node;
123
- const linesBetween = currNode.loc.start.line - prevNode.loc.end.line;
124
- if (linesBetween < 2) {
125
- needsEmptyLines = true;
126
- }
127
- }
128
- }
129
- if (isSorted && !needsEmptyLines)
130
- return;
131
- const messageId = !isSorted
132
- ? MESSAGE_IDS.unsortedCustomProperties
133
- : MESSAGE_IDS.missingEmptyLineBetweenGroups;
134
- context.report({
135
- ...(messageId === MESSAGE_IDS.unsortedCustomProperties && {
136
- data: {
137
- order: order.join(", "),
138
- },
139
- }),
140
- fix: (fixer) => {
141
- const sorted = [...currentBlockProperties].sort((a, b) => {
142
- if (a.orderIndex !== b.orderIndex) {
143
- return a.orderIndex - b.orderIndex;
144
- }
145
- return a.property.localeCompare(b.property);
146
- });
147
- const allNodesHaveOffsets = sorted.every((item) => isNodeWithOffset(item.node));
148
- if (!allNodesHaveOffsets)
149
- return null;
150
- const getFullDeclaration = (node) => {
151
- const lineStartIndex = sourceCode.getIndexFromLoc({
152
- column: 1,
153
- line: node.loc.start.line,
154
- });
155
- const endIndex = node.loc.end.offset + 1; // Include semicolon
156
- return sourceCode.text.slice(lineStartIndex, endIndex);
157
- };
158
- const fixes = currentBlockProperties.map((prop, index) => {
159
- const sortedNode = sorted[index].node;
160
- const sortedDeclaration = getFullDeclaration(sortedNode);
161
- // Column is 1-based in ESLint loc
162
- const currentLineStart = sourceCode.getIndexFromLoc({
163
- column: 1,
164
- line: prop.node.loc.start.line,
165
- });
166
- let endLine = prop.node.loc.end.line + 1;
167
- const nextProp = currentBlockProperties[index + 1];
168
- if (nextProp) {
169
- endLine = nextProp.node.loc.start.line;
170
- }
171
- const currentLineEnd = sourceCode.getIndexFromLoc({
172
- column: 1,
173
- line: endLine,
174
- });
175
- let replacement = "";
176
- if (emptyLineBetweenGroups && index > 0) {
177
- const prevOrderIndex = sorted[index - 1].orderIndex;
178
- const currOrderIndex = sorted[index].orderIndex;
179
- if (prevOrderIndex !== currOrderIndex) {
180
- replacement = "\n";
181
- }
182
- }
183
- replacement += sortedDeclaration + "\n";
184
- return fixer.replaceTextRange([currentLineStart, currentLineEnd], replacement);
185
- });
186
- return fixes;
187
- },
188
- messageId: messageId,
189
- node: currentBlockProperties[0].node,
190
- });
191
- },
192
- [`:matches(${BLOCK_SELECTOR}) > Declaration`](node) {
193
- if (!node.property.startsWith("--"))
194
- return;
195
- const orderIndex = getMatchingOrderIndex(node.property);
196
- const currentBlock = blockStack[blockStack.length - 1];
197
- if (!currentBlock)
198
- return;
199
- currentBlock.push({
200
- node,
201
- orderIndex,
202
- property: node.property,
203
- });
204
- },
205
- [BLOCK_SELECTOR]() {
206
- blockStack.push([]);
207
- },
208
- };
209
- },
210
- });
@@ -1,6 +0,0 @@
1
- import { RuleContext, RuleListener } from "@typescript-eslint/utils/ts-eslint";
2
- /**
3
- * Creates rule visitors that work for both Vue single-file components and
4
- * regular script files (e.g., React).
5
- */
6
- export declare function createRuleVisitors<TMessageIds extends string, TOptions extends readonly unknown[]>(context: RuleContext<TMessageIds, TOptions>, templateVisitor: RuleListener, scriptVisitor: RuleListener): RuleListener;
@@ -1,18 +0,0 @@
1
- /**
2
- * Creates rule visitors that work for both Vue single-file components and
3
- * regular script files (e.g., React).
4
- */
5
- export function createRuleVisitors(context, templateVisitor, scriptVisitor) {
6
- const fileName = context.filename;
7
- if (fileName.endsWith(".vue")) {
8
- const sourceCode = context.sourceCode;
9
- const parserServices = sourceCode.parserServices;
10
- if (isVueParserServices(parserServices)) {
11
- return parserServices.defineTemplateBodyVisitor(templateVisitor, scriptVisitor);
12
- }
13
- }
14
- return scriptVisitor;
15
- }
16
- function isVueParserServices(services) {
17
- return services !== undefined && "defineTemplateBodyVisitor" in services;
18
- }
@@ -1,6 +0,0 @@
1
- import { AST } from "vue-eslint-parser";
2
- /**
3
- * Return the expression container for a bound class attribute,
4
- * or null if not applicable
5
- */
6
- export declare function getBindClassExpression(node: AST.VAttribute): AST.VExpressionContainer | null;
@@ -1,20 +0,0 @@
1
- /**
2
- * Return the expression container for a bound class attribute,
3
- * or null if not applicable
4
- */
5
- export function getBindClassExpression(node) {
6
- if (!node.directive)
7
- return null;
8
- if (!node.value)
9
- return null;
10
- const key = node.key;
11
- if (key.name.name !== "bind")
12
- return null;
13
- if (key.argument?.type !== "VIdentifier")
14
- return null;
15
- if (key.argument.name !== "class")
16
- return null;
17
- if (!("expression" in node.value))
18
- return null;
19
- return node.value;
20
- }