"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.messages = exports.ruleName = undefined;

exports.default = function (expectation) {
  return function (root, result) {
    var validOptions = _stylelint.utils.validateOptions(result, ruleName, {
      actual: expectation
    });
    if (!validOptions) {
      return;
    }

    calculationOperatorSpaceChecker({
      root: root,
      result: result,
      checker: checkSpaces
    });
  };
};

exports.calculationOperatorSpaceChecker = calculationOperatorSpaceChecker;

var _stylelint = require("stylelint");

var _utils = require("../../utils");

var _postcssMediaQueryParser = require("postcss-media-query-parser");

var _postcssMediaQueryParser2 = _interopRequireDefault(_postcssMediaQueryParser);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var ruleName = exports.ruleName = (0, _utils.namespace)("operator-no-unspaced");

var messages = exports.messages = _stylelint.utils.ruleMessages(ruleName, {
  expectedAfter: function expectedAfter(operator) {
    return "Expected single space after \"" + operator + "\"";
  },
  expectedBefore: function expectedBefore(operator) {
    return "Expected single space before \"" + operator + "\"";
  }
});

/**
 * The actual check for are there (un)necessary whitespaces
 */
function checkSpaces(_ref) {
  var string = _ref.string,
      globalIndex = _ref.globalIndex,
      startIndex = _ref.startIndex,
      endIndex = _ref.endIndex,
      node = _ref.node,
      result = _ref.result;

  var symbol = string.substring(startIndex, endIndex + 1);

  var beforeOk = string[startIndex - 1] === " " && !(0, _utils.isWhitespace)(string[startIndex - 2]) || newlineBefore(string, startIndex - 1);
  if (!beforeOk) {
    _stylelint.utils.report({
      ruleName: ruleName,
      result: result,
      node: node,
      message: messages.expectedBefore(symbol),
      index: startIndex + globalIndex
    });
  }

  var afterOk = string[endIndex + 1] === " " && !(0, _utils.isWhitespace)(string[endIndex + 2]) || string[endIndex + 1] === "\n" || string.substr(endIndex + 1, 2) === "\r\n";

  if (!afterOk) {
    _stylelint.utils.report({
      ruleName: ruleName,
      result: result,
      node: node,
      message: messages.expectedAfter(symbol),
      index: endIndex + globalIndex
    });
  }
}

function newlineBefore(str, startIndex) {
  var index = startIndex;
  while (index && (0, _utils.isWhitespace)(str[index])) {
    if (str[index] === "\n") return true;
    index--;
  }
  return false;
}

/**
 * The core rule logic function. This one can be imported by some other rules
 * that work with Sass operators
 *
 * @param {Object} args -- Named arguments object
 * @param {PostCSS Root} args.root
 * @param {PostCSS Result} args.result
 * @param {function} args.checker -- the function that is run against all the
 *    operators found in the input. Takes these arguments:
 *    {Object} cbArgs -- Named arguments object
 *    {string} cbArgs.string -- the input string (suspected operation)
 *    {number} cbArgs.globalIndex -- the string's index in a global input
 *    {number} cbArgs.startIndex -- the start index of a sybmol to inspect
 *    {number} cbArgs.endIndex -- the end index of a sybmol to inspect
 *      (two indexes needed to allow for `==`, `!=`, etc.)
 *    {PostCSS Node} cbArgs.node -- for stylelint.utils.report
 *    {PostCSS Result} cbArgs.result -- for stylelint.utils.report
 */
function calculationOperatorSpaceChecker(_ref2) {
  var root = _ref2.root,
      result = _ref2.result,
      checker = _ref2.checker;


  /**
   * Takes a string, finds all occurencies of Sass interpolaion in it, then
   * finds all operators inside that interpolation
   *
   * @return {array} An array of ojbects { string, operators } - effectively,
   *    a list of operators for each Sass interpolation occurence
   */
  function findInterpolation(string, startIndex) {
    var interpolationRegex = /#{(.*?)}/g;
    var results = [];
    // Searching for interpolation
    var match = interpolationRegex.exec(string);
    startIndex = !isNaN(startIndex) ? Number(startIndex) : 0;
    while (match !== null) {
      results.push({
        source: match[0],
        operators: (0, _utils.findOperators)({
          string: match[0],
          globalIndex: match.index + startIndex
        })
      });
      match = interpolationRegex.exec(string);
    }
    return results;
  }

  root.walk(function (item) {
    var results = [];

    // Check a value (`10px` in `width: 10px;`)
    if (item.value !== undefined) {
      results.push({
        source: item.value,
        operators: (0, _utils.findOperators)({
          string: item.value,
          globalIndex: (0, _utils.declarationValueIndex)(item),
          // For Sass variable values some special rules apply
          isAfterColon: item.prop[0] === "$"
        })
      });
    }

    // Property name
    if (item.prop !== undefined) {
      results = results.concat(findInterpolation(item.prop));
    }
    // Selector
    if (item.selector !== undefined) {
      results = results.concat(findInterpolation(item.selector));
    }

    if (item.type === "atrule") {

      // Media queries
      if (item.name === "media" || item.name === "import") {
        (0, _postcssMediaQueryParser2.default)(item.params).walk(function (node) {
          var type = node.type;
          if (["keyword", "media-type", "media-feature"].indexOf(type) !== -1) {
            results = results.concat(findInterpolation(node.value, (0, _utils.atRuleParamIndex)(item) + node.sourceIndex));
          } else if (["value", "url"].indexOf(type) !== -1) {
            results.push({
              source: node.value,
              operators: (0, _utils.findOperators)({
                string: node.value,
                globalIndex: (0, _utils.atRuleParamIndex)(item) + node.sourceIndex,
                isAfterColon: true
              })
            });
          }
        });
      } else {
        // Function and mixin definitions and other rules
        results.push({
          source: item.params,
          operators: (0, _utils.findOperators)({
            string: item.params,
            globalIndex: (0, _utils.atRuleParamIndex)(item),
            isAfterColon: true
          })
        });
      }
    }

    // All the strings have been parsed, now run whitespace checking
    results.forEach(function (el) {
      // Only if there are operators within a string
      if (el.operators && el.operators.length > 0) {
        el.operators.forEach(function (operator) {
          checker({
            string: el.source,
            globalIndex: operator.globalIndex,
            startIndex: operator.startIndex,
            endIndex: operator.endIndex,
            node: item,
            result: result
          });
        });
      }
    });
  });

  // Checking interpolation inside comments
  // We have to give up on PostCSS here because it skips some inline comments
  (0, _utils.findCommentsInRaws)(root.source.input.css).forEach(function (comment) {
    var startIndex = comment.source.start + comment.raws.startToken.length + comment.raws.left.length;
    if (comment.type !== "css") {
      return;
    }

    findInterpolation(comment.text).forEach(function (el) {
      // Only if there are operators within a string
      if (el.operators && el.operators.length > 0) {
        el.operators.forEach(function (operator) {
          checker({
            string: el.source,
            globalIndex: operator.globalIndex + startIndex,
            startIndex: operator.startIndex,
            endIndex: operator.endIndex,
            node: root,
            result: result
          });
        });
      }
    });
  });
}