eslint-plugin-absolute 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,83 +0,0 @@
1
- import { TSESLint, TSESTree } from "@typescript-eslint/utils";
2
-
3
- type Options = [];
4
- type MessageIds = "noExplicitReturnType";
5
-
6
- type AnyFunctionNode =
7
- | TSESTree.FunctionDeclaration
8
- | TSESTree.FunctionExpression
9
- | TSESTree.ArrowFunctionExpression;
10
-
11
- export const noExplicitReturnTypes: TSESLint.RuleModule<MessageIds, Options> = {
12
- create(context) {
13
- const hasSingleObjectReturn = (body: TSESTree.BlockStatement) => {
14
- const returnStatements = body.body.filter(
15
- (stmt) => stmt.type === "ReturnStatement"
16
- );
17
-
18
- if (returnStatements.length !== 1) {
19
- return false;
20
- }
21
-
22
- const [returnStmt] = returnStatements;
23
- return returnStmt?.argument?.type === "ObjectExpression";
24
- };
25
-
26
- return {
27
- "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
28
- node: AnyFunctionNode
29
- ) {
30
- const { returnType } = node;
31
- if (!returnType) {
32
- return;
33
- }
34
-
35
- // Allow type predicate annotations for type guards.
36
- const { typeAnnotation } = returnType;
37
- if (
38
- typeAnnotation &&
39
- typeAnnotation.type === "TSTypePredicate"
40
- ) {
41
- return;
42
- }
43
-
44
- // Allow if it's an arrow function that directly returns an object literal.
45
- if (
46
- node.type === "ArrowFunctionExpression" &&
47
- node.expression === true &&
48
- node.body.type === "ObjectExpression"
49
- ) {
50
- return;
51
- }
52
-
53
- // Allow if the function has a block body with a single return statement that returns an object literal.
54
- if (
55
- node.body &&
56
- node.body.type === "BlockStatement" &&
57
- hasSingleObjectReturn(node.body)
58
- ) {
59
- return;
60
- }
61
-
62
- // Otherwise, report an error.
63
- context.report({
64
- messageId: "noExplicitReturnType",
65
- node: returnType
66
- });
67
- }
68
- };
69
- },
70
- defaultOptions: [],
71
- meta: {
72
- docs: {
73
- description:
74
- "Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects)."
75
- },
76
- messages: {
77
- noExplicitReturnType:
78
- "Explicit return types are disallowed; rely on TypeScript's inference instead."
79
- },
80
- schema: [],
81
- type: "suggestion"
82
- }
83
- };
@@ -1,68 +0,0 @@
1
- import { TSESLint, TSESTree } from "@typescript-eslint/utils";
2
-
3
- type Options = [];
4
- type MessageIds = "noInlinePropTypes";
5
-
6
- export const noInlinePropTypes: TSESLint.RuleModule<MessageIds, Options> = {
7
- create(context) {
8
- /**
9
- * Checks the node representing a parameter to determine if it is an ObjectPattern with an inline type literal.
10
- * @param {ASTNode} param The parameter node from the function declaration/expression.
11
- */
12
- const checkParameter = (param: TSESTree.Parameter) => {
13
- // Ensure we are dealing with a destructured object pattern with a type annotation.
14
- if (
15
- param.type !== "ObjectPattern" ||
16
- !param.typeAnnotation ||
17
- param.typeAnnotation.type !== "TSTypeAnnotation"
18
- ) {
19
- return;
20
- }
21
-
22
- // The actual type annotation node (for example, { mode: string } yields a TSTypeLiteral).
23
- const annotation = param.typeAnnotation.typeAnnotation;
24
- // If the type is an inline object (TSTypeLiteral), we want to report it.
25
- if (annotation.type === "TSTypeLiteral") {
26
- context.report({
27
- messageId: "noInlinePropTypes",
28
- node: param
29
- });
30
- }
31
- };
32
-
33
- return {
34
- // Applies to FunctionDeclaration, ArrowFunctionExpression, and FunctionExpression nodes.
35
- "FunctionDeclaration, ArrowFunctionExpression, FunctionExpression"(
36
- node:
37
- | TSESTree.FunctionDeclaration
38
- | TSESTree.ArrowFunctionExpression
39
- | TSESTree.FunctionExpression
40
- ) {
41
- // It is common to define props as the first parameter.
42
- if (node.params.length === 0) {
43
- return;
44
- }
45
-
46
- const [firstParam] = node.params;
47
- if (!firstParam) {
48
- return;
49
- }
50
-
51
- checkParameter(firstParam);
52
- }
53
- };
54
- },
55
- defaultOptions: [],
56
- meta: {
57
- docs: {
58
- description:
59
- "Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
60
- },
61
- messages: {
62
- noInlinePropTypes:
63
- "Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
64
- },
65
- schema: [],
66
- type: "suggestion"
67
- }
68
- };
@@ -1,80 +0,0 @@
1
- import { TSESLint, TSESTree } from "@typescript-eslint/utils";
2
-
3
- /**
4
- * @fileoverview Disallow grouping CSS style objects in a single export.
5
- * Instead of exporting an object that contains multiple CSS style objects,
6
- * export each style separately.
7
- */
8
-
9
- type Options = [];
10
- type MessageIds = "noMultiStyleObjects";
11
-
12
- const getPropertyName = (prop: TSESTree.Property) => {
13
- const { key } = prop;
14
- if (key.type === "Identifier") {
15
- return key.name;
16
- }
17
- if (key.type === "Literal" && typeof key.value === "string") {
18
- return key.value;
19
- }
20
- return null;
21
- };
22
-
23
- export const noMultiStyleObjects: TSESLint.RuleModule<MessageIds, Options> = {
24
- create(context) {
25
- /**
26
- * Checks if the given ObjectExpression node contains multiple properties
27
- * that look like CSS style objects (i.e. property keys ending with "Style").
28
- */
29
- const checkObjectExpression = (node: TSESTree.ObjectExpression) => {
30
- if (!node.properties.length) {
31
- return;
32
- }
33
-
34
- const cssStyleProperties = node.properties.filter((prop) => {
35
- if (prop.type !== "Property") {
36
- return false;
37
- }
38
- const name = getPropertyName(prop);
39
- return name !== null && name.endsWith("Style");
40
- });
41
-
42
- if (cssStyleProperties.length > 1) {
43
- context.report({
44
- messageId: "noMultiStyleObjects",
45
- node
46
- });
47
- }
48
- };
49
-
50
- return {
51
- // Check default exports that are object literals.
52
- ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration) {
53
- const { declaration } = node;
54
- if (declaration && declaration.type === "ObjectExpression") {
55
- checkObjectExpression(declaration);
56
- }
57
- },
58
- // Optionally, also check for object literals returned from exported functions.
59
- ReturnStatement(node: TSESTree.ReturnStatement) {
60
- const { argument } = node;
61
- if (argument && argument.type === "ObjectExpression") {
62
- checkObjectExpression(argument);
63
- }
64
- }
65
- };
66
- },
67
- defaultOptions: [],
68
- meta: {
69
- docs: {
70
- description:
71
- "Disallow grouping CSS style objects in a single export; export each style separately."
72
- },
73
- messages: {
74
- noMultiStyleObjects:
75
- "Do not group CSS style objects in a single export; export each style separately."
76
- },
77
- schema: [], // no options,
78
- type: "problem"
79
- }
80
- };
@@ -1,205 +0,0 @@
1
- import { TSESLint, TSESTree, AST_NODE_TYPES } from "@typescript-eslint/utils";
2
-
3
- /**
4
- * @fileoverview Disallow nested functions that return non-component, non-singular JSX
5
- * to enforce one component per file.
6
- */
7
-
8
- type Options = [];
9
- type MessageIds =
10
- | "nestedFunctionJSX"
11
- | "nestedArrowJSX"
12
- | "nestedArrowFragment";
13
-
14
- type AnyFunctionNode =
15
- | TSESTree.FunctionDeclaration
16
- | TSESTree.FunctionExpression
17
- | TSESTree.ArrowFunctionExpression;
18
-
19
- export const noNestedJSXReturn: TSESLint.RuleModule<MessageIds, Options> = {
20
- create(context) {
21
- // Returns true if the node is a JSX element or fragment.
22
- const isJSX = (
23
- node: TSESTree.Node | null | undefined
24
- ): node is TSESTree.JSXElement | TSESTree.JSXFragment =>
25
- node !== null &&
26
- node !== undefined &&
27
- (node.type === AST_NODE_TYPES.JSXElement ||
28
- node.type === AST_NODE_TYPES.JSXFragment);
29
-
30
- const getLeftmostJSXIdentifier = (
31
- name: TSESTree.JSXTagNameExpression
32
- ) => {
33
- let current: TSESTree.JSXTagNameExpression = name;
34
- while (current.type === AST_NODE_TYPES.JSXMemberExpression) {
35
- current = current.object;
36
- }
37
- if (current.type === AST_NODE_TYPES.JSXIdentifier) {
38
- return current;
39
- }
40
- return null;
41
- };
42
-
43
- // Returns true if the JSX element is a component (its tag name starts with an uppercase letter).
44
- const isJSXComponentElement = (
45
- node: TSESTree.Node | null | undefined
46
- ) => {
47
- if (!node || node.type !== AST_NODE_TYPES.JSXElement) {
48
- return false;
49
- }
50
- const opening = node.openingElement;
51
- const nameNode = opening.name;
52
-
53
- if (nameNode.type === AST_NODE_TYPES.JSXIdentifier) {
54
- return /^[A-Z]/.test(nameNode.name);
55
- }
56
-
57
- const leftmost = getLeftmostJSXIdentifier(nameNode);
58
- if (!leftmost) {
59
- return false;
60
- }
61
- return /^[A-Z]/.test(leftmost.name);
62
- };
63
-
64
- const hasNoMeaningfulChildren = (children: TSESTree.JSXChild[]) => {
65
- const filtered = children.filter((child) => {
66
- if (child.type === AST_NODE_TYPES.JSXText) {
67
- return child.value.trim() !== "";
68
- }
69
- return true;
70
- });
71
- return filtered.length === 0;
72
- };
73
-
74
- // Returns true if the returned JSX is singular.
75
- // For both JSXElement and JSXFragment, singular means 0 or 1 non-whitespace child.
76
- const isSingularJSXReturn = (
77
- node: TSESTree.JSXElement | TSESTree.JSXFragment
78
- ) => {
79
- if (!isJSX(node)) return false;
80
-
81
- const children = node.children.filter((child) => {
82
- if (child.type === AST_NODE_TYPES.JSXText) {
83
- return child.value.trim() !== "";
84
- }
85
- return true;
86
- });
87
-
88
- // If there are no children, it's singular.
89
- if (children.length === 0) {
90
- return true;
91
- }
92
-
93
- // Check if the returned element has exactly one child.
94
- if (children.length !== 1) {
95
- return false;
96
- }
97
-
98
- const [child] = children;
99
- if (!child) {
100
- return false;
101
- }
102
-
103
- // If the singular child is also a JSX element or fragment,
104
- // ensure that it doesn't have any meaningful children.
105
- if (
106
- child.type === AST_NODE_TYPES.JSXElement ||
107
- child.type === AST_NODE_TYPES.JSXFragment
108
- ) {
109
- return hasNoMeaningfulChildren(child.children);
110
- }
111
- // If it's not a JSX element (maybe a simple expression), it's acceptable.
112
- return true;
113
- };
114
-
115
- // Stack to track nested function nodes.
116
- const functionStack: AnyFunctionNode[] = [];
117
- const pushFunction = (node: AnyFunctionNode) => {
118
- functionStack.push(node);
119
- };
120
- const popFunction = () => {
121
- functionStack.pop();
122
- };
123
-
124
- return {
125
- // For implicit returns in arrow functions, use the same checks.
126
- "ArrowFunctionExpression > JSXElement"(node: TSESTree.JSXElement) {
127
- if (functionStack.length <= 1) {
128
- return;
129
- }
130
- if (
131
- !isJSXComponentElement(node) &&
132
- !isSingularJSXReturn(node)
133
- ) {
134
- context.report({
135
- messageId: "nestedArrowJSX",
136
- node
137
- });
138
- }
139
- },
140
- "ArrowFunctionExpression > JSXFragment"(
141
- node: TSESTree.JSXFragment
142
- ) {
143
- if (functionStack.length <= 1) {
144
- return;
145
- }
146
- if (!isSingularJSXReturn(node)) {
147
- context.report({
148
- messageId: "nestedArrowFragment",
149
- node
150
- });
151
- }
152
- },
153
- "ArrowFunctionExpression:exit"() {
154
- popFunction();
155
- },
156
- "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
157
- node: AnyFunctionNode
158
- ) {
159
- pushFunction(node);
160
- },
161
- "FunctionDeclaration:exit"() {
162
- popFunction();
163
- },
164
- "FunctionExpression:exit"() {
165
- popFunction();
166
- },
167
- // For explicit return statements, report if the returned JSX is not a component and not singular.
168
- ReturnStatement(node: TSESTree.ReturnStatement) {
169
- if (functionStack.length <= 1) {
170
- return;
171
- }
172
- const { argument } = node;
173
- if (!isJSX(argument)) {
174
- return;
175
- }
176
- if (
177
- !isJSXComponentElement(argument) &&
178
- !isSingularJSXReturn(argument)
179
- ) {
180
- context.report({
181
- messageId: "nestedFunctionJSX",
182
- node
183
- });
184
- }
185
- }
186
- };
187
- },
188
- defaultOptions: [],
189
- meta: {
190
- docs: {
191
- description:
192
- "Disallow nested functions that return non-component, non-singular JSX to enforce one component per file"
193
- },
194
- messages: {
195
- nestedArrowFragment:
196
- "Nested arrow function returning a non-singular JSX fragment detected. Extract it into its own component.",
197
- nestedArrowJSX:
198
- "Nested arrow function returning non-component, non-singular JSX detected. Extract it into its own component.",
199
- nestedFunctionJSX:
200
- "Nested function returning non-component, non-singular JSX detected. Extract it into its own component."
201
- },
202
- schema: [],
203
- type: "problem"
204
- }
205
- };
@@ -1,63 +0,0 @@
1
- import { TSESLint, TSESTree } from "@typescript-eslint/utils";
2
-
3
- type Options = [];
4
- type MessageIds = "useLogicalAnd";
5
-
6
- export const noOrNoneComponent: TSESLint.RuleModule<MessageIds, Options> = {
7
- create(context) {
8
- return {
9
- ConditionalExpression(node: TSESTree.ConditionalExpression) {
10
- const { alternate } = node;
11
-
12
- // Check if alternate is explicitly null or undefined
13
- const isNullAlternate =
14
- alternate &&
15
- alternate.type === "Literal" &&
16
- alternate.value === null;
17
-
18
- const isUndefinedAlternate =
19
- alternate &&
20
- alternate.type === "Identifier" &&
21
- alternate.name === "undefined";
22
-
23
- if (!isNullAlternate && !isUndefinedAlternate) {
24
- return;
25
- }
26
-
27
- // Check if the node is within a JSX expression container.
28
- const { parent } = node;
29
- if (!parent || parent.type !== "JSXExpressionContainer") {
30
- return;
31
- }
32
-
33
- const containerParent = parent.parent;
34
- // Only flag if the JSXExpressionContainer is used as a child,
35
- // not as a prop (i.e. not within a JSXAttribute)
36
- if (
37
- containerParent &&
38
- containerParent.type !== "JSXAttribute"
39
- ) {
40
- context.report({
41
- messageId: "useLogicalAnd",
42
- node
43
- });
44
- }
45
- }
46
- };
47
- },
48
- defaultOptions: [],
49
- meta: {
50
- docs: {
51
- description:
52
- "Prefer using logical && operator over ternary with null/undefined for conditional JSX rendering."
53
- },
54
- messages: {
55
- useLogicalAnd:
56
- "Prefer using the logical '&&' operator instead of a ternary with null/undefined for conditional rendering."
57
- },
58
- schema: [],
59
- type: "suggestion"
60
- }
61
- };
62
-
63
- // TODO : Add a fix function to this rule, it needs a deep unconflicting fix becasue of react/jsx-no-leaked-render, it needs to explicitly be === something like that
@@ -1,131 +0,0 @@
1
- /**
2
- * @fileoverview Disallow the "transition" property in objects typed as CSSProperties.
3
- *
4
- * This rule inspects VariableDeclarators where the identifier has a type annotation that
5
- * includes "CSSProperties" (either as a TSTypeReference or by a text check fallback).
6
- * It then checks if the initializer is an object literal containing a property named "transition".
7
- *
8
- * This is intended to help avoid conflicts with react-spring.
9
- */
10
-
11
- import { TSESLint, TSESTree } from "@typescript-eslint/utils";
12
-
13
- type Options = [];
14
- type MessageIds = "forbiddenTransition";
15
-
16
- const getKeyName = (prop: TSESTree.Property) => {
17
- if (prop.key.type === "Identifier") {
18
- return prop.key.name;
19
- }
20
- if (prop.key.type !== "Literal") {
21
- return null;
22
- }
23
- return typeof prop.key.value === "string"
24
- ? prop.key.value
25
- : String(prop.key.value);
26
- };
27
-
28
- const checkPropForTransition = (
29
- context: TSESLint.RuleContext<MessageIds, Options>,
30
- prop: TSESTree.Property
31
- ) => {
32
- if (prop.computed) {
33
- return;
34
- }
35
- const keyName = getKeyName(prop);
36
- if (keyName === "transition") {
37
- context.report({
38
- messageId: "forbiddenTransition",
39
- node: prop
40
- });
41
- }
42
- };
43
-
44
- export const noTransitionCSSProperties: TSESLint.RuleModule<
45
- MessageIds,
46
- Options
47
- > = {
48
- create(context) {
49
- const { sourceCode } = context;
50
-
51
- const isCSSPropertiesType = (typeAnnotation: TSESTree.TypeNode) => {
52
- if (typeAnnotation.type !== "TSTypeReference") {
53
- return false;
54
- }
55
-
56
- const { typeName } = typeAnnotation;
57
-
58
- if (
59
- typeName.type === "Identifier" &&
60
- typeName.name === "CSSProperties"
61
- ) {
62
- return true;
63
- }
64
-
65
- return (
66
- typeName.type === "TSQualifiedName" &&
67
- typeName.right &&
68
- typeName.right.type === "Identifier" &&
69
- typeName.right.name === "CSSProperties"
70
- );
71
- };
72
-
73
- return {
74
- VariableDeclarator(node: TSESTree.VariableDeclarator) {
75
- // Ensure the variable identifier exists, is an Identifier, and has a type annotation.
76
- if (
77
- !node.id ||
78
- node.id.type !== "Identifier" ||
79
- !node.id.typeAnnotation
80
- ) {
81
- return;
82
- }
83
-
84
- const { typeAnnotation } = node.id.typeAnnotation;
85
-
86
- // Check if the type annotation is CSSProperties
87
- let isStyleType = isCSSPropertiesType(typeAnnotation);
88
-
89
- // Fallback: if the AST shape doesn't match, check the raw text of the annotation.
90
- if (!isStyleType) {
91
- const annotationText = sourceCode.getText(
92
- node.id.typeAnnotation
93
- );
94
- isStyleType = annotationText.includes("CSSProperties");
95
- }
96
-
97
- if (!isStyleType) {
98
- return;
99
- }
100
-
101
- // Check that the initializer is an object literal.
102
- const { init } = node;
103
- if (!init || init.type !== "ObjectExpression") {
104
- return;
105
- }
106
-
107
- const properties = init.properties.filter(
108
- (prop): prop is TSESTree.Property =>
109
- prop.type === "Property"
110
- );
111
-
112
- properties.forEach((prop) => {
113
- checkPropForTransition(context, prop);
114
- });
115
- }
116
- };
117
- },
118
- defaultOptions: [],
119
- meta: {
120
- docs: {
121
- description:
122
- "Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
123
- },
124
- messages: {
125
- forbiddenTransition:
126
- "Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
127
- },
128
- schema: [], // no options,
129
- type: "problem"
130
- }
131
- };
@@ -1,65 +0,0 @@
1
- import { TSESLint, TSESTree, AST_NODE_TYPES } from "@typescript-eslint/utils";
2
-
3
- type Options = [];
4
- type MessageIds = "unnecessaryDivWrapper";
5
-
6
- export const noUnnecessaryDiv: TSESLint.RuleModule<MessageIds, Options> = {
7
- create(context) {
8
- const isDivElement = (node: TSESTree.JSXElement) => {
9
- const nameNode = node.openingElement.name;
10
- return (
11
- nameNode.type === AST_NODE_TYPES.JSXIdentifier &&
12
- nameNode.name === "div"
13
- );
14
- };
15
-
16
- const isMeaningfulChild = (child: TSESTree.JSXChild) => {
17
- if (child.type === AST_NODE_TYPES.JSXText) {
18
- return child.value.trim() !== "";
19
- }
20
- return true;
21
- };
22
-
23
- const getMeaningfulChildren = (node: TSESTree.JSXElement) =>
24
- node.children.filter(isMeaningfulChild);
25
-
26
- return {
27
- JSXElement(node: TSESTree.JSXElement) {
28
- if (!isDivElement(node)) {
29
- return;
30
- }
31
-
32
- const meaningfulChildren = getMeaningfulChildren(node);
33
-
34
- if (meaningfulChildren.length !== 1) {
35
- return;
36
- }
37
-
38
- const [onlyChild] = meaningfulChildren;
39
- if (!onlyChild) {
40
- return;
41
- }
42
-
43
- if (onlyChild.type === AST_NODE_TYPES.JSXElement) {
44
- context.report({
45
- messageId: "unnecessaryDivWrapper",
46
- node
47
- });
48
- }
49
- }
50
- };
51
- },
52
- defaultOptions: [],
53
- meta: {
54
- docs: {
55
- description:
56
- "Flag unnecessary <div> wrappers that enclose a single JSX element. Remove the wrapper if it doesn't add semantic or functional value, or replace it with a semantic element if wrapping is needed."
57
- },
58
- messages: {
59
- unnecessaryDivWrapper:
60
- "Unnecessary <div> wrapper detected. Remove it if not needed, or replace with a semantic element that reflects its purpose."
61
- },
62
- schema: [],
63
- type: "suggestion"
64
- }
65
- };