File size: 5,228 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
/**
 * Splits the given node by the given logical operator.
 * @param operator Logical operator `||` or `&&`.
 * @param node The node to split.
 * @returns Array of conditions that makes the node when joined by the operator.
 */
function splitByLogicalOperator(operator, node) {
    if (node.type === 'LogicalExpression' && node.operator === operator) {
        return [
            ...splitByLogicalOperator(operator, node.left),
            ...splitByLogicalOperator(operator, node.right)
        ];
    }
    return [node];
}
/**
 * Split with ||.
 */
function splitByOr(node) {
    return splitByLogicalOperator('||', node);
}
/**
 * Split with &&.
 */
function splitByAnd(node) {
    return splitByLogicalOperator('&&', node);
}
/**
 * Build OrOperands
 */
function buildOrOperands(node) {
    const orOperands = splitByOr(node);
    return {
        node,
        operands: orOperands.map((orOperand) => {
            const andOperands = splitByAnd(orOperand);
            return {
                node: orOperand,
                operands: andOperands
            };
        })
    };
}
exports.default = (0, utils_1.createRule)('no-dupe-else-if-blocks', {
    meta: {
        docs: {
            description: 'disallow duplicate conditions in `{#if}` / `{:else if}` chains',
            category: 'Possible Errors',
            recommended: true
        },
        schema: [],
        messages: {
            unexpected: 'This branch can never execute. Its condition is a duplicate or covered by previous conditions in the `{#if}` / `{:else if}` chain.'
        },
        type: 'problem' // "problem",
    },
    create(context) {
        const sourceCode = (0, compat_1.getSourceCode)(context);
        /**
         * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes
         * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators.
         * @param a First node.
         * @param b Second node.
         * @returns `true` if the nodes are considered to be equal.
         */
        function equal(a, b) {
            if (a.type !== b.type) {
                return false;
            }
            if (a.type === 'LogicalExpression' &&
                b.type === 'LogicalExpression' &&
                (a.operator === '||' || a.operator === '&&') &&
                a.operator === b.operator) {
                return ((equal(a.left, b.left) && equal(a.right, b.right)) ||
                    (equal(a.left, b.right) && equal(a.right, b.left)));
            }
            return (0, ast_utils_1.equalTokens)(a, b, sourceCode);
        }
        /**
         * Determines whether the first given AndOperands is a subset of the second given AndOperands.
         *
         * e.g. A: (a && b), B: (a && b && c): B is a subset of A.
         *
         * @param operandsA The AndOperands to compare from.
         * @param operandsB The AndOperands to compare against.
         * @returns `true` if the `andOperandsA` is a subset of the `andOperandsB`.
         */
        function isSubset(operandsA, operandsB) {
            return operandsA.operands.every((operandA) => operandsB.operands.some((operandB) => equal(operandA, operandB)));
        }
        /** Iterate SvelteIfBlock nodes */
        function* iterateIfElseIf(node) {
            let target = node;
            while (target.parent.type === 'SvelteElseBlock' &&
                target.parent.children.includes(target) &&
                target.parent.parent.type === 'SvelteIfBlock') {
                yield target.parent.parent;
                target = target.parent.parent;
            }
        }
        return {
            SvelteIfBlock(node) {
                const test = node.expression;
                const conditionsToCheck = test.type === 'LogicalExpression' && test.operator === '&&'
                    ? [...splitByAnd(test), test]
                    : [test];
                const listToCheck = conditionsToCheck.map(buildOrOperands);
                for (const currentIdBlock of iterateIfElseIf(node)) {
                    if (currentIdBlock.expression) {
                        const currentOrOperands = buildOrOperands(currentIdBlock.expression);
                        for (const condition of listToCheck) {
                            const operands = (condition.operands = condition.operands.filter((orOperand) => {
                                return !currentOrOperands.operands.some((currentOrOperand) => isSubset(currentOrOperand, orOperand));
                            }));
                            if (!operands.length) {
                                context.report({
                                    node: condition.node,
                                    messageId: 'unexpected'
                                });
                                return;
                            }
                        }
                    }
                }
            }
        };
    }
});