Repository: stylelint-scss/stylelint-scss
Pull request #940 (2023-12-18 19:08:43)

Fix `at-mixin-named-arguments` end positions
Part of #652

Note: This PR uses the same utility (`parseFunctionArguments()`) as #939.



Patch:
diff --git a/src/rules/at-function-named-arguments/index.js b/src/rules/at-function-named-arguments/index.js
index c77920a..3cee9d4 100644
--- a/src/rules/at-function-named-arguments/index.js
+++ b/src/rules/at-function-named-arguments/index.js
@@ -2,6 +2,7 @@
 
 const valueParser = require("postcss-value-parser");
 const { utils } = require("stylelint");
+const declarationValueIndex = require("../../utils/declarationValueIndex");
 const isNativeCssFunction = require("../../utils/isNativeCssFunction");
 const { isString } = require("../../utils/validateTypes");
 const namespace = require("../../utils/namespace");
@@ -53,6 +54,8 @@ function rule(expectation, options) {
     );
 
     root.walkDecls(decl => {
+      const declValueIndex = declarationValueIndex(decl);
+
       valueParser(decl.value).walk(node => {
         if (
           node.type !== "function" ||
@@ -88,10 +91,12 @@ function rule(expectation, options) {
           return;
         }
 
-        args.forEach(arg => {
+        const baseIndex = declValueIndex + node.sourceIndex;
+
+        args.forEach(({ key, index, endIndex }) => {
           switch (expectation) {
             case "never": {
-              if (!arg.key) {
+              if (!key) {
                 return;
               }
 
@@ -99,13 +104,15 @@ function rule(expectation, options) {
                 message: messages.rejected,
                 node: decl,
                 result,
-                ruleName
+                ruleName,
+                index: baseIndex + index,
+                endIndex: baseIndex + endIndex
               });
               break;
             }
 
             case "always": {
-              if (arg.key && isScssVarRegExp.test(arg.key)) {
+              if (key && isScssVarRegExp.test(key)) {
                 return;
               }
 
@@ -113,7 +120,9 @@ function rule(expectation, options) {
                 message: messages.expected,
                 node: decl,
                 result,
-                ruleName
+                ruleName,
+                index: baseIndex + index,
+                endIndex: baseIndex + endIndex
               });
               break;
             }
diff --git a/src/rules/at-mixin-named-arguments/index.js b/src/rules/at-mixin-named-arguments/index.js
index c3063f4..f3202d2 100644
--- a/src/rules/at-mixin-named-arguments/index.js
+++ b/src/rules/at-mixin-named-arguments/index.js
@@ -1,7 +1,11 @@
 "use strict";
 
 const { utils } = require("stylelint");
+const atRuleParamIndex = require("../../utils/atRuleParamIndex");
 const optionsHaveIgnored = require("../../utils/optionsHaveIgnored");
+const {
+  parseFunctionArguments
+} = require("../../utils/parseFunctionArguments");
 const namespace = require("../../utils/namespace");
 const ruleUrl = require("../../utils/ruleUrl");
 
@@ -60,33 +64,17 @@ function rule(expectation, options) {
         return;
       }
 
-      const args = argsString[1]
-        // Create array of arguments.
-        .split(",")
-        // Create a key-value array for every argument.
-        .map(argsString =>
-          argsString.split(":").map(argsKeyValuePair => argsKeyValuePair.trim())
-        )
-        .reduce((resultArray, keyValuePair) => {
-          const pair = { value: keyValuePair[1] || keyValuePair[0] };
-
-          if (keyValuePair[1]) {
-            pair.key = keyValuePair[0];
-          }
-
-          return [...resultArray, pair];
-        }, []);
+      const mixinArgs = parseFunctionArguments(atRule.params);
 
-      const isSingleArgument = args.length === 1;
+      if (mixinArgs.length === 0) return;
+      if (mixinArgs.length === 1 && shouldIgnoreSingleArgument) return;
 
-      if (isSingleArgument && shouldIgnoreSingleArgument) {
-        return;
-      }
+      const baseIndex = atRuleParamIndex(atRule);
 
-      args.forEach(arg => {
+      mixinArgs.forEach(({ key, index, endIndex }) => {
         switch (expectation) {
           case "never": {
-            if (!arg.key) {
+            if (!key) {
               return;
             }
 
@@ -94,13 +82,15 @@ function rule(expectation, options) {
               message: messages.rejected,
               node: atRule,
               result,
-              ruleName
+              ruleName,
+              index: baseIndex + index,
+              endIndex: baseIndex + endIndex
             });
             break;
           }
 
           case "always": {
-            if (arg.key && isScssVarRegExp.test(arg.key)) {
+            if (key && isScssVarRegExp.test(key)) {
               return;
             }
 
@@ -108,7 +98,9 @@ function rule(expectation, options) {
               message: messages.expected,
               node: atRule,
               result,
-              ruleName
+              ruleName,
+              index: baseIndex + index,
+              endIndex: baseIndex + endIndex
             });
             break;
           }
diff --git a/src/rules/declaration-nested-properties-no-divided-groups/index.js b/src/rules/declaration-nested-properties-no-divided-groups/index.js
index cce9e98..a841805 100644
--- a/src/rules/declaration-nested-properties-no-divided-groups/index.js
+++ b/src/rules/declaration-nested-properties-no-divided-groups/index.js
@@ -54,18 +54,19 @@ function rule(expectation) {
         }
       });
 
-      Object.keys(nestedGroups).forEach(namespace => {
+      Object.entries(nestedGroups).forEach(([namespace, groups]) => {
         // Only warn if there are more than one nested groups with equal namespaces
-        if (nestedGroups[namespace].length === 1) {
+        if (groups.length === 1) {
           return;
         }
 
-        nestedGroups[namespace].forEach(group => {
+        groups.forEach(group => {
           utils.report({
             message: messages.expected(namespace),
             node: group,
             result,
-            ruleName
+            ruleName,
+            word: namespace
           });
         });
       });
diff --git a/src/rules/double-slash-comment-inline/index.js b/src/rules/double-slash-comment-inline/index.js
index 75f4483..1c02f0e 100644
--- a/src/rules/double-slash-comment-inline/index.js
+++ b/src/rules/double-slash-comment-inline/index.js
@@ -82,6 +82,7 @@ function rule(expectation, options) {
           message,
           node: root,
           index: comment.source.start,
+          endIndex: comment.source.end + 1,
           result,
           ruleName
         });
diff --git a/src/rules/function-quote-no-quoted-strings-inside/index.js b/src/rules/function-quote-no-quoted-strings-inside/index.js
index b0c91a8..b202942 100644
--- a/src/rules/function-quote-no-quoted-strings-inside/index.js
+++ b/src/rules/function-quote-no-quoted-strings-inside/index.js
@@ -63,10 +63,12 @@ function rule(primary, _, context) {
             const contents = decl.value.match(/quote\((.*)\)/);
             decl.value = decl.value.replace(/quote\(.*\)/, contents[1]);
           } else {
+            const index = declarationValueIndex(decl) + node.sourceIndex;
             utils.report({
               message: messages.rejected,
               node: decl,
-              index: declarationValueIndex(decl) + node.sourceIndex,
+              index,
+              endIndex: index + node.value.length,
               result,
               ruleName
             });
diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/index.js b/src/rules/function-unquote-no-unquoted-strings-inside/index.js
index 1caded7..9a46635 100644
--- a/src/rules/function-unquote-no-unquoted-strings-inside/index.js
+++ b/src/rules/function-unquote-no-unquoted-strings-inside/index.js
@@ -67,10 +67,12 @@ function rule(primary, _, context) {
 
             decl.value = contents[1];
           } else {
+            const index = declarationValueIndex(decl) + node.sourceIndex;
             utils.report({
               message: messages.rejected,
               node: decl,
-              index: declarationValueIndex(decl) + node.sourceIndex,
+              index,
+              endIndex: index + node.value.length,
               result,
               ruleName
             });
diff --git a/src/rules/operator-no-newline-after/index.js b/src/rules/operator-no-newline-after/index.js
index a8d93ed..28b2e90 100644
--- a/src/rules/operator-no-newline-after/index.js
+++ b/src/rules/operator-no-newline-after/index.js
@@ -43,12 +43,14 @@ function checkNewlineBefore({
   }
 
   if (newLineBefore) {
+    const index = globalIndex + startIndex;
     utils.report({
       ruleName,
       result,
       node,
       message: messages.rejected(symbol),
-      index: endIndex + globalIndex
+      index,
+      endIndex: index + symbol.length
     });
   }
 }
diff --git a/src/rules/operator-no-newline-before/index.js b/src/rules/operator-no-newline-before/index.js
index 0369ae3..26a2eb4 100644
--- a/src/rules/operator-no-newline-before/index.js
+++ b/src/rules/operator-no-newline-before/index.js
@@ -43,12 +43,14 @@ function checkNewlineBefore({
   }
 
   if (newLineBefore) {
+    const index = globalIndex + startIndex;
     utils.report({
       ruleName,
       result,
       node,
       message: messages.rejected(symbol),
-      index: endIndex + globalIndex
+      index,
+      endIndex: index + symbol.length
     });
   }
 }
diff --git a/src/rules/operator-no-unspaced/index.js b/src/rules/operator-no-unspaced/index.js
index cdee380..3e03d73 100644
--- a/src/rules/operator-no-unspaced/index.js
+++ b/src/rules/operator-no-unspaced/index.js
@@ -40,12 +40,14 @@ function checkSpaces({
     newlineBefore(string, startIndex - 1);
 
   if (!beforeOk) {
+    const index = globalIndex + startIndex;
     utils.report({
       ruleName,
       result,
       node,
       message: messages.expectedBefore(symbol),
-      index: startIndex + globalIndex
+      index,
+      endIndex: index + symbol.length
     });
   }
 
@@ -55,12 +57,14 @@ function checkSpaces({
     string.substr(endIndex + 1, 2) === "\r\n";
 
   if (!afterOk) {
+    const index = globalIndex + startIndex;
     utils.report({
       ruleName,
       result,
       node,
       message: messages.expectedAfter(symbol),
-      index: endIndex + globalIndex
+      index,
+      endIndex: index + symbol.length
     });
   }
 }
diff --git a/src/utils/parseFunctionArguments.js b/src/utils/parseFunctionArguments.js
index 2b944cb..b09f183 100644
--- a/src/utils/parseFunctionArguments.js
+++ b/src/utils/parseFunctionArguments.js
@@ -32,7 +32,7 @@ function groupByKeyValue(nodes) {
 }
 
 function mapToKeyValue(nodes) {
-  const keyVal = nodes.reduce((acc, curr, i) => {
+  const keyVal = nodes.reduce((acc, currentNode, i) => {
     if (acc.length === 1) {
       return acc;
     }
@@ -43,15 +43,19 @@ function mapToKeyValue(nodes) {
 
     if (isNextNodeColon) {
       acc.push({
-        key: valueParser.stringify(nodes[i]),
-        value: valueParser.stringify(nodes.slice(2))
+        key: valueParser.stringify(currentNode),
+        value: valueParser.stringify(nodes.slice(2)),
+        index: currentNode.sourceIndex,
+        endIndex: nodes.at(-1).sourceEndIndex
       });
 
       return acc;
     }
 
     acc.push({
-      value: valueParser.stringify(nodes)
+      value: valueParser.stringify(nodes),
+      index: currentNode.sourceIndex,
+      endIndex: nodes.at(-1).sourceEndIndex
     });
 
     return acc;
@@ -61,15 +65,14 @@ function mapToKeyValue(nodes) {
 }
 
 function parseFunctionArguments(value) {
-  const parsed = valueParser(value);
+  const { nodes } = valueParser(value);
+  const [firstNode] = nodes;
 
-  if (!parsed.nodes[0] || parsed.nodes[0].type !== "function") {
+  if (!firstNode || firstNode.type !== "function") {
     return [];
   }
 
-  return parsed.nodes.map(node =>
-    groupByKeyValue(node.nodes).map(mapToKeyValue)
-  )[0];
+  return nodes.map(node => groupByKeyValue(node.nodes).map(mapToKeyValue))[0];
 }
 
 module.exports = {


Test patch:
diff --git a/src/rules/at-function-named-arguments/__tests__/index.js b/src/rules/at-function-named-arguments/__tests__/index.js
index 056db95..345119f 100644
--- a/src/rules/at-function-named-arguments/__tests__/index.js
+++ b/src/rules/at-function-named-arguments/__tests__/index.js
@@ -188,7 +188,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 27,
       message: messages.expected,
       description: "Always. Example: single argument that is not named."
     },
@@ -201,12 +203,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 23,
+          endLine: 3,
+          endColumn: 27,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 29,
+          endLine: 3,
+          endColumn: 33,
           message: messages.expected
         }
       ],
@@ -223,13 +229,17 @@ testRule({
     `,
       warnings: [
         {
-          line: 3,
-          column: 9,
+          line: 4,
+          column: 11,
+          endLine: 4,
+          endColumn: 15,
           message: messages.expected
         },
         {
-          line: 3,
-          column: 9,
+          line: 5,
+          column: 11,
+          endLine: 5,
+          endColumn: 15,
           message: messages.expected
         }
       ],
@@ -243,7 +253,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 32,
       message: messages.expected,
       description:
         "Always. Example: single argument is a variable but is not named."
@@ -255,7 +267,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 32,
       message: messages.expected,
       description:
         "Always. Example: single argument is a calculated value but is not named."
@@ -269,12 +283,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 37,
+          endLine: 3,
+          endColumn: 41,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 43,
+          endLine: 3,
+          endColumn: 50,
           message: messages.expected
         }
       ],
@@ -290,12 +308,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 23,
+          endLine: 3,
+          endColumn: 27,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 43,
+          endLine: 3,
+          endColumn: 50,
           message: messages.expected
         }
       ],
@@ -308,7 +330,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 32,
+      endLine: 3,
+      endColumn: 74,
       message: messages.expected,
       description:
         "Always. Example: native CSS function inside a function call."
@@ -325,18 +349,24 @@ testRule({
       `,
       warnings: [
         {
-          line: 4,
-          column: 7,
+          line: 5,
+          column: 18,
+          endLine: 5,
+          endColumn: 24,
           message: messages.expected
         },
         {
-          line: 4,
-          column: 7,
+          line: 6,
+          column: 20,
+          endLine: 6,
+          endColumn: 26,
           message: messages.expected
         },
         {
-          line: 4,
-          column: 7,
+          line: 7,
+          column: 19,
+          endLine: 7,
+          endColumn: 25,
           message: messages.expected
         }
       ],
@@ -481,7 +511,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 35,
       message: messages.rejected,
       description: "Never. Example: single argument is named."
     },
@@ -492,7 +524,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 43,
       message: messages.rejected,
       description: "Never. Example: single argument is a variable."
     },
@@ -503,7 +537,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 46,
       message: messages.rejected,
       description: "Never. Example: single argument is an interpolated value."
     },
@@ -514,7 +550,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 25,
+      endLine: 3,
+      endColumn: 45,
       message: messages.rejected,
       description: "Never. Example: single argument is a calculated value."
     },
@@ -525,7 +563,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 38,
       message: messages.rejected,
       description: "Never. Example: single argument is a quoted string."
     },
@@ -536,7 +576,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 25,
+      endLine: 3,
+      endColumn: 45,
       message: messages.rejected,
       description: "Never. Example: single argument is an unquoted string."
     },
@@ -549,17 +591,23 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 23,
+          endLine: 3,
+          endColumn: 35,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 37,
+          endLine: 3,
+          endColumn: 56,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 58,
+          endLine: 3,
+          endColumn: 73,
           message: messages.rejected
         }
       ],
@@ -577,18 +625,24 @@ testRule({
       `,
       warnings: [
         {
-          line: 3,
-          column: 9,
+          line: 4,
+          column: 11,
+          endLine: 4,
+          endColumn: 23,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 5,
+          column: 11,
+          endLine: 5,
+          endColumn: 30,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 6,
+          column: 11,
+          endLine: 6,
+          endColumn: 26,
           message: messages.rejected
         }
       ],
@@ -602,7 +656,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 35,
       message: messages.rejected,
       description:
         "Never. Example: first argument is named but remaining are not."
@@ -614,7 +670,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 29,
+      endLine: 3,
+      endColumn: 41,
       message: messages.rejected,
       description: "Never. Example: mixed named arguments."
     },
@@ -625,7 +683,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 32,
+      endLine: 3,
+      endColumn: 82,
       message: messages.rejected,
       description: "Never. Example: native CSS function inside a function call."
     },
@@ -641,18 +701,24 @@ testRule({
       `,
       warnings: [
         {
-          line: 4,
-          column: 7,
+          line: 5,
+          column: 18,
+          endLine: 5,
+          endColumn: 30,
           message: messages.rejected
         },
         {
-          line: 4,
-          column: 7,
+          line: 6,
+          column: 20,
+          endLine: 6,
+          endColumn: 34,
           message: messages.rejected
         },
         {
-          line: 4,
-          column: 7,
+          line: 7,
+          column: 19,
+          endLine: 7,
+          endColumn: 33,
           message: messages.rejected
         }
       ],
@@ -809,8 +875,10 @@ testRule({
         );
       }
     `,
-      line: 3,
-      column: 9,
+      line: 5,
+      column: 11,
+      endLine: 5,
+      endColumn: 15,
       message: messages.expected,
       description:
         "Always and ignore single argument. Example: first argument is named but remaining are not in multiline function call."
@@ -824,12 +892,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 23,
+          endLine: 3,
+          endColumn: 27,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 43,
+          endLine: 3,
+          endColumn: 50,
           message: messages.expected
         }
       ],
@@ -845,12 +917,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 37,
+          endLine: 3,
+          endColumn: 41,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 43,
+          endLine: 3,
+          endColumn: 50,
           message: messages.expected
         }
       ],
@@ -866,12 +942,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 23,
+          endLine: 3,
+          endColumn: 27,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 43,
+          endLine: 3,
+          endColumn: 50,
           message: messages.expected
         }
       ],
@@ -1055,17 +1135,23 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 23,
+          endLine: 3,
+          endColumn: 35,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 37,
+          endLine: 3,
+          endColumn: 56,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 58,
+          endLine: 3,
+          endColumn: 73,
           message: messages.rejected
         }
       ],
@@ -1084,18 +1170,24 @@ testRule({
       `,
       warnings: [
         {
-          line: 3,
-          column: 9,
+          line: 4,
+          column: 11,
+          endLine: 4,
+          endColumn: 23,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 5,
+          column: 11,
+          endLine: 5,
+          endColumn: 30,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 6,
+          column: 11,
+          endLine: 6,
+          endColumn: 26,
           message: messages.rejected
         }
       ],
@@ -1109,7 +1201,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 35,
       message: messages.rejected,
       description:
         "Never and ignore single argument. Example: first argument is named but remaining are not."
@@ -1121,7 +1215,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 29,
+      endLine: 3,
+      endColumn: 41,
       message: messages.rejected,
       description:
         "Never and ignore single argument. Example: mixed named arguments."
@@ -1186,7 +1282,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 27,
       message: messages.expected,
       description:
         "Always and ignore function. Example: single argument that is not named."
@@ -1200,12 +1298,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 25,
+          endLine: 3,
+          endColumn: 29,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 31,
+          endLine: 3,
+          endColumn: 34,
           message: messages.expected
         }
       ],
@@ -1221,12 +1323,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 27,
+          endLine: 3,
+          endColumn: 31,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 33,
+          endLine: 3,
+          endColumn: 36,
           message: messages.expected
         }
       ],
@@ -1292,7 +1398,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 23,
+      endLine: 3,
+      endColumn: 35,
       message: messages.rejected,
       description: "Never. Example: single argument is named."
     }
diff --git a/src/rules/at-mixin-named-arguments/__tests__/index.js b/src/rules/at-mixin-named-arguments/__tests__/index.js
index 5980c5d..ea32739 100644
--- a/src/rules/at-mixin-named-arguments/__tests__/index.js
+++ b/src/rules/at-mixin-named-arguments/__tests__/index.js
@@ -113,7 +113,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 28,
       message: messages.expected,
       description: "Always. Example: single argument that is not named."
     },
@@ -126,12 +128,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 24,
+          endLine: 3,
+          endColumn: 28,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 30,
+          endLine: 3,
+          endColumn: 34,
           message: messages.expected
         }
       ],
@@ -148,13 +154,17 @@ testRule({
     `,
       warnings: [
         {
-          line: 3,
-          column: 9,
+          line: 4,
+          column: 11,
+          endLine: 4,
+          endColumn: 15,
           message: messages.expected
         },
         {
-          line: 3,
-          column: 9,
+          line: 5,
+          column: 11,
+          endLine: 5,
+          endColumn: 15,
           message: messages.expected
         }
       ],
@@ -168,7 +178,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 33,
       message: messages.expected,
       description:
         "Always. Example: single argument is a variable but is not named."
@@ -180,7 +192,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 33,
       message: messages.expected,
       description:
         "Always. Example: single argument is a calculated value but is not named."
@@ -194,12 +208,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 38,
+          endLine: 3,
+          endColumn: 42,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 44,
+          endLine: 3,
+          endColumn: 51,
           message: messages.expected
         }
       ],
@@ -215,12 +233,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 24,
+          endLine: 3,
+          endColumn: 28,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 44,
+          endLine: 3,
+          endColumn: 51,
           message: messages.expected
         }
       ],
@@ -231,7 +253,9 @@ testRule({
       @include reset(40px);
     `,
       line: 2,
-      column: 7,
+      column: 22,
+      endLine: 2,
+      endColumn: 26,
       message: messages.expected,
       description:
         "Always. Example: single argument is not named in standalone mixin."
@@ -243,12 +267,16 @@ testRule({
       warnings: [
         {
           line: 2,
-          column: 7,
+          column: 22,
+          endLine: 2,
+          endColumn: 26,
           message: messages.expected
         },
         {
           line: 2,
-          column: 7,
+          column: 28,
+          endLine: 2,
+          endColumn: 32,
           message: messages.expected
         }
       ],
@@ -378,7 +406,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 36,
       message: messages.rejected,
       description: "Never. Example: single argument is named."
     },
@@ -389,7 +419,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 44,
       message: messages.rejected,
       description: "Never. Example: single argument is a variable."
     },
@@ -400,7 +432,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 47,
       message: messages.rejected,
       description: "Never. Example: single argument is an interpolated value."
     },
@@ -411,7 +445,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 28,
+      endLine: 3,
+      endColumn: 48,
       message: messages.rejected,
       description: "Never. Example: single argument is a calculated value."
     },
@@ -422,7 +458,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 39,
       message: messages.rejected,
       description: "Never. Example: single argument is a quoted string."
     },
@@ -433,7 +471,9 @@ testRule({
       }
       `,
       line: 3,
-      column: 9,
+      column: 28,
+      endLine: 3,
+      endColumn: 48,
       message: messages.rejected,
       description: "Never. Example: single argument is an unquoted string."
     },
@@ -442,7 +482,9 @@ testRule({
       @include reset($value: 40px);
       `,
       line: 2,
-      column: 7,
+      column: 22,
+      endLine: 2,
+      endColumn: 34,
       message: messages.rejected,
       description: "Never. Example: standalone mixin."
     },
@@ -455,17 +497,23 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 24,
+          endLine: 3,
+          endColumn: 36,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 38,
+          endLine: 3,
+          endColumn: 57,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 59,
+          endLine: 3,
+          endColumn: 74,
           message: messages.rejected
         }
       ],
@@ -483,18 +531,24 @@ testRule({
       `,
       warnings: [
         {
-          line: 3,
-          column: 9,
+          line: 4,
+          column: 11,
+          endLine: 4,
+          endColumn: 23,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 5,
+          column: 11,
+          endLine: 5,
+          endColumn: 30,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 6,
+          column: 11,
+          endLine: 6,
+          endColumn: 26,
           message: messages.rejected
         }
       ],
@@ -508,7 +562,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 36,
       message: messages.rejected,
       description:
         "Never. Example: first argument is named but remaining are not."
@@ -520,7 +576,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 30,
+      endLine: 3,
+      endColumn: 42,
       message: messages.rejected,
       description: "Never. Example: mixed named arguments."
     }
@@ -668,12 +726,16 @@ testRule({
       warnings: [
         {
           line: 2,
-          column: 7,
+          column: 22,
+          endLine: 2,
+          endColumn: 26,
           message: messages.expected
         },
         {
           line: 2,
-          column: 7,
+          column: 28,
+          endLine: 2,
+          endColumn: 32,
           message: messages.expected
         }
       ],
@@ -687,7 +749,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 38,
+      endLine: 3,
+      endColumn: 42,
       message: messages.expected,
       description:
         "Always and ignore single argument. Example: first argument is named but remaining are not."
@@ -701,8 +765,10 @@ testRule({
         );
       }
     `,
-      line: 3,
-      column: 9,
+      line: 5,
+      column: 11,
+      endLine: 5,
+      endColumn: 15,
       message: messages.expected,
       description:
         "Always and ignore single argument. Example: first argument is named but remaining are not in multiline mixin call."
@@ -716,12 +782,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 24,
+          endLine: 3,
+          endColumn: 28,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 44,
+          endLine: 3,
+          endColumn: 51,
           message: messages.expected
         }
       ],
@@ -737,12 +807,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 38,
+          endLine: 3,
+          endColumn: 42,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 44,
+          endLine: 3,
+          endColumn: 51,
           message: messages.expected
         }
       ],
@@ -758,12 +832,16 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 24,
+          endLine: 3,
+          endColumn: 28,
           message: messages.expected
         },
         {
           line: 3,
-          column: 9,
+          column: 44,
+          endLine: 3,
+          endColumn: 51,
           message: messages.expected
         }
       ],
@@ -949,17 +1027,23 @@ testRule({
       warnings: [
         {
           line: 3,
-          column: 9,
+          column: 24,
+          endLine: 3,
+          endColumn: 36,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 38,
+          endLine: 3,
+          endColumn: 57,
           message: messages.rejected
         },
         {
           line: 3,
-          column: 9,
+          column: 59,
+          endLine: 3,
+          endColumn: 74,
           message: messages.rejected
         }
       ],
@@ -978,18 +1062,24 @@ testRule({
       `,
       warnings: [
         {
-          line: 3,
-          column: 9,
+          line: 4,
+          column: 11,
+          endLine: 4,
+          endColumn: 23,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 5,
+          column: 11,
+          endLine: 5,
+          endColumn: 30,
           message: messages.rejected
         },
         {
-          line: 3,
-          column: 9,
+          line: 6,
+          column: 11,
+          endLine: 6,
+          endColumn: 26,
           message: messages.rejected
         }
       ],
@@ -1003,7 +1093,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 24,
+      endLine: 3,
+      endColumn: 36,
       message: messages.rejected,
       description:
         "Never and ignore single argument. Example: first argument is named but remaining are not."
@@ -1015,7 +1107,9 @@ testRule({
       }
     `,
       line: 3,
-      column: 9,
+      column: 30,
+      endLine: 3,
+      endColumn: 42,
       message: messages.rejected,
       description:
         "Never and ignore single argument. Example: mixed named arguments."
diff --git a/src/rules/declaration-nested-properties-no-divided-groups/__tests__/index.js b/src/rules/declaration-nested-properties-no-divided-groups/__tests__/index.js
index 2c61ddc..ab6683f 100644
--- a/src/rules/declaration-nested-properties-no-divided-groups/__tests__/index.js
+++ b/src/rules/declaration-nested-properties-no-divided-groups/__tests__/index.js
@@ -124,11 +124,15 @@ testRule({
         {
           line: 3,
           column: 9,
+          endLine: 3,
+          endColumn: 19,
           message: messages.expected("background")
         },
         {
           line: 6,
           column: 9,
+          endLine: 6,
+          endColumn: 19,
           message: messages.expected("background")
         }
       ]
diff --git a/src/rules/double-slash-comment-inline/__tests__/index.js b/src/rules/double-slash-comment-inline/__tests__/index.js
index 34ee967..f910bf6 100644
--- a/src/rules/double-slash-comment-inline/__tests__/index.js
+++ b/src/rules/double-slash-comment-inline/__tests__/index.js
@@ -50,7 +50,9 @@ testRule({
       description: "Non-inline comment (after a ruleset)",
       message: messages.expected,
       line: 3,
-      column: 7
+      column: 7,
+      endLine: 3,
+      endColumn: 46
     },
     {
       code: `
@@ -61,7 +63,9 @@ testRule({
       description: "Non-inline comment between selectors.",
       message: messages.expected,
       line: 3,
-      column: 7
+      column: 7,
+      endLine: 3,
+      endColumn: 46
     },
     {
       code: `
@@ -73,7 +77,9 @@ testRule({
       description: "Non-inline comment (before a decl)",
       message: messages.expected,
       line: 3,
-      column: 9
+      column: 9,
+      endLine: 3,
+      endColumn: 46
     }
   ]
 });
@@ -189,7 +195,9 @@ testRule({
       message: messages.rejected,
       description: "Inline comment, inside a ruleset.",
       line: 3,
-      column: 34
+      column: 34,
+      endLine: 3,
+      endColumn: 70
     },
     {
       code: `
@@ -201,7 +209,9 @@ testRule({
       message: messages.rejected,
       description: "Inline comment, between selectors.",
       line: 2,
-      column: 10
+      column: 10,
+      endLine: 2,
+      endColumn: 47
     }
   ]
 });
diff --git a/src/rules/function-quote-no-quoted-strings-inside/__tests__/index.js b/src/rules/function-quote-no-quoted-strings-inside/__tests__/index.js
index 43abd6e..4d7d268 100644
--- a/src/rules/function-quote-no-quoted-strings-inside/__tests__/index.js
+++ b/src/rules/function-quote-no-quoted-strings-inside/__tests__/index.js
@@ -40,6 +40,8 @@ testRule({
       message: messages.rejected,
       line: 3,
       column: 24,
+      endLine: 3,
+      endColumn: 29,
       fixed: `
         p {
           font-family: "Helvetica";
@@ -57,6 +59,8 @@ testRule({
         "does not accept variables representing strings that are quoted.",
       line: 4,
       column: 24,
+      endLine: 4,
+      endColumn: 29,
       message: messages.rejected,
       fixed: `
         $font: "Helvetica";
@@ -79,14 +83,14 @@ testRule({
       warnings: [
         {
           column: 30,
-          endColumn: 31,
+          endColumn: 35,
           endLine: 4,
           line: 4,
           message: messages.rejected
         },
         {
           column: 20,
-          endColumn: 21,
+          endColumn: 25,
           endLine: 7,
           line: 7,
           message: messages.rejected
diff --git a/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js b/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js
index 0560302..8bec538 100644
--- a/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js
+++ b/src/rules/function-unquote-no-unquoted-strings-inside/__tests__/index.js
@@ -40,6 +40,8 @@ testRule({
       message: messages.rejected,
       line: 3,
       column: 24,
+      endLine: 3,
+      endColumn: 31,
       fixed: `
         p {
           font-family: Helvetica;
@@ -58,6 +60,8 @@ testRule({
       message: messages.rejected,
       line: 4,
       column: 24,
+      endLine: 4,
+      endColumn: 31,
       fixed: `
         $font: Helvetica;
         p {
diff --git a/src/rules/operator-no-newline-after/__tests__/index.js b/src/rules/operator-no-newline-after/__tests__/index.js
index e71af28..b685785 100644
--- a/src/rules/operator-no-newline-after/__tests__/index.js
+++ b/src/rules/operator-no-newline-after/__tests__/index.js
@@ -73,7 +73,9 @@ testRule({
       description: "Newline-operator-indentation: 10px +\\n1px.",
       message: messages.rejected("+"),
       line: 3,
-      column: 21
+      column: 21,
+      endLine: 3,
+      endColumn: 22
     },
     {
       code: `
diff --git a/src/rules/operator-no-newline-before/__tests__/index.js b/src/rules/operator-no-newline-before/__tests__/index.js
index 5ff6b70..2a3bd54 100644
--- a/src/rules/operator-no-newline-before/__tests__/index.js
+++ b/src/rules/operator-no-newline-before/__tests__/index.js
@@ -81,7 +81,9 @@ testRule({
       description: "Newline-indentation-operator: 10px\\n        + 1.",
       message: messages.rejected("+"),
       line: 4,
-      column: 9
+      column: 9,
+      endLine: 4,
+      endColumn: 10
     },
     {
       code: `
diff --git a/src/rules/operator-no-unspaced/__tests__/index.js b/src/rules/operator-no-unspaced/__tests__/index.js
index b4bbe47..018fee9 100644
--- a/src/rules/operator-no-unspaced/__tests__/index.js
+++ b/src/rules/operator-no-unspaced/__tests__/index.js
@@ -120,7 +120,9 @@ testRule({
       description: "Op, -. width: 1- +1.",
       message: messages.expectedBefore("-"),
       line: 2,
-      column: 15
+      column: 15,
+      endLine: 2,
+      endColumn: 16
     },
     {
       code: "a { width: #{1 +1}; }",
@@ -1830,7 +1832,7 @@ testRule({
       code: "a { width: $var !=1; }",
       description: "Op: $var !=1.",
       message: messages.expectedAfter("!="),
-      column: 18
+      column: 17
     },
     {
       code: "a { width: $var< 1; }",
@@ -1848,7 +1850,7 @@ testRule({
       code: "a { width: $var ==1; }",
       description: "Op: $var ==1.",
       message: messages.expectedAfter("=="),
-      column: 18
+      column: 17
     },
     {
       code: "a { width: string== string; }",
@@ -1860,7 +1862,7 @@ testRule({
       code: "a { width: string ==string; }",
       description: "Op: string ==string.",
       message: messages.expectedAfter("=="),
-      column: 20
+      column: 19
     }
   ]
 });
@@ -2042,11 +2044,15 @@ testRule({
         {
           line: 1,
           column: 16,
+          endLine: 1,
+          endColumn: 18,
           message: messages.expectedBefore("==")
         },
         {
           line: 1,
-          column: 17,
+          column: 16,
+          endLine: 1,
+          endColumn: 18,
           message: messages.expectedAfter("==")
         }
       ]
@@ -2058,11 +2064,15 @@ testRule({
         {
           line: 1,
           column: 15,
+          endLine: 1,
+          endColumn: 17,
           message: messages.expectedBefore("==")
         },
         {
           line: 1,
-          column: 16,
+          column: 15,
+          endLine: 1,
+          endColumn: 17,
           message: messages.expectedAfter("==")
         }
       ]
@@ -2088,7 +2098,7 @@ testRule({
         },
         {
           line: 1,
-          column: 17,
+          column: 16,
           message: messages.expectedAfter("==")
         }
       ]
@@ -2104,7 +2114,7 @@ testRule({
         },
         {
           line: 1,
-          column: 16,
+          column: 15,
           message: messages.expectedAfter("==")
         }
       ]
diff --git a/src/utils/__tests__/parseFunctionArguments.js b/src/utils/__tests__/parseFunctionArguments.js
index 7623e1b..ae16df1 100644
--- a/src/utils/__tests__/parseFunctionArguments.js
+++ b/src/utils/__tests__/parseFunctionArguments.js
@@ -155,10 +155,10 @@ describe("mapToKeyValue", () => {
         { after: " ", before: "", sourceIndex: 12, type: "div", value: ":" },
         { sourceIndex: 14, type: "word", value: "40px" }
       ])
-    ).toEqual({ key: "$value", value: "40px" });
+    ).toEqual({ key: "$value", value: "40px", index: 6 });
     expect(
       mapToKeyValue([{ sourceIndex: 20, type: "word", value: "10px" }])
-    ).toEqual({ value: "10px" });
+    ).toEqual({ value: "10px", index: 20 });
   });
 });
 
@@ -178,7 +178,9 @@ describe("parseFunctionArguments", () => {
   it("parses number as the value", () => {
     expect(parseFunctionArguments("func(1)")).toEqual([
       {
-        value: "1"
+        value: "1",
+        index: 5,
+        endIndex: 6
       }
     ]);
   });
@@ -186,7 +188,9 @@ describe("parseFunctionArguments", () => {
   it("parses calculation as the value", () => {
     expect(parseFunctionArguments("func(30 * 25ms)")).toEqual([
       {
-        value: "30 * 25ms"
+        value: "30 * 25ms",
+        index: 5,
+        endIndex: 14
       }
     ]);
   });
@@ -194,10 +198,14 @@ describe("parseFunctionArguments", () => {
   it("parses multiple args", () => {
     expect(parseFunctionArguments("func(1, 2)")).toEqual([
       {
-        value: "1"
+        value: "1",
+        index: 5,
+        endIndex: 6
       },
       {
-        value: "2"
+        value: "2",
+        index: 8,
+        endIndex: 9
       }
     ]);
   });
@@ -205,10 +213,14 @@ describe("parseFunctionArguments", () => {
   it("parses trailing commas", () => {
     expect(parseFunctionArguments("func(1, 2,)")).toEqual([
       {
-        value: "1"
+        value: "1",
+        index: 5,
+        endIndex: 6
       },
       {
-        value: "2"
+        value: "2",
+        index: 8,
+        endIndex: 9
       }
     ]);
   });
@@ -217,7 +229,9 @@ describe("parseFunctionArguments", () => {
     expect(parseFunctionArguments("func($var: 1)")).toEqual([
       {
         key: "$var",
-        value: "1"
+        value: "1",
+        index: 5,
+        endIndex: 12
       }
     ]);
   });
@@ -230,7 +244,9 @@ describe("parseFunctionArguments", () => {
     ).toEqual([
       {
         key: "$foo",
-        value: 'url("data:image/svg+xml;charset=utf8,%3C")'
+        value: 'url("data:image/svg+xml;charset=utf8,%3C")',
+        index: 5,
+        endIndex: 53
       }
     ]);
   });
@@ -239,7 +255,9 @@ describe("parseFunctionArguments", () => {
     expect(parseFunctionArguments("reset($value: #{$other-value})")).toEqual([
       {
         key: "$value",
-        value: "#{$other-value}"
+        value: "#{$other-value}",
+        index: 6,
+        endIndex: 29
       }
     ]);
   });
@@ -248,7 +266,9 @@ describe("parseFunctionArguments", () => {
     expect(parseFunctionArguments("anim($duration: 30 * 25ms)")).toEqual([
       {
         key: "$duration",
-        value: "30 * 25ms"
+        value: "30 * 25ms",
+        index: 5,
+        endIndex: 25
       }
     ]);
   });
@@ -257,11 +277,15 @@ describe("parseFunctionArguments", () => {
     expect(parseFunctionArguments("func($var: 1, $foo: bar)")).toEqual([
       {
         key: "$var",
-        value: "1"
+        value: "1",
+        index: 5,
+        endIndex: 12
       },
       {
         key: `$foo`,
-        value: "bar"
+        value: "bar",
+        index: 14,
+        endIndex: 23
       }
     ]);
   });
@@ -274,15 +298,21 @@ describe("parseFunctionArguments", () => {
     ).toEqual([
       {
         key: "$value",
-        value: "40px"
+        value: "40px",
+        index: 6,
+        endIndex: 18
       },
       {
         key: "$second-value",
-        value: "10px"
+        value: "10px",
+        index: 20,
+        endIndex: 39
       },
       {
         key: "$color",
-        value: "'black'"
+        value: "'black'",
+        index: 41,
+        endIndex: 56
       }
     ]);
   });
@@ -294,19 +324,29 @@ describe("parseFunctionArguments", () => {
       )
     ).toEqual([
       {
-        value: "to left"
+        value: "to left",
+        index: 16,
+        endIndex: 23
       },
       {
-        value: "#333"
+        value: "#333",
+        index: 25,
+        endIndex: 29
       },
       {
-        value: "#333 50%"
+        value: "#333 50%",
+        index: 31,
+        endIndex: 39
       },
       {
-        value: "#eee 75%"
+        value: "#eee 75%",
+        index: 41,
+        endIndex: 49
       },
       {
-        value: "#333 75%"
+        value: "#333 75%",
+        index: 51,
+        endIndex: 59
       }
     ]);
   });