eslint-plugin-absolute 0.2.8 → 0.4.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 +506 -13
  2. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -185,6 +185,7 @@ var explicitObjectTypes = {
185
185
  };
186
186
 
187
187
  // src/rules/sort-keys-fixable.ts
188
+ import * as ts from "typescript";
188
189
  var SORT_BEFORE = -1;
189
190
  var PURE_CONSTRUCTORS = new Set(["Date"]);
190
191
  var PURE_GLOBAL_IDENTIFIERS = new Set([
@@ -224,6 +225,37 @@ var hasDuplicateNames = (names) => {
224
225
  }
225
226
  return false;
226
227
  };
228
+ var hasDuplicatePropertyNames = (properties) => {
229
+ const kindsByName = new Map;
230
+ for (const property of properties) {
231
+ if (property.type !== "Property") {
232
+ continue;
233
+ }
234
+ let keyName = null;
235
+ if (property.key.type === "Identifier") {
236
+ keyName = property.key.name;
237
+ } else if (property.key.type === "Literal") {
238
+ const { value } = property.key;
239
+ keyName = typeof value === "string" ? value : String(value);
240
+ }
241
+ if (keyName === null) {
242
+ continue;
243
+ }
244
+ const kinds = kindsByName.get(keyName) ?? [];
245
+ kinds.push(property.kind);
246
+ kindsByName.set(keyName, kinds);
247
+ }
248
+ for (const kinds of kindsByName.values()) {
249
+ if (kinds.length === 1) {
250
+ continue;
251
+ }
252
+ if (kinds.length === 2 && kinds.includes("get") && kinds.includes("set")) {
253
+ continue;
254
+ }
255
+ return true;
256
+ }
257
+ return false;
258
+ };
227
259
  var sortKeysFixable = {
228
260
  create(context) {
229
261
  const { sourceCode } = context;
@@ -236,6 +268,13 @@ var sortKeysFixable = {
236
268
  const natural = option && typeof option.natural === "boolean" ? option.natural : false;
237
269
  const minKeys = option && typeof option.minKeys === "number" ? option.minKeys : 2;
238
270
  const variablesBeforeFunctions = option && typeof option.variablesBeforeFunctions === "boolean" ? option.variablesBeforeFunctions : false;
271
+ const pureImports = new Set(option && Array.isArray(option.pureImports) ? option.pureImports : []);
272
+ const parserServices = sourceCode.parserServices ?? null;
273
+ const tsProgram = parserServices && "program" in parserServices ? parserServices.program : null;
274
+ const tsChecker = tsProgram ? tsProgram.getTypeChecker() : null;
275
+ const esTreeNodeToTSNodeMap = parserServices && "esTreeNodeToTSNodeMap" in parserServices ? parserServices.esTreeNodeToTSNodeMap : null;
276
+ const importedCallPurityCache = new Map;
277
+ const importedCallPurityInProgress = new Set;
239
278
  const compareKeys = (keyLeft, keyRight) => {
240
279
  let left = keyLeft;
241
280
  let right = keyRight;
@@ -334,7 +373,7 @@ var sortKeysFixable = {
334
373
  };
335
374
  const addAncestorConstBindings = (ancestor, node, stableLocals) => {
336
375
  const addDeclarationBindings = (statement) => {
337
- if (statement.type !== "VariableDeclaration" || statement.kind !== "const") {
376
+ if (statement.type !== "VariableDeclaration") {
338
377
  return;
339
378
  }
340
379
  for (const declaration of statement.declarations) {
@@ -369,6 +408,7 @@ var sortKeysFixable = {
369
408
  for (const ancestor of ancestors) {
370
409
  addAncestorBindingsForNode(ancestor, node, stableLocals);
371
410
  }
411
+ stableLocals.add("this");
372
412
  return stableLocals;
373
413
  };
374
414
  const getStaticMemberName = (memberExpression) => {
@@ -449,6 +489,329 @@ var sortKeysFixable = {
449
489
  pureFunctionCache.set(functionNode, isPure);
450
490
  return isPure;
451
491
  };
492
+ const ASSIGNMENT_OPERATOR_KINDS = new Set([
493
+ ts.SyntaxKind.EqualsToken,
494
+ ts.SyntaxKind.PlusEqualsToken,
495
+ ts.SyntaxKind.MinusEqualsToken,
496
+ ts.SyntaxKind.AsteriskEqualsToken,
497
+ ts.SyntaxKind.AsteriskAsteriskEqualsToken,
498
+ ts.SyntaxKind.SlashEqualsToken,
499
+ ts.SyntaxKind.PercentEqualsToken,
500
+ ts.SyntaxKind.AmpersandEqualsToken,
501
+ ts.SyntaxKind.BarEqualsToken,
502
+ ts.SyntaxKind.CaretEqualsToken,
503
+ ts.SyntaxKind.LessThanLessThanEqualsToken,
504
+ ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
505
+ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
506
+ ts.SyntaxKind.AmpersandAmpersandEqualsToken,
507
+ ts.SyntaxKind.BarBarEqualsToken,
508
+ ts.SyntaxKind.QuestionQuestionEqualsToken
509
+ ]);
510
+ const addTsBoundIdentifiers = (node, stableLocals) => {
511
+ if (node.kind === ts.SyntaxKind.OmittedExpression) {
512
+ return;
513
+ }
514
+ if (ts.isIdentifier(node)) {
515
+ stableLocals.add(node.text);
516
+ return;
517
+ }
518
+ if (ts.isBindingElement(node)) {
519
+ addTsBoundIdentifiers(node.name, stableLocals);
520
+ return;
521
+ }
522
+ if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node)) {
523
+ for (const element of node.elements) {
524
+ addTsBoundIdentifiers(element, stableLocals);
525
+ }
526
+ }
527
+ };
528
+ const getCalleeIdentifier = (callee) => {
529
+ if (ts.isIdentifier(callee)) {
530
+ return callee;
531
+ }
532
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
533
+ return callee.name;
534
+ }
535
+ return null;
536
+ };
537
+ const getTsCalleePath = (callee) => {
538
+ if (ts.isParenthesizedExpression(callee)) {
539
+ return getTsCalleePath(callee.expression);
540
+ }
541
+ if (ts.isIdentifier(callee)) {
542
+ return callee.text;
543
+ }
544
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
545
+ const objectPath = getTsCalleePath(callee.expression);
546
+ return objectPath === null ? null : `${objectPath}.${callee.name.text}`;
547
+ }
548
+ return null;
549
+ };
550
+ const getFunctionLikeFromDeclaration = (declaration) => {
551
+ if (ts.isFunctionDeclaration(declaration)) {
552
+ return declaration;
553
+ }
554
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
555
+ const init = declaration.initializer;
556
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
557
+ return init;
558
+ }
559
+ }
560
+ return null;
561
+ };
562
+ const isPureTsIdentifier = (identifier, stableLocals) => {
563
+ const name = identifier.text;
564
+ if (PURE_GLOBAL_IDENTIFIERS.has(name)) {
565
+ return true;
566
+ }
567
+ if (stableLocals.has(name)) {
568
+ return true;
569
+ }
570
+ if (!tsChecker) {
571
+ return false;
572
+ }
573
+ const symbol = tsChecker.getSymbolAtLocation(identifier);
574
+ if (!symbol) {
575
+ return false;
576
+ }
577
+ const target = symbol.flags & ts.SymbolFlags.Alias ? tsChecker.getAliasedSymbol(symbol) : symbol;
578
+ const declaration = target.declarations?.[0];
579
+ if (!declaration) {
580
+ return false;
581
+ }
582
+ if (declaration.getSourceFile().isDeclarationFile) {
583
+ return pureImports.has(name);
584
+ }
585
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer && declaration.parent && ts.isVariableDeclarationList(declaration.parent) && declaration.parent.flags & ts.NodeFlags.Const) {
586
+ return isPureTsExpression(declaration.initializer, new Set);
587
+ }
588
+ if (ts.isFunctionDeclaration(declaration) || ts.isClassDeclaration(declaration)) {
589
+ return true;
590
+ }
591
+ return false;
592
+ };
593
+ const isPureTsBlock = (block, stableLocals) => {
594
+ for (const statement of block.statements) {
595
+ if (ts.isReturnStatement(statement)) {
596
+ if (statement.expression && !isPureTsExpression(statement.expression, stableLocals)) {
597
+ return false;
598
+ }
599
+ continue;
600
+ }
601
+ if (ts.isVariableStatement(statement)) {
602
+ if (!(statement.declarationList.flags & ts.NodeFlags.Const)) {
603
+ return false;
604
+ }
605
+ for (const declaration of statement.declarationList.declarations) {
606
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
607
+ return false;
608
+ }
609
+ if (!isPureTsExpression(declaration.initializer, stableLocals)) {
610
+ return false;
611
+ }
612
+ stableLocals.add(declaration.name.text);
613
+ }
614
+ continue;
615
+ }
616
+ return false;
617
+ }
618
+ return true;
619
+ };
620
+ const isPureTsFunction = (func) => {
621
+ const cached = importedCallPurityCache.get(func);
622
+ if (cached !== undefined) {
623
+ return cached;
624
+ }
625
+ if (importedCallPurityInProgress.has(func)) {
626
+ return false;
627
+ }
628
+ importedCallPurityInProgress.add(func);
629
+ const stableLocals = new Set;
630
+ for (const parameter of func.parameters) {
631
+ addTsBoundIdentifiers(parameter.name, stableLocals);
632
+ }
633
+ let pure = false;
634
+ if (func.body) {
635
+ pure = ts.isBlock(func.body) ? isPureTsBlock(func.body, stableLocals) : isPureTsExpression(func.body, stableLocals);
636
+ }
637
+ importedCallPurityInProgress.delete(func);
638
+ importedCallPurityCache.set(func, pure);
639
+ return pure;
640
+ };
641
+ const isPureTsCallExpression = (node, stableLocals) => {
642
+ const argsArePure = node.arguments.every((argument) => {
643
+ if (ts.isSpreadElement(argument)) {
644
+ return false;
645
+ }
646
+ return isPureTsExpression(argument, stableLocals);
647
+ });
648
+ if (!argsArePure) {
649
+ return false;
650
+ }
651
+ const calleePath = getTsCalleePath(node.expression);
652
+ if (calleePath !== null && pureImports.has(calleePath)) {
653
+ return true;
654
+ }
655
+ const calleeId = getCalleeIdentifier(node.expression);
656
+ if (!calleeId) {
657
+ return false;
658
+ }
659
+ if (PURE_GLOBAL_FUNCTIONS.has(calleeId.text)) {
660
+ return true;
661
+ }
662
+ if (!tsChecker) {
663
+ return false;
664
+ }
665
+ const symbol = tsChecker.getSymbolAtLocation(calleeId);
666
+ if (!symbol) {
667
+ return false;
668
+ }
669
+ const target = symbol.flags & ts.SymbolFlags.Alias ? tsChecker.getAliasedSymbol(symbol) : symbol;
670
+ const declaration = target.declarations?.[0];
671
+ if (!declaration) {
672
+ return false;
673
+ }
674
+ if (declaration.getSourceFile().isDeclarationFile) {
675
+ return false;
676
+ }
677
+ const funcNode = getFunctionLikeFromDeclaration(declaration);
678
+ if (!funcNode) {
679
+ return false;
680
+ }
681
+ return isPureTsFunction(funcNode);
682
+ };
683
+ const isPureTsExpression = (node, stableLocals) => {
684
+ if (!node) {
685
+ return false;
686
+ }
687
+ if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node) || ts.isNonNullExpression(node) || ts.isSatisfiesExpression(node)) {
688
+ return isPureTsExpression(node.expression, stableLocals);
689
+ }
690
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node) || ts.isBigIntLiteral(node) || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword) {
691
+ return true;
692
+ }
693
+ if (ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isClassExpression(node)) {
694
+ return true;
695
+ }
696
+ if (node.kind === ts.SyntaxKind.ThisKeyword) {
697
+ return stableLocals.has("this");
698
+ }
699
+ if (ts.isIdentifier(node)) {
700
+ return isPureTsIdentifier(node, stableLocals);
701
+ }
702
+ if (ts.isTemplateExpression(node)) {
703
+ return node.templateSpans.every((span) => isPureTsExpression(span.expression, stableLocals));
704
+ }
705
+ if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) {
706
+ return isPureTsExpression(node.operand, stableLocals);
707
+ }
708
+ if (ts.isBinaryExpression(node)) {
709
+ if (ASSIGNMENT_OPERATOR_KINDS.has(node.operatorToken.kind) || node.operatorToken.kind === ts.SyntaxKind.CommaToken) {
710
+ return false;
711
+ }
712
+ return isPureTsExpression(node.left, stableLocals) && isPureTsExpression(node.right, stableLocals);
713
+ }
714
+ if (ts.isConditionalExpression(node)) {
715
+ return isPureTsExpression(node.condition, stableLocals) && isPureTsExpression(node.whenTrue, stableLocals) && isPureTsExpression(node.whenFalse, stableLocals);
716
+ }
717
+ if (ts.isArrayLiteralExpression(node)) {
718
+ return node.elements.every((element) => {
719
+ if (ts.isSpreadElement(element)) {
720
+ return false;
721
+ }
722
+ if (element.kind === ts.SyntaxKind.OmittedExpression) {
723
+ return false;
724
+ }
725
+ return isPureTsExpression(element, stableLocals);
726
+ });
727
+ }
728
+ if (ts.isObjectLiteralExpression(node)) {
729
+ return node.properties.every((property) => {
730
+ if (ts.isShorthandPropertyAssignment(property)) {
731
+ return isPureTsIdentifier(property.name, stableLocals);
732
+ }
733
+ if (ts.isPropertyAssignment(property)) {
734
+ if (ts.isComputedPropertyName(property.name)) {
735
+ return false;
736
+ }
737
+ return isPureTsExpression(property.initializer, stableLocals);
738
+ }
739
+ if (ts.isMethodDeclaration(property)) {
740
+ return !ts.isComputedPropertyName(property.name);
741
+ }
742
+ return false;
743
+ });
744
+ }
745
+ if (ts.isPropertyAccessExpression(node)) {
746
+ return isPureTsExpression(node.expression, stableLocals);
747
+ }
748
+ if (ts.isElementAccessExpression(node)) {
749
+ return isPureTsExpression(node.expression, stableLocals) && isPureTsExpression(node.argumentExpression, stableLocals);
750
+ }
751
+ if (ts.isNewExpression(node)) {
752
+ if (!ts.isIdentifier(node.expression) || !PURE_CONSTRUCTORS.has(node.expression.text)) {
753
+ return false;
754
+ }
755
+ return node.arguments?.every((argument) => {
756
+ if (ts.isSpreadElement(argument)) {
757
+ return false;
758
+ }
759
+ return isPureTsExpression(argument, stableLocals);
760
+ }) ?? true;
761
+ }
762
+ if (ts.isCallExpression(node)) {
763
+ return isPureTsCallExpression(node, stableLocals);
764
+ }
765
+ return false;
766
+ };
767
+ const getCalleePath = (node) => {
768
+ if (node.type === "Identifier") {
769
+ return node.name;
770
+ }
771
+ if (node.type === "ChainExpression") {
772
+ return getCalleePath(node.expression);
773
+ }
774
+ if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
775
+ const objectPath = getCalleePath(node.object);
776
+ return objectPath === null ? null : `${objectPath}.${node.property.name}`;
777
+ }
778
+ return null;
779
+ };
780
+ const isPureImportedCallExpression = (callExpression) => {
781
+ if (!tsChecker || !esTreeNodeToTSNodeMap) {
782
+ return false;
783
+ }
784
+ let calleeEsNode = null;
785
+ if (callExpression.callee.type === "Identifier") {
786
+ calleeEsNode = callExpression.callee;
787
+ } else if (callExpression.callee.type === "MemberExpression" && !callExpression.callee.computed && callExpression.callee.property.type === "Identifier") {
788
+ calleeEsNode = callExpression.callee.property;
789
+ }
790
+ if (!calleeEsNode) {
791
+ return false;
792
+ }
793
+ const tsCallee = esTreeNodeToTSNodeMap.get(calleeEsNode);
794
+ if (!tsCallee || !ts.isIdentifier(tsCallee)) {
795
+ return false;
796
+ }
797
+ const symbol = tsChecker.getSymbolAtLocation(tsCallee);
798
+ if (!symbol) {
799
+ return false;
800
+ }
801
+ const target = symbol.flags & ts.SymbolFlags.Alias ? tsChecker.getAliasedSymbol(symbol) : symbol;
802
+ const declaration = target.declarations?.[0];
803
+ if (!declaration) {
804
+ return false;
805
+ }
806
+ if (declaration.getSourceFile().isDeclarationFile) {
807
+ return false;
808
+ }
809
+ const funcNode = getFunctionLikeFromDeclaration(declaration);
810
+ if (!funcNode) {
811
+ return false;
812
+ }
813
+ return isPureTsFunction(funcNode);
814
+ };
452
815
  const isPureIdentifierCall = (callExpression) => {
453
816
  if (callExpression.callee.type !== "Identifier") {
454
817
  return false;
@@ -456,13 +819,22 @@ var sortKeysFixable = {
456
819
  if (PURE_GLOBAL_FUNCTIONS.has(callExpression.callee.name)) {
457
820
  return true;
458
821
  }
822
+ if (pureImports.has(callExpression.callee.name)) {
823
+ return true;
824
+ }
459
825
  const binding = topLevelBindings.get(callExpression.callee.name);
460
- return binding?.kind === "function" ? isPureTopLevelFunction(binding.node) : false;
826
+ if (binding?.kind === "function") {
827
+ return isPureTopLevelFunction(binding.node);
828
+ }
829
+ return isPureImportedCallExpression(callExpression);
461
830
  };
462
831
  const isPureRuntimeExpression = (node, stableLocals) => {
463
832
  if (!node || node.type === "PrivateIdentifier") {
464
833
  return false;
465
834
  }
835
+ if (node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSInstantiationExpression") {
836
+ return isPureRuntimeExpression(node.expression, stableLocals);
837
+ }
466
838
  switch (node.type) {
467
839
  case "Identifier":
468
840
  return isStableIdentifier(node.name, stableLocals);
@@ -473,6 +845,8 @@ var sortKeysFixable = {
473
845
  return true;
474
846
  case "ThisExpression":
475
847
  return stableLocals.has("this");
848
+ case "ChainExpression":
849
+ return isPureRuntimeExpression(node.expression, stableLocals);
476
850
  case "TemplateLiteral":
477
851
  return node.expressions.every((expression) => isPureRuntimeExpression(expression, stableLocals));
478
852
  case "UnaryExpression":
@@ -521,6 +895,10 @@ var sortKeysFixable = {
521
895
  if (!argsArePure) {
522
896
  return false;
523
897
  }
898
+ const calleePath = getCalleePath(node.callee);
899
+ if (calleePath !== null && pureImports.has(calleePath)) {
900
+ return true;
901
+ }
524
902
  if (node.callee.type === "Identifier") {
525
903
  return isPureIdentifierCall(node);
526
904
  }
@@ -528,10 +906,10 @@ var sortKeysFixable = {
528
906
  return false;
529
907
  }
530
908
  const memberName = getStaticMemberName(node.callee);
531
- if (!memberName || !PURE_MEMBER_METHODS.has(memberName)) {
532
- return false;
909
+ if (memberName && PURE_MEMBER_METHODS.has(memberName)) {
910
+ return isPureRuntimeExpression(node.callee.object, stableLocals);
533
911
  }
534
- return isPureRuntimeExpression(node.callee.object, stableLocals);
912
+ return isPureImportedCallExpression(node);
535
913
  }
536
914
  default:
537
915
  return false;
@@ -695,11 +1073,14 @@ ${indent}`;
695
1073
  node: prop
696
1074
  };
697
1075
  });
698
- if (hasDuplicateNames(keys.map((key) => key.keyName))) {
1076
+ if (hasDuplicatePropertyNames(node.properties)) {
699
1077
  autoFixable = false;
700
1078
  }
701
- if (autoFixable && keys.some((key) => key.node.type === "Property" && !isPureRuntimeExpression(key.node.value, getStableLocalsForNode(key.node)))) {
702
- autoFixable = false;
1079
+ if (autoFixable) {
1080
+ const impureCount = keys.filter((key) => key.node.type === "Property" && !isPureRuntimeExpression(key.node.value, getStableLocalsForNode(key.node))).length;
1081
+ if (impureCount > 1) {
1082
+ autoFixable = false;
1083
+ }
703
1084
  }
704
1085
  let fixProvided = false;
705
1086
  const createReportWithFix = (curr, shouldFix) => {
@@ -797,7 +1178,8 @@ ${indent}`;
797
1178
  });
798
1179
  return;
799
1180
  }
800
- if (attrs.some((attr) => attr.type === "JSXAttribute" && !isSafeJSXAttributeValue(attr.value, attr))) {
1181
+ const impureAttrCount = attrs.filter((attr) => attr.type === "JSXAttribute" && !isSafeJSXAttributeValue(attr.value, attr)).length;
1182
+ if (impureAttrCount > 1) {
801
1183
  context.report({
802
1184
  messageId: "unsorted",
803
1185
  node: attrs[0].type === "JSXAttribute" ? attrs[0].name : attrs[0]
@@ -872,6 +1254,11 @@ ${indent}`;
872
1254
  enum: ["asc", "desc"],
873
1255
  type: "string"
874
1256
  },
1257
+ pureImports: {
1258
+ items: { type: "string" },
1259
+ type: "array",
1260
+ uniqueItems: true
1261
+ },
875
1262
  variablesBeforeFunctions: {
876
1263
  type: "boolean"
877
1264
  }
@@ -2666,16 +3053,121 @@ var noInlinePropTypes = {
2666
3053
  }
2667
3054
  };
2668
3055
 
2669
- // src/rules/no-unnecessary-div.ts
3056
+ // src/rules/no-nondeterministic-render.ts
2670
3057
  import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
3058
+ var BANNED_TEMPLATE_PATTERN = /\bMath\.random\s*\(|\bDate\.now\s*\(|\bnew\s+Date\s*\(\s*\)|\bcrypto\.randomUUID\s*\(|\bperformance\.now\s*\(/;
3059
+ var isIdentifier2 = (node, name) => node?.type === AST_NODE_TYPES3.Identifier && node.name === name;
3060
+ 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);
3061
+ var getPropertyName2 = (node) => {
3062
+ const { key } = node;
3063
+ if (key.type === AST_NODE_TYPES3.Identifier)
3064
+ return key.name;
3065
+ if (key.type === AST_NODE_TYPES3.Literal && typeof key.value === "string") {
3066
+ return key.value;
3067
+ }
3068
+ return null;
3069
+ };
3070
+ var isComponentDecorator = (decorator) => {
3071
+ const { expression } = decorator;
3072
+ return expression.type === AST_NODE_TYPES3.CallExpression && isIdentifier2(expression.callee, "Component");
3073
+ };
3074
+ var isAngularComponentClass = (node) => node.type === AST_NODE_TYPES3.ClassDeclaration && (node.decorators ?? []).some(isComponentDecorator);
3075
+ var getTemplateText = (node) => {
3076
+ if (node.type === AST_NODE_TYPES3.Literal && typeof node.value === "string") {
3077
+ return node.value;
3078
+ }
3079
+ if (node.type === AST_NODE_TYPES3.TemplateLiteral) {
3080
+ return node.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
3081
+ }
3082
+ return null;
3083
+ };
3084
+ var getEnclosingAngularComponentClass = (node) => {
3085
+ let current = node.parent;
3086
+ while (current) {
3087
+ if (isAngularComponentClass(current))
3088
+ return current;
3089
+ current = current.parent;
3090
+ }
3091
+ return null;
3092
+ };
3093
+ var getEnclosingPropertyDefinition = (node) => {
3094
+ let current = node.parent;
3095
+ while (current) {
3096
+ if (current.type === AST_NODE_TYPES3.PropertyDefinition) {
3097
+ return current;
3098
+ }
3099
+ 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) {
3100
+ return null;
3101
+ }
3102
+ current = current.parent;
3103
+ }
3104
+ return null;
3105
+ };
3106
+ var isInAngularFieldInitializer = (node) => {
3107
+ const propertyDefinition = getEnclosingPropertyDefinition(node);
3108
+ if (!propertyDefinition || propertyDefinition.value === null)
3109
+ return false;
3110
+ return getEnclosingAngularComponentClass(propertyDefinition) !== null;
3111
+ };
3112
+ var isBannedCall = (node) => isStaticMemberCall(node, "Math", "random") || isStaticMemberCall(node, "Date", "now") || isStaticMemberCall(node, "crypto", "randomUUID") || isStaticMemberCall(node, "performance", "now");
3113
+ var noNondeterministicRender = {
3114
+ create(context) {
3115
+ const reportField = (node) => {
3116
+ if (!isInAngularFieldInitializer(node))
3117
+ return;
3118
+ context.report({
3119
+ messageId: "nondeterministicField",
3120
+ node
3121
+ });
3122
+ };
3123
+ return {
3124
+ CallExpression(node) {
3125
+ if (isBannedCall(node))
3126
+ reportField(node);
3127
+ },
3128
+ "ClassDeclaration > Decorator CallExpression > ObjectExpression > Property"(node) {
3129
+ if (getPropertyName2(node) !== "template")
3130
+ return;
3131
+ const templateText = getTemplateText(node.value);
3132
+ if (templateText === null || !BANNED_TEMPLATE_PATTERN.test(templateText)) {
3133
+ return;
3134
+ }
3135
+ context.report({
3136
+ messageId: "nondeterministicTemplate",
3137
+ node: node.value
3138
+ });
3139
+ },
3140
+ NewExpression(node) {
3141
+ if (isIdentifier2(node.callee, "Date") && node.arguments.length === 0) {
3142
+ reportField(node);
3143
+ }
3144
+ }
3145
+ };
3146
+ },
3147
+ defaultOptions: [],
3148
+ meta: {
3149
+ docs: {
3150
+ description: "Disallow nondeterministic values in Angular render paths that can cause SSR hydration mismatches."
3151
+ },
3152
+ messages: {
3153
+ nondeterministicField: "Do not use nondeterministic values in Angular component field initializers. Inject AbsoluteJS deterministic tokens instead.",
3154
+ nondeterministicTemplate: "Do not use nondeterministic values in Angular templates. Compute a deterministic value before render instead."
3155
+ },
3156
+ schema: [],
3157
+ type: "problem"
3158
+ }
3159
+ };
3160
+
3161
+ // src/rules/no-unnecessary-div.ts
3162
+ import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
2671
3163
  var noUnnecessaryDiv = {
2672
3164
  create(context) {
2673
3165
  const isDivElement = (node) => {
2674
3166
  const nameNode = node.openingElement.name;
2675
- return nameNode.type === AST_NODE_TYPES3.JSXIdentifier && nameNode.name === "div";
3167
+ return nameNode.type === AST_NODE_TYPES4.JSXIdentifier && nameNode.name === "div";
2676
3168
  };
2677
3169
  const isMeaningfulChild = (child) => {
2678
- if (child.type === AST_NODE_TYPES3.JSXText) {
3170
+ if (child.type === AST_NODE_TYPES4.JSXText) {
2679
3171
  return child.value.trim() !== "";
2680
3172
  }
2681
3173
  return true;
@@ -2694,7 +3186,7 @@ var noUnnecessaryDiv = {
2694
3186
  if (!onlyChild) {
2695
3187
  return;
2696
3188
  }
2697
- if (onlyChild.type === AST_NODE_TYPES3.JSXElement) {
3189
+ if (onlyChild.type === AST_NODE_TYPES4.JSXElement) {
2698
3190
  context.report({
2699
3191
  messageId: "unnecessaryDivWrapper",
2700
3192
  node
@@ -2730,6 +3222,7 @@ var src_default = {
2730
3222
  "no-inline-prop-types": noInlinePropTypes,
2731
3223
  "no-multi-style-objects": noMultiStyleObjects,
2732
3224
  "no-nested-jsx-return": noNestedJSXReturn,
3225
+ "no-nondeterministic-render": noNondeterministicRender,
2733
3226
  "no-or-none-component": noOrNoneComponent,
2734
3227
  "no-transition-cssproperties": noTransitionCSSProperties,
2735
3228
  "no-unnecessary-div": noUnnecessaryDiv,
package/package.json CHANGED
@@ -21,12 +21,17 @@
21
21
  "license": "CC BY-NC 4.0",
22
22
  "main": "./dist/index.js",
23
23
  "name": "eslint-plugin-absolute",
24
+ "peerDependencies": {
25
+ "@typescript-eslint/utils": "^8.0.0",
26
+ "eslint": ">=9",
27
+ "typescript": ">=5.0.0"
28
+ },
24
29
  "repository": {
25
30
  "type": "git",
26
31
  "url": "https://github.com/absolutejs/eslint-plugin-absolute.git"
27
32
  },
28
33
  "scripts": {
29
- "build": "rm -rf dist && bun build src/index.ts --outdir dist --splitting --target=bun --external eslint --external @typescript-eslint/utils",
34
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --splitting --target=bun --external eslint --external @typescript-eslint/utils --external typescript",
30
35
  "format": "absolutejs prettier --write",
31
36
  "lint": "bun run build && bun run absolutejs eslint",
32
37
  "prune": "ts-prune --error",
@@ -35,5 +40,5 @@
35
40
  "typecheck": "bun run tsc --noEmit"
36
41
  },
37
42
  "type": "module",
38
- "version": "0.2.8"
43
+ "version": "0.4.0"
39
44
  }