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.
Files changed (2) hide show
  1. package/dist/index.js +335 -130
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,26 +1,83 @@
1
1
  // @bun
2
- // src/rules/no-nested-jsx-return.ts
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 === AST_NODE_TYPES.JSXElement || node.type === AST_NODE_TYPES.JSXFragment);
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 === AST_NODE_TYPES.JSXMemberExpression) {
66
+ while (current.type === AST_NODE_TYPES2.JSXMemberExpression) {
10
67
  current = current.object;
11
68
  }
12
- if (current.type === AST_NODE_TYPES.JSXIdentifier) {
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 !== AST_NODE_TYPES.JSXElement) {
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 === AST_NODE_TYPES.JSXIdentifier) {
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 === AST_NODE_TYPES.JSXText) {
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 === AST_NODE_TYPES.JSXText) {
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 === AST_NODE_TYPES.JSXElement || child.type === AST_NODE_TYPES.JSXFragment) {
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
- for (const property of properties) {
362
- if (property.type !== "Property") {
363
- continue;
364
- }
365
- let keyName = null;
366
- if (property.key.type === "Identifier") {
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
- for (const kinds of kindsByName.values()) {
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 === "VariableDeclaration") {
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
- if (!isPureRuntimeExpression(declaration.init, stableLocals)) {
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
- for (const element of node.elements) {
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
- for (const declaration of statement.declarationList.declarations) {
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 getCallReturnTypeSymbol = (node) => {
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.flags & ts.TypeFlags.Union) {
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
- if (isPureIdentifierCall(node)) {
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
- if (impureCount > 1) {
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 AST_NODE_TYPES2 } from "@typescript-eslint/utils";
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 === AST_NODE_TYPES2.JSXMemberExpression) {
2281
+ while (current.type === AST_NODE_TYPES3.JSXMemberExpression) {
2250
2282
  current = current.property;
2251
2283
  }
2252
- if (current.type === AST_NODE_TYPES2.JSXIdentifier) {
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 === AST_NODE_TYPES2.JSXMemberExpression) {
2291
+ while (current.type === AST_NODE_TYPES3.JSXMemberExpression) {
2260
2292
  current = current.object;
2261
2293
  }
2262
- if (current.type === AST_NODE_TYPES2.JSXIdentifier) {
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 === AST_NODE_TYPES2.JSXIdentifier) {
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 === AST_NODE_TYPES2.CallExpression && node.callee !== null && (node.callee.type === AST_NODE_TYPES2.Identifier && node.callee.name === "useState" || node.callee.type === AST_NODE_TYPES2.MemberExpression && node.callee.property !== null && node.callee.property.type === AST_NODE_TYPES2.Identifier && node.callee.property.name === "useState");
2282
- const isHookCall = (node) => node !== null && node.type === AST_NODE_TYPES2.CallExpression && node.callee !== null && node.callee.type === AST_NODE_TYPES2.Identifier && /^use[A-Z]/.test(node.callee.name) && node.callee.name !== "useState";
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 === AST_NODE_TYPES2.JSXElement) {
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 === AST_NODE_TYPES2.JSXIdentifier) {
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 === AST_NODE_TYPES2.JSXAttribute && node.name && node.name.type === AST_NODE_TYPES2.JSXIdentifier && node.name.name === "value" && node.parent && node.parent.type === AST_NODE_TYPES2.JSXOpeningElement && (() => {
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 === AST_NODE_TYPES2.JSXIdentifier) {
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 === AST_NODE_TYPES2.FunctionDeclaration || current.type === AST_NODE_TYPES2.FunctionExpression || current.type === AST_NODE_TYPES2.ArrowFunctionExpression) {
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 !== AST_NODE_TYPES2.ArrayPattern || node.id.elements.length < 2) {
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 !== AST_NODE_TYPES2.Identifier || !setterElem || setterElem.type !== AST_NODE_TYPES2.Identifier) {
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 !== AST_NODE_TYPES2.Identifier) {
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 === AST_NODE_TYPES2.Identifier && node.init.type === AST_NODE_TYPES2.CallExpression && isHookCall(node.init)) {
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-prop-types.ts
3333
- var noInlinePropTypes = {
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 checkParameter = (param) => {
3336
- if (param.type !== "ObjectPattern" || !param.typeAnnotation || param.typeAnnotation.type !== "TSTypeAnnotation") {
3337
- return;
3338
- }
3339
- const annotation = param.typeAnnotation.typeAnnotation;
3340
- if (annotation.type === "TSTypeLiteral") {
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
- messageId: "noInlinePropTypes",
3343
- node: param
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
- "FunctionDeclaration, ArrowFunctionExpression, FunctionExpression"(node) {
3349
- if (node.params.length === 0) {
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
- const [firstParam] = node.params;
3353
- if (!firstParam) {
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
- checkParameter(firstParam);
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: "Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
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
- noInlinePropTypes: "Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
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 AST_NODE_TYPES3 } from "@typescript-eslint/utils";
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 === AST_NODE_TYPES3.Identifier && node.name === name;
3377
- var isStaticMemberCall = (node, objectName, propertyName) => node.callee.type === AST_NODE_TYPES3.MemberExpression && !node.callee.computed && isIdentifier2(node.callee.object, objectName) && isIdentifier2(node.callee.property, propertyName);
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 === AST_NODE_TYPES3.Identifier)
3511
+ if (key.type === AST_NODE_TYPES4.Identifier)
3381
3512
  return key.name;
3382
- if (key.type === AST_NODE_TYPES3.Literal && typeof key.value === "string") {
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 === AST_NODE_TYPES3.CallExpression && isIdentifier2(expression.callee, "Component");
3520
+ return expression.type === AST_NODE_TYPES4.CallExpression && isIdentifier2(expression.callee, "Component");
3390
3521
  };
3391
- var isAngularComponentClass = (node) => node.type === AST_NODE_TYPES3.ClassDeclaration && (node.decorators ?? []).some(isComponentDecorator);
3522
+ var isAngularComponentClass = (node) => node.type === AST_NODE_TYPES4.ClassDeclaration && (node.decorators ?? []).some(isComponentDecorator);
3392
3523
  var getTemplateText = (node) => {
3393
- if (node.type === AST_NODE_TYPES3.Literal && typeof node.value === "string") {
3524
+ if (node.type === AST_NODE_TYPES4.Literal && typeof node.value === "string") {
3394
3525
  return node.value;
3395
3526
  }
3396
- if (node.type === AST_NODE_TYPES3.TemplateLiteral) {
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 === AST_NODE_TYPES3.PropertyDefinition) {
3544
+ if (current.type === AST_NODE_TYPES4.PropertyDefinition) {
3414
3545
  return current;
3415
3546
  }
3416
- if (current.type === AST_NODE_TYPES3.MethodDefinition || current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.ArrowFunctionExpression) {
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 AST_NODE_TYPES4 } from "@typescript-eslint/utils";
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 === AST_NODE_TYPES4.JSXIdentifier && nameNode.name === "div";
3690
+ return nameNode.type === AST_NODE_TYPES5.JSXIdentifier && nameNode.name === "div";
3485
3691
  };
3486
3692
  const isMeaningfulChild = (child) => {
3487
- if (child.type === AST_NODE_TYPES4.JSXText) {
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 === AST_NODE_TYPES4.JSXElement) {
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" && stmt.declaration.type !== "Identifier" && isLocalDeclaration(stmt.declaration) && declarationName(stmt.declaration) === name) {
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-prop-types": noInlinePropTypes,
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.16.10",
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.7.0"
43
+ "version": "0.9.0"
44
44
  }