pasika 0.7.6 → 0.8.0

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.
@@ -199,11 +199,14 @@ declare const pasikaPlugin: {
199
199
  "enforce-barrel-exports": eslint.Rule.RuleModule;
200
200
  "config-extraction": eslint.Rule.RuleModule;
201
201
  "value-extraction": eslint.Rule.RuleModule;
202
+ "constant-casing": eslint.Rule.RuleModule;
202
203
  "type-extraction": eslint.Rule.RuleModule;
203
204
  "zod-schema-validation": eslint.Rule.RuleModule;
205
+ "schema-casing": eslint.Rule.RuleModule;
204
206
  "source-under-src": eslint.Rule.RuleModule;
205
207
  "zirka-baseline": eslint.Rule.RuleModule;
206
208
  "component-placement": eslint.Rule.RuleModule;
209
+ "component-casing": eslint.Rule.RuleModule;
207
210
  "application-structure": eslint.Rule.RuleModule;
208
211
  "data-testid-case": eslint.Rule.RuleModule;
209
212
  "jsx-hygiene": eslint.Rule.RuleModule;
@@ -137,6 +137,30 @@ function parseComponentInfo(text, filename, options) {
137
137
  }
138
138
  return components;
139
139
  }
140
+ function findJsxReturningDeclarations(text, filename) {
141
+ const sourceFile = ts.createSourceFile(path.resolve(filename), text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
142
+ const results = [];
143
+ const record = (identifier, body) => {
144
+ if (isHookCallName(identifier.text) || !containsJsx(body)) return;
145
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(identifier.getStart(sourceFile));
146
+ results.push({ name: identifier.text, line: line + 1, column: character });
147
+ };
148
+ for (const statement of sourceFile.statements) {
149
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
150
+ record(statement.name, statement);
151
+ continue;
152
+ }
153
+ if (ts.isVariableStatement(statement)) {
154
+ for (const declaration of statement.declarationList.declarations) {
155
+ if (!ts.isIdentifier(declaration.name)) continue;
156
+ const initializer = declaration.initializer;
157
+ if (!initializer || !ts.isArrowFunction(initializer) && !ts.isFunctionExpression(initializer)) continue;
158
+ record(declaration.name, initializer);
159
+ }
160
+ }
161
+ }
162
+ return results;
163
+ }
140
164
  function jsxTagName(element) {
141
165
  const tagName2 = ts.isJsxElement(element) ? element.openingElement.tagName : element.tagName;
142
166
  return ts.isIdentifier(tagName2) ? tagName2.text : void 0;
@@ -2056,7 +2080,7 @@ var supportFolderShapeRule = {
2056
2080
  schema: [],
2057
2081
  type: "problem",
2058
2082
  docs: {
2059
- description: "Require support-folder exports to be defined in index.ts or named-re-exported by it."
2083
+ description: "Require a support folder to either define its exports directly in index.ts or re-export every sibling from it, never both."
2060
2084
  }
2061
2085
  },
2062
2086
  create(context) {
@@ -2078,13 +2102,22 @@ var supportFolderShapeRule = {
2078
2102
  Program(node) {
2079
2103
  const source = context.sourceCode.text;
2080
2104
  const hasDirectExport = /export\s+(?:const|let|var|function|class|type|interface|enum)\b/.test(source);
2081
- if (hasDirectExport) return;
2082
2105
  const exportedFiles = /* @__PURE__ */ new Set();
2083
2106
  const exportPattern = /export\s+(?:\{[^}]*\}|\*[^;]*)\s+from\s+["'](?<specifier>\.[^"']+)["']/g;
2084
2107
  for (const match of source.matchAll(exportPattern)) {
2085
2108
  const specifier = match.groups?.specifier;
2086
2109
  if (specifier) exportedFiles.add(path16.basename(specifier));
2087
2110
  }
2111
+ const hasAnyReExport = exportedFiles.size > 0;
2112
+ const guide = `docs/next-codebase-guide/rules/${folder === "constants" ? "constants" : "types-and-schemas"}-rule.md`;
2113
+ if (hasDirectExport && hasAnyReExport) {
2114
+ context.report({
2115
+ node,
2116
+ message: `${folder}/index.ts mixes direct exports with re-exported support files; pick one strategy for the whole folder. See ${guide}`
2117
+ });
2118
+ return;
2119
+ }
2120
+ if (hasDirectExport) return;
2088
2121
  const missing = siblingModules.filter((entry) => {
2089
2122
  const stem = entry.replace(/\.(?:[cm]?tsx?|jsx?)$/, "");
2090
2123
  return !exportedFiles.has(stem);
@@ -2092,15 +2125,53 @@ var supportFolderShapeRule = {
2092
2125
  if (missing.length === 0) return;
2093
2126
  context.report({
2094
2127
  node,
2095
- message: `${folder}/index.ts must named-re-export every support file: ${missing.join(", ")}. See docs/next-codebase-guide/rules/${folder === "constants" ? "constants" : "types-and-schemas"}-rule.md`
2128
+ message: `${folder}/index.ts must re-export every support file: ${missing.join(", ")}. See ${guide}`
2096
2129
  });
2097
2130
  }
2098
2131
  };
2099
2132
  }
2100
2133
  };
2101
2134
 
2102
- // eslint/rules/import-through-index.ts
2135
+ // eslint/rules/constant-casing.ts
2103
2136
  import path17 from "path";
2137
+ var NEXTJS_ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2138
+ function isScreamingSnakeCase(name) {
2139
+ return /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(name);
2140
+ }
2141
+ var constantCasingRule = {
2142
+ meta: {
2143
+ schema: [],
2144
+ type: "problem",
2145
+ docs: {
2146
+ description: "Require a module-level constant to be camelCase, unless a framework requires a specific name."
2147
+ }
2148
+ },
2149
+ create(context) {
2150
+ const filename = path17.resolve(context.filename);
2151
+ const sourceRoot = sourceRootOf(context);
2152
+ if (!filename.startsWith(sourceRoot + path17.sep)) return {};
2153
+ return {
2154
+ VariableDeclarator(node) {
2155
+ if (node.id.type !== "Identifier") return;
2156
+ const declaration = node.parent;
2157
+ if (declaration.type !== "VariableDeclaration" || declaration.kind !== "const") return;
2158
+ const container = declaration.parent;
2159
+ const isModuleLevel = container.type === "Program" || container.type === "ExportNamedDeclaration" && container.parent.type === "Program";
2160
+ if (!isModuleLevel) return;
2161
+ const { name } = node.id;
2162
+ if (!isScreamingSnakeCase(name)) return;
2163
+ if (NEXTJS_ROUTE_HANDLER_NAMES.has(name)) return;
2164
+ context.report({
2165
+ node,
2166
+ message: `${name} must be camelCase, unless a framework requires this exact name. See docs/next-codebase-guide/rules/constants-rule.md`
2167
+ });
2168
+ }
2169
+ };
2170
+ }
2171
+ };
2172
+
2173
+ // eslint/rules/import-through-index.ts
2174
+ import path18 from "path";
2104
2175
  var importThroughIndexRule = {
2105
2176
  meta: {
2106
2177
  schema: [],
@@ -2110,7 +2181,7 @@ var importThroughIndexRule = {
2110
2181
  }
2111
2182
  },
2112
2183
  create(context) {
2113
- const filename = path17.resolve(context.filename);
2184
+ const filename = path18.resolve(context.filename);
2114
2185
  const sourceRoot = sourceRootOf2(context, filename);
2115
2186
  return {
2116
2187
  Program(node) {
@@ -2122,7 +2193,7 @@ var importThroughIndexRule = {
2122
2193
  (segment) => ["constants", "types", "schemas"].includes(segment)
2123
2194
  );
2124
2195
  const supportFolder = supportFolderIndex >= 0 ? targetSegments[supportFolderIndex] : void 0;
2125
- if (!supportFolder || path17.basename(target).startsWith("index.")) continue;
2196
+ if (!supportFolder || path18.basename(target).startsWith("index.")) continue;
2126
2197
  const folderIndex = targetSegments.slice(0, supportFolderIndex + 1);
2127
2198
  const expected = `@/${folderIndex.join("/")}`;
2128
2199
  context.report({
@@ -2144,14 +2215,14 @@ function importSpecifiers(source) {
2144
2215
  return specifiers;
2145
2216
  }
2146
2217
  function sourceRootOf2(context, filename) {
2147
- const marker = `${path17.sep}src${path17.sep}`;
2218
+ const marker = `${path18.sep}src${path18.sep}`;
2148
2219
  const srcIndex = filename.lastIndexOf(marker);
2149
2220
  if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
2150
- return path17.resolve(context.cwd ?? process.cwd(), "src");
2221
+ return path18.resolve(context.cwd ?? process.cwd(), "src");
2151
2222
  }
2152
2223
 
2153
2224
  // eslint/rules/util-file-name.ts
2154
- import path18 from "path";
2225
+ import path19 from "path";
2155
2226
  function toKebabCase2(value) {
2156
2227
  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();
2157
2228
  }
@@ -2164,7 +2235,7 @@ var utilFileNameRule = {
2164
2235
  }
2165
2236
  },
2166
2237
  create(context) {
2167
- const filename = path18.resolve(context.filename);
2238
+ const filename = path19.resolve(context.filename);
2168
2239
  const segments = filename.replace(/\\/g, "/").split("/");
2169
2240
  if (!segments.includes("utils")) return {};
2170
2241
  let module;
@@ -2178,13 +2249,13 @@ var utilFileNameRule = {
2178
2249
  const functionName = functions[0]?.name;
2179
2250
  if (!functionName) return {};
2180
2251
  const expected = toKebabCase2(functionName);
2181
- const actual = path18.basename(filename, path18.extname(filename));
2252
+ const actual = path19.basename(filename, path19.extname(filename));
2182
2253
  if (!expected || actual === expected) return {};
2183
2254
  return {
2184
2255
  Program(node) {
2185
2256
  context.report({
2186
2257
  node,
2187
- message: `A utility file exporting ${functionName} must be named ${expected}.${path18.extname(filename).slice(1)}.`
2258
+ message: `A utility file exporting ${functionName} must be named ${expected}.${path19.extname(filename).slice(1)}.`
2188
2259
  });
2189
2260
  }
2190
2261
  };
@@ -2192,7 +2263,7 @@ var utilFileNameRule = {
2192
2263
  };
2193
2264
 
2194
2265
  // eslint/rules/no-util-barrel.ts
2195
- import path19 from "path";
2266
+ import path20 from "path";
2196
2267
  var noUtilBarrelRule = {
2197
2268
  meta: {
2198
2269
  schema: [],
@@ -2202,7 +2273,7 @@ var noUtilBarrelRule = {
2202
2273
  }
2203
2274
  },
2204
2275
  create(context) {
2205
- const filename = path19.resolve(context.filename);
2276
+ const filename = path20.resolve(context.filename);
2206
2277
  const sourceRoot = sourceRootOf3(context, filename);
2207
2278
  return {
2208
2279
  Program(node) {
@@ -2211,7 +2282,7 @@ var noUtilBarrelRule = {
2211
2282
  if (!target) continue;
2212
2283
  const segments = target.replace(/\\/g, "/").split("/");
2213
2284
  const utilsIndex = segments.lastIndexOf("utils");
2214
- if (utilsIndex < 0 || !path19.basename(target).startsWith("index.")) continue;
2285
+ if (utilsIndex < 0 || !path20.basename(target).startsWith("index.")) continue;
2215
2286
  context.report({
2216
2287
  node,
2217
2288
  message: `Import utilities directly instead of through "${specifier}". See docs/next-codebase-guide/rules/utilities-rule.md`
@@ -2231,10 +2302,10 @@ function importSpecifiers2(source) {
2231
2302
  return specifiers;
2232
2303
  }
2233
2304
  function sourceRootOf3(context, filename) {
2234
- const marker = `${path19.sep}src${path19.sep}`;
2305
+ const marker = `${path20.sep}src${path20.sep}`;
2235
2306
  const srcIndex = filename.lastIndexOf(marker);
2236
2307
  if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
2237
- return path19.resolve(context.cwd ?? process.cwd(), "src");
2308
+ return path20.resolve(context.cwd ?? process.cwd(), "src");
2238
2309
  }
2239
2310
 
2240
2311
  // eslint/rules/jsx-hygiene.ts
@@ -2327,7 +2398,7 @@ var jsxHygieneRule = {
2327
2398
  };
2328
2399
 
2329
2400
  // eslint/rules/interactive-component.ts
2330
- import path20 from "path";
2401
+ import path21 from "path";
2331
2402
  var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
2332
2403
  "a",
2333
2404
  "button",
@@ -2482,8 +2553,8 @@ var interactiveComponentRule = {
2482
2553
  },
2483
2554
  create(context) {
2484
2555
  if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
2485
- const filename = path20.resolve(context.filename);
2486
- const base = path20.basename(filename, path20.extname(filename));
2556
+ const filename = path21.resolve(context.filename);
2557
+ const base = path21.basename(filename, path21.extname(filename));
2487
2558
  if (NEXT_ROUTING_FILES3.has(base)) return {};
2488
2559
  return {
2489
2560
  JSXElement(node) {
@@ -2734,12 +2805,12 @@ var cvaBooleanVariantsRule = {
2734
2805
  };
2735
2806
 
2736
2807
  // eslint/rules/cross-feature-import.ts
2737
- import path21 from "path";
2808
+ import path22 from "path";
2738
2809
  var FEATURES_SEGMENT = "features";
2739
2810
  function featureNameOf(resolvedPath, sourceRoot) {
2740
- const relative = path21.relative(sourceRoot, resolvedPath);
2811
+ const relative = path22.relative(sourceRoot, resolvedPath);
2741
2812
  if (relative.startsWith("..")) return void 0;
2742
- const segments = relative.split(path21.sep);
2813
+ const segments = relative.split(path22.sep);
2743
2814
  if (segments[0] !== FEATURES_SEGMENT || segments.length < 2) return void 0;
2744
2815
  return segments[1];
2745
2816
  }
@@ -2755,9 +2826,9 @@ var crossFeatureImportRule = {
2755
2826
  const filename = context.filename;
2756
2827
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2757
2828
  const sourceRoot = sourceRootOf(context);
2758
- const fileRelative = path21.relative(sourceRoot, filename);
2829
+ const fileRelative = path22.relative(sourceRoot, filename);
2759
2830
  if (fileRelative.startsWith("..")) return {};
2760
- const fileSegments = fileRelative.split(path21.sep);
2831
+ const fileSegments = fileRelative.split(path22.sep);
2761
2832
  const isInCompositions = fileSegments[0] === "compositions";
2762
2833
  const isInApp = fileSegments[0] === "app";
2763
2834
  const isConfig = fileSegments[0] === "config";
@@ -2771,9 +2842,9 @@ var crossFeatureImportRule = {
2771
2842
  if (typeof source.value !== "string") return;
2772
2843
  let resolved;
2773
2844
  if (source.value.startsWith("@/")) {
2774
- resolved = path21.resolve(sourceRoot, source.value.slice(2));
2845
+ resolved = path22.resolve(sourceRoot, source.value.slice(2));
2775
2846
  } else if (source.value.startsWith(".")) {
2776
- resolved = path21.resolve(path21.dirname(filename), source.value);
2847
+ resolved = path22.resolve(path22.dirname(filename), source.value);
2777
2848
  }
2778
2849
  if (!resolved) return;
2779
2850
  const feature = featureNameOf(resolved, sourceRoot);
@@ -2792,7 +2863,7 @@ var crossFeatureImportRule = {
2792
2863
  };
2793
2864
 
2794
2865
  // eslint/rules/pure-function-extract.ts
2795
- import path22 from "path";
2866
+ import path23 from "path";
2796
2867
  function isComponentLikeName(name) {
2797
2868
  return /^[A-Z]/.test(name);
2798
2869
  }
@@ -2821,9 +2892,9 @@ var pureFunctionExtractRule = {
2821
2892
  const filename = context.filename;
2822
2893
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2823
2894
  const sourceRoot = sourceRootOf(context);
2824
- const relative = path22.relative(sourceRoot, filename);
2895
+ const relative = path23.relative(sourceRoot, filename);
2825
2896
  if (relative.startsWith("..")) return {};
2826
- const segments = relative.split(path22.sep);
2897
+ const segments = relative.split(path23.sep);
2827
2898
  if (segments[0] === "utils") return {};
2828
2899
  if (segments[0] === "app") return {};
2829
2900
  const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
@@ -2863,7 +2934,7 @@ var pureFunctionExtractRule = {
2863
2934
  };
2864
2935
 
2865
2936
  // eslint/rules/hook-complexity.ts
2866
- import path23 from "path";
2937
+ import path24 from "path";
2867
2938
  import ts4 from "typescript";
2868
2939
  var REACT_HOOKS = /* @__PURE__ */ new Set([
2869
2940
  "useState",
@@ -2916,9 +2987,9 @@ var hookComplexityRule = {
2916
2987
  create(context) {
2917
2988
  const filename = context.filename;
2918
2989
  const sourceRoot = sourceRootOf(context);
2919
- const relative = path23.relative(sourceRoot, filename);
2990
+ const relative = path24.relative(sourceRoot, filename);
2920
2991
  if (relative.startsWith("..")) return {};
2921
- const segments = relative.split(path23.sep);
2992
+ const segments = relative.split(path24.sep);
2922
2993
  const sourceText = context.sourceCode.text;
2923
2994
  function checkHook(node, name, body, exported) {
2924
2995
  if (!exported) return;
@@ -2960,9 +3031,9 @@ var hookComplexityRule = {
2960
3031
  };
2961
3032
 
2962
3033
  // eslint/rules/locale-dotted-path.ts
2963
- import path24 from "path";
3034
+ import path25 from "path";
2964
3035
  function isInLocalesDir(filename) {
2965
- const segments = path24.resolve(filename).split(path24.sep);
3036
+ const segments = path25.resolve(filename).split(path25.sep);
2966
3037
  const srcIdx = segments.lastIndexOf("src");
2967
3038
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
2968
3039
  }
@@ -3011,9 +3082,9 @@ var localeDottedPathRule = {
3011
3082
  };
3012
3083
 
3013
3084
  // eslint/rules/locales-location.ts
3014
- import path25 from "path";
3085
+ import path26 from "path";
3015
3086
  function isLocalesFile(filename) {
3016
- const segments = path25.resolve(filename).split(path25.sep);
3087
+ const segments = path26.resolve(filename).split(path26.sep);
3017
3088
  const srcIdx = segments.lastIndexOf("src");
3018
3089
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3019
3090
  }
@@ -3032,7 +3103,7 @@ var localesLocationRule = {
3032
3103
  create(context) {
3033
3104
  if (isLocalesFile(context.filename)) return {};
3034
3105
  const filename = context.filename;
3035
- const segments = path25.resolve(filename).split(path25.sep);
3106
+ const segments = path26.resolve(filename).split(path26.sep);
3036
3107
  const srcIdx = segments.lastIndexOf("src");
3037
3108
  if (srcIdx === -1) return {};
3038
3109
  const folder = segments[srcIdx + 1];
@@ -3054,7 +3125,7 @@ var localesLocationRule = {
3054
3125
  };
3055
3126
 
3056
3127
  // eslint/rules/hook-extraction.ts
3057
- import path26 from "path";
3128
+ import path27 from "path";
3058
3129
  var hookExtractionRule = {
3059
3130
  meta: {
3060
3131
  schema: [],
@@ -3065,7 +3136,7 @@ var hookExtractionRule = {
3065
3136
  },
3066
3137
  create(context) {
3067
3138
  const sourceRoot = sourceRootOf(context);
3068
- const file = path26.resolve(context.filename);
3139
+ const file = path27.resolve(context.filename);
3069
3140
  const segments = segmentsOf(file, sourceRoot);
3070
3141
  if (segments.length === 0) return {};
3071
3142
  const index = getProjectIndex(sourceRoot);
@@ -3091,7 +3162,7 @@ var hookExtractionRule = {
3091
3162
  };
3092
3163
 
3093
3164
  // eslint/rules/value-extraction.ts
3094
- import path27 from "path";
3165
+ import path28 from "path";
3095
3166
  var valueExtractionRule = {
3096
3167
  meta: {
3097
3168
  schema: [],
@@ -3102,7 +3173,7 @@ var valueExtractionRule = {
3102
3173
  },
3103
3174
  create(context) {
3104
3175
  const sourceRoot = sourceRootOf(context);
3105
- const file = path27.resolve(context.filename);
3176
+ const file = path28.resolve(context.filename);
3106
3177
  const segments = segmentsOf(file, sourceRoot);
3107
3178
  if (segments.length === 0 || segments[0] !== "app") return {};
3108
3179
  const index = getProjectIndex(sourceRoot);
@@ -3123,7 +3194,7 @@ var valueExtractionRule = {
3123
3194
  };
3124
3195
 
3125
3196
  // eslint/rules/config-extraction.ts
3126
- import path28 from "path";
3197
+ import path29 from "path";
3127
3198
  var configExtractionRule = {
3128
3199
  meta: {
3129
3200
  schema: [],
@@ -3134,7 +3205,7 @@ var configExtractionRule = {
3134
3205
  },
3135
3206
  create(context) {
3136
3207
  const sourceRoot = sourceRootOf(context);
3137
- const file = path28.resolve(context.filename);
3208
+ const file = path29.resolve(context.filename);
3138
3209
  const segments = segmentsOf(file, sourceRoot);
3139
3210
  if (segments.length < 3 || segments[0] !== "config") return {};
3140
3211
  if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
@@ -3172,7 +3243,7 @@ var configExtractionRule = {
3172
3243
  };
3173
3244
 
3174
3245
  // eslint/rules/component-nesting.ts
3175
- import path29 from "path";
3246
+ import path30 from "path";
3176
3247
  var componentNestingRule = {
3177
3248
  meta: {
3178
3249
  schema: [],
@@ -3183,7 +3254,7 @@ var componentNestingRule = {
3183
3254
  },
3184
3255
  create(context) {
3185
3256
  const sourceRoot = sourceRootOf(context);
3186
- const file = path29.resolve(context.filename);
3257
+ const file = path30.resolve(context.filename);
3187
3258
  const segments = segmentsOf(file, sourceRoot);
3188
3259
  if (segments.length !== 4 || segments[0] !== "features") return {};
3189
3260
  const index = getProjectIndex(sourceRoot);
@@ -3216,7 +3287,7 @@ var componentNestingRule = {
3216
3287
  };
3217
3288
 
3218
3289
  // eslint/rules/stay-flat.ts
3219
- import path30 from "path";
3290
+ import path31 from "path";
3220
3291
  var stayFlatRule = {
3221
3292
  meta: {
3222
3293
  schema: [],
@@ -3227,7 +3298,7 @@ var stayFlatRule = {
3227
3298
  },
3228
3299
  create(context) {
3229
3300
  const sourceRoot = sourceRootOf(context);
3230
- const file = path30.resolve(context.filename);
3301
+ const file = path31.resolve(context.filename);
3231
3302
  const segments = segmentsOf(file, sourceRoot);
3232
3303
  if (segments.length !== 3 || segments[0] !== "features") return {};
3233
3304
  const index = getProjectIndex(sourceRoot);
@@ -3267,7 +3338,7 @@ var stayFlatRule = {
3267
3338
  };
3268
3339
 
3269
3340
  // eslint/rules/type-extraction.ts
3270
- import path31 from "path";
3341
+ import path32 from "path";
3271
3342
  var typeExtractionRule = {
3272
3343
  meta: {
3273
3344
  schema: [],
@@ -3278,7 +3349,7 @@ var typeExtractionRule = {
3278
3349
  },
3279
3350
  create(context) {
3280
3351
  const sourceRoot = sourceRootOf(context);
3281
- const file = path31.resolve(context.filename);
3352
+ const file = path32.resolve(context.filename);
3282
3353
  const segments = segmentsOf(file, sourceRoot);
3283
3354
  if (segments.length === 0) return {};
3284
3355
  const index = getProjectIndex(sourceRoot);
@@ -3324,7 +3395,7 @@ var typeExtractionRule = {
3324
3395
  };
3325
3396
 
3326
3397
  // eslint/rules/locale-placement.ts
3327
- import path32 from "path";
3398
+ import path33 from "path";
3328
3399
  import { readFileSync as readFileSync4 } from "fs";
3329
3400
  import ts5 from "typescript";
3330
3401
  var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
@@ -3372,7 +3443,7 @@ var localePlacementRule = {
3372
3443
  },
3373
3444
  create(context) {
3374
3445
  const sourceRoot = sourceRootOf(context);
3375
- const file = path32.resolve(context.filename);
3446
+ const file = path33.resolve(context.filename);
3376
3447
  const segments = segmentsOf(file, sourceRoot);
3377
3448
  if (segments.length === 0) return {};
3378
3449
  const index = getProjectIndex(sourceRoot);
@@ -3450,7 +3521,7 @@ var localePlacementRule = {
3450
3521
  };
3451
3522
 
3452
3523
  // eslint/rules/sole-state-owner.ts
3453
- import path33 from "path";
3524
+ import path34 from "path";
3454
3525
  import ts6 from "typescript";
3455
3526
  function findStateHooks(node) {
3456
3527
  const hooks = [];
@@ -3530,7 +3601,7 @@ var soleStateOwnerRule = {
3530
3601
  }
3531
3602
  },
3532
3603
  create(context) {
3533
- const filename = path33.resolve(context.filename);
3604
+ const filename = path34.resolve(context.filename);
3534
3605
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
3535
3606
  const text = context.sourceCode.text;
3536
3607
  const components = parseComponentInfo(text, filename);
@@ -3609,7 +3680,7 @@ function usesOutsideJsx(declaration, hook, children) {
3609
3680
  }
3610
3681
 
3611
3682
  // eslint/rules/locale-key-shape.ts
3612
- import path34 from "path";
3683
+ import path35 from "path";
3613
3684
  var MAX_KEY_LENGTH = 30;
3614
3685
  var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
3615
3686
  "Button",
@@ -3659,7 +3730,7 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
3659
3730
  var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
3660
3731
  var ENGLISH = /^[A-Za-z0-9_]*$/;
3661
3732
  function isLocalesFile2(filename) {
3662
- const segments = path34.resolve(filename).split(path34.sep);
3733
+ const segments = path35.resolve(filename).split(path35.sep);
3663
3734
  const srcIdx = segments.lastIndexOf("src");
3664
3735
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3665
3736
  }
@@ -3730,7 +3801,7 @@ var localeKeyShapeRule = {
3730
3801
  };
3731
3802
 
3732
3803
  // eslint/rules/shared-style-dedup.ts
3733
- import path35 from "path";
3804
+ import path36 from "path";
3734
3805
  import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
3735
3806
  var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
3736
3807
  var comboCache;
@@ -3768,7 +3839,7 @@ var sharedStyleDedupRule = {
3768
3839
  },
3769
3840
  create(context) {
3770
3841
  const sourceRoot = sourceRootOf(context);
3771
- const file = path35.resolve(context.filename);
3842
+ const file = path36.resolve(context.filename);
3772
3843
  const segments = segmentsOf(file, sourceRoot);
3773
3844
  if (segments.length === 0) return {};
3774
3845
  const index = getProjectIndex(sourceRoot);
@@ -3971,8 +4042,103 @@ var zodSchemaValidationRule = {
3971
4042
  }
3972
4043
  };
3973
4044
 
4045
+ // eslint/rules/schema-casing.ts
4046
+ import path37 from "path";
4047
+ function isCamelCase(name) {
4048
+ return /^[a-z][a-zA-Z0-9]*$/.test(name);
4049
+ }
4050
+ function rootIdentifierName(node) {
4051
+ let current = node;
4052
+ for (; ; ) {
4053
+ if (current.type === "Identifier") return current.name;
4054
+ if (current.type === "CallExpression") {
4055
+ current = current.callee;
4056
+ continue;
4057
+ }
4058
+ if (current.type === "MemberExpression") {
4059
+ current = current.object;
4060
+ continue;
4061
+ }
4062
+ return void 0;
4063
+ }
4064
+ }
4065
+ var schemaCasingRule = {
4066
+ meta: {
4067
+ schema: [],
4068
+ type: "problem",
4069
+ docs: {
4070
+ description: "Require a Zod schema constant to be named in camelCase."
4071
+ }
4072
+ },
4073
+ create(context) {
4074
+ const filename = path37.resolve(context.filename);
4075
+ const sourceRoot = sourceRootOf(context);
4076
+ if (!filename.startsWith(sourceRoot + path37.sep)) return {};
4077
+ let zodLocalName;
4078
+ return {
4079
+ ImportDeclaration(node) {
4080
+ if (node.source.value !== "zod") return;
4081
+ for (const specifier of node.specifiers) {
4082
+ if (specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "z") {
4083
+ zodLocalName = specifier.local.name;
4084
+ }
4085
+ }
4086
+ },
4087
+ VariableDeclarator(node) {
4088
+ if (!zodLocalName) return;
4089
+ if (node.id.type !== "Identifier") return;
4090
+ const declaration = node.parent;
4091
+ if (declaration.type !== "VariableDeclaration" || declaration.kind !== "const") return;
4092
+ const container = declaration.parent;
4093
+ const isModuleLevel = container.type === "Program" || container.type === "ExportNamedDeclaration" && container.parent.type === "Program";
4094
+ if (!isModuleLevel) return;
4095
+ if (!node.init || rootIdentifierName(node.init) !== zodLocalName) return;
4096
+ const { name } = node.id;
4097
+ if (isCamelCase(name)) return;
4098
+ context.report({
4099
+ node,
4100
+ message: `${name} must be camelCase. See docs/next-codebase-guide/rules/types-and-schemas-rule.md`
4101
+ });
4102
+ }
4103
+ };
4104
+ }
4105
+ };
4106
+
4107
+ // eslint/rules/component-casing.ts
4108
+ import path38 from "path";
4109
+ function isPascalCase6(name) {
4110
+ return /^[A-Z][A-Za-z0-9]*$/.test(name);
4111
+ }
4112
+ var componentCasingRule = {
4113
+ meta: {
4114
+ schema: [],
4115
+ type: "problem",
4116
+ docs: {
4117
+ description: "Require a JSX-returning function or const to be named in PascalCase."
4118
+ }
4119
+ },
4120
+ create(context) {
4121
+ const filename = path38.resolve(context.filename);
4122
+ const sourceRoot = sourceRootOf(context);
4123
+ if (!filename.startsWith(sourceRoot + path38.sep)) return {};
4124
+ if (path38.extname(filename) !== ".tsx") return {};
4125
+ return {
4126
+ Program(node) {
4127
+ for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
4128
+ if (isPascalCase6(declaration.name)) continue;
4129
+ context.report({
4130
+ node,
4131
+ loc: { line: declaration.line, column: declaration.column },
4132
+ message: `${declaration.name} must be PascalCase; only a PascalCase name resolves as a JSX component. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
4133
+ });
4134
+ }
4135
+ }
4136
+ };
4137
+ }
4138
+ };
4139
+
3974
4140
  // eslint/rules/source-under-src.ts
3975
- import path36 from "path";
4141
+ import path39 from "path";
3976
4142
  var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
3977
4143
  ".agents",
3978
4144
  ".cache",
@@ -4013,14 +4179,14 @@ var sourceUnderSrcRule = {
4013
4179
  }
4014
4180
  },
4015
4181
  create(context) {
4016
- const filename = path36.resolve(context.filename);
4182
+ const filename = path39.resolve(context.filename);
4017
4183
  if (!MODULE_EXTENSION.test(filename)) return {};
4018
- const relative = path36.relative(context.cwd, filename).replace(/\\/g, "/");
4184
+ const relative = path39.relative(context.cwd, filename).replace(/\\/g, "/");
4019
4185
  if (relative === "src" || relative.startsWith("src/")) return {};
4020
4186
  const topLevel = relative.split("/")[0] ?? "";
4021
4187
  if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
4022
4188
  if (!relative.includes("/")) {
4023
- const basename = path36.basename(filename);
4189
+ const basename = path39.basename(filename);
4024
4190
  if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
4025
4191
  }
4026
4192
  return {
@@ -4037,7 +4203,7 @@ var sourceUnderSrcRule = {
4037
4203
 
4038
4204
  // eslint/rules/zirka-baseline.ts
4039
4205
  import fs5 from "fs";
4040
- import path37 from "path";
4206
+ import path40 from "path";
4041
4207
  var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
4042
4208
  var PRETTIER_CONFIGS = [
4043
4209
  "prettier.config.mjs",
@@ -4056,10 +4222,10 @@ var zirkaBaselineRule = {
4056
4222
  }
4057
4223
  },
4058
4224
  create(context) {
4059
- const filename = path37.resolve(context.filename);
4060
- const basename = path37.basename(filename);
4225
+ const filename = path40.resolve(context.filename);
4226
+ const basename = path40.basename(filename);
4061
4227
  if (!ESLINT_CONFIG.test(basename)) return {};
4062
- const projectRoot = path37.dirname(filename);
4228
+ const projectRoot = path40.dirname(filename);
4063
4229
  const report3 = (message) => {
4064
4230
  context.report({
4065
4231
  node: context.sourceCode.ast,
@@ -4074,7 +4240,7 @@ var zirkaBaselineRule = {
4074
4240
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
4075
4241
  );
4076
4242
  }
4077
- const tsconfigPath = path37.join(projectRoot, "tsconfig.json");
4243
+ const tsconfigPath = path40.join(projectRoot, "tsconfig.json");
4078
4244
  if (!fs5.existsSync(tsconfigPath)) {
4079
4245
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
4080
4246
  } else {
@@ -4092,13 +4258,13 @@ var zirkaBaselineRule = {
4092
4258
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
4093
4259
  }
4094
4260
  }
4095
- const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path37.join(projectRoot, name)));
4261
+ const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path40.join(projectRoot, name)));
4096
4262
  if (!prettierConfigFile) {
4097
4263
  report3(
4098
4264
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
4099
4265
  );
4100
4266
  } else {
4101
- const content = fs5.readFileSync(path37.join(projectRoot, prettierConfigFile), "utf8");
4267
+ const content = fs5.readFileSync(path40.join(projectRoot, prettierConfigFile), "utf8");
4102
4268
  if (!content.includes("zirka")) {
4103
4269
  report3(
4104
4270
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -4168,7 +4334,7 @@ var docKindSuffixRule = {
4168
4334
  };
4169
4335
 
4170
4336
  // eslint/rules/documentation/title-matches-file-name.ts
4171
- import path38 from "path";
4337
+ import path41 from "path";
4172
4338
  function toExpectedFileName(title) {
4173
4339
  return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
4174
4340
  }
@@ -4188,7 +4354,7 @@ var titleMatchesFileNameRule = {
4188
4354
  if (!filename.endsWith(".md")) return;
4189
4355
  const title = getTextContent(node).trim();
4190
4356
  const expectedFileName = toExpectedFileName(title);
4191
- const actualFileName = path38.basename(filename);
4357
+ const actualFileName = path41.basename(filename);
4192
4358
  if (!title) {
4193
4359
  context.report({
4194
4360
  node,
@@ -4631,20 +4797,20 @@ var referenceBlockHeadingsRule = {
4631
4797
 
4632
4798
  // eslint/rules/documentation/support-document-placement.ts
4633
4799
  import { existsSync as existsSync2 } from "fs";
4634
- import path39 from "path";
4800
+ import path42 from "path";
4635
4801
  function checkPlacement(filename, kind) {
4636
- const parentFolder = path39.basename(path39.dirname(filename));
4802
+ const parentFolder = path42.basename(path42.dirname(filename));
4637
4803
  const expectedParent = `${kind}s`;
4638
4804
  if (parentFolder !== expectedParent) {
4639
4805
  return `${kind} lives in "${parentFolder}/" instead of "${expectedParent}/"`;
4640
4806
  }
4641
- const guideFolderPath = path39.dirname(path39.dirname(filename));
4642
- const guideFolder = path39.basename(guideFolderPath);
4807
+ const guideFolderPath = path42.dirname(path42.dirname(filename));
4808
+ const guideFolder = path42.basename(guideFolderPath);
4643
4809
  if (!guideFolder.endsWith("-guide")) {
4644
4810
  return `${kind} owner folder "${guideFolder}/" does not use the "*-guide/" suffix`;
4645
4811
  }
4646
4812
  const entryPoint = `${guideFolder}.md`;
4647
- if (!existsSync2(path39.join(guideFolderPath, entryPoint))) {
4813
+ if (!existsSync2(path42.join(guideFolderPath, entryPoint))) {
4648
4814
  return `${kind} owner folder "${guideFolder}/" has no "${entryPoint}" entry point`;
4649
4815
  }
4650
4816
  return void 0;
@@ -4703,11 +4869,11 @@ var noTemplatePromptRule = {
4703
4869
  };
4704
4870
 
4705
4871
  // eslint/rules/documentation/guide-folder-entry-point.ts
4706
- import path41 from "path";
4872
+ import path44 from "path";
4707
4873
 
4708
4874
  // eslint/rules/documentation/project-index.ts
4709
4875
  import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
4710
- import path40 from "path";
4876
+ import path43 from "path";
4711
4877
  var KIND_BY_SUFFIX = [
4712
4878
  ["-rule.md", "rule"],
4713
4879
  ["-guide.md", "guide"],
@@ -4716,7 +4882,7 @@ var KIND_BY_SUFFIX = [
4716
4882
  ];
4717
4883
  function listMarkdownFiles(dir) {
4718
4884
  return readdirSync3(dir).flatMap((entry) => {
4719
- const entryPath = path40.join(dir, entry);
4885
+ const entryPath = path43.join(dir, entry);
4720
4886
  if (statSync4(entryPath).isDirectory()) {
4721
4887
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
4722
4888
  }
@@ -4734,11 +4900,11 @@ function getProjectDocs(docsRoot) {
4734
4900
  if (cached) return cached;
4735
4901
  const files = listMarkdownFiles(docsRoot);
4736
4902
  const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
4737
- const fileName = path40.basename(filePath);
4903
+ const fileName = path43.basename(filePath);
4738
4904
  const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
4739
4905
  return {
4740
4906
  filePath,
4741
- doc: path40.relative(docsRoot, filePath).split(path40.sep).join("/"),
4907
+ doc: path43.relative(docsRoot, filePath).split(path43.sep).join("/"),
4742
4908
  fileName,
4743
4909
  kind,
4744
4910
  title: extractTitle(filePath)
@@ -4748,12 +4914,12 @@ function getProjectDocs(docsRoot) {
4748
4914
  return docs;
4749
4915
  }
4750
4916
  function findDocsRoot(filePath) {
4751
- let dir = path40.dirname(filePath);
4917
+ let dir = path43.dirname(filePath);
4752
4918
  for (; ; ) {
4753
- if (path40.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
4919
+ if (path43.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
4754
4920
  return dir;
4755
4921
  }
4756
- const parent = path40.dirname(dir);
4922
+ const parent = path43.dirname(dir);
4757
4923
  if (parent === dir) return void 0;
4758
4924
  dir = parent;
4759
4925
  }
@@ -4777,13 +4943,13 @@ var guideFolderEntryPointRule = {
4777
4943
  if (!docsRoot) return;
4778
4944
  const docs = getProjectDocs(docsRoot);
4779
4945
  const guideFolders = new Set(
4780
- docs.filter((doc) => ["rules", "references"].includes(path41.basename(path41.dirname(doc.filePath)))).map((doc) => path41.dirname(path41.dirname(doc.filePath))).filter((folder) => path41.resolve(folder) !== path41.resolve(docsRoot))
4946
+ docs.filter((doc) => ["rules", "references"].includes(path44.basename(path44.dirname(doc.filePath)))).map((doc) => path44.dirname(path44.dirname(doc.filePath))).filter((folder) => path44.resolve(folder) !== path44.resolve(docsRoot))
4781
4947
  );
4782
- const currentDir = path41.dirname(filename);
4948
+ const currentDir = path44.dirname(filename);
4783
4949
  if (guideFolders.has(currentDir)) {
4784
- const expectedEntryPoint = `${path41.basename(currentDir)}.md`;
4950
+ const expectedEntryPoint = `${path44.basename(currentDir)}.md`;
4785
4951
  const hasEntryPoint = docs.some(
4786
- (doc) => doc.kind === "guide" && path41.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
4952
+ (doc) => doc.kind === "guide" && path44.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
4787
4953
  );
4788
4954
  if (!hasEntryPoint) {
4789
4955
  context.report({
@@ -5008,7 +5174,7 @@ var noNestedHowToRule = {
5008
5174
 
5009
5175
  // eslint/rules/documentation/glossary-term-linking.ts
5010
5176
  import { readFileSync as readFileSync7 } from "fs";
5011
- import path42 from "path";
5177
+ import path45 from "path";
5012
5178
  function extractGlossaryTerms(filePath) {
5013
5179
  const content = readFileSync7(filePath, "utf8");
5014
5180
  const terms = [];
@@ -5062,9 +5228,9 @@ var glossaryTermLinkingRule = {
5062
5228
  const docsRoot = findDocsRoot(filename);
5063
5229
  if (!docsRoot) return;
5064
5230
  const docs = getProjectDocs(docsRoot);
5065
- const guideDir = path42.dirname(filename);
5231
+ const guideDir = path45.dirname(filename);
5066
5232
  const guideReferences = docs.filter(
5067
- (doc) => doc.kind === "reference" && path42.dirname(doc.filePath) === guideDir
5233
+ (doc) => doc.kind === "reference" && path45.dirname(doc.filePath) === guideDir
5068
5234
  );
5069
5235
  if (guideReferences.length === 0) return;
5070
5236
  const glossaryTerms = [];
@@ -5089,7 +5255,7 @@ var glossaryTermLinkingRule = {
5089
5255
 
5090
5256
  // eslint/rules/documentation/guide-mentions-documents.ts
5091
5257
  import { existsSync as existsSync3 } from "fs";
5092
- import path43 from "path";
5258
+ import path46 from "path";
5093
5259
  function visitSteps3(node, check) {
5094
5260
  if (node.type === "list" && node.ordered) {
5095
5261
  for (const child of node.children) check(child);
@@ -5122,12 +5288,12 @@ var guideMentionsDocumentsRule = {
5122
5288
  if (!filename.endsWith("-guide.md")) return;
5123
5289
  const docsRoot = findDocsRoot(filename);
5124
5290
  if (!docsRoot) return;
5125
- const guideDir = path43.dirname(filename);
5126
- if (path43.basename(filename, ".md") !== path43.basename(guideDir)) return;
5291
+ const guideDir = path46.dirname(filename);
5292
+ if (path46.basename(filename, ".md") !== path46.basename(guideDir)) return;
5127
5293
  const docs = getProjectDocs(docsRoot);
5128
5294
  const owned = docs.filter((doc) => {
5129
- const parent = path43.dirname(doc.filePath);
5130
- return parent === path43.join(guideDir, "rules") || parent === path43.join(guideDir, "references");
5295
+ const parent = path46.dirname(doc.filePath);
5296
+ return parent === path46.join(guideDir, "rules") || parent === path46.join(guideDir, "references");
5131
5297
  });
5132
5298
  const allLinks = [];
5133
5299
  collectMarkdownLinks(node, allLinks);
@@ -5154,7 +5320,7 @@ var guideMentionsDocumentsRule = {
5154
5320
  for (const link of allLinks) {
5155
5321
  const target = linkTarget(link.url);
5156
5322
  if (!target.endsWith(".md")) continue;
5157
- const resolved = path43.normalize(path43.join(guideDir, target));
5323
+ const resolved = path46.normalize(path46.join(guideDir, target));
5158
5324
  if (!existsSync3(resolved)) {
5159
5325
  context.report({
5160
5326
  node: link,
@@ -5704,11 +5870,11 @@ var themeVariableNamespaceRule = {
5704
5870
 
5705
5871
  // eslint/rules/tailwind/css-entry-point.ts
5706
5872
  import { statSync as statSync6 } from "fs";
5707
- import path46 from "path";
5873
+ import path49 from "path";
5708
5874
 
5709
5875
  // eslint/rules/tailwind/source-files.ts
5710
5876
  import { readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
5711
- import path44 from "path";
5877
+ import path47 from "path";
5712
5878
  var CSS_EXTENSIONS = [".css"];
5713
5879
  var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
5714
5880
  var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
@@ -5721,7 +5887,7 @@ function findFiles(dir, extensions) {
5721
5887
  }
5722
5888
  return entries.flatMap((entry) => {
5723
5889
  if (entry.startsWith(".") || entry === "node_modules") return [];
5724
- const entryPath = path44.join(dir, entry);
5890
+ const entryPath = path47.join(dir, entry);
5725
5891
  let stats;
5726
5892
  try {
5727
5893
  stats = statSync5(entryPath);
@@ -5729,7 +5895,7 @@ function findFiles(dir, extensions) {
5729
5895
  return [];
5730
5896
  }
5731
5897
  if (stats.isDirectory()) return findFiles(entryPath, extensions);
5732
- return extensions.includes(path44.extname(entry)) ? [entryPath] : [];
5898
+ return extensions.includes(path47.extname(entry)) ? [entryPath] : [];
5733
5899
  });
5734
5900
  }
5735
5901
  function cachedTextReader() {
@@ -5752,7 +5918,7 @@ function escapeRegExp(text) {
5752
5918
  }
5753
5919
 
5754
5920
  // eslint/rules/tailwind/stylesheet-graph.ts
5755
- import path45 from "path";
5921
+ import path48 from "path";
5756
5922
  function registersTailwind(text) {
5757
5923
  return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
5758
5924
  }
@@ -5766,25 +5932,25 @@ function moduleImports(text, fileName) {
5766
5932
  return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
5767
5933
  }
5768
5934
  function resolveSpecifier2(fromFile, spec, sourceRoot) {
5769
- if (spec.startsWith("/")) return path45.resolve(spec);
5770
- if (spec.startsWith("./") || spec.startsWith("../")) return path45.resolve(path45.dirname(fromFile), spec);
5771
- if (spec.startsWith("@/")) return path45.resolve(sourceRoot, spec.slice(2));
5935
+ if (spec.startsWith("/")) return path48.resolve(spec);
5936
+ if (spec.startsWith("./") || spec.startsWith("../")) return path48.resolve(path48.dirname(fromFile), spec);
5937
+ if (spec.startsWith("@/")) return path48.resolve(sourceRoot, spec.slice(2));
5772
5938
  return void 0;
5773
5939
  }
5774
5940
  function buildStylesheetGraph(options) {
5775
5941
  const { cssFiles, sourceRoot, textOf } = options;
5776
- const cssSet = new Set(cssFiles.map((file) => path45.normalize(file)));
5942
+ const cssSet = new Set(cssFiles.map((file) => path48.normalize(file)));
5777
5943
  const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
5778
5944
  const reachable = /* @__PURE__ */ new Set();
5779
5945
  const queue = [...globals];
5780
- for (const global of globals) reachable.add(path45.normalize(global));
5946
+ for (const global of globals) reachable.add(path48.normalize(global));
5781
5947
  while (queue.length > 0) {
5782
5948
  const from = queue.shift();
5783
5949
  if (!from) continue;
5784
5950
  for (const spec of importedSpecifiers(textOf(from))) {
5785
5951
  const target = resolveSpecifier2(from, spec, sourceRoot);
5786
5952
  if (!target) continue;
5787
- const normalized = path45.normalize(target);
5953
+ const normalized = path48.normalize(target);
5788
5954
  if (cssSet.has(normalized) && !reachable.has(normalized)) {
5789
5955
  reachable.add(normalized);
5790
5956
  queue.push(normalized);
@@ -5796,7 +5962,7 @@ function buildStylesheetGraph(options) {
5796
5962
  for (const spec of importedSpecifiers(textOf(global))) {
5797
5963
  const target = resolveSpecifier2(global, spec, sourceRoot);
5798
5964
  if (!target) continue;
5799
- const normalized = path45.normalize(target);
5965
+ const normalized = path48.normalize(target);
5800
5966
  if (cssSet.has(normalized)) directChildren.add(normalized);
5801
5967
  }
5802
5968
  }
@@ -5829,7 +5995,7 @@ var cssEntryPointRule = {
5829
5995
  return {
5830
5996
  "StyleSheet:exit"(node) {
5831
5997
  if (globals.length === 0) return;
5832
- const current = path46.normalize(path46.resolve(context.filename));
5998
+ const current = path49.normalize(path49.resolve(context.filename));
5833
5999
  if (globals.includes(current)) {
5834
6000
  if (globals.length > 1) {
5835
6001
  context.report({
@@ -5838,7 +6004,7 @@ var cssEntryPointRule = {
5838
6004
  });
5839
6005
  return;
5840
6006
  }
5841
- const basename = path46.basename(current);
6007
+ const basename = path49.basename(current);
5842
6008
  const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
5843
6009
  if (importCount !== 1) {
5844
6010
  context.report({
@@ -6167,7 +6333,7 @@ var nextjsStackRule = {
6167
6333
 
6168
6334
  // eslint/rules/package-json/vitest-coverage.ts
6169
6335
  import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
6170
- import path47 from "path";
6336
+ import path50 from "path";
6171
6337
  function memberName4(member) {
6172
6338
  return member.name.type === "String" ? member.name.value : member.name.name;
6173
6339
  }
@@ -6236,7 +6402,7 @@ var vitestCoverageRule = {
6236
6402
  message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
6237
6403
  });
6238
6404
  }
6239
- const configName = VITEST_CONFIG_NAMES.find((name) => existsSync4(path47.join(context.cwd, name)));
6405
+ const configName = VITEST_CONFIG_NAMES.find((name) => existsSync4(path50.join(context.cwd, name)));
6240
6406
  if (!configName) {
6241
6407
  context.report({
6242
6408
  node,
@@ -6244,7 +6410,7 @@ var vitestCoverageRule = {
6244
6410
  });
6245
6411
  return;
6246
6412
  }
6247
- const content = readFileSync9(path47.join(context.cwd, configName), "utf8");
6413
+ const content = readFileSync9(path50.join(context.cwd, configName), "utf8");
6248
6414
  for (const metric of THRESHOLD_METRICS) {
6249
6415
  if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
6250
6416
  context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
@@ -6291,7 +6457,7 @@ var nextjsPackageJsonRules = {
6291
6457
 
6292
6458
  // eslint/rules/husky/husky-hook.ts
6293
6459
  import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
6294
- import path48 from "path";
6460
+ import path51 from "path";
6295
6461
  var VITEST_CONFIG_NAMES2 = [
6296
6462
  "vitest.config.ts",
6297
6463
  "vitest.config.mts",
@@ -6317,7 +6483,7 @@ var huskyHookRule = {
6317
6483
  const root = node.body;
6318
6484
  if (root.type !== "Object") return;
6319
6485
  if (!context.filename.endsWith("package.json")) return;
6320
- const hookPath = path48.join(context.cwd, ".husky", "pre-commit");
6486
+ const hookPath = path51.join(context.cwd, ".husky", "pre-commit");
6321
6487
  if (!existsSync5(hookPath)) {
6322
6488
  context.report({
6323
6489
  node,
@@ -6341,7 +6507,7 @@ var huskyHookRule = {
6341
6507
  }
6342
6508
  requireNamedScript("typecheck");
6343
6509
  requireNamedScript("test:unit:coverage");
6344
- const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync5(path48.join(context.cwd, name)));
6510
+ const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync5(path51.join(context.cwd, name)));
6345
6511
  if (vitestConfigName !== void 0) {
6346
6512
  const coverageIndex = content.indexOf("npm run test:unit:coverage");
6347
6513
  const localAddIndex = content.indexOf(`git add ${vitestConfigName}`);
@@ -6355,7 +6521,7 @@ var huskyHookRule = {
6355
6521
  if (!content.includes("libyear --limit-major-individual=1")) {
6356
6522
  context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
6357
6523
  }
6358
- const suppressionsPath = path48.join(context.cwd, "eslint-suppressions.json");
6524
+ const suppressionsPath = path51.join(context.cwd, "eslint-suppressions.json");
6359
6525
  if (existsSync5(suppressionsPath)) {
6360
6526
  requireNamedScript("lint:prune");
6361
6527
  const pruneIndex = content.indexOf("npm run lint:prune");
@@ -6428,7 +6594,7 @@ var vulykDependencyRule = {
6428
6594
 
6429
6595
  // eslint/rules/vulyk/vulyk-docs.ts
6430
6596
  import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
6431
- import path49 from "path";
6597
+ import path52 from "path";
6432
6598
  var PASIKA_REPO = "Bredansky/pasika";
6433
6599
  var BASE_REQUIRED_DOCS = [
6434
6600
  { name: "documentation-guide", path: "docs/documentation-guide" },
@@ -6461,8 +6627,8 @@ var vulykDocsRule = {
6461
6627
  if (!context.filename.endsWith("package.json")) return;
6462
6628
  const root = node.body;
6463
6629
  if (root.type !== "Object") return;
6464
- const projectRoot = path49.dirname(path49.resolve(context.filename));
6465
- const configPath = path49.join(projectRoot, "vulyk.config.ts");
6630
+ const projectRoot = path52.dirname(path52.resolve(context.filename));
6631
+ const configPath = path52.join(projectRoot, "vulyk.config.ts");
6466
6632
  if (!existsSync6(configPath)) {
6467
6633
  context.report({
6468
6634
  node,
@@ -6487,7 +6653,7 @@ var vulykDocsRule = {
6487
6653
  });
6488
6654
  }
6489
6655
  }
6490
- const agentsPath = path49.join(projectRoot, "AGENTS.md");
6656
+ const agentsPath = path52.join(projectRoot, "AGENTS.md");
6491
6657
  if (!existsSync6(agentsPath)) {
6492
6658
  context.report({
6493
6659
  node,
@@ -6519,12 +6685,15 @@ var pasikaNextjsAppRules = {
6519
6685
  "enforce-barrel-exports": enforceBarrelExportsRule,
6520
6686
  "config-extraction": configExtractionRule,
6521
6687
  "value-extraction": valueExtractionRule,
6688
+ "constant-casing": constantCasingRule,
6522
6689
  "type-extraction": typeExtractionRule,
6523
6690
  "zod-schema-validation": zodSchemaValidationRule,
6691
+ "schema-casing": schemaCasingRule,
6524
6692
  "source-under-src": sourceUnderSrcRule,
6525
6693
  "zirka-baseline": zirkaBaselineRule,
6526
6694
  // Next.js/React application rules.
6527
6695
  "component-placement": componentPlacementRule,
6696
+ "component-casing": componentCasingRule,
6528
6697
  "application-structure": applicationStructureRule,
6529
6698
  "data-testid-case": dataTestIdCaseRule,
6530
6699
  "jsx-hygiene": jsxHygieneRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.7.6",
3
+ "version": "0.8.0",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",