pasika 0.10.48 → 0.10.49

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.
@@ -192,6 +192,7 @@ declare const pasikaPlugin: {
192
192
  "glossary-term-linking": _eslint_markdown.MarkdownRuleDefinition;
193
193
  "guide-mentions-documents": _eslint_markdown.MarkdownRuleDefinition;
194
194
  "guide-section-shape": _eslint_markdown.MarkdownRuleDefinition;
195
+ "file-length": eslint.Rule.RuleModule;
195
196
  "filename-case": eslint.Rule.RuleModule;
196
197
  "import-boundaries": eslint.Rule.RuleModule;
197
198
  "import-spacing": eslint.Rule.RuleModule;
@@ -2925,6 +2925,31 @@ var enforceCvaVariantPropsRule = {
2925
2925
  }
2926
2926
  };
2927
2927
 
2928
+ // eslint/rules/file-length.ts
2929
+ var MAX_SOURCE_FILE_LINES = 300;
2930
+ var fileLengthRule = {
2931
+ meta: {
2932
+ schema: [],
2933
+ type: "problem",
2934
+ docs: {
2935
+ description: "Require application source files to stay at or below 300 lines."
2936
+ }
2937
+ },
2938
+ create(context) {
2939
+ return {
2940
+ Program(node) {
2941
+ const lineCount = context.sourceCode.lines.length;
2942
+ if (lineCount <= MAX_SOURCE_FILE_LINES) return;
2943
+ context.report({
2944
+ node,
2945
+ loc: { line: 1, column: 0 },
2946
+ message: `Source file has ${String(lineCount)} lines; split it so no source file exceeds ${String(MAX_SOURCE_FILE_LINES)} lines. See docs/next-codebase-guide/rules/application-structure-rule.md`
2947
+ });
2948
+ }
2949
+ };
2950
+ }
2951
+ };
2952
+
2928
2953
  // eslint/rules/filename-case.ts
2929
2954
  import path24 from "path";
2930
2955
  var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
@@ -3087,6 +3112,10 @@ function sideEffectCategoryOf(node) {
3087
3112
  }
3088
3113
  return void 0;
3089
3114
  }
3115
+ function score(hookNames, sideEffectCategories) {
3116
+ const hookDiversityPoint = hookNames.size >= 2 ? 1 : 0;
3117
+ return hookDiversityPoint + sideEffectCategories.size;
3118
+ }
3090
3119
  function computeExtractionScore(body, sourceText) {
3091
3120
  const start = body.range?.[0] ?? 0;
3092
3121
  const end = body.range?.[1] ?? sourceText.length;
@@ -3107,15 +3136,44 @@ function computeExtractionScore(body, sourceText) {
3107
3136
  ts3.forEachChild(node, visit);
3108
3137
  };
3109
3138
  visit(sourceFile);
3110
- const hookDiversityPoint = hookNames.size >= 2 ? 1 : 0;
3111
- return hookDiversityPoint + sideEffectCategories.size;
3139
+ return score(hookNames, sideEffectCategories);
3140
+ }
3141
+ function componentBody(component) {
3142
+ const { declaration } = component;
3143
+ if (ts3.isFunctionDeclaration(declaration)) return declaration.body;
3144
+ const initializer = declaration.initializer;
3145
+ if (!initializer || !ts3.isArrowFunction(initializer) && !ts3.isFunctionExpression(initializer)) return void 0;
3146
+ return ts3.isBlock(initializer.body) ? initializer.body : void 0;
3147
+ }
3148
+ function computeInlineComponentExtractionScore(component) {
3149
+ const body = componentBody(component);
3150
+ if (!body) return 0;
3151
+ const hookNames = /* @__PURE__ */ new Set();
3152
+ const sideEffectCategories = /* @__PURE__ */ new Set();
3153
+ const collectSideEffects = (node) => {
3154
+ const sideEffect = sideEffectCategoryOf(node);
3155
+ if (sideEffect) sideEffectCategories.add(sideEffect);
3156
+ ts3.forEachChild(node, collectSideEffects);
3157
+ };
3158
+ const visit = (node) => {
3159
+ if (node !== body && ts3.isFunctionLike(node)) return;
3160
+ const hookName = calledHookName(node);
3161
+ if (hookName && ts3.isCallExpression(node)) {
3162
+ hookNames.add(hookName);
3163
+ for (const argument of node.arguments) collectSideEffects(argument);
3164
+ return;
3165
+ }
3166
+ ts3.forEachChild(node, visit);
3167
+ };
3168
+ visit(body);
3169
+ return score(hookNames, sideEffectCategories);
3112
3170
  }
