eslint-plugin-absolute 0.2.8 → 0.3.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 +469 -11
- 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([
|
|
@@ -236,6 +237,13 @@ var sortKeysFixable = {
|
|
|
236
237
|
const natural = option && typeof option.natural === "boolean" ? option.natural : false;
|
|
237
238
|
const minKeys = option && typeof option.minKeys === "number" ? option.minKeys : 2;
|
|
238
239
|
const variablesBeforeFunctions = option && typeof option.variablesBeforeFunctions === "boolean" ? option.variablesBeforeFunctions : false;
|
|
240
|
+
const pureImports = new Set(option && Array.isArray(option.pureImports) ? option.pureImports : []);
|
|
241
|
+
const parserServices = sourceCode.parserServices ?? null;
|
|
242
|
+
const tsProgram = parserServices && "program" in parserServices ? parserServices.program : null;
|
|
243
|
+
const tsChecker = tsProgram ? tsProgram.getTypeChecker() : null;
|
|
244
|
+
const esTreeNodeToTSNodeMap = parserServices && "esTreeNodeToTSNodeMap" in parserServices ? parserServices.esTreeNodeToTSNodeMap : null;
|
|
245
|
+
const importedCallPurityCache = new Map;
|
|
246
|
+
const importedCallPurityInProgress = new Set;
|
|
239
247
|
const compareKeys = (keyLeft, keyRight) => {
|
|
240
248
|
let left = keyLeft;
|
|
241
249
|
let right = keyRight;
|
|
@@ -449,6 +457,329 @@ var sortKeysFixable = {
|
|
|
449
457
|
pureFunctionCache.set(functionNode, isPure);
|
|
450
458
|
return isPure;
|
|
451
459
|
};
|
|
460
|
+
const ASSIGNMENT_OPERATOR_KINDS = new Set([
|
|
461
|
+
ts.SyntaxKind.EqualsToken,
|
|
462
|
+
ts.SyntaxKind.PlusEqualsToken,
|
|
463
|
+
ts.SyntaxKind.MinusEqualsToken,
|
|
464
|
+
ts.SyntaxKind.AsteriskEqualsToken,
|
|
465
|
+
ts.SyntaxKind.AsteriskAsteriskEqualsToken,
|
|
466
|
+
ts.SyntaxKind.SlashEqualsToken,
|
|
467
|
+
ts.SyntaxKind.PercentEqualsToken,
|
|
468
|
+
ts.SyntaxKind.AmpersandEqualsToken,
|
|
469
|
+
ts.SyntaxKind.BarEqualsToken,
|
|
470
|
+
ts.SyntaxKind.CaretEqualsToken,
|
|
471
|
+
ts.SyntaxKind.LessThanLessThanEqualsToken,
|
|
472
|
+
ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
|
|
473
|
+
ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
|
|
474
|
+
ts.SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
475
|
+
ts.SyntaxKind.BarBarEqualsToken,
|
|
476
|
+
ts.SyntaxKind.QuestionQuestionEqualsToken
|
|
477
|
+
]);
|
|
478
|
+
const addTsBoundIdentifiers = (node, stableLocals) => {
|
|
479
|
+
if (node.kind === ts.SyntaxKind.OmittedExpression) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (ts.isIdentifier(node)) {
|
|
483
|
+
stableLocals.add(node.text);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (ts.isBindingElement(node)) {
|
|
487
|
+
addTsBoundIdentifiers(node.name, stableLocals);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node)) {
|
|
491
|
+
for (const element of node.elements) {
|
|
492
|
+
addTsBoundIdentifiers(element, stableLocals);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const getCalleeIdentifier = (callee) => {
|
|
497
|
+
if (ts.isIdentifier(callee)) {
|
|
498
|
+
return callee;
|
|
499
|
+
}
|
|
500
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
|
|
501
|
+
return callee.name;
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
};
|
|
505
|
+
const getTsCalleePath = (callee) => {
|
|
506
|
+
if (ts.isParenthesizedExpression(callee)) {
|
|
507
|
+
return getTsCalleePath(callee.expression);
|
|
508
|
+
}
|
|
509
|
+
if (ts.isIdentifier(callee)) {
|
|
510
|
+
return callee.text;
|
|
511
|
+
}
|
|
512
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
|
|
513
|
+
const objectPath = getTsCalleePath(callee.expression);
|
|
514
|
+
return objectPath === null ? null : `${objectPath}.${callee.name.text}`;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
};
|
|
518
|
+
const getFunctionLikeFromDeclaration = (declaration) => {
|
|
519
|
+
if (ts.isFunctionDeclaration(declaration)) {
|
|
520
|
+
return declaration;
|
|
521
|
+
}
|
|
522
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
523
|
+
const init = declaration.initializer;
|
|
524
|
+
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
|
|
525
|
+
return init;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
};
|
|
530
|
+
const isPureTsIdentifier = (identifier, stableLocals) => {
|
|
531
|
+
const name = identifier.text;
|
|
532
|
+
if (PURE_GLOBAL_IDENTIFIERS.has(name)) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
if (stableLocals.has(name)) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
if (!tsChecker) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
const symbol = tsChecker.getSymbolAtLocation(identifier);
|
|
542
|
+
if (!symbol) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
const target = symbol.flags & ts.SymbolFlags.Alias ? tsChecker.getAliasedSymbol(symbol) : symbol;
|
|
546
|
+
const declaration = target.declarations?.[0];
|
|
547
|
+
if (!declaration) {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
if (declaration.getSourceFile().isDeclarationFile) {
|
|
551
|
+
return pureImports.has(name);
|
|
552
|
+
}
|
|
553
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer && declaration.parent && ts.isVariableDeclarationList(declaration.parent) && declaration.parent.flags & ts.NodeFlags.Const) {
|
|
554
|
+
return isPureTsExpression(declaration.initializer, new Set);
|
|
555
|
+
}
|
|
556
|
+
if (ts.isFunctionDeclaration(declaration) || ts.isClassDeclaration(declaration)) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
};
|
|
561
|
+
const isPureTsBlock = (block, stableLocals) => {
|
|
562
|
+
for (const statement of block.statements) {
|
|
563
|
+
if (ts.isReturnStatement(statement)) {
|
|
564
|
+
if (statement.expression && !isPureTsExpression(statement.expression, stableLocals)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (ts.isVariableStatement(statement)) {
|
|
570
|
+
if (!(statement.declarationList.flags & ts.NodeFlags.Const)) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
574
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
if (!isPureTsExpression(declaration.initializer, stableLocals)) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
stableLocals.add(declaration.name.text);
|
|
581
|
+
}
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
return true;
|
|
587
|
+
};
|
|
588
|
+
const isPureTsFunction = (func) => {
|
|
589
|
+
const cached = importedCallPurityCache.get(func);
|
|
590
|
+
if (cached !== undefined) {
|
|
591
|
+
return cached;
|
|
592
|
+
}
|
|
593
|
+
if (importedCallPurityInProgress.has(func)) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
importedCallPurityInProgress.add(func);
|
|
597
|
+
const stableLocals = new Set;
|
|
598
|
+
for (const parameter of func.parameters) {
|
|
599
|
+
addTsBoundIdentifiers(parameter.name, stableLocals);
|
|
600
|
+
}
|
|
601
|
+
let pure = false;
|
|
602
|
+
if (func.body) {
|
|
603
|
+
pure = ts.isBlock(func.body) ? isPureTsBlock(func.body, stableLocals) : isPureTsExpression(func.body, stableLocals);
|
|
604
|
+
}
|
|
605
|
+
importedCallPurityInProgress.delete(func);
|
|
606
|
+
importedCallPurityCache.set(func, pure);
|
|
607
|
+
return pure;
|
|
608
|
+
};
|
|
609
|
+
const isPureTsCallExpression = (node, stableLocals) => {
|
|
610
|
+
const argsArePure = node.arguments.every((argument) => {
|
|
611
|
+
if (ts.isSpreadElement(argument)) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return isPureTsExpression(argument, stableLocals);
|
|
615
|
+
});
|
|
616
|
+
if (!argsArePure) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
const calleePath = getTsCalleePath(node.expression);
|
|
620
|
+
if (calleePath !== null && pureImports.has(calleePath)) {
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
const calleeId = getCalleeIdentifier(node.expression);
|
|
624
|
+
if (!calleeId) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
if (PURE_GLOBAL_FUNCTIONS.has(calleeId.text)) {
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
if (!tsChecker) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
const symbol = tsChecker.getSymbolAtLocation(calleeId);
|
|
634
|
+
if (!symbol) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
const target = symbol.flags & ts.SymbolFlags.Alias ? tsChecker.getAliasedSymbol(symbol) : symbol;
|
|
638
|
+
const declaration = target.declarations?.[0];
|
|
639
|
+
if (!declaration) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
if (declaration.getSourceFile().isDeclarationFile) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
const funcNode = getFunctionLikeFromDeclaration(declaration);
|
|
646
|
+
if (!funcNode) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
return isPureTsFunction(funcNode);
|
|
650
|
+
};
|
|
651
|
+
const isPureTsExpression = (node, stableLocals) => {
|
|
652
|
+
if (!node) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node) || ts.isNonNullExpression(node) || ts.isSatisfiesExpression(node)) {
|
|
656
|
+
return isPureTsExpression(node.expression, stableLocals);
|
|
657
|
+
}
|
|
658
|
+
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) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
if (ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isClassExpression(node)) {
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
if (node.kind === ts.SyntaxKind.ThisKeyword) {
|
|
665
|
+
return stableLocals.has("this");
|
|
666
|
+
}
|
|
667
|
+
if (ts.isIdentifier(node)) {
|
|
668
|
+
return isPureTsIdentifier(node, stableLocals);
|
|
669
|
+
}
|
|
670
|
+
if (ts.isTemplateExpression(node)) {
|
|
671
|
+
return node.templateSpans.every((span) => isPureTsExpression(span.expression, stableLocals));
|
|
672
|
+
}
|
|
673
|
+
if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) {
|
|
674
|
+
return isPureTsExpression(node.operand, stableLocals);
|
|
675
|
+
}
|
|
676
|
+
if (ts.isBinaryExpression(node)) {
|
|
677
|
+
if (ASSIGNMENT_OPERATOR_KINDS.has(node.operatorToken.kind) || node.operatorToken.kind === ts.SyntaxKind.CommaToken) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
return isPureTsExpression(node.left, stableLocals) && isPureTsExpression(node.right, stableLocals);
|
|
681
|
+
}
|
|
682
|
+
if (ts.isConditionalExpression(node)) {
|
|
683
|
+
return isPureTsExpression(node.condition, stableLocals) && isPureTsExpression(node.whenTrue, stableLocals) && isPureTsExpression(node.whenFalse, stableLocals);
|
|
684
|
+
}
|
|
685
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
686
|
+
return node.elements.every((element) => {
|
|
687
|
+
if (ts.isSpreadElement(element)) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
if (element.kind === ts.SyntaxKind.OmittedExpression) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
return isPureTsExpression(element, stableLocals);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
697
|
+
return node.properties.every((property) => {
|
|
698
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
699
|
+
return isPureTsIdentifier(property.name, stableLocals);
|
|
700
|
+
}
|
|
701
|
+
if (ts.isPropertyAssignment(property)) {
|
|
702
|
+
if (ts.isComputedPropertyName(property.name)) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
return isPureTsExpression(property.initializer, stableLocals);
|
|
706
|
+
}
|
|
707
|
+
if (ts.isMethodDeclaration(property)) {
|
|
708
|
+
return !ts.isComputedPropertyName(property.name);
|
|
709
|
+
}
|
|
710
|
+
return false;
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
714
|
+
return isPureTsExpression(node.expression, stableLocals);
|
|
715
|
+
}
|
|
716
|
+
if (ts.isElementAccessExpression(node)) {
|
|
717
|
+
return isPureTsExpression(node.expression, stableLocals) && isPureTsExpression(node.argumentExpression, stableLocals);
|
|
718
|
+
}
|
|
719
|
+
if (ts.isNewExpression(node)) {
|
|
720
|
+
if (!ts.isIdentifier(node.expression) || !PURE_CONSTRUCTORS.has(node.expression.text)) {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
return node.arguments?.every((argument) => {
|
|
724
|
+
if (ts.isSpreadElement(argument)) {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
return isPureTsExpression(argument, stableLocals);
|
|
728
|
+
}) ?? true;
|
|
729
|
+
}
|
|
730
|
+
if (ts.isCallExpression(node)) {
|
|
731
|
+
return isPureTsCallExpression(node, stableLocals);
|
|
732
|
+
}
|
|
733
|
+
return false;
|
|
734
|
+
};
|
|
735
|
+
const getCalleePath = (node) => {
|
|
736
|
+
if (node.type === "Identifier") {
|
|
737
|
+
return node.name;
|
|
738
|
+
}
|
|
739
|
+
if (node.type === "ChainExpression") {
|
|
740
|
+
return getCalleePath(node.expression);
|
|
741
|
+
}
|
|
742
|
+
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
|
|
743
|
+
const objectPath = getCalleePath(node.object);
|
|
744
|
+
return objectPath === null ? null : `${objectPath}.${node.property.name}`;
|
|
745
|
+
}
|
|
746
|
+
return null;
|
|
747
|
+
};
|
|
748
|
+
const isPureImportedCallExpression = (callExpression) => {
|
|
749
|
+
if (!tsChecker || !esTreeNodeToTSNodeMap) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
let calleeEsNode = null;
|
|
753
|
+
if (callExpression.callee.type === "Identifier") {
|
|
754
|
+
calleeEsNode = callExpression.callee;
|
|
755
|
+
} else if (callExpression.callee.type === "MemberExpression" && !callExpression.callee.computed && callExpression.callee.property.type === "Identifier") {
|
|
756
|
+
calleeEsNode = callExpression.callee.property;
|
|
757
|
+
}
|
|
758
|
+
if (!calleeEsNode) {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
const tsCallee = esTreeNodeToTSNodeMap.get(calleeEsNode);
|
|
762
|
+
if (!tsCallee || !ts.isIdentifier(tsCallee)) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
const symbol = tsChecker.getSymbolAtLocation(tsCallee);
|
|
766
|
+
if (!symbol) {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
const target = symbol.flags & ts.SymbolFlags.Alias ? tsChecker.getAliasedSymbol(symbol) : symbol;
|
|
770
|
+
const declaration = target.declarations?.[0];
|
|
771
|
+
if (!declaration) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
if (declaration.getSourceFile().isDeclarationFile) {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
const funcNode = getFunctionLikeFromDeclaration(declaration);
|
|
778
|
+
if (!funcNode) {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
return isPureTsFunction(funcNode);
|
|
782
|
+
};
|
|
452
783
|
const isPureIdentifierCall = (callExpression) => {
|
|
453
784
|
if (callExpression.callee.type !== "Identifier") {
|
|
454
785
|
return false;
|
|
@@ -456,8 +787,14 @@ var sortKeysFixable = {
|
|
|
456
787
|
if (PURE_GLOBAL_FUNCTIONS.has(callExpression.callee.name)) {
|
|
457
788
|
return true;
|
|
458
789
|
}
|
|
790
|
+
if (pureImports.has(callExpression.callee.name)) {
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
459
793
|
const binding = topLevelBindings.get(callExpression.callee.name);
|
|
460
|
-
|
|
794
|
+
if (binding?.kind === "function") {
|
|
795
|
+
return isPureTopLevelFunction(binding.node);
|
|
796
|
+
}
|
|
797
|
+
return isPureImportedCallExpression(callExpression);
|
|
461
798
|
};
|
|
462
799
|
const isPureRuntimeExpression = (node, stableLocals) => {
|
|
463
800
|
if (!node || node.type === "PrivateIdentifier") {
|
|
@@ -473,6 +810,8 @@ var sortKeysFixable = {
|
|
|
473
810
|
return true;
|
|
474
811
|
case "ThisExpression":
|
|
475
812
|
return stableLocals.has("this");
|
|
813
|
+
case "ChainExpression":
|
|
814
|
+
return isPureRuntimeExpression(node.expression, stableLocals);
|
|
476
815
|
case "TemplateLiteral":
|
|
477
816
|
return node.expressions.every((expression) => isPureRuntimeExpression(expression, stableLocals));
|
|
478
817
|
case "UnaryExpression":
|
|
@@ -521,6 +860,10 @@ var sortKeysFixable = {
|
|
|
521
860
|
if (!argsArePure) {
|
|
522
861
|
return false;
|
|
523
862
|
}
|
|
863
|
+
const calleePath = getCalleePath(node.callee);
|
|
864
|
+
if (calleePath !== null && pureImports.has(calleePath)) {
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
524
867
|
if (node.callee.type === "Identifier") {
|
|
525
868
|
return isPureIdentifierCall(node);
|
|
526
869
|
}
|
|
@@ -528,10 +871,10 @@ var sortKeysFixable = {
|
|
|
528
871
|
return false;
|
|
529
872
|
}
|
|
530
873
|
const memberName = getStaticMemberName(node.callee);
|
|
531
|
-
if (
|
|
532
|
-
return
|
|
874
|
+
if (memberName && PURE_MEMBER_METHODS.has(memberName)) {
|
|
875
|
+
return isPureRuntimeExpression(node.callee.object, stableLocals);
|
|
533
876
|
}
|
|
534
|
-
return
|
|
877
|
+
return isPureImportedCallExpression(node);
|
|
535
878
|
}
|
|
536
879
|
default:
|
|
537
880
|
return false;
|
|
@@ -698,8 +1041,11 @@ ${indent}`;
|
|
|
698
1041
|
if (hasDuplicateNames(keys.map((key) => key.keyName))) {
|
|
699
1042
|
autoFixable = false;
|
|
700
1043
|
}
|
|
701
|
-
if (autoFixable
|
|
702
|
-
|
|
1044
|
+
if (autoFixable) {
|
|
1045
|
+
const impureCount = keys.filter((key) => key.node.type === "Property" && !isPureRuntimeExpression(key.node.value, getStableLocalsForNode(key.node))).length;
|
|
1046
|
+
if (impureCount > 1) {
|
|
1047
|
+
autoFixable = false;
|
|
1048
|
+
}
|
|
703
1049
|
}
|
|
704
1050
|
let fixProvided = false;
|
|
705
1051
|
const createReportWithFix = (curr, shouldFix) => {
|
|
@@ -797,7 +1143,8 @@ ${indent}`;
|
|
|
797
1143
|
});
|
|
798
1144
|
return;
|
|
799
1145
|
}
|
|
800
|
-
|
|
1146
|
+
const impureAttrCount = attrs.filter((attr) => attr.type === "JSXAttribute" && !isSafeJSXAttributeValue(attr.value, attr)).length;
|
|
1147
|
+
if (impureAttrCount > 1) {
|
|
801
1148
|
context.report({
|
|
802
1149
|
messageId: "unsorted",
|
|
803
1150
|
node: attrs[0].type === "JSXAttribute" ? attrs[0].name : attrs[0]
|
|
@@ -872,6 +1219,11 @@ ${indent}`;
|
|
|
872
1219
|
enum: ["asc", "desc"],
|
|
873
1220
|
type: "string"
|
|
874
1221
|
},
|
|
1222
|
+
pureImports: {
|
|
1223
|
+
items: { type: "string" },
|
|
1224
|
+
type: "array",
|
|
1225
|
+
uniqueItems: true
|
|
1226
|
+
},
|
|
875
1227
|
variablesBeforeFunctions: {
|
|
876
1228
|
type: "boolean"
|
|
877
1229
|
}
|
|
@@ -2666,16 +3018,121 @@ var noInlinePropTypes = {
|
|
|
2666
3018
|
}
|
|
2667
3019
|
};
|
|
2668
3020
|
|
|
2669
|
-
// src/rules/no-
|
|
3021
|
+
// src/rules/no-nondeterministic-render.ts
|
|
2670
3022
|
import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
|
|
3023
|
+
var BANNED_TEMPLATE_PATTERN = /\bMath\.random\s*\(|\bDate\.now\s*\(|\bnew\s+Date\s*\(\s*\)|\bcrypto\.randomUUID\s*\(|\bperformance\.now\s*\(/;
|
|
3024
|
+
var isIdentifier2 = (node, name) => node?.type === AST_NODE_TYPES3.Identifier && node.name === name;
|
|
3025
|
+
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);
|
|
3026
|
+
var getPropertyName2 = (node) => {
|
|
3027
|
+
const { key } = node;
|
|
3028
|
+
if (key.type === AST_NODE_TYPES3.Identifier)
|
|
3029
|
+
return key.name;
|
|
3030
|
+
if (key.type === AST_NODE_TYPES3.Literal && typeof key.value === "string") {
|
|
3031
|
+
return key.value;
|
|
3032
|
+
}
|
|
3033
|
+
return null;
|
|
3034
|
+
};
|
|
3035
|
+
var isComponentDecorator = (decorator) => {
|
|
3036
|
+
const { expression } = decorator;
|
|
3037
|
+
return expression.type === AST_NODE_TYPES3.CallExpression && isIdentifier2(expression.callee, "Component");
|
|
3038
|
+
};
|
|
3039
|
+
var isAngularComponentClass = (node) => node.type === AST_NODE_TYPES3.ClassDeclaration && (node.decorators ?? []).some(isComponentDecorator);
|
|
3040
|
+
var getTemplateText = (node) => {
|
|
3041
|
+
if (node.type === AST_NODE_TYPES3.Literal && typeof node.value === "string") {
|
|
3042
|
+
return node.value;
|
|
3043
|
+
}
|
|
3044
|
+
if (node.type === AST_NODE_TYPES3.TemplateLiteral) {
|
|
3045
|
+
return node.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
|
|
3046
|
+
}
|
|
3047
|
+
return null;
|
|
3048
|
+
};
|
|
3049
|
+
var getEnclosingAngularComponentClass = (node) => {
|
|
3050
|
+
let current = node.parent;
|
|
3051
|
+
while (current) {
|
|
3052
|
+
if (isAngularComponentClass(current))
|
|
3053
|
+
return current;
|
|
3054
|
+
current = current.parent;
|
|
3055
|
+
}
|
|
3056
|
+
return null;
|
|
3057
|
+
};
|
|
3058
|
+
var getEnclosingPropertyDefinition = (node) => {
|
|
3059
|
+
let current = node.parent;
|
|
3060
|
+
while (current) {
|
|
3061
|
+
if (current.type === AST_NODE_TYPES3.PropertyDefinition) {
|
|
3062
|
+
return current;
|
|
3063
|
+
}
|
|
3064
|
+
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) {
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
current = current.parent;
|
|
3068
|
+
}
|
|
3069
|
+
return null;
|
|
3070
|
+
};
|
|
3071
|
+
var isInAngularFieldInitializer = (node) => {
|
|
3072
|
+
const propertyDefinition = getEnclosingPropertyDefinition(node);
|
|
3073
|
+
if (!propertyDefinition || propertyDefinition.value === null)
|
|
3074
|
+
return false;
|
|
3075
|
+
return getEnclosingAngularComponentClass(propertyDefinition) !== null;
|
|
3076
|
+
};
|
|
3077
|
+
var isBannedCall = (node) => isStaticMemberCall(node, "Math", "random") || isStaticMemberCall(node, "Date", "now") || isStaticMemberCall(node, "crypto", "randomUUID") || isStaticMemberCall(node, "performance", "now");
|
|
3078
|
+
var noNondeterministicRender = {
|
|
3079
|
+
create(context) {
|
|
3080
|
+
const reportField = (node) => {
|
|
3081
|
+
if (!isInAngularFieldInitializer(node))
|
|
3082
|
+
return;
|
|
3083
|
+
context.report({
|
|
3084
|
+
messageId: "nondeterministicField",
|
|
3085
|
+
node
|
|
3086
|
+
});
|
|
3087
|
+
};
|
|
3088
|
+
return {
|
|
3089
|
+
CallExpression(node) {
|
|
3090
|
+
if (isBannedCall(node))
|
|
3091
|
+
reportField(node);
|
|
3092
|
+
},
|
|
3093
|
+
"ClassDeclaration > Decorator CallExpression > ObjectExpression > Property"(node) {
|
|
3094
|
+
if (getPropertyName2(node) !== "template")
|
|
3095
|
+
return;
|
|
3096
|
+
const templateText = getTemplateText(node.value);
|
|
3097
|
+
if (templateText === null || !BANNED_TEMPLATE_PATTERN.test(templateText)) {
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
context.report({
|
|
3101
|
+
messageId: "nondeterministicTemplate",
|
|
3102
|
+
node: node.value
|
|
3103
|
+
});
|
|
3104
|
+
},
|
|
3105
|
+
NewExpression(node) {
|
|
3106
|
+
if (isIdentifier2(node.callee, "Date") && node.arguments.length === 0) {
|
|
3107
|
+
reportField(node);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
},
|
|
3112
|
+
defaultOptions: [],
|
|
3113
|
+
meta: {
|
|
3114
|
+
docs: {
|
|
3115
|
+
description: "Disallow nondeterministic values in Angular render paths that can cause SSR hydration mismatches."
|
|
3116
|
+
},
|
|
3117
|
+
messages: {
|
|
3118
|
+
nondeterministicField: "Do not use nondeterministic values in Angular component field initializers. Inject AbsoluteJS deterministic tokens instead.",
|
|
3119
|
+
nondeterministicTemplate: "Do not use nondeterministic values in Angular templates. Compute a deterministic value before render instead."
|
|
3120
|
+
},
|
|
3121
|
+
schema: [],
|
|
3122
|
+
type: "problem"
|
|
3123
|
+
}
|
|
3124
|
+
};
|
|
3125
|
+
|
|
3126
|
+
// src/rules/no-unnecessary-div.ts
|
|
3127
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
|
|
2671
3128
|
var noUnnecessaryDiv = {
|
|
2672
3129
|
create(context) {
|
|
2673
3130
|
const isDivElement = (node) => {
|
|
2674
3131
|
const nameNode = node.openingElement.name;
|
|
2675
|
-
return nameNode.type ===
|
|
3132
|
+
return nameNode.type === AST_NODE_TYPES4.JSXIdentifier && nameNode.name === "div";
|
|
2676
3133
|
};
|
|
2677
3134
|
const isMeaningfulChild = (child) => {
|
|
2678
|
-
if (child.type ===
|
|
3135
|
+
if (child.type === AST_NODE_TYPES4.JSXText) {
|
|
2679
3136
|
return child.value.trim() !== "";
|
|
2680
3137
|
}
|
|
2681
3138
|
return true;
|
|
@@ -2694,7 +3151,7 @@ var noUnnecessaryDiv = {
|
|
|
2694
3151
|
if (!onlyChild) {
|
|
2695
3152
|
return;
|
|
2696
3153
|
}
|
|
2697
|
-
if (onlyChild.type ===
|
|
3154
|
+
if (onlyChild.type === AST_NODE_TYPES4.JSXElement) {
|
|
2698
3155
|
context.report({
|
|
2699
3156
|
messageId: "unnecessaryDivWrapper",
|
|
2700
3157
|
node
|
|
@@ -2730,6 +3187,7 @@ var src_default = {
|
|
|
2730
3187
|
"no-inline-prop-types": noInlinePropTypes,
|
|
2731
3188
|
"no-multi-style-objects": noMultiStyleObjects,
|
|
2732
3189
|
"no-nested-jsx-return": noNestedJSXReturn,
|
|
3190
|
+
"no-nondeterministic-render": noNondeterministicRender,
|
|
2733
3191
|
"no-or-none-component": noOrNoneComponent,
|
|
2734
3192
|
"no-transition-cssproperties": noTransitionCSSProperties,
|
|
2735
3193
|
"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.
|
|
43
|
+
"version": "0.3.0"
|
|
39
44
|
}
|