Repository: stylelint-scss/stylelint-scss
Pull request #580 (2021-12-05 11:07:56)

Consider the use of sass modules in at-each-key-value-single-line rule
The `at-each-key-value-single-line` rule does not report an error when set to `true` and using sass modules.

This should fail but it doesn't:
```scss
@use 'sass:map';

$font-weights: ("regular": 400, "medium": 500, "bold": 700);
@each $key in map.keys($font-weights) {
  $value: map.get($font-weights, $key);
}
```

This pull request changes the rule to take the use of sass modules into account.

kristerkari: Thanks! I'll try to review asap
stof: This should support cases where the module is imported with a different namespace than the default `map` to be complete

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c2c996..ea4cd2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,3 @@
-# 4.0.1
-
-- Fixed: `operator-no-unspaced` false positive when using a loop (#575).
-- Fixed: `operator-no-unspaced` don't check operators from data URIs (#574).
-- Updated: `parseSelector` function to match the one in stylelint repo (#567).
-- Fixed: `no-global-function-names` removed checks functions that cause warnings for global CSS functions (min, max, filter) (#566).
-
 # 4.0.0
 
 - Removed: `stylelint@13` support.
diff --git a/README.md b/README.md
index e0d5ff5..11317ce 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ Please also see the [example configs](./docs/examples/) for special cases.
 
 ### `@`-each
 
-- [`at-each-key-value-single-line`](./src/rules/at-each-key-value-single-line/README.md): This is a rule that checks for situations where users have done a loop using map-keys and grabbed the value for that key inside of the loop.
+- [`at-each-key-value-single-line`](./src/rules/at-each-key-value-single-line/README.md): This is a rule that checks for situations where users have done a loop using map-keys or map.keys and grabbed the value for that key inside of the loop.
 
 ### `@`-else
 
diff --git a/package-lock.json b/package-lock.json
index cce5795..5fd1749 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "stylelint-scss",
-  "version": "4.0.1",
+  "version": "4.0.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "stylelint-scss",
-      "version": "4.0.1",
+      "version": "4.0.0",
       "license": "MIT",
       "dependencies": {
         "lodash": "^4.17.21",
diff --git a/package.json b/package.json
index 975bca2..b1e114a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "stylelint-scss",
   "description": "A collection of SCSS specific rules for Stylelint",
-  "version": "4.0.1",
+  "version": "4.0.0",
   "author": "Krister Kari",
   "babel": {
     "presets": [
diff --git a/src/rules/at-each-key-value-single-line/README.md b/src/rules/at-each-key-value-single-line/README.md
index 0483d27..ead328e 100644
--- a/src/rules/at-each-key-value-single-line/README.md
+++ b/src/rules/at-each-key-value-single-line/README.md
@@ -36,6 +36,19 @@ $font-weights: (
 }
 ```
 
+```scss
+@use 'sass:map';
+
+$font-weights: (
+  "regular": 400,
+  "medium": 500,
+  "bold": 700
+);
+@each $key in map.keys($font-weights) {
+  $value: map.get($font-weights, $key);
+}
+```
+
 The following patterns are _not_ considered violations:
 
 ```scss
@@ -61,7 +74,32 @@ $other-weights: (
 ```
 
 ```scss
-$font-weights: ("regular": 400, "medium": 500, "bold": 700);
+@use 'sass:map';
+
+$font-weights: (
+  "regular": 400,
+  "medium": 500,
+  "bold": 700
+);
+$other-weights: (
+  "regular": 400,
+  "medium": 500,
+  "bold": 700
+);
+
+@each $key, $value in map.keys($font-weights) {
+  $value: map.get($other-weights, $key);
+}
+```
 
+```scss
+$font-weights: ("regular": 400, "medium": 500, "bold": 700);
 @each $key, $value in map-keys($font-weights) {...}
 ```
+
+```scss
+@use 'sass:map';
+
+$font-weights: ("regular": 400, "medium": 500, "bold": 700);
+@each $key, $value in map.keys($font-weights) {...}
+```
diff --git a/src/rules/at-each-key-value-single-line/index.js b/src/rules/at-each-key-value-single-line/index.js
index 834b68b..139d6bd 100644
--- a/src/rules/at-each-key-value-single-line/index.js
+++ b/src/rules/at-each-key-value-single-line/index.js
@@ -1,5 +1,5 @@
 import { utils } from "stylelint";
-import { namespace } from "../../utils";
+import { moduleNamespace, namespace } from "../../utils";
 
 export const ruleName = namespace("at-each-key-value-single-line");
 
@@ -18,6 +18,8 @@ export default function(primary) {
       return;
     }
 
+    const mapNamespace = moduleNamespace(root, "sass:map");
+
     root.walkAtRules("each", rule => {
       const parts = separateEachParams(rule.params);
 
@@ -27,7 +29,7 @@ export default function(primary) {
       }
 
       // If didn't call map-keys, return.
-      if (!didCallMapKeys(parts[1])) {
+      if (!didCallMapKeys(parts[1], mapNamespace)) {
         return;
       }
 
@@ -38,15 +40,15 @@ export default function(primary) {
           return;
         }
 
-        if (!didCallMapGet(innerDecl.value)) {
+        if (!didCallMapGet(innerDecl.value, mapNamespace)) {
           return;
         }
 
         // Check map_name + key_name match.
-        const map_get_parts = mapGetParameters(innerDecl.value);
+        const map_get_parts = mapGetParameters(innerDecl.value, mapNamespace);
 
         // Check map names match.
-        if (map_get_parts[0] !== mapName(parts[1])) {
+        if (map_get_parts[0] !== mapName(parts[1], mapNamespace)) {
           return;
         }
 
@@ -74,27 +76,38 @@ function separateEachParams(paramString) {
   return [parts[0].split(",").map(s => s.trim()), parts[1].trim()];
 }
 
-function didCallMapKeys(map_decl) {
-  return map_decl.match(/map-keys\(.*\)/);
+function didCallMapKeys(mapDecl, mapNamespace) {
+  const pattern = getNamespacedPattern("keys\\(.*\\)", mapNamespace);
+
+  return new RegExp(pattern).test(mapDecl);
 }
 
-function didCallMapGet(map_decl) {
-  return map_decl.match(/map-get\((.*),(.*)\)/);
+function didCallMapGet(mapDecl, mapNamespace) {
+  const pattern = getNamespacedPattern("get\\((.*),(.*)\\)", mapNamespace);
+
+  return new RegExp(pattern).test(mapDecl);
 }
 
-// Fetch the name of the map from a map-keys() call.
-function mapName(map_decl) {
-  if (didCallMapKeys(map_decl)) {
-    return map_decl.match(/map-keys\((.*)\)/)[1];
-  } else {
-    return map_decl;
+// Fetch the name of the map from a map-keys() or map.keys() call.
+function mapName(mapDecl, mapNamespace) {
+  if (didCallMapKeys(mapDecl, mapNamespace)) {
+    const pattern = getNamespacedPattern("keys\\((.*)\\)", mapNamespace);
+
+    return mapDecl.match(new RegExp(pattern))[1];
   }
+
+  return mapDecl;
 }
 
-// Returns the parameters of a map-get call
+// Returns the parameters of a map-get or map.get call
 // Returns [map variable, key_variable]
-function mapGetParameters(mapGetDecl) {
-  const parts = mapGetDecl.match(/map-get\((.*), ?(.*)\)/);
+function mapGetParameters(mapGetDecl, mapNamespace) {
+  const pattern = getNamespacedPattern("get\\((.*), ?(.*)\\)", mapNamespace);
+  const parts = mapGetDecl.match(new RegExp(pattern));
 
   return [parts[1], parts[2]];
 }
+
+function getNamespacedPattern(pattern, namespace) {
+  return namespace !== null ? `(?:${namespace}\\.|map-)${pattern}` : pattern;
+}
diff --git a/src/rules/operator-no-unspaced/index.js b/src/rules/operator-no-unspaced/index.js
index b33e6fe..397fcda 100644
--- a/src/rules/operator-no-unspaced/index.js
+++ b/src/rules/operator-no-unspaced/index.js
@@ -147,8 +147,6 @@ export function calculationOperatorSpaceChecker({ root, result, checker }) {
     return results;
   }
 
-  const dataURIRegex = /^url\(\s*['"]?data:.+;base64,.+['"]?\s*\)$/;
-
   root.walk(item => {
     if (item.prop === "unicode-range") {
       return;
@@ -158,9 +156,6 @@ export function calculationOperatorSpaceChecker({ root, result, checker }) {
 
     // Check a value (`10px` in `width: 10px;`)
     if (item.value !== undefined) {
-      if (dataURIRegex.test(item.value)) {
-        return results;
-      }
       results.push({
         source: item.value,
         operators: findOperators({
diff --git a/src/utils/index.js b/src/utils/index.js
index d67b92c..b634193 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -29,3 +29,4 @@ export { default as findOperators } from "./sassValueParser";
 export { default as whitespaceChecker } from "./whitespaceChecker";
 export { default as hasNestedSibling } from "./hasNestedSibling";
 export { default as isType } from "./isType";
+export { moduleNamespace } from "./moduleNamespace";
diff --git a/src/utils/moduleNamespace.js b/src/utils/moduleNamespace.js
new file mode 100644
index 0000000..6dc9398
--- /dev/null
+++ b/src/utils/moduleNamespace.js
@@ -0,0 +1,60 @@
+/**
+ * Returns the namespace of an imported module or `null` if the namespace is removed.
+ *
+ * Example: no import found, possibly using global function
+ * `@use "sass:color";`
+ * `moduleNamespace(root, 'sass:map')` returns 'map'
+ *
+ * Example: default namespace
+ * `@use "sass:map";`
+ * `moduleNamespace(root, 'sass:map')` returns `map`
+ *
+ * Example: custom namespace
+ * `@use "sass:map" as ns;`
+ * `moduleNamespace(root, 'sass:map')` returns 'ns'
+ *
+ * Example: no namespace
+ * `@use "sass:map" as *;`
+ * `moduleNamespace(root, 'sass:map')` returns `null`
+ *
+ * Have a look at the tests for more examples.
+ */
+export function moduleNamespace(root, module) {
+  let moduleNamespace = getDefaultNamespace(module);
+
+  root.walkAtRules("use", rule => {
+    const customNamespace = getCustomNamespace(module, rule);
+
+    switch (customNamespace) {
+      case null:
+        return;
+      case "*":
+        moduleNamespace = null;
+        return;
+      default:
+        moduleNamespace = customNamespace;
+    }
+  });
+
+  return moduleNamespace;
+}
+
+function getDefaultNamespace(module) {
+  return module.match(/([^/:]+)$/)[1].replace(/\.[^.]+$/, "");
+}
+
+function getCustomNamespace(module, rule) {
+  const ruleParamsParts = rule.params.split(
+    new RegExp(`["']${module}['"][ \n]*as`)
+  );
+
+  if (ruleParamsParts.length < 2) {
+    return null;
+  }
+
+  const secondRuleParamsPart = ruleParamsParts[1].trim();
+
+  return !secondRuleParamsPart.startsWith("*")
+    ? secondRuleParamsPart.split(" ")[0]
+    : "*";
+}
diff --git a/src/utils/parseSelector.js b/src/utils/parseSelector.js
index eb9cb2d..aa66a6b 100644
--- a/src/utils/parseSelector.js
+++ b/src/utils/parseSelector.js
@@ -2,7 +2,7 @@ import postCssSelectorParser from "postcss-selector-parser";
 
 export default function(selector, result, node, cb) {
   try {
-    return postCssSelectorParser(cb).processSync(selector);
+    postCssSelectorParser(cb).process(selector);
   } catch (e) {
     result.warn("Cannot parse selector", { node });
   }
diff --git a/src/utils/sassValueParser/index.js b/src/utils/sassValueParser/index.js
index 5508fd0..3d1d79c 100644
--- a/src/utils/sassValueParser/index.js
+++ b/src/utils/sassValueParser/index.js
@@ -358,11 +358,6 @@ function checkMinus(string, index) {
         return "sign";
       }
 
-      // e.g. `#{$i * -10}px`
-      if (isWhitespaceBefore && isNumberAfter_.is && isPrecedingOperator_) {
-        return "sign";
-      }
-
       return "op";
     }
 
@@ -731,9 +726,7 @@ function isStringBefore(before) {
   ) {
     result.is = true;
   } else if (
-    stringOpsClipped.search(
-      /(?:^|[/(){},: ])([a-zA-Z_][\w-]*|-+[a-zA-Z_][\w-]*)$/
-    ) !== -1
+    stringOpsClipped.search(/(?:^|[/(){},: ])([a-zA-Z_][\w-]*|-+[a-zA-Z_][\w-]*)$/) !== -1
   ) {
     // First pattern: a1, a1a, a-1,
     result.is = true;
@@ -749,10 +742,7 @@ function isStringAfter(after) {
   if (stringTrimmed[0] === '"' || stringTrimmed[0] === "'") return true;
 
   // e.g. `a1`, `a1a`, `a-1`, and even `--s323`
-  return (
-    stringTrimmed.search(/^([a-zA-Z_][\w-]*|-+[a-zA-Z_][\w-]*)(?:$|[)}, ])/) !==
-    -1
-  );
+  return stringTrimmed.search(/^([a-zA-Z_][\w-]*|-+[a-zA-Z_][\w-]*)(?:$|[)}, ])/) !== -1;
 }
 
 function isInterpolationAfter(after) {
@@ -939,9 +929,7 @@ function isHexColorAfter(after) {
 }
 
 function isHexColorBefore(before) {
-  return (
-    before.search(/(?:[/(){},+*%-\s]|^)#([\da-fA-F]{3}|[\da-fA-F]{6})$/) !== -1
-  );
+  return before.search(/(?:[/(){},+*%-\s]|^)#([\da-fA-F]{3}|[\da-fA-F]{6})$/) !== -1;
 }
 
 /**


Test patch:
diff --git a/src/rules/at-each-key-value-single-line/__tests__/index.js b/src/rules/at-each-key-value-single-line/__tests__/index.js
index 50cd20d..cf85d25 100644
--- a/src/rules/at-each-key-value-single-line/__tests__/index.js
+++ b/src/rules/at-each-key-value-single-line/__tests__/index.js
@@ -8,42 +8,248 @@ testRule({
   accept: [
     {
       code: `
-      $font-weights: ("regular": 400, "medium": 500, "bold": 700);
-      @each $key, $value in $font-weights {}
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in $font-weights {}
       `,
       description: "Proper map loop that gets both keys + values"
     },
     {
       code: `
-      $font-weights: ("regular": 400, "medium": 500, "bold": 700);
-      @each $key in map-keys($font-weights) {}
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {}
       `,
-      description: "Loop that just gets keys + has no need for values"
+      description:
+        "Loop that just gets keys + has no need for values when using global function"
     },
     {
       code: `
-       $font-weights: ("regular": 400, "medium": 500, "bold": 700);
-       $other-weights: ("regular": 400, "medium": 500, "bold": 700);
-
-       @each $key, $value in map-keys($font-weights) {
+        @use "sass:map";
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map.keys($font-weights) {}
+      `,
+      description:
+        "Loop that just gets keys + has no need for values when loading sass module with default namespace"
+    },
+    {
+      code: `
+        @use "sass:map";
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {}
+      `,
+      description:
+        "Loop that just gets keys + has no need for values when loading sass module with default namespace but using global function"
+    },
+    {
+      code: `
+        @use "sass:map" as *;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in keys($font-weights) {}
+      `,
+      description:
+        "Loop that just gets keys + has no need for values when loading sass module with no namespace"
+    },
+    {
+      code: `
+        @use "sass:map" as *;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {}
+      `,
+      description:
+        "Loop that just gets keys + has no need for values when loading sass module with no namespace but using global function"
+    },
+    {
+      code: `
+        @use "sass:map" as ns;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in ns.keys($font-weights) {}
+      `,
+      description:
+        "Loop that just gets keys + has no need for values when loading sass module with custom namespace"
+    },
+    {
+      code: `
+        @use "sass:map" as ns;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {}
+      `,
+      description:
+        "Loop that just gets keys + has no need for values when loading sass module with custom namespace but using global function"
+    },
+    {
+      code: `
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in map-keys($font-weights) {
+         $value: map-get($other-weights, $key);
+        }
+      `,
+      description:
+        "map-get pattern used with different hash than loop when using global function"
+    },
+    {
+      code: `
+        @use "sass:map";
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in map.keys($font-weights) {
+         $value: map.get($other-weights, $key);
+        }
+      `,
+      description:
+        "map.get pattern used with different hash than loop when loading sass module with default namespace"
+    },
+    {
+      code: `
+        @use "sass:map";
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in map-keys($font-weights) {
+         $value: map-get($other-weights, $key);
+        }
+      `,
+      description:
+        "map.get pattern used with different hash than loop when loading sass module with default namespace but using global function"
+    },
+    {
+      code: `
+        @use "sass:map" as *;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in keys($font-weights) {
+         $value: get($other-weights, $key);
+        }
+      `,
+      description:
+        "map.get pattern used with different hash than loop when loading sass module with no namespace"
+    },
+    {
+      code: `
+        @use "sass:map" as *;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in map-keys($font-weights) {
+         $value: map-get($other-weights, $key);
+        }
+      `,
+      description:
+        "map.get pattern used with different hash than loop when loading sass module with no namespace but using global function"
+    },
+    {
+      code: `
+        @use "sass:map" as ns;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in ns.keys($font-weights) {
+         $value: ns.get($other-weights, $key);
+        }
+      `,
+      description:
+        "map.get pattern used with different hash than loop when loading sass module with custom namespace"
+    },
+    {
+      code: `
+        @use "sass:map" as ns;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        $other-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key, $value in map-keys($font-weights) {
          $value: map-get($other-weights, $key);
-       }
+        }
       `,
-      description: "map-get pattern used with different hash than loop"
+      description:
+        "map.get pattern used with different hash than loop when loading sass module with custom namespace but using global function"
     }
   ],
 
   reject: [
     {
       code: `
-      $font-weights: ("regular": 400, "medium": 500, "bold": 700);
-      @each $key in map-keys($font-weights) {
-        $value: map-get($font-weights, $key);
-      }
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {
+          $value: map-get($font-weights, $key);
+        }
       `,
-      description: "Loop that gets keys + then grabs values inside the map",
+      description:
+        "Loop that gets keys + then grabs values inside the map when using global function",
       message: messages.expected,
       line: 3
+    },
+    {
+      code: `
+        @use "sass:map";
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map.keys($font-weights) {
+          $value: map.get($font-weights, $key);
+        }
+      `,
+      description:
+        "Loop that gets keys + then grabs values inside the map when loading sass module with default namespace",
+      message: messages.expected,
+      line: 4
+    },
+    {
+      code: `
+        @use "sass:map";
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {
+          $value: map-get($font-weights, $key);
+        }
+      `,
+      description:
+        "Loop that gets keys + then grabs values inside the map when loading sass module with default namespace but using global function",
+      message: messages.expected,
+      line: 4
+    },
+    {
+      code: `
+        @use "sass:map" as *;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in keys($font-weights) {
+          $value: get($font-weights, $key);
+        }
+      `,
+      description:
+        "Loop that gets keys + then grabs values inside the map when loading sass module with no namespace",
+      message: messages.expected,
+      line: 4
+    },
+    {
+      code: `
+        @use "sass:map" as *;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {
+          $value: map-get($font-weights, $key);
+        }
+      `,
+      description:
+        "Loop that gets keys + then grabs values inside the map when loading sass module with no namespace but using global function",
+      message: messages.expected,
+      line: 4
+    },
+    {
+      code: `
+        @use "sass:map" as ns;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in ns.keys($font-weights) {
+          $value: ns.get($font-weights, $key);
+        }
+      `,
+      description:
+        "Loop that gets keys + then grabs values inside the map when loading sass module with custom namespace",
+      message: messages.expected,
+      line: 4
+    },
+    {
+      code: `
+        @use "sass:map" as ns;
+        $font-weights: ("regular": 400, "medium": 500, "bold": 700);
+        @each $key in map-keys($font-weights) {
+          $value: map-get($font-weights, $key);
+        }
+      `,
+      description:
+        "Loop that gets keys + then grabs values inside the map when loading sass module with custom namespace but using global function",
+      message: messages.expected,
+      line: 4
     }
   ]
 });
diff --git a/src/rules/operator-no-unspaced/__tests__/index.js b/src/rules/operator-no-unspaced/__tests__/index.js
index f346fc8..00a936d 100644
--- a/src/rules/operator-no-unspaced/__tests__/index.js
+++ b/src/rules/operator-no-unspaced/__tests__/index.js
@@ -40,19 +40,6 @@ testRule({
       code: ":root { --foo: '{{}}'; }",
       description: "Custom property"
     },
-    {
-      code: `
-      @mixin test {
-        @for $i from 0 through 10 {
-          &:nth-child(#{$i + 1}) {
-            top: #{$i * -10}px;
-            transform: scale(#{(10 - $i) * 0.1}) !important;
-          }
-        }
-      }
-      `,
-      description: "issue #561"
-    },
     {
       code: `
       @font-face {
@@ -88,10 +75,6 @@ testRule({
       div { background-image: url(https://99-0a.x.y.rackcdn.com/z.jpg); }
       `,
       description: "Op +: background-image with url that has a hyphen"
-    },
-    {
-      code: `div { background: url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0Ljk1IDEwIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2ZmZjt9LmNscy0ye2ZpbGw6IzQ0NDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPmFycm93czwvdGl0bGU+PHJlY3QgY2xhc3M9ImNscy0xIiB3aWR0aD0iNC45NSIgaGVpZ2h0PSIxMCIvPjxwb2x5Z29uIGNsYXNzPSJjbHMtMiIgcG9pbnRzPSIxLjQxIDQuNjcgMi40OCAzLjE4IDMuNTQgNC42NyAxLjQxIDQuNjciLz48cG9seWdvbiBjbGFzcz0iY2xzLTIiIHBvaW50cz0iMy41NCA1LjMzIDIuNDggNi44MiAxLjQxIDUuMzMgMy41NCA1LjMzIi8+PC9zdmc+) }`,
-      description: "background image with base64 data, issue #561"
     }
   ],
 
diff --git a/src/utils/__tests__/moduleNamespace.test.js b/src/utils/__tests__/moduleNamespace.test.js
new file mode 100644
index 0000000..b5e358d
--- /dev/null
+++ b/src/utils/__tests__/moduleNamespace.test.js
@@ -0,0 +1,179 @@
+import postcss from "postcss";
+import { moduleNamespace } from "../moduleNamespace";
+
+describe("moduleNamespace", () => {
+  it("should return 'map' when module import was not found", () => {
+    expect(getModuleNamespace("@use 'sass:color';", "sass:map")).toBe("map");
+  });
+
+  it("should return 'map' on default namespace", () => {
+    expect(getModuleNamespace("@use 'sass:map';", "sass:map")).toBe("map");
+  });
+
+  it("should return 'map' on default namespace using 'with'", () => {
+    expect(getModuleNamespace("@use 'sass:map' with ();", "sass:map")).toBe(
+      "map"
+    );
+  });
+
+  it("should return 'map' on default namespace using 'with' (with multiple spaces)", () => {
+    expect(getModuleNamespace("@use  'sass:map'  with  ();", "sass:map")).toBe(
+      "map"
+    );
+  });
+
+  it("should return null on no namespace", () => {
+    expect(getModuleNamespace("@use 'sass:map' as *;", "sass:map")).toBeNull();
+  });
+
+  it("should return null on no namespace using 'with'", () => {
+    expect(
+      getModuleNamespace("@use 'sass:map' as * with ();", "sass:map")
+    ).toBeNull();
+  });
+
+  it("should return null on no namespace using 'with' (with multiple spaces)", () => {
+    expect(
+      getModuleNamespace("@use  'sass:map'  as  *  with  ();", "sass:map")
+    ).toBeNull();
+  });
+
+  it("should return null on no namespace using 'with' (without spaces)", () => {
+    expect(
+      getModuleNamespace("@use 'sass:map'as*with ();", "sass:map")
+    ).toBeNull();
+  });
+
+  it("should return null on no namespace using 'with' (without spaces, with line breaks)", () => {
+    expect(
+      getModuleNamespace("@use\n'sass:map'\nas\n*\nwith\n();", "sass:map")
+    ).toBeNull();
+  });
+
+  it("should return null on no namespace using 'with' (with multiple spaces, with line breaks)", () => {
+    expect(
+      getModuleNamespace(
+        "@use  \n  'sass:map'  \n  as  \n  *  \n  with  \n  ();",
+        "sass:map"
+      )
+    ).toBeNull();
+  });
+
+  it("should return 'ns' on custom namespace", () => {
+    expect(getModuleNamespace("@use 'sass:map' as ns;", "sass:map")).toBe("ns");
+  });
+
+  it("should return 'ns' on custom namespace using 'with'", () => {
+    expect(
+      getModuleNamespace("@use 'sass:map' as ns with ();", "sass:map")
+    ).toBe("ns");
+  });
+
+  it("should return 'ns' on custom namespace using 'with' (with multiple spaces)", () => {
+    expect(
+      getModuleNamespace("@use  'sass:map'  as  ns  with  ();", "sass:map")
+    ).toBe("ns");
+  });
+
+  it("should return 'ns' on custom namespace using 'with' (with multiple spaces, with line breaks)", () => {
+    expect(
+      getModuleNamespace(
+        "@use  \n \n  'sass:map'  \n \n  as  \n \n  ns  \n \n  with  \n \n  ();",
+        "sass:map"
+      )
+    ).toBe("ns");
+  });
+
+  it("should return 'custom-module' when module import is 'custom-module'", () => {
+    expect(getModuleNamespace("@use 'custom-module';", "custom-module")).toBe(
+      "custom-module"
+    );
+  });
+
+  it("should return 'custom-module' when module import is 'custom-module' using 'with'", () => {
+    expect(
+      getModuleNamespace("@use 'custom-module' with ();", "custom-module")
+    ).toBe("custom-module");
+  });
+
+  it("should return 'custom-module' when module import is 'custom-module.scss'", () => {
+    expect(
+      getModuleNamespace("@use 'custom-module.scss';", "custom-module.scss")
+    ).toBe("custom-module");
+  });
+
+  it("should return 'custom-module' when module import is 'custom-module.scss' using 'with'", () => {
+    expect(
+      getModuleNamespace(
+        "@use 'custom-module.scss' with ();",
+        "custom-module.scss"
+      )
+    ).toBe("custom-module");
+  });
+
+  it("should return 'custom-module' when module import is 'path/to/custom-module'", () => {
+    expect(
+      getModuleNamespace(
+        "@use 'path/to/custom-module';",
+        "path/to/custom-module"
+      )
+    ).toBe("custom-module");
+  });
+
+  it("should return 'custom-module' when module import is 'path/to/custom-module' using with ()", () => {
+    expect(
+      getModuleNamespace(
+        "@use 'path/to/custom-module' with ();",
+        "path/to/custom-module"
+      )
+    ).toBe("custom-module");
+  });
+
+  it("should return 'custom-module' when module import is 'path/to/custom-module.scss'", () => {
+    expect(
+      getModuleNamespace(
+        "@use 'path/to/custom-module.scss';",
+        "path/to/custom-module.scss"
+      )
+    ).toBe("custom-module");
+  });
+
+  it("should return 'custom-module' when module import is 'path/to/custom-module.scss' using 'with'", () => {
+    expect(
+      getModuleNamespace(
+        "@use 'path/to/custom-module.scss' with ();",
+        "path/to/custom-module.scss"
+      )
+    ).toBe("custom-module");
+  });
+
+  it("should return `null` on no namespace when module import is 'custom-module'", () => {
+    expect(
+      getModuleNamespace("@use 'custom-module' as *;", "custom-module")
+    ).toBeNull();
+  });
+
+  it("should return `null` on no namespace when module import is 'custom-module' using 'with'", () => {
+    expect(
+      getModuleNamespace("@use 'custom-module' as * with ();", "custom-module")
+    ).toBeNull();
+  });
+
+  it("should return 'ns' on custom namespace when module import is 'custom-module'", () => {
+    expect(
+      getModuleNamespace("@use 'custom-module' as ns;", "custom-module")
+    ).toBe("ns");
+  });
+
+  it("should return 'ns' on custom namespace when module import is 'custom-module' using 'with'", () => {
+    expect(
+      getModuleNamespace("@use 'custom-module' as ns with ();", "custom-module")
+    ).toBe("ns");
+  });
+});
+
+function getModuleNamespace(css, module) {
+  const root = postcss.parse(css);
+
+  return moduleNamespace(root, module);
+}