3113
3171
  var hookComplexityRule = {
3114
3172
  meta: {
3115
3173
  schema: [],
3116
3174
  type: "problem",
3117
3175
  docs: {
3118
- description: "Require extraction of complex single-consumer hooks and inline of simple ones."
3176
+ description: "Require extraction of complex inline or single-consumer hook logic and inline of simple hooks."
3119
3177
  }
3120
3178
  },
3121
3179
  create(context) {
@@ -3132,18 +3190,32 @@ var hookComplexityRule = {
3132
3190
  if (!name || !isHookName2(name) || !body) return;
3133
3191
  hooks.push({ node, name, body, exported });
3134
3192
  }
3193
+ function reportInlineComponentHooks() {
3194
+ if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return;
3195
+ const components = parseComponentInfo(sourceText, filename, { includeNonExported: true });
3196
+ for (const component of components) {
3197
+ const extractionScore = computeInlineComponentExtractionScore(component);
3198
+ if (extractionScore < 2) continue;
3199
+ const sourceFile = component.declaration.getSourceFile();
3200
+ const { line } = sourceFile.getLineAndCharacterOfPosition(component.declaration.getStart(sourceFile));
3201
+ context.report({
3202
+ loc: { line: line + 1, column: 0 },
3203
+ message: `Component "${component.name}" has inline hook logic with an extraction score of ${String(extractionScore)}; extract that logic to a custom hook in a hooks/ folder. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3204
+ });
3205
+ }
3206
+ }
3135
3207
  function reportHooks() {
3136
3208
  const parentFolder = segments.length >= 2 ? segments[segments.length - 2] : void 0;
3137
3209
  const inSupportFolder = parentFolder === "hooks";
3138
3210
  for (const { node, name, body, exported } of hooks) {
3139
- const score = computeExtractionScore(body, sourceText);
3211
+ const extractionScore = computeExtractionScore(body, sourceText);
3140
3212
  const hasLocalConsumer = localHookCalls.has(name);
3141
3213
  const externalConsumerCount = exported ? index?.symbolConsumers.get(symbolKey(filename, name))?.size ?? 0 : 0;
3142
- if (score >= 2) {
3214
+ if (extractionScore >= 2) {
3143
3215
  if (!inSupportFolder && (exported || hasLocalConsumer)) {
3144
3216
  context.report({
3145
3217
  node,
3146
- message: `Hook "${name}" has an extraction score of ${String(score)} and must be extracted to a hooks/ folder. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3218
+ message: `Hook "${name}" has an extraction score of ${String(extractionScore)} and must be extracted to a hooks/ folder. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3147
3219
  });
3148
3220
  }
3149
3221
  continue;
@@ -3189,6 +3261,7 @@ var hookComplexityRule = {
3189
3261
  },
3190
3262
  "Program:exit"() {
3191
3263
  reportHooks();
3264
+ reportInlineComponentHooks();
3192
3265
  }
3193
3266
  };
3194
3267
  }
@@ -5308,6 +5381,27 @@ function hasHookUsage(body) {
5308
5381
  }
5309
5382
  return false;
5310
5383
  }
5384
+ function enclosingComponentFunction(node) {
5385
+ let current = node.parent;
5386
+ while (current) {
5387
+ if (current.type === "FunctionDeclaration") {
5388
+ const name = current.id.name;
5389
+ return name && isComponentLikeName(name) ? current : void 0;
5390
+ }
5391
+ if (current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") {
5392
+ const declarator = current.parent;
5393
+ if (declarator.type !== "VariableDeclarator" || declarator.id.type !== "Identifier") return void 0;
5394
+ return isComponentLikeName(declarator.id.name) ? current : void 0;
5395
+ }
5396
+ current = current.parent;
5397
+ }
5398
+ return void 0;
5399
+ }
5400
+ function capturesComponentLocal(context, helper, component) {
5401
+ const helperScope = context.sourceCode.getScope(helper);
5402
+ const componentScope = context.sourceCode.getScope(component);
5403
+ return helperScope.through.some((reference) => reference.resolved?.scope === componentScope);
5404
+ }
5311
5405
  var pureFunctionExtractRule = {
5312
5406
  meta: {
5313
5407
  schema: [],
@@ -5337,25 +5431,27 @@ var pureFunctionExtractRule = {
5337
5431
  }
5338
5432
  return {
5339
5433
  FunctionDeclaration(node) {
5340
- const exported = node.parent?.type === "ExportNamedDeclaration";
5341
- const moduleLevel = exported || node.parent?.type === "Program";
5342
- if (!moduleLevel) return;
5343
5434
  const name = node.id?.name;
5344
5435
  if (!name) return;
5345
5436
  if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
5346
5437
  if (isComponentLikeName(name) || isHookName3(name)) return;
5347
5438
  if (isHookSupportFile && node.async) return;
5348
5439
  if (!node.body || hasHookUsage(node.body)) return;
5440
+ const exported = node.parent?.type === "ExportNamedDeclaration";
5441
+ const moduleLevel = exported || node.parent?.type === "Program";
5442
+ if (moduleLevel) {
5443
+ report3(node, name);
5444
+ return;
5445
+ }
5446
+ if (!isComponentFile) return;
5447
+ const component = enclosingComponentFunction(node);
5448
+ if (!component || capturesComponentLocal(context, node, component)) return;
5349
5449
  report3(node, name);
5350
5450
  },
5351
5451
  VariableDeclarator(node) {
5352
5452
  if (node.id.type !== "Identifier") return;
5353
5453
  const name = node.id.name;
5354
5454
  if (!name) return;
5355
- const container = node.parent.parent;
5356
- const exported = container?.type === "ExportNamedDeclaration";
5357
- const moduleLevel = exported || container?.type === "Program";
5358
- if (!moduleLevel) return;
5359
5455
  if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
5360
5456
  if (isComponentLikeName(name) || isHookName3(name)) return;
5361
5457
  const init = node.init;
@@ -5364,6 +5460,16 @@ var pureFunctionExtractRule = {
5364
5460
  }
5365
5461
  if (isHookSupportFile && init.async) return;
5366
5462
  if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
5463
+ const container = node.parent.parent;
5464
+ const exported = container?.type === "ExportNamedDeclaration";
5465
+ const moduleLevel = exported || container?.type === "Program";
5466
+ if (moduleLevel) {
5467
+ report3(node, name);
5468
+ return;
5469
+ }
5470
+ if (!isComponentFile) return;
5471
+ const component = enclosingComponentFunction(node);
5472
+ if (!component || capturesComponentLocal(context, init, component)) return;
5367
5473
  report3(node, name);
5368
5474
  }
5369
5475
  };
@@ -5443,6 +5549,26 @@ function structuralChildren(node) {
5443
5549
  (child) => isJsxNode(child) || isJsxExpressionContainer(child)
5444
5550
  );
5445
5551
  }
5552
+ function branchRootSignature(node) {
5553
+ const children = blockChildren(node);
5554
+ if (children.length < 2) return void 0;
5555
+ return children.map((child) => {
5556
+ const opening = nodeKind(child) === "JSXFragment" ? "<>" : child.openingElement?.name.name ?? "<>";
5557
+ const attributes = (child.openingElement?.attributes ?? []).filter((attribute) => attribute.type === "JSXAttribute").map((attribute) => attribute.name?.name ?? "").sort().join(",");
5558
+ return `${opening}[${attributes}]`;
5559
+ }).join("/");
5560
+ }
5561
+ function isConditionalAssignmentRoot(node) {
5562
+ let current = node;
5563
+ let parent = current.parent;
5564
+ while (parent) {
5565
+ const parentType = parent.type;
5566
+ if (parentType !== "ParenthesizedExpression") break;
5567
+ current = parent;
5568
+ parent = current.parent;
5569
+ }
5570
+ return parent?.type === "AssignmentExpression" || parent?.type === "ConditionalExpression";
5571
+ }
5446
5572
  var repeatedStructureRule = {
5447
5573
  meta: {
5448
5574
  schema: [],
@@ -5453,18 +5579,37 @@ var repeatedStructureRule = {
5453
5579
  },
5454
5580
  create(context) {
5455
5581
  if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
5582
+ const conditionalAssignmentBlocks = /* @__PURE__ */ new Map();
5456
5583
  return {
5457
5584
  JSXElement(node) {
5458
5585
  checkChildren(node);
5586
+ collectConditionalAssignmentBlock(node);
5459
5587
  },
5460
5588
  JSXFragment(node) {
5461
5589
  checkChildren(node);
5590
+ collectConditionalAssignmentBlock(node);
5591
+ },
5592
+ "Program:exit"() {
5593
+ for (const group of conditionalAssignmentBlocks.values()) {
5594
+ if (group.length < 2) continue;
5595
+ const first = group[0];
5596
+ if (!first) continue;
5597
+ reportRepeatedStructure(first, group.length);
5598
+ }
5462
5599
  }
5463
5600
  };
5464
5601
  function checkChildren(node) {
5465
5602
  checkSubstantialBlocks(node);
5466
5603
  checkSmallElementExpressionPairs(node);
5467
5604
  }
5605
+ function collectConditionalAssignmentBlock(node) {
5606
+ if (!isConditionalAssignmentRoot(node) || !isSubstantial(node)) return;
5607
+ const sig = branchRootSignature(node);
5608
+ if (!sig) return;
5609
+ const group = conditionalAssignmentBlocks.get(sig) ?? [];
5610
+ group.push(node);
5611
+ conditionalAssignmentBlocks.set(sig, group);
5612
+ }
5468
5613
  function checkSubstantialBlocks(node) {
5469
5614
  const children = blockChildren(node);
5470
5615
  const seen = /* @__PURE__ */ new Map();
@@ -8155,6 +8300,7 @@ function assertTypescriptAlignment() {
8155
8300
  // eslint/index.ts
8156
8301
  var pasikaNextjsAppRules = {
8157
8302
  // Framework-agnostic TypeScript rules.
8303
+ "file-length": fileLengthRule,
8158
8304
  "filename-case": filenameCaseRule,
8159
8305
  "import-boundaries": importBoundariesRule,
8160
8306
  "import-spacing": importSpacingRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.10.48",
3
+ "version": "0.10.49",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",