eslint-plugin-absolute 0.2.2 → 0.2.4

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 CHANGED
@@ -186,6 +186,16 @@ var explicitObjectTypes = {
186
186
 
187
187
  // src/rules/sort-keys-fixable.ts
188
188
  var SORT_BEFORE = -1;
189
+ var PURE_CONSTRUCTORS = new Set(["Date"]);
190
+ var PURE_GLOBAL_FUNCTIONS = new Set(["Boolean", "Number", "String"]);
191
+ var PURE_MEMBER_METHODS = new Set([
192
+ "getDay",
193
+ "getHours",
194
+ "getMilliseconds",
195
+ "getMinutes",
196
+ "getSeconds",
197
+ "padStart"
198
+ ]);
189
199
  var hasDuplicateNames = (names) => {
190
200
  const seen = new Set;
191
201
  const nonNullNames = names.flatMap((name) => name === null ? [] : [name]);
@@ -197,73 +207,54 @@ var hasDuplicateNames = (names) => {
197
207
  }
198
208
  return false;
199
209
  };
200
- var isSafeStaticTemplate = (node) => node.expressions.length === 0;
201
- var isSafeArrayElement = (node) => {
202
- if (!node || node.type === "SpreadElement") {
203
- return false;
204
- }
205
- return isSafeToReorderExpression(node);
206
- };
207
- var isSafeObjectProperty = (property) => {
208
- if (property.type !== "Property" || property.computed || property.kind !== "init") {
209
- return false;
210
- }
211
- if (property.key.type !== "Identifier" && property.key.type !== "Literal") {
212
- return false;
213
- }
214
- if (property.method) {
215
- return true;
216
- }
217
- return isSafeToReorderExpression(property.value);
218
- };
219
- var isSafeToReorderExpression = (node) => {
220
- if (!node || node.type === "PrivateIdentifier") {
221
- return false;
222
- }
223
- switch (node.type) {
224
- case "Identifier":
225
- case "Literal":
226
- case "ThisExpression":
227
- case "FunctionExpression":
228
- case "ArrowFunctionExpression":
229
- case "ClassExpression":
230
- return true;
231
- case "TemplateLiteral":
232
- return isSafeStaticTemplate(node);
233
- case "UnaryExpression":
234
- return isSafeToReorderExpression(node.argument);
235
- case "ArrayExpression":
236
- return node.elements.every(isSafeArrayElement);
237
- case "ObjectExpression":
238
- return node.properties.every(isSafeObjectProperty);
239
- default:
240
- return false;
241
- }
242
- };
243
- var isSafeJSXAttributeValue = (value) => {
244
- if (value === null) {
245
- return true;
246
- }
247
- if (value.type === "Literal") {
248
- return true;
249
- }
250
- if (value.type !== "JSXExpressionContainer") {
251
- return false;
252
- }
253
- if (value.expression.type === "JSXEmptyExpression") {
254
- return false;
255
- }
256
- return isSafeToReorderExpression(value.expression);
257
- };
258
210
  var sortKeysFixable = {
259
211
  create(context) {
260
212
  const { sourceCode } = context;
261
213
  const [option] = context.options;
214
+ const topLevelBindings = new Map;
215
+ const pureFunctionCache = new Map;
216
+ const pureFunctionInProgress = new Set;
262
217
  const order = option && option.order ? option.order : "asc";
263
218
  const caseSensitive = option && typeof option.caseSensitive === "boolean" ? option.caseSensitive : false;
264
219
  const natural = option && typeof option.natural === "boolean" ? option.natural : false;
265
220
  const minKeys = option && typeof option.minKeys === "number" ? option.minKeys : 2;
266
221
  const variablesBeforeFunctions = option && typeof option.variablesBeforeFunctions === "boolean" ? option.variablesBeforeFunctions : false;
222
+ for (const statement of sourceCode.ast.body) {
223
+ if (statement.type === "ImportDeclaration" && statement.specifiers.length > 0) {
224
+ for (const specifier of statement.specifiers) {
225
+ topLevelBindings.set(specifier.local.name, {
226
+ kind: "import"
227
+ });
228
+ }
229
+ continue;
230
+ }
231
+ if (statement.type === "FunctionDeclaration" && statement.id) {
232
+ topLevelBindings.set(statement.id.name, {
233
+ kind: "function",
234
+ node: statement
235
+ });
236
+ continue;
237
+ }
238
+ if (statement.type !== "VariableDeclaration" || statement.kind !== "const") {
239
+ continue;
240
+ }
241
+ for (const declaration of statement.declarations) {
242
+ if (declaration.id.type !== "Identifier" || !declaration.init) {
243
+ continue;
244
+ }
245
+ if (declaration.init.type === "ArrowFunctionExpression" || declaration.init.type === "FunctionExpression") {
246
+ topLevelBindings.set(declaration.id.name, {
247
+ kind: "function",
248
+ node: declaration.init
249
+ });
250
+ continue;
251
+ }
252
+ topLevelBindings.set(declaration.id.name, {
253
+ kind: "value",
254
+ node: declaration.init
255
+ });
256
+ }
257
+ }
267
258
  const compareKeys = (keyLeft, keyRight) => {
268
259
  let left = keyLeft;
269
260
  let right = keyRight;
@@ -278,6 +269,177 @@ var sortKeysFixable = {
278
269
  }
279
270
  return left.localeCompare(right);
280
271
  };
272
+ const getStaticMemberName = (memberExpression) => {
273
+ if (!memberExpression.computed && memberExpression.property.type === "Identifier") {
274
+ return memberExpression.property.name;
275
+ }
276
+ if (memberExpression.computed && memberExpression.property.type === "Literal" && typeof memberExpression.property.value === "string") {
277
+ return memberExpression.property.value;
278
+ }
279
+ return null;
280
+ };
281
+ const isStableIdentifier = (name, stableLocals) => {
282
+ if (stableLocals.has(name)) {
283
+ return true;
284
+ }
285
+ const binding = topLevelBindings.get(name);
286
+ if (!binding) {
287
+ return false;
288
+ }
289
+ if (binding.kind === "import") {
290
+ return true;
291
+ }
292
+ if (binding.kind === "value") {
293
+ return isPureRuntimeExpression(binding.node, stableLocals);
294
+ }
295
+ return false;
296
+ };
297
+ const isPureTopLevelFunction = (functionNode) => {
298
+ const cached = pureFunctionCache.get(functionNode);
299
+ if (cached !== undefined) {
300
+ return cached;
301
+ }
302
+ if (pureFunctionInProgress.has(functionNode)) {
303
+ return false;
304
+ }
305
+ pureFunctionInProgress.add(functionNode);
306
+ const stableLocals = new Set;
307
+ for (const parameter of functionNode.params) {
308
+ if (parameter.type === "Identifier") {
309
+ stableLocals.add(parameter.name);
310
+ }
311
+ }
312
+ let isPure = true;
313
+ const checkExpression = (expression) => isPureRuntimeExpression(expression, stableLocals);
314
+ if (functionNode.body.type === "BlockStatement") {
315
+ for (const statement of functionNode.body.body) {
316
+ if (statement.type === "ReturnStatement") {
317
+ if (statement.argument && !checkExpression(statement.argument)) {
318
+ isPure = false;
319
+ }
320
+ continue;
321
+ }
322
+ if (statement.type === "VariableDeclaration" && statement.kind === "const") {
323
+ for (const declaration of statement.declarations) {
324
+ if (declaration.id.type !== "Identifier" || !declaration.init || !checkExpression(declaration.init)) {
325
+ isPure = false;
326
+ break;
327
+ }
328
+ stableLocals.add(declaration.id.name);
329
+ }
330
+ if (!isPure) {
331
+ break;
332
+ }
333
+ continue;
334
+ }
335
+ isPure = false;
336
+ break;
337
+ }
338
+ } else {
339
+ isPure = checkExpression(functionNode.body);
340
+ }
341
+ pureFunctionInProgress.delete(functionNode);
342
+ pureFunctionCache.set(functionNode, isPure);
343
+ return isPure;
344
+ };
345
+ const isPureRuntimeExpression = (node, stableLocals) => {
346
+ if (!node || node.type === "PrivateIdentifier") {
347
+ return false;
348
+ }
349
+ switch (node.type) {
350
+ case "Identifier":
351
+ return isStableIdentifier(node.name, stableLocals);
352
+ case "Literal":
353
+ case "FunctionExpression":
354
+ case "ArrowFunctionExpression":
355
+ case "ClassExpression":
356
+ return true;
357
+ case "ThisExpression":
358
+ return stableLocals.has("this");
359
+ case "TemplateLiteral":
360
+ return node.expressions.every((expression) => isPureRuntimeExpression(expression, stableLocals));
361
+ case "UnaryExpression":
362
+ return isPureRuntimeExpression(node.argument, stableLocals);
363
+ case "BinaryExpression":
364
+ case "LogicalExpression":
365
+ return isPureRuntimeExpression(node.left, stableLocals) && isPureRuntimeExpression(node.right, stableLocals);
366
+ case "ConditionalExpression":
367
+ return isPureRuntimeExpression(node.test, stableLocals) && isPureRuntimeExpression(node.consequent, stableLocals) && isPureRuntimeExpression(node.alternate, stableLocals);
368
+ case "ArrayExpression":
369
+ return node.elements.every((element) => {
370
+ if (!element || element.type === "SpreadElement") {
371
+ return false;
372
+ }
373
+ return isPureRuntimeExpression(element, stableLocals);
374
+ });
375
+ case "ObjectExpression":
376
+ return node.properties.every((property) => {
377
+ if (property.type !== "Property" || property.computed || property.kind !== "init") {
378
+ return false;
379
+ }
380
+ if (property.key.type !== "Identifier" && property.key.type !== "Literal") {
381
+ return false;
382
+ }
383
+ if (property.method) {
384
+ return true;
385
+ }
386
+ return isPureRuntimeExpression(property.value, stableLocals);
387
+ });
388
+ case "MemberExpression":
389
+ return isPureRuntimeExpression(node.object, stableLocals) && (!node.computed || isPureRuntimeExpression(node.property, stableLocals));
390
+ case "NewExpression":
391
+ return node.callee.type === "Identifier" && PURE_CONSTRUCTORS.has(node.callee.name) && node.arguments.every((argument) => {
392
+ if (argument.type === "SpreadElement") {
393
+ return false;
394
+ }
395
+ return isPureRuntimeExpression(argument, stableLocals);
396
+ });
397
+ case "CallExpression": {
398
+ const argsArePure = node.arguments.every((argument) => {
399
+ if (argument.type === "SpreadElement") {
400
+ return false;
401
+ }
402
+ return isPureRuntimeExpression(argument, stableLocals);
403
+ });
404
+ if (!argsArePure) {
405
+ return false;
406
+ }
407
+ if (node.callee.type === "Identifier") {
408
+ if (PURE_GLOBAL_FUNCTIONS.has(node.callee.name)) {
409
+ return true;
410
+ }
411
+ const binding = topLevelBindings.get(node.callee.name);
412
+ return binding?.kind === "function" && isPureTopLevelFunction(binding.node);
413
+ }
414
+ if (node.callee.type !== "MemberExpression") {
415
+ return false;
416
+ }
417
+ const memberName = getStaticMemberName(node.callee);
418
+ if (!memberName || !PURE_MEMBER_METHODS.has(memberName)) {
419
+ return false;
420
+ }
421
+ return isPureRuntimeExpression(node.callee.object, stableLocals);
422
+ }
423
+ default:
424
+ return false;
425
+ }
426
+ };
427
+ const isSafeToReorderExpression = (node) => isPureRuntimeExpression(node, new Set);
428
+ const isSafeJSXAttributeValue = (value) => {
429
+ if (value === null) {
430
+ return true;
431
+ }
432
+ if (value.type === "Literal") {
433
+ return true;
434
+ }
435
+ if (value.type !== "JSXExpressionContainer") {
436
+ return false;
437
+ }
438
+ if (value.expression.type === "JSXEmptyExpression") {
439
+ return false;
440
+ }
441
+ return isSafeToReorderExpression(value.expression);
442
+ };
281
443
  const isFunctionProperty = (prop) => {
282
444
  const { value } = prop;
283
445
  return Boolean(value) && (value.type === "FunctionExpression" || value.type === "ArrowFunctionExpression" || prop.method === true);
@@ -1114,7 +1276,7 @@ var sortExports = {
1114
1276
  });
1115
1277
  return unsorted ? messageId : null;
1116
1278
  };
1117
- const checkForwardDependencies = (items) => {
1279
+ const hasForwardDependenciesInOrder = (items) => {
1118
1280
  const exportNames = items.map((item) => item.name);
1119
1281
  return items.some((item, idx) => {
1120
1282
  const laterNames = new Set(exportNames.slice(idx + 1));
@@ -1130,6 +1292,23 @@ var sortExports = {
1130
1292
  return false;
1131
1293
  });
1132
1294
  };
1295
+ const wouldCreateForwardDependencies = (items, sortedItems) => {
1296
+ const sortedIndices = new Map(sortedItems.map((item, idx) => [item.name, idx]));
1297
+ const exportNames = new Set(items.map((item) => item.name));
1298
+ return items.some((item) => {
1299
+ const itemIndex = sortedIndices.get(item.name);
1300
+ if (itemIndex === undefined) {
1301
+ return false;
1302
+ }
1303
+ const dependencies = getImmediateDependencyNames(item.node);
1304
+ for (const dependency of dependencies) {
1305
+ const dependencyIndex = exportNames.has(dependency) ? sortedIndices.get(dependency) : undefined;
1306
+ if (dependencyIndex !== undefined && itemIndex < dependencyIndex)
1307
+ return true;
1308
+ }
1309
+ return false;
1310
+ });
1311
+ };
1133
1312
  const processExportBlock = (block) => {
1134
1313
  if (block.length < minKeys) {
1135
1314
  return;
@@ -1142,10 +1321,13 @@ var sortExports = {
1142
1321
  if (!messageId) {
1143
1322
  return;
1144
1323
  }
1145
- if (checkForwardDependencies(items)) {
1324
+ if (hasForwardDependenciesInOrder(items)) {
1146
1325
  return;
1147
1326
  }
1148
1327
  const sortedItems = items.slice().sort(sortComparator);
1328
+ if (wouldCreateForwardDependencies(items, sortedItems)) {
1329
+ return;
1330
+ }
1149
1331
  const expectedOrder = sortedItems.map((item) => item.name).join(", ");
1150
1332
  const [firstNode] = block;
1151
1333
  const lastNode = block[block.length - 1];
package/package.json CHANGED
@@ -30,5 +30,5 @@
30
30
  "typecheck": "bun run tsc --noEmit"
31
31
  },
32
32
  "type": "module",
33
- "version": "0.2.2"
33
+ "version": "0.2.4"
34
34
  }
@@ -388,7 +388,7 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
388
388
  return unsorted ? messageId : null;
389
389
  };
390
390
 
391
- const checkForwardDependencies = (items: ExportItem[]) => {
391
+ const hasForwardDependenciesInOrder = (items: ExportItem[]) => {
392
392
  const exportNames = items.map((item) => item.name);
393
393
  return items.some((item, idx) => {
394
394
  const laterNames = new Set(exportNames.slice(idx + 1));
@@ -406,6 +406,37 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
406
406
  });
407
407
  };
408
408
 
409
+ const wouldCreateForwardDependencies = (
410
+ items: ExportItem[],
411
+ sortedItems: ExportItem[]
412
+ ) => {
413
+ const sortedIndices = new Map(
414
+ sortedItems.map((item, idx) => [item.name, idx])
415
+ );
416
+ const exportNames = new Set(items.map((item) => item.name));
417
+
418
+ return items.some((item) => {
419
+ const itemIndex = sortedIndices.get(item.name);
420
+ if (itemIndex === undefined) {
421
+ return false;
422
+ }
423
+
424
+ const dependencies = getImmediateDependencyNames(item.node);
425
+ for (const dependency of dependencies) {
426
+ const dependencyIndex = exportNames.has(dependency)
427
+ ? sortedIndices.get(dependency)
428
+ : undefined;
429
+ if (
430
+ dependencyIndex !== undefined &&
431
+ itemIndex < dependencyIndex
432
+ )
433
+ return true;
434
+ }
435
+
436
+ return false;
437
+ });
438
+ };
439
+
409
440
  const processExportBlock = (
410
441
  block: TSESTree.ExportNamedDeclaration[]
411
442
  ) => {
@@ -424,12 +455,16 @@ export const sortExports: TSESLint.RuleModule<MessageIds, Options> = {
424
455
  return;
425
456
  }
426
457
 
427
- if (checkForwardDependencies(items)) {
458
+ if (hasForwardDependenciesInOrder(items)) {
428
459
  return;
429
460
  }
430
461
 
431
462
  const sortedItems = items.slice().sort(sortComparator);
432
463
 
464
+ if (wouldCreateForwardDependencies(items, sortedItems)) {
465
+ return;
466
+ }
467
+
433
468
  const expectedOrder = sortedItems
434
469
  .map((item) => item.name)
435
470
  .join(", ");