Repository: stylelint-scss/stylelint-scss (js)

Issue:
`scss/function-no-unknown` doesn't work with interpolation
Sass requires interpolation when assigning a function's output to a css var: https://sass-lang.com/documentation/breaking-changes/css-vars

e.g. one has to do:
```scss
--background-mark-yellow: #{color.adjust(
    $mdn-color-light-theme-yellow-30,
    $alpha: -0.6
  )};
```

rather than:
```scss
--background-mark-yellow: color.adjust(
  $mdn-color-light-theme-yellow-30,
  $alpha: -0.6
);
```

however, `scss/function-no-unknown` throws an error when using interpolation:
```
✖  Unexpected unknown function "#{color.adjust"  scss/function-no-unknown
```

Pull request:
function-no-unknown: handle functions inside interpolation
Fixes #817

Patch:
diff --git a/src/rules/function-no-unknown/index.js b/src/rules/function-no-unknown/index.js
index 2563ec3..a46b9c3 100644
--- a/src/rules/function-no-unknown/index.js
+++ b/src/rules/function-no-unknown/index.js
@@ -6,6 +6,7 @@ const { ALL_FUNCTIONS } = require("../../utils/functions");
 const namespace = require("../../utils/namespace");
 const { isRegExp, isString } = require("../../utils/validateTypes");
 const ruleUrl = require("../../utils/ruleUrl");
+const hasInterpolation = require("../../utils/hasInterpolation");
 
 const ruleToCheckAgainst = "function-no-unknown";
 
@@ -123,12 +124,17 @@ function rule(primaryOption, secondaryOptions) {
     if (namespaceWarnings.size === 0) {
       root.walkDecls(decl => {
         valueParser(decl.value).walk(valueNode => {
-          const { type, value: funcName } = valueNode;
+          const { type, value } = valueNode;
 
-          if (type !== "function" || funcName.trim() === "") return;
+          if (type !== "function" || value.trim() === "") return;
+
+          const interpolationRegex = /^#{/;
+          const funcName = value.replace(interpolationRegex, "");
 
           const namespace = extractNamespaceFromFunction(funcName);
-          if (!namespace) return;
+
+          if (!namespace && !hasInterpolation(decl.value)) return;
+
           if (atUseNamespaces.has(namespace)) return;
 
           if (ignoreFunctionsAsSet.has(funcName)) return;
diff --git a/src/utils/hasLessInterpolation.js b/src/utils/hasLessInterpolation.js
index bb4e82f..0e97d4b 100644
--- a/src/utils/hasLessInterpolation.js
+++ b/src/utils/hasLessInterpolation.js
@@ -7,5 +7,5 @@
  * @return {boolean} If `true`, a string has less interpolation
  */
 module.exports = function (string) {
-  return /@{.+?}/.test(string);
+  return /@{[\s\S]*?}/.test(string);
 };
diff --git a/src/utils/hasPsvInterpolation.js b/src/utils/hasPsvInterpolation.js
index 95a524c..05d88ed 100644
--- a/src/utils/hasPsvInterpolation.js
+++ b/src/utils/hasPsvInterpolation.js
@@ -6,5 +6,5 @@
  * @param {string} string
  */
 module.exports = function (string) {
-  return /\$\(.+?\)/.test(string);
+  return /\$\([\s\S]*?\)/.test(string);
 };
diff --git a/src/utils/hasScssInterpolation.js b/src/utils/hasScssInterpolation.js
index 58ed69a..8d22c47 100644
--- a/src/utils/hasScssInterpolation.js
+++ b/src/utils/hasScssInterpolation.js
@@ -6,5 +6,5 @@
  * @param {string} string
  */
 module.exports = function (string) {
-  return /#{.+?}/.test(string);
+  return /#{[\s\S]*?}/.test(string);
 };


Test patch:
diff --git a/src/rules/function-no-unknown/__tests__/index.js b/src/rules/function-no-unknown/__tests__/index.js
index 52acc62..342b655 100644
--- a/src/rules/function-no-unknown/__tests__/index.js
+++ b/src/rules/function-no-unknown/__tests__/index.js
@@ -97,6 +97,15 @@ testRule({
       $str-index: string.index('string', 'i');
       `,
       description: "@use built-in function."
+    },
+    {
+      code: `
+      --background-mark-yellow: #{color.adjust(
+        $mdn-color-light-theme-yellow-30,
+        $alpha: -0.6
+      )};
+      `,
+      description: "built-in function inside interpolation, issue #817"
     }
   ],
 
@@ -151,6 +160,18 @@ testRule({
       line: 5,
       column: 16,
       description: "@use without a namespace"
+    },
+    {
+      code: `
+      --background-mark-yellow: #{some-fn(
+        $mdn-color-light-theme-yellow-30,
+        $alpha: -0.6
+      )};
+      `,
+      description: "unknown function inside interpolation, issue #817",
+      message: messages.rejected("some-fn"),
+      line: 2,
+      column: 35
     }
   ]
 });
@@ -172,6 +193,15 @@ testRule({
     },
     {
       code: "a { color: bar(1); }"
+    },
+    {
+      code: `
+      --background-mark-yellow: #{bar(
+        $mdn-color-light-theme-yellow-30,
+        $alpha: -0.6
+      )};
+      `,
+      description: "ignored function inside interpolation, issue #817"
     }
   ],
 
diff --git a/src/utils/__tests__/hasInterpolation.test.js b/src/utils/__tests__/hasInterpolation.test.js
index 553ea00..7ad1b7c 100644
--- a/src/utils/__tests__/hasInterpolation.test.js
+++ b/src/utils/__tests__/hasInterpolation.test.js
@@ -6,7 +6,31 @@ it("hasInterpolation", () => {
   expect(hasInterpolation("(min-width#{$value}: 10px)")).toBeTruthy();
   expect(hasInterpolation("(@{value}min-width : 10px)")).toBeTruthy();
   expect(hasInterpolation("#{$Attr}-color")).toBeTruthy();
+  expect(
+    hasInterpolation(`
+    #{color.adjust(
+      $mdn-color-light-theme-yellow-30,
+      $alpha: -0.6
+    )}
+    `)
+  ).toBe(true);
   expect(hasInterpolation("@{Attr}-color")).toBeTruthy();
+  expect(
+    hasInterpolation(`
+    @{color.adjust(
+      $mdn-color-light-theme-yellow-30,
+      $alpha: -0.6
+    )}
+  `)
+  ).toBe(true);
+  expect(
+    hasInterpolation(`
+    $(color.adjust(
+      $mdn-color-light-theme-yellow-30,
+      $alpha: -0.6
+    ))
+  `)
+  ).toBe(true);
   expect(hasInterpolation("#{50% - $n}")).toBeTruthy();
   expect(hasInterpolation(".n-#{$n}")).toBeTruthy();
   expect(hasInterpolation(":n-#{$n}")).toBeTruthy();