pasika 0.10.113 → 0.10.115

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.
@@ -7121,10 +7121,10 @@ var nextjsStackRule = {
7121
7121
  Document(node) {
7122
7122
  const root = node.body;
7123
7123
  if (root.type !== "Object") return;
7124
- const dependencies = sectionPackages(root, "dependencies");
7124
+ const dependencies2 = sectionPackages(root, "dependencies");
7125
7125
  const devDependencies = sectionPackages(root, "devDependencies");
7126
7126
  for (const pkg of NEXTJS_STACK_DEPENDENCIES) {
7127
- if (dependencies.has(pkg)) continue;
7127
+ if (dependencies2.has(pkg)) continue;
7128
7128
  context.report({
7129
7129
  node,
7130
7130
  message: `${pkg} must be listed in package.json as a dependency.`
@@ -9708,46 +9708,9 @@ var applyUsageRule = {
9708
9708
  };
9709
9709
 
9710
9710
  // eslint/rules/tailwind/base-layer-pair.ts
9711
- var baseLayerPairRule = {
9712
- meta: {
9713
- schema: [],
9714
- type: "problem",
9715
- docs: {
9716
- description: "Require the base layer to apply base-canvas and base-ink to the body."
9717
- }
9718
- },
9719
- create(context) {
9720
- return {
9721
- "StyleSheet:exit"(node) {
9722
- const baseLayers = atrulesNamed(node, "layer").filter((layer) => preludeIdentifiers(layer).includes("base"));
9723
- if (baseLayers.length === 0) return;
9724
- const applied = [];
9725
- for (const layer of baseLayers) {
9726
- for (const child of blockChildren2(layer)) {
9727
- if (child.type !== "Rule") continue;
9728
- if (!selectorNames(child).includes("body")) continue;
9729
- for (const declaration of blockChildren2(child)) {
9730
- if (declaration.type !== "Atrule" || declaration.name !== "apply") continue;
9731
- applied.push(...preludeIdentifiers(declaration));
9732
- }
9733
- }
9734
- }
9735
- const hasCanvas = applied.some((name) => name.includes("canvas"));
9736
- const hasInk = applied.some((name) => name.includes("ink"));
9737
- if (!hasCanvas || !hasInk) {
9738
- context.report({
9739
- node,
9740
- message: "The global base layer must apply a canvas and an ink utility to the document body as the default page pair."
9741
- });
9742
- }
9743
- }
9744
- };
9745
- }
9746
- };
9747
-
9748
- // eslint/rules/tailwind/css-entry-point.ts
9749
- import { statSync as statSync4 } from "fs";
9711
+ import { existsSync as existsSync8 } from "fs";
9750
9712
  import path66 from "path";
9713
+ import { parse as parse3, toPlainObject } from "@eslint/css-tree";
9751
9714
 
9752
9715
  // eslint/rules/tailwind/source-files.ts
9753
9716
  import { readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync3 } from "fs";
@@ -9846,9 +9809,137 @@ function buildStylesheetGraph(options) {
9846
9809
  return { globals, reachable, directChildren };
9847
9810
  }
9848
9811
 
9812
+ // eslint/rules/tailwind/base-layer-pair.ts
9813
+ function collectBodyUtilities(node) {
9814
+ const baseLayers = atrulesNamed(node, "layer").filter((layer) => preludeIdentifiers(layer).includes("base"));
9815
+ const names = [];
9816
+ for (const layer of baseLayers) {
9817
+ for (const child of blockChildren2(layer)) {
9818
+ if (child.type !== "Rule" || !selectorNames(child).includes("body")) continue;
9819
+ for (const declaration of blockChildren2(child)) {
9820
+ if (declaration.type === "Atrule" && declaration.name === "apply") {
9821
+ names.push(...preludeIdentifiers(declaration));
9822
+ }
9823
+ }
9824
+ }
9825
+ }
9826
+ return { hasBaseLayer: baseLayers.length > 0, names };
9827
+ }
9828
+ var baseLayerPairRule = {
9829
+ meta: {
9830
+ schema: [],
9831
+ type: "problem",
9832
+ docs: {
9833
+ description: "Require the global CSS tree's base layer to apply canvas and ink utilities to body."
9834
+ }
9835
+ },
9836
+ create(context) {
9837
+ const sourceRoot = sourceRootOf(context);
9838
+ const current = path66.normalize(path66.resolve(context.filename));
9839
+ const entryPoint = path66.normalize(path66.join(sourceRoot, "app", "styles", "globals.css"));
9840
+ if (current !== entryPoint) return {};
9841
+ return {
9842
+ "StyleSheet:exit"(node) {
9843
+ const applied = collectBodyUtilities(node);
9844
+ const seen = /* @__PURE__ */ new Set([current]);
9845
+ const queue = [{ file: current, source: context.sourceCode.text }];
9846
+ const textOf = cachedTextReader();
9847
+ while (queue.length > 0) {
9848
+ const from = queue.shift();
9849
+ if (!from) continue;
9850
+ for (const specifier of importedSpecifiers(from.source)) {
9851
+ const target = resolveSpecifier2(from.file, specifier, sourceRoot);
9852
+ if (!target || path66.extname(target) !== ".css") continue;
9853
+ const filename = path66.normalize(target);
9854
+ if (seen.has(filename) || !existsSync8(filename)) continue;
9855
+ seen.add(filename);
9856
+ const source = textOf(filename);
9857
+ const parsed = toPlainObject(parse3(source, { parseAtrulePrelude: true }));
9858
+ if (parsed.type !== "StyleSheet") continue;
9859
+ const fragment = collectBodyUtilities(parsed);
9860
+ applied.hasBaseLayer ||= fragment.hasBaseLayer;
9861
+ applied.names.push(...fragment.names);
9862
+ queue.push({ file: filename, source });
9863
+ }
9864
+ }
9865
+ if (!applied.hasBaseLayer) return;
9866
+ const hasCanvas = applied.names.some((name) => name.includes("canvas"));
9867
+ const hasInk = applied.names.some((name) => name.includes("ink"));
9868
+ if (!hasCanvas || !hasInk) {
9869
+ context.report({
9870
+ node,
9871
+ message: "The global base layer must apply a canvas and an ink utility to the document body as the default page pair."
9872
+ });
9873
+ }
9874
+ }
9875
+ };
9876
+ }
9877
+ };
9878
+
9879
+ // eslint/rules/color-role.ts
9880
+ function colorRoleUsageMessage(className) {
9881
+ const segments = className.replace(/!+$/u, "").split(":");
9882
+ const utility = ((segments[segments.length - 1] ?? "").split("/")[0] ?? "").replace(/^!/u, "");
9883
+ if (utility.includes("[") || utility.includes("(")) return void 0;
9884
+ const role = /-(?<role>canvas|ink|edge)$/u.exec(utility)?.groups?.role;
9885
+ if (!role) return void 0;
9886
+ let expected;
9887
+ let purpose;
9888
+ if (/^(?:bg|from|via|to|fill|ring-offset)-/u.test(utility)) {
9889
+ expected = "canvas";
9890
+ purpose = "backgrounds, fills, or gradients";
9891
+ } else if (/^(?:text|caret|decoration)-/u.test(utility)) {
9892
+ expected = "ink";
9893
+ purpose = "text, carets, or text decoration";
9894
+ } else if (/^(?:border|outline|ring|divide|stroke)-/u.test(utility)) {
9895
+ expected = "edge";
9896
+ purpose = "borders, outlines, rings, dividers, or strokes";
9897
+ }
9898
+ if (!expected || !purpose || role === expected) return void 0;
9899
+ return `Color utility "${className}" uses a "${role}" token for ${purpose}; use a "${expected}" token instead.`;
9900
+ }
9901
+
9902
+ // eslint/rules/tailwind/color-role-usage.ts
9903
+ var COLOR_TOKEN = /^--color-[a-z][a-z0-9-]*-(?:canvas|ink|edge)$/u;
9904
+ var colorRoleUsageRule = {
9905
+ meta: {
9906
+ schema: [],
9907
+ type: "problem",
9908
+ docs: { description: "Require semantic color-token names and matching Tailwind color utilities." }
9909
+ },
9910
+ create(context) {
9911
+ return {
9912
+ "StyleSheet:exit"(node) {
9913
+ for (const theme of atrulesNamed(node, "theme")) {
9914
+ const source = context.sourceCode.getText(theme).replace(/\/\*[\s\S]*?\*\//gu, " ");
9915
+ for (const match of source.matchAll(/(?:^|[{;\r\n])\s*(?<token>--color-[a-z][a-z0-9-]*)\s*:/gu)) {
9916
+ const token = match.groups?.token;
9917
+ if (token && !COLOR_TOKEN.test(token)) {
9918
+ context.report({
9919
+ node: theme,
9920
+ message: `Color token "${token}" needs a semantic role ending in -canvas, -ink, or -edge.`
9921
+ });
9922
+ }
9923
+ }
9924
+ }
9925
+ for (const apply of atrulesNamed(node, "apply")) {
9926
+ const source = context.sourceCode.getText(apply);
9927
+ const classes = source.replace(/^@apply\s+/u, "").replace(/;\s*$/u, "").trim().split(/\s+/u);
9928
+ for (const className of classes) {
9929
+ const message = colorRoleUsageMessage(className);
9930
+ if (message) context.report({ node: apply, message });
9931
+ }
9932
+ }
9933
+ }
9934
+ };
9935
+ }
9936
+ };
9937
+
9849
9938
  // eslint/rules/tailwind/css-entry-point.ts
9850
- var APP_STYLES_DIR = path66.join("app", "styles");
9851
- var GLOBAL_STYLESHEET_PATH = path66.join(APP_STYLES_DIR, "globals.css");
9939
+ import { statSync as statSync4 } from "fs";
9940
+ import path67 from "path";
9941
+ var APP_STYLES_DIR = path67.join("app", "styles");
9942
+ var GLOBAL_STYLESHEET_PATH = path67.join(APP_STYLES_DIR, "globals.css");
9852
9943
  var cssEntryPointRule = {
9853
9944
  meta: {
9854
9945
  schema: [],
@@ -9874,8 +9965,8 @@ var cssEntryPointRule = {
9874
9965
  return {
9875
9966
  "StyleSheet:exit"(node) {
9876
9967
  if (globals.length === 0) return;
9877
- const current = path66.normalize(path66.resolve(context.filename));
9878
- const relative = path66.relative(sourceRoot, current);
9968
+ const current = path67.normalize(path67.resolve(context.filename));
9969
+ const relative = path67.relative(sourceRoot, current);
9879
9970
  if (globals.includes(current)) {
9880
9971
  if (globals.length > 1) {
9881
9972
  context.report({
@@ -9891,7 +9982,7 @@ var cssEntryPointRule = {
9891
9982
  });
9892
9983
  return;
9893
9984
  }
9894
- const basename = path66.basename(current);
9985
+ const basename = path67.basename(current);
9895
9986
  const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
9896
9987
  if (importCount !== 1) {
9897
9988
  context.report({
@@ -9901,7 +9992,7 @@ var cssEntryPointRule = {
9901
9992
  }
9902
9993
  return;
9903
9994
  }
9904
- const isInAppStyles = relative === APP_STYLES_DIR || relative.startsWith(`${APP_STYLES_DIR}${path66.sep}`);
9995
+ const isInAppStyles = relative === APP_STYLES_DIR || relative.startsWith(`${APP_STYLES_DIR}${path67.sep}`);
9905
9996
  if (!isInAppStyles) {
9906
9997
  context.report({
9907
9998
  node,
@@ -9932,106 +10023,6 @@ var cssEntryPointRule = {
9932
10023
  }
9933
10024
  };
9934
10025
 
9935
- // eslint/project/semantic-color-role.ts
9936
- var BOUNDARY_PROPERTY_WORDS = /* @__PURE__ */ new Set(["border", "outline", "ring", "stroke"]);
9937
- function violatesBoundaryColorRole(token) {
9938
- return !token.endsWith("-edge") && token.split("-").some((word) => BOUNDARY_PROPERTY_WORDS.has(word));
9939
- }
9940
- var BACKGROUND_COLOR_UTILITIES = /* @__PURE__ */ new Set(["bg", "from", "via", "to"]);
9941
- var TEXT_COLOR_UTILITIES = /* @__PURE__ */ new Set(["text"]);
9942
- var BOUNDARY_COLOR_UTILITIES = /* @__PURE__ */ new Set(["border", "divide", "outline", "ring", "stroke"]);
9943
- function incompatibleColorUtilityRole(prefix, token) {
9944
- let role;
9945
- if (token.endsWith("-canvas")) role = "canvas";
9946
- else if (token.endsWith("-ink")) role = "ink";
9947
- else if (token.endsWith("-edge")) role = "edge";
9948
- if (!role) return void 0;
9949
- if (BACKGROUND_COLOR_UTILITIES.has(prefix)) return role === "canvas" ? void 0 : role;
9950
- if (TEXT_COLOR_UTILITIES.has(prefix)) return role === "ink" ? void 0 : role;
9951
- if (BOUNDARY_COLOR_UTILITIES.has(prefix)) return role === "edge" ? void 0 : role;
9952
- return void 0;
9953
- }
9954
-
9955
- // eslint/rules/tailwind/css-variable-naming.ts
9956
- var EDGE_PROPERTY_WORDS = /* @__PURE__ */ new Set(["border", "outline", "ring", "stroke"]);
9957
- var NON_COLOR_WORDS = /* @__PURE__ */ new Set(["height", "leading", "offset", "radius", "size", "spacing", "style", "width"]);
9958
- var TAILWIND_THEME_NAMESPACES = /* @__PURE__ */ new Set([
9959
- "animate",
9960
- "aspect",
9961
- "blur",
9962
- "breakpoint",
9963
- "color",
9964
- "container",
9965
- "drop",
9966
- "ease",
9967
- "font",
9968
- "inset",
9969
- "leading",
9970
- "perspective",
9971
- "radius",
9972
- "shadow",
9973
- "spacing",
9974
- "text",
9975
- "tracking"
9976
- ]);
9977
- function isTailwindThemeVariable(property) {
9978
- const namespace = property.slice(2).split("-")[0];
9979
- return namespace !== void 0 && TAILWIND_THEME_NAMESPACES.has(namespace);
9980
- }
9981
- function looksLikeEdgeColor(lower) {
9982
- const words = lower.slice(2).split("-");
9983
- return words.some((word) => EDGE_PROPERTY_WORDS.has(word)) && !words.some((word) => NON_COLOR_WORDS.has(word));
9984
- }
9985
- function looksLikeTextColor(lower) {
9986
- const words = lower.slice(2).split("-");
9987
- return words.includes("text") && !words.some((word) => NON_COLOR_WORDS.has(word));
9988
- }
9989
- var cssVariableNamingRule = {
9990
- meta: {
9991
- schema: [],
9992
- type: "problem",
9993
- docs: {
9994
- description: "Require property-specific color variables to use canvas, ink, and edge role suffixes."
9995
- }
9996
- },
9997
- create(context) {
9998
- return {
9999
- Declaration(node) {
10000
- const property = node.property;
10001
- if (!property.startsWith("--")) return;
10002
- if (isTailwindThemeVariable(property)) {
10003
- if (property.startsWith("--color-") && violatesBoundaryColorRole(property.slice("--color-".length))) {
10004
- context.report({
10005
- node,
10006
- message: `Theme color "${property}" names a boundary using a CSS property; use --color-<role>-edge and border-<role>-edge instead.`
10007
- });
10008
- }
10009
- return;
10010
- }
10011
- const lower = property.toLowerCase();
10012
- if ((lower.includes("bg") || lower.includes("background")) && !lower.endsWith("-canvas")) {
10013
- context.report({
10014
- node,
10015
- message: `CSS variable "${property}" looks like a background token; name it --<role>-canvas instead.`
10016
- });
10017
- }
10018
- if (looksLikeTextColor(lower) && !lower.endsWith("-ink")) {
10019
- context.report({
10020
- node,
10021
- message: `CSS variable "${property}" looks like a text token; name it --<role>-ink instead.`
10022
- });
10023
- }
10024
- if (looksLikeEdgeColor(lower) && !lower.endsWith("-edge")) {
10025
- context.report({
10026
- node,
10027
- message: `CSS variable "${property}" looks like a boundary token; name it --<role>-edge instead.`
10028
- });
10029
- }
10030
- }
10031
- };
10032
- }
10033
- };
10034
-
10035
10026
  // eslint/rules/tailwind/custom-utility-apply.ts
10036
10027
  function rawProperty(raw) {
10037
10028
  const match = /^\s*(?<property>[a-z][a-z-]*)\s*:/.exec(raw);
@@ -10074,6 +10065,440 @@ var customUtilityApplyRule = {
10074
10065
  }
10075
10066
  };
10076
10067
 
10068
+ // eslint/rules/tailwind/feature-css-placement.ts
10069
+ import { existsSync as existsSync9 } from "fs";
10070
+ import path70 from "path";
10071
+
10072
+ // eslint/rules/tailwind/css-dependency-graph.ts
10073
+ import path68 from "path";
10074
+ import { generate, parse as parse4, walk } from "@eslint/css-tree";
10075
+ function lineAt(text, offset) {
10076
+ return text.slice(0, offset).split("\n").length;
10077
+ }
10078
+ function tokenLines(content, token) {
10079
+ const regex = new RegExp(`(?<![\\w-])${escapeRegExp(token)}(?![\\w-])`, "gu");
10080
+ return [...content.matchAll(regex)].map((found) => lineAt(content, found.index));
10081
+ }
10082
+ function pushSymbol(symbols, kind, name, file, line, value) {
10083
+ if (name) symbols.push({ kind, name, file, line, ...value === void 0 ? {} : { value } });
10084
+ }
10085
+ function themeClasses(name) {
10086
+ if (name.includes("--", 2)) return [];
10087
+ if (name.startsWith("--color-")) {
10088
+ const stem = name.slice("--color-".length);
10089
+ if (stem.endsWith("-canvas")) {
10090
+ return ["bg", "fill", "from", "via", "to", "ring-offset"].map((prefix) => `${prefix}-${stem}`);
10091
+ }
10092
+ if (stem.endsWith("-ink")) return ["text", "caret", "decoration"].map((prefix) => `${prefix}-${stem}`);
10093
+ if (stem.endsWith("-edge")) {
10094
+ return ["border", "outline", "ring", "divide", "stroke"].map((prefix) => `${prefix}-${stem}`);
10095
+ }
10096
+ return ["bg", "text", "border", "fill", "stroke", "ring"].map((prefix) => `${prefix}-${stem}`);
10097
+ }
10098
+ if (name.startsWith("--animate-")) return [`animate-${name.slice("--animate-".length)}`];
10099
+ if (name.startsWith("--radius-")) return [`rounded-${name.slice("--radius-".length)}`];
10100
+ if (name.startsWith("--text-") || name.startsWith("--tracking-") || name.startsWith("--font-")) {
10101
+ return [name.slice(2)];
10102
+ }
10103
+ return [];
10104
+ }
10105
+ function buildCssDependencyGraph(options) {
10106
+ const definitions = [];
10107
+ const references = [];
10108
+ const imports = [];
10109
+ for (const file of options.css) {
10110
+ const filename = path68.normalize(file.path);
10111
+ for (const spec of importedSpecifiers(file.content)) {
10112
+ const target = resolveSpecifier2(filename, spec, options.sourceRoot);
10113
+ if (target) imports.push({ from: filename, to: path68.normalize(target) });
10114
+ }
10115
+ const ast = parse4(file.content, { positions: true, parseAtrulePrelude: false });
10116
+ const atRuleStack = [];
10117
+ walk(ast, {
10118
+ enter(node) {
10119
+ if (node.type === "Atrule") {
10120
+ atRuleStack.push({
10121
+ name: node.name,
10122
+ baseLayer: node.name === "layer" && node.prelude !== null && generate(node.prelude).trim() === "base"
10123
+ });
10124
+ }
10125
+ const line = node.loc?.start.line ?? 1;
10126
+ if (node.type === "Atrule") {
10127
+ if (node.name === "utility" || node.name === "keyframes") {
10128
+ pushSymbol(definitions, node.name, node.prelude ? generate(node.prelude).trim() : "", filename, line);
10129
+ } else if (node.name === "font-face") {
10130
+ const descriptors = /* @__PURE__ */ new Map();
10131
+ for (const child of node.block?.children ?? []) {
10132
+ if (child.type === "Declaration") descriptors.set(child.property, generate(child.value).trim());
10133
+ }
10134
+ const family = descriptors.get("font-family")?.replace(/^["']|["']$/gu, "").replace(/\s+/gu, " ").toLowerCase();
10135
+ if (family) {
10136
+ const descriptor = (name, fallback) => (descriptors.get(name) ?? fallback).toLowerCase().replace(/\s+/gu, " ").trim();
10137
+ const weight = descriptor("font-weight", "400");
10138
+ let normalizedWeight = weight;
10139
+ if (weight === "normal") normalizedWeight = "400";
10140
+ else if (weight === "bold") normalizedWeight = "700";
10141
+ const stretch = descriptor("font-stretch", "normal");
10142
+ definitions.push({
10143
+ kind: "font-face",
10144
+ name: family,
10145
+ file: filename,
10146
+ line,
10147
+ identity: [
10148
+ family,
10149
+ descriptor("font-style", "normal"),
10150
+ normalizedWeight,
10151
+ stretch === "100%" ? "normal" : stretch,
10152
+ descriptor("unicode-range", "u+0-10ffff")
10153
+ ].join("|")
10154
+ });
10155
+ }
10156
+ } else if (node.name === "theme" && node.loc) {
10157
+ const source = file.content.slice(node.loc.start.offset, node.loc.end.offset).replace(/\/\*[\s\S]*?\*\//gu, (comment) => comment.replace(/[^\r\n]/gu, " "));
10158
+ for (const found of source.matchAll(
10159
+ /(?:^|[{;\r\n])\s*(?<name>--[a-z][\w-]*)\s*:\s*(?<value>[^;\r\n]+);/gu
10160
+ )) {
10161
+ const name = found.groups?.name;
10162
+ const value = found.groups?.value;
10163
+ if (!name || !value) continue;
10164
+ pushSymbol(
10165
+ definitions,
10166
+ "theme-token",
10167
+ name,
10168
+ filename,
10169
+ lineAt(file.content, node.loc.start.offset + found.index + found[0].indexOf(name)),
10170
+ value.trim()
10171
+ );
10172
+ }
10173
+ }
10174
+ }
10175
+ if (node.type === "Rule" && !atRuleStack.some((entry) => entry.name === "keyframes" || entry.baseLayer)) {
10176
+ const selector = generate(node.prelude).trim();
10177
+ const match = /^\.(?<name>[a-zA-Z_][\w-]*)(?:::{0,1}[a-zA-Z-]+(?:\([^)]*\))?)*$/u.exec(selector);
10178
+ if (match?.groups?.name) pushSymbol(definitions, "selector", match.groups.name, filename, line);
10179
+ }
10180
+ if (node.type !== "Declaration" || !node.property.startsWith("--") || node.property === "--*") return;
10181
+ const inRoot = this.rule?.type === "Rule" && generate(this.rule.prelude).trim() === ":root";
10182
+ if (inRoot) {
10183
+ const scope = atRuleStack.every((entry) => entry.name === "layer") ? "default" : "conditional";
10184
+ definitions.push({
10185
+ kind: "root-variable",
10186
+ name: node.property,
10187
+ file: filename,
10188
+ line,
10189
+ value: generate(node.value),
10190
+ scope
10191
+ });
10192
+ }
10193
+ },
10194
+ leave(node) {
10195
+ if (node.type === "Atrule") atRuleStack.pop();
10196
+ }
10197
+ });
10198
+ if (options.includeReferences === false) continue;
10199
+ for (const found of file.content.matchAll(/var\(\s*(?<name>--[\w-]+)/gu)) {
10200
+ if (!found.groups?.name) continue;
10201
+ references.push({
10202
+ kind: "variable",
10203
+ name: found.groups.name,
10204
+ file: filename,
10205
+ line: lineAt(file.content, found.index),
10206
+ source: "css"
10207
+ });
10208
+ }
10209
+ }
10210
+ if (options.includeReferences === false) return { definitions, references, imports };
10211
+ const utilities = new Set(definitions.filter((item) => item.kind === "utility").map((item) => item.name));
10212
+ const selectors = new Set(definitions.filter((item) => item.kind === "selector").map((item) => item.name));
10213
+ const keyframes = new Set(definitions.filter((item) => item.kind === "keyframes").map((item) => item.name));
10214
+ const generatedClasses = /* @__PURE__ */ new Map();
10215
+ for (const symbol of definitions) {
10216
+ if (symbol.kind !== "theme-token") continue;
10217
+ for (const className of themeClasses(symbol.name)) generatedClasses.set(className, symbol.name);
10218
+ }
10219
+ for (const file of options.css) {
10220
+ const filename = path68.normalize(file.path);
10221
+ const ast = parse4(file.content, { positions: true, parseAtrulePrelude: false });
10222
+ walk(ast, {
10223
+ enter(node) {
10224
+ let kind;
10225
+ let text;
10226
+ let names;
10227
+ if (node.type === "Atrule" && node.name === "apply" && node.prelude) {
10228
+ kind = "utility";
10229
+ text = generate(node.prelude);
10230
+ names = utilities;
10231
+ } else if (node.type === "Declaration" && (node.property === "animation" || node.property === "animation-name")) {
10232
+ kind = "keyframes";
10233
+ text = generate(node.value);
10234
+ names = keyframes;
10235
+ } else return;
10236
+ for (const name of names) {
10237
+ if (tokenLines(text, name).length > 0) {
10238
+ references.push({ kind, name, file: filename, line: node.loc?.start.line ?? 1, source: "css" });
10239
+ }
10240
+ }
10241
+ if (kind === "utility") {
10242
+ for (const match of text.matchAll(/\[(?:animation|animation-name):(?<value>[^\]]+)\]/gu)) {
10243
+ const normalized = (match.groups?.value ?? "").replace(/_/gu, " ");
10244
+ for (const name of keyframes) {
10245
+ if (tokenLines(normalized, name).length > 0) {
10246
+ references.push({
10247
+ kind: "keyframes",
10248
+ name,
10249
+ file: filename,
10250
+ line: node.loc?.start.line ?? 1,
10251
+ source: "css"
10252
+ });
10253
+ }
10254
+ }
10255
+ }
10256
+ for (const [name, token] of generatedClasses) {
10257
+ if (tokenLines(text, name).length > 0) {
10258
+ references.push({
10259
+ kind: "theme-class",
10260
+ name,
10261
+ token,
10262
+ file: filename,
10263
+ line: node.loc?.start.line ?? 1,
10264
+ source: "css"
10265
+ });
10266
+ }
10267
+ }
10268
+ }
10269
+ }
10270
+ });
10271
+ }
10272
+ for (const file of options.modules) {
10273
+ const filename = path68.normalize(file.path);
10274
+ for (const [kind, names] of [
10275
+ ["utility", utilities],
10276
+ ["keyframes", keyframes],
10277
+ ["selector", selectors]
10278
+ ]) {
10279
+ for (const name of names) {
10280
+ for (const line of tokenLines(file.content, name)) {
10281
+ references.push({ kind, name, file: filename, line, source: "module" });
10282
+ }
10283
+ }
10284
+ }
10285
+ for (const [name, token] of generatedClasses) {
10286
+ for (const line of tokenLines(file.content, name)) {
10287
+ references.push({ kind: "theme-class", name, token, file: filename, line, source: "module" });
10288
+ }
10289
+ }
10290
+ }
10291
+ return { definitions, references, imports };
10292
+ }
10293
+ function cssSymbolUsages(graph, kind, name) {
10294
+ return graph.references.filter((item) => item.kind === kind && item.name === name);
10295
+ }
10296
+
10297
+ // eslint/rules/tailwind/css-ownership.ts
10298
+ import path69 from "path";
10299
+ import { generate as generate2, parse as parse5, walk as walk2 } from "@eslint/css-tree";
10300
+ var GLOBAL = "globals.css";
10301
+ function symbolId(def) {
10302
+ return [def.file, def.kind, def.name, def.line].join("|");
10303
+ }
10304
+ function consumerFeature(file, sourceRoot) {
10305
+ const parts = path69.relative(sourceRoot, file).split(path69.sep);
10306
+ return parts[0] === "features" && parts.length > 2 ? parts[1] ?? GLOBAL : GLOBAL;
10307
+ }
10308
+ function contains(text, name) {
10309
+ return new RegExp(`(?<![\\w-])${escapeRegExp(name)}(?![\\w-])`, "u").test(text);
10310
+ }
10311
+ function dependencies(text, defs) {
10312
+ const result = /* @__PURE__ */ new Set();
10313
+ const variables = new Set([...text.matchAll(/var\(\s*(?<name>--[\w-]+)/gu)].map((m) => m.groups?.name));
10314
+ const applied = [...text.matchAll(/@apply\s+(?<value>[^;}]+)/gu)].map((m) => m.groups?.value ?? "");
10315
+ const animations = [...text.matchAll(/(?:animation|animation-name)\s*:\s*(?<value>[^;}\]]+)/gu)].map(
10316
+ (m) => (m.groups?.value ?? "").replaceAll("_", " ")
10317
+ );
10318
+ const families = [...text.matchAll(/font-family\s*:\s*(?<value>[^;}\]]+)/gu)].map(
10319
+ (m) => (m.groups?.value ?? "").replaceAll("_", " ").toLowerCase()
10320
+ );
10321
+ for (const def of defs) {
10322
+ const match = (def.kind === "theme-token" || def.kind === "root-variable") && variables.has(def.name) || (def.kind === "utility" || def.kind === "selector") && applied.some((part) => contains(part, def.name)) || def.kind === "keyframes" && animations.some((part) => contains(part, def.name)) || def.kind === "font-face" && (families.some((part) => contains(part, def.name)) || new RegExp(`^\\s*["']?${escapeRegExp(def.name)}["']?\\s*(?:,|$)`, "iu").test(text)) || def.kind === "theme-token" && applied.some((part) => themeClasses(def.name).some((name) => contains(part, name)));
10323
+ if (match) result.add(symbolId(def));
10324
+ }
10325
+ return result;
10326
+ }
10327
+ function bodiesOf(css2, defs) {
10328
+ const bodies = /* @__PURE__ */ new Map();
10329
+ for (const src of css2) {
10330
+ const file = path69.normalize(src.path);
10331
+ const ast = parse5(src.content, { positions: true, parseAtrulePrelude: false });
10332
+ walk2(ast, {
10333
+ enter(node) {
10334
+ if (!node.loc) return;
10335
+ let kind;
10336
+ if (node.type === "Atrule" && ["utility", "keyframes", "font-face"].includes(node.name)) {
10337
+ kind = node.name;
10338
+ } else if (node.type === "Rule") {
10339
+ kind = "selector";
10340
+ }
10341
+ if (!kind) return;
10342
+ const found = defs.filter((def) => def.file === file && def.kind === kind && def.line === node.loc?.start.line);
10343
+ if (found.length !== 1 || !found[0]) return;
10344
+ bodies.set(symbolId(found[0]), src.content.slice(node.loc.start.offset, node.loc.end.offset));
10345
+ }
10346
+ });
10347
+ }
10348
+ for (const def of defs) {
10349
+ if ((def.kind === "theme-token" || def.kind === "root-variable") && def.value !== void 0) {
10350
+ bodies.set(symbolId(def), def.value);
10351
+ }
10352
+ }
10353
+ return bodies;
10354
+ }
10355
+ function inferCssOwners(options) {
10356
+ const { graph, css: css2, modules, sourceRoot } = options;
10357
+ const defs = graph.definitions;
10358
+ const owners = new Map(defs.map((def) => [symbolId(def), /* @__PURE__ */ new Set()]));
10359
+ const counts = /* @__PURE__ */ new Map();
10360
+ for (const def of defs) {
10361
+ if (def.kind === "root-variable" && def.scope === "conditional") continue;
10362
+ const key = `${def.kind}:${def.identity ?? def.name}`;
10363
+ counts.set(key, (counts.get(key) ?? 0) + 1);
10364
+ }
10365
+ const duplicates = new Set(
10366
+ defs.filter((d) => (counts.get(`${d.kind}:${d.identity ?? d.name}`) ?? 0) > 1).map(symbolId)
10367
+ );
10368
+ for (const def of defs) {
10369
+ const key = symbolId(def);
10370
+ if (duplicates.has(key) || def.kind === "root-variable" && def.scope === "conditional") continue;
10371
+ for (const ref of graph.references) {
10372
+ if (ref.source !== "module") continue;
10373
+ if (def.kind === "utility" && ref.kind === "utility" && ref.name === def.name || def.kind === "selector" && ref.kind === "selector" && ref.name === def.name || def.kind === "theme-token" && ref.kind === "theme-class" && ref.token === def.name) {
10374
+ owners.get(key)?.add(consumerFeature(ref.file, sourceRoot));
10375
+ }
10376
+ }
10377
+ for (const mod of modules) {
10378
+ const name = escapeRegExp(def.name);
10379
+ const explicit = (def.kind === "theme-token" || def.kind === "root-variable") && new RegExp(`var\\(\\s*${name}(?![\\w-])`, "u").test(mod.content) || def.kind === "keyframes" && new RegExp(`(?:animationName|animation-name|animation)\\s*[:=]\\s*["'][^"']*${name}`, "u").test(
10380
+ mod.content
10381
+ ) || def.kind === "font-face" && new RegExp(`(?:fontFamily|font-family)\\s*[:=]\\s*["'][^"']*${name}`, "iu").test(mod.content);
10382
+ if (explicit) owners.get(key)?.add(consumerFeature(mod.path, sourceRoot));
10383
+ }
10384
+ if (def.kind === "theme-token" && (def.name === "--spacing" || def.name.startsWith("--color-base-")) || def.kind === "root-variable" && def.name.startsWith("--color-base-")) {
10385
+ owners.get(key)?.add(GLOBAL);
10386
+ }
10387
+ }
10388
+ const links = /* @__PURE__ */ new Map();
10389
+ for (const [key, content] of bodiesOf(css2, defs)) {
10390
+ const targets = dependencies(content, defs);
10391
+ const def = defs.find((candidate) => symbolId(candidate) === key);
10392
+ if (def?.kind === "theme-token" && def.name.startsWith("--animate-")) {
10393
+ for (const frame of defs) {
10394
+ if (frame.kind === "keyframes" && contains(content, frame.name)) {
10395
+ targets.add(symbolId(frame));
10396
+ }
10397
+ }
10398
+ }
10399
+ targets.delete(key);
10400
+ links.set(key, targets);
10401
+ }
10402
+ for (const src of css2) {
10403
+ if (path69.basename(src.path) !== GLOBAL) continue;
10404
+ const ast = parse5(src.content, { positions: true, parseAtrulePrelude: false });
10405
+ if (ast.type !== "StyleSheet") continue;
10406
+ for (const node of ast.children) {
10407
+ if (node.type !== "Atrule" || node.name !== "layer" || !node.loc || !node.prelude || generate2(node.prelude).trim() !== "base") {
10408
+ continue;
10409
+ }
10410
+ const body = src.content.slice(node.loc.start.offset, node.loc.end.offset);
10411
+ for (const key of dependencies(body, defs)) owners.get(key)?.add(GLOBAL);
10412
+ }
10413
+ }
10414
+ let changed = true;
10415
+ while (changed) {
10416
+ changed = false;
10417
+ for (const [from, targets] of links) {
10418
+ for (const feature of owners.get(from) ?? []) {
10419
+ for (const to of targets) {
10420
+ if (duplicates.has(to)) continue;
10421
+ const set = owners.get(to);
10422
+ if (set && !set.has(feature)) {
10423
+ set.add(feature);
10424
+ changed = true;
10425
+ }
10426
+ }
10427
+ }
10428
+ }
10429
+ }
10430
+ const result = /* @__PURE__ */ new Map();
10431
+ for (const def of defs) {
10432
+ const key = symbolId(def);
10433
+ const set = owners.get(key);
10434
+ let owner;
10435
+ if (!duplicates.has(key) && set && set.size > 0 && !(def.kind === "root-variable" && def.scope === "conditional")) {
10436
+ owner = set.size === 1 ? [...set][0] : GLOBAL;
10437
+ }
10438
+ result.set(key, owner);
10439
+ }
10440
+ return result;
10441
+ }
10442
+
10443
+ // eslint/rules/tailwind/feature-css-placement.ts
10444
+ var STYLES_DIR = path70.join("app", "styles");
10445
+ var GLOBALS = "globals.css";
10446
+ var FEATURE_CSS = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.css$/u;
10447
+ var LABELS = {
10448
+ utility: "utility",
10449
+ "theme-token": "theme token",
10450
+ "root-variable": "root variable",
10451
+ keyframes: "keyframes",
10452
+ "font-face": "font face",
10453
+ selector: "selector"
10454
+ };
10455
+ var featureCssPlacementRule = {
10456
+ meta: {
10457
+ schema: [],
10458
+ type: "problem",
10459
+ docs: {
10460
+ description: "Enforce ownership of CSS definitions and their transitive dependencies across feature scopes."
10461
+ }
10462
+ },
10463
+ create(context) {
10464
+ const sourceRoot = sourceRootOf(context);
10465
+ const current = path70.normalize(path70.resolve(context.filename));
10466
+ const relative = path70.relative(sourceRoot, current);
10467
+ const styles = path70.join(sourceRoot, STYLES_DIR);
10468
+ if (!relative.startsWith(`${STYLES_DIR}${path70.sep}`)) return {};
10469
+ const basename = path70.basename(current);
10470
+ const featureName = basename.slice(0, -".css".length);
10471
+ const isFeatureFile = path70.dirname(current) === styles && FEATURE_CSS.test(basename) && existsSync9(path70.join(sourceRoot, "features", featureName));
10472
+ if (basename !== GLOBALS && !isFeatureFile) return {};
10473
+ const read = cachedTextReader();
10474
+ const css2 = findFiles(sourceRoot, CSS_EXTENSIONS).map((file) => ({ path: file, content: read(file) }));
10475
+ const modules = findFiles(sourceRoot, MODULE_EXTENSIONS4).map((file) => ({ path: file, content: read(file) }));
10476
+ const graph = buildCssDependencyGraph({ sourceRoot, css: css2, modules });
10477
+ const owners = inferCssOwners({ graph, css: css2, modules, sourceRoot });
10478
+ return {
10479
+ "StyleSheet:exit"(node) {
10480
+ for (const definition of graph.definitions) {
10481
+ if (definition.file !== current) continue;
10482
+ const owner = owners.get(symbolId(definition));
10483
+ if (!owner) continue;
10484
+ const expected = owner === GLOBALS ? GLOBALS : `${owner}.css`;
10485
+ if (basename === GLOBALS && expected !== GLOBALS && !existsSync9(path70.join(styles, expected))) continue;
10486
+ if (basename === expected) continue;
10487
+ const kind = LABELS[definition.kind] ?? definition.kind;
10488
+ context.report({
10489
+ node,
10490
+ loc: {
10491
+ start: { line: definition.line, column: 1 },
10492
+ end: { line: definition.line, column: 2 }
10493
+ },
10494
+ message: `CSS ${kind} "${definition.name}" is consumed ${owner === GLOBALS ? "across feature scopes" : `only by feature "${owner}"`}; define it in ${expected}.`
10495
+ });
10496
+ }
10497
+ }
10498
+ };
10499
+ }
10500
+ };
10501
+
10077
10502
  // eslint/rules/tailwind/global-selector-styling.ts
10078
10503
  function hasStyling(node) {
10079
10504
  for (const child of blockChildren2(node)) {
@@ -10107,74 +10532,215 @@ var globalSelectorStylingRule = {
10107
10532
  };
10108
10533
 
10109
10534
  // eslint/rules/tailwind/global-stylesheet.ts
10535
+ import { existsSync as existsSync10 } from "fs";
10536
+ import path71 from "path";
10537
+ var MISSING_REGISTRATION = 'Global stylesheet must register Tailwind with @import "tailwindcss".';
10538
+ var MISSING_ENTRYPOINT = "Project CSS must have a canonical global stylesheet at src/app/styles/globals.css.";
10110
10539
  var globalStylesheetRule = {
10111
10540
  meta: {
10112
10541
  schema: [],
10113
10542
  type: "problem",
10543
+ fixable: "code",
10114
10544
  docs: {
10115
- description: "Require a global stylesheet entry point that registers Tailwind."
10545
+ description: "Require the canonical global CSS entry point to register Tailwind, not every imported partial."
10116
10546
  }
10117
10547
  },
10118
10548
  create(context) {
10549
+ const sourceRoot = sourceRootOf(context);
10550
+ const entryPoint = path71.normalize(path71.join(sourceRoot, "app", "styles", "globals.css"));
10551
+ const current = path71.normalize(path71.resolve(context.filename));
10552
+ const isEntryPoint = current === entryPoint;
10553
+ const hasEntryPoint = existsSync10(entryPoint);
10554
+ const isFirstStylesheet = !hasEntryPoint && [current, ...findFiles(sourceRoot, CSS_EXTENSIONS).map((file) => path71.normalize(file))].sort()[0] === current;
10555
+ if (!isEntryPoint && !isFirstStylesheet) return {};
10119
10556
  return {
10120
10557
  "StyleSheet:exit"(node) {
10558
+ if (!isEntryPoint) {
10559
+ context.report({ node, message: MISSING_ENTRYPOINT });
10560
+ return;
10561
+ }
10121
10562
  const registersTailwind2 = node.children.some(
10122
10563
  (child) => child.type === "Atrule" && child.name === "import" && JSON.stringify(child.prelude).includes("tailwindcss")
10123
10564
  );
10124
10565
  if (!registersTailwind2) {
10125
- const hasProjectCss = node.children.some((child) => {
10126
- if (child.type === "Atrule" && child.name === "import") return false;
10127
- if (child.type === "Comment") return false;
10128
- return true;
10566
+ context.report({
10567
+ node,
10568
+ message: MISSING_REGISTRATION,
10569
+ fix(fixer) {
10570
+ return fixer.insertTextBeforeRange([0, 0], '@import "tailwindcss";\n');
10571
+ }
10129
10572
  });
10130
- if (hasProjectCss) {
10131
- context.report({
10132
- node,
10133
- message: 'Global stylesheet must register Tailwind with @import "tailwindcss".'
10134
- });
10135
- }
10136
10573
  }
10137
10574
  }
10138
10575
  };
10139
10576
  }
10140
10577
  };
10141
10578
 
10142
- // eslint/rules/tailwind/root-variables.ts
10143
- var rootVariablesRule = {
10579
+ // eslint/rules/tailwind/no-utility-alias.ts
10580
+ function plainUtilityAlias(utility, textOf) {
10581
+ const children = blockChildren2(utility);
10582
+ if (children.length !== 1) return void 0;
10583
+ const apply = children[0];
10584
+ if (apply?.type !== "Atrule" || apply.name !== "apply") return void 0;
10585
+ const source = textOf(apply).trim();
10586
+ return /^@apply\s+(?<name>-?[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:\/[a-z0-9.%-]+)?)\s*;?$/u.exec(source)?.groups?.name;
10587
+ }
10588
+ var noUtilityAliasRule = {
10144
10589
  meta: {
10145
10590
  schema: [],
10146
10591
  type: "problem",
10147
10592
  docs: {
10148
- description: "Require styling values to be CSS variables in :root referenced through @theme inline."
10593
+ description: "Disallow custom @utility definitions that only rename one plain Tailwind class."
10149
10594
  }
10150
10595
  },
10151
10596
  create(context) {
10152
10597
  return {
10153
10598
  "StyleSheet:exit"(node) {
10154
- const hasRootVars = rules(node).some((rule) => {
10155
- if (!selectorNames(rule).includes("root")) return false;
10156
- return blockChildren2(rule).some((child) => child.type === "Declaration");
10157
- });
10158
- if (!hasRootVars) {
10599
+ for (const utility of atrulesNamed(node, "utility")) {
10600
+ const alias = plainUtilityAlias(utility, (child) => context.sourceCode.getText(child));
10601
+ if (!alias) continue;
10602
+ const name = preludeIdentifiers(utility)[0] ?? "unknown";
10159
10603
  context.report({
10160
- node,
10161
- message: "Styling values must be defined as CSS variables in a :root block."
10604
+ node: utility,
10605
+ message: `Custom utility "${name}" only renames "${alias}"; use the class directly instead of defining an @utility alias.`
10162
10606
  });
10163
10607
  }
10164
- const referencingThemes = atrulesNamed(node, "theme").filter(
10165
- (theme) => blockChildren2(theme).some((child) => {
10166
- if (child.type === "Raw") return child.value.includes("var(");
10167
- if (child.type === "Declaration") return JSON.stringify(child.value).includes("var(");
10168
- return false;
10169
- })
10170
- );
10171
- const inlineThemes = atrulesNamed(node, "theme").filter(
10172
- (theme) => preludeIdentifiers(theme).includes("inline")
10173
- );
10174
- if (referencingThemes.length > 0 && inlineThemes.length === 0) {
10608
+ }
10609
+ };
10610
+ }
10611
+ };
10612
+
10613
+ // eslint/rules/tailwind/root-variables.ts
10614
+ import path72 from "path";
10615
+ var INLINE_MESSAGE = "Define design tokens with direct values in @theme, not @theme inline.";
10616
+ var INDIRECT_MESSAGE = "Define design tokens that reference CSS variables via var() in @theme inline.";
10617
+ var rootVariablesRule = {
10618
+ meta: {
10619
+ schema: [],
10620
+ type: "problem",
10621
+ docs: {
10622
+ description: "Require @theme for fixed tokens, matching :root variables for @theme inline tokens, and no other token redeclarations."
10623
+ }
10624
+ },
10625
+ create(context) {
10626
+ const sourceRoot = sourceRootOf(context);
10627
+ const current = path72.normalize(path72.resolve(context.filename));
10628
+ const textOf = cachedTextReader();
10629
+ const stylesheets = findFiles(sourceRoot, CSS_EXTENSIONS);
10630
+ const graph = buildCssDependencyGraph({
10631
+ sourceRoot,
10632
+ css: [
10633
+ ...stylesheets.filter((file) => path72.normalize(file) !== current).map((file) => ({ path: file, content: textOf(file) })),
10634
+ { path: current, content: context.sourceCode.text }
10635
+ ],
10636
+ modules: [],
10637
+ includeReferences: false
10638
+ });
10639
+ const names = new Set(
10640
+ graph.definitions.filter((symbol) => symbol.kind === "theme-token").map((symbol) => symbol.name)
10641
+ );
10642
+ const mirroredTokens = /* @__PURE__ */ new Set();
10643
+ for (const file of [...stylesheets.filter((candidate) => path72.normalize(candidate) !== current), current]) {
10644
+ const source = (path72.normalize(file) === current ? context.sourceCode.text : textOf(file)).replace(
10645
+ /\/\*[\s\S]*?\*\//gu,
10646
+ " "
10647
+ );
10648
+ for (const theme of source.matchAll(/@theme(?<options>(?:\s+[\w-]+)*)\s*\{(?<body>[^{}]*)\}/gu)) {
10649
+ const inline = /\binline\b/u.test(theme.groups?.options ?? "");
10650
+ for (const declaration of (theme.groups?.body ?? "").matchAll(
10651
+ /(?:^|;)\s*(?<name>--[a-z][\w-]*)\s*:\s*(?<value>[^;]*)/gu
10652
+ )) {
10653
+ const { name, value } = declaration.groups ?? {};
10654
+ if (!name) continue;
10655
+ names.add(name);
10656
+ if (inline && /\bvar\s*\(/u.test(value ?? "")) {
10657
+ mirroredTokens.add(name);
10658
+ }
10659
+ }
10660
+ }
10661
+ }
10662
+ const defaultRootNames = new Set(
10663
+ graph.definitions.filter((symbol) => symbol.kind === "root-variable" && symbol.scope === "default").map((symbol) => symbol.name)
10664
+ );
10665
+ return {
10666
+ "StyleSheet:exit"(node) {
10667
+ for (const theme of atrulesNamed(node, "theme")) {
10668
+ const inline = preludeIdentifiers(theme).includes("inline");
10669
+ const source = context.sourceCode.getText(theme).replace(/\/\*[\s\S]*?\*\//gu, (comment) => comment.replace(/[^\r\n]/gu, " "));
10670
+ for (const match of source.matchAll(
10671
+ /(?:^|[{;\r\n])\s*(?<name>--[a-z][\w-]*)\s*:\s*(?<value>[^;{}]*)(?=;|[}])/gu
10672
+ )) {
10673
+ const { name, value } = match.groups ?? {};
10674
+ if (!name || value === void 0) continue;
10675
+ const unquoted = value.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/gu, "");
10676
+ const referencesVariable = /\bvar\s*\(/iu.test(unquoted);
10677
+ if (inline && referencesVariable) {
10678
+ for (const reference of unquoted.matchAll(/\bvar\(\s*(?<variable>--[\w-]+)/gu)) {
10679
+ const runtime = reference.groups?.variable;
10680
+ if (runtime && runtime !== name) {
10681
+ context.report({
10682
+ node: theme,
10683
+ message: `Tailwind token "${name}" must reference its same-named CSS variable, not "${runtime}".`
10684
+ });
10685
+ } else if (runtime && !defaultRootNames.has(runtime)) {
10686
+ context.report({
10687
+ node: theme,
10688
+ message: `Tailwind token "${name}" references "${runtime}" without a default :root declaration.`
10689
+ });
10690
+ }
10691
+ }
10692
+ }
10693
+ if (inline === referencesVariable) continue;
10694
+ const offset = (theme.loc?.start.offset ?? 0) + match.index + match[0].indexOf(name);
10695
+ const preceding = context.sourceCode.text.slice(0, offset);
10696
+ const line = preceding.split("\n").length;
10697
+ const column = offset - preceding.lastIndexOf("\n");
10698
+ context.report({
10699
+ node: theme,
10700
+ loc: {
10701
+ start: { line, column },
10702
+ end: { line, column: column + name.length }
10703
+ },
10704
+ message: inline ? INLINE_MESSAGE : INDIRECT_MESSAGE
10705
+ });
10706
+ }
10707
+ }
10708
+ function checkTokenRedeclarations(currentNode, conditional = false) {
10709
+ if (currentNode.type === "Atrule" && ["theme", "utility", "keyframes"].includes(currentNode.name)) {
10710
+ return;
10711
+ }
10712
+ const isConditional = conditional || currentNode.type === "Atrule" && currentNode.name !== "layer";
10713
+ if (currentNode.type === "Rule") {
10714
+ const selector = context.sourceCode.getText(currentNode.prelude).trim();
10715
+ const isDefaultRoot = selector === ":root" && !isConditional;
10716
+ if (!isDefaultRoot) {
10717
+ for (const declaration of blockChildren2(currentNode)) {
10718
+ if (declaration.type !== "Declaration") continue;
10719
+ if (mirroredTokens.has(declaration.property)) continue;
10720
+ if (!names.has(declaration.property) && !declaration.property.startsWith("--color-")) continue;
10721
+ context.report({
10722
+ node: declaration,
10723
+ message: `Tailwind token "${declaration.property}" must not be redeclared outside @theme; map it through @theme inline to its same-named runtime variable instead.`
10724
+ });
10725
+ }
10726
+ }
10727
+ }
10728
+ const children = currentNode.type === "StyleSheet" ? currentNode.children : blockChildren2(currentNode);
10729
+ for (const child of children) {
10730
+ if (child.type === "Atrule" || child.type === "Rule") checkTokenRedeclarations(child, isConditional);
10731
+ }
10732
+ }
10733
+ checkTokenRedeclarations(node);
10734
+ for (const symbol of graph.definitions) {
10735
+ if (symbol.kind !== "root-variable" || symbol.scope !== "default") continue;
10736
+ if (symbol.file !== current || !names.has(symbol.name) || mirroredTokens.has(symbol.name)) continue;
10175
10737
  context.report({
10176
10738
  node,
10177
- message: "Tailwind theme variables that reference CSS variables must use @theme inline."
10739
+ loc: {
10740
+ start: { line: symbol.line, column: 1 },
10741
+ end: { line: symbol.line, column: 2 }
10742
+ },
10743
+ message: `Token "${symbol.name}" is already defined in @theme; remove its duplicate default :root declaration.`
10178
10744
  });
10179
10745
  }
10180
10746
  }
@@ -10234,83 +10800,161 @@ var stylesheetOrderingRule = {
10234
10800
  }
10235
10801
  lastSeen = at;
10236
10802
  }
10803
+ let furthestSection = -1;
10804
+ let furthestName;
10805
+ for (const section of sections) {
10806
+ const order = SECTION_ORDER.findIndex((name) => name === section.name);
10807
+ if (order < furthestSection) {
10808
+ context.report({
10809
+ node,
10810
+ message: `Global stylesheet section "${section.name}" cannot reappear after "${String(furthestName)}". Keep all section occurrences in the required order.`
10811
+ });
10812
+ return;
10813
+ }
10814
+ if (order > furthestSection) {
10815
+ furthestSection = order;
10816
+ furthestName = section.name;
10817
+ }
10818
+ }
10237
10819
  }
10238
10820
  };
10239
10821
  }
10240
10822
  };
10241
10823
 
10242
10824
  // eslint/rules/tailwind/theme-reset.ts
10825
+ import { existsSync as existsSync11 } from "fs";
10826
+ import path73 from "path";
10827
+ import { generate as generate3, parse as parse6, toPlainObject as toPlainObject2 } from "@eslint/css-tree";
10243
10828
  function isThemeReset(node) {
10244
10829
  if (!node) return false;
10245
- if (node.type === "Declaration") return node.property.startsWith("--*");
10246
- if (node.type === "Raw") return node.value.includes("--*: initial");
10830
+ if (node.type === "Declaration") return node.property === "--*" && generate3(node.value).trim() === "initial";
10831
+ if (node.type === "Raw") return /--\*\s*:\s*initial\s*;/u.test(node.value);
10247
10832
  return false;
10248
10833
  }
10834
+ function hasThemeReset(node) {
10835
+ return atrulesNamed(node, "theme").some((theme) => {
10836
+ if (blockChildren2(theme).some(isThemeReset)) return true;
10837
+ let nested = false;
10838
+ walkNodes(theme, (child) => {
10839
+ if (isThemeReset(child)) nested = true;
10840
+ });
10841
+ return nested;
10842
+ });
10843
+ }
10249
10844
  var themeResetRule = {
10250
10845
  meta: {
10251
10846
  schema: [],
10252
10847
  type: "problem",
10253
10848
  docs: {
10254
- description: "Require Tailwind's default theme reset --*: initial."
10849
+ description: "Require the CSS tree to reset Tailwind's default theme with --*: initial."
10255
10850
  }
10256
10851
  },
10257
10852
  create(context) {
10853
+ const sourceRoot = sourceRootOf(context);
10854
+ const current = path73.normalize(path73.resolve(context.filename));
10855
+ const entryPoint = path73.normalize(path73.join(sourceRoot, "app", "styles", "globals.css"));
10856
+ if (current !== entryPoint) return {};
10258
10857
  return {
10259
10858
  "StyleSheet:exit"(node) {
10260
- const resetFound = atrulesNamed(node, "theme").some((theme) => {
10261
- const direct = blockChildren2(theme).some(isThemeReset);
10262
- if (direct) return true;
10263
- let nested = false;
10264
- walkNodes(theme, (current) => {
10265
- if (nested) return;
10266
- if (isThemeReset(current)) nested = true;
10267
- });
10268
- return nested;
10269
- });
10270
- if (!resetFound) {
10271
- context.report({
10272
- node,
10273
- message: "The global stylesheet must reset Tailwind's default theme with --*: initial inside an @theme block."
10274
- });
10859
+ if (hasThemeReset(node)) return;
10860
+ const textOf = cachedTextReader();
10861
+ const seen = /* @__PURE__ */ new Set([current]);
10862
+ const queue = [{ file: current, source: context.sourceCode.text }];
10863
+ while (queue.length > 0) {
10864
+ const from = queue.shift();
10865
+ if (!from) continue;
10866
+ for (const specifier of importedSpecifiers(from.source)) {
10867
+ const target = resolveSpecifier2(from.file, specifier, sourceRoot);
10868
+ if (!target || path73.extname(target) !== ".css") continue;
10869
+ const filename = path73.normalize(target);
10870
+ if (seen.has(filename) || !existsSync11(filename)) continue;
10871
+ seen.add(filename);
10872
+ const source = textOf(filename);
10873
+ const parsed = toPlainObject2(parse6(source, { parseAtrulePrelude: false }));
10874
+ if (parsed.type === "StyleSheet" && hasThemeReset(parsed)) return;
10875
+ queue.push({ file: filename, source });
10876
+ }
10275
10877
  }
10878
+ context.report({
10879
+ node,
10880
+ message: "The global stylesheet must reset Tailwind's default theme with --*: initial inside an @theme block."
10881
+ });
10276
10882
  }
10277
10883
  };
10278
10884
  }
10279
10885
  };
10280
10886
 
10281
- // eslint/rules/tailwind/theme-variable-namespace.ts
10282
- var themeVariableNamespaceRule = {
10887
+ // eslint/rules/tailwind/unique-css-definition.ts
10888
+ import path74 from "path";
10889
+ function ownershipKey(symbol) {
10890
+ if (symbol.kind === "selector") return void 0;
10891
+ if (symbol.kind === "root-variable" && symbol.scope !== "default") return void 0;
10892
+ return [symbol.kind, symbol.identity ?? symbol.name].join("|");
10893
+ }
10894
+ function symbolLabel(symbol) {
10895
+ switch (symbol.kind) {
10896
+ case "utility":
10897
+ return "Custom utility";
10898
+ case "theme-token":
10899
+ return "Theme token";
10900
+ case "root-variable":
10901
+ return "Default :root variable";
10902
+ case "keyframes":
10903
+ return "@keyframes";
10904
+ case "font-face":
10905
+ return "@font-face with matching descriptors for";
10906
+ case "selector":
10907
+ return "CSS selector";
10908
+ }
10909
+ }
10910
+ var uniqueCssDefinitionRule = {
10283
10911
  meta: {
10284
10912
  schema: [],
10285
10913
  type: "problem",
10286
10914
  docs: {
10287
- description: "Require utility classes from the same theme namespace to share a prefix."
10915
+ description: "Reject duplicate project-level CSS definitions across stylesheets while preserving scoped overrides."
10288
10916
  }
10289
10917
  },
10290
10918
  create(context) {
10919
+ const sourceRoot = sourceRootOf(context);
10920
+ const cssFiles = findFiles(sourceRoot, CSS_EXTENSIONS);
10921
+ if (cssFiles.length === 0) return {};
10922
+ const textOf = cachedTextReader();
10923
+ const graph = buildCssDependencyGraph({
10924
+ sourceRoot,
10925
+ css: cssFiles.map((file) => ({ path: file, content: textOf(file) })),
10926
+ modules: [],
10927
+ includeReferences: false
10928
+ });
10929
+ const owners = /* @__PURE__ */ new Map();
10930
+ for (const symbol of graph.definitions) {
10931
+ const key = ownershipKey(symbol);
10932
+ if (!key) continue;
10933
+ const files = owners.get(key) ?? /* @__PURE__ */ new Set();
10934
+ files.add(symbol.file);
10935
+ owners.set(key, files);
10936
+ }
10291
10937
  return {
10292
10938
  "StyleSheet:exit"(node) {
10293
- const namespaces = /* @__PURE__ */ new Set();
10294
- for (const theme of atrulesNamed(node, "theme")) {
10295
- for (const child of blockChildren2(theme)) {
10296
- if (child.type !== "Declaration") continue;
10297
- const segments = child.property.replace(/^--/, "").split("-");
10298
- if (segments[0]) namespaces.add(segments[0]);
10299
- }
10300
- }
10301
- for (const apply of atrulesNamed(node, "apply")) {
10302
- const classes = preludeIdentifiers(apply);
10303
- for (const ns of namespaces) {
10304
- const matching = classes.filter((name) => name.startsWith(`${ns}-`) || name === ns);
10305
- if (matching.length < 2) continue;
10306
- const prefixes = matching.map((name) => name.split("-").slice(0, 2).join("-"));
10307
- if (new Set(prefixes).size > 1) {
10308
- context.report({
10309
- node: apply,
10310
- message: `Utility classes ${matching.join(", ")} use namespace "${ns}" but lack a shared prefix; use the theme-variable namespace prefix.`
10311
- });
10312
- }
10313
- }
10939
+ const current = path74.normalize(path74.resolve(context.filename));
10940
+ const checked = /* @__PURE__ */ new Set();
10941
+ for (const symbol of graph.definitions) {
10942
+ if (symbol.file !== current) continue;
10943
+ const key = ownershipKey(symbol);
10944
+ if (!key || checked.has(key)) continue;
10945
+ checked.add(key);
10946
+ const files = owners.get(key);
10947
+ if (!files || files.size < 2) continue;
10948
+ const canonical = [...files].sort()[0];
10949
+ if (!canonical || canonical === current) continue;
10950
+ context.report({
10951
+ node,
10952
+ loc: {
10953
+ start: { line: symbol.line, column: 1 },
10954
+ end: { line: symbol.line, column: 2 }
10955
+ },
10956
+ message: `${symbolLabel(symbol)} "${symbol.name}" is also defined in ${path74.relative(sourceRoot, canonical)}; define it in only one CSS file.`
10957
+ });
10314
10958
  }
10315
10959
  }
10316
10960
  };
@@ -10319,9 +10963,7 @@ var themeVariableNamespaceRule = {
10319
10963
 
10320
10964
  // eslint/rules/tailwind/unused-utility.ts
10321
10965
  import { statSync as statSync5 } from "fs";
10322
- function usagePattern(name) {
10323
- return new RegExp(`(?<![\\w-])${escapeRegExp(name)}(?![\\w-])`);
10324
- }
10966
+ import path75 from "path";
10325
10967
  var unusedUtilityRule = {
10326
10968
  meta: {
10327
10969
  schema: [],
@@ -10332,23 +10974,29 @@ var unusedUtilityRule = {
10332
10974
  },
10333
10975
  create(context) {
10334
10976
  const sourceRoot = sourceRootOf(context);
10335
- let files;
10336
10977
  try {
10337
10978
  if (!statSync5(sourceRoot).isDirectory()) return {};
10338
- files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
10339
10979
  } catch {
10340
10980
  return {};
10341
10981
  }
10982
+ const current = path75.normalize(path75.resolve(context.filename));
10342
10983
  const textOf = cachedTextReader();
10984
+ const cssFiles = findFiles(sourceRoot, CSS_EXTENSIONS);
10985
+ const moduleFiles = findFiles(sourceRoot, MODULE_EXTENSIONS4);
10986
+ const graph = buildCssDependencyGraph({
10987
+ sourceRoot,
10988
+ css: cssFiles.map((file) => ({
10989
+ path: file,
10990
+ content: path75.normalize(file) === current ? context.sourceCode.text : textOf(file)
10991
+ })),
10992
+ modules: moduleFiles.map((file) => ({ path: file, content: textOf(file) }))
10993
+ });
10343
10994
  return {
10344
10995
  "StyleSheet:exit"(node) {
10345
10996
  for (const utility of atrulesNamed(node, "utility")) {
10346
10997
  const name = preludeIdentifiers(utility)[0];
10347
10998
  if (!name) continue;
10348
- const pattern = usagePattern(name);
10349
- const definition = `@utility ${name}`;
10350
- const used = files.some((file) => pattern.test(textOf(file).replaceAll(definition, "")));
10351
- if (used) continue;
10999
+ if (cssSymbolUsages(graph, "utility", name).length > 0) continue;
10352
11000
  context.report({
10353
11001
  node: utility,
10354
11002
  message: `Custom utility "${name}" is never used by the repository's source; remove it or use it.`
@@ -10547,22 +11195,24 @@ var utilityMappingRule = {
10547
11195
  // eslint/rules/tailwind/index.ts
10548
11196
  var tailwindRules = {
10549
11197
  "theme-reset": themeResetRule,
11198
+ "color-role-usage": colorRoleUsageRule,
10550
11199
  "root-variables": rootVariablesRule,
10551
11200
  "apply-usage": applyUsageRule,
10552
11201
  "base-layer-pair": baseLayerPairRule,
10553
11202
  "stylesheet-ordering": stylesheetOrderingRule,
10554
- "css-variable-naming": cssVariableNamingRule,
10555
11203
  "custom-utility-apply": customUtilityApplyRule,
10556
11204
  "utility-mapping": utilityMappingRule,
10557
- "theme-variable-namespace": themeVariableNamespaceRule,
10558
11205
  "css-entry-point": cssEntryPointRule,
11206
+ "feature-css-placement": featureCssPlacementRule,
10559
11207
  "global-stylesheet": globalStylesheetRule,
10560
11208
  "unused-utility": unusedUtilityRule,
11209
+ "unique-css-definition": uniqueCssDefinitionRule,
11210
+ "no-utility-alias": noUtilityAliasRule,
10561
11211
  "global-selector-styling": globalSelectorStylingRule
10562
11212
  };
10563
11213
 
10564
11214
  // eslint/rules/type-extraction.ts
10565
- import path67 from "path";
11215
+ import path76 from "path";
10566
11216
  var typeExtractionRule = {
10567
11217
  meta: {
10568
11218
  schema: [],
@@ -10573,7 +11223,7 @@ var typeExtractionRule = {
10573
11223
  },
10574
11224
  create(context) {
10575
11225
  const sourceRoot = sourceRootOf(context);
10576
- const file = path67.resolve(context.filename);
11226
+ const file = path76.resolve(context.filename);
10577
11227
  const segments = segmentsOf(file, sourceRoot);
10578
11228
  if (segments.length === 0) return {};
10579
11229
  const index = getProjectIndex(sourceRoot);
@@ -10712,9 +11362,9 @@ var uiStateRule = {
10712
11362
  };
10713
11363
 
10714
11364
  // eslint/rules/unknown-utility.ts
10715
- import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
11365
+ import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
10716
11366
  import { createRequire } from "module";
10717
- import path68 from "path";
11367
+ import path77 from "path";
10718
11368
  var PREFIX_NAMESPACES = {
10719
11369
  bg: ["color"],
10720
11370
  text: ["color", "text"],
@@ -10954,7 +11604,7 @@ function stylesheetFiles(dir) {
10954
11604
  }
10955
11605
  return entries.flatMap((entry) => {
10956
11606
  if (entry.startsWith(".") || entry === "node_modules") return [];
10957
- const entryPath = path68.join(dir, entry);
11607
+ const entryPath = path77.join(dir, entry);
10958
11608
  let stats;
10959
11609
  try {
10960
11610
  stats = statSync6(entryPath);
@@ -10962,7 +11612,7 @@ function stylesheetFiles(dir) {
10962
11612
  return [];
10963
11613
  }
10964
11614
  if (stats.isDirectory()) return stylesheetFiles(entryPath);
10965
- return path68.extname(entry) === ".css" ? [entryPath] : [];
11615
+ return path77.extname(entry) === ".css" ? [entryPath] : [];
10966
11616
  });
10967
11617
  }
10968
11618
  function packageNameOf(specifier) {
@@ -10973,24 +11623,24 @@ function packageNameOf(specifier) {
10973
11623
  function resolvePackageStylesheet(fromFile, specifier) {
10974
11624
  const packageName = packageNameOf(specifier);
10975
11625
  if (!packageName || packageName === "tailwindcss") return void 0;
10976
- let directory = path68.dirname(fromFile);
10977
- while (directory !== path68.dirname(directory)) {
10978
- const packageRoot = path68.join(directory, "node_modules", packageName);
10979
- const manifestPath = path68.join(packageRoot, "package.json");
10980
- if (existsSync8(manifestPath)) {
11626
+ let directory = path77.dirname(fromFile);
11627
+ while (directory !== path77.dirname(directory)) {
11628
+ const packageRoot = path77.join(directory, "node_modules", packageName);
11629
+ const manifestPath = path77.join(packageRoot, "package.json");
11630
+ if (existsSync12(manifestPath)) {
10981
11631
  try {
10982
11632
  const manifest = readFileSync15(manifestPath, "utf8");
10983
11633
  const stylesheet = /"style"\s*:\s*"(?<file>[^"]+\.css)"/.exec(manifest)?.groups?.file;
10984
11634
  const main = /"main"\s*:\s*"(?<file>[^"]+\.css)"/.exec(manifest)?.groups?.file;
10985
11635
  const target = stylesheet ?? main;
10986
11636
  if (!target) return void 0;
10987
- const resolved = path68.resolve(packageRoot, target);
10988
- return existsSync8(resolved) ? resolved : void 0;
11637
+ const resolved = path77.resolve(packageRoot, target);
11638
+ return existsSync12(resolved) ? resolved : void 0;
10989
11639
  } catch {
10990
11640
  return void 0;
10991
11641
  }
10992
11642
  }
10993
- directory = path68.dirname(directory);
11643
+ directory = path77.dirname(directory);
10994
11644
  }
10995
11645
  return void 0;
10996
11646
  }
@@ -11111,24 +11761,6 @@ function isColorToken(token, inventory) {
11111
11761
  if (hyphen === -1) return false;
11112
11762
  return DEFAULT_PALETTE_FAMILIES.has(token.slice(0, hyphen)) && DEFAULT_PALETTE_SHADES.has(token.slice(hyphen + 1));
11113
11763
  }
11114
- function definedThemeColorClass(className, inventory) {
11115
- if (className.includes("[") || className.includes("(")) return void 0;
11116
- const utility = (className.replace(/!+$/, "").split(":").pop() ?? "").split("/")[0] ?? "";
11117
- const compound = Object.keys(PREFIX_NAMESPACES).find(
11118
- (prefix2) => prefix2.includes("-") && utility.startsWith(`${prefix2}-`)
11119
- );
11120
- const dash = utility.indexOf("-");
11121
- if (dash === -1) return void 0;
11122
- const prefix = compound ?? utility.slice(0, dash);
11123
- const token = utility.slice(prefix.length + 1);
11124
- if (!(PREFIX_NAMESPACES[prefix]?.includes("color") ?? false)) return void 0;
11125
- if (!(inventory.themeTokens.get("color")?.has(token) ?? false)) return void 0;
11126
- return { prefix, token };
11127
- }
11128
- function isInvalidBoundaryColorClass(className, inventory) {
11129
- const themeColor = definedThemeColorClass(className, inventory);
11130
- return themeColor !== void 0 && violatesBoundaryColorRole(themeColor.token);
11131
- }
11132
11764
  function isKnown(className, inventory) {
11133
11765
  if (className.includes("[") || className.includes("(")) return true;
11134
11766
  const segments = className.replace(/!+$/, "").split(":");
@@ -11189,28 +11821,11 @@ var unknownUtilityRule = {
11189
11821
  for (const candidate of value.split(/\s+/)) {
11190
11822
  if (!candidate || seen.has(candidate)) continue;
11191
11823
  seen.add(candidate);
11192
- if (isInvalidBoundaryColorClass(candidate, inventory)) {
11193
- context.report({
11194
- node,
11195
- message: `Utility class "${candidate}" uses a boundary color token named after a CSS property; use a semantic -edge color token instead (for example, border-control-edge).`
11196
- });
11824
+ if (isKnown(candidate, inventory)) {
11825
+ const roleError = colorRoleUsageMessage(candidate);
11826
+ if (roleError) context.report({ node, message: roleError });
11197
11827
  continue;
11198
11828
  }
11199
- const themeColor = definedThemeColorClass(candidate, inventory);
11200
- const incompatibleRole = themeColor ? incompatibleColorUtilityRole(themeColor.prefix, themeColor.token) : void 0;
11201
- if (incompatibleRole) {
11202
- const expected = {
11203
- canvas: "bg-<role>-canvas",
11204
- ink: "text-<role>-ink",
11205
- edge: "border-<role>-edge"
11206
- }[incompatibleRole];
11207
- context.report({
11208
- node,
11209
- message: `Utility class "${candidate}" applies a -${incompatibleRole} color token to an incompatible property; use ${expected} for that role or define a token for the intended property.`
11210
- });
11211
- continue;
11212
- }
11213
- if (isKnown(candidate, inventory)) continue;
11214
11829
  const plainBase = candidate.replace(/!+$/, "").split(":").pop() ?? "";
11215
11830
  if (inventory.plainClasses.has(plainBase)) {
11216
11831
  context.report({
@@ -11280,7 +11895,7 @@ var unknownUtilityRule = {
11280
11895
  };
11281
11896
 
11282
11897
  // eslint/rules/util-file-name.ts
11283
- import path69 from "path";
11898
+ import path78 from "path";
11284
11899
  function toKebabCase2(value) {
11285
11900
  return value.replace(/(?<lower>[a-z0-9])(?<upper>[A-Z])/g, "$<lower>-$<upper>").replace(/(?<first>[A-Z])(?<rest>[A-Z][a-z])/g, "$<first>-$<rest>").toLowerCase();
11286
11901
  }
@@ -11293,7 +11908,7 @@ var utilFileNameRule = {
11293
11908
  }
11294
11909
  },
11295
11910
  create(context) {
11296
- const filename = path69.resolve(context.filename);
11911
+ const filename = path78.resolve(context.filename);
11297
11912
  const segments = filename.replace(/\\/g, "/").split("/");
11298
11913
  if (!segments.includes("utils")) return {};
11299
11914
  let module;
@@ -11307,13 +11922,13 @@ var utilFileNameRule = {
11307
11922
  const functionName = functions[0]?.name;
11308
11923
  if (!functionName) return {};
11309
11924
  const expected = toKebabCase2(functionName);
11310
- const actual = path69.basename(filename, path69.extname(filename));
11925
+ const actual = path78.basename(filename, path78.extname(filename));
11311
11926
  if (!expected || actual === expected) return {};
11312
11927
  return {
11313
11928
  Program(node) {
11314
11929
  context.report({
11315
11930
  node,
11316
- message: `A utility file exporting ${functionName} must be named ${expected}.${path69.extname(filename).slice(1)}.`
11931
+ message: `A utility file exporting ${functionName} must be named ${expected}.${path78.extname(filename).slice(1)}.`
11317
11932
  });
11318
11933
  }
11319
11934
  };
@@ -11321,7 +11936,7 @@ var utilFileNameRule = {
11321
11936
  };
11322
11937
 
11323
11938
  // eslint/rules/value-extraction.ts
11324
- import path70 from "path";
11939
+ import path79 from "path";
11325
11940
  var valueExtractionRule = {
11326
11941
  meta: {
11327
11942
  schema: [],
@@ -11332,7 +11947,7 @@ var valueExtractionRule = {
11332
11947
  },
11333
11948
  create(context) {
11334
11949
  const sourceRoot = sourceRootOf(context);
11335
- const file = path70.resolve(context.filename);
11950
+ const file = path79.resolve(context.filename);
11336
11951
  const segments = segmentsOf(file, sourceRoot);
11337
11952
  if (segments.length === 0 || segments[0] !== "app") return {};
11338
11953
  const index = getProjectIndex(sourceRoot);
@@ -11353,8 +11968,8 @@ var valueExtractionRule = {
11353
11968
  };
11354
11969
 
11355
11970
  // eslint/rules/vulyk/tracked-docs.ts
11356
- import { existsSync as existsSync9, readFileSync as readFileSync16 } from "fs";
11357
- import path71 from "path";
11971
+ import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
11972
+ import path80 from "path";
11358
11973
  var PASIKA_REPO = "Bredansky/pasika";
11359
11974
  var BASE_REQUIRED_TRACKED_DOCS = [
11360
11975
  { name: "documentation-guide", path: "docs/documentation-guide" },
@@ -11387,9 +12002,9 @@ var trackedDocsRule = {
11387
12002
  if (!context.filename.endsWith("package.json")) return;
11388
12003
  const root = node.body;
11389
12004
  if (root.type !== "Object") return;
11390
- const projectRoot = path71.dirname(path71.resolve(context.filename));
11391
- const configPath = path71.join(projectRoot, "vulyk.config.ts");
11392
- if (!existsSync9(configPath)) {
12005
+ const projectRoot = path80.dirname(path80.resolve(context.filename));
12006
+ const configPath = path80.join(projectRoot, "vulyk.config.ts");
12007
+ if (!existsSync13(configPath)) {
11393
12008
  context.report({
11394
12009
  node,
11395
12010
  message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's required tracked docs."
@@ -11413,8 +12028,8 @@ var trackedDocsRule = {
11413
12028
  });
11414
12029
  }
11415
12030
  }
11416
- const agentsPath = path71.join(projectRoot, "AGENTS.md");
11417
- if (!existsSync9(agentsPath)) {
12031
+ const agentsPath = path80.join(projectRoot, "AGENTS.md");
12032
+ if (!existsSync13(agentsPath)) {
11418
12033
  context.report({
11419
12034
  node,
11420
12035
  message: "No AGENTS.md found. Run npx vulyk agents to generate the agent file that routes to the tracked docs."
@@ -11513,7 +12128,7 @@ var withResponseHelperRule = {
11513
12128
 
11514
12129
  // eslint/rules/zirka-baseline.ts
11515
12130
  import fs7 from "fs";
11516
- import path72 from "path";
12131
+ import path81 from "path";
11517
12132
  var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
11518
12133
  var PRETTIER_CONFIGS = [
11519
12134
  "prettier.config.mjs",
@@ -11532,10 +12147,10 @@ var zirkaBaselineRule = {
11532
12147
  }
11533
12148
  },
11534
12149
  create(context) {
11535
- const filename = path72.resolve(context.filename);
11536
- const basename = path72.basename(filename);
12150
+ const filename = path81.resolve(context.filename);
12151
+ const basename = path81.basename(filename);
11537
12152
  if (!ESLINT_CONFIG.test(basename)) return {};
11538
- const projectRoot = path72.dirname(filename);
12153
+ const projectRoot = path81.dirname(filename);
11539
12154
  const report3 = (message) => {
11540
12155
  context.report({
11541
12156
  node: context.sourceCode.ast,
@@ -11550,7 +12165,7 @@ var zirkaBaselineRule = {
11550
12165
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
11551
12166
  );
11552
12167
  }
11553
- const tsconfigPath = path72.join(projectRoot, "tsconfig.json");
12168
+ const tsconfigPath = path81.join(projectRoot, "tsconfig.json");
11554
12169
  if (!fs7.existsSync(tsconfigPath)) {
11555
12170
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
11556
12171
  } else {
@@ -11568,13 +12183,13 @@ var zirkaBaselineRule = {
11568
12183
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
11569
12184
  }
11570
12185
  }
11571
- const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs7.existsSync(path72.join(projectRoot, name)));
12186
+ const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs7.existsSync(path81.join(projectRoot, name)));
11572
12187
  if (!prettierConfigFile) {
11573
12188
  report3(
11574
12189
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
11575
12190
  );
11576
12191
  } else {
11577
- const content = fs7.readFileSync(path72.join(projectRoot, prettierConfigFile), "utf8");
12192
+ const content = fs7.readFileSync(path81.join(projectRoot, prettierConfigFile), "utf8");
11578
12193
  if (!content.includes("zirka")) {
11579
12194
  report3(
11580
12195
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -11908,7 +12523,11 @@ var tailwindStructureRules = {
11908
12523
  },
11909
12524
  language: "css/css",
11910
12525
  languageOptions: { tolerant: true },
11911
- rules: Object.fromEntries(tailwindRuleIds.filter((id) => id !== "pasika/css-entry-point").map((id) => [id, "error"]))
12526
+ rules: Object.fromEntries(
12527
+ tailwindRuleIds.filter(
12528
+ (id) => id !== "pasika/css-entry-point" && id !== "pasika/feature-css-placement" && id !== "pasika/unique-css-definition" && id !== "pasika/no-utility-alias" && id !== "pasika/root-variables" && id !== "pasika/color-role-usage"
12529
+ ).map((id) => [id, "error"])
12530
+ )
11912
12531
  };
11913
12532
  var tailwindImportGraph = {
11914
12533
  files: ["src/**/*.css"],
@@ -11918,7 +12537,14 @@ var tailwindImportGraph = {
11918
12537
  },
11919
12538
  language: "css/css",
11920
12539
  languageOptions: { tolerant: true },
11921
- rules: { "pasika/css-entry-point": "error" }
12540
+ rules: {
12541
+ "pasika/css-entry-point": "error",
12542
+ "pasika/feature-css-placement": "error",
12543
+ "pasika/unique-css-definition": "error",
12544
+ "pasika/no-utility-alias": "error",
12545
+ "pasika/root-variables": "error",
12546
+ "pasika/color-role-usage": "error"
12547
+ }
11922
12548
  };
11923
12549
  var pasikaAppPackageJsonConfig = {
11924
12550
  files: ["package.json"],