/**
 * @fileoverview Rule to flag fall-through cases in switch statements.
 * @author Matt DuVall <http://mattduvall.com/>
 */
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var getLast = require("../util").getLast;

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

var FALLTHROUGH_COMMENT = /falls?\s?through/i;

/**
 * Checks whether or not a given node has a fallthrough comment.
 * @param {ASTNode} node - A SwitchCase node to get comments.
 * @param {RuleContext} context - A rule context which stores comments.
 * @returns {boolean} `true` if the node has a fallthrough comment.
 */
function hasFallthroughComment(node, context) {
    var sourceCode = context.getSourceCode();
    var comment = getLast(sourceCode.getComments(node).leading);

    return Boolean(comment && FALLTHROUGH_COMMENT.test(comment.value));
}

/**
 * Checks whether or not a given code path segment is reachable.
 * @param {CodePathSegment} segment - A CodePathSegment to check.
 * @returns {boolean} `true` if the segment is reachable.
 */
function isReachable(segment) {
    return segment.reachable;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function(context) {
    var currentCodePath = null;

    // We need to use leading comments of the next SwitchCase node because
    // trailing comments is wrong if semicolons are omitted.
    var fallthroughCase = null;

    return {
        "onCodePathStart": function(codePath) {
            currentCodePath = codePath;
        },
        "onCodePathEnd": function() {
            currentCodePath = currentCodePath.upper;
        },

        "SwitchCase": function(node) {
            // Checks whether or not there is a fallthrough comment.
            // And reports the previous fallthrough node if that does not exist.
            if (fallthroughCase && !hasFallthroughComment(node, context)) {
                context.report({
                    message: "Expected a 'break' statement before '{{type}}'.",
                    data: {type: node.test ? "case" : "default"},
                    node: node
                });
            }
            fallthroughCase = null;
        },

        "SwitchCase:exit": function(node) {
            // `reachable` meant fall through because statements preceded by
            // `break`, `return`, or `throw` are unreachable.
            // And allows empty cases and the last case.
            if (currentCodePath.currentSegments.some(isReachable) &&
                node.consequent.length > 0 &&
                getLast(node.parent.cases) !== node
            ) {
                fallthroughCase = node;
            }
        }
    };
};

module.exports.schema = [];
