eslint-plugin-absolute 0.7.0 → 0.9.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.
- package/dist/index.js +335 -130
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,26 +1,83 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// src/rules/
|
|
2
|
+
// src/rules/angular-one-feature-per-file.ts
|
|
3
3
|
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
4
|
+
var FEATURE_DECORATOR_NAMES = new Set([
|
|
5
|
+
"Component",
|
|
6
|
+
"Directive",
|
|
7
|
+
"Injectable",
|
|
8
|
+
"NgModule",
|
|
9
|
+
"Pipe"
|
|
10
|
+
]);
|
|
11
|
+
var getDecoratorName = (decorator) => {
|
|
12
|
+
const { expression } = decorator;
|
|
13
|
+
if (expression.type === AST_NODE_TYPES.CallExpression && expression.callee.type === AST_NODE_TYPES.Identifier) {
|
|
14
|
+
return expression.callee.name;
|
|
15
|
+
}
|
|
16
|
+
if (expression.type === AST_NODE_TYPES.Identifier) {
|
|
17
|
+
return expression.name;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
var isFeatureClass = (node) => {
|
|
22
|
+
if (!node.decorators || node.decorators.length === 0)
|
|
23
|
+
return false;
|
|
24
|
+
return node.decorators.some((decorator) => {
|
|
25
|
+
const name = getDecoratorName(decorator);
|
|
26
|
+
return name !== null && FEATURE_DECORATOR_NAMES.has(name);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var angularOneFeaturePerFile = {
|
|
30
|
+
create(context) {
|
|
31
|
+
const seenFeatures = [];
|
|
32
|
+
return {
|
|
33
|
+
ClassDeclaration(node) {
|
|
34
|
+
if (!isFeatureClass(node))
|
|
35
|
+
return;
|
|
36
|
+
seenFeatures.push(node);
|
|
37
|
+
if (seenFeatures.length === 1)
|
|
38
|
+
return;
|
|
39
|
+
context.report({
|
|
40
|
+
messageId: "multiFeature",
|
|
41
|
+
node: node.id ?? node
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
defaultOptions: [],
|
|
47
|
+
meta: {
|
|
48
|
+
docs: {
|
|
49
|
+
description: 'Disallow defining more than one Angular feature class (@Component, @Directive, @Pipe, @Injectable, @NgModule) per file. Mirrors the Angular Style Guide\'s Single Responsibility / Rule of One. Test and Storybook files legitimately define stub/host classes alongside the subject under test \u2014 disable this rule for those files via an ESLint override (e.g., `{ files: ["**/*.spec.ts", "**/*.stories.ts"], rules: { "absolute/angular-one-feature-per-file": "off" } }`).'
|
|
50
|
+
},
|
|
51
|
+
messages: {
|
|
52
|
+
multiFeature: "Only one Angular feature class is allowed per file. Move this class into its own file (Angular Style Guide: Single Responsibility / Rule of One)."
|
|
53
|
+
},
|
|
54
|
+
schema: [],
|
|
55
|
+
type: "problem"
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/rules/no-nested-jsx-return.ts
|
|
60
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
|
|
4
61
|
var noNestedJSXReturn = {
|
|
5
62
|
create(context) {
|
|
6
|
-
const isJSX = (node) => node !== null && node !== undefined && (node.type ===
|
|
63
|
+
const isJSX = (node) => node !== null && node !== undefined && (node.type === AST_NODE_TYPES2.JSXElement || node.type === AST_NODE_TYPES2.JSXFragment);
|
|
7
64
|
const getLeftmostJSXIdentifier = (name) => {
|
|
8
65
|
let current = name;
|
|
9
|
-
while (current.type ===
|
|
66
|
+
while (current.type === AST_NODE_TYPES2.JSXMemberExpression) {
|
|
10
67
|
current = current.object;
|
|
11
68
|
}
|
|
12
|
-
if (current.type ===
|
|
69
|
+
if (current.type === AST_NODE_TYPES2.JSXIdentifier) {
|
|
13
70
|
return current;
|
|
14
71
|
}
|
|
15
72
|
return null;
|
|
16
73
|
};
|
|
17
74
|
const isJSXComponentElement = (node) => {
|
|
18
|
-
if (!node || node.type !==
|
|
75
|
+
if (!node || node.type !== AST_NODE_TYPES2.JSXElement) {
|
|
19
76
|
return false;
|
|
20
77
|
}
|
|
21
78
|
const opening = node.openingElement;
|
|
22
79
|
const nameNode = opening.name;
|
|
23
|
-
if (nameNode.type ===
|
|
80
|
+
if (nameNode.type === AST_NODE_TYPES2.JSXIdentifier) {
|
|
24
81
|
return /^[A-Z]/.test(nameNode.name);
|
|
25
82
|
}
|
|
26
83
|
const leftmost = getLeftmostJSXIdentifier(nameNode);
|
|
@@ -31,7 +88,7 @@ var noNestedJSXReturn = {
|
|
|
31
88
|
};
|
|
32
89
|
const hasNoMeaningfulChildren = (children) => {
|
|
33
90
|
const filtered = children.filter((child) => {
|
|
34
|
-
if (child.type ===
|
|
91
|
+
if (child.type === AST_NODE_TYPES2.JSXText) {
|
|
35
92
|
return child.value.trim() !== "";
|
|
36
93
|
}
|
|
37
94
|
return true;
|
|
@@ -42,7 +99,7 @@ var noNestedJSXReturn = {
|
|
|
42
99
|
if (!isJSX(node))
|
|
43
100
|
return false;
|
|
44
101
|
const children = node.children.filter((child2) => {
|
|
45
|
-
if (child2.type ===
|
|
102
|
+
if (child2.type === AST_NODE_TYPES2.JSXText) {
|
|
46
103
|
return child2.value.trim() !== "";
|
|
47
104
|
}
|
|
48
105
|
return true;
|
|
@@ -57,7 +114,7 @@ var noNestedJSXReturn = {
|
|
|
57
114
|
if (!child) {
|
|
58
115
|
return false;
|
|
59
116
|
}
|
|
60
|
-
if (child.type ===
|
|
117
|
+
if (child.type === AST_NODE_TYPES2.JSXElement || child.type === AST_NODE_TYPES2.JSXFragment) {
|
|
61
118
|
return hasNoMeaningfulChildren(child.children);
|
|
62
119
|
}
|
|
63
120
|
return true;
|
|
@@ -356,36 +413,29 @@ var hasDuplicateNames = (names) => {
|
|
|
356
413
|
}
|
|
357
414
|
return false;
|
|
358
415
|
};
|
|
416
|
+
var getStaticPropertyKeyName = (property) => {
|
|
417
|
+
if (property.key.type === "Identifier")
|
|
418
|
+
return property.key.name;
|
|
419
|
+
if (property.key.type === "Literal") {
|
|
420
|
+
const { value } = property.key;
|
|
421
|
+
return typeof value === "string" ? value : String(value);
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
};
|
|
425
|
+
var isAccessorPairOnly = (kinds) => kinds.length === 2 && kinds.includes("get") && kinds.includes("set");
|
|
359
426
|
var hasDuplicatePropertyNames = (properties) => {
|
|
360
427
|
const kindsByName = new Map;
|
|
361
|
-
|
|
362
|
-
if (property.type !== "Property")
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
keyName = property.key.name;
|
|
368
|
-
} else if (property.key.type === "Literal") {
|
|
369
|
-
const { value } = property.key;
|
|
370
|
-
keyName = typeof value === "string" ? value : String(value);
|
|
371
|
-
}
|
|
372
|
-
if (keyName === null) {
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
428
|
+
properties.forEach((property) => {
|
|
429
|
+
if (property.type !== "Property")
|
|
430
|
+
return;
|
|
431
|
+
const keyName = getStaticPropertyKeyName(property);
|
|
432
|
+
if (keyName === null)
|
|
433
|
+
return;
|
|
375
434
|
const kinds = kindsByName.get(keyName) ?? [];
|
|
376
435
|
kinds.push(property.kind);
|
|
377
436
|
kindsByName.set(keyName, kinds);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (kinds.length === 1) {
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (kinds.length === 2 && kinds.includes("get") && kinds.includes("set")) {
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
|
-
return false;
|
|
437
|
+
});
|
|
438
|
+
return [...kindsByName.values()].some((kinds) => kinds.length > 1 && !isAccessorPairOnly(kinds));
|
|
389
439
|
};
|
|
390
440
|
var sortKeysFixable = {
|
|
391
441
|
create(context) {
|
|
@@ -547,13 +597,11 @@ var sortKeysFixable = {
|
|
|
547
597
|
const left = ancestor.type === "ForStatement" ? ancestor.init : ancestor.left;
|
|
548
598
|
if (!left)
|
|
549
599
|
return;
|
|
550
|
-
if (left.type
|
|
551
|
-
for (const declaration of left.declarations) {
|
|
552
|
-
addBoundIdentifiers(declaration.id, stableLocals);
|
|
553
|
-
}
|
|
554
|
-
} else {
|
|
600
|
+
if (left.type !== "VariableDeclaration") {
|
|
555
601
|
addBoundIdentifiers(left, stableLocals);
|
|
602
|
+
return;
|
|
556
603
|
}
|
|
604
|
+
left.declarations.forEach((declaration) => addBoundIdentifiers(declaration.id, stableLocals));
|
|
557
605
|
};
|
|
558
606
|
const addCatchClauseBindings = (ancestor, stableLocals) => {
|
|
559
607
|
if (ancestor.type !== "CatchClause" || !ancestor.param)
|
|
@@ -604,10 +652,8 @@ var sortKeysFixable = {
|
|
|
604
652
|
};
|
|
605
653
|
const isPureLocalVariableStatement = (statement, stableLocals) => {
|
|
606
654
|
for (const declaration of statement.declarations) {
|
|
607
|
-
if (declaration.init) {
|
|
608
|
-
|
|
609
|
-
return false;
|
|
610
|
-
}
|
|
655
|
+
if (declaration.init && !isPureRuntimeExpression(declaration.init, stableLocals)) {
|
|
656
|
+
return false;
|
|
611
657
|
}
|
|
612
658
|
addBoundIdentifiers(declaration.id, stableLocals);
|
|
613
659
|
}
|
|
@@ -708,9 +754,7 @@ var sortKeysFixable = {
|
|
|
708
754
|
return;
|
|
709
755
|
}
|
|
710
756
|
if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node)) {
|
|
711
|
-
|
|
712
|
-
addTsBoundIdentifiers(element, stableLocals);
|
|
713
|
-
}
|
|
757
|
+
node.elements.forEach((element) => addTsBoundIdentifiers(element, stableLocals));
|
|
714
758
|
}
|
|
715
759
|
};
|
|
716
760
|
const getCalleeIdentifier = (callee) => {
|
|
@@ -789,20 +833,21 @@ var sortKeysFixable = {
|
|
|
789
833
|
return false;
|
|
790
834
|
return isPureTsExpression(expression.right, stableLocals);
|
|
791
835
|
};
|
|
836
|
+
const isPureTsVariableStatement = (statement, stableLocals) => {
|
|
837
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
838
|
+
if (declaration.initializer && !isPureTsExpression(declaration.initializer, stableLocals)) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
addTsBoundIdentifiers(declaration.name, stableLocals);
|
|
842
|
+
}
|
|
843
|
+
return true;
|
|
844
|
+
};
|
|
792
845
|
const isPureTsStatement = (statement, stableLocals) => {
|
|
793
846
|
if (ts.isReturnStatement(statement)) {
|
|
794
847
|
return !statement.expression || isPureTsExpression(statement.expression, stableLocals);
|
|
795
848
|
}
|
|
796
849
|
if (ts.isVariableStatement(statement)) {
|
|
797
|
-
|
|
798
|
-
if (declaration.initializer) {
|
|
799
|
-
if (!isPureTsExpression(declaration.initializer, stableLocals)) {
|
|
800
|
-
return false;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
addTsBoundIdentifiers(declaration.name, stableLocals);
|
|
804
|
-
}
|
|
805
|
-
return true;
|
|
850
|
+
return isPureTsVariableStatement(statement, stableLocals);
|
|
806
851
|
}
|
|
807
852
|
if (ts.isExpressionStatement(statement)) {
|
|
808
853
|
return isPureTsLocalAssignment(statement.expression, stableLocals);
|
|
@@ -1059,20 +1104,12 @@ var sortKeysFixable = {
|
|
|
1059
1104
|
}
|
|
1060
1105
|
return isPureImportedCallExpression(callExpression);
|
|
1061
1106
|
};
|
|
1062
|
-
const
|
|
1063
|
-
if (!tsChecker || !esTreeNodeToTSNodeMap)
|
|
1064
|
-
return;
|
|
1065
|
-
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
1066
|
-
if (!tsNode)
|
|
1067
|
-
return;
|
|
1068
|
-
const type = tsChecker.getTypeAtLocation(tsNode);
|
|
1069
|
-
return type.symbol ?? type.aliasSymbol;
|
|
1070
|
-
};
|
|
1107
|
+
const isUnionType = (type) => Boolean(type.flags & ts.TypeFlags.Union);
|
|
1071
1108
|
const isObjectLikeType = (type) => {
|
|
1072
1109
|
if (type.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) {
|
|
1073
1110
|
return false;
|
|
1074
1111
|
}
|
|
1075
|
-
if (type
|
|
1112
|
+
if (isUnionType(type)) {
|
|
1076
1113
|
return type.types.every((member) => {
|
|
1077
1114
|
if (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) {
|
|
1078
1115
|
return true;
|
|
@@ -1208,10 +1245,7 @@ var sortKeysFixable = {
|
|
|
1208
1245
|
return true;
|
|
1209
1246
|
}
|
|
1210
1247
|
if (node.callee.type === "Identifier") {
|
|
1211
|
-
|
|
1212
|
-
return true;
|
|
1213
|
-
}
|
|
1214
|
-
return callReturnsNominalInstance(node);
|
|
1248
|
+
return isPureIdentifierCall(node) || callReturnsNominalInstance(node);
|
|
1215
1249
|
}
|
|
1216
1250
|
if (node.callee.type !== "MemberExpression") {
|
|
1217
1251
|
return false;
|
|
@@ -1395,9 +1429,7 @@ ${indent}`;
|
|
|
1395
1429
|
}
|
|
1396
1430
|
if (autoFixable) {
|
|
1397
1431
|
const impureCount = keys.filter((key) => key.node.type === "Property" && !isPureRuntimeExpression(key.node.value, getStableLocalsForNode(key.node))).length;
|
|
1398
|
-
|
|
1399
|
-
autoFixable = false;
|
|
1400
|
-
}
|
|
1432
|
+
autoFixable = impureCount <= 1;
|
|
1401
1433
|
}
|
|
1402
1434
|
let fixProvided = false;
|
|
1403
1435
|
const createReportWithFix = (curr, shouldFix) => {
|
|
@@ -2234,7 +2266,7 @@ var sortExports = {
|
|
|
2234
2266
|
};
|
|
2235
2267
|
|
|
2236
2268
|
// src/rules/localize-react-props.ts
|
|
2237
|
-
import { AST_NODE_TYPES as
|
|
2269
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
|
|
2238
2270
|
var localizeReactProps = {
|
|
2239
2271
|
create(context) {
|
|
2240
2272
|
const candidateVariables = [];
|
|
@@ -2246,20 +2278,20 @@ var localizeReactProps = {
|
|
|
2246
2278
|
};
|
|
2247
2279
|
const getRightmostJSXIdentifier = (name) => {
|
|
2248
2280
|
let current = name;
|
|
2249
|
-
while (current.type ===
|
|
2281
|
+
while (current.type === AST_NODE_TYPES3.JSXMemberExpression) {
|
|
2250
2282
|
current = current.property;
|
|
2251
2283
|
}
|
|
2252
|
-
if (current.type ===
|
|
2284
|
+
if (current.type === AST_NODE_TYPES3.JSXIdentifier) {
|
|
2253
2285
|
return current;
|
|
2254
2286
|
}
|
|
2255
2287
|
return null;
|
|
2256
2288
|
};
|
|
2257
2289
|
const getLeftmostJSXIdentifier = (name) => {
|
|
2258
2290
|
let current = name;
|
|
2259
|
-
while (current.type ===
|
|
2291
|
+
while (current.type === AST_NODE_TYPES3.JSXMemberExpression) {
|
|
2260
2292
|
current = current.object;
|
|
2261
2293
|
}
|
|
2262
|
-
if (current.type ===
|
|
2294
|
+
if (current.type === AST_NODE_TYPES3.JSXIdentifier) {
|
|
2263
2295
|
return current;
|
|
2264
2296
|
}
|
|
2265
2297
|
return null;
|
|
@@ -2269,7 +2301,7 @@ var localizeReactProps = {
|
|
|
2269
2301
|
return "";
|
|
2270
2302
|
}
|
|
2271
2303
|
const nameNode = jsxElement.openingElement.name;
|
|
2272
|
-
if (nameNode.type ===
|
|
2304
|
+
if (nameNode.type === AST_NODE_TYPES3.JSXIdentifier) {
|
|
2273
2305
|
return nameNode.name;
|
|
2274
2306
|
}
|
|
2275
2307
|
const rightmost = getRightmostJSXIdentifier(nameNode);
|
|
@@ -2278,12 +2310,12 @@ var localizeReactProps = {
|
|
|
2278
2310
|
}
|
|
2279
2311
|
return "";
|
|
2280
2312
|
};
|
|
2281
|
-
const isUseStateCall = (node) => node !== null && node.type ===
|
|
2282
|
-
const isHookCall = (node) => node !== null && node.type ===
|
|
2313
|
+
const isUseStateCall = (node) => node !== null && node.type === AST_NODE_TYPES3.CallExpression && node.callee !== null && (node.callee.type === AST_NODE_TYPES3.Identifier && node.callee.name === "useState" || node.callee.type === AST_NODE_TYPES3.MemberExpression && node.callee.property !== null && node.callee.property.type === AST_NODE_TYPES3.Identifier && node.callee.property.name === "useState");
|
|
2314
|
+
const isHookCall = (node) => node !== null && node.type === AST_NODE_TYPES3.CallExpression && node.callee !== null && node.callee.type === AST_NODE_TYPES3.Identifier && /^use[A-Z]/.test(node.callee.name) && node.callee.name !== "useState";
|
|
2283
2315
|
const getJSXAncestor = (node) => {
|
|
2284
2316
|
let current = node.parent;
|
|
2285
2317
|
while (current) {
|
|
2286
|
-
if (current.type ===
|
|
2318
|
+
if (current.type === AST_NODE_TYPES3.JSXElement) {
|
|
2287
2319
|
return current;
|
|
2288
2320
|
}
|
|
2289
2321
|
current = current.parent;
|
|
@@ -2292,14 +2324,14 @@ var localizeReactProps = {
|
|
|
2292
2324
|
};
|
|
2293
2325
|
const getTagNameFromOpening = (openingElement) => {
|
|
2294
2326
|
const nameNode = openingElement.name;
|
|
2295
|
-
if (nameNode.type ===
|
|
2327
|
+
if (nameNode.type === AST_NODE_TYPES3.JSXIdentifier) {
|
|
2296
2328
|
return nameNode.name;
|
|
2297
2329
|
}
|
|
2298
2330
|
const rightmost = getRightmostJSXIdentifier(nameNode);
|
|
2299
2331
|
return rightmost ? rightmost.name : null;
|
|
2300
2332
|
};
|
|
2301
2333
|
const isProviderOrContext = (tagName) => tagName.endsWith("Provider") || tagName.endsWith("Context");
|
|
2302
|
-
const isValueAttributeOnProvider = (node) => node.type ===
|
|
2334
|
+
const isValueAttributeOnProvider = (node) => node.type === AST_NODE_TYPES3.JSXAttribute && node.name && node.name.type === AST_NODE_TYPES3.JSXIdentifier && node.name.name === "value" && node.parent && node.parent.type === AST_NODE_TYPES3.JSXOpeningElement && (() => {
|
|
2303
2335
|
const tagName = getTagNameFromOpening(node.parent);
|
|
2304
2336
|
return tagName !== null && isProviderOrContext(tagName);
|
|
2305
2337
|
})();
|
|
@@ -2318,7 +2350,7 @@ var localizeReactProps = {
|
|
|
2318
2350
|
return false;
|
|
2319
2351
|
}
|
|
2320
2352
|
const nameNode = jsxElement.openingElement.name;
|
|
2321
|
-
if (nameNode.type ===
|
|
2353
|
+
if (nameNode.type === AST_NODE_TYPES3.JSXIdentifier) {
|
|
2322
2354
|
return /^[A-Z]/.test(nameNode.name);
|
|
2323
2355
|
}
|
|
2324
2356
|
const leftmost = getLeftmostJSXIdentifier(nameNode);
|
|
@@ -2327,7 +2359,7 @@ var localizeReactProps = {
|
|
|
2327
2359
|
const getComponentFunction = (node) => {
|
|
2328
2360
|
let current = node;
|
|
2329
2361
|
while (current) {
|
|
2330
|
-
if (current.type ===
|
|
2362
|
+
if (current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.ArrowFunctionExpression) {
|
|
2331
2363
|
return current;
|
|
2332
2364
|
}
|
|
2333
2365
|
current = current.parent;
|
|
@@ -2399,11 +2431,11 @@ var localizeReactProps = {
|
|
|
2399
2431
|
return false;
|
|
2400
2432
|
};
|
|
2401
2433
|
const processUseStateDeclarator = (node) => {
|
|
2402
|
-
if (!node.init || !isUseStateCall(node.init) || node.id.type !==
|
|
2434
|
+
if (!node.init || !isUseStateCall(node.init) || node.id.type !== AST_NODE_TYPES3.ArrayPattern || node.id.elements.length < 2) {
|
|
2403
2435
|
return false;
|
|
2404
2436
|
}
|
|
2405
2437
|
const [stateElem, setterElem] = node.id.elements;
|
|
2406
|
-
if (!stateElem || stateElem.type !==
|
|
2438
|
+
if (!stateElem || stateElem.type !== AST_NODE_TYPES3.Identifier || !setterElem || setterElem.type !== AST_NODE_TYPES3.Identifier) {
|
|
2407
2439
|
return false;
|
|
2408
2440
|
}
|
|
2409
2441
|
const stateVarName = stateElem.name;
|
|
@@ -2427,7 +2459,7 @@ var localizeReactProps = {
|
|
|
2427
2459
|
return true;
|
|
2428
2460
|
};
|
|
2429
2461
|
const processGeneralVariable = (node, componentFunction) => {
|
|
2430
|
-
if (!node.id || node.id.type !==
|
|
2462
|
+
if (!node.id || node.id.type !== AST_NODE_TYPES3.Identifier) {
|
|
2431
2463
|
return;
|
|
2432
2464
|
}
|
|
2433
2465
|
const varName = node.id.name;
|
|
@@ -2479,7 +2511,7 @@ var localizeReactProps = {
|
|
|
2479
2511
|
const componentFunction = getComponentFunction(node);
|
|
2480
2512
|
if (!componentFunction || !componentFunction.body)
|
|
2481
2513
|
return;
|
|
2482
|
-
if (node.init && node.id && node.id.type ===
|
|
2514
|
+
if (node.init && node.id && node.id.type === AST_NODE_TYPES3.Identifier && node.init.type === AST_NODE_TYPES3.CallExpression && isHookCall(node.init)) {
|
|
2483
2515
|
const hookSet = getHookSet(componentFunction);
|
|
2484
2516
|
hookSet.add(node.id.name);
|
|
2485
2517
|
}
|
|
@@ -3329,71 +3361,170 @@ var inlineStyleLimit = {
|
|
|
3329
3361
|
}
|
|
3330
3362
|
};
|
|
3331
3363
|
|
|
3332
|
-
// src/rules/no-inline-
|
|
3333
|
-
var
|
|
3364
|
+
// src/rules/no-inline-object-types.ts
|
|
3365
|
+
var DEFAULT_MIN_PROPERTIES = 2;
|
|
3366
|
+
var toPascalCase = (name) => {
|
|
3367
|
+
const parts = name.replace(/[_-]+/g, " ").split(/\s+/).filter(Boolean);
|
|
3368
|
+
if (parts.length === 0)
|
|
3369
|
+
return name;
|
|
3370
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3371
|
+
};
|
|
3372
|
+
var collectInlineObjectTypes = (node, found) => {
|
|
3373
|
+
if (node.type === "TSTypeLiteral") {
|
|
3374
|
+
found.push(node);
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
if (node.type === "TSArrayType") {
|
|
3378
|
+
collectInlineObjectTypes(node.elementType, found);
|
|
3379
|
+
return;
|
|
3380
|
+
}
|
|
3381
|
+
if (node.type === "TSUnionType" || node.type === "TSIntersectionType") {
|
|
3382
|
+
node.types.forEach((member) => collectInlineObjectTypes(member, found));
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
if (node.type === "TSTypeReference" && node.typeArguments) {
|
|
3386
|
+
node.typeArguments.params.forEach((arg) => collectInlineObjectTypes(arg, found));
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3389
|
+
var unwrapParamTarget = (node) => {
|
|
3390
|
+
if (node.type === "TSParameterProperty") {
|
|
3391
|
+
return unwrapParamTarget(node.parameter);
|
|
3392
|
+
}
|
|
3393
|
+
if (node.type === "AssignmentPattern") {
|
|
3394
|
+
return unwrapParamTarget(node.left);
|
|
3395
|
+
}
|
|
3396
|
+
return node;
|
|
3397
|
+
};
|
|
3398
|
+
var SCOPE_BOUNDARY_TYPES = new Set([
|
|
3399
|
+
"ArrowFunctionExpression",
|
|
3400
|
+
"ClassDeclaration",
|
|
3401
|
+
"ClassExpression",
|
|
3402
|
+
"FunctionDeclaration",
|
|
3403
|
+
"FunctionExpression"
|
|
3404
|
+
]);
|
|
3405
|
+
var deriveNameFromAncestors = (node) => {
|
|
3406
|
+
let current = node.parent;
|
|
3407
|
+
while (current) {
|
|
3408
|
+
if (current.type === "VariableDeclarator" && current.id.type === "Identifier") {
|
|
3409
|
+
return toPascalCase(current.id.name);
|
|
3410
|
+
}
|
|
3411
|
+
if (current.type === "PropertyDefinition" && current.key.type === "Identifier") {
|
|
3412
|
+
return toPascalCase(current.key.name);
|
|
3413
|
+
}
|
|
3414
|
+
if (SCOPE_BOUNDARY_TYPES.has(current.type))
|
|
3415
|
+
return "T";
|
|
3416
|
+
current = current.parent;
|
|
3417
|
+
}
|
|
3418
|
+
return "T";
|
|
3419
|
+
};
|
|
3420
|
+
var noInlineObjectTypes = {
|
|
3334
3421
|
create(context) {
|
|
3335
|
-
const
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3422
|
+
const [options] = context.options;
|
|
3423
|
+
const minProperties = options?.minProperties ?? DEFAULT_MIN_PROPERTIES;
|
|
3424
|
+
const reportAnnotation = (typeNode, suggestedName) => {
|
|
3425
|
+
const literals = [];
|
|
3426
|
+
collectInlineObjectTypes(typeNode, literals);
|
|
3427
|
+
for (const literal of literals) {
|
|
3428
|
+
if (literal.members.length < minProperties)
|
|
3429
|
+
continue;
|
|
3430
|
+
const hasIndexSignature = literal.members.some((member) => member.type === "TSIndexSignature");
|
|
3431
|
+
if (hasIndexSignature)
|
|
3432
|
+
continue;
|
|
3341
3433
|
context.report({
|
|
3342
|
-
|
|
3343
|
-
|
|
3434
|
+
data: { suggestedName },
|
|
3435
|
+
messageId: "inlineObjectType",
|
|
3436
|
+
node: literal
|
|
3344
3437
|
});
|
|
3345
3438
|
}
|
|
3346
3439
|
};
|
|
3440
|
+
const handleFunctionParams = (params) => {
|
|
3441
|
+
for (const param of params) {
|
|
3442
|
+
const target = unwrapParamTarget(param);
|
|
3443
|
+
if (!("typeAnnotation" in target))
|
|
3444
|
+
continue;
|
|
3445
|
+
const annotation = target.typeAnnotation;
|
|
3446
|
+
if (!annotation || annotation.type !== "TSTypeAnnotation")
|
|
3447
|
+
continue;
|
|
3448
|
+
const name = target.type === "Identifier" ? toPascalCase(target.name) : "Params";
|
|
3449
|
+
reportAnnotation(annotation.typeAnnotation, name);
|
|
3450
|
+
}
|
|
3451
|
+
};
|
|
3347
3452
|
return {
|
|
3348
|
-
"
|
|
3349
|
-
if (node.
|
|
3453
|
+
"CallExpression, NewExpression"(node) {
|
|
3454
|
+
if (!node.typeArguments)
|
|
3350
3455
|
return;
|
|
3456
|
+
const name = deriveNameFromAncestors(node);
|
|
3457
|
+
for (const typeArg of node.typeArguments.params) {
|
|
3458
|
+
reportAnnotation(typeArg, name);
|
|
3351
3459
|
}
|
|
3352
|
-
|
|
3353
|
-
|
|
3460
|
+
},
|
|
3461
|
+
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
|
|
3462
|
+
handleFunctionParams(node.params);
|
|
3463
|
+
},
|
|
3464
|
+
PropertyDefinition(node) {
|
|
3465
|
+
if (!node.typeAnnotation || node.typeAnnotation.type !== "TSTypeAnnotation")
|
|
3354
3466
|
return;
|
|
3355
|
-
|
|
3356
|
-
|
|
3467
|
+
const name = node.key.type === "Identifier" ? toPascalCase(node.key.name) : "Field";
|
|
3468
|
+
reportAnnotation(node.typeAnnotation.typeAnnotation, name);
|
|
3469
|
+
},
|
|
3470
|
+
VariableDeclarator(node) {
|
|
3471
|
+
if (node.id.type !== "Identifier")
|
|
3472
|
+
return;
|
|
3473
|
+
const annotation = node.id.typeAnnotation;
|
|
3474
|
+
if (!annotation || annotation.type !== "TSTypeAnnotation")
|
|
3475
|
+
return;
|
|
3476
|
+
reportAnnotation(annotation.typeAnnotation, toPascalCase(node.id.name));
|
|
3357
3477
|
}
|
|
3358
3478
|
};
|
|
3359
3479
|
},
|
|
3360
|
-
defaultOptions: [],
|
|
3480
|
+
defaultOptions: [{ minProperties: DEFAULT_MIN_PROPERTIES }],
|
|
3361
3481
|
meta: {
|
|
3362
3482
|
docs: {
|
|
3363
|
-
description: "
|
|
3483
|
+
description: "Disallow inline object type literals on annotations (variables, class fields, function params, generic type arguments); prefer extracting them to a named type alias."
|
|
3364
3484
|
},
|
|
3365
3485
|
messages: {
|
|
3366
|
-
|
|
3486
|
+
inlineObjectType: "Inline object type should be extracted to a named type alias (e.g., `type {{suggestedName}} = { ... }`)."
|
|
3367
3487
|
},
|
|
3368
|
-
schema: [
|
|
3488
|
+
schema: [
|
|
3489
|
+
{
|
|
3490
|
+
additionalProperties: false,
|
|
3491
|
+
properties: {
|
|
3492
|
+
minProperties: {
|
|
3493
|
+
minimum: 1,
|
|
3494
|
+
type: "number"
|
|
3495
|
+
}
|
|
3496
|
+
},
|
|
3497
|
+
type: "object"
|
|
3498
|
+
}
|
|
3499
|
+
],
|
|
3369
3500
|
type: "suggestion"
|
|
3370
3501
|
}
|
|
3371
3502
|
};
|
|
3372
3503
|
|
|
3373
3504
|
// src/rules/no-nondeterministic-render.ts
|
|
3374
|
-
import { AST_NODE_TYPES as
|
|
3505
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
|
|
3375
3506
|
var BANNED_TEMPLATE_PATTERN = /\bMath\.random\s*\(|\bDate\.now\s*\(|\bnew\s+Date\s*\(\s*\)|\bcrypto\.randomUUID\s*\(|\bperformance\.now\s*\(/;
|
|
3376
|
-
var isIdentifier2 = (node, name) => node?.type ===
|
|
3377
|
-
var isStaticMemberCall = (node, objectName, propertyName) => node.callee.type ===
|
|
3507
|
+
var isIdentifier2 = (node, name) => node?.type === AST_NODE_TYPES4.Identifier && node.name === name;
|
|
3508
|
+
var isStaticMemberCall = (node, objectName, propertyName) => node.callee.type === AST_NODE_TYPES4.MemberExpression && !node.callee.computed && isIdentifier2(node.callee.object, objectName) && isIdentifier2(node.callee.property, propertyName);
|
|
3378
3509
|
var getPropertyName2 = (node) => {
|
|
3379
3510
|
const { key } = node;
|
|
3380
|
-
if (key.type ===
|
|
3511
|
+
if (key.type === AST_NODE_TYPES4.Identifier)
|
|
3381
3512
|
return key.name;
|
|
3382
|
-
if (key.type ===
|
|
3513
|
+
if (key.type === AST_NODE_TYPES4.Literal && typeof key.value === "string") {
|
|
3383
3514
|
return key.value;
|
|
3384
3515
|
}
|
|
3385
3516
|
return null;
|
|
3386
3517
|
};
|
|
3387
3518
|
var isComponentDecorator = (decorator) => {
|
|
3388
3519
|
const { expression } = decorator;
|
|
3389
|
-
return expression.type ===
|
|
3520
|
+
return expression.type === AST_NODE_TYPES4.CallExpression && isIdentifier2(expression.callee, "Component");
|
|
3390
3521
|
};
|
|
3391
|
-
var isAngularComponentClass = (node) => node.type ===
|
|
3522
|
+
var isAngularComponentClass = (node) => node.type === AST_NODE_TYPES4.ClassDeclaration && (node.decorators ?? []).some(isComponentDecorator);
|
|
3392
3523
|
var getTemplateText = (node) => {
|
|
3393
|
-
if (node.type ===
|
|
3524
|
+
if (node.type === AST_NODE_TYPES4.Literal && typeof node.value === "string") {
|
|
3394
3525
|
return node.value;
|
|
3395
3526
|
}
|
|
3396
|
-
if (node.type ===
|
|
3527
|
+
if (node.type === AST_NODE_TYPES4.TemplateLiteral) {
|
|
3397
3528
|
return node.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
|
|
3398
3529
|
}
|
|
3399
3530
|
return null;
|
|
@@ -3410,10 +3541,10 @@ var getEnclosingAngularComponentClass = (node) => {
|
|
|
3410
3541
|
var getEnclosingPropertyDefinition = (node) => {
|
|
3411
3542
|
let current = node.parent;
|
|
3412
3543
|
while (current) {
|
|
3413
|
-
if (current.type ===
|
|
3544
|
+
if (current.type === AST_NODE_TYPES4.PropertyDefinition) {
|
|
3414
3545
|
return current;
|
|
3415
3546
|
}
|
|
3416
|
-
if (current.type ===
|
|
3547
|
+
if (current.type === AST_NODE_TYPES4.MethodDefinition || current.type === AST_NODE_TYPES4.FunctionDeclaration || current.type === AST_NODE_TYPES4.FunctionExpression || current.type === AST_NODE_TYPES4.ArrowFunctionExpression) {
|
|
3417
3548
|
return null;
|
|
3418
3549
|
}
|
|
3419
3550
|
current = current.parent;
|
|
@@ -3475,16 +3606,91 @@ var noNondeterministicRender = {
|
|
|
3475
3606
|
}
|
|
3476
3607
|
};
|
|
3477
3608
|
|
|
3609
|
+
// src/rules/no-redundant-type-annotation.ts
|
|
3610
|
+
import * as ts2 from "typescript";
|
|
3611
|
+
var ALLOWED_INIT_TYPES = new Set([
|
|
3612
|
+
"CallExpression",
|
|
3613
|
+
"Identifier",
|
|
3614
|
+
"MemberExpression",
|
|
3615
|
+
"NewExpression",
|
|
3616
|
+
"TSAsExpression"
|
|
3617
|
+
]);
|
|
3618
|
+
var noRedundantTypeAnnotation = {
|
|
3619
|
+
create(context) {
|
|
3620
|
+
const { sourceCode } = context;
|
|
3621
|
+
const parserServices = sourceCode.parserServices ?? null;
|
|
3622
|
+
const tsProgram = parserServices && "program" in parserServices ? parserServices.program : null;
|
|
3623
|
+
const tsChecker = tsProgram ? tsProgram.getTypeChecker() : null;
|
|
3624
|
+
const esTreeNodeToTSNodeMap = parserServices && "esTreeNodeToTSNodeMap" in parserServices ? parserServices.esTreeNodeToTSNodeMap : null;
|
|
3625
|
+
if (!tsChecker || !esTreeNodeToTSNodeMap) {
|
|
3626
|
+
return {};
|
|
3627
|
+
}
|
|
3628
|
+
const stringify = (type) => tsChecker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation | ts2.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope);
|
|
3629
|
+
return {
|
|
3630
|
+
VariableDeclarator(node) {
|
|
3631
|
+
if (node.id.type !== "Identifier")
|
|
3632
|
+
return;
|
|
3633
|
+
if (!node.id.typeAnnotation)
|
|
3634
|
+
return;
|
|
3635
|
+
if (!node.init)
|
|
3636
|
+
return;
|
|
3637
|
+
if (!ALLOWED_INIT_TYPES.has(node.init.type))
|
|
3638
|
+
return;
|
|
3639
|
+
const annotationASTNode = node.id.typeAnnotation.typeAnnotation;
|
|
3640
|
+
const annotationTSNode = esTreeNodeToTSNodeMap.get(annotationASTNode);
|
|
3641
|
+
const initTSNode = esTreeNodeToTSNodeMap.get(node.init);
|
|
3642
|
+
if (!annotationTSNode || !initTSNode)
|
|
3643
|
+
return;
|
|
3644
|
+
if (!ts2.isTypeNode(annotationTSNode))
|
|
3645
|
+
return;
|
|
3646
|
+
const annotationType = tsChecker.getTypeFromTypeNode(annotationTSNode);
|
|
3647
|
+
const initType = tsChecker.getTypeAtLocation(initTSNode);
|
|
3648
|
+
const aliasSymbol = ts2.isTypeReferenceNode(annotationTSNode) ? tsChecker.getSymbolAtLocation(annotationTSNode.typeName) : undefined;
|
|
3649
|
+
if (aliasSymbol && aliasSymbol.flags & ts2.SymbolFlags.TypeAlias && initType.aliasSymbol !== aliasSymbol) {
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
const bothAny = (annotationType.flags & ts2.TypeFlags.Any) !== 0 && (initType.flags & ts2.TypeFlags.Any) !== 0;
|
|
3653
|
+
if (bothAny)
|
|
3654
|
+
return;
|
|
3655
|
+
if (annotationType.aliasSymbol !== initType.aliasSymbol)
|
|
3656
|
+
return;
|
|
3657
|
+
if (stringify(annotationType) !== stringify(initType))
|
|
3658
|
+
return;
|
|
3659
|
+
const annotationNode = node.id.typeAnnotation;
|
|
3660
|
+
context.report({
|
|
3661
|
+
fix(fixer) {
|
|
3662
|
+
return fixer.removeRange(annotationNode.range);
|
|
3663
|
+
},
|
|
3664
|
+
messageId: "redundantTypeAnnotation",
|
|
3665
|
+
node: annotationNode
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
};
|
|
3669
|
+
},
|
|
3670
|
+
defaultOptions: [],
|
|
3671
|
+
meta: {
|
|
3672
|
+
docs: {
|
|
3673
|
+
description: "Disallow type annotations on variable declarations whose initializer already has the same inferred type."
|
|
3674
|
+
},
|
|
3675
|
+
fixable: "code",
|
|
3676
|
+
messages: {
|
|
3677
|
+
redundantTypeAnnotation: "Type annotation is redundant \u2014 the initializer already has this type. Remove the annotation and let TypeScript infer it."
|
|
3678
|
+
},
|
|
3679
|
+
schema: [],
|
|
3680
|
+
type: "suggestion"
|
|
3681
|
+
}
|
|
3682
|
+
};
|
|
3683
|
+
|
|
3478
3684
|
// src/rules/no-unnecessary-div.ts
|
|
3479
|
-
import { AST_NODE_TYPES as
|
|
3685
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
|
|
3480
3686
|
var noUnnecessaryDiv = {
|
|
3481
3687
|
create(context) {
|
|
3482
3688
|
const isDivElement = (node) => {
|
|
3483
3689
|
const nameNode = node.openingElement.name;
|
|
3484
|
-
return nameNode.type ===
|
|
3690
|
+
return nameNode.type === AST_NODE_TYPES5.JSXIdentifier && nameNode.name === "div";
|
|
3485
3691
|
};
|
|
3486
3692
|
const isMeaningfulChild = (child) => {
|
|
3487
|
-
if (child.type ===
|
|
3693
|
+
if (child.type === AST_NODE_TYPES5.JSXText) {
|
|
3488
3694
|
return child.value.trim() !== "";
|
|
3489
3695
|
}
|
|
3490
3696
|
return true;
|
|
@@ -3503,7 +3709,7 @@ var noUnnecessaryDiv = {
|
|
|
3503
3709
|
if (!onlyChild) {
|
|
3504
3710
|
return;
|
|
3505
3711
|
}
|
|
3506
|
-
if (onlyChild.type ===
|
|
3712
|
+
if (onlyChild.type === AST_NODE_TYPES5.JSXElement) {
|
|
3507
3713
|
context.report({
|
|
3508
3714
|
messageId: "unnecessaryDivWrapper",
|
|
3509
3715
|
node
|
|
@@ -3545,11 +3751,8 @@ var findOwnDeclaration = (program, name) => {
|
|
|
3545
3751
|
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration && isLocalDeclaration(stmt.declaration) && declarationName(stmt.declaration) === name) {
|
|
3546
3752
|
return { alreadyExported: true, decl: stmt.declaration };
|
|
3547
3753
|
}
|
|
3548
|
-
if (stmt.type === "ExportDefaultDeclaration" &&
|
|
3549
|
-
return {
|
|
3550
|
-
alreadyExported: true,
|
|
3551
|
-
decl: stmt.declaration
|
|
3552
|
-
};
|
|
3754
|
+
if (stmt.type === "ExportDefaultDeclaration" && isLocalDeclaration(stmt.declaration) && declarationName(stmt.declaration) === name) {
|
|
3755
|
+
return { alreadyExported: true, decl: stmt.declaration };
|
|
3553
3756
|
}
|
|
3554
3757
|
if (isLocalDeclaration(stmt) && declarationName(stmt) === name) {
|
|
3555
3758
|
return { alreadyExported: false, decl: stmt };
|
|
@@ -3634,6 +3837,7 @@ var preferInlineExports = {
|
|
|
3634
3837
|
// src/index.ts
|
|
3635
3838
|
var src_default = {
|
|
3636
3839
|
rules: {
|
|
3840
|
+
"angular-one-feature-per-file": angularOneFeaturePerFile,
|
|
3637
3841
|
"explicit-object-types": explicitObjectTypes,
|
|
3638
3842
|
"inline-style-limit": inlineStyleLimit,
|
|
3639
3843
|
"localize-react-props": localizeReactProps,
|
|
@@ -3642,11 +3846,12 @@ var src_default = {
|
|
|
3642
3846
|
"min-var-length": minVarLength,
|
|
3643
3847
|
"no-button-navigation": noButtonNavigation,
|
|
3644
3848
|
"no-explicit-return-type": noExplicitReturnTypes,
|
|
3645
|
-
"no-inline-
|
|
3849
|
+
"no-inline-object-types": noInlineObjectTypes,
|
|
3646
3850
|
"no-multi-style-objects": noMultiStyleObjects,
|
|
3647
3851
|
"no-nested-jsx-return": noNestedJSXReturn,
|
|
3648
3852
|
"no-nondeterministic-render": noNondeterministicRender,
|
|
3649
3853
|
"no-or-none-component": noOrNoneComponent,
|
|
3854
|
+
"no-redundant-type-annotation": noRedundantTypeAnnotation,
|
|
3650
3855
|
"no-transition-cssproperties": noTransitionCSSProperties,
|
|
3651
3856
|
"no-unnecessary-div": noUnnecessaryDiv,
|
|
3652
3857
|
"no-unnecessary-key": noUnnecessaryKey,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Alex Kahn",
|
|
3
3
|
"description": "ESLint plugin for AbsoluteJS",
|
|
4
4
|
"devDependencies": {
|
|
5
|
-
"@absolutejs/absolute": "0.
|
|
5
|
+
"@absolutejs/absolute": "0.19.0-beta.838",
|
|
6
6
|
"@types/bun": "1.3.3",
|
|
7
7
|
"@types/react": "19.2.14",
|
|
8
8
|
"@typescript-eslint/rule-tester": "8.56.0",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"typecheck": "bun run tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"type": "module",
|
|
43
|
-
"version": "0.
|
|
43
|
+
"version": "0.9.0"
|
|
44
44
|
}
|