eslint-plugin-absolute 0.2.1 → 0.2.3

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.
@@ -36,6 +36,101 @@ type KeyInfo = {
36
36
 
37
37
  const SORT_BEFORE = -1;
38
38
 
39
+ const hasDuplicateNames = (names: Array<string | null>) => {
40
+ const seen = new Set<string>();
41
+ const nonNullNames = names.flatMap((name) => (name === null ? [] : [name]));
42
+
43
+ for (const name of nonNullNames) {
44
+ if (seen.has(name)) {
45
+ return true;
46
+ }
47
+ seen.add(name);
48
+ }
49
+
50
+ return false;
51
+ };
52
+
53
+ const isSafeStaticTemplate = (node: TSESTree.TemplateLiteral) =>
54
+ node.expressions.length === 0;
55
+
56
+ const isSafeArrayElement: (node: TSESTree.Node | null) => boolean = (node) => {
57
+ if (!node || node.type === "SpreadElement") {
58
+ return false;
59
+ }
60
+
61
+ return isSafeToReorderExpression(node);
62
+ };
63
+
64
+ const isSafeObjectProperty: (
65
+ property: TSESTree.ObjectExpression["properties"][number]
66
+ ) => boolean = (property) => {
67
+ if (
68
+ property.type !== "Property" ||
69
+ property.computed ||
70
+ property.kind !== "init"
71
+ ) {
72
+ return false;
73
+ }
74
+
75
+ if (property.key.type !== "Identifier" && property.key.type !== "Literal") {
76
+ return false;
77
+ }
78
+
79
+ if (property.method) {
80
+ return true;
81
+ }
82
+
83
+ return isSafeToReorderExpression(property.value);
84
+ };
85
+
86
+ const isSafeToReorderExpression: (node: TSESTree.Node | null) => boolean = (
87
+ node
88
+ ) => {
89
+ if (!node || node.type === "PrivateIdentifier") {
90
+ return false;
91
+ }
92
+
93
+ switch (node.type) {
94
+ case "Identifier":
95
+ case "Literal":
96
+ case "ThisExpression":
97
+ case "FunctionExpression":
98
+ case "ArrowFunctionExpression":
99
+ case "ClassExpression":
100
+ return true;
101
+ case "TemplateLiteral":
102
+ return isSafeStaticTemplate(node);
103
+ case "UnaryExpression":
104
+ return isSafeToReorderExpression(node.argument);
105
+ case "ArrayExpression":
106
+ return node.elements.every(isSafeArrayElement);
107
+ case "ObjectExpression":
108
+ return node.properties.every(isSafeObjectProperty);
109
+ default:
110
+ return false;
111
+ }
112
+ };
113
+
114
+ const isSafeJSXAttributeValue = (value: TSESTree.JSXAttribute["value"]) => {
115
+ if (value === null) {
116
+ return true;
117
+ }
118
+
119
+ if (value.type === "Literal") {
120
+ return true;
121
+ }
122
+
123
+ if (value.type !== "JSXExpressionContainer") {
124
+ return false;
125
+ }
126
+
127
+ if (value.expression.type === "JSXEmptyExpression") {
128
+ return false;
129
+ }
130
+
131
+ return isSafeToReorderExpression(value.expression);
132
+ };
133
+
39
134
  export const sortKeysFixable: TSESLint.RuleModule<MessageIds, Options> = {
40
135
  create(context) {
41
136
  const { sourceCode } = context;
@@ -367,6 +462,21 @@ export const sortKeysFixable: TSESLint.RuleModule<MessageIds, Options> = {
367
462
  };
368
463
  });
369
464
 
465
+ if (hasDuplicateNames(keys.map((key) => key.keyName))) {
466
+ autoFixable = false;
467
+ }
468
+
469
+ if (
470
+ autoFixable &&
471
+ keys.some(
472
+ (key) =>
473
+ key.node.type === "Property" &&
474
+ !isSafeToReorderExpression(key.node.value)
475
+ )
476
+ ) {
477
+ autoFixable = false;
478
+ }
479
+
370
480
  let fixProvided = false;
371
481
 
372
482
  const createReportWithFix = (curr: KeyInfo, shouldFix: boolean) => {
@@ -538,6 +648,34 @@ export const sortKeysFixable: TSESLint.RuleModule<MessageIds, Options> = {
538
648
  return;
539
649
  }
540
650
 
651
+ if (hasDuplicateNames(names)) {
652
+ context.report({
653
+ messageId: "unsorted",
654
+ node:
655
+ attrs[0]!.type === "JSXAttribute"
656
+ ? attrs[0]!.name
657
+ : attrs[0]!
658
+ });
659
+ return;
660
+ }
661
+
662
+ if (
663
+ attrs.some(
664
+ (attr) =>
665
+ attr.type === "JSXAttribute" &&
666
+ !isSafeJSXAttributeValue(attr.value)
667
+ )
668
+ ) {
669
+ context.report({
670
+ messageId: "unsorted",
671
+ node:
672
+ attrs[0]!.type === "JSXAttribute"
673
+ ? attrs[0]!.name
674
+ : attrs[0]!
675
+ });
676
+ return;
677
+ }
678
+
541
679
  // Be conservative: only fix if there are no JSX comments/braces between attributes.
542
680
  const braceConflict = attrs.find((currAttr, idx) => {
543
681
  if (idx === 0) {
package/tsconfig.json CHANGED
@@ -2,15 +2,15 @@
2
2
  "compilerOptions": {
3
3
  "esModuleInterop": true,
4
4
  "forceConsistentCasingInFileNames": true,
5
+ "incremental": true,
5
6
  "jsx": "react-jsx",
6
7
  "lib": ["DOM", "DOM.Iterable", "ESNext"],
7
8
  "module": "ESNext",
8
9
  "moduleResolution": "bundler",
10
+ "noUncheckedIndexedAccess": true,
9
11
  "skipLibCheck": true,
10
12
  "strict": true,
11
13
  "target": "ESNext",
12
- "noUncheckedIndexedAccess": true,
13
- "incremental": true,
14
14
  "tsBuildInfoFile": ".absolutejs/tsconfig.tsbuildinfo",
15
15
  "types": ["bun-types"]
16
16
  }