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.
- package/dist/index.js +506 -13
- 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"
|
|
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
|
-
|
|
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 (
|
|
532
|
-
return
|
|
909
|
+
if (memberName && PURE_MEMBER_METHODS.has(memberName)) {
|
|
910
|
+
return isPureRuntimeExpression(node.callee.object, stableLocals);
|
|
533
911
|
}
|
|
534
|
-
return
|
|
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 (
|
|
1076
|
+
if (hasDuplicatePropertyNames(node.properties)) {
|
|
699
1077
|
autoFixable = false;
|
|
700
1078
|
}
|
|
701
|
-
if (autoFixable
|
|
702
|
-
|
|
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
|
-
|
|
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-
|
|
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 ===
|
|
3167
|
+
return nameNode.type === AST_NODE_TYPES4.JSXIdentifier && nameNode.name === "div";
|
|
2676
3168
|
};
|
|
2677
3169
|
const isMeaningfulChild = (child) => {
|
|
2678
|
-
if (child.type ===
|
|
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 ===
|
|
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.
|
|
43
|
+
"version": "0.4.0"
|
|
39
44
|
}
|