pasika 0.10.48 → 0.10.50
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.
- package/dist/eslint/pasika/index.d.ts +1 -0
- package/dist/eslint/pasika/index.js +176 -15
- package/package.json +1 -1
|
@@ -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
|
-
|
|
3111
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
}
|
|
@@ -4685,6 +4758,15 @@ import path37 from "path";
|
|
|
4685
4758
|
var DOC3 = "See docs/next-codebase-guide/rules/redundant-aliases-rule.md";
|
|
4686
4759
|
var VALUE_SENTINELS = /* @__PURE__ */ new Set(["undefined", "NaN", "Infinity"]);
|
|
4687
4760
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
4761
|
+
var PRIMITIVE_TYPE_NAMES = {
|
|
4762
|
+
TSBigIntKeyword: "bigint",
|
|
4763
|
+
TSBooleanKeyword: "boolean",
|
|
4764
|
+
TSNullKeyword: "null",
|
|
4765
|
+
TSNumberKeyword: "number",
|
|
4766
|
+
TSStringKeyword: "string",
|
|
4767
|
+
TSSymbolKeyword: "symbol",
|
|
4768
|
+
TSUndefinedKeyword: "undefined"
|
|
4769
|
+
};
|
|
4688
4770
|
function entityNameText(name) {
|
|
4689
4771
|
if (name.type === "Identifier") return name.name;
|
|
4690
4772
|
if (name.type === "TSQualifiedName") {
|
|
@@ -4696,6 +4778,12 @@ function entityNameText(name) {
|
|
|
4696
4778
|
const property = name.property.name;
|
|
4697
4779
|
return object && property ? `${object}.${property}` : void 0;
|
|
4698
4780
|
}
|
|
4781
|
+
function aliasedTypeName(annotation) {
|
|
4782
|
+
const primitiveTypeName = PRIMITIVE_TYPE_NAMES[annotation.type];
|
|
4783
|
+
if (primitiveTypeName) return primitiveTypeName;
|
|
4784
|
+
if (annotation.type !== "TSTypeReference" || annotation.typeArguments || !annotation.typeName) return void 0;
|
|
4785
|
+
return entityNameText(annotation.typeName);
|
|
4786
|
+
}
|
|
4699
4787
|
function aliasMessage(alias, source) {
|
|
4700
4788
|
const aliasName = alias ?? "This declaration";
|
|
4701
4789
|
const sourceName = source ?? "the original symbol";
|
|
@@ -4726,8 +4814,8 @@ var noRedundantAliasRule = {
|
|
|
4726
4814
|
TSTypeAliasDeclaration(node) {
|
|
4727
4815
|
if (node.typeParameters) return;
|
|
4728
4816
|
const annotation = node.typeAnnotation;
|
|
4729
|
-
if (annotation
|
|
4730
|
-
const source =
|
|
4817
|
+
if (!annotation) return;
|
|
4818
|
+
const source = aliasedTypeName(annotation);
|
|
4731
4819
|
if (!source) return;
|
|
4732
4820
|
context.report({
|
|
4733
4821
|
node,
|
|
@@ -5308,6 +5396,27 @@ function hasHookUsage(body) {
|
|
|
5308
5396
|
}
|
|
5309
5397
|
return false;
|
|
5310
5398
|
}
|
|
5399
|
+
function enclosingComponentFunction(node) {
|
|
5400
|
+
let current = node.parent;
|
|
5401
|
+
while (current) {
|
|
5402
|
+
if (current.type === "FunctionDeclaration") {
|
|
5403
|
+
const name = current.id.name;
|
|
5404
|
+
return name && isComponentLikeName(name) ? current : void 0;
|
|
5405
|
+
}
|
|
5406
|
+
if (current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") {
|
|
5407
|
+
const declarator = current.parent;
|
|
5408
|
+
if (declarator.type !== "VariableDeclarator" || declarator.id.type !== "Identifier") return void 0;
|
|
5409
|
+
return isComponentLikeName(declarator.id.name) ? current : void 0;
|
|
5410
|
+
}
|
|
5411
|
+
current = current.parent;
|
|
5412
|
+
}
|
|
5413
|
+
return void 0;
|
|
5414
|
+
}
|
|
5415
|
+
function capturesComponentLocal(context, helper, component) {
|
|
5416
|
+
const helperScope = context.sourceCode.getScope(helper);
|
|
5417
|
+
const componentScope = context.sourceCode.getScope(component);
|
|
5418
|
+
return helperScope.through.some((reference) => reference.resolved?.scope === componentScope);
|
|
5419
|
+
}
|
|
5311
5420
|
var pureFunctionExtractRule = {
|
|
5312
5421
|
meta: {
|
|
5313
5422
|
schema: [],
|
|
@@ -5337,25 +5446,27 @@ var pureFunctionExtractRule = {
|
|
|
5337
5446
|
}
|
|
5338
5447
|
return {
|
|
5339
5448
|
FunctionDeclaration(node) {
|
|
5340
|
-
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
5341
|
-
const moduleLevel = exported || node.parent?.type === "Program";
|
|
5342
|
-
if (!moduleLevel) return;
|
|
5343
5449
|
const name = node.id?.name;
|
|
5344
5450
|
if (!name) return;
|
|
5345
5451
|
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
5346
5452
|
if (isComponentLikeName(name) || isHookName3(name)) return;
|
|
5347
5453
|
if (isHookSupportFile && node.async) return;
|
|
5348
5454
|
if (!node.body || hasHookUsage(node.body)) return;
|
|
5455
|
+
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
5456
|
+
const moduleLevel = exported || node.parent?.type === "Program";
|
|
5457
|
+
if (moduleLevel) {
|
|
5458
|
+
report3(node, name);
|
|
5459
|
+
return;
|
|
5460
|
+
}
|
|
5461
|
+
if (!isComponentFile) return;
|
|
5462
|
+
const component = enclosingComponentFunction(node);
|
|
5463
|
+
if (!component || capturesComponentLocal(context, node, component)) return;
|
|
5349
5464
|
report3(node, name);
|
|
5350
5465
|
},
|
|
5351
5466
|
VariableDeclarator(node) {
|
|
5352
5467
|
if (node.id.type !== "Identifier") return;
|
|
5353
5468
|
const name = node.id.name;
|
|
5354
5469
|
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
5470
|
if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
|
|
5360
5471
|
if (isComponentLikeName(name) || isHookName3(name)) return;
|
|
5361
5472
|
const init = node.init;
|
|
@@ -5364,6 +5475,16 @@ var pureFunctionExtractRule = {
|
|
|
5364
5475
|
}
|
|
5365
5476
|
if (isHookSupportFile && init.async) return;
|
|
5366
5477
|
if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
|
|
5478
|
+
const container = node.parent.parent;
|
|
5479
|
+
const exported = container?.type === "ExportNamedDeclaration";
|
|
5480
|
+
const moduleLevel = exported || container?.type === "Program";
|
|
5481
|
+
if (moduleLevel) {
|
|
5482
|
+
report3(node, name);
|
|
5483
|
+
return;
|
|
5484
|
+
}
|
|
5485
|
+
if (!isComponentFile) return;
|
|
5486
|
+
const component = enclosingComponentFunction(node);
|
|
5487
|
+
if (!component || capturesComponentLocal(context, init, component)) return;
|
|
5367
5488
|
report3(node, name);
|
|
5368
5489
|
}
|
|
5369
5490
|
};
|
|
@@ -5443,6 +5564,26 @@ function structuralChildren(node) {
|
|
|
5443
5564
|
(child) => isJsxNode(child) || isJsxExpressionContainer(child)
|
|
5444
5565
|
);
|
|
5445
5566
|
}
|
|
5567
|
+
function branchRootSignature(node) {
|
|
5568
|
+
const children = blockChildren(node);
|
|
5569
|
+
if (children.length < 2) return void 0;
|
|
5570
|
+
return children.map((child) => {
|
|
5571
|
+
const opening = nodeKind(child) === "JSXFragment" ? "<>" : child.openingElement?.name.name ?? "<>";
|
|
5572
|
+
const attributes = (child.openingElement?.attributes ?? []).filter((attribute) => attribute.type === "JSXAttribute").map((attribute) => attribute.name?.name ?? "").sort().join(",");
|
|
5573
|
+
return `${opening}[${attributes}]`;
|
|
5574
|
+
}).join("/");
|
|
5575
|
+
}
|
|
5576
|
+
function isConditionalAssignmentRoot(node) {
|
|
5577
|
+
let current = node;
|
|
5578
|
+
let parent = current.parent;
|
|
5579
|
+
while (parent) {
|
|
5580
|
+
const parentType = parent.type;
|
|
5581
|
+
if (parentType !== "ParenthesizedExpression") break;
|
|
5582
|
+
current = parent;
|
|
5583
|
+
parent = current.parent;
|
|
5584
|
+
}
|
|
5585
|
+
return parent?.type === "AssignmentExpression" || parent?.type === "ConditionalExpression";
|
|
5586
|
+
}
|
|
5446
5587
|
var repeatedStructureRule = {
|
|
5447
5588
|
meta: {
|
|
5448
5589
|
schema: [],
|
|
@@ -5453,18 +5594,37 @@ var repeatedStructureRule = {
|
|
|
5453
5594
|
},
|
|
5454
5595
|
create(context) {
|
|
5455
5596
|
if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
|
|
5597
|
+
const conditionalAssignmentBlocks = /* @__PURE__ */ new Map();
|
|
5456
5598
|
return {
|
|
5457
5599
|
JSXElement(node) {
|
|
5458
5600
|
checkChildren(node);
|
|
5601
|
+
collectConditionalAssignmentBlock(node);
|
|
5459
5602
|
},
|
|
5460
5603
|
JSXFragment(node) {
|
|
5461
5604
|
checkChildren(node);
|
|
5605
|
+
collectConditionalAssignmentBlock(node);
|
|
5606
|
+
},
|
|
5607
|
+
"Program:exit"() {
|
|
5608
|
+
for (const group of conditionalAssignmentBlocks.values()) {
|
|
5609
|
+
if (group.length < 2) continue;
|
|
5610
|
+
const first = group[0];
|
|
5611
|
+
if (!first) continue;
|
|
5612
|
+
reportRepeatedStructure(first, group.length);
|
|
5613
|
+
}
|
|
5462
5614
|
}
|
|
5463
5615
|
};
|
|
5464
5616
|
function checkChildren(node) {
|
|
5465
5617
|
checkSubstantialBlocks(node);
|
|
5466
5618
|
checkSmallElementExpressionPairs(node);
|
|
5467
5619
|
}
|
|
5620
|
+
function collectConditionalAssignmentBlock(node) {
|
|
5621
|
+
if (!isConditionalAssignmentRoot(node) || !isSubstantial(node)) return;
|
|
5622
|
+
const sig = branchRootSignature(node);
|
|
5623
|
+
if (!sig) return;
|
|
5624
|
+
const group = conditionalAssignmentBlocks.get(sig) ?? [];
|
|
5625
|
+
group.push(node);
|
|
5626
|
+
conditionalAssignmentBlocks.set(sig, group);
|
|
5627
|
+
}
|
|
5468
5628
|
function checkSubstantialBlocks(node) {
|
|
5469
5629
|
const children = blockChildren(node);
|
|
5470
5630
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -8155,6 +8315,7 @@ function assertTypescriptAlignment() {
|
|
|
8155
8315
|
// eslint/index.ts
|
|
8156
8316
|
var pasikaNextjsAppRules = {
|
|
8157
8317
|
// Framework-agnostic TypeScript rules.
|
|
8318
|
+
"file-length": fileLengthRule,
|
|
8158
8319
|
"filename-case": filenameCaseRule,
|
|
8159
8320
|
"import-boundaries": importBoundariesRule,
|
|
8160
8321
|
"import-spacing": importSpacingRule,
|