@slip-stream-kit/eslint-plugin 0.1.13 → 0.1.17
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/index.js +142 -28
- package/dist/index.js.map +4 -4
- package/dist/rules/props-type-name.d.ts +3 -0
- package/dist/utils/component.d.ts +16 -0
- package/package.json +1 -1
- package/readme.md +61 -9
package/dist/index.js
CHANGED
|
@@ -113,6 +113,39 @@ var getComponentFunction = (node) => {
|
|
|
113
113
|
}
|
|
114
114
|
return null;
|
|
115
115
|
};
|
|
116
|
+
var getAnnotatedParam = (param) => {
|
|
117
|
+
return param.type === "AssignmentPattern" ? param.left : param;
|
|
118
|
+
};
|
|
119
|
+
var getPropsTypeNameFromFunction = (node) => {
|
|
120
|
+
const firstParam = node.params[0];
|
|
121
|
+
if (!firstParam) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const inner = getAnnotatedParam(firstParam).typeAnnotation?.typeAnnotation;
|
|
125
|
+
if (inner?.type !== "TSTypeReference" || inner.typeName?.type !== "Identifier") {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return inner.typeName.name ?? null;
|
|
129
|
+
};
|
|
130
|
+
var getComponentPropsTypeName = (node) => {
|
|
131
|
+
if (!node) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (node.type === "FunctionDeclaration") {
|
|
135
|
+
return isComponent(node) ? getPropsTypeNameFromFunction(node) : null;
|
|
136
|
+
}
|
|
137
|
+
if (node.type === "VariableDeclaration") {
|
|
138
|
+
for (const declaration of node.declarations) {
|
|
139
|
+
const fn2 = getComponentFunction(declaration.init);
|
|
140
|
+
if (fn2 && isComponent(fn2)) {
|
|
141
|
+
return getPropsTypeNameFromFunction(fn2);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const fn = getComponentFunction(node);
|
|
147
|
+
return fn && isComponent(fn) ? getPropsTypeNameFromFunction(fn) : null;
|
|
148
|
+
};
|
|
116
149
|
var unwrapExport = (statement) => {
|
|
117
150
|
if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") {
|
|
118
151
|
return statement.declaration ?? null;
|
|
@@ -297,7 +330,7 @@ var getDirectivePrologueCount = (body) => {
|
|
|
297
330
|
}
|
|
298
331
|
return count;
|
|
299
332
|
};
|
|
300
|
-
var
|
|
333
|
+
var getTypeDeclName = (node) => {
|
|
301
334
|
if (!node) {
|
|
302
335
|
return null;
|
|
303
336
|
}
|
|
@@ -305,13 +338,12 @@ var getPropsTypeName = (node) => {
|
|
|
305
338
|
if (named.type !== "TSInterfaceDeclaration" && named.type !== "TSTypeAliasDeclaration") {
|
|
306
339
|
return null;
|
|
307
340
|
}
|
|
308
|
-
|
|
309
|
-
return name?.endsWith(PROPS_SUFFIX) ? name : null;
|
|
341
|
+
return named.id?.name ?? null;
|
|
310
342
|
};
|
|
311
343
|
var collectTopLevel = (body) => {
|
|
312
344
|
const importIndices = [];
|
|
313
345
|
const components = [];
|
|
314
|
-
const
|
|
346
|
+
const declIndexByName = /* @__PURE__ */ new Map();
|
|
315
347
|
const importedNames = /* @__PURE__ */ new Set();
|
|
316
348
|
body.forEach((statement, index) => {
|
|
317
349
|
if (statement.type === "ImportDeclaration") {
|
|
@@ -322,15 +354,17 @@ var collectTopLevel = (body) => {
|
|
|
322
354
|
return;
|
|
323
355
|
}
|
|
324
356
|
const declaration = unwrapExport(statement);
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
|
|
357
|
+
const declName = getTypeDeclName(declaration);
|
|
358
|
+
if (declName !== null && !declIndexByName.has(declName)) {
|
|
359
|
+
declIndexByName.set(declName, index);
|
|
328
360
|
}
|
|
329
361
|
if (declaresComponent(declaration)) {
|
|
330
|
-
|
|
362
|
+
const name = getDeclaredComponentName(declaration);
|
|
363
|
+
const propsName = getComponentPropsTypeName(declaration) ?? (name === null ? null : `${name}${PROPS_SUFFIX}`);
|
|
364
|
+
components.push({ index, name, propsName });
|
|
331
365
|
}
|
|
332
366
|
});
|
|
333
|
-
return { importIndices, components,
|
|
367
|
+
return { importIndices, components, declIndexByName, importedNames };
|
|
334
368
|
};
|
|
335
369
|
var hasStrayBefore = (body, boundary, directiveCount, skipIndex) => {
|
|
336
370
|
return body.some((statement, index) => {
|
|
@@ -344,13 +378,20 @@ var findImportOrderViolations = (importIndices, importBoundary) => {
|
|
|
344
378
|
return { index: importIndex, messageId: "importsFirst" };
|
|
345
379
|
});
|
|
346
380
|
};
|
|
347
|
-
var findAdjacencyViolations = (components,
|
|
381
|
+
var findAdjacencyViolations = (components, declIndexByName) => {
|
|
348
382
|
const violations = [];
|
|
383
|
+
const referenceCount = /* @__PURE__ */ new Map();
|
|
349
384
|
for (const component of components) {
|
|
350
|
-
if (component.
|
|
385
|
+
if (component.propsName !== null) {
|
|
386
|
+
referenceCount.set(component.propsName, (referenceCount.get(component.propsName) ?? 0) + 1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const component of components) {
|
|
390
|
+
const propsName = component.propsName;
|
|
391
|
+
if (propsName === null || (referenceCount.get(propsName) ?? 0) > 1) {
|
|
351
392
|
continue;
|
|
352
393
|
}
|
|
353
|
-
const propsIndex =
|
|
394
|
+
const propsIndex = declIndexByName.get(propsName);
|
|
354
395
|
if (propsIndex !== void 0 && propsIndex !== component.index - 1) {
|
|
355
396
|
violations.push({ index: propsIndex, messageId: "interfaceImmediatelyBeforeComponent" });
|
|
356
397
|
}
|
|
@@ -421,17 +462,17 @@ var componentFileOrder = {
|
|
|
421
462
|
Program(program) {
|
|
422
463
|
const body = program.body;
|
|
423
464
|
const directiveCount = getDirectivePrologueCount(body);
|
|
424
|
-
const { importIndices, components,
|
|
465
|
+
const { importIndices, components, declIndexByName, importedNames } = collectTopLevel(body);
|
|
425
466
|
if (components.length === 0) {
|
|
426
467
|
return;
|
|
427
468
|
}
|
|
428
469
|
const first = components[0];
|
|
429
|
-
const firstPropsName = first.
|
|
430
|
-
const firstPropsIndex = firstPropsName === null ? void 0 :
|
|
470
|
+
const firstPropsName = first.propsName;
|
|
471
|
+
const firstPropsIndex = firstPropsName === null ? void 0 : declIndexByName.get(firstPropsName);
|
|
431
472
|
const importBoundary = Math.min(first.index, firstPropsIndex ?? first.index);
|
|
432
473
|
const violations = [
|
|
433
474
|
...findImportOrderViolations(importIndices, importBoundary),
|
|
434
|
-
...findAdjacencyViolations(components,
|
|
475
|
+
...findAdjacencyViolations(components, declIndexByName),
|
|
435
476
|
...findAnchorViolations(
|
|
436
477
|
body,
|
|
437
478
|
directiveCount,
|
|
@@ -567,14 +608,12 @@ var propsDestructuringNewline = {
|
|
|
567
608
|
}
|
|
568
609
|
const boundNames = /* @__PURE__ */ new Set();
|
|
569
610
|
collectBoundNames(firstParam, boundNames);
|
|
570
|
-
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
611
|
+
const bindsProps = boundNames.has("props");
|
|
573
612
|
const objectPattern = firstParam;
|
|
574
613
|
context.report({
|
|
575
614
|
node: firstParam,
|
|
576
615
|
messageId: "destructureOnNewLine",
|
|
577
|
-
fix(fixer) {
|
|
616
|
+
fix: bindsProps ? void 0 : (fixer) => {
|
|
578
617
|
const text = sourceCode.getText();
|
|
579
618
|
const annotation = objectPattern.typeAnnotation;
|
|
580
619
|
const patternStart = objectPattern.range[0];
|
|
@@ -626,11 +665,80 @@ ${baseIndent}}`
|
|
|
626
665
|
}
|
|
627
666
|
};
|
|
628
667
|
|
|
668
|
+
// src/rules/props-type-name.ts
|
|
669
|
+
var PROPS_SUFFIX2 = "Props";
|
|
670
|
+
var propsTypeName = {
|
|
671
|
+
meta: {
|
|
672
|
+
type: "suggestion",
|
|
673
|
+
docs: {
|
|
674
|
+
description: "Require a React component's props type to be named `<ComponentName>Props` (e.g. `ButtonProps` for `Button`).",
|
|
675
|
+
recommended: true,
|
|
676
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin"
|
|
677
|
+
},
|
|
678
|
+
schema: [
|
|
679
|
+
{
|
|
680
|
+
type: "object",
|
|
681
|
+
properties: {
|
|
682
|
+
paths: {
|
|
683
|
+
type: "array",
|
|
684
|
+
items: { type: "string" },
|
|
685
|
+
description: "Optional glob patterns. When provided, the rule only runs for files whose path matches one of them."
|
|
686
|
+
},
|
|
687
|
+
ignore: {
|
|
688
|
+
type: "array",
|
|
689
|
+
items: { type: "string" },
|
|
690
|
+
description: "Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`."
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
additionalProperties: false
|
|
694
|
+
}
|
|
695
|
+
],
|
|
696
|
+
messages: {
|
|
697
|
+
propsTypeNameMismatch: "A component's props type must be named `{{expected}}`, but it is named `{{actual}}`."
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
create(context) {
|
|
701
|
+
const options = context.options[0] ?? {};
|
|
702
|
+
const paths = options.paths ?? [];
|
|
703
|
+
const ignore = options.ignore ?? [];
|
|
704
|
+
if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {
|
|
705
|
+
return {};
|
|
706
|
+
}
|
|
707
|
+
if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {
|
|
708
|
+
return {};
|
|
709
|
+
}
|
|
710
|
+
const check = (node) => {
|
|
711
|
+
if (!isComponent(node)) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const componentName = getComponentName(node);
|
|
715
|
+
if (componentName === null) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const actual = getPropsTypeNameFromFunction(node);
|
|
719
|
+
if (actual === null) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const expected = `${componentName}${PROPS_SUFFIX2}`;
|
|
723
|
+
if (actual === expected) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
context.report({
|
|
727
|
+
node: getAnnotatedParam(node.params[0]),
|
|
728
|
+
messageId: "propsTypeNameMismatch",
|
|
729
|
+
data: { expected, actual }
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
return {
|
|
733
|
+
ArrowFunctionExpression: check,
|
|
734
|
+
FunctionDeclaration: check,
|
|
735
|
+
FunctionExpression: check
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
|
|
629
740
|
// src/rules/props-type-reference.ts
|
|
630
741
|
var INLINE_OBJECT_TYPE = "TSTypeLiteral";
|
|
631
|
-
var getAnnotatedParam = (param) => {
|
|
632
|
-
return param.type === "AssignmentPattern" ? param.left : param;
|
|
633
|
-
};
|
|
634
742
|
var propsTypeReference = {
|
|
635
743
|
meta: {
|
|
636
744
|
type: "suggestion",
|
|
@@ -901,6 +1009,7 @@ var rules = {
|
|
|
901
1009
|
"props-destructuring-newline": propsDestructuringNewline,
|
|
902
1010
|
"props-destructuring-blank-line": propsDestructuringBlankLine,
|
|
903
1011
|
"props-type-reference": propsTypeReference,
|
|
1012
|
+
"props-type-name": propsTypeName,
|
|
904
1013
|
"component-file-order": componentFileOrder,
|
|
905
1014
|
"component-arrow-function": componentArrowFunction,
|
|
906
1015
|
"require-component-stories": requireComponentStories
|
|
@@ -911,7 +1020,7 @@ var PLUGIN_NAME = "@wl";
|
|
|
911
1020
|
var plugin = {
|
|
912
1021
|
meta: {
|
|
913
1022
|
name: "@wl/eslint-plugin",
|
|
914
|
-
version: "0.1.
|
|
1023
|
+
version: "0.1.14"
|
|
915
1024
|
},
|
|
916
1025
|
rules,
|
|
917
1026
|
configs: {}
|
|
@@ -926,6 +1035,7 @@ plugin.configs.recommended = [
|
|
|
926
1035
|
[`${PLUGIN_NAME}/props-destructuring-newline`]: "error",
|
|
927
1036
|
[`${PLUGIN_NAME}/props-destructuring-blank-line`]: "error",
|
|
928
1037
|
[`${PLUGIN_NAME}/props-type-reference`]: "error",
|
|
1038
|
+
[`${PLUGIN_NAME}/props-type-name`]: "error",
|
|
929
1039
|
[`${PLUGIN_NAME}/component-file-order`]: "error",
|
|
930
1040
|
// Pages and routes are excluded: route/page modules commonly use `function`
|
|
931
1041
|
// declarations (and framework conventions like default-exported page functions).
|
|
@@ -933,13 +1043,17 @@ plugin.configs.recommended = [
|
|
|
933
1043
|
[`${PLUGIN_NAME}/require-component-stories`]: "error"
|
|
934
1044
|
}
|
|
935
1045
|
},
|
|
936
|
-
// Storybook stories legitimately deviate from the
|
|
937
|
-
// order (meta/args/decorators/render fns),
|
|
938
|
-
//
|
|
1046
|
+
// Storybook stories legitimately deviate from the component conventions: the
|
|
1047
|
+
// imports → *Props → component order (meta/args/decorators/render fns), and
|
|
1048
|
+
// named templates that reference the *component's* props type (e.g.
|
|
1049
|
+
// `const Template = (args: ButtonProps) => ...`) rather than their own
|
|
1050
|
+
// `<TemplateName>Props`. So both the ordering and the props-type-name rules
|
|
1051
|
+
// would only produce noise there. Every other rule stays enabled for stories.
|
|
939
1052
|
{
|
|
940
1053
|
files: ["**/*.stories.{ts,tsx}"],
|
|
941
1054
|
rules: {
|
|
942
|
-
[`${PLUGIN_NAME}/component-file-order`]: "off"
|
|
1055
|
+
[`${PLUGIN_NAME}/component-file-order`]: "off",
|
|
1056
|
+
[`${PLUGIN_NAME}/props-type-name`]: "off"
|
|
943
1057
|
}
|
|
944
1058
|
}
|
|
945
1059
|
];
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/utils/component.ts", "../src/utils/path-match.ts", "../src/rules/component-arrow-function.ts", "../src/rules/component-file-order.ts", "../src/rules/props-destructuring-blank-line.ts", "../src/rules/props-destructuring-newline.ts", "../src/rules/props-type-reference.ts", "../src/rules/require-component-stories.ts", "../src/utils/story-path.ts", "../src/rules/index.ts", "../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type * as ESTree from 'estree'\n\nexport type ComponentFunction = ESTree.ArrowFunctionExpression | ESTree.FunctionDeclaration | ESTree.FunctionExpression\n\n// Minimal structural view over the `parent` back-reference ESLint adds to every node.\ninterface WithParent {\n parent?: ESTree.Node\n}\n\n// Calls that wrap a component while preserving its identity (memo, forwardRef, observer, ...).\nconst COMPONENT_WRAPPER_CALLEES = new Set(['memo', 'forwardRef', 'observer', 'React.memo', 'React.forwardRef'])\n\n// Node types that introduce a new function scope \u2014 their returns are not the outer component's.\nconst NESTED_SCOPES = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'])\n\nconst isPascalCase = (name: string): boolean => {\n return /^[A-Z]/.test(name)\n}\n\nconst isJsxNode = (node: ESTree.Node | null | undefined): boolean => {\n if (!node) {\n return false\n }\n\n const type = node.type as string\n\n return type === 'JSXElement' || type === 'JSXFragment'\n}\n\nconst getParent = (node: ESTree.Node | undefined): ESTree.Node | undefined => {\n return (node as (WithParent & ESTree.Node) | undefined)?.parent\n}\n\n/** Best-effort name of a call's callee: `memo` for `memo(...)`, `React.memo` for `React.memo(...)`. */\nconst getCalleeName = (callee: ESTree.CallExpression['callee']): string | null => {\n if (callee.type === 'Identifier') {\n return callee.name\n }\n\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.property.type === 'Identifier'\n ) {\n return `${callee.object.name}.${callee.property.name}`\n }\n\n return null\n}\n\n/**\n * Resolve the declared name of a function, looking through component wrappers\n * such as `memo`/`forwardRef` so that `const Comp = memo(({ a }) => ...)` is\n * still recognised by its PascalCase variable name.\n */\nexport const getComponentName = (node: ComponentFunction): string | null => {\n if (node.type === 'FunctionDeclaration') {\n return node.id?.name ?? null\n }\n\n let current = getParent(node)\n\n // Walk through wrapping call expressions (memo, forwardRef, React.memo, ...).\n while (current?.type === 'CallExpression') {\n const calleeName = getCalleeName(current.callee)\n\n if (!calleeName || !COMPONENT_WRAPPER_CALLEES.has(calleeName)) {\n break\n }\n\n current = getParent(current)\n }\n\n if (current?.type === 'VariableDeclarator' && current.id.type === 'Identifier') {\n return current.id.name\n }\n\n return null\n}\n\n/** Whether a function returns JSX, scanning its own body without descending into nested functions. */\nconst returnsJsx = (node: ComponentFunction): boolean => {\n if (node.body.type !== 'BlockStatement') {\n return isJsxNode(node.body)\n }\n\n let found = false\n\n const visit = (current: ESTree.Node | null | undefined): void => {\n if (found || !current || NESTED_SCOPES.has(current.type)) {\n return\n }\n\n if (current.type === 'ReturnStatement') {\n if (isJsxNode(current.argument)) {\n found = true\n }\n\n return\n }\n\n if (current.type === 'IfStatement') {\n visit(current.consequent)\n visit(current.alternate)\n\n return\n }\n\n if (current.type === 'BlockStatement') {\n current.body.forEach(visit)\n\n return\n }\n\n if (current.type === 'SwitchStatement') {\n for (const switchCase of current.cases) {\n switchCase.consequent.forEach(visit)\n }\n\n return\n }\n\n if (current.type === 'TryStatement') {\n visit(current.block)\n visit(current.handler?.body)\n visit(current.finalizer)\n\n return\n }\n\n if (\n current.type === 'ForStatement' ||\n current.type === 'ForInStatement' ||\n current.type === 'ForOfStatement' ||\n current.type === 'WhileStatement' ||\n current.type === 'DoWhileStatement'\n ) {\n visit(current.body)\n }\n }\n\n node.body.body.forEach(visit)\n\n return found\n}\n\n/** A function is treated as a React component when it is PascalCase-named or returns JSX. */\nexport const isComponent = (node: ComponentFunction): boolean => {\n const name = getComponentName(node)\n\n if (name && isPascalCase(name)) {\n return true\n }\n\n return returnsJsx(node)\n}\n\nconst isComponentFunctionNode = (node: ESTree.Node): node is ComponentFunction => {\n return (\n node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration'\n )\n}\n\n/**\n * Extract the component function from an expression, unwrapping a single layer of\n * component wrappers (`memo(fn)`, `forwardRef(fn)`, `React.memo(fn)`, ...). Returns\n * null when no function is found.\n */\nexport const getComponentFunction = (node: ESTree.Node | null | undefined): ComponentFunction | null => {\n if (!node) {\n return null\n }\n\n if (isComponentFunctionNode(node)) {\n return node\n }\n\n if (node.type === 'CallExpression') {\n for (const argument of node.arguments) {\n if (argument.type === 'SpreadElement') {\n continue\n }\n\n const found = getComponentFunction(argument)\n\n if (found) {\n return found\n }\n }\n }\n\n return null\n}\n\n/** Unwrap an `export ...` statement to the declaration it wraps (or the statement itself). */\nexport const unwrapExport = (statement: ESTree.Statement | ESTree.ModuleDeclaration): ESTree.Node | null => {\n if (statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration') {\n return (statement.declaration as ESTree.Node | null) ?? null\n }\n\n return statement\n}\n\n/** Whether a single top-level declaration declares a React component. */\nexport const declaresComponent = (node: ESTree.Node | null): boolean => {\n if (!node) {\n return false\n }\n\n if (node.type === 'FunctionDeclaration') {\n return isComponent(node)\n }\n\n if (node.type === 'VariableDeclaration') {\n return node.declarations.some((declaration) => {\n const fn = getComponentFunction(declaration.init)\n\n return fn ? isComponent(fn) : false\n })\n }\n\n const fn = getComponentFunction(node)\n\n return fn ? isComponent(fn) : false\n}\n\n/**\n * Resolve the name of the React component declared by a single top-level declaration,\n * or null when the declaration is not a component or the component is anonymous\n * (e.g. `export default () => <div />`). Mirrors `declaresComponent`'s node dispatch and\n * resolves the name through component wrappers (`memo`/`forwardRef`).\n */\nexport const getDeclaredComponentName = (node: ESTree.Node | null): string | null => {\n if (!node) {\n return null\n }\n\n if (node.type === 'FunctionDeclaration') {\n return isComponent(node) ? getComponentName(node) : null\n }\n\n if (node.type === 'VariableDeclaration') {\n for (const declaration of node.declarations) {\n const fn = getComponentFunction(declaration.init)\n\n if (fn && isComponent(fn)) {\n return getComponentName(fn)\n }\n }\n\n return null\n }\n\n const fn = getComponentFunction(node)\n\n return fn && isComponent(fn) ? getComponentName(fn) : null\n}\n\n/** Whether any top-level statement in a program body declares a React component. */\nexport const bodyDeclaresComponent = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): boolean => {\n return body.some((statement) => {\n return declaresComponent(unwrapExport(statement))\n })\n}\n", "// Characters that must be escaped when embedded literally into a RegExp source.\nconst REGEX_METACHARS = new Set(['\\\\', '^', '$', '.', '|', '+', '(', ')', '[', ']', '{', '}'])\n\n/**\n * Convert a glob pattern to an (unanchored) RegExp.\n *\n * - `**` matches any characters, including path separators.\n * - `*` matches any characters except a path separator.\n * - `?` matches a single non-separator character.\n *\n * The result is intentionally unanchored so a pattern matches anywhere in the\n * path (e.g. `features/**` matches `/repo/src/features/x/comp.tsx`).\n */\nconst globToRegExp = (glob: string): RegExp => {\n let source = ''\n\n for (let index = 0; index < glob.length; index++) {\n const char = glob[index]!\n\n if (char === '*') {\n if (glob[index + 1] === '*') {\n source += '.*'\n index++\n\n // Consume a trailing slash so `**/foo` also matches a bare `foo`.\n if (glob[index + 1] === '/') {\n index++\n }\n } else {\n source += '[^/]*'\n }\n } else if (char === '?') {\n source += '[^/]'\n } else if (REGEX_METACHARS.has(char)) {\n source += `\\\\${char}`\n } else {\n source += char\n }\n }\n\n return new RegExp(source)\n}\n\n/** Whether `filename` matches at least one of the provided glob `patterns`. */\nexport const matchesAnyGlob = (filename: string, patterns: readonly string[]): boolean => {\n const normalized = filename.split('\\\\').join('/')\n\n return patterns.some((pattern) => {\n return globToRegExp(pattern).test(normalized)\n })\n}\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport { getComponentFunction, getComponentName, isComponent, unwrapExport } from '../utils/component'\nimport type { ComponentFunction } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\n// Fallback used when a component is anonymous (e.g. `export default memo(function () { ... })`),\n// since `getComponentName` returns null rather than a placeholder in that case.\nconst ANONYMOUS_NAME = 'component'\n\ntype MessageId = 'functionDeclaration' | 'functionExpression'\n\ninterface Violation {\n node: ESTree.Node\n messageId: MessageId\n name: string\n}\n\nconst reportName = (fn: ComponentFunction): string => {\n return getComponentName(fn) ?? ANONYMOUS_NAME\n}\n\n/**\n * Violation for a single non-`VariableDeclaration` top-level declaration:\n * - a `function Foo() {}` component declaration (incl. exported/default/anonymous), or\n * - an expression that resolves to a function-expression component\n * (e.g. `export default memo(function () { ... })`).\n */\nconst nonVariableViolation = (declaration: ESTree.Node): Violation | null => {\n if (declaration.type === 'FunctionDeclaration') {\n return isComponent(declaration)\n ? { node: declaration, messageId: 'functionDeclaration', name: reportName(declaration) }\n : null\n }\n\n const fn = getComponentFunction(declaration)\n\n return fn?.type === 'FunctionExpression' && isComponent(fn)\n ? { node: fn, messageId: 'functionExpression', name: reportName(fn) }\n : null\n}\n\n/** Function-expression component violations across every declarator in a `const`/`let`/`var`. */\nconst variableViolations = (declaration: ESTree.VariableDeclaration): Violation[] => {\n const violations: Violation[] = []\n\n for (const declarator of declaration.declarations) {\n const fn = getComponentFunction(declarator.init)\n\n if (fn?.type === 'FunctionExpression' && isComponent(fn)) {\n violations.push({ node: declarator, messageId: 'functionExpression', name: reportName(fn) })\n }\n }\n\n return violations\n}\n\n/** Every non-arrow component declared at the top level of the program body. */\nconst collectViolations = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): Violation[] => {\n const violations: Violation[] = []\n\n for (const statement of body) {\n const declaration = unwrapExport(statement)\n\n if (!declaration) {\n continue\n }\n\n if (declaration.type === 'VariableDeclaration') {\n violations.push(...variableViolations(declaration))\n\n continue\n }\n\n const violation = nonVariableViolation(declaration)\n\n if (violation) {\n violations.push(violation)\n }\n }\n\n return violations\n}\n\nexport const componentArrowFunction: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Enforce that React components are declared as arrow functions, not `function` declarations or function expressions.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`. Use this to exclude pages and routes (e.g. `**/pages/**`, `**/routes/**`).',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n functionDeclaration:\n 'React components must be arrow functions; convert the `function {{name}}` declaration to `const {{name}} = () => { \u2026 }`.',\n functionExpression:\n 'React components must be arrow functions; replace the `function` expression for `{{name}}` with an arrow function.',\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n return {\n Program(program) {\n for (const { node, messageId, name } of collectViolations(program.body)) {\n context.report({ node, messageId, data: { name } })\n }\n },\n }\n },\n}\n\nexport default componentArrowFunction\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport { declaresComponent, getDeclaredComponentName, unwrapExport } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\n// TS-only nodes are not modelled by estree; access their identifier structurally.\ninterface NamedDeclaration {\n type: string\n id?: { name?: string } | null\n}\n\nconst PROPS_SUFFIX = 'Props'\n\n/**\n * Count the directive prologue \u2014 the leading run of string-literal expression statements\n * (`'use client'`, `'use server'`, `'use strict'`) at the top of the file. They are\n * legitimate file-leading content (like imports) and must not count as \"stray\" before the\n * props interface. Per the ECMAScript spec a directive is only one that *precedes* any other\n * statement, so we stop at the first non-string-literal statement rather than matching any\n * bare string anywhere in the body.\n */\nconst getDirectivePrologueCount = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): number => {\n let count = 0\n\n for (const statement of body) {\n const isStringExpression =\n statement.type === 'ExpressionStatement' &&\n statement.expression.type === 'Literal' &&\n typeof statement.expression.value === 'string'\n\n if (!isStringExpression) {\n break\n }\n\n count += 1\n }\n\n return count\n}\n\n/** The name of a props interface/type alias (`SomethingProps`), or null when it is neither. */\nconst getPropsTypeName = (node: ESTree.Node | null): string | null => {\n if (!node) {\n return null\n }\n\n const named = node as NamedDeclaration\n\n if (named.type !== 'TSInterfaceDeclaration' && named.type !== 'TSTypeAliasDeclaration') {\n return null\n }\n\n const name = named.id?.name\n\n return name?.endsWith(PROPS_SUFFIX) ? name : null\n}\n\ninterface ComponentRef {\n index: number\n name: string | null\n}\n\ninterface TopLevel {\n importIndices: number[]\n components: ComponentRef[]\n // First index of each `*Props` declaration, keyed by its name.\n propsIndexByName: Map<string, number>\n // Local binding names introduced by imports \u2014 used to detect a props type that is\n // imported (e.g. `import type { CompProps }`) rather than declared in the file.\n importedNames: Set<string>\n}\n\n/** Classify each top-level statement into imports, components, local props types, and import bindings. */\nconst collectTopLevel = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): TopLevel => {\n const importIndices: number[] = []\n const components: ComponentRef[] = []\n const propsIndexByName = new Map<string, number>()\n const importedNames = new Set<string>()\n\n body.forEach((statement, index) => {\n if (statement.type === 'ImportDeclaration') {\n importIndices.push(index)\n\n for (const specifier of statement.specifiers) {\n importedNames.add(specifier.local.name)\n }\n\n return\n }\n\n const declaration = unwrapExport(statement)\n\n const propsName = getPropsTypeName(declaration)\n\n if (propsName !== null && !propsIndexByName.has(propsName)) {\n propsIndexByName.set(propsName, index)\n }\n\n if (declaresComponent(declaration)) {\n components.push({ index, name: getDeclaredComponentName(declaration) })\n }\n })\n\n return { importIndices, components, propsIndexByName, importedNames }\n}\n\n/**\n * Whether any top-level statement in the half-open range `[directiveCount, boundary)` is a stray \u2014\n * i.e. not an import and not part of the leading directive prologue. `skipIndex` excludes a single\n * known-good statement (the component itself, when scanning the gap before its props interface).\n */\nconst hasStrayBefore = (\n body: Array<ESTree.Statement | ESTree.ModuleDeclaration>,\n boundary: number,\n directiveCount: number,\n skipIndex?: number,\n): boolean => {\n return body.some((statement, index) => {\n return index < boundary && index >= directiveCount && index !== skipIndex && statement.type !== 'ImportDeclaration'\n })\n}\n\ntype MessageId =\n | 'importsFirst'\n | 'interfaceImmediatelyBeforeComponent'\n | 'interfaceImmediatelyAfterImports'\n | 'componentImmediatelyAfterImports'\n\n// A pending report, expressed as a body index plus the message to raise against it.\ninterface Violation {\n index: number\n messageId: MessageId\n}\n\n/** Imports that sit after the first component (or its props interface) must move up. */\nconst findImportOrderViolations = (importIndices: number[], importBoundary: number): Violation[] => {\n return importIndices\n .filter((importIndex) => {\n return importIndex > importBoundary\n })\n .map((importIndex) => {\n return { index: importIndex, messageId: 'importsFirst' }\n })\n}\n\n/**\n * Each component's own `<Name>Props` interface, when present, must sit immediately before the\n * component. Matching by name keeps a sibling component's props from being judged against this one.\n */\nconst findAdjacencyViolations = (components: ComponentRef[], propsIndexByName: Map<string, number>): Violation[] => {\n const violations: Violation[] = []\n\n for (const component of components) {\n if (component.name === null) {\n continue\n }\n\n const propsIndex = propsIndexByName.get(`${component.name}${PROPS_SUFFIX}`)\n\n if (propsIndex !== undefined && propsIndex !== component.index - 1) {\n violations.push({ index: propsIndex, messageId: 'interfaceImmediatelyBeforeComponent' })\n }\n }\n\n return violations\n}\n\n/**\n * The first component is anchored to the import block: no stray top-level definition may wedge\n * between the imports and the props interface \u2014 or, when the props type is *imported* rather than\n * declared in the file, between the imports and the component itself. Only the first component is\n * anchored; later interfaces are governed solely by the adjacency check. Each `every` guard skips\n * when an import sits after its anchor, since that misorder is already reported by `importsFirst`.\n */\nconst findAnchorViolations = (\n body: Array<ESTree.Statement | ESTree.ModuleDeclaration>,\n directiveCount: number,\n importIndices: number[],\n first: ComponentRef,\n firstPropsName: string | null,\n firstPropsIndex: number | undefined,\n importedNames: Set<string>,\n): Violation[] => {\n const violations: Violation[] = []\n\n const importsBeforeInterface =\n firstPropsIndex !== undefined &&\n importIndices.every((importIndex) => {\n return importIndex < firstPropsIndex\n })\n\n if (importsBeforeInterface && hasStrayBefore(body, firstPropsIndex!, directiveCount, first.index)) {\n violations.push({ index: firstPropsIndex!, messageId: 'interfaceImmediatelyAfterImports' })\n }\n\n const firstPropsImported =\n firstPropsIndex === undefined && firstPropsName !== null && importedNames.has(firstPropsName)\n const importsBeforeComponent = importIndices.every((importIndex) => {\n return importIndex < first.index\n })\n\n if (firstPropsImported && importsBeforeComponent && hasStrayBefore(body, first.index, directiveCount)) {\n violations.push({ index: first.index, messageId: 'componentImmediatelyAfterImports' })\n }\n\n return violations\n}\n\nexport const componentFileOrder: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Enforce a strict top-level order in React component files: imports first, then \u2014 for each component \u2014 its props interface/type declared immediately before the component.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n importsFirst: 'Imports must come before the component interface and declaration.',\n interfaceImmediatelyBeforeComponent:\n 'The component props interface must be declared immediately before the component.',\n interfaceImmediatelyAfterImports:\n 'The component props interface must be declared immediately after the imports, with no other declarations in between.',\n componentImmediatelyAfterImports:\n 'When the component props type is imported, the component must be declared immediately after the imports, with no other declarations in between.',\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n return {\n Program(program) {\n const body = program.body\n\n // Leading directive prologue (`'use client'`, ...) \u2014 excluded from the stray checks.\n const directiveCount = getDirectivePrologueCount(body)\n\n const { importIndices, components, propsIndexByName, importedNames } = collectTopLevel(body)\n\n // The rule only governs files that actually contain a component.\n if (components.length === 0) {\n return\n }\n\n const first = components[0]!\n const firstPropsName = first.name === null ? null : `${first.name}${PROPS_SUFFIX}`\n const firstPropsIndex = firstPropsName === null ? undefined : propsIndexByName.get(firstPropsName)\n // `importBoundary` is intentionally directive-insensitive: a leading directive prologue\n // shifts every subsequent index up uniformly, so it never crosses this boundary. The\n // prologue is excluded only from the stray checks, where the raw index matters.\n const importBoundary = Math.min(first.index, firstPropsIndex ?? first.index)\n\n const violations = [\n ...findImportOrderViolations(importIndices, importBoundary),\n ...findAdjacencyViolations(components, propsIndexByName),\n ...findAnchorViolations(\n body,\n directiveCount,\n importIndices,\n first,\n firstPropsName,\n firstPropsIndex,\n importedNames,\n ),\n ]\n\n for (const violation of violations) {\n context.report({ node: body[violation.index]!, messageId: violation.messageId })\n }\n },\n }\n },\n}\n\nexport default componentFileOrder\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { isComponent } from '../utils/component'\n\n/** Whether a statement is `const { ... } = props` (destructuring the `props` identifier). */\nconst isPropsDestructuring = (statement: ESTree.Statement): boolean => {\n if (statement.type !== 'VariableDeclaration') {\n return false\n }\n\n return statement.declarations.some((declaration) => {\n return (\n declaration.id.type === 'ObjectPattern' &&\n declaration.init?.type === 'Identifier' &&\n declaration.init.name === 'props'\n )\n })\n}\n\nexport const propsDestructuringBlankLine: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Require a blank line after the `const { ... } = props` destructuring statement at the top of a React component body.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n fixable: 'whitespace',\n schema: [],\n messages: {\n blankLineAfterProps: 'Add a blank line after destructuring props.',\n },\n },\n\n create(context) {\n const sourceCode = context.sourceCode\n\n const check = (node: ComponentFunction): void => {\n if (node.body.type !== 'BlockStatement') {\n return\n }\n\n if (!isComponent(node)) {\n return\n }\n\n const statements = node.body.body\n const index = statements.findIndex(isPropsDestructuring)\n\n if (index === -1) {\n return\n }\n\n const propsStatement = statements[index]\n const nextStatement = statements[index + 1]\n\n // `propsStatement` is defined because `index !== -1`; the guard also narrows the type.\n // Nothing follows the destructuring \u2014 no separation needed.\n if (!propsStatement || !nextStatement) {\n return\n }\n\n // The token/comment that follows the destructuring statement; a comment on the\n // next line still counts as \"no blank line\" until it is pushed down.\n const tokenAfter = sourceCode.getTokenAfter(propsStatement, { includeComments: true })\n const referenceLine = (tokenAfter ?? nextStatement).loc!.start.line\n\n if (referenceLine - propsStatement.loc!.end.line >= 2) {\n return\n }\n\n context.report({\n node: propsStatement,\n messageId: 'blankLineAfterProps',\n fix(fixer) {\n return fixer.insertTextAfter(propsStatement, '\\n')\n },\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsDestructuringBlankLine\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { isComponent } from '../utils/component'\n\n// Minimal structural views over nodes that estree's types do not fully model:\n// the optional TS type annotation and `range` that the parser attaches to params.\ninterface WithRange {\n range?: [number, number]\n}\ntype AnnotatedPattern = ESTree.ObjectPattern & { typeAnnotation?: ESTree.Node & WithRange } & WithRange\n\n// Recursively collect every identifier a destructuring pattern binds, so we can detect\n// whether it already introduces a `props` binding (e.g. a `...props` rest).\nconst collectBoundNames = (node: ESTree.Node | null, names: Set<string>): void => {\n if (!node) {\n return\n }\n\n switch (node.type) {\n case 'Identifier':\n names.add(node.name)\n break\n case 'ObjectPattern':\n for (const property of node.properties) collectBoundNames(property, names)\n break\n case 'ArrayPattern':\n for (const element of node.elements) collectBoundNames(element, names)\n break\n case 'Property':\n collectBoundNames(node.value, names)\n break\n case 'RestElement':\n collectBoundNames(node.argument, names)\n break\n case 'AssignmentPattern':\n collectBoundNames(node.left, names)\n break\n default:\n break\n }\n}\n\nexport const propsDestructuringNewline: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Require React components to accept a single props parameter and destructure it on its own line in the body, rather than destructuring inline in the parameter list.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n fixable: 'code',\n schema: [],\n messages: {\n destructureOnNewLine:\n 'Accept a single `props` parameter and destructure it on its own line in the component body instead of destructuring in the parameter list.',\n },\n },\n\n create(context) {\n const sourceCode = context.sourceCode\n\n const check = (node: ComponentFunction): void => {\n const firstParam = node.params[0]\n\n if (!firstParam || firstParam.type !== 'ObjectPattern') {\n return\n }\n\n if (!isComponent(node)) {\n return\n }\n\n // The fix renames the parameter to `props` and re-destructures it in the body\n // (`const <pattern> = props`). If the pattern already binds `props` (e.g. a\n // `...props` rest), that body binding collides with the new parameter and yields\n // an invalid \"Duplicate declaration props\". There is no safe rename that keeps the\n // `props` name the rule mandates, so skip these patterns entirely.\n const boundNames = new Set<string>()\n\n collectBoundNames(firstParam, boundNames)\n\n if (boundNames.has('props')) {\n return\n }\n\n const objectPattern = firstParam as AnnotatedPattern\n\n context.report({\n node: firstParam,\n messageId: 'destructureOnNewLine',\n fix(fixer) {\n const text = sourceCode.getText()\n const annotation = objectPattern.typeAnnotation\n\n const patternStart = objectPattern.range![0]\n const patternEnd = annotation ? annotation.range![0] : objectPattern.range![1]\n const fullEnd = annotation ? annotation.range![1] : objectPattern.range![1]\n\n const patternText = text.slice(patternStart, patternEnd).trim()\n const annotationText = annotation ? sourceCode.getText(annotation) : ''\n\n const fixes = [fixer.replaceTextRange([patternStart, fullEnd], `props${annotationText}`)]\n\n const destructureStatement = `const ${patternText} = props`\n\n // Indentation of the line the component is declared on, used as the base for inserted code.\n const lines = sourceCode.getLines()\n const declarationLine = lines[node.loc!.start.line - 1] ?? ''\n const baseIndent = declarationLine.slice(0, declarationLine.length - declarationLine.trimStart().length)\n const innerIndent = `${baseIndent} `\n\n if (node.body.type === 'BlockStatement') {\n const [firstStatement] = node.body.body\n\n if (firstStatement) {\n const indent = ' '.repeat(firstStatement.loc!.start.column)\n\n fixes.push(fixer.insertTextBefore(firstStatement, `${destructureStatement}\\n\\n${indent}`))\n } else {\n const openBrace = sourceCode.getFirstToken(node.body)!\n\n fixes.push(fixer.insertTextAfter(openBrace, `\\n${innerIndent}${destructureStatement}\\n${baseIndent}`))\n }\n\n return fixes\n }\n\n // Expression-bodied arrow (implicit return) \u2014 wrap it in a block.\n const bodyText = sourceCode.getText(node.body)\n\n fixes.push(\n fixer.replaceText(\n node.body,\n `{\\n${innerIndent}${destructureStatement}\\n\\n${innerIndent}return ${bodyText}\\n${baseIndent}}`,\n ),\n )\n\n return fixes\n },\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsDestructuringNewline\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { getComponentName, isComponent } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\n// TS-only nodes are not modelled by estree, so we view them through a minimal two-level\n// structural interface: the `TSTypeAnnotation` wrapper a parser attaches to a parameter, and\n// its inner type node. An inline object type is `TSTypeLiteral`; a named type is `TSTypeReference`.\ninterface AnnotatedNode {\n typeAnnotation?: {\n typeAnnotation?: { type?: string }\n }\n}\n\nconst INLINE_OBJECT_TYPE = 'TSTypeLiteral'\n\n/**\n * The parameter that actually carries the props type annotation. A default value wraps the\n * parameter in an `AssignmentPattern` (`(props: { x } = {})`), moving the annotation onto its\n * `.left`; unwrap one layer so the inline-type check sees the annotated binding either way.\n */\nconst getAnnotatedParam = (param: ESTree.Pattern): ESTree.Pattern => {\n return param.type === 'AssignmentPattern' ? param.left : param\n}\n\nexport const propsTypeReference: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Require a React component's props parameter to use a named type (e.g. `ButtonProps`) instead of an inline object type literal.\",\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n useNamedPropsType:\n \"Use a named props type (e.g. `{{name}}Props`) instead of an inline object type for this component's props.\",\n useNamedPropsTypeAnonymous: \"Use a named props type instead of an inline object type for this component's props.\",\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n const check = (node: ComponentFunction): void => {\n const firstParam = node.params[0]\n\n if (!firstParam || !isComponent(node)) {\n return\n }\n\n const annotatedParam = getAnnotatedParam(firstParam)\n const innerType = (annotatedParam as AnnotatedNode).typeAnnotation?.typeAnnotation?.type\n\n if (innerType !== INLINE_OBJECT_TYPE) {\n return\n }\n\n const name = getComponentName(node)\n\n context.report(\n name === null\n ? { node: annotatedParam, messageId: 'useNamedPropsTypeAnonymous' }\n : { node: annotatedParam, messageId: 'useNamedPropsType', data: { name } },\n )\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsTypeReference\n", "import type { Rule } from 'eslint'\nimport { existsSync } from 'node:fs'\nimport path from 'node:path'\n\nimport { bodyDeclaresComponent } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\nimport type { ExtraTarget, StoryPathOptions } from '../utils/story-path'\nimport { DEFAULT_STORY_PATH_OPTIONS, classifyComponent, deriveExpectedStoryPaths } from '../utils/story-path'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n storiesDir?: string\n storySuffix?: string\n storyExtensions?: string[]\n componentSuffix?: string\n requireComponentAst?: boolean\n extraTargets?: ExtraTarget[]\n}\n\n/** Pick the story-path knobs out of the rule options, leaving defaults to the helper. */\nconst toStoryPathOptions = (options: Options): Partial<StoryPathOptions> => {\n const picked: Partial<StoryPathOptions> = {}\n\n if (options.storiesDir !== undefined) {\n picked.storiesDir = options.storiesDir\n }\n\n if (options.storySuffix !== undefined) {\n picked.storySuffix = options.storySuffix\n }\n\n if (options.storyExtensions !== undefined) {\n picked.storyExtensions = options.storyExtensions\n }\n\n if (options.componentSuffix !== undefined) {\n picked.componentSuffix = options.componentSuffix\n }\n\n if (options.extraTargets !== undefined) {\n picked.extraTargets = options.extraTargets\n }\n\n return picked\n}\n\nexport const requireComponentStories: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require a co-located Storybook story for every dumb component.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional globs; when provided the rule only runs for files whose path matches one.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional globs; the rule is skipped for matching files, even if they also match `paths`.',\n },\n storiesDir: {\n type: 'string',\n description: `Story directory name (default '${DEFAULT_STORY_PATH_OPTIONS.storiesDir}').`,\n },\n storySuffix: {\n type: 'string',\n description: `Suffix inserted before the extension (default '${DEFAULT_STORY_PATH_OPTIONS.storySuffix}').`,\n },\n storyExtensions: {\n type: 'array',\n items: { type: 'string' },\n description: 'Extensions a satisfying story file may have, in priority order.',\n },\n componentSuffix: {\n type: 'string',\n description: `Basename suffix a component file must end with (default '${DEFAULT_STORY_PATH_OPTIONS.componentSuffix}'; '' disables).`,\n },\n requireComponentAst: {\n type: 'boolean',\n description: 'When true (default), only require a story for files that actually declare a component.',\n },\n extraTargets: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n componentsDir: { type: 'string' },\n anchorParentDir: { type: 'string' },\n storyMode: { enum: ['feature-root', 'sibling'] },\n },\n required: ['componentsDir', 'storyMode'],\n additionalProperties: false,\n },\n description: 'Extra structured component layouts (componentsDir + optional anchorParentDir + storyMode).',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n missingStory: \"Dumb component '{{component}}' is missing a Storybook story (expected at '{{expected}}').\",\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n const requireComponentAst = options.requireComponentAst ?? true\n const storyPathOptions = toStoryPathOptions(options)\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n return {\n Program(program) {\n // Is this file a dumb component that requires a story, and where would the story live?\n if (!classifyComponent(context.filename, storyPathOptions)) {\n return\n }\n\n // Only enforce on files that actually declare a component (avoids flagging stray\n // non-component files placed under a components/ directory).\n if (requireComponentAst && !bodyDeclaresComponent(program.body)) {\n return\n }\n\n const candidates = deriveExpectedStoryPaths(context.filename, storyPathOptions)\n\n // The story is satisfied if ANY candidate (by extension) exists on disk.\n const hasStory = candidates.some((candidate) => {\n return existsSync(candidate)\n })\n\n if (hasStory) {\n return\n }\n\n context.report({\n node: program,\n messageId: 'missingStory',\n data: {\n component: path.posix.basename(context.filename.split('\\\\').join('/')),\n expected: candidates[0] ?? '',\n },\n })\n },\n }\n },\n}\n\nexport default requireComponentStories\n", "import path from 'node:path'\n\n/** Where a story file lives relative to its component. */\nexport type StoryMode = 'feature-root' | 'sibling'\n\n/** A consumer-defined extra component layout (structured \u2014 never a glob). */\nexport interface ExtraTarget {\n /** Immediate parent directory name a component file must sit directly inside. */\n componentsDir: string\n /** Optional grandparent directory name gate (e.g. `features`). */\n anchorParentDir?: string\n /** Where the story is expected for files matched by this target. */\n storyMode: StoryMode\n}\n\nexport interface StoryPathOptions {\n /** Story directory name (default `__stories__`). */\n storiesDir: string\n /** Suffix inserted before the extension (default `.stories`). */\n storySuffix: string\n /** Extensions a satisfying story file may have, in priority order. */\n storyExtensions: string[]\n /** Extensions a file must have to be considered a component. */\n componentExtensions: string[]\n /** Basename suffix a component file must end with (default `-component`; `''` disables). */\n componentSuffix: string\n /** Additional structured component layouts beyond the two built-ins. */\n extraTargets: ExtraTarget[]\n}\n\nexport const DEFAULT_STORY_PATH_OPTIONS: StoryPathOptions = {\n storiesDir: '__stories__',\n storySuffix: '.stories',\n storyExtensions: ['.tsx', '.jsx', '.ts', '.js'],\n componentExtensions: ['.tsx', '.jsx'],\n componentSuffix: '-component',\n extraTargets: [],\n}\n\nconst resolveOptions = (opts?: Partial<StoryPathOptions>): StoryPathOptions => {\n return { ...DEFAULT_STORY_PATH_OPTIONS, ...opts }\n}\n\n/** Normalize OS-native separators to posix so all downstream path logic is deterministic. */\nconst toPosix = (filePath: string): string => {\n return filePath.split('\\\\').join('/')\n}\n\ninterface ParsedComponent {\n /** Posix directory of the file. */\n dir: string\n /** Basename without its extension. */\n base: string\n /** Original extension (e.g. `.tsx`). */\n ext: string\n /** Immediate parent directory name. */\n parent: string\n /** Grandparent directory name. */\n grandparent: string\n /** Great-grandparent directory name. */\n greatGrandparent: string\n}\n\n/** Parse a file path into the segment view the gate and derivation both need. */\nconst parse = (filePath: string): ParsedComponent => {\n const normalized = toPosix(filePath)\n const segments = normalized.split('/')\n const basename = segments[segments.length - 1] ?? ''\n const ext = path.posix.extname(basename)\n const base = ext ? basename.slice(0, -ext.length) : basename\n\n return {\n dir: path.posix.dirname(normalized),\n base,\n ext,\n parent: segments[segments.length - 2] ?? '',\n grandparent: segments[segments.length - 3] ?? '',\n greatGrandparent: segments[segments.length - 4] ?? '',\n }\n}\n\n/** Whether the file passes the component preconditions (extension + name suffix). */\nconst passesComponentPreconditions = (parsed: ParsedComponent, options: StoryPathOptions): boolean => {\n if (!options.componentExtensions.includes(parsed.ext)) {\n return false\n }\n\n return options.componentSuffix === '' || parsed.base.endsWith(options.componentSuffix)\n}\n\n/**\n * Classify a file as a dumb component requiring a story, resolving WHERE its story should live.\n * Returns null when the file is not a component-requiring-a-story under any branch.\n *\n * Exactly one admit-condition may hold:\n * - feature-root: direct child of `components/` whose chain is `features/<f>/components`.\n * - sibling: a `components/default/<name>-component` file (parent `default`, grandparent `components`).\n * - extraTargets: a structured consumer-defined layout.\n */\nexport const classifyComponent = (filePath: string, opts?: Partial<StoryPathOptions>): { mode: StoryMode } | null => {\n const options = resolveOptions(opts)\n const parsed = parse(filePath)\n\n if (!passesComponentPreconditions(parsed, options)) {\n return null\n }\n\n // (a) feature-root \u2014 immediate parent is `components` AND the chain is `features/<feature>/components`.\n if (parsed.parent === 'components' && parsed.greatGrandparent === 'features') {\n return { mode: 'feature-root' }\n }\n\n // (b) sibling \u2014 `components/default/<name>-component.*` (NOT a direct child of `components/`).\n if (parsed.parent === 'default' && parsed.grandparent === 'components') {\n return { mode: 'sibling' }\n }\n\n // (c) extraTargets \u2014 structured layouts (componentsDir + optional anchorParentDir).\n for (const target of options.extraTargets) {\n const parentMatches = parsed.parent === target.componentsDir\n const anchorMatches = target.anchorParentDir == null || parsed.grandparent === target.anchorParentDir\n\n if (parentMatches && anchorMatches) {\n return { mode: target.storyMode }\n }\n }\n\n return null\n}\n\n/**\n * Ordered candidate story paths for a component file. Empty when the file is not a\n * component-requiring-a-story (see {@link classifyComponent}). The story is considered present\n * when ANY candidate exists on disk.\n */\nexport const deriveExpectedStoryPaths = (filePath: string, opts?: Partial<StoryPathOptions>): string[] => {\n const options = resolveOptions(opts)\n const classification = classifyComponent(filePath, options)\n\n if (!classification) {\n return []\n }\n\n const parsed = parse(filePath)\n\n // feature-root: dirname(file) is `.../components`, so its parent is the feature root.\n // sibling: the story dir sits next to the component file itself.\n const storyBaseDir = classification.mode === 'feature-root' ? path.posix.dirname(parsed.dir) : parsed.dir\n const storyDir = path.posix.join(storyBaseDir, options.storiesDir)\n\n return options.storyExtensions.map((extension) => {\n return path.posix.join(storyDir, `${parsed.base}${options.storySuffix}${extension}`)\n })\n}\n", "import type { Rule } from 'eslint'\n\nimport { componentArrowFunction } from './component-arrow-function'\nimport { componentFileOrder } from './component-file-order'\nimport { propsDestructuringBlankLine } from './props-destructuring-blank-line'\nimport { propsDestructuringNewline } from './props-destructuring-newline'\nimport { propsTypeReference } from './props-type-reference'\nimport { requireComponentStories } from './require-component-stories'\n\nexport const rules: Record<string, Rule.RuleModule> = {\n 'props-destructuring-newline': propsDestructuringNewline,\n 'props-destructuring-blank-line': propsDestructuringBlankLine,\n 'props-type-reference': propsTypeReference,\n 'component-file-order': componentFileOrder,\n 'component-arrow-function': componentArrowFunction,\n 'require-component-stories': requireComponentStories,\n}\n", "import type { ESLint, Linter } from 'eslint'\n\nimport { rules } from './rules'\n\nconst PLUGIN_NAME = '@wl'\n\nconst plugin: ESLint.Plugin & { configs: Record<string, Linter.Config | Linter.Config[]> } = {\n meta: {\n name: '@wl/eslint-plugin',\n version: '0.1.11',\n },\n rules,\n configs: {},\n}\n\n/**\n * Flat-config preset that registers the plugin and turns every rule on, scoped to\n * the files each rule is meant for. It is an array of config blocks, so spread it:\n *\n * @example\n * import wl from '@wl/eslint-plugin'\n *\n * export default [...wl.configs.recommended]\n */\nplugin.configs.recommended = [\n {\n files: ['**/*.tsx'],\n plugins: {\n [PLUGIN_NAME]: plugin,\n },\n rules: {\n [`${PLUGIN_NAME}/props-destructuring-newline`]: 'error',\n [`${PLUGIN_NAME}/props-destructuring-blank-line`]: 'error',\n [`${PLUGIN_NAME}/props-type-reference`]: 'error',\n [`${PLUGIN_NAME}/component-file-order`]: 'error',\n // Pages and routes are excluded: route/page modules commonly use `function`\n // declarations (and framework conventions like default-exported page functions).\n [`${PLUGIN_NAME}/component-arrow-function`]: ['error', { ignore: ['**/pages/**', '**/routes/**'] }],\n [`${PLUGIN_NAME}/require-component-stories`]: 'error',\n },\n },\n // Storybook stories legitimately deviate from the imports \u2192 *Props \u2192 component\n // order (meta/args/decorators/render fns), so component-file-order would only\n // produce noise there. Every other rule stays enabled for story files.\n {\n files: ['**/*.stories.{ts,tsx}'],\n rules: {\n [`${PLUGIN_NAME}/component-file-order`]: 'off',\n },\n },\n]\n\nexport const meta: ESLint.Plugin['meta'] = plugin.meta\nexport const configs: Record<string, Linter.Config | Linter.Config[]> = plugin.configs\nexport { rules }\n\nexport default plugin\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["fn", "path", "path"]
|
|
3
|
+
"sources": ["../src/utils/component.ts", "../src/utils/path-match.ts", "../src/rules/component-arrow-function.ts", "../src/rules/component-file-order.ts", "../src/rules/props-destructuring-blank-line.ts", "../src/rules/props-destructuring-newline.ts", "../src/rules/props-type-name.ts", "../src/rules/props-type-reference.ts", "../src/rules/require-component-stories.ts", "../src/utils/story-path.ts", "../src/rules/index.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["import type * as ESTree from 'estree'\n\nexport type ComponentFunction = ESTree.ArrowFunctionExpression | ESTree.FunctionDeclaration | ESTree.FunctionExpression\n\n// Minimal structural view over the `parent` back-reference ESLint adds to every node.\ninterface WithParent {\n parent?: ESTree.Node\n}\n\n// Calls that wrap a component while preserving its identity (memo, forwardRef, observer, ...).\nconst COMPONENT_WRAPPER_CALLEES = new Set(['memo', 'forwardRef', 'observer', 'React.memo', 'React.forwardRef'])\n\n// Node types that introduce a new function scope \u2014 their returns are not the outer component's.\nconst NESTED_SCOPES = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'])\n\nconst isPascalCase = (name: string): boolean => {\n return /^[A-Z]/.test(name)\n}\n\nconst isJsxNode = (node: ESTree.Node | null | undefined): boolean => {\n if (!node) {\n return false\n }\n\n const type = node.type as string\n\n return type === 'JSXElement' || type === 'JSXFragment'\n}\n\nconst getParent = (node: ESTree.Node | undefined): ESTree.Node | undefined => {\n return (node as (WithParent & ESTree.Node) | undefined)?.parent\n}\n\n/** Best-effort name of a call's callee: `memo` for `memo(...)`, `React.memo` for `React.memo(...)`. */\nconst getCalleeName = (callee: ESTree.CallExpression['callee']): string | null => {\n if (callee.type === 'Identifier') {\n return callee.name\n }\n\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.property.type === 'Identifier'\n ) {\n return `${callee.object.name}.${callee.property.name}`\n }\n\n return null\n}\n\n/**\n * Resolve the declared name of a function, looking through component wrappers\n * such as `memo`/`forwardRef` so that `const Comp = memo(({ a }) => ...)` is\n * still recognised by its PascalCase variable name.\n */\nexport const getComponentName = (node: ComponentFunction): string | null => {\n if (node.type === 'FunctionDeclaration') {\n return node.id?.name ?? null\n }\n\n let current = getParent(node)\n\n // Walk through wrapping call expressions (memo, forwardRef, React.memo, ...).\n while (current?.type === 'CallExpression') {\n const calleeName = getCalleeName(current.callee)\n\n if (!calleeName || !COMPONENT_WRAPPER_CALLEES.has(calleeName)) {\n break\n }\n\n current = getParent(current)\n }\n\n if (current?.type === 'VariableDeclarator' && current.id.type === 'Identifier') {\n return current.id.name\n }\n\n return null\n}\n\n/** Whether a function returns JSX, scanning its own body without descending into nested functions. */\nconst returnsJsx = (node: ComponentFunction): boolean => {\n if (node.body.type !== 'BlockStatement') {\n return isJsxNode(node.body)\n }\n\n let found = false\n\n const visit = (current: ESTree.Node | null | undefined): void => {\n if (found || !current || NESTED_SCOPES.has(current.type)) {\n return\n }\n\n if (current.type === 'ReturnStatement') {\n if (isJsxNode(current.argument)) {\n found = true\n }\n\n return\n }\n\n if (current.type === 'IfStatement') {\n visit(current.consequent)\n visit(current.alternate)\n\n return\n }\n\n if (current.type === 'BlockStatement') {\n current.body.forEach(visit)\n\n return\n }\n\n if (current.type === 'SwitchStatement') {\n for (const switchCase of current.cases) {\n switchCase.consequent.forEach(visit)\n }\n\n return\n }\n\n if (current.type === 'TryStatement') {\n visit(current.block)\n visit(current.handler?.body)\n visit(current.finalizer)\n\n return\n }\n\n if (\n current.type === 'ForStatement' ||\n current.type === 'ForInStatement' ||\n current.type === 'ForOfStatement' ||\n current.type === 'WhileStatement' ||\n current.type === 'DoWhileStatement'\n ) {\n visit(current.body)\n }\n }\n\n node.body.body.forEach(visit)\n\n return found\n}\n\n/** A function is treated as a React component when it is PascalCase-named or returns JSX. */\nexport const isComponent = (node: ComponentFunction): boolean => {\n const name = getComponentName(node)\n\n if (name && isPascalCase(name)) {\n return true\n }\n\n return returnsJsx(node)\n}\n\nconst isComponentFunctionNode = (node: ESTree.Node): node is ComponentFunction => {\n return (\n node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration'\n )\n}\n\n/**\n * Extract the component function from an expression, unwrapping a single layer of\n * component wrappers (`memo(fn)`, `forwardRef(fn)`, `React.memo(fn)`, ...). Returns\n * null when no function is found.\n */\nexport const getComponentFunction = (node: ESTree.Node | null | undefined): ComponentFunction | null => {\n if (!node) {\n return null\n }\n\n if (isComponentFunctionNode(node)) {\n return node\n }\n\n if (node.type === 'CallExpression') {\n for (const argument of node.arguments) {\n if (argument.type === 'SpreadElement') {\n continue\n }\n\n const found = getComponentFunction(argument)\n\n if (found) {\n return found\n }\n }\n }\n\n return null\n}\n\n// TS-only nodes are not modelled by estree. A parameter's `typeAnnotation` wraps the type node;\n// for a named type that inner node is a `TSTypeReference` whose `typeName` is the referenced\n// identifier (`Props`, `ButtonProps`, ...). Inline object types, qualified names (`NS.Props`) and\n// generics surface a different `typeName` shape and are intentionally left unresolved.\ninterface TypeReferenceNode {\n type?: string\n typeName?: { type?: string; name?: string }\n}\n\ninterface AnnotatedParam {\n typeAnnotation?: {\n typeAnnotation?: TypeReferenceNode\n }\n}\n\n/**\n * The parameter that actually carries the props type annotation. A default value wraps the\n * parameter in an `AssignmentPattern` (`(props: Props = {})`), moving the annotation onto its\n * `.left`; unwrap one layer so the annotation is found either way.\n */\nexport const getAnnotatedParam = (param: ESTree.Pattern): ESTree.Pattern => {\n return param.type === 'AssignmentPattern' ? param.left : param\n}\n\n/** The named type referenced by a component function's first parameter (`Props`), or null. */\nexport const getPropsTypeNameFromFunction = (node: ComponentFunction): string | null => {\n const firstParam = node.params[0]\n\n if (!firstParam) {\n return null\n }\n\n const inner = (getAnnotatedParam(firstParam) as AnnotatedParam).typeAnnotation?.typeAnnotation\n\n if (inner?.type !== 'TSTypeReference' || inner.typeName?.type !== 'Identifier') {\n return null\n }\n\n return inner.typeName.name ?? null\n}\n\n/**\n * Resolve the props type name a component *uses* \u2014 the named type on its first parameter \u2014\n * looking through component wrappers (`memo`/`forwardRef`). Returns null when the component is\n * anonymous of props (no parameter) or its props type is not a simple named reference (inline\n * object, qualified name, generic). Mirrors `getDeclaredComponentName`'s node dispatch so a\n * `const`-declared arrow component resolves through its `init`, not the `VariableDeclaration`.\n */\nexport const getComponentPropsTypeName = (node: ESTree.Node | null): string | null => {\n if (!node) {\n return null\n }\n\n if (node.type === 'FunctionDeclaration') {\n return isComponent(node) ? getPropsTypeNameFromFunction(node) : null\n }\n\n if (node.type === 'VariableDeclaration') {\n for (const declaration of node.declarations) {\n const fn = getComponentFunction(declaration.init)\n\n if (fn && isComponent(fn)) {\n return getPropsTypeNameFromFunction(fn)\n }\n }\n\n return null\n }\n\n const fn = getComponentFunction(node)\n\n return fn && isComponent(fn) ? getPropsTypeNameFromFunction(fn) : null\n}\n\n/** Unwrap an `export ...` statement to the declaration it wraps (or the statement itself). */\nexport const unwrapExport = (statement: ESTree.Statement | ESTree.ModuleDeclaration): ESTree.Node | null => {\n if (statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration') {\n return (statement.declaration as ESTree.Node | null) ?? null\n }\n\n return statement\n}\n\n/** Whether a single top-level declaration declares a React component. */\nexport const declaresComponent = (node: ESTree.Node | null): boolean => {\n if (!node) {\n return false\n }\n\n if (node.type === 'FunctionDeclaration') {\n return isComponent(node)\n }\n\n if (node.type === 'VariableDeclaration') {\n return node.declarations.some((declaration) => {\n const fn = getComponentFunction(declaration.init)\n\n return fn ? isComponent(fn) : false\n })\n }\n\n const fn = getComponentFunction(node)\n\n return fn ? isComponent(fn) : false\n}\n\n/**\n * Resolve the name of the React component declared by a single top-level declaration,\n * or null when the declaration is not a component or the component is anonymous\n * (e.g. `export default () => <div />`). Mirrors `declaresComponent`'s node dispatch and\n * resolves the name through component wrappers (`memo`/`forwardRef`).\n */\nexport const getDeclaredComponentName = (node: ESTree.Node | null): string | null => {\n if (!node) {\n return null\n }\n\n if (node.type === 'FunctionDeclaration') {\n return isComponent(node) ? getComponentName(node) : null\n }\n\n if (node.type === 'VariableDeclaration') {\n for (const declaration of node.declarations) {\n const fn = getComponentFunction(declaration.init)\n\n if (fn && isComponent(fn)) {\n return getComponentName(fn)\n }\n }\n\n return null\n }\n\n const fn = getComponentFunction(node)\n\n return fn && isComponent(fn) ? getComponentName(fn) : null\n}\n\n/** Whether any top-level statement in a program body declares a React component. */\nexport const bodyDeclaresComponent = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): boolean => {\n return body.some((statement) => {\n return declaresComponent(unwrapExport(statement))\n })\n}\n", "// Characters that must be escaped when embedded literally into a RegExp source.\nconst REGEX_METACHARS = new Set(['\\\\', '^', '$', '.', '|', '+', '(', ')', '[', ']', '{', '}'])\n\n/**\n * Convert a glob pattern to an (unanchored) RegExp.\n *\n * - `**` matches any characters, including path separators.\n * - `*` matches any characters except a path separator.\n * - `?` matches a single non-separator character.\n *\n * The result is intentionally unanchored so a pattern matches anywhere in the\n * path (e.g. `features/**` matches `/repo/src/features/x/comp.tsx`).\n */\nconst globToRegExp = (glob: string): RegExp => {\n let source = ''\n\n for (let index = 0; index < glob.length; index++) {\n const char = glob[index]!\n\n if (char === '*') {\n if (glob[index + 1] === '*') {\n source += '.*'\n index++\n\n // Consume a trailing slash so `**/foo` also matches a bare `foo`.\n if (glob[index + 1] === '/') {\n index++\n }\n } else {\n source += '[^/]*'\n }\n } else if (char === '?') {\n source += '[^/]'\n } else if (REGEX_METACHARS.has(char)) {\n source += `\\\\${char}`\n } else {\n source += char\n }\n }\n\n return new RegExp(source)\n}\n\n/** Whether `filename` matches at least one of the provided glob `patterns`. */\nexport const matchesAnyGlob = (filename: string, patterns: readonly string[]): boolean => {\n const normalized = filename.split('\\\\').join('/')\n\n return patterns.some((pattern) => {\n return globToRegExp(pattern).test(normalized)\n })\n}\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport { getComponentFunction, getComponentName, isComponent, unwrapExport } from '../utils/component'\nimport type { ComponentFunction } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\n// Fallback used when a component is anonymous (e.g. `export default memo(function () { ... })`),\n// since `getComponentName` returns null rather than a placeholder in that case.\nconst ANONYMOUS_NAME = 'component'\n\ntype MessageId = 'functionDeclaration' | 'functionExpression'\n\ninterface Violation {\n node: ESTree.Node\n messageId: MessageId\n name: string\n}\n\nconst reportName = (fn: ComponentFunction): string => {\n return getComponentName(fn) ?? ANONYMOUS_NAME\n}\n\n/**\n * Violation for a single non-`VariableDeclaration` top-level declaration:\n * - a `function Foo() {}` component declaration (incl. exported/default/anonymous), or\n * - an expression that resolves to a function-expression component\n * (e.g. `export default memo(function () { ... })`).\n */\nconst nonVariableViolation = (declaration: ESTree.Node): Violation | null => {\n if (declaration.type === 'FunctionDeclaration') {\n return isComponent(declaration)\n ? { node: declaration, messageId: 'functionDeclaration', name: reportName(declaration) }\n : null\n }\n\n const fn = getComponentFunction(declaration)\n\n return fn?.type === 'FunctionExpression' && isComponent(fn)\n ? { node: fn, messageId: 'functionExpression', name: reportName(fn) }\n : null\n}\n\n/** Function-expression component violations across every declarator in a `const`/`let`/`var`. */\nconst variableViolations = (declaration: ESTree.VariableDeclaration): Violation[] => {\n const violations: Violation[] = []\n\n for (const declarator of declaration.declarations) {\n const fn = getComponentFunction(declarator.init)\n\n if (fn?.type === 'FunctionExpression' && isComponent(fn)) {\n violations.push({ node: declarator, messageId: 'functionExpression', name: reportName(fn) })\n }\n }\n\n return violations\n}\n\n/** Every non-arrow component declared at the top level of the program body. */\nconst collectViolations = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): Violation[] => {\n const violations: Violation[] = []\n\n for (const statement of body) {\n const declaration = unwrapExport(statement)\n\n if (!declaration) {\n continue\n }\n\n if (declaration.type === 'VariableDeclaration') {\n violations.push(...variableViolations(declaration))\n\n continue\n }\n\n const violation = nonVariableViolation(declaration)\n\n if (violation) {\n violations.push(violation)\n }\n }\n\n return violations\n}\n\nexport const componentArrowFunction: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Enforce that React components are declared as arrow functions, not `function` declarations or function expressions.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`. Use this to exclude pages and routes (e.g. `**/pages/**`, `**/routes/**`).',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n functionDeclaration:\n 'React components must be arrow functions; convert the `function {{name}}` declaration to `const {{name}} = () => { \u2026 }`.',\n functionExpression:\n 'React components must be arrow functions; replace the `function` expression for `{{name}}` with an arrow function.',\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n return {\n Program(program) {\n for (const { node, messageId, name } of collectViolations(program.body)) {\n context.report({ node, messageId, data: { name } })\n }\n },\n }\n },\n}\n\nexport default componentArrowFunction\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport {\n declaresComponent,\n getComponentPropsTypeName,\n getDeclaredComponentName,\n unwrapExport,\n} from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\n// TS-only nodes are not modelled by estree; access their identifier structurally.\ninterface NamedDeclaration {\n type: string\n id?: { name?: string } | null\n}\n\nconst PROPS_SUFFIX = 'Props'\n\n/**\n * Count the directive prologue \u2014 the leading run of string-literal expression statements\n * (`'use client'`, `'use server'`, `'use strict'`) at the top of the file. They are\n * legitimate file-leading content (like imports) and must not count as \"stray\" before the\n * props interface. Per the ECMAScript spec a directive is only one that *precedes* any other\n * statement, so we stop at the first non-string-literal statement rather than matching any\n * bare string anywhere in the body.\n */\nconst getDirectivePrologueCount = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): number => {\n let count = 0\n\n for (const statement of body) {\n const isStringExpression =\n statement.type === 'ExpressionStatement' &&\n statement.expression.type === 'Literal' &&\n typeof statement.expression.value === 'string'\n\n if (!isStringExpression) {\n break\n }\n\n count += 1\n }\n\n return count\n}\n\n/**\n * The name of a top-level interface or type-alias declaration, or null when it is neither.\n * Unlike the props lookup, this is name-agnostic: any local type declaration is indexed so a\n * component can be matched against the type it actually references, whatever that type is called.\n */\nconst getTypeDeclName = (node: ESTree.Node | null): string | null => {\n if (!node) {\n return null\n }\n\n const named = node as NamedDeclaration\n\n if (named.type !== 'TSInterfaceDeclaration' && named.type !== 'TSTypeAliasDeclaration') {\n return null\n }\n\n return named.id?.name ?? null\n}\n\ninterface ComponentRef {\n index: number\n name: string | null\n // The type name the component uses for its props \u2014 resolved from its parameter annotation, or\n // the `<Name>Props` convention when the parameter carries no resolvable named type. Null when\n // neither is available (e.g. an anonymous component with no typed props parameter).\n propsName: string | null\n}\n\ninterface TopLevel {\n importIndices: number[]\n components: ComponentRef[]\n // First index of each top-level type declaration, keyed by its name.\n declIndexByName: Map<string, number>\n // Local binding names introduced by imports \u2014 used to detect a props type that is\n // imported (e.g. `import type { CompProps }`) rather than declared in the file.\n importedNames: Set<string>\n}\n\n/** Classify each top-level statement into imports, components, local type declarations, and import bindings. */\nconst collectTopLevel = (body: Array<ESTree.Statement | ESTree.ModuleDeclaration>): TopLevel => {\n const importIndices: number[] = []\n const components: ComponentRef[] = []\n const declIndexByName = new Map<string, number>()\n const importedNames = new Set<string>()\n\n body.forEach((statement, index) => {\n if (statement.type === 'ImportDeclaration') {\n importIndices.push(index)\n\n for (const specifier of statement.specifiers) {\n importedNames.add(specifier.local.name)\n }\n\n return\n }\n\n const declaration = unwrapExport(statement)\n\n const declName = getTypeDeclName(declaration)\n\n if (declName !== null && !declIndexByName.has(declName)) {\n declIndexByName.set(declName, index)\n }\n\n if (declaresComponent(declaration)) {\n const name = getDeclaredComponentName(declaration)\n // Prefer the type the component's parameter actually references; fall back to the\n // `<Name>Props` convention only when no named parameter type is resolvable.\n const propsName = getComponentPropsTypeName(declaration) ?? (name === null ? null : `${name}${PROPS_SUFFIX}`)\n\n components.push({ index, name, propsName })\n }\n })\n\n return { importIndices, components, declIndexByName, importedNames }\n}\n\n/**\n * Whether any top-level statement in the half-open range `[directiveCount, boundary)` is a stray \u2014\n * i.e. not an import and not part of the leading directive prologue. `skipIndex` excludes a single\n * known-good statement (the component itself, when scanning the gap before its props interface).\n */\nconst hasStrayBefore = (\n body: Array<ESTree.Statement | ESTree.ModuleDeclaration>,\n boundary: number,\n directiveCount: number,\n skipIndex?: number,\n): boolean => {\n return body.some((statement, index) => {\n return index < boundary && index >= directiveCount && index !== skipIndex && statement.type !== 'ImportDeclaration'\n })\n}\n\ntype MessageId =\n | 'importsFirst'\n | 'interfaceImmediatelyBeforeComponent'\n | 'interfaceImmediatelyAfterImports'\n | 'componentImmediatelyAfterImports'\n\n// A pending report, expressed as a body index plus the message to raise against it.\ninterface Violation {\n index: number\n messageId: MessageId\n}\n\n/** Imports that sit after the first component (or its props interface) must move up. */\nconst findImportOrderViolations = (importIndices: number[], importBoundary: number): Violation[] => {\n return importIndices\n .filter((importIndex) => {\n return importIndex > importBoundary\n })\n .map((importIndex) => {\n return { index: importIndex, messageId: 'importsFirst' }\n })\n}\n\n/**\n * Each component's props type, when declared locally, must sit immediately before the component.\n * The type is matched by the name the component's parameter actually references, so an interface\n * named anything (`Props`, `UserCardProps`, ...) is judged \u2014 not just the `<Name>Props` convention.\n * A type referenced by more than one component is skipped: a single declaration cannot sit\n * immediately before two components, so adjacency is unenforceable and would misfire.\n */\nconst findAdjacencyViolations = (components: ComponentRef[], declIndexByName: Map<string, number>): Violation[] => {\n const violations: Violation[] = []\n\n const referenceCount = new Map<string, number>()\n\n for (const component of components) {\n if (component.propsName !== null) {\n referenceCount.set(component.propsName, (referenceCount.get(component.propsName) ?? 0) + 1)\n }\n }\n\n for (const component of components) {\n const propsName = component.propsName\n\n if (propsName === null || (referenceCount.get(propsName) ?? 0) > 1) {\n continue\n }\n\n const propsIndex = declIndexByName.get(propsName)\n\n if (propsIndex !== undefined && propsIndex !== component.index - 1) {\n violations.push({ index: propsIndex, messageId: 'interfaceImmediatelyBeforeComponent' })\n }\n }\n\n return violations\n}\n\n/**\n * The first component is anchored to the import block: no stray top-level definition may wedge\n * between the imports and the props interface \u2014 or, when the props type is *imported* rather than\n * declared in the file, between the imports and the component itself. Only the first component is\n * anchored; later interfaces are governed solely by the adjacency check. Each `every` guard skips\n * when an import sits after its anchor, since that misorder is already reported by `importsFirst`.\n */\nconst findAnchorViolations = (\n body: Array<ESTree.Statement | ESTree.ModuleDeclaration>,\n directiveCount: number,\n importIndices: number[],\n first: ComponentRef,\n firstPropsName: string | null,\n firstPropsIndex: number | undefined,\n importedNames: Set<string>,\n): Violation[] => {\n const violations: Violation[] = []\n\n const importsBeforeInterface =\n firstPropsIndex !== undefined &&\n importIndices.every((importIndex) => {\n return importIndex < firstPropsIndex\n })\n\n if (importsBeforeInterface && hasStrayBefore(body, firstPropsIndex!, directiveCount, first.index)) {\n violations.push({ index: firstPropsIndex!, messageId: 'interfaceImmediatelyAfterImports' })\n }\n\n const firstPropsImported =\n firstPropsIndex === undefined && firstPropsName !== null && importedNames.has(firstPropsName)\n const importsBeforeComponent = importIndices.every((importIndex) => {\n return importIndex < first.index\n })\n\n if (firstPropsImported && importsBeforeComponent && hasStrayBefore(body, first.index, directiveCount)) {\n violations.push({ index: first.index, messageId: 'componentImmediatelyAfterImports' })\n }\n\n return violations\n}\n\nexport const componentFileOrder: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Enforce a strict top-level order in React component files: imports first, then \u2014 for each component \u2014 its props interface/type declared immediately before the component.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n importsFirst: 'Imports must come before the component interface and declaration.',\n interfaceImmediatelyBeforeComponent:\n 'The component props interface must be declared immediately before the component.',\n interfaceImmediatelyAfterImports:\n 'The component props interface must be declared immediately after the imports, with no other declarations in between.',\n componentImmediatelyAfterImports:\n 'When the component props type is imported, the component must be declared immediately after the imports, with no other declarations in between.',\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n return {\n Program(program) {\n const body = program.body\n\n // Leading directive prologue (`'use client'`, ...) \u2014 excluded from the stray checks.\n const directiveCount = getDirectivePrologueCount(body)\n\n const { importIndices, components, declIndexByName, importedNames } = collectTopLevel(body)\n\n // The rule only governs files that actually contain a component.\n if (components.length === 0) {\n return\n }\n\n const first = components[0]!\n const firstPropsName = first.propsName\n const firstPropsIndex = firstPropsName === null ? undefined : declIndexByName.get(firstPropsName)\n // `importBoundary` is intentionally directive-insensitive: a leading directive prologue\n // shifts every subsequent index up uniformly, so it never crosses this boundary. The\n // prologue is excluded only from the stray checks, where the raw index matters.\n const importBoundary = Math.min(first.index, firstPropsIndex ?? first.index)\n\n const violations = [\n ...findImportOrderViolations(importIndices, importBoundary),\n ...findAdjacencyViolations(components, declIndexByName),\n ...findAnchorViolations(\n body,\n directiveCount,\n importIndices,\n first,\n firstPropsName,\n firstPropsIndex,\n importedNames,\n ),\n ]\n\n for (const violation of violations) {\n context.report({ node: body[violation.index]!, messageId: violation.messageId })\n }\n },\n }\n },\n}\n\nexport default componentFileOrder\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { isComponent } from '../utils/component'\n\n/** Whether a statement is `const { ... } = props` (destructuring the `props` identifier). */\nconst isPropsDestructuring = (statement: ESTree.Statement): boolean => {\n if (statement.type !== 'VariableDeclaration') {\n return false\n }\n\n return statement.declarations.some((declaration) => {\n return (\n declaration.id.type === 'ObjectPattern' &&\n declaration.init?.type === 'Identifier' &&\n declaration.init.name === 'props'\n )\n })\n}\n\nexport const propsDestructuringBlankLine: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Require a blank line after the `const { ... } = props` destructuring statement at the top of a React component body.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n fixable: 'whitespace',\n schema: [],\n messages: {\n blankLineAfterProps: 'Add a blank line after destructuring props.',\n },\n },\n\n create(context) {\n const sourceCode = context.sourceCode\n\n const check = (node: ComponentFunction): void => {\n if (node.body.type !== 'BlockStatement') {\n return\n }\n\n if (!isComponent(node)) {\n return\n }\n\n const statements = node.body.body\n const index = statements.findIndex(isPropsDestructuring)\n\n if (index === -1) {\n return\n }\n\n const propsStatement = statements[index]\n const nextStatement = statements[index + 1]\n\n // `propsStatement` is defined because `index !== -1`; the guard also narrows the type.\n // Nothing follows the destructuring \u2014 no separation needed.\n if (!propsStatement || !nextStatement) {\n return\n }\n\n // The token/comment that follows the destructuring statement; a comment on the\n // next line still counts as \"no blank line\" until it is pushed down.\n const tokenAfter = sourceCode.getTokenAfter(propsStatement, { includeComments: true })\n const referenceLine = (tokenAfter ?? nextStatement).loc!.start.line\n\n if (referenceLine - propsStatement.loc!.end.line >= 2) {\n return\n }\n\n context.report({\n node: propsStatement,\n messageId: 'blankLineAfterProps',\n fix(fixer) {\n return fixer.insertTextAfter(propsStatement, '\\n')\n },\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsDestructuringBlankLine\n", "import type { Rule } from 'eslint'\nimport type * as ESTree from 'estree'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { isComponent } from '../utils/component'\n\n// Minimal structural views over nodes that estree's types do not fully model:\n// the optional TS type annotation and `range` that the parser attaches to params.\ninterface WithRange {\n range?: [number, number]\n}\ntype AnnotatedPattern = ESTree.ObjectPattern & { typeAnnotation?: ESTree.Node & WithRange } & WithRange\n\n// Recursively collect every identifier a destructuring pattern binds, so we can detect\n// whether it already introduces a `props` binding (e.g. a `...props` rest).\nconst collectBoundNames = (node: ESTree.Node | null, names: Set<string>): void => {\n if (!node) {\n return\n }\n\n switch (node.type) {\n case 'Identifier':\n names.add(node.name)\n break\n case 'ObjectPattern':\n for (const property of node.properties) collectBoundNames(property, names)\n break\n case 'ArrayPattern':\n for (const element of node.elements) collectBoundNames(element, names)\n break\n case 'Property':\n collectBoundNames(node.value, names)\n break\n case 'RestElement':\n collectBoundNames(node.argument, names)\n break\n case 'AssignmentPattern':\n collectBoundNames(node.left, names)\n break\n default:\n break\n }\n}\n\nexport const propsDestructuringNewline: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Require React components to accept a single props parameter and destructure it on its own line in the body, rather than destructuring inline in the parameter list.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n fixable: 'code',\n schema: [],\n messages: {\n destructureOnNewLine:\n 'Accept a single `props` parameter and destructure it on its own line in the component body instead of destructuring in the parameter list.',\n },\n },\n\n create(context) {\n const sourceCode = context.sourceCode\n\n const check = (node: ComponentFunction): void => {\n const firstParam = node.params[0]\n\n if (!firstParam || firstParam.type !== 'ObjectPattern') {\n return\n }\n\n if (!isComponent(node)) {\n return\n }\n\n // The fix renames the parameter to `props` and re-destructures it in the body\n // (`const <pattern> = props`). If the pattern already binds `props` (e.g. a\n // `...props` rest, a `{ props }` shorthand, or a `{ data: props }` rename), that\n // body binding collides with the new parameter and yields an invalid \"Duplicate\n // declaration props\". No safe rename keeps the `props` name the rule mandates \u2014\n // and an ESLint fixer cannot scope-rename the downstream `props.*` usages a rename\n // would require \u2014 so these patterns are still reported (the inline destructuring is\n // a violation regardless) but WITHOUT an autofix; the developer resolves them by hand.\n const boundNames = new Set<string>()\n\n collectBoundNames(firstParam, boundNames)\n\n const bindsProps = boundNames.has('props')\n\n const objectPattern = firstParam as AnnotatedPattern\n\n context.report({\n node: firstParam,\n messageId: 'destructureOnNewLine',\n fix: bindsProps\n ? undefined\n : (fixer) => {\n const text = sourceCode.getText()\n const annotation = objectPattern.typeAnnotation\n\n const patternStart = objectPattern.range![0]\n const patternEnd = annotation ? annotation.range![0] : objectPattern.range![1]\n const fullEnd = annotation ? annotation.range![1] : objectPattern.range![1]\n\n const patternText = text.slice(patternStart, patternEnd).trim()\n const annotationText = annotation ? sourceCode.getText(annotation) : ''\n\n const fixes = [fixer.replaceTextRange([patternStart, fullEnd], `props${annotationText}`)]\n\n const destructureStatement = `const ${patternText} = props`\n\n // Indentation of the line the component is declared on, used as the base for inserted code.\n const lines = sourceCode.getLines()\n const declarationLine = lines[node.loc!.start.line - 1] ?? ''\n const baseIndent = declarationLine.slice(0, declarationLine.length - declarationLine.trimStart().length)\n const innerIndent = `${baseIndent} `\n\n if (node.body.type === 'BlockStatement') {\n const [firstStatement] = node.body.body\n\n if (firstStatement) {\n const indent = ' '.repeat(firstStatement.loc!.start.column)\n\n fixes.push(fixer.insertTextBefore(firstStatement, `${destructureStatement}\\n\\n${indent}`))\n } else {\n const openBrace = sourceCode.getFirstToken(node.body)!\n\n fixes.push(fixer.insertTextAfter(openBrace, `\\n${innerIndent}${destructureStatement}\\n${baseIndent}`))\n }\n\n return fixes\n }\n\n // Expression-bodied arrow (implicit return) \u2014 wrap it in a block.\n const bodyText = sourceCode.getText(node.body)\n\n fixes.push(\n fixer.replaceText(\n node.body,\n `{\\n${innerIndent}${destructureStatement}\\n\\n${innerIndent}return ${bodyText}\\n${baseIndent}}`,\n ),\n )\n\n return fixes\n },\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsDestructuringNewline\n", "import type { Rule } from 'eslint'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { getAnnotatedParam, getComponentName, getPropsTypeNameFromFunction, isComponent } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\nconst PROPS_SUFFIX = 'Props'\n\nexport const propsTypeName: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Require a React component's props type to be named `<ComponentName>Props` (e.g. `ButtonProps` for `Button`).\",\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n propsTypeNameMismatch: \"A component's props type must be named `{{expected}}`, but it is named `{{actual}}`.\",\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n const check = (node: ComponentFunction): void => {\n if (!isComponent(node)) {\n return\n }\n\n // An anonymous component (e.g. `export default () => ...`) has no name to derive the\n // expected props type name from, so the convention cannot be checked.\n const componentName = getComponentName(node)\n\n if (componentName === null) {\n return\n }\n\n // Only a simple named type reference is checked. An inline object type is the\n // `props-type-reference` rule's concern; qualified names and generics are out of scope and\n // resolve to null. A component with no typed props parameter is likewise not constrained.\n const actual = getPropsTypeNameFromFunction(node)\n\n if (actual === null) {\n return\n }\n\n const expected = `${componentName}${PROPS_SUFFIX}`\n\n if (actual === expected) {\n return\n }\n\n // `getPropsTypeNameFromFunction` only returns non-null when the first parameter carries a\n // named type reference, so an annotated parameter is guaranteed to exist here.\n context.report({\n node: getAnnotatedParam(node.params[0]!),\n messageId: 'propsTypeNameMismatch',\n data: { expected, actual },\n })\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsTypeName\n", "import type { Rule } from 'eslint'\n\nimport type { ComponentFunction } from '../utils/component'\nimport { getAnnotatedParam, getComponentName, isComponent } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n}\n\n// TS-only nodes are not modelled by estree, so we view them through a minimal two-level\n// structural interface: the `TSTypeAnnotation` wrapper a parser attaches to a parameter, and\n// its inner type node. An inline object type is `TSTypeLiteral`; a named type is `TSTypeReference`.\ninterface AnnotatedNode {\n typeAnnotation?: {\n typeAnnotation?: { type?: string }\n }\n}\n\nconst INLINE_OBJECT_TYPE = 'TSTypeLiteral'\n\nexport const propsTypeReference: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Require a React component's props parameter to use a named type (e.g. `ButtonProps`) instead of an inline object type literal.\",\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. When provided, the rule only runs for files whose path matches one of them.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n useNamedPropsType:\n \"Use a named props type (e.g. `{{name}}Props`) instead of an inline object type for this component's props.\",\n useNamedPropsTypeAnonymous: \"Use a named props type instead of an inline object type for this component's props.\",\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n const check = (node: ComponentFunction): void => {\n const firstParam = node.params[0]\n\n if (!firstParam || !isComponent(node)) {\n return\n }\n\n const annotatedParam = getAnnotatedParam(firstParam)\n const innerType = (annotatedParam as AnnotatedNode).typeAnnotation?.typeAnnotation?.type\n\n if (innerType !== INLINE_OBJECT_TYPE) {\n return\n }\n\n const name = getComponentName(node)\n\n context.report(\n name === null\n ? { node: annotatedParam, messageId: 'useNamedPropsTypeAnonymous' }\n : { node: annotatedParam, messageId: 'useNamedPropsType', data: { name } },\n )\n }\n\n return {\n ArrowFunctionExpression: check,\n FunctionDeclaration: check,\n FunctionExpression: check,\n }\n },\n}\n\nexport default propsTypeReference\n", "import type { Rule } from 'eslint'\nimport { existsSync } from 'node:fs'\nimport path from 'node:path'\n\nimport { bodyDeclaresComponent } from '../utils/component'\nimport { matchesAnyGlob } from '../utils/path-match'\nimport type { ExtraTarget, StoryPathOptions } from '../utils/story-path'\nimport { DEFAULT_STORY_PATH_OPTIONS, classifyComponent, deriveExpectedStoryPaths } from '../utils/story-path'\n\ninterface Options {\n paths?: string[]\n ignore?: string[]\n storiesDir?: string\n storySuffix?: string\n storyExtensions?: string[]\n componentSuffix?: string\n requireComponentAst?: boolean\n extraTargets?: ExtraTarget[]\n}\n\n/** Pick the story-path knobs out of the rule options, leaving defaults to the helper. */\nconst toStoryPathOptions = (options: Options): Partial<StoryPathOptions> => {\n const picked: Partial<StoryPathOptions> = {}\n\n if (options.storiesDir !== undefined) {\n picked.storiesDir = options.storiesDir\n }\n\n if (options.storySuffix !== undefined) {\n picked.storySuffix = options.storySuffix\n }\n\n if (options.storyExtensions !== undefined) {\n picked.storyExtensions = options.storyExtensions\n }\n\n if (options.componentSuffix !== undefined) {\n picked.componentSuffix = options.componentSuffix\n }\n\n if (options.extraTargets !== undefined) {\n picked.extraTargets = options.extraTargets\n }\n\n return picked\n}\n\nexport const requireComponentStories: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Require a co-located Storybook story for every dumb component.',\n recommended: true,\n url: 'https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin',\n },\n schema: [\n {\n type: 'object',\n properties: {\n paths: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional globs; when provided the rule only runs for files whose path matches one.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional globs; the rule is skipped for matching files, even if they also match `paths`.',\n },\n storiesDir: {\n type: 'string',\n description: `Story directory name (default '${DEFAULT_STORY_PATH_OPTIONS.storiesDir}').`,\n },\n storySuffix: {\n type: 'string',\n description: `Suffix inserted before the extension (default '${DEFAULT_STORY_PATH_OPTIONS.storySuffix}').`,\n },\n storyExtensions: {\n type: 'array',\n items: { type: 'string' },\n description: 'Extensions a satisfying story file may have, in priority order.',\n },\n componentSuffix: {\n type: 'string',\n description: `Basename suffix a component file must end with (default '${DEFAULT_STORY_PATH_OPTIONS.componentSuffix}'; '' disables).`,\n },\n requireComponentAst: {\n type: 'boolean',\n description: 'When true (default), only require a story for files that actually declare a component.',\n },\n extraTargets: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n componentsDir: { type: 'string' },\n anchorParentDir: { type: 'string' },\n storyMode: { enum: ['feature-root', 'sibling'] },\n },\n required: ['componentsDir', 'storyMode'],\n additionalProperties: false,\n },\n description: 'Extra structured component layouts (componentsDir + optional anchorParentDir + storyMode).',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n missingStory: \"Dumb component '{{component}}' is missing a Storybook story (expected at '{{expected}}').\",\n },\n },\n\n create(context) {\n const options = (context.options[0] ?? {}) as Options\n const paths = options.paths ?? []\n const ignore = options.ignore ?? []\n const requireComponentAst = options.requireComponentAst ?? true\n const storyPathOptions = toStoryPathOptions(options)\n\n // `ignore` takes precedence: skip excluded files even when they also match `paths`.\n if (ignore.length > 0 && matchesAnyGlob(context.filename, ignore)) {\n return {}\n }\n\n if (paths.length > 0 && !matchesAnyGlob(context.filename, paths)) {\n return {}\n }\n\n return {\n Program(program) {\n // Is this file a dumb component that requires a story, and where would the story live?\n if (!classifyComponent(context.filename, storyPathOptions)) {\n return\n }\n\n // Only enforce on files that actually declare a component (avoids flagging stray\n // non-component files placed under a components/ directory).\n if (requireComponentAst && !bodyDeclaresComponent(program.body)) {\n return\n }\n\n const candidates = deriveExpectedStoryPaths(context.filename, storyPathOptions)\n\n // The story is satisfied if ANY candidate (by extension) exists on disk.\n const hasStory = candidates.some((candidate) => {\n return existsSync(candidate)\n })\n\n if (hasStory) {\n return\n }\n\n context.report({\n node: program,\n messageId: 'missingStory',\n data: {\n component: path.posix.basename(context.filename.split('\\\\').join('/')),\n expected: candidates[0] ?? '',\n },\n })\n },\n }\n },\n}\n\nexport default requireComponentStories\n", "import path from 'node:path'\n\n/** Where a story file lives relative to its component. */\nexport type StoryMode = 'feature-root' | 'sibling'\n\n/** A consumer-defined extra component layout (structured \u2014 never a glob). */\nexport interface ExtraTarget {\n /** Immediate parent directory name a component file must sit directly inside. */\n componentsDir: string\n /** Optional grandparent directory name gate (e.g. `features`). */\n anchorParentDir?: string\n /** Where the story is expected for files matched by this target. */\n storyMode: StoryMode\n}\n\nexport interface StoryPathOptions {\n /** Story directory name (default `__stories__`). */\n storiesDir: string\n /** Suffix inserted before the extension (default `.stories`). */\n storySuffix: string\n /** Extensions a satisfying story file may have, in priority order. */\n storyExtensions: string[]\n /** Extensions a file must have to be considered a component. */\n componentExtensions: string[]\n /** Basename suffix a component file must end with (default `-component`; `''` disables). */\n componentSuffix: string\n /** Additional structured component layouts beyond the two built-ins. */\n extraTargets: ExtraTarget[]\n}\n\nexport const DEFAULT_STORY_PATH_OPTIONS: StoryPathOptions = {\n storiesDir: '__stories__',\n storySuffix: '.stories',\n storyExtensions: ['.tsx', '.jsx', '.ts', '.js'],\n componentExtensions: ['.tsx', '.jsx'],\n componentSuffix: '-component',\n extraTargets: [],\n}\n\nconst resolveOptions = (opts?: Partial<StoryPathOptions>): StoryPathOptions => {\n return { ...DEFAULT_STORY_PATH_OPTIONS, ...opts }\n}\n\n/** Normalize OS-native separators to posix so all downstream path logic is deterministic. */\nconst toPosix = (filePath: string): string => {\n return filePath.split('\\\\').join('/')\n}\n\ninterface ParsedComponent {\n /** Posix directory of the file. */\n dir: string\n /** Basename without its extension. */\n base: string\n /** Original extension (e.g. `.tsx`). */\n ext: string\n /** Immediate parent directory name. */\n parent: string\n /** Grandparent directory name. */\n grandparent: string\n /** Great-grandparent directory name. */\n greatGrandparent: string\n}\n\n/** Parse a file path into the segment view the gate and derivation both need. */\nconst parse = (filePath: string): ParsedComponent => {\n const normalized = toPosix(filePath)\n const segments = normalized.split('/')\n const basename = segments[segments.length - 1] ?? ''\n const ext = path.posix.extname(basename)\n const base = ext ? basename.slice(0, -ext.length) : basename\n\n return {\n dir: path.posix.dirname(normalized),\n base,\n ext,\n parent: segments[segments.length - 2] ?? '',\n grandparent: segments[segments.length - 3] ?? '',\n greatGrandparent: segments[segments.length - 4] ?? '',\n }\n}\n\n/** Whether the file passes the component preconditions (extension + name suffix). */\nconst passesComponentPreconditions = (parsed: ParsedComponent, options: StoryPathOptions): boolean => {\n if (!options.componentExtensions.includes(parsed.ext)) {\n return false\n }\n\n return options.componentSuffix === '' || parsed.base.endsWith(options.componentSuffix)\n}\n\n/**\n * Classify a file as a dumb component requiring a story, resolving WHERE its story should live.\n * Returns null when the file is not a component-requiring-a-story under any branch.\n *\n * Exactly one admit-condition may hold:\n * - feature-root: direct child of `components/` whose chain is `features/<f>/components`.\n * - sibling: a `components/default/<name>-component` file (parent `default`, grandparent `components`).\n * - extraTargets: a structured consumer-defined layout.\n */\nexport const classifyComponent = (filePath: string, opts?: Partial<StoryPathOptions>): { mode: StoryMode } | null => {\n const options = resolveOptions(opts)\n const parsed = parse(filePath)\n\n if (!passesComponentPreconditions(parsed, options)) {\n return null\n }\n\n // (a) feature-root \u2014 immediate parent is `components` AND the chain is `features/<feature>/components`.\n if (parsed.parent === 'components' && parsed.greatGrandparent === 'features') {\n return { mode: 'feature-root' }\n }\n\n // (b) sibling \u2014 `components/default/<name>-component.*` (NOT a direct child of `components/`).\n if (parsed.parent === 'default' && parsed.grandparent === 'components') {\n return { mode: 'sibling' }\n }\n\n // (c) extraTargets \u2014 structured layouts (componentsDir + optional anchorParentDir).\n for (const target of options.extraTargets) {\n const parentMatches = parsed.parent === target.componentsDir\n const anchorMatches = target.anchorParentDir == null || parsed.grandparent === target.anchorParentDir\n\n if (parentMatches && anchorMatches) {\n return { mode: target.storyMode }\n }\n }\n\n return null\n}\n\n/**\n * Ordered candidate story paths for a component file. Empty when the file is not a\n * component-requiring-a-story (see {@link classifyComponent}). The story is considered present\n * when ANY candidate exists on disk.\n */\nexport const deriveExpectedStoryPaths = (filePath: string, opts?: Partial<StoryPathOptions>): string[] => {\n const options = resolveOptions(opts)\n const classification = classifyComponent(filePath, options)\n\n if (!classification) {\n return []\n }\n\n const parsed = parse(filePath)\n\n // feature-root: dirname(file) is `.../components`, so its parent is the feature root.\n // sibling: the story dir sits next to the component file itself.\n const storyBaseDir = classification.mode === 'feature-root' ? path.posix.dirname(parsed.dir) : parsed.dir\n const storyDir = path.posix.join(storyBaseDir, options.storiesDir)\n\n return options.storyExtensions.map((extension) => {\n return path.posix.join(storyDir, `${parsed.base}${options.storySuffix}${extension}`)\n })\n}\n", "import type { Rule } from 'eslint'\n\nimport { componentArrowFunction } from './component-arrow-function'\nimport { componentFileOrder } from './component-file-order'\nimport { propsDestructuringBlankLine } from './props-destructuring-blank-line'\nimport { propsDestructuringNewline } from './props-destructuring-newline'\nimport { propsTypeName } from './props-type-name'\nimport { propsTypeReference } from './props-type-reference'\nimport { requireComponentStories } from './require-component-stories'\n\nexport const rules: Record<string, Rule.RuleModule> = {\n 'props-destructuring-newline': propsDestructuringNewline,\n 'props-destructuring-blank-line': propsDestructuringBlankLine,\n 'props-type-reference': propsTypeReference,\n 'props-type-name': propsTypeName,\n 'component-file-order': componentFileOrder,\n 'component-arrow-function': componentArrowFunction,\n 'require-component-stories': requireComponentStories,\n}\n", "import type { ESLint, Linter } from 'eslint'\n\nimport { rules } from './rules'\n\nconst PLUGIN_NAME = '@wl'\n\nconst plugin: ESLint.Plugin & { configs: Record<string, Linter.Config | Linter.Config[]> } = {\n meta: {\n name: '@wl/eslint-plugin',\n version: '0.1.14',\n },\n rules,\n configs: {},\n}\n\n/**\n * Flat-config preset that registers the plugin and turns every rule on, scoped to\n * the files each rule is meant for. It is an array of config blocks, so spread it:\n *\n * @example\n * import wl from '@wl/eslint-plugin'\n *\n * export default [...wl.configs.recommended]\n */\nplugin.configs.recommended = [\n {\n files: ['**/*.tsx'],\n plugins: {\n [PLUGIN_NAME]: plugin,\n },\n rules: {\n [`${PLUGIN_NAME}/props-destructuring-newline`]: 'error',\n [`${PLUGIN_NAME}/props-destructuring-blank-line`]: 'error',\n [`${PLUGIN_NAME}/props-type-reference`]: 'error',\n [`${PLUGIN_NAME}/props-type-name`]: 'error',\n [`${PLUGIN_NAME}/component-file-order`]: 'error',\n // Pages and routes are excluded: route/page modules commonly use `function`\n // declarations (and framework conventions like default-exported page functions).\n [`${PLUGIN_NAME}/component-arrow-function`]: ['error', { ignore: ['**/pages/**', '**/routes/**'] }],\n [`${PLUGIN_NAME}/require-component-stories`]: 'error',\n },\n },\n // Storybook stories legitimately deviate from the component conventions: the\n // imports \u2192 *Props \u2192 component order (meta/args/decorators/render fns), and\n // named templates that reference the *component's* props type (e.g.\n // `const Template = (args: ButtonProps) => ...`) rather than their own\n // `<TemplateName>Props`. So both the ordering and the props-type-name rules\n // would only produce noise there. Every other rule stays enabled for stories.\n {\n files: ['**/*.stories.{ts,tsx}'],\n rules: {\n [`${PLUGIN_NAME}/component-file-order`]: 'off',\n [`${PLUGIN_NAME}/props-type-name`]: 'off',\n },\n },\n]\n\nexport const meta: ESLint.Plugin['meta'] = plugin.meta\nexport const configs: Record<string, Linter.Config | Linter.Config[]> = plugin.configs\nexport { rules }\n\nexport default plugin\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["fn", "PROPS_SUFFIX", "path", "path"]
|
|
7
7
|
}
|
|
@@ -14,6 +14,22 @@ export declare const isComponent: (node: ComponentFunction) => boolean;
|
|
|
14
14
|
* null when no function is found.
|
|
15
15
|
*/
|
|
16
16
|
export declare const getComponentFunction: (node: ESTree.Node | null | undefined) => ComponentFunction | null;
|
|
17
|
+
/**
|
|
18
|
+
* The parameter that actually carries the props type annotation. A default value wraps the
|
|
19
|
+
* parameter in an `AssignmentPattern` (`(props: Props = {})`), moving the annotation onto its
|
|
20
|
+
* `.left`; unwrap one layer so the annotation is found either way.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getAnnotatedParam: (param: ESTree.Pattern) => ESTree.Pattern;
|
|
23
|
+
/** The named type referenced by a component function's first parameter (`Props`), or null. */
|
|
24
|
+
export declare const getPropsTypeNameFromFunction: (node: ComponentFunction) => string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the props type name a component *uses* — the named type on its first parameter —
|
|
27
|
+
* looking through component wrappers (`memo`/`forwardRef`). Returns null when the component is
|
|
28
|
+
* anonymous of props (no parameter) or its props type is not a simple named reference (inline
|
|
29
|
+
* object, qualified name, generic). Mirrors `getDeclaredComponentName`'s node dispatch so a
|
|
30
|
+
* `const`-declared arrow component resolves through its `init`, not the `VariableDeclaration`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const getComponentPropsTypeName: (node: ESTree.Node | null) => string | null;
|
|
17
33
|
/** Unwrap an `export ...` statement to the declaration it wraps (or the statement itself). */
|
|
18
34
|
export declare const unwrapExport: (statement: ESTree.Statement | ESTree.ModuleDeclaration) => ESTree.Node | null;
|
|
19
35
|
/** Whether a single top-level declaration declares a React component. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slip-stream-kit/eslint-plugin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.17",
|
|
5
5
|
"description": "Custom ESLint rules enforcing the white-label frontend architecture conventions",
|
|
6
6
|
"author": "Arthur Saenko <arthur.saenz7@gmail.com> (https://github.com/ArthurSaenz)",
|
|
7
7
|
"license": "MIT",
|
package/readme.md
CHANGED
|
@@ -11,8 +11,8 @@ pnpm add -D @wl/eslint-plugin
|
|
|
11
11
|
## Usage (flat config)
|
|
12
12
|
|
|
13
13
|
Enable everything via the recommended preset. It is an array of config blocks
|
|
14
|
-
(rules scoped to `*.tsx`, with `component-file-order`
|
|
15
|
-
`*.stories.{ts,tsx}`), so spread it:
|
|
14
|
+
(rules scoped to `*.tsx`, with `component-file-order` and `props-type-name`
|
|
15
|
+
turned off for `*.stories.{ts,tsx}`), so spread it:
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
18
|
// eslint.config.js
|
|
@@ -136,12 +136,61 @@ matching files, `ignore` skips matching files (and takes precedence over
|
|
|
136
136
|
}
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
### `props-type-name`
|
|
140
|
+
|
|
141
|
+
A React component's props type must be named **`<ComponentName>Props`** (e.g.
|
|
142
|
+
`ButtonProps` for `Button`). This complements `props-type-reference`: that rule
|
|
143
|
+
requires a _named_ type (not an inline literal); this rule requires that name to
|
|
144
|
+
follow the convention. Report-only.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
// ❌ Incorrect — props type does not match the component name
|
|
148
|
+
const Button = (props: Props) => <button>{props.label}</button>
|
|
149
|
+
function Card({ title }: CardConfig) {
|
|
150
|
+
return <div>{title}</div>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ✅ Correct — `<ComponentName>Props`
|
|
154
|
+
const Button = (props: ButtonProps) => <button>{props.label}</button>
|
|
155
|
+
function Card({ title }: CardProps) {
|
|
156
|
+
return <div>{title}</div>
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The component is detected the same way as the other rules (PascalCase name
|
|
161
|
+
through `memo`/`forwardRef`/`observer` wrappers, or a JSX return). Only a simple
|
|
162
|
+
named type reference on the first parameter is checked: inline object types are
|
|
163
|
+
the `props-type-reference` rule's concern, and anonymous components, untyped
|
|
164
|
+
props, and qualified/generic annotations (`NS.Props`, `FC<Props>`) are left
|
|
165
|
+
alone. An imported props type with a non-conventional name is still flagged —
|
|
166
|
+
use `paths`/`ignore` to exempt it.
|
|
167
|
+
|
|
168
|
+
The recommended preset turns this rule **off for `*.stories.{ts,tsx}`**: story
|
|
169
|
+
templates legitimately reference the component's own props type (e.g.
|
|
170
|
+
`const Template = (args: ButtonProps) => ...`) rather than `<TemplateName>Props`.
|
|
171
|
+
|
|
172
|
+
#### Option: `paths` / `ignore` (optional)
|
|
173
|
+
|
|
174
|
+
Same glob semantics as `component-file-order`: `paths` restricts the rule to
|
|
175
|
+
matching files, `ignore` skips matching files (and takes precedence over
|
|
176
|
+
`paths`).
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
{
|
|
180
|
+
rules: {
|
|
181
|
+
'@wl/props-type-name': ['error', { ignore: ['**/*.stories.tsx'] }],
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
139
186
|
### `component-file-order`
|
|
140
187
|
|
|
141
188
|
Enforce a strict top-level order in files that contain a React component:
|
|
142
|
-
**imports → component props interface/type → component declaration
|
|
143
|
-
|
|
144
|
-
|
|
189
|
+
**imports → component props interface/type → component declaration**, with the
|
|
190
|
+
props interface declared **immediately before** the component — no constants,
|
|
191
|
+
helpers, or other declarations wedged between them. Helpers are allowed _after_
|
|
192
|
+
the component (or between two separate component blocks). Report-only (it does
|
|
193
|
+
not auto-reorder code).
|
|
145
194
|
|
|
146
195
|
```tsx
|
|
147
196
|
// ❌ Incorrect — interface before imports, or component before its interface
|
|
@@ -162,15 +211,18 @@ const Card = (props: CardProps) => {
|
|
|
162
211
|
}
|
|
163
212
|
```
|
|
164
213
|
|
|
165
|
-
The rule activates only when the file actually contains a component.
|
|
166
|
-
interface
|
|
214
|
+
The rule activates only when the file actually contains a component. A
|
|
215
|
+
component's props interface is matched by the **type its parameter actually
|
|
216
|
+
references** (e.g. `Props` in `(props: Props)`), not by a name convention — so an
|
|
217
|
+
interface named anything is enforced, as long as the component uses it. (When the
|
|
218
|
+
parameter has no resolvable named type, the rule falls back to looking for a
|
|
219
|
+
`<ComponentName>Props` interface.)
|
|
167
220
|
|
|
168
221
|
When the first component's props type is **imported** (e.g.
|
|
169
222
|
`import type { CardProps } from './types'`) instead of declared in the file,
|
|
170
223
|
there is no in-file interface to anchor against — so the component itself must
|
|
171
224
|
sit immediately after the imports, with no stray top-level definitions wedged in
|
|
172
|
-
between. Only the first component is anchored this way
|
|
173
|
-
the conventional `<ComponentName>Props` name for the check to apply.
|
|
225
|
+
between. Only the first component is anchored this way.
|
|
174
226
|
|
|
175
227
|
```tsx
|
|
176
228
|
// ❌ Incorrect — props imported, but a stray const sits before the component
